@scriptc/compiler 0.0.31 → 0.0.33

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.
Files changed (55) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +1 -1
  2. package/dist/backend/cc-cache.test.js +308 -1
  3. package/dist/backend/cc-cache.test.js.map +1 -1
  4. package/dist/backend/cc.d.ts +17 -5
  5. package/dist/backend/cc.js +745 -75
  6. package/dist/backend/cc.js.map +1 -1
  7. package/dist/backend/emission/emit-exprs.js +53 -2
  8. package/dist/backend/emission/emit-exprs.js.map +1 -1
  9. package/dist/backend/emission/emitter.d.ts +12 -5
  10. package/dist/backend/emission/emitter.js +160 -22
  11. package/dist/backend/emission/emitter.js.map +1 -1
  12. package/dist/backend/emission/may-throw.js +7 -4
  13. package/dist/backend/emission/may-throw.js.map +1 -1
  14. package/dist/backend/ffi-callbacks.d.ts +38 -1
  15. package/dist/backend/ffi-callbacks.js +76 -3
  16. package/dist/backend/ffi-callbacks.js.map +1 -1
  17. package/dist/backend/llvm/emitter.js +243 -12
  18. package/dist/backend/llvm/emitter.js.map +1 -1
  19. package/dist/diagnostics/diagnostic.js +2 -0
  20. package/dist/diagnostics/diagnostic.js.map +1 -1
  21. package/dist/ffi/profile.d.ts +17 -5
  22. package/dist/ffi/profile.js +169 -22
  23. package/dist/ffi/profile.js.map +1 -1
  24. package/dist/frontend/lowering/lower-calls.d.ts +1 -0
  25. package/dist/frontend/lowering/lower-calls.js +89 -23
  26. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  27. package/dist/frontend/lowering/lower-containers.js +65 -19
  28. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  29. package/dist/frontend/lowering/lowerer.d.ts +12 -0
  30. package/dist/frontend/lowering/lowerer.js +20 -1
  31. package/dist/frontend/lowering/lowerer.js.map +1 -1
  32. package/dist/frontend/ts7/checker.d.ts +2 -0
  33. package/dist/frontend/ts7/checker.js +20 -3
  34. package/dist/frontend/ts7/checker.js.map +1 -1
  35. package/dist/frontend/types.d.ts +8 -0
  36. package/dist/frontend/types.js +33 -5
  37. package/dist/frontend/types.js.map +1 -1
  38. package/dist/index.js +35 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir/nodes.d.ts +22 -9
  41. package/dist/ir/nodes.js +7 -1
  42. package/dist/ir/nodes.js.map +1 -1
  43. package/dist/ir/serialize.d.ts +1 -1
  44. package/dist/ir/serialize.js +1 -1
  45. package/dist/ir/validate.js +74 -1
  46. package/dist/ir/validate.js.map +1 -1
  47. package/dist/library/int-infer.js +7 -1
  48. package/dist/library/int-infer.js.map +1 -1
  49. package/dist/library/int-infer.test.js +73 -4
  50. package/dist/library/int-infer.test.js.map +1 -1
  51. package/dist/library/profile.d.ts +3 -0
  52. package/dist/library/profile.js +9 -1
  53. package/dist/library/profile.js.map +1 -1
  54. package/package.json +2 -2
  55. package/surface-manifest.json +1 -1
@@ -2378,7 +2378,7 @@ declare module "tls" {
2378
2378
  /** The CA-store introspection surface (scr_tls_ca.c): per-type cached
2379
2379
  * PEM string arrays. The host bundle stands in for Node's compiled-in
2380
2380
  * Mozilla roots ('bundled', and rootCertificates) AND the platform
2381
- * store ('system') — the /etc/ssl/cert.pem stance, documented;
2381
+ * store ('system') — the Windows certificate stores or POSIX bundle probe;
2382
2382
  * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws
2383
2383
  * Node's ERR_INVALID_ARG_VALUE TypeError. */
2384
2384
  export function getCACertificates(type?: string): string[];
@@ -1,7 +1,7 @@
1
1
  import { execFileSync, spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import { existsSync } from "node:fs";
4
- import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
4
+ import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, utimes, writeFile } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { delimiter, join } from "node:path";
7
7
  import { afterAll, afterEach, expect, test as vitestTest } from "vitest";
@@ -203,6 +203,101 @@ test("the runtime fingerprint includes the textually included Ryū sources", asy
203
203
  await utimes(join(ryuDir, "d2s.c"), pinnedTime, pinnedTime);
204
204
  expect(await runtimeFingerprint(rtDir)).not.toBe(first);
205
205
  });
