@goodfoot/claude-code-hooks 1.0.18 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +29 -26
- package/package.json +1 -1
- package/types/cli.d.ts +5 -3
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as crypto from "node:crypto";
|
|
18
18
|
import * as fs from "node:fs";
|
|
19
|
-
import * as os from "node:os";
|
|
20
19
|
import * as path from "node:path";
|
|
21
20
|
import * as esbuild from "esbuild";
|
|
22
21
|
import { glob } from "glob";
|
|
@@ -374,51 +373,53 @@ async function discoverHookFiles(pattern, cwd) {
|
|
|
374
373
|
/**
|
|
375
374
|
* Compiles a TypeScript hook file to a self-contained ESM executable.
|
|
376
375
|
*
|
|
376
|
+
* Uses esbuild's stdin option to avoid writing temporary wrapper files to disk.
|
|
377
|
+
* This produces stable, reproducible builds by:
|
|
378
|
+
* - Using a distinct sourcefile name to avoid import resolution conflicts
|
|
379
|
+
* - Eliminating environment-specific temp paths from source comments
|
|
380
|
+
*
|
|
377
381
|
* Uses a two-step process to generate stable content hashes:
|
|
378
382
|
* 1. Compile WITHOUT sourcemaps → generate stable content hash
|
|
379
383
|
* 2. Compile WITH sourcemaps → final output content
|
|
380
384
|
*
|
|
381
|
-
* This ensures content hashes are reproducible across different build
|
|
382
|
-
* environments, since sourcemaps contain file paths that can vary.
|
|
383
|
-
*
|
|
384
385
|
* @param options - Compilation options
|
|
385
386
|
* @returns Compiled content and stable content hash
|
|
386
387
|
*/
|
|
387
388
|
async function compileHook(options) {
|
|
388
389
|
const { sourcePath, logFilePath } = options;
|
|
389
|
-
// Create a temporary wrapper file that imports the hook and executes it
|
|
390
|
-
// Use system temp directory with deterministic name based on hook basename (not full path)
|
|
391
|
-
// This ensures builds are reproducible across different environments/machines
|
|
392
|
-
const hashInputs = [path.basename(sourcePath), logFilePath ? "log" : ""].join(":");
|
|
393
|
-
const buildHash = crypto.createHash("sha256").update(hashInputs).digest("hex").substring(0, 16);
|
|
394
|
-
const tempDir = path.join(os.tmpdir(), "claude-code-hooks-build", buildHash);
|
|
395
|
-
const wrapperPath = path.join(tempDir, "wrapper.ts");
|
|
396
|
-
const tempOutputNoSourcemap = path.join(tempDir, "output-no-sourcemap.mjs");
|
|
397
|
-
const tempOutputWithSourcemap = path.join(tempDir, "output.mjs");
|
|
398
390
|
// Get the path to the runtime module (relative to this CLI)
|
|
399
391
|
const runtimePath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "./runtime.js");
|
|
400
|
-
// Ensure temp directory exists (don't delete - concurrent builds may be using it)
|
|
401
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
402
392
|
// Build log file injection code if specified
|
|
403
393
|
const logFileInjection = logFilePath !== undefined
|
|
404
394
|
? `process.env['CLAUDE_CODE_HOOKS_CLI_LOG_FILE'] = ${JSON.stringify(logFilePath)};\n`
|
|
405
395
|
: "";
|
|
406
|
-
// Create wrapper that imports the hook and calls execute
|
|
396
|
+
// Create wrapper content that imports the hook and calls execute
|
|
397
|
+
// Uses absolute paths to avoid resolution issues
|
|
407
398
|
const wrapperContent = `${logFileInjection}
|
|
408
399
|
import hook from '${sourcePath.replace(/\\/g, "/")}';
|
|
409
400
|
import { execute } from '${runtimePath.replace(/\\/g, "/")}';
|
|
410
401
|
|
|
411
402
|
execute(hook);
|
|
412
403
|
`;
|
|
413
|
-
|
|
404
|
+
// Use stdin instead of a temp file - sourcefile becomes the stable reference
|
|
405
|
+
// The sourcefile name must be distinct from the actual source file to avoid
|
|
406
|
+
// esbuild resolving the import to the stdin content instead of the real file
|
|
407
|
+
const baseName = path.basename(sourcePath, path.extname(sourcePath));
|
|
408
|
+
const stdinOptions = {
|
|
409
|
+
contents: wrapperContent,
|
|
410
|
+
resolveDir: path.dirname(sourcePath),
|
|
411
|
+
sourcefile: `${baseName}-entry.ts`,
|
|
412
|
+
loader: "ts",
|
|
413
|
+
};
|
|
414
414
|
// Common esbuild options
|
|
415
415
|
const commonOptions = {
|
|
416
|
-
|
|
416
|
+
stdin: stdinOptions,
|
|
417
417
|
format: "esm",
|
|
418
418
|
platform: "node",
|
|
419
419
|
target: "node20",
|
|
420
420
|
bundle: true,
|
|
421
421
|
minify: false,
|
|
422
|
+
write: false, // Return content directly via outputFiles
|
|
422
423
|
// Keep node built-ins external
|
|
423
424
|
external: [
|
|
424
425
|
"node:*",
|
|
@@ -444,22 +445,24 @@ execute(hook);
|
|
|
444
445
|
conditions: ["import", "node"],
|
|
445
446
|
};
|
|
446
447
|
// Step 1: Compile WITHOUT sourcemaps to generate stable content hash
|
|
447
|
-
await esbuild.build({
|
|
448
|
+
const resultNoSourcemap = await esbuild.build({
|
|
448
449
|
...commonOptions,
|
|
449
|
-
outfile: tempOutputNoSourcemap,
|
|
450
450
|
sourcemap: false,
|
|
451
451
|
});
|
|
452
|
-
const contentForHash =
|
|
452
|
+
const contentForHash = resultNoSourcemap.outputFiles?.[0]?.text;
|
|
453
|
+
if (contentForHash === undefined) {
|
|
454
|
+
throw new Error(`esbuild produced no output for ${sourcePath}`);
|
|
455
|
+
}
|
|
453
456
|
const contentHash = generateContentHash(contentForHash);
|
|
454
457
|
// Step 2: Compile WITH sourcemaps for final output
|
|
455
|
-
await esbuild.build({
|
|
458
|
+
const resultWithSourcemap = await esbuild.build({
|
|
456
459
|
...commonOptions,
|
|
457
|
-
outfile: tempOutputWithSourcemap,
|
|
458
460
|
sourcemap: "inline",
|
|
459
461
|
});
|
|
460
|
-
const content =
|
|
461
|
-
|
|
462
|
-
|
|
462
|
+
const content = resultWithSourcemap.outputFiles?.[0]?.text;
|
|
463
|
+
if (content === undefined) {
|
|
464
|
+
throw new Error(`esbuild produced no output for ${sourcePath}`);
|
|
465
|
+
}
|
|
463
466
|
return { content, contentHash };
|
|
464
467
|
}
|
|
465
468
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodfoot/claude-code-hooks",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Type-safe Claude Code hooks library with camelCase types and output builders",
|
|
5
5
|
"homepage": "https://github.com/goodfoot-io/marketplace/tree/main/packages/claude-code-hooks",
|
|
6
6
|
"repository": {
|
package/types/cli.d.ts
CHANGED
|
@@ -163,13 +163,15 @@ interface CompileHookResult {
|
|
|
163
163
|
/**
|
|
164
164
|
* Compiles a TypeScript hook file to a self-contained ESM executable.
|
|
165
165
|
*
|
|
166
|
+
* Uses esbuild's stdin option to avoid writing temporary wrapper files to disk.
|
|
167
|
+
* This produces stable, reproducible builds by:
|
|
168
|
+
* - Using a distinct sourcefile name to avoid import resolution conflicts
|
|
169
|
+
* - Eliminating environment-specific temp paths from source comments
|
|
170
|
+
*
|
|
166
171
|
* Uses a two-step process to generate stable content hashes:
|
|
167
172
|
* 1. Compile WITHOUT sourcemaps → generate stable content hash
|
|
168
173
|
* 2. Compile WITH sourcemaps → final output content
|
|
169
174
|
*
|
|
170
|
-
* This ensures content hashes are reproducible across different build
|
|
171
|
-
* environments, since sourcemaps contain file paths that can vary.
|
|
172
|
-
*
|
|
173
175
|
* @param options - Compilation options
|
|
174
176
|
* @returns Compiled content and stable content hash
|
|
175
177
|
*/
|