Quarto 项目渲染输出文件夹的Robocopy镜像同步工作流

避免file.copy简单复制形成残留文件,同时保留上传工具Resilio Sync的状态文件夹

Quarto
Robocopy
Mirror
Sync
PowerShell
Author

huhuaping

Published

May 15, 2026

本文总结在Quarto 项目仓库中采用的 「渲染输出目录 → 上传 staging 目录」 工作流:用 Windows Robocopy镜像(/MIR),并用 排除目录(/XD) 保留上传工具的状态文件夹;通过 R + PowerShell + .Renviron 统一配置,便于单项目或跨项目复用。

1. 要解决什么问题

历史工作流:在每次 quarto render 完成后,将渲染输出目录 _book 复制一份到_book_sync。然后,使用服务器上传工具Resilio Sync,上传_book_sync文件夹到云端网站服务服务器。过去,我会使用post-render钩子,调用R/copy-post-sync-old.R脚本(查看scripts/copy-post-sync-old.R),其中R脚本只是简单地使用fs包的file.copy函数,将_book文件夹复制到_book_sync文件夹。

问题

现象 原因
staging 里总有「删不掉的旧 HTML」 仅用「复制并覆盖」不会删除目标侧多余文件
上传工具在 staging 下放 .sync 等缓存 镜像时不能把这些目录删掉或覆盖成空

为什么不能直接使用Resilio Sync上传_book文件夹到云端网站服务服务器?因为_book文件夹中包含了很多临时文件和缓存文件,这些文件在每次quarto render完成后都会被删除,所以不能直接使用Resilio Sync上传_book文件夹到云端网站服务服务器。

新工作流思路:使用Robocopy /MIR镜像功能,将_book文件夹镜像到_book_sync文件夹。Robocopy 的 /MIR 功能会删除_book_sync文件夹中_book文件夹中不存在的文件。同时,Robocopy 的 /XD 功能会排除_book_sync文件夹中上传工具产生的同步状态目录文件(例如.sync.well-known.rclone-cache)等文件。这样,_book_sync文件夹中只会保留_book文件夹中存在的文件,并且不会保留上传工具产生的同步状态目录文件。这样,就可以使得云端网站服务服务器上的网站内容与本地渲染输出保持一致。

2. Quarto 项目仓库涉及的文件

文件 作用
_quarto.yml quarto yaml文件,定义post-render 调用 copy-post-sync.R脚本,在每次 quarto render 完成后执行文件同步。
R/copy-post-sync.R 解析路径与环境变量,调用 robocopy,校验退出码与路径安全
scripts/mirror-book-sync.ps1 不跑完整 render 时,在命令行手动执行同一逻辑
.Renviron 提供环境变量(源目录、目标目录、排除目录),便于跨项目复用

3. Robocopy 要点(Windows)

  • /MIR:等价于 /E + /PURGE,目标与源对齐;慎用,务必确认源、目标路径正确。
  • 退出码0–7 表示成功(含「有文件被复制/删除」等);>7 为失败。R 与 PS1 中均按 > 7 判失败。
  • 日志/NFL /NDL /NJH /NJS 用于减少刷屏,可按需去掉以便排查。

4. 环境变量约定

.Renviron(或 Shell / CI 中预先 export / $env:)中设置;未设置时使用下表默认值。

变量 含义 默认
BOOK_MIRROR_FROM 源目录(一般为 Quarto output-dir,如 _book <项目根>/_book
BOOK_MIRROR_TO 目标目录(上传工具读写的 staging) <项目根>/_book_sync2
BOOK_MIRROR_EXCLUDE_DIRS 逗号分隔的目录名,映射为多个 /XD .sync

路径解析规则(R 脚本)

  • 形如 D:\...\\server\share\... 的字符串视为绝对路径(经 path.expand)。
  • 否则视为相对当前 Quarto 项目根here::here()),便于写 ../other-repo/_book 等。

Quarto 与 .Renviron:post-render 子进程未必按交互式 R 的惯例加载项目 .Renviron,因此 R/copy-post-sync.R 在开头对 here::here(".Renviron") 显式 readRenviron()(若文件存在),保证 render 后镜像能读到项目级变量。

5. 路径安全(两脚本一致)

在调用 robocopy 前会拒绝以下情况,避免 /MIR 误删大范围目录:

  • 源与目标为同一路径(Windows 下大小写不敏感比较);
  • 一方位于另一方目录树内部(嵌套)。

跨项目镜像时,应保证 staging 不要放在 _book 内部,也不要把 _book 指到 staging 的子路径里。

6. 排除目录与空变量

  • BOOK_MIRROR_EXCLUDE_DIRS 拆成多个 /XD;若解析后没有任何有效名称,两脚本都会回退为仅排除 .sync,避免误把上传状态目录纳入 purge。

7. 使用方式速查

自动(推荐):项目根执行 quarto render(或触发完整 render 的流程),post-render 会运行 R 脚本。

仅镜像(PowerShell),在项目根:

powershell -File scripts/mirror-book-sync.ps1

可选参数(优先于环境变量):

.\scripts\mirror-book-sync.ps1 -From "D:\a\_book" -To "D:\b\_book_sync2"

跨项目示例(先设环境变量再 render 或跑 PS1):

$env:BOOK_MIRROR_FROM = "D:\github\projA\_book"
$env:BOOK_MIRROR_TO   = "D:\github\projB\_book_sync2"
quarto render   # 在含 post-render 的项目根执行;路径以实际为准

更稳妥的做法是:在 实际执行 mirror 的项目 里配置 .Renviron,或使用 -From / -To 显式传路径。

R 内手动

source("R/copy-post-sync.R")

8. 迁移到其他 Quarto Book 项目的清单

8.1 推荐:从 harness 一键安装(Windows)

在已克隆的 harness 仓库根执行(将路径换成你的书项目根):

.\scripts\powershell\Install-Harness.ps1 -InstallProfile quarto-sync -ProjectRoot "D:\path\to\your-book"

仅预览不写盘时加 -NoDeploy。合并 _quarto.yml.Renviron 等后续步骤见 Harness 安装与分发说明scripts/quarto-sync 目录说明

8.2 手动复制清单

  1. 复制 R/copy-post-sync.Rscripts/mirror-book-sync.ps1.Renviron.example
  2. _quarto.ymlproject 下增加 post-render: - R/copy-post-sync.R(路径与工程结构一致即可)。
  3. .Renviron 加入 .gitignore;新环境从 .Renviron.example 复制为 .Renviron 并按机子路径修改。
  4. 确认项目依赖 herecopy-post-sync.R 使用 here::here());若不用 here,可改为 normalizePath("..")Sys.getenv("QUARTO_PROJECT_DIR") 等,与 Quarto 文档对齐。
  5. 根据实际上传工具状态目录名,调整 BOOK_MIRROR_EXCLUDE_DIRS(不仅限于 .sync)。

9. 局限与注意

  • 本方案针对 Windows + Robocopy;Linux/macOS 需改用 rsync --delete 等并对齐退出码语义。
  • /XD目录名匹配,若需按路径级排除需查阅 Robocopy 文档使用其他开关组合。
  • 镜像会删除目标侧「源中不存在」的文件(被 /XD 排除的目录名除外);首次接入前建议备份 staging

与仓库内实现保持同步时,以 R/copy-post-sync.Rscripts/mirror-book-sync.ps1.Renviron.example 的源码为准。