@sproutboat/toolchain 0.8.0 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sproutboat/toolchain
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 742e61c: A CLI compiled with `bun build --compile` builds again. 0.8.0 embedded the patch sources with text imports to fingerprint them, but Bun bundles a text import of `patch.ts` as the module `patch.ts` is everywhere else, so compiling failed with `No matching export ... for import "default"`. The fingerprint now hashes the sources when they are on disk, and falls back to the fingerprint they were released with (`RELEASED_FINGERPRINT`, kept current by a test). The value is unchanged, so existing caches stay valid.
8
+
3
9
  ## 0.8.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  import { expect, test } from "bun:test";
2
2
  import { readdir } from "node:fs/promises";
3
- import { PATCH_FINGERPRINT, PATCH_SOURCES } from "./fingerprint";
3
+ import { PATCH_FINGERPRINT, PATCH_SOURCES, RELEASED_FINGERPRINT, sourceFingerprint } from "./fingerprint";
4
4
 
5
5
  // Not part of the patched compiler: acquisition, the barrel, generators and this module.
6
6
  const NOT_PATCH_SOURCES = new Set(["acquire.ts", "index.ts", "fingerprint.ts"]);
@@ -9,8 +9,20 @@ test("the patch fingerprint covers every toolchain module that shapes the compil
9
9
  const modules = (await readdir(import.meta.dir)).filter(
10
10
  (name) => name.endsWith(".ts") && !name.endsWith(".test.ts") && !name.startsWith("gen-") && !NOT_PATCH_SOURCES.has(name),
11
11
  );
12
- expect(Object.keys(PATCH_SOURCES).sort()).toEqual(modules.sort());
13
- const embedded = new Map<string, string>(Object.entries(PATCH_SOURCES));
14
- for (const name of modules) expect(embedded.get(name)).toBe(await Bun.file(`${import.meta.dir}/${name}`).text());
12
+ expect(modules.sort()).toEqual(PATCH_SOURCES.map(String).sort());
15
13
  expect(PATCH_FINGERPRINT).toMatch(/^[0-9a-f]{12}$/);
14
+ expect(PATCH_FINGERPRINT).toBe(sourceFingerprint(import.meta.dir)!);
15
+ expect(sourceFingerprint("/nonexistent")).toBeNull();
16
+ });
17
+
18
+ // A compiled CLI falls back to RELEASED_FINGERPRINT, so it must match the sources.
19
+ test("RELEASED_FINGERPRINT matches the sources", async () => {
20
+ const actual = sourceFingerprint(import.meta.dir)!;
21
+ if (process.env.UPDATE_FINGERPRINT && actual !== RELEASED_FINGERPRINT) {
22
+ const file = `${import.meta.dir}/fingerprint.ts`;
23
+ const text = await Bun.file(file).text();
24
+ await Bun.write(file, text.replace(`RELEASED_FINGERPRINT = "${RELEASED_FINGERPRINT}"`, `RELEASED_FINGERPRINT = "${actual}"`));
25
+ return;
26
+ }
27
+ expect(RELEASED_FINGERPRINT, "run UPDATE_FINGERPRINT=1 bun test packages/toolchain/src/fingerprint.test.ts").toBe(actual);
16
28
  });
@@ -1,48 +1,44 @@
1
1
  import { createHash } from "node:crypto";
2
- // The patch chain's own source text, embedded by Bun's text loader (it survives
3
- // `bun build --compile`, where these files do not exist on disk). TypeScript sees
4
- // a module, not a string, hence the expect-errors.
5
- // @ts-expect-error text import
6
- import caseMap from "./case-map.ts" with { type: "text" };
7
- // @ts-expect-error text import
8
- import fetchClasses from "./fetch-classes.ts" with { type: "text" };
9
- // @ts-expect-error text import
10
- import headersClass from "./headers-class.ts" with { type: "text" };
11
- // @ts-expect-error text import
12
- import patch from "./patch.ts" with { type: "text" };
13
- // @ts-expect-error text import
14
- import pin from "./pin.ts" with { type: "text" };
15
- // @ts-expect-error text import
16
- import streaming from "./streaming.ts" with { type: "text" };
17
- // @ts-expect-error text import
18
- import textDecoder from "./text-decoder.ts" with { type: "text" };
19
- // @ts-expect-error text import
20
- import unicode from "./unicode.ts" with { type: "text" };
21
- // @ts-expect-error text import
22
- import urlClass from "./url-class.ts" with { type: "text" };
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
23
4
 
24
5
  /** Every module that shapes the patched compiler. fingerprint.test.ts fails when one is missing. */
25
- export const PATCH_SOURCES = {
26
- "case-map.ts": caseMap,
27
- "fetch-classes.ts": fetchClasses,
28
- "headers-class.ts": headersClass,
29
- "patch.ts": patch,
30
- "pin.ts": pin,
31
- "streaming.ts": streaming,
32
- "text-decoder.ts": textDecoder,
33
- "unicode.ts": unicode,
34
- "url-class.ts": urlClass,
35
- } satisfies Record<string, string>;
6
+ export const PATCH_SOURCES = [
7
+ "case-map.ts",
8
+ "fetch-classes.ts",
9
+ "headers-class.ts",
10
+ "patch.ts",
11
+ "pin.ts",
12
+ "streaming.ts",
13
+ "text-decoder.ts",
14
+ "unicode.ts",
15
+ "url-class.ts",
16
+ ] as const;
17
+
18
+ /**
19
+ * The fingerprint of the sources as released. A compiled CLI (`bun build --compile`) has
20
+ * no source files to hash, and cannot embed them either: Bun bundles a text import of
21
+ * patch.ts as the module patch.ts is everywhere else. Kept current by fingerprint.test.ts
22
+ * (UPDATE_FINGERPRINT=1 bun test packages/toolchain/src/fingerprint.test.ts).
23
+ */
24
+ export const RELEASED_FINGERPRINT = "f0ff426f97b0";
25
+
26
+ /** The fingerprint of the sources in dir, or null where they are not on disk. */
27
+ export function sourceFingerprint(dir: string): string | null {
28
+ const hash = createHash("sha256");
29
+ try {
30
+ for (const name of PATCH_SOURCES) hash.update(`${name}\0${readFileSync(join(dir, name), "utf8")}\0`);
31
+ } catch {
32
+ return null;
33
+ }
34
+ return hash.digest("hex").slice(0, 12);
35
+ }
36
36
 
37
37
  /**
38
38
  * Identifies the patch chain, not just the toolchain release. A cache entry is keyed by it, so
39
39
  * an edited patch gets a fresh, fully patched compiler instead of one a same-version build
40
40
  * already marked complete (B19: the prelude began calling sb_stream_*, which only the new
41
- * chain defines, and every build against an older entry failed to link).
41
+ * chain defines, and every build against an older entry failed to link). From the sources
42
+ * when they are on disk, so an unreleased edit counts at once; else as released.
42
43
  */
43
- export const PATCH_FINGERPRINT = (() => {
44
- const hash = createHash("sha256");
45
- for (const [name, source] of Object.entries(PATCH_SOURCES).sort(([a], [b]) => a.localeCompare(b)))
46
- hash.update(`${name}\0${source}\0`);
47
- return hash.digest("hex").slice(0, 12);
48
- })();
44
+ export const PATCH_FINGERPRINT = sourceFingerprint(import.meta.dir) ?? RELEASED_FINGERPRINT;