Skip to main content

内置资源同步流程

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.jsonpin 文件 —— { repo, commit, subdirs }✅ 唯一入 git 的
resources/design-studio-builtin/同步落盘的内容(600+ 文件)❌ gitignored
resources/design-studio-builtin/.synced.jsonmarker —— 记录上次成功同步的 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 installpostinstall 钩子--soft(网络失败不阻断安装)
npm run setup / setup:native手动初始化--soft
npm run build:electron / build:official / build:steamelectron-vite buildelectron-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 主流程:

  1. resources/design-studio-builtin.lock.json,校验 schema(必有 repo / commit / subdirs[]
  2. 比对本地 .synced.json marker —— 三字段全等且无 --force 则 skip 退出
  3. 拉 tarball:
    • 公开仓库 → https://codeload.github.com/<repo>/tar.gz/<commit>
    • 设置 GITHUB_TOKEN / GH_TOKEN 环境变量后 → https://api.github.com/repos/<repo>/tarball/<commit>(私有仓库走这条)
  4. decompress 解压,strip: 1 去掉外层包名目录,filter 仅保留 lock 里列的 subdirs
  5. 解压前先 fs.rm(subdir, { recursive: true, force: true }) 清掉每个目标 subdir,避免上游删文件后本地还残留旧文件
  6. 写 marker .synced.json
  7. 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 把"升级"流程的三件事打成一个动作:

  1. GET https://api.github.com/repos/<repo>/commits/HEAD —— 解析上游默认分支 HEAD 的 SHA
  2. 如果与 lock 里的 commit 相同 → log already up to date — nothing to do 后退出 0
  3. 否则正则替换 lock 文件的 commit 字段(保留原格式),spawn sync-design-studio.mjs 拉取并解压;sync 失败会自动回滚 lock,不会留下一个指向未同步 commit 的脏指针
  4. 用上游新 commit 的 short-sha(前 7 位)regex-rewrite services/content/workspace/design/builtin/index.tsFLAG_FILENAME 常量为 .elftia-builtin-materialized-<short-sha>.json —— 老用户 <userData> 里的旧 flag 文件名不再匹配,下次启动重新 materialize 新内容
  5. 打印 git diff / git commit 提示

提交时只会改这两个文件:lock.jsonbuiltin/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 时用:

  1. 改 lockresources/design-studio-builtin.lock.jsoncommit 字段写完整 40 字符 SHA(不要短哈希 —— 脚本做字符串精确比对)

  2. 跑 sync

    npm run sync:design

    commit 不匹配 marker 时会自动重拉,不需要 --force--force 只在 marker 与 lock 一致但本地目录损坏想强制重拉时用。

  3. 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-<旧>.json flag 不再匹配,下次启动才会触发 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
看本地是否已同步 / 同步到哪个 commitcat resources/design-studio-builtin/.synced.json
强制重拉一次(目录损坏 / 怀疑解压不全)npm run sync:design -- --force
查上游 main 最新 SHAgh 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:updateHTTP 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 缓存,命中时跳过下载

相关文件 / 模块