206
+ test("the runtime fingerprint includes newly added nested headers", async () => {
207
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-fingerprint-nested-"));
208
+ scratch.push(dir);
209
+ const rtDir = join(dir, "src");
210
+ const ryuDir = join(dir, "vendor", "ryu");
211
+ await Promise.all([
212
+ mkdir(join(rtDir, "sys"), { recursive: true }),
213
+ mkdir(ryuDir, { recursive: true }),
214
+ ]);
215
+ await writeFile(join(rtDir, "scr_number.c"), "int scriptc_probe;\n");
216
+ const first = await runtimeFingerprint(rtDir);
217
+ // A new file below an existing include root can win resolution without
218
+ // changing any path selected by the previous dependency scan.
219
+ await writeFile(join(rtDir, "sys", "types.h"), "#define SCRIPTC_SHADOW 1\n");
220
+ expect(await runtimeFingerprint(rtDir)).not.toBe(first);
221
+ });
222
+ test.skipIf(process.platform === "win32")("the runtime fingerprint follows symlinked source files", async () => {
223
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-fingerprint-symlink-"));
224
+ scratch.push(dir);
225
+ const rtDir = join(dir, "src");
226
+ const ryuDir = join(dir, "vendor", "ryu");
227
+ const target = join(dir, "runtime-target.h");
228
+ await Promise.all([
229
+ mkdir(rtDir, { recursive: true }),
230
+ mkdir(ryuDir, { recursive: true }),
231
+ writeFile(target, "#define SCRIPTC_SYMLINK 1\n"),
232
+ ]);
233
+ await symlink(target, join(rtDir, "linked.h"));
234
+ const first = await runtimeFingerprint(rtDir);
235
+ await writeFile(target, "#define SCRIPTC_SYMLINK 2\n");
236
+ expect(await runtimeFingerprint(rtDir)).not.toBe(first);
237
+ });
238
+ test.skipIf(process.platform === "win32")("new nested runtime headers invalidate complete and output-local artifacts", async () => {
239
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-shadow-"));
240
+ scratch.push(dir);
241
+ const cacheRoot = join(dir, "cache");
242
+ const fakeRuntime = join(dir, "runtime", "src");
243
+ const projectDir = join(dir, "project");
244
+ const originalRuntime = runtimeSrcDir();
245
+ const cPath = join(projectDir, "program.c");
246
+ const firstOut = join(projectDir, "first");
247
+ const crossOutput = join(projectDir, "cross-output");
248
+ const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
249
+ const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
250
+ const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
251
+ try {
252
+ await Promise.all([
253
+ cp(originalRuntime, fakeRuntime, { recursive: true }),
254
+ mkdir(projectDir),
255
+ mkdir(join(dir, "runtime", "vendor"), { recursive: true }).then(() => cp(join(originalRuntime, "..", "vendor", "ryu"), join(dir, "runtime", "vendor", "ryu"), { recursive: true })),
256
+ ]);
257
+ await mkdir(join(fakeRuntime, "sys"));
258
+ const numberSource = join(fakeRuntime, "scr_number.c");
259
+ await writeFile(numberSource, `${await readFile(numberSource, "utf8")}\n` +
260
+ "#ifndef SCRIPTC_SHADOW_VALUE\n#define SCRIPTC_SHADOW_VALUE 1\n#endif\n" +
261
+ "int scriptc_shadow_value(void) { return SCRIPTC_SHADOW_VALUE; }\n");
262
+ await writeFile(cPath, "#include <stdio.h>\nint scriptc_shadow_value(void);\n" +
263
+ 'int main(void) { printf("%d\\n", scriptc_shadow_value()); return 0; }\n');
264
+ process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
265
+ process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
266
+ delete process.env["SCRIPTC_NO_CACHE"];
267
+ await compileC({ cPath, outPath: firstOut, cacheIdentity: "scriptc-generated-v1" });
268
+ expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("1");
269
+ // scr_runtime.h includes <sys/types.h>. This newly created header wins
270
+ // the existing -I runtime search without changing any dependency path
271
+ // selected during the first build.
272
+ await writeFile(join(fakeRuntime, "sys", "types.h"), "#include_next <sys/types.h>\n#define SCRIPTC_SHADOW_VALUE 2\n");
273
+ // A new output path bypasses the output-local stamp and probes the
274
+ // cross-output complete-artifact cache directly.
275
+ await compileC({
276
+ cPath,
277
+ outPath: crossOutput,
278
+ cacheIdentity: "scriptc-generated-v1",
279
+ });
280
+ expect(execFileSync(crossOutput, { encoding: "utf8" }).trim()).toBe("2");
281
+ // The original output has an output-local stamp from the first build;
282
+ // it must invalidate for the same namespace change.
283
+ await compileC({ cPath, outPath: firstOut, cacheIdentity: "scriptc-generated-v1" });
284
+ expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("2");
285
+ }
286
+ finally {
287
+ if (oldCacheDir === undefined)
288
+ delete process.env["SCRIPTC_CACHE_DIR"];
289
+ else
290
+ process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
291
+ if (oldNoCache === undefined)
292
+ delete process.env["SCRIPTC_NO_CACHE"];
293
+ else
294
+ process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
295
+ if (oldRuntimeDir === undefined)
296
+ delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
297
+ else
298
+ process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
299
+ }
300
+ });
206
301
  test("implicit dependency seeds include separately compiled vendor system headers", async () => {
207
302
  const includes = await implicitDependencyProbeIncludes(runtimeSrcDir());
208
303
  // zlib's crc32.c is compiled independently and is not textually reachable
@@ -2283,6 +2378,218 @@ test("system libraries relink after an in-place rebuild while runtime objects re
2283
2378
  process.env["LIBRARY_PATH"] = oldLibraryPath;
2284
2379
  }
2285
2380
  });
2381
+ test("frontend-generated same-output builds no-op only while output and dependency stamp are valid", async () => {
2382
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-"));
2383
+ scratch.push(dir);
2384
+ const cacheRoot = join(dir, "cache");
2385
+ const cPath = join(dir, "program.c");
2386
+ const outPath = join(dir, "program");
2387
+ const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
2388
+ const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
2389
+ const oldUmask = process.umask();
2390
+ try {
2391
+ process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
2392
+ delete process.env["SCRIPTC_NO_CACHE"];
2393
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("valid"); return 0; }\n');
2394
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2395
+ const stampPath = join(cacheRoot, "local", createHash("sha256").update(outPath).digest("hex"));
2396
+ const stamp = JSON.parse(await readFile(stampPath, "utf8"));
2397
+ expect(stamp.dependencies.length).toBeGreaterThan(0);
2398
+ const pinnedTime = new Date("2001-01-01T00:00:00.000Z");
2399
+ await utimes(outPath, pinnedTime, pinnedTime);
2400
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2401
+ expect((await stat(outPath)).mtimeMs).toBe(pinnedTime.getTime());
2402
+ process.umask(0o077);
2403
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2404
+ expect((await stat(outPath)).mode & 0o777).toBe(0o700);
2405
+ process.umask(oldUmask);
2406
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2407
+ expect((await stat(outPath)).mode & 0o777).toBe(0o777 & ~oldUmask);
2408
+ // Generated TU bytes join the key: an actual source edit must replace the
2409
+ // output even though its path, runtime, and toolchain are unchanged.
2410
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("edited"); return 0; }\n');
2411
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2412
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("edited");
2413
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("valid"); return 0; }\n');
2414
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2415
+ // A damaged output cannot no-op even when every build input is unchanged.
2416
+ await writeFile(outPath, "damaged\n");
2417
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2418
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("valid");
2419
+ // The stamp is disposable cache data. A changed SDK/linker dependency
2420
+ // metadata record must take the strict CAS path, which reinstalls output.
2421
+ const damagedStamp = JSON.parse(await readFile(stampPath, "utf8"));
2422
+ damagedStamp.dependencies[0].size++;
2423
+ await writeFile(stampPath, `${JSON.stringify(damagedStamp)}\n`);
2424
+ await utimes(outPath, pinnedTime, pinnedTime);
2425
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2426
+ expect((await stat(outPath)).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
2427
+ }
2428
+ finally {
2429
+ if (oldCacheDir === undefined)
2430
+ delete process.env["SCRIPTC_CACHE_DIR"];
2431
+ else
2432
+ process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
2433
+ if (oldNoCache === undefined)
2434
+ delete process.env["SCRIPTC_NO_CACHE"];
2435
+ else
2436
+ process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
2437
+ process.umask(oldUmask);
2438
+ }
2439
+ });
2440
+ test.skipIf(process.platform === "win32")("output-local hits follow symlinked header targets", async () => {
2441
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-symlink-"));
2442
+ scratch.push(dir);
2443
+ const cacheRoot = join(dir, "cache");
2444
+ const cPath = join(dir, "program.c");
2445
+ const header = join(dir, "value.h");
2446
+ const target = join(dir, "target.h");
2447
+ const outPath = join(dir, "program");
2448
+ const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
2449
+ const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
2450
+ try {
2451
+ process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
2452
+ delete process.env["SCRIPTC_NO_CACHE"];
2453
+ await writeFile(target, '#define VALUE "one"\n');
2454
+ await symlink(target, header);
2455
+ await writeFile(cPath, '#include <stdio.h>\n#include "value.h"\nint main(void) { puts(VALUE); return 0; }\n');
2456
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2457
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("one");
2458
+ // clang's dependency file names the symlink path. The local stamp must
2459
+ // also follow that path and invalidate when only the target changes.
2460
+ await writeFile(target, '#define VALUE "two"\n');
2461
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2462
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
2463
+ }
2464
+ finally {
2465
+ if (oldCacheDir === undefined)
2466
+ delete process.env["SCRIPTC_CACHE_DIR"];
2467
+ else
2468
+ process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
2469
+ if (oldNoCache === undefined)
2470
+ delete process.env["SCRIPTC_NO_CACHE"];
2471
+ else
2472
+ process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
2473
+ }
2474
+ });
2475
+ test.skipIf(process.platform === "win32")("output-local hits detect newly shadowing nested headers", async () => {
2476
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-shadow-"));
2477
+ scratch.push(dir);
2478
+ const cacheRoot = join(dir, "cache");
2479
+ const cPath = join(dir, "program.c");
2480
+ const outPath = join(dir, "program");
2481
+ const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
2482
+ const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
2483
+ try {
2484
+ process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
2485
+ delete process.env["SCRIPTC_NO_CACHE"];
2486
+ await mkdir(join(dir, "sys"));
2487
+ await writeFile(cPath, '#include <stdio.h>\n#include "sys/param.h"\nint main(void) { printf("%d\\n", MAXPATHLEN); return 0; }\n');
2488
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2489
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).not.toBe("7");
2490
+ // The source directory itself does not necessarily change metadata when
2491
+ // a child directory gains a file. Its recursive namespace digest must.
2492
+ await writeFile(join(dir, "sys", "param.h"), "#define MAXPATHLEN 7\n");
2493
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2494
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("7");
2495
+ }
2496
+ finally {
2497
+ if (oldCacheDir === undefined)
2498
+ delete process.env["SCRIPTC_CACHE_DIR"];
2499
+ else
2500
+ process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
2501
+ if (oldNoCache === undefined)
2502
+ delete process.env["SCRIPTC_NO_CACHE"];
2503
+ else
2504
+ process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
2505
+ }
2506
+ });
2507
+ test("fresh processes preserve output-local dependency coverage after source edits", async () => {
2508
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-process-"));
2509
+ scratch.push(dir);
2510
+ const cacheRoot = join(dir, "cache");
2511
+ const cPath = join(dir, "program.c");
2512
+ const outPath = join(dir, "program");
2513
+ const helperPath = join(dir, "compile.mjs");
2514
+ const childEnv = { ...process.env, SCRIPTC_CACHE_DIR: cacheRoot };
2515
+ delete childEnv["SCRIPTC_NO_CACHE"];
2516
+ delete childEnv["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
2517
+ delete childEnv["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"];
2518
+ await writeFile(helperPath, `import { compileC } from ${JSON.stringify(new URL("./cc.ts", import.meta.url).href)};\n` +
2519
+ `await compileC({ cPath: process.argv[2], outPath: process.argv[3], cacheIdentity: "scriptc-generated-v1" });\n`);
2520
+ const compileInFreshProcess = () => {
2521
+ execFileSync(process.execPath, ["--import", "tsx", helperPath, cPath, outPath], {
2522
+ env: childEnv,
2523
+ stdio: "pipe",
2524
+ });
2525
+ };
2526
+ const stampPath = join(cacheRoot, "local", createHash("sha256").update(outPath).digest("hex"));
2527
+ const dependencyPaths = async () => {
2528
+ const stamp = JSON.parse(await readFile(stampPath, "utf8"));
2529
+ return stamp.dependencies.map((dependency) => dependency.path);
2530
+ };
2531
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
2532
+ compileInFreshProcess();
2533
+ const firstPaths = await dependencyPaths();
2534
+ expect(firstPaths.length).toBeGreaterThan(2);
2535
+ // The metadata fingerprints are restored from cache files in this second
2536
+ // Node process; their in-memory fingerprint-to-path map starts empty.
2537
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("two"); return 0; }\n');
2538
+ compileInFreshProcess();
2539
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
2540
+ expect(await dependencyPaths()).toEqual(firstPaths);
2541
+ });
2542
+ test("native metadata snapshots survive source edits and repair after tampering", async () => {
2543
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-native-metadata-"));
2544
+ scratch.push(dir);
2545
+ const cacheRoot = join(dir, "cache");
2546
+ const cPath = join(dir, "program.c");
2547
+ const outPath = join(dir, "program");
2548
+ const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
2549
+ const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
2550
+ try {
2551
+ process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
2552
+ delete process.env["SCRIPTC_NO_CACHE"];
2553
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
2554
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2555
+ const metadataDir = join(cacheRoot, "meta");
2556
+ const metadataNames = await readdir(metadataDir);
2557
+ expect(metadataNames.length).toBeGreaterThanOrEqual(3);
2558
+ const pinnedTime = new Date("2002-01-01T00:00:00.000Z");
2559
+ await Promise.all(metadataNames.map((name) => utimes(join(metadataDir, name), pinnedTime, pinnedTime)));
2560
+ // Program bytes are not toolchain identity. A source edit should reuse the
2561
+ // validated target/compiler/link snapshots without republishing them.
2562
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("two"); return 0; }\n');
2563
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2564
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
2565
+ for (const name of metadataNames) {
2566
+ expect((await stat(join(metadataDir, name))).mtimeMs).toBe(pinnedTime.getTime());
2567
+ }
2568
+ // Snapshot files are disposable cache data. Tampering invalidates one,
2569
+ // strict discovery repairs it, and the requested source edit still lands.
2570
+ const damagedPath = join(metadataDir, metadataNames[0]);
2571
+ const damaged = JSON.parse(await readFile(damagedPath, "utf8"));
2572
+ damaged.integrity = "0".repeat(64);
2573
+ await writeFile(damagedPath, `${JSON.stringify(damaged)}\n`);
2574
+ await utimes(damagedPath, pinnedTime, pinnedTime);
2575
+ await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("three"); return 0; }\n');
2576
+ await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
2577
+ expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("three");
2578
+ expect((await stat(damagedPath)).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
2579
+ const repaired = JSON.parse(await readFile(damagedPath, "utf8"));
2580
+ expect(repaired.integrity).not.toBe("0".repeat(64));
2581
+ }
2582
+ finally {
2583
+ if (oldCacheDir === undefined)
2584
+ delete process.env["SCRIPTC_CACHE_DIR"];
2585
+ else
2586
+ process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
2587
+ if (oldNoCache === undefined)
2588
+ delete process.env["SCRIPTC_NO_CACHE"];
2589
+ else
2590
+ process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
2591
+ }
2592
+ });
2286
2593
  test("damaged complete artifacts are rejected and rebuilt", async () => {
2287
2594
  const dir = await mkdtemp(join(tmpdir(), "scriptc-artifact-integrity-"));
2288
2595
  scratch.push(dir);