@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 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
- fs.writeFileSync(wrapperPath, wrapperContent, "utf-8");
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
- entryPoints: [wrapperPath],
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 = fs.readFileSync(tempOutputNoSourcemap, "utf-8");
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 = fs.readFileSync(tempOutputWithSourcemap, "utf-8");
461
- // Don't delete temp directory - allows concurrent builds of same source
462
- // and the OS will clean up /tmp periodically
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.18",
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
  */