内置资源同步流程
resources/design-studio-builtin/ 下的 600+ 文件(Skill / Design System / Craft / Prompt 模板)不入 git。它们按 commit pin 从上游 nexu-io/open-design 拉取,落到本地后由首次启动时的 materializer 拷到用户目录。
为什么这么做:上游内容多且更新频繁,全部入库会让 elftia 的 git 历史噪声很大、合并冲突频繁。pin 一个 commit 既保证可重复构建,又把"升级内置素材"变成一次性的 lock 文件 bump。
涉及到的文件
| 路径 | 作用 | 入 git? |
|---|---|---|
resources/design-studio-builtin.lock.json | pin 文件 —— { repo, commit, subdirs } | ✅ 唯一入 git 的 |
resources/design-studio-builtin/ | 同步落盘的内容(600+ 文件) | ❌ gitignored |
resources/design-studio-builtin/.synced.json | marker —— 记录上次成功同步的 commit,命中则 skip | ❌(在被 ignore 的目录内) |
resources/design-studio-builtin/NOTICE | 自动生成的第三方归属声明 | ❌ |
scripts/sync-design-studio.mjs | 同步脚本(按 lock 拉取) | ✅ |
scripts/update-design-studio.mjs | 自动升级脚本 —— 拉取上游 HEAD + bump lock + sync + bump FLAG_FILENAME 一键完成 | ✅ |
packages/desktop/app/main/services/content/workspace/design/builtin/index.ts | 首次启动 materializer + FLAG_FILENAME | ✅ |
触发时机
| 触发 | 命令 / 钩子 | 模式 |
|---|---|---|
npm install | postinstall 钩子 | --soft(网络失败不阻断安装) |
npm run setup / setup:native | 手动初始化 | --soft |
npm run build:electron / build:official / build:steam | 在 electron-vite build 与 electron-builder 之间 | 严格模式(失败即 fail build) |
npm run sync:design | 手动执行 | 严格模式 |
npm run sync:design -- --force | 手动执行(强制重拉) | 严格模式 + 跳过 marker |
npm run sync:design:update | 一键升级到上游 HEAD(推荐) | 见下文「升级到上游最新 commit」 |
marker 命中({repo, commit, subdirs} 与 lock 完全一致)时直接 skip,输出 [sync:design] already synced @ <repo>@<sha7> — skipping (use --force)。这是正常状态,不要误以为出错。
同步脚本做了什么
scripts/sync-design-studio.mjs 主流程:
- 读
resources/design-studio-builtin.lock.json,校验 schema(必有repo/commit/subdirs[]) - 比对本地
.synced.jsonmarker —— 三字段全等且无--force则 skip 退出 - 拉 tarball:
- 公开仓库 →
https://codeload.github.com/<repo>/tar.gz/<commit> - 设置
GITHUB_TOKEN/GH_TOKEN环境变量后 →https://api.github.com/repos/<repo>/tarball/<commit>(私有仓库走这条)
- 公开仓库 →
- 用
decompress解压,strip: 1去掉外层包名目录,filter仅保留 lock 里列的subdirs - 解压前先
fs.rm(subdir, { recursive: true, force: true })清掉每个目标 subdir,避免上游删文件后本地还残留旧文件 - 写 marker
.synced.json - 写
NOTICE—— 自动生成第三方归属(Apache-2.0)
decompress 4.x 跨平台坑(不要破坏)
strip 会在 filter 之前应用,但 Windows 上 filter 收到的路径已被规范化为反斜杠。脚本里的 filter 用 file.path.split(/[\\/]/)[0] 同时切两种分隔符 —— 只切 / 在 Linux 上 600+ 文件全过、Windows 上 0 文件过,且失败模式是静默的(只输出空目录),调试很痛苦。改这段时务必两端都验。
升级到上游最新 commit
推荐路径:一键自动化
npm run sync:design:update
scripts/update-design-studio.mjs 把"升级"流程的三件事打成一个动作:
GET https://api.github.com/repos/<repo>/commits/HEAD—— 解析上游默认分支 HEAD 的 SHA- 如果与 lock 里的
commit相同 → logalready up to date — nothing to do后退出 0 - 否则正则替换 lock 文件的
commit字段(保留原格式),spawnsync-design-studio.mjs拉取并解压;sync 失败会自动回滚 lock,不会留下一个指向未同步 commit 的脏指针 - 用上游新 commit 的 short-sha(前 7 位)regex-rewrite
services/content/workspace/design/builtin/index.ts的FLAG_FILENAME常量为.elftia-builtin-materialized-<short-sha>.json—— 老用户<userData>里的旧 flag 文件名不再匹配,下次启动重新 materialize 新内容 - 打印
git diff/git commit提示
提交时只会改这两个文件:lock.json 和 builtin/index.ts。
Flag 命名约定:FLAG_FILENAME 嵌入上游 commit 的 short-sha(如 .elftia-builtin-materialized-83ddf76.json)。原来用整数版本号 v1/v2/...,已迁到 short-sha 格式 —— 切 commit 自然换名,不必维护一个手工版本号。
Dry-run:
npm run sync:design:update -- --dry-run
只打印 SHA 差异,不动任何文件。CI 上可以用来检测"是否有上游新版可升"。
Auth:未设 GITHUB_TOKEN 时走 GitHub 公开 API,未授权请求 60 req/hr。CI 里设 GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} 提到 5000 req/hr。
备选路径:手动三步
只在自动脚本异常、或想 pin 到一个非默认分支 / 历史 commit 时用:
-
改 lock:
resources/design-studio-builtin.lock.json的commit字段写完整 40 字符 SHA(不要短哈希 —— 脚本做字符串精确比对) -
跑 sync:
npm run sync:designcommit不匹配 marker 时会自动重拉,不需要--force。--force只在 marker 与 lock 一致但本地目录损坏想强制重拉时用。 -
Bump
FLAG_FILENAME:编辑packages/desktop/app/main/services/content/workspace/design/builtin/index.ts,把常量改成新 commit 的 short-sha:const FLAG_FILENAME = '.elftia-builtin-materialized-<新 short-sha>.json';这一步是关键 —— 已装 elftia 的用户的
<userData>/elftia/.elftia-builtin-materialized-<旧>.jsonflag 不再匹配,下次启动才会触发 re-materialize。不 bump 的话只有全新安装的用户能拿到新内容,老用户继续看旧内容。
验证
# 删本地 marker 强制重跑验证
rm resources/design-studio-builtin/.synced.json
npm run sync:design
# 看新文件数 / NOTICE 里的日期
cat resources/design-studio-builtin/.synced.json
cat resources/design-studio-builtin/NOTICE | head -10
常见操作
| 场景 | 命令 |
|---|---|
| 升级到上游最新 commit(含 flag bump) | npm run sync:design:update |
| 检测是否有上游新版可升(不动文件) | npm run sync:design:update -- --dry-run |
| 看本地是否已同步 / 同步到哪个 commit | cat resources/design-studio-builtin/.synced.json |
| 强制重拉一次(目录损坏 / 怀疑解压不全) | npm run sync:design -- --force |
| 查上游 main 最新 SHA | gh api repos/nexu-io/open-design/commits/main --jq .sha |
| 临时跳过同步(CI 调试用) | 删除 lock 文件 → 脚本 log "lock file missing, skipping" 后退出 |
| 用私有 fork 替代上游 | 改 lock 的 repo 字段,导出 GITHUB_TOKEN 后跑 sync |
故障排查
| 现象 | 原因 / 解决 |
|---|---|
[sync:design] already synced @ ... — skipping (use --force) | 正常,marker 命中。要重拉加 -- --force |
HTTP 404 ... (private repo? set GITHUB_TOKEN) | lock 里的 repo 是私有的或 commit 不存在,导出 GITHUB_TOKEN 或核对 SHA |
no files matched subdirs [...] | subdirs 里写的目录在那个 commit 上不存在(上游做了重命名 / 重构),核对该 commit 上的目录布局 |
| Windows 上同步成功但产物目录是空的 | decompress 的 filter 路径分隔符问题。不要把脚本里 split(/[\\/]/) 改成 split('/') |
| 用户升级后看不到新内置 Skill | 忘了 bump FLAG_FILENAME,老用户的旧 flag 还在 → 跳过 materialize。sync:design:update 会自动 bump,手动改 lock 的话别忘了第 3 步 |
sync:design:update 报 HTTP 403 ... rate-limited? | GitHub 未授权 API 限 60 req/hr。导出 GITHUB_TOKEN 后重跑 |
sync:design:update 同步成功但 flag 没改 | 检查 services/content/workspace/design/builtin/index.ts 里的 FLAG_FILENAME 是否被改名 —— 脚本用正则 const FLAG_FILENAME = '\.elftia-builtin-materialized-...\.json'; 定位,重命名后会失败 |
| CI 拉 tarball 太慢 / 流量超标 | 用 actions/cache 把 resources/design-studio-builtin/ 按 lock 文件 hash 做 key 缓存,命中时跳过下载 |
相关文件 / 模块
scripts/sync-design-studio.mjs—— 按 lock 拉取 + 解压scripts/update-design-studio.mjs—— 一键升级到上游 HEADresources/design-studio-builtin.lock.jsonpackages/desktop/app/main/services/content/workspace/design/builtin/index.ts——FLAG_FILENAME、materializeBuiltinResources、isBuiltinMaterialized- Design Studio 架构概览