fix(3579): ship graphify hook + lib/ helper through build-hooks + install (#3640)
* fix(3579): ship graphify hook + lib/ helper through build-hooks + install `scripts/build-hooks.js` `HOOKS_TO_COPY` did not include `gsd-graphify-update.sh` (added in #3347 / PR #3557), so it never landed in `hooks/dist/` and `bin/install.js` — which `readdirSync`s the dist — never copied it to `~/.claude/hooks/`. The hook's detached rebuild helper at `hooks/lib/gsd-graphify-rebuild.sh` was also silently dropped because both build-hooks.js (flat allowlist) and bin/install.js (readdir + isFile filter) only walked top-level files. The published tarball gap (Gap 3 in the issue body) is not reproduced on origin/main — `npm pack --dry-run --json` shows both source files are present today. Only Gaps 1 and 2 are in scope. Changes: - Add `gsd-graphify-update.sh` to `HOOKS_TO_COPY`. - Add `HOOKS_SUBDIRS_TO_COPY = ['lib']` and copy whitelisted hook subdirectories (`hooks/<dir>/*` → `hooks/dist/<dir>/*`) in build-hooks.js, with the same syntax-check + atomic-rename path the top-level loop uses. - `bin/install.js`: when copying `hooks/dist/`, recurse one level into any directory entry so subdir files (e.g. `lib/gsd-graphify-rebuild.sh`) land at the mirrored target path the hook's REBUILD_SCRIPT lookup expects. Top-level if/else structure for files is unchanged. Regression test `tests/bug-3579-graphify-hook-publish.test.cjs`: - Drift guard: every top-level `hooks/*.sh` must appear in `HOOKS_TO_COPY`. Generalizes beyond graphify so the next .sh hook added cannot regress. - After build: `hooks/dist/gsd-graphify-update.sh` AND `hooks/dist/lib/gsd-graphify-rebuild.sh` exist. - After install: both files land at the target, and no "Missing expected hook: gsd-graphify-update.sh" warning is emitted. Fixes #3579 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(3579): replace source-grep drift guard with filesystem-behavior assertion The Gap-1 drift guard read scripts/build-hooks.js as text and regex-parsed the HOOKS_TO_COPY literal, which tripped lint-no-source-grep and is brittle under refactors. Replace with a behavior-based assertion: run the build, then for every top-level hooks/*.sh assert hooks/dist/<name> exists. Strictly stronger — catches both the original allowlist gap and any future regression that silently drops a hook for any other reason. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8793,6 +8793,27 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
}
|
||||
}
|
||||
} else if (fs.statSync(srcFile).isDirectory()) {
|
||||
// #3579: recurse one level into hook subdirs (lib/ etc.). The
|
||||
// graphify auto-update hook's rebuild helper lives at
|
||||
// hooks/dist/lib/gsd-graphify-rebuild.sh and must land at the
|
||||
// mirrored target path so the hook's REBUILD_SCRIPT lookup resolves.
|
||||
const subDest = path.join(hooksDest, entry);
|
||||
fs.mkdirSync(subDest, { recursive: true });
|
||||
const subEntries = fs.readdirSync(srcFile);
|
||||
for (const subEntry of subEntries) {
|
||||
const subSrcFile = path.join(srcFile, subEntry);
|
||||
if (!fs.statSync(subSrcFile).isFile()) continue;
|
||||
const subDestFile = path.join(subDest, subEntry);
|
||||
if (subEntry.endsWith('.sh')) {
|
||||
let content = fs.readFileSync(subSrcFile, 'utf8');
|
||||
content = content.replace(/\{\{GSD_VERSION\}\}/g, pkg.version);
|
||||
fs.writeFileSync(subDestFile, content);
|
||||
try { fs.chmodSync(subDestFile, 0o755); } catch (e) { /* Windows */ }
|
||||
} else {
|
||||
fs.copyFileSync(subSrcFile, subDestFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (verifyInstalled(hooksDest, 'hooks')) {
|
||||
|
||||
Reference in New Issue
Block a user