내장 리소스 동기화 프로세스
resources/design-studio-builtin/ 아래의 600개 이상 파일(Skill / Design System / Craft)은 git에 포함되지 않습니다. 이 파일들은 commit pin을 기준으로 upstream nexu-io/open-design repository에서 가져오며, 첫 시작 시 materializer가 사용자 디렉터리에 구체화합니다.
이 방식을 사용하는 이유: upstream 콘텐츠는 규모가 크고 자주 업데이트됩니다. 모든 파일을 commit하면 elftia의 git 기록에 상당한 노이즈가 생기고 merge conflict가 자주 발생합니다. commit을 pin하면 재현 가능한 build를 보장하면서 "내장 asset 업그레이드"를 한 번의 lock file bump로 처리할 수 있습니다.
:::warning 큐레이션 예외: prompt-templates/는 절대 sync하지 않음(2026-06-04)
prompt-templates/는 제자리에서 유지 관리되는 수동 큐레이션 subset이며 sync에 참여하지 않습니다. upstream 전체 컬렉션에는 실제 인물 얼굴(예: Elon Musk 이미지를 생성하는 template) 같은 콘텐츠 위험 항목이 포함되어 있고, 이 JSON들은 모든 channel release에 익명으로 보이는 "community templates"로 packaging됩니다. 따라서:
- lock의
subdirs에는prompt-templates가 포함되지 않습니다. sync script에는 다시 추가되면 hard fail하는NEVER_SYNC_SUBDIRSsafeguard가 있습니다(resync는 먼저clearSubdirs로 큐레이션된 set을 삭제한 뒤 필터링되지 않은 upstream 전체 set을 주입합니다). - upstream에서 새 template을 도입하려면: JSON을 하나씩 수동으로 복사하고 검토합니다(실제 인물 얼굴 / brand IP / NSFW). curation log를 업데이트하세요 —
resources/design-studio-builtin/prompt-templates/README.md를 참고하세요. - 이 디렉터리는 예외적으로 git에 포함됩니다(
.gitignore는resources/design-studio-builtin/*에 대해!…/prompt-templates/로 반전됨). fresh clone에서도 network access 없이 큐레이션된 set을 즉시 재현할 수 있습니다.
:::
관련 파일
| Path | Purpose | In git? |
|---|---|---|
resources/design-studio-builtin.lock.json | Pin file — { repo, commit, subdirs } | ✅ git에 포함되는 유일한 파일 |
resources/design-studio-builtin/ | disk에 동기화된 콘텐츠(600개 이상 파일) | ❌ gitignored |
resources/design-studio-builtin/prompt-templates/ | 큐레이션 예외 — 수동 큐레이션 subset, 절대 sync하지 않음(위 warning 참고), README.md curation log 포함 | ✅ git의 예외 항목(gitignore 반전) |
resources/design-studio-builtin/.synced.json | Marker — 마지막으로 성공한 sync commit을 기록하고, hit 시 skip | ❌(ignored directory 내부) |
resources/design-studio-builtin/NOTICE | 자동 생성되는 third-party attribution | ❌ |
scripts/sync-design-studio.mjs | Sync script(lock 기준 pull) | ✅ |
scripts/update-design-studio.mjs | 자동 upgrade script — upstream HEAD pull + lock bump + sync + FLAG_FILENAME bump를 한 command로 수행 | ✅ |
packages/desktop/app/main/services/content/workspace/design/builtin/index.ts | 첫 시작 materializer + FLAG_FILENAME | ✅ |
Trigger 시점
| Trigger | Command / Hook | Mode |
|---|---|---|
npm install | postinstall hook | --soft(network failure가 install을 막지 않음) |
npm run setup / setup:native | 수동 initialization | --soft |
npm run build:electron / build:official / build:steam | electron-vite build와 electron-builder 사이 | Strict mode(failure 시 build 실패) |
npm run sync:design | 수동 실행 | Strict mode |
npm run sync:design -- --force | 수동 실행(force refetch) | Strict mode + marker skip |
npm run sync:design:update | upstream HEAD로 one-click upgrade(권장) | 아래 "Upgrade to upstream latest commit" 참고 |
marker가 hit되면({repo, commit, subdirs}의 세 필드가 lock과 정확히 일치) 바로 skip하고 [sync:design] already synced @ <repo>@<sha7> — skipping (use --force)를 출력합니다. 이는 정상 상태이므로 error로 오해하지 마세요.
Sync Script가 하는 일
scripts/sync-design-studio.mjs의 main flow:
resources/design-studio-builtin.lock.json을 읽고 schema를 validate합니다(repo/commit/subdirs[]필수)- local
.synced.jsonmarker를 비교합니다 — 세 필드가 모두 일치하고--force가 없으면 skip하고 exit합니다 - tarball을 pull합니다:
- Public repo →
https://codeload.github.com/<repo>/tar.gz/<commit> GITHUB_TOKEN/GH_TOKENenv vars 설정 후 →https://api.github.com/repos/<repo>/tarball/<commit>(private repos는 이 방식을 사용)
- Public repo →
decompress로 extract하고,strip: 1로 외부 package name directory를 제거하며,filter로 lock에 나열된subdirs만 유지합니다- extract 전에
fs.rm(subdir, { recursive: true, force: true })로 각 target subdir을 정리해 upstream에서 삭제된 뒤 stale file이 남지 않도록 합니다 - marker
.synced.json을 씁니다 NOTICE를 씁니다 — third-party attribution(Apache-2.0)을 자동 생성합니다
decompress 4.x cross-platform gotcha(깨뜨리지 말 것)
strip은 filter보다 먼저 적용되지만, Windows에서는 filter가 받는 path가 이미 backslash로 normalize되어 있습니다. script의 filter는 file.path.split(/[\\/]/)[0]를 사용해 두 separator를 모두 자릅니다. /만 자르면 Linux에서는 600개 이상 파일이 모두 통과하지만 Windows에서는 0개가 통과하며, failure mode가 silent입니다(빈 directory만 출력). 디버깅이 어렵습니다. 이 섹션을 수정할 때는 양쪽 platform에서 모두 validate하세요.
Upgrade to Upstream Latest Commit
권장 경로: One-Click Automation
npm run sync:design:update
scripts/update-design-studio.mjs는 "upgrade" process의 세 단계를 하나의 action으로 묶습니다:
GET https://api.github.com/repos/<repo>/commits/HEAD— upstream default branch HEAD의 SHA를 parse합니다- lock의
commit과 같으면 →already up to date — nothing to do를 log하고 0으로 exit합니다 - 그렇지 않으면 lock file의
commitfield를 regex replace하고(원래 format 유지),sync-design-studio.mjs를 spawn해 pull 및 extract합니다. sync가 실패하면 lock을 자동 rollback하므로, 동기화되지 않은 commit을 가리키는 dirty pointer를 남기지 않습니다 - upstream 새 commit의 short-sha(앞 7자)를 사용해
services/content/workspace/design/builtin/index.ts의FLAG_FILENAMEconstant를.elftia-builtin-materialized-<short-sha>.json으로 regex-rewrite합니다 — 기존 user의<userData>에 있는 old flag filename이 더 이상 match되지 않아, 다음 startup에서 새 content를 다시 materialize합니다 git diff/git commithint를 출력합니다
commit 시 변경되는 파일은 lock.json과 builtin/index.ts 두 개뿐입니다.
Flag naming convention: FLAG_FILENAME에는 upstream commit의 short-sha가 포함됩니다(예: .elftia-builtin-materialized-83ddf76.json). 이전에는 integer version number v1/v2/...를 사용했지만 short-sha format으로 migration되었습니다 — commit을 바꾸면 이름도 자연스럽게 바뀌므로 manual version number를 유지할 필요가 없습니다.
Dry-run:
npm run sync:design:update -- --dry-run
SHA 차이만 출력하고 어떤 file도 건드리지 않습니다. CI에서 "새 upstream version이 있는지" 감지하는 데 사용할 수 있습니다.
Auth: GITHUB_TOKEN이 설정되어 있지 않으면 GitHub public API를 사용하며 unauthenticated 제한은 60 req/hr입니다. CI에서는 GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}를 설정해 5000 req/hr로 올리세요.
대체 경로: 수동 세 단계
auto script가 깨졌거나 non-default branch / historical commit으로 pin하려는 경우에만 사용하세요:
-
Change lock:
resources/design-studio-builtin.lock.json의commitfield에 전체 40자 SHA를 씁니다(short hash가 아님 — script는 exact string comparison을 수행합니다) -
Run sync:
npm run sync:designcommit이 marker와 match하지 않으면 자동으로 refetch하므로--force가 필요 없습니다.--force는 marker와 lock이 match하지만 local directory가 손상되어 force refetch하려는 경우에만 사용합니다. -
Bump
FLAG_FILENAME:packages/desktop/app/main/services/content/workspace/design/builtin/index.ts를 edit하고 constant를 새 commit의 short-sha로 바꿉니다:const FLAG_FILENAME = '.elftia-builtin-materialized-<new short-sha>.json';이 단계는 critical합니다 — 기존 elftia user의
<userData>/elftia/.elftia-builtin-materialized-<old>.jsonflag가 더 이상 match되지 않아야 다음 startup에서 re-materialize가 trigger됩니다. bump하지 않으면 fresh install만 새 content를 받고, 기존 user는 계속 old content를 보게 됩니다.
Verification
# Delete local marker to force rerun verification
rm resources/design-studio-builtin/.synced.json
npm run sync:design
# See new file count / date in NOTICE
cat resources/design-studio-builtin/.synced.json
cat resources/design-studio-builtin/NOTICE | head -10
일반 작업
| Scenario | Command |
|---|---|
| upstream latest commit으로 upgrade(flag bump 포함) | npm run sync:design:update |
| 새 upstream version이 있는지 확인(file 수정 없음) | npm run sync:design:update -- --dry-run |
| local이 sync되었는지 / 어떤 commit에 sync되었는지 확인 | cat resources/design-studio-builtin/.synced.json |
| 한 번 force refetch(directory 손상 / incomplete extract 의심) | npm run sync:design -- --force |
| upstream main latest SHA 조회 | gh api repos/nexu-io/open-design/commits/main --jq .sha |
| sync를 임시로 skip(CI debugging) | lock file 삭제 → script가 "lock file missing, skipping"을 log하고 exit |
| upstream을 private fork로 교체 | lock의 repo field 변경, GITHUB_TOKEN export 후 sync 실행 |
Troubleshooting
| Symptom | Cause / Solution |
|---|---|
[sync:design] already synced @ ... — skipping (use --force) | 정상, marker hit. refetch하려면 -- --force 추가 |
HTTP 404 ... (private repo? set GITHUB_TOKEN) | lock의 repo가 private이거나 commit이 존재하지 않습니다. GITHUB_TOKEN을 export하거나 SHA를 verify하세요 |
no files matched subdirs [...] | subdirs의 directory가 해당 commit에 존재하지 않습니다(upstream rename/refactor). 해당 commit의 directory layout을 확인하세요 |
| Windows에서 sync는 성공하지만 product directory가 비어 있음 | decompress filter path separator 문제. script의 split(/[\\/]/)를 split('/')로 바꾸지 마세요 |
| User가 upgrade했지만 새 built-in Skill이 보이지 않음 | FLAG_FILENAME bump를 잊었고 old user의 old flag가 남아 있음 → materialize skip. sync:design:update는 자동 bump하며, lock을 수동 변경했다면 step 3을 잊지 마세요 |
sync:design:update가 HTTP 403 ... rate-limited?를 report함 | GitHub unauthenticated API는 60 req/hr로 제한됩니다. GITHUB_TOKEN을 export하고 다시 실행하세요 |
sync:design:update sync는 성공했지만 flag가 바뀌지 않음 | services/content/workspace/design/builtin/index.ts의 FLAG_FILENAME이 rename되었는지 확인하세요 — script는 regex const FLAG_FILENAME = '\.elftia-builtin-materialized-...\.json';로 locate하므로 rename 후에는 실패합니다 |
| CI에서 tarball pull이 너무 느림 / bandwidth 초과 | actions/cache를 사용해 resources/design-studio-builtin/를 lock file hash key로 cache하고, hit 시 download를 skip하세요 |
관련 파일 / 모듈
scripts/sync-design-studio.mjs— lock 기준 pull + extractscripts/update-design-studio.mjs— upstream HEAD로 one-click upgraderesources/design-studio-builtin.lock.jsonpackages/desktop/app/main/services/content/workspace/design/builtin/index.ts—FLAG_FILENAME,materializeBuiltinResources,isBuiltinMaterialized- Design Studio Architecture Overview