@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 +6 -0
- package/package.json +1 -1
- package/src/fingerprint.test.ts +16 -4
- package/src/fingerprint.ts +35 -39
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
package/src/fingerprint.test.ts
CHANGED
|
@@ -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(
|
|
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
|
});
|
package/src/fingerprint.ts
CHANGED
|
@@ -1,48 +1,44 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
|
|
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"
|
|
27
|
-
"fetch-classes.ts"
|
|
28
|
-
"headers-class.ts"
|
|
29
|
-
"patch.ts"
|
|
30
|
-
"pin.ts"
|
|
31
|
-
"streaming.ts"
|
|
32
|
-
"text-decoder.ts"
|
|
33
|
-
"unicode.ts"
|
|
34
|
-
"url-class.ts"
|
|
35
|
-
|
|
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;
|