- bin/install.js now copies scripts/changeset/ and scripts/lib/ into <configDir>/scripts/ so $GSD_DIR/scripts/changeset/cli.cjs resolves at runtime; aborts install with an explicit failure if the source directory is missing from the package. - gsd-core/workflows/update.md: corrected path from gsd-core/scripts/changeset/cli.cjs to scripts/changeset/cli.cjs; added an explicit [ ! -f ] guard so a missing CLI surfaces a clear message rather than silently swallowing the error; stderr captured via 2>&1 sentinel so node errors are visible in the preview output. - release.yml's changeset-CLI invocations (node scripts/changeset/cli.cjs) remain at the repo-root path and are unaffected by this change. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/935-changeset-cli-install.md
Normal file
5
.changeset/935-changeset-cli-install.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 935
|
||||
---
|
||||
**`/gsd-update` changelog preview no longer silently fails** — the installer now copies `scripts/changeset/` and `scripts/lib/` into the runtime config dir so `$GSD_DIR/scripts/changeset/cli.cjs` resolves at runtime; `update.md` was updated to use the correct installed path and to surface an explicit error if the CLI is missing rather than swallowing it.
|
||||
122
bin/install.js
122
bin/install.js
@@ -8894,6 +8894,52 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
}
|
||||
}
|
||||
|
||||
// 4a. Remove scripts/changeset/ and scripts/lib/ (#935)
|
||||
// GSD-managed files only: enumerate the exact set the installer writes.
|
||||
// Any file NOT in this set is user-owned and must survive uninstall.
|
||||
// After removing GSD files, attempt to rmdir — if the directory is still
|
||||
// non-empty (user has custom helpers) it stays; otherwise it goes cleanly.
|
||||
const GSD_CHANGESET_FILES = [
|
||||
'cli.cjs', 'parse.cjs', 'render.cjs', 'serialize.cjs',
|
||||
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
||||
'README.md', // documentation only — not user-authored
|
||||
];
|
||||
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs'];
|
||||
|
||||
const changesetUninstallDir = path.join(targetDir, 'scripts', 'changeset');
|
||||
if (fs.existsSync(changesetUninstallDir)) {
|
||||
let removedChangeset = 0;
|
||||
for (const file of GSD_CHANGESET_FILES) {
|
||||
const fp = path.join(changesetUninstallDir, file);
|
||||
try { fs.unlinkSync(fp); removedChangeset++; } catch (_) { /* best-effort */ }
|
||||
}
|
||||
// Remove directory if empty after our cleanup
|
||||
try { fs.rmdirSync(changesetUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
||||
if (removedChangeset > 0) {
|
||||
removedCount++;
|
||||
console.log(` ${green}✓${reset} Removed scripts/changeset/ GSD files`);
|
||||
}
|
||||
}
|
||||
const scriptsLibUninstallDir = path.join(targetDir, 'scripts', 'lib');
|
||||
if (fs.existsSync(scriptsLibUninstallDir)) {
|
||||
let removedScriptsLib = 0;
|
||||
for (const file of GSD_SCRIPTS_LIB_FILES) {
|
||||
const fp = path.join(scriptsLibUninstallDir, file);
|
||||
try { fs.unlinkSync(fp); removedScriptsLib++; } catch (_) { /* best-effort */ }
|
||||
}
|
||||
// Remove directory if empty after our cleanup
|
||||
try { fs.rmdirSync(scriptsLibUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
||||
if (removedScriptsLib > 0) {
|
||||
removedCount++;
|
||||
console.log(` ${green}✓${reset} Removed scripts/lib/ GSD files`);
|
||||
}
|
||||
}
|
||||
// If scripts/ dir is now empty, remove it too
|
||||
const scriptsUninstallDir = path.join(targetDir, 'scripts');
|
||||
if (fs.existsSync(scriptsUninstallDir)) {
|
||||
try { fs.rmdirSync(scriptsUninstallDir); } catch (_) { /* Not empty — leave it */ }
|
||||
}
|
||||
|
||||
// 5. Remove GSD package.json (CommonJS mode marker)
|
||||
const pkgJsonPath = path.join(targetDir, 'package.json');
|
||||
if (fs.existsSync(pkgJsonPath)) {
|
||||
@@ -9577,6 +9623,24 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Track scripts/changeset/ and scripts/lib/ so saveLocalPatches() can detect drift
|
||||
const changesetInstallDir = path.join(configDir, 'scripts', 'changeset');
|
||||
if (fs.existsSync(changesetInstallDir)) {
|
||||
for (const file of fs.readdirSync(changesetInstallDir)) {
|
||||
if (file.endsWith('.cjs')) {
|
||||
manifest.files['scripts/changeset/' + file] = fileHash(path.join(changesetInstallDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
const scriptsLibInstallDir = path.join(configDir, 'scripts', 'lib');
|
||||
if (fs.existsSync(scriptsLibInstallDir)) {
|
||||
for (const file of fs.readdirSync(scriptsLibInstallDir)) {
|
||||
if (file.endsWith('.cjs')) {
|
||||
manifest.files['scripts/lib/' + file] = fileHash(path.join(scriptsLibInstallDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
|
||||
return manifest;
|
||||
}
|
||||
@@ -10898,6 +10962,64 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
console.log(` ${green}✓${reset} Installed hooks/lib/ helpers (git-cmd, graphify-rebuild, ...)`);
|
||||
}
|
||||
|
||||
// Install scripts/changeset/ and scripts/lib/ into <configDir>/scripts/
|
||||
// so that `node "$GSD_DIR/scripts/changeset/cli.cjs"` resolves at runtime.
|
||||
//
|
||||
// The changeset CLI (scripts/changeset/cli.cjs) is invoked by the update
|
||||
// workflow (gsd-core/workflows/update.md) to extract changelog ranges for
|
||||
// the /gsd-update preview step. It was previously only present in the npm
|
||||
// tarball root but never copied to the runtime config dir, causing the
|
||||
// preview to always silently fail (#935).
|
||||
//
|
||||
// cli.cjs requires:
|
||||
// - sibling files in scripts/changeset/ (parse/render/serialize/github-release-notes)
|
||||
// - ../lib/cli-exit.cjs → scripts/lib/cli-exit.cjs
|
||||
// - ../../gsd-core/bin/lib/semver-compare.cjs (already installed under gsd-core/)
|
||||
// - ../../gsd-core/bin/lib/package-identity.cjs (already installed under gsd-core/)
|
||||
//
|
||||
// All runtimes that use the update workflow need this, so we copy unconditionally
|
||||
// (same scope as gsd-core/ itself — every runtime that installs workflows gets it).
|
||||
const changesetSrc = path.join(src, 'scripts', 'changeset');
|
||||
const scriptsLibSrc = path.join(src, 'scripts', 'lib');
|
||||
if (!fs.existsSync(changesetSrc)) {
|
||||
// The changeset CLI source is missing from the package — mark as a hard failure
|
||||
// so the user knows the changelog preview will not work rather than silently degrading.
|
||||
failures.push('scripts/changeset/ (source missing from package — reinstall from npm)');
|
||||
} else {
|
||||
const changesetDest = path.join(targetDir, 'scripts', 'changeset');
|
||||
const scriptsLibDest = path.join(targetDir, 'scripts', 'lib');
|
||||
fs.mkdirSync(changesetDest, { recursive: true });
|
||||
fs.mkdirSync(scriptsLibDest, { recursive: true });
|
||||
// Copy scripts/changeset/ — all .cjs and .md files
|
||||
for (const entry of fs.readdirSync(changesetSrc)) {
|
||||
const srcFile = path.join(changesetSrc, entry);
|
||||
if (fs.statSync(srcFile).isFile()) {
|
||||
fs.copyFileSync(srcFile, path.join(changesetDest, entry));
|
||||
}
|
||||
}
|
||||
// Copy scripts/lib/ — cli-exit.cjs (required by cli.cjs) and any future lib helpers.
|
||||
// Hard-fail if missing: without cli-exit.cjs the installed CLI throws MODULE_NOT_FOUND.
|
||||
if (!fs.existsSync(scriptsLibSrc)) {
|
||||
failures.push('scripts/lib/ (source missing from package — reinstall from npm)');
|
||||
} else {
|
||||
for (const entry of fs.readdirSync(scriptsLibSrc)) {
|
||||
const srcFile = path.join(scriptsLibSrc, entry);
|
||||
if (fs.statSync(srcFile).isFile()) {
|
||||
fs.copyFileSync(srcFile, path.join(scriptsLibDest, entry));
|
||||
}
|
||||
}
|
||||
// Verify the critical dep cli-exit.cjs landed
|
||||
if (!verifyFileInstalled(path.join(scriptsLibDest, 'cli-exit.cjs'), 'scripts/lib/cli-exit.cjs')) {
|
||||
failures.push('scripts/lib/cli-exit.cjs');
|
||||
}
|
||||
}
|
||||
if (verifyFileInstalled(path.join(changesetDest, 'cli.cjs'), 'scripts/changeset/cli.cjs')) {
|
||||
console.log(` ${green}✓${reset} Installed scripts/changeset/ (changelog preview CLI)`);
|
||||
} else {
|
||||
failures.push('scripts/changeset/cli.cjs');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove legacy get-shit-done-cc artifacts and stale update caches (#607).
|
||||
// cleanupLegacyGsdCc handles both the legacy shared cache and the per-package
|
||||
// cache (formerly an inline unlinkSync here). A cleanup failure must never
|
||||
|
||||
@@ -195,24 +195,29 @@ CHANGELOG_TMP="/tmp/gsd-changelog-$$.md"
|
||||
curl -fsSL "https://raw.githubusercontent.com/open-gsd/gsd-core/main/CHANGELOG.md" -o "$CHANGELOG_TMP" 2>/dev/null \
|
||||
|| wget -qO "$CHANGELOG_TMP" "https://raw.githubusercontent.com/open-gsd/gsd-core/main/CHANGELOG.md" 2>/dev/null
|
||||
|
||||
EXTRACT_JSON=$(node "$GSD_DIR/gsd-core/scripts/changeset/cli.cjs" extract \
|
||||
--from "$INSTALLED_VERSION" \
|
||||
--to "$LATEST_VERSION" \
|
||||
--changelog "$CHANGELOG_TMP" \
|
||||
--json 2>/dev/null)
|
||||
EXTRACT_EXIT=$?
|
||||
|
||||
if [ "$EXTRACT_EXIT" -eq 2 ]; then
|
||||
# Exit 2 = no releases in range (e.g. versions are equal or changelog is sparse)
|
||||
CHANGELOG_PREVIEW="No changelog updates between v${INSTALLED_VERSION} and v${LATEST_VERSION}."
|
||||
elif [ "$EXTRACT_EXIT" -ne 0 ] || [ -z "$EXTRACT_JSON" ]; then
|
||||
CHANGELOG_PREVIEW="(Could not extract changelog — update will still proceed)"
|
||||
GSD_CHANGESET_CLI="$GSD_DIR/scripts/changeset/cli.cjs"
|
||||
if [ ! -f "$GSD_CHANGESET_CLI" ]; then
|
||||
CHANGELOG_PREVIEW="(Changelog CLI not found at $GSD_CHANGESET_CLI — reinstall GSD to restore preview. Update will still proceed.)"
|
||||
else
|
||||
# Re-run without --json to get the human-readable markdown for display
|
||||
CHANGELOG_PREVIEW=$(node "$GSD_DIR/gsd-core/scripts/changeset/cli.cjs" extract \
|
||||
EXTRACT_JSON=$(node "$GSD_CHANGESET_CLI" extract \
|
||||
--from "$INSTALLED_VERSION" \
|
||||
--to "$LATEST_VERSION" \
|
||||
--changelog "$CHANGELOG_TMP" 2>/dev/null || echo "(changelog unavailable)")
|
||||
--changelog "$CHANGELOG_TMP" \
|
||||
--json 2>&1)
|
||||
EXTRACT_EXIT=$?
|
||||
|
||||
if [ "$EXTRACT_EXIT" -eq 2 ]; then
|
||||
# Exit 2 = no releases in range (e.g. versions are equal or changelog is sparse)
|
||||
CHANGELOG_PREVIEW="No changelog updates between v${INSTALLED_VERSION} and v${LATEST_VERSION}."
|
||||
elif [ "$EXTRACT_EXIT" -ne 0 ] || [ -z "$EXTRACT_JSON" ]; then
|
||||
CHANGELOG_PREVIEW="(Could not extract changelog — update will still proceed)"
|
||||
else
|
||||
# Re-run without --json to get the human-readable markdown for display
|
||||
CHANGELOG_PREVIEW=$(node "$GSD_CHANGESET_CLI" extract \
|
||||
--from "$INSTALLED_VERSION" \
|
||||
--to "$LATEST_VERSION" \
|
||||
--changelog "$CHANGELOG_TMP" 2>/dev/null || echo "(changelog unavailable)")
|
||||
fi
|
||||
fi
|
||||
# Clean up temp changelog now that both extract runs are done
|
||||
rm -f "$CHANGELOG_TMP"
|
||||
|
||||
@@ -324,7 +324,7 @@ function resolveChangelogPath(opts) {
|
||||
* 1 — I/O error or missing required flags.
|
||||
*
|
||||
* Fix for #3496: provides a deterministic range-aware helper so the
|
||||
* `/gsd:update` show_changes_and_confirm step no longer relies on
|
||||
* `/gsd-update` show_changes_and_confirm step no longer relies on
|
||||
* vague/manual extraction that can silently skip intermediate versions.
|
||||
*/
|
||||
function cmdExtract(opts) {
|
||||
|
||||
@@ -332,10 +332,13 @@ describe('changeset cli extract: version-range changelog extraction (#3496)', ()
|
||||
test('F1: workflows/update.md contains concrete extract subcommand invocation', (_t) => {
|
||||
const workflowPath = path.join(ROOT, 'gsd-core', 'workflows', 'update.md');
|
||||
const workflowText = fs.readFileSync(workflowPath, 'utf8');
|
||||
// The invocation is: node "$GSD_DIR/gsd-core/scripts/changeset/cli.cjs" extract
|
||||
// so the literal substring is 'cli.cjs" extract' (quote between script path and subcommand)
|
||||
// The invocation uses either a direct path or an intermediate variable:
|
||||
// node "$GSD_DIR/scripts/changeset/cli.cjs" extract
|
||||
// node "$GSD_CHANGESET_CLI" extract
|
||||
// Accept either form so future refactors don't immediately trip this anchor.
|
||||
assert.ok(
|
||||
workflowText.includes('cli.cjs" extract') || workflowText.includes('cli.cjs extract'),
|
||||
workflowText.includes('cli.cjs" extract') || workflowText.includes('cli.cjs extract') ||
|
||||
(workflowText.includes('GSD_CHANGESET_CLI') && workflowText.includes('" extract')),
|
||||
'update.md must invoke cli.cjs extract (fix for #3496 BLOCKER 1)',
|
||||
);
|
||||
assert.ok(
|
||||
@@ -351,6 +354,40 @@ describe('changeset cli extract: version-range changelog extraction (#3496)', ()
|
||||
'update.md must capture exit code or JSON output from extract',
|
||||
);
|
||||
});
|
||||
|
||||
// F2: update.md must use the INSTALLED path ($GSD_DIR/scripts/changeset/cli.cjs),
|
||||
// NOT the old broken path ($GSD_DIR/gsd-core/scripts/changeset/cli.cjs).
|
||||
// The installer copies scripts/changeset/ into <configDir>/scripts/changeset/,
|
||||
// so the runtime path is $GSD_DIR/scripts/changeset/cli.cjs (#935).
|
||||
// allow-test-rule: reads a product workflow .md file (not CJS source) to verify
|
||||
// the runtime install path contract; there is no behavioural runtime to invoke.
|
||||
test('F2: update.md CLI path is $GSD_DIR/scripts/changeset/cli.cjs (not gsd-core/scripts/…) (#935)', (_t) => {
|
||||
const workflowPath = path.join(ROOT, 'gsd-core', 'workflows', 'update.md');
|
||||
const workflowText = fs.readFileSync(workflowPath, 'utf8');
|
||||
// The correct installed path must appear somewhere in the update workflow
|
||||
assert.ok(
|
||||
workflowText.includes('scripts/changeset/cli.cjs'),
|
||||
'update.md must reference scripts/changeset/cli.cjs',
|
||||
);
|
||||
// The old broken path ($GSD_DIR/gsd-core/scripts/changeset/cli.cjs) must not appear
|
||||
assert.ok(
|
||||
!workflowText.includes('gsd-core/scripts/changeset/cli.cjs'),
|
||||
'update.md must NOT reference the old gsd-core/scripts/changeset/cli.cjs path (fix for #935)',
|
||||
);
|
||||
});
|
||||
|
||||
// F3: update.md must guard against the CLI being missing (not pure silent-swallow)
|
||||
// allow-test-rule: reads a product workflow .md file (not CJS source) to verify
|
||||
// the guard is present; there is no behavioural runtime to invoke.
|
||||
test('F3: update.md has an explicit guard when changeset CLI is missing (#935)', (_t) => {
|
||||
const workflowPath = path.join(ROOT, 'gsd-core', 'workflows', 'update.md');
|
||||
const workflowText = fs.readFileSync(workflowPath, 'utf8');
|
||||
// The workflow must check for CLI existence before invoking it
|
||||
assert.ok(
|
||||
workflowText.includes('GSD_CHANGESET_CLI') && workflowText.includes('! -f'),
|
||||
'update.md must guard against a missing changeset CLI with [ ! -f "$GSD_CHANGESET_CLI" ] (#935)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeset cli render: file-I/O wrapper (#2975)', () => {
|
||||
|
||||
@@ -707,3 +707,126 @@ describe('Kilo source integration assertions', () => {
|
||||
assert.ok(updateContextSrc.includes('KILO_CONFIG'));
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Section N: changeset CLI install regression (#935) ──────────────────────
|
||||
|
||||
describe('install — changeset CLI lands at scripts/changeset/cli.cjs (#935)', () => {
|
||||
// Regression guard: the changeset CLI must be copied into the runtime config dir
|
||||
// by the installer so $GSD_DIR/scripts/changeset/cli.cjs resolves at runtime.
|
||||
// Before this fix, scripts/ was never copied and /gsd-update changelog preview
|
||||
// silently failed on every real install.
|
||||
let tmpDir;
|
||||
let previousCwd;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempDir('gsd-changeset-install-');
|
||||
previousCwd = process.cwd();
|
||||
process.chdir(tmpDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(previousCwd);
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('install() copies scripts/changeset/cli.cjs to <configDir>/scripts/changeset/cli.cjs', () => {
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
const cliPath = path.join(claudeDir, 'scripts', 'changeset', 'cli.cjs');
|
||||
assert.ok(
|
||||
fs.existsSync(cliPath),
|
||||
`scripts/changeset/cli.cjs must exist at ${path.relative(tmpDir, cliPath)} after install (#935)`,
|
||||
);
|
||||
});
|
||||
|
||||
test('install() copies scripts/lib/cli-exit.cjs to <configDir>/scripts/lib/cli-exit.cjs', () => {
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
const cliExitPath = path.join(claudeDir, 'scripts', 'lib', 'cli-exit.cjs');
|
||||
assert.ok(
|
||||
fs.existsSync(cliExitPath),
|
||||
`scripts/lib/cli-exit.cjs must exist at ${path.relative(tmpDir, cliExitPath)} after install (#935)`,
|
||||
);
|
||||
});
|
||||
|
||||
test('installed cli.cjs executes without module-resolution errors', () => {
|
||||
// Smoke test: node can load the installed changeset CLI without crashing.
|
||||
// This catches path mismatches in require('../lib/cli-exit.cjs') etc.
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
const cliPath = path.join(claudeDir, 'scripts', 'changeset', 'cli.cjs');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const result = spawnSync(process.execPath, [cliPath, '--help'], { encoding: 'utf8' });
|
||||
// --help exits with code 1 (usage shown), but must NOT throw a MODULE_NOT_FOUND error
|
||||
assert.ok(
|
||||
!result.stderr.includes('MODULE_NOT_FOUND'),
|
||||
`cli.cjs must not produce MODULE_NOT_FOUND errors; stderr=${result.stderr}`,
|
||||
);
|
||||
assert.ok(
|
||||
!result.stderr.includes('Cannot find module'),
|
||||
`cli.cjs must resolve all modules; stderr=${result.stderr}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('installed cli.cjs can run extract subcommand end-to-end (#935)', () => {
|
||||
// Integration smoke test: the installed CLI's extract path (invoked by update.md)
|
||||
// must actually work — this catches require() path issues that --help wouldn't surface.
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
const cliPath = path.join(claudeDir, 'scripts', 'changeset', 'cli.cjs');
|
||||
// Use the CHANGELOG.md that was installed into gsd-core/ (installed by the installer)
|
||||
const changelogPath = path.join(claudeDir, 'gsd-core', 'CHANGELOG.md');
|
||||
assert.ok(fs.existsSync(changelogPath), 'CHANGELOG.md must be installed under gsd-core/');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[cliPath, 'extract', '--from', '0.0.0', '--to', '9999.0.0', '--changelog', changelogPath, '--json'],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
// extract must NOT throw a MODULE_NOT_FOUND or Cannot find module error
|
||||
assert.ok(
|
||||
!result.stderr.includes('MODULE_NOT_FOUND') && !result.stderr.includes('Cannot find module'),
|
||||
`installed cli.cjs extract must resolve all modules; stderr=${result.stderr}`,
|
||||
);
|
||||
// extract exit code 0 (found entries) or 2 (no entries in range) are both valid;
|
||||
// any other exit code is an error
|
||||
assert.ok(
|
||||
result.status === 0 || result.status === 2,
|
||||
`installed cli.cjs extract must exit 0 or 2; got ${result.status}; stderr=${result.stderr}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('writeManifest() tracks scripts/changeset/ and scripts/lib/ files', () => {
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
const manifest = writeManifest(claudeDir, 'claude');
|
||||
const changesetKeys = Object.keys(manifest.files).filter(k => k.startsWith('scripts/changeset/'));
|
||||
const libKeys = Object.keys(manifest.files).filter(k => k.startsWith('scripts/lib/'));
|
||||
assert.ok(changesetKeys.length > 0, 'manifest must track scripts/changeset/ files');
|
||||
assert.ok(libKeys.length > 0, 'manifest must track scripts/lib/ files');
|
||||
assert.ok(
|
||||
changesetKeys.includes('scripts/changeset/cli.cjs'),
|
||||
'manifest must include scripts/changeset/cli.cjs',
|
||||
);
|
||||
assert.ok(
|
||||
libKeys.includes('scripts/lib/cli-exit.cjs'),
|
||||
'manifest must include scripts/lib/cli-exit.cjs',
|
||||
);
|
||||
});
|
||||
|
||||
test('uninstall() removes scripts/changeset/ and scripts/lib/', () => {
|
||||
install(false, 'claude');
|
||||
const claudeDir = path.join(tmpDir, '.claude');
|
||||
assert.ok(fs.existsSync(path.join(claudeDir, 'scripts', 'changeset', 'cli.cjs')),
|
||||
'pre-condition: cli.cjs must be installed before uninstall');
|
||||
uninstall(false, 'claude');
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(claudeDir, 'scripts', 'changeset')),
|
||||
'scripts/changeset/ must be removed on uninstall',
|
||||
);
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(claudeDir, 'scripts', 'lib')),
|
||||
'scripts/lib/ must be removed on uninstall',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user