fix: honor --dry-run in update and uninstall

This commit is contained in:
Yun Chan 2026-09-14 19:53:20 +09:00
parent 6229538d29
commit 744cb36d74
9 changed files with 113 additions and 23 deletions

View file

@ -149,7 +149,7 @@ export interface RemoveResult {
/** 매니페스트에 기록된 것만 되돌린다. 기록에 없는 파일은 손대지 않는다. */
export async function removeInstall(
record: InstallRecord,
opts: { force?: boolean; env?: EnvOptions } = {},
opts: { force?: boolean; dryRun?: boolean; env?: EnvOptions } = {},
): Promise<RemoveResult> {
const drift = await inspectDrift(record);
const dirty = new Set(drift.modified);
@ -164,21 +164,25 @@ export async function removeInstall(
if (f.marker) {
const cur = await readIfExists(f.path);
if (cur === null) continue;
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 if (!opts.dryRun) {
await removeFileAndPrune(f.path, record.root);
}
removed.push(f.path);
}
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 (!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);
}
}
return { removed, keptModified };

View file

@ -56,6 +56,21 @@ 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 });