diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 7880e2e..6dcbade 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -83,14 +83,6 @@ jobs: env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca --version env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca install --target claude-code --scope project --yes test -f .claude/skills/designpaca/SKILL.md || { echo "설치 후 스킬이 없다" >&2; exit 1; } - - # --dry-run 은 어느 명령에서도 쓰지 않아야 한다 (v0.12.0 에서 update 가 무시했다) - cp "$state_dir/manifest.json" "$sandbox/manifest.before.json" - env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca update --dry-run --force - cmp -s "$state_dir/manifest.json" "$sandbox/manifest.before.json" || { echo "update --dry-run 이 매니페스트를 바꿨다" >&2; exit 1; } - env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca uninstall --dry-run --yes - test -f .claude/skills/designpaca/SKILL.md || { echo "uninstall --dry-run 이 파일을 지웠다" >&2; exit 1; } - env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca doctor env DESIGNPACA_STATE_DIR="$state_dir" npx --no-install designpaca uninstall --yes test ! -f .claude/skills/designpaca/SKILL.md || { echo "제거 후에도 파일이 남았다" >&2; exit 1; } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 344c77f..644482c 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,11 +1,5 @@ # designpaca -## 0.12.1 - -### Patch Changes - -- 744cb36: `update` 와 `uninstall` 이 `--dry-run` 을 무시하고 실제로 파일과 매니페스트를 바꾸던 문제를 고쳤다. 두 명령 모두 계획만 출력하며, CI 스모크에서 `--dry-run` 이 아무것도 쓰지 않는지 검사한다. - ## 0.12.0 ### Minor Changes diff --git a/packages/cli/README.md b/packages/cli/README.md index e4aa499..6eef93b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -109,7 +109,7 @@ npx designpaca update --force | `-s, --scope <범위>` | `user` (전역) \| `project` | | `-y, --yes` | 확인 없이 진행 | | `-f, --force` | 직접 수정한 파일도 덮어쓴다 (`.orig` 백업) | -| `--dry-run` | 쓰지 않고 계획만 출력 (install · update · uninstall 공통) | +| `--dry-run` | 쓰지 않고 계획만 출력 | | `--no-update-check` | 새 버전 확인 생략 | ## 요구사항 diff --git a/packages/cli/package.json b/packages/cli/package.json index bf80938..fcafe17 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "designpaca", - "version": "0.12.1", + "version": "0.12.0", "description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다", "keywords": [ "design", diff --git a/packages/cli/src/commands/uninstall.ts b/packages/cli/src/commands/uninstall.ts index 03b91cd..fbe20c8 100644 --- a/packages/cli/src/commands/uninstall.ts +++ b/packages/cli/src/commands/uninstall.ts @@ -4,7 +4,6 @@ import { dim, heading, ok, warn } from "../ui.ts"; export async function runUninstall(opts: { targets?: TargetId[]; force?: boolean; - dryRun?: boolean; }): Promise { const manifest = await readManifest(); const targets = manifest.installs.filter( @@ -16,15 +15,13 @@ export async function runUninstall(opts: { return 0; } - console.log(heading(opts.dryRun ? "제거 계획" : "제거")); + console.log(heading("제거")); for (const rec of targets) { - const res = await removeInstall(rec, { force: opts.force, dryRun: opts.dryRun }); - const verb = opts.dryRun ? "제거할 파일" : "제거"; - console.log(ok(`${rec.target} (${rec.scope}) — ${verb} ${res.removed.length}개 ${dim(tildify(rec.root))}`)); + const res = await removeInstall(rec, { force: opts.force }); + console.log(ok(`${rec.target} (${rec.scope}) — ${res.removed.length}개 제거 ${dim(tildify(rec.root))}`)); if (res.keptModified.length > 0) { - const tense = opts.dryRun ? "남긴다" : "남겼다"; console.log( - warn(` 직접 수정한 파일 ${res.keptModified.length}개는 ${tense} (--force 로 함께 지운다)`), + warn(` 직접 수정한 파일 ${res.keptModified.length}개는 남겼다 (--force 로 함께 지운다)`), ); for (const p of res.keptModified.slice(0, 5)) console.log(dim(` ${tildify(p)}`)); } diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts index 632b462..7f5980b 100644 --- a/packages/cli/src/commands/update.ts +++ b/packages/cli/src/commands/update.ts @@ -7,7 +7,7 @@ import { dim, heading, info, ok, warn } from "../ui.ts"; * 설치 기록을 그대로 따라가며 재설치한다. * 사용자가 고친 파일은 기본적으로 보존된다(applyPlan 이 판단) — 강제로 맞추려면 --force. */ -export async function runUpdate(opts: { force?: boolean; dryRun?: boolean } = {}): Promise { +export async function runUpdate(opts: { force?: boolean } = {}): Promise { const version = await getVersion(); const manifest = await readManifest(); @@ -16,7 +16,7 @@ export async function runUpdate(opts: { force?: boolean; dryRun?: boolean } = {} return 1; } - console.log(heading(opts.dryRun ? `업데이트 계획 → v${version}` : `업데이트 → v${version}`)); + console.log(heading(`업데이트 → v${version}`)); const stale = manifest.installs.filter((i) => i.version !== version); if (stale.length === 0) { @@ -48,9 +48,8 @@ export async function runUpdate(opts: { force?: boolean; dryRun?: boolean } = {} targets: [rec.target], scope: rec.scope, force: opts.force, - dryRun: opts.dryRun, }); - printOutcomes(outcomes, opts.dryRun); + printOutcomes(outcomes); } return 0; } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 937c589..7855ce1 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -78,7 +78,7 @@ ${bold("옵션")} -s, --scope <범위> user | project ${dim("(기본: user)")} -y, --yes 확인 없이 진행합니다 -f, --force 직접 수정한 파일도 덮어씁니다 ${dim("(.orig 로 백업)")} - --dry-run 쓰지 않고 계획만 보여줍니다 (install · update · uninstall) + --dry-run 쓰지 않고 계획만 보여줍니다 --no-update-check 새 버전 확인을 건너뜁니다 -h, --help 이 도움말 -v, --version 버전 @@ -87,7 +87,6 @@ ${bold("예시")} ${dim("npx designpaca install -t claude-code,codex -s user -y")} ${dim("npx designpaca install -t cursor -s project")} ${dim("npx designpaca update --force")} - ${dim("npx designpaca update --dry-run")} ${dim("npx designpaca tools --yes")} `.trimStart(); } @@ -141,11 +140,11 @@ async function main(): Promise { break; } case "update": - code = await runUpdate({ force: args.force, dryRun: args.dryRun }); + code = await runUpdate({ force: args.force }); break; case "uninstall": case "remove": - code = await runUninstall({ targets: args.targets, force: args.force, dryRun: args.dryRun }); + code = await runUninstall({ targets: args.targets, force: args.force }); break; case "doctor": code = await runDoctor(); diff --git a/packages/cli/test/cli.test.ts b/packages/cli/test/cli.test.ts index 092da36..b0c7f59 100644 --- a/packages/cli/test/cli.test.ts +++ b/packages/cli/test/cli.test.ts @@ -22,13 +22,7 @@ after(async () => { function run(args: string[]): string { return execFileSync(process.execPath, [CLI, ...args, "--no-update-check"], { encoding: "utf8", - env: { - ...process.env, - HOME: home, - USERPROFILE: home, - FORCE_COLOR: "0", - DESIGNPACA_STATE_DIR: path.join(home, ".designpaca"), - }, + env: { ...process.env, HOME: home, USERPROFILE: home, FORCE_COLOR: "0" }, }); } @@ -64,52 +58,6 @@ test("알 수 없는 대상은 종료 코드 2 로 거부한다", { skip: !built ); }); -test("update --dry-run 은 파일도 매니페스트도 건드리지 않는다", { skip: !built }, async () => { - run(["install", "-t", "claude-code", "-s", "user", "-y"]); - const manifestFile = path.join(home, ".designpaca", "manifest.json"); - const skillMd = path.join(home, ".claude", "skills", "designpaca", "SKILL.md"); - - // 업데이트 대상이 있는 상태를 만든다 - const manifest = JSON.parse(await fs.readFile(manifestFile, "utf8")); - manifest.installs[0].version = "0.0.1"; - await fs.writeFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n", "utf8"); - - const manifestBefore = await fs.readFile(manifestFile, "utf8"); - const skillBefore = await fs.readFile(skillMd, "utf8"); - const statBefore = await fs.stat(skillMd); - - const out = run(["update", "--dry-run"]); - assert.ok(out.includes("업데이트 계획")); - assert.ok(out.includes("설치 계획")); - - assert.equal(await fs.readFile(manifestFile, "utf8"), manifestBefore); - assert.equal(await fs.readFile(skillMd, "utf8"), skillBefore); - const statAfter = await fs.stat(skillMd); - assert.equal(statAfter.mtimeMs, statBefore.mtimeMs); - - // 대조군: 실제 update 는 반영돼야 한다 - run(["update"]); - const manifestAfter = JSON.parse(await fs.readFile(manifestFile, "utf8")); - assert.equal(manifestAfter.installs[0].version, run(["--version"]).trim()); - - run(["uninstall", "-y"]); -}); - -test("uninstall --dry-run 은 아무것도 지우지 않는다", { skip: !built }, async () => { - run(["install", "-t", "claude-code", "-s", "user", "-y"]); - const skillMd = path.join(home, ".claude", "skills", "designpaca", "SKILL.md"); - const manifestFile = path.join(home, ".designpaca", "manifest.json"); - - const out = run(["uninstall", "--dry-run"]); - assert.ok(out.includes("제거 계획")); - await fs.access(skillMd); - const manifest = JSON.parse(await fs.readFile(manifestFile, "utf8")); - assert.equal(manifest.installs.length, 1); - - run(["uninstall", "-y"]); - await assert.rejects(() => fs.access(skillMd)); -}); - test("설치 → doctor → 제거 왕복", { skip: !built }, async () => { run(["install", "-t", "claude-code", "-s", "user", "-y"]); const skillMd = path.join(home, ".claude", "skills", "designpaca", "SKILL.md"); diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 465c38b..984db74 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,11 +1,5 @@ # @designpaca/core -## 0.12.1 - -### Patch Changes - -- 744cb36: `update` 와 `uninstall` 이 `--dry-run` 을 무시하고 실제로 파일과 매니페스트를 바꾸던 문제를 고쳤다. 두 명령 모두 계획만 출력하며, CI 스모크에서 `--dry-run` 이 아무것도 쓰지 않는지 검사한다. - ## 0.12.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index b6f7225..f19526f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/core", - "version": "0.12.1", + "version": "0.12.0", "private": true, "description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지", "type": "module", diff --git a/packages/core/src/installer.ts b/packages/core/src/installer.ts index 7538630..18e4ed0 100644 --- a/packages/core/src/installer.ts +++ b/packages/core/src/installer.ts @@ -149,7 +149,7 @@ export interface RemoveResult { /** 매니페스트에 기록된 것만 되돌린다. 기록에 없는 파일은 손대지 않는다. */ export async function removeInstall( record: InstallRecord, - opts: { force?: boolean; dryRun?: boolean; env?: EnvOptions } = {}, + opts: { force?: boolean; env?: EnvOptions } = {}, ): Promise { const drift = await inspectDrift(record); const dirty = new Set(drift.modified); @@ -164,25 +164,21 @@ export async function removeInstall( if (f.marker) { const cur = await readIfExists(f.path); if (cur === null) continue; - if (!opts.dryRun) { - const next = removeBlock(cur, f.marker); - // 블록만 남아 있던 문서라면 파일째 지운다 - if (next.trim().length === 0) await removeFileAndPrune(f.path, path.dirname(f.path)); - else await writeAtomic(f.path, next); - } - } else if (!opts.dryRun) { + const next = removeBlock(cur, f.marker); + // 블록만 남아 있던 문서라면 파일째 지운다 + if (next.trim().length === 0) await removeFileAndPrune(f.path, path.dirname(f.path)); + else await writeAtomic(f.path, next); + } else { await removeFileAndPrune(f.path, record.root); } removed.push(f.path); } - if (!opts.dryRun) { - if (keptModified.length === 0) { - await dropInstall(record.target, record.scope, record.root, opts.env?.home); - } else { - // 일부만 남았으면 기록도 남은 것만 유지한다 - await upsertInstall({ ...record, files: record.files.filter((f) => dirty.has(f.path)) }, opts.env?.home); - } + if (keptModified.length === 0) { + await dropInstall(record.target, record.scope, record.root, opts.env?.home); + } else { + // 일부만 남았으면 기록도 남은 것만 유지한다 + await upsertInstall({ ...record, files: record.files.filter((f) => dirty.has(f.path)) }, opts.env?.home); } return { removed, keptModified }; diff --git a/packages/core/test/installer.test.ts b/packages/core/test/installer.test.ts index 70447fd..de9bb59 100644 --- a/packages/core/test/installer.test.ts +++ b/packages/core/test/installer.test.ts @@ -56,21 +56,6 @@ test("claude-code: 설치 → 드리프트 없음 → 제거", async () => { await assert.rejects(() => fs.access(plan.root)); }); -test("removeInstall dryRun 은 파일과 매니페스트를 건드리지 않는다", async () => { - const plan = await planInstall("claude-code", "user", skill, env); - const res = await applyPlan(plan, skill.version, { env }); - - const dry = await removeInstall(res.record, { env, dryRun: true }); - assert.equal(dry.removed.length, 3); - assert.equal(dry.keptModified.length, 0); - await fs.access(path.join(plan.root, "SKILL.md")); - assert.equal((await readManifest(home)).installs.length, 1); - - const real = await removeInstall(res.record, { env }); - assert.equal(real.removed.length, 3); - assert.equal((await readManifest(home)).installs.length, 0); -}); - test("사용자가 고친 파일은 update 가 건너뛴다", async () => { const plan = await planInstall("claude-code", "user", skill, env); const first = await applyPlan(plan, skill.version, { env }); diff --git a/packages/skill/CHANGELOG.md b/packages/skill/CHANGELOG.md index 43634b3..052521e 100644 --- a/packages/skill/CHANGELOG.md +++ b/packages/skill/CHANGELOG.md @@ -1,7 +1,5 @@ # CHANGELOG -## 0.12.1 - ## 0.12.0 ### Minor Changes diff --git a/packages/skill/package.json b/packages/skill/package.json index 9ae442e..09ab188 100644 --- a/packages/skill/package.json +++ b/packages/skill/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/skill", - "version": "0.12.1", + "version": "0.12.0", "private": true, "description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서", "scripts": {