-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathcheck-edge-bundle.mjs
More file actions
152 lines (142 loc) · 4.38 KB
/
Copy pathcheck-edge-bundle.mjs
File metadata and controls
152 lines (142 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env bun
import { dirname, join } from "node:path";
/**
* CI edge-safety gate: the runtime-agnostic core (`src/core/**`) must
* bundle for a browser/edge target with **zero** Node dependencies.
*
* Where `check-core-imports.mjs` reads the source statically, this check proves
* the guarantee at the *bundler* level: it asks Bun to bundle the core entry the
* way an edge platform (Cloudflare Workers, Vercel/Deno Edge) would, and fails if
* a Node builtin is reachable from `src/core/index.ts` - even transitively, past
* the import-specifier lint.
*
* Why a resolver plugin instead of just scanning the output: Bun's
* `target: "browser"` *polyfills* Node builtins (a stray `node:crypto` bloats the
* bundle to ~950 KiB but leaves no `node:` text to grep for). A real edge runtime
* provides no such polyfills, so we model that faithfully - a custom resolver
* intercepts every `node:*` / bare-builtin import and makes the build FAIL, which
* is exactly what would happen on a Worker. A literal `node:` scan of the output
* is kept as a redundant second layer.
*
* Run with Bun (uses the Bun.build API): `bun scripts/check-edge-bundle.mjs`.
* Exit 0 if the core is Node-free, 1 otherwise.
*/
import { fileURLToPath } from "node:url";
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
const ENTRY = join(ROOT, "src", "core", "index.ts");
/** Node built-in module names that may appear without the `node:` prefix. */
const BUILTINS = new Set([
"assert",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"domain",
"events",
"fs",
"http",
"http2",
"https",
"inspector",
"module",
"net",
"os",
"path",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"string_decoder",
"sys",
"timers",
"tls",
"trace_events",
"tty",
"url",
"util",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
]);
/** True if `spec` resolves to a Node builtin, with or without the `node:` prefix. */
function isNodeBuiltin(spec) {
if (spec.startsWith("node:")) return true;
return BUILTINS.has(spec.split("/")[0]);
}
// Records every Node builtin the bundler tried to resolve from core.
const leaked = [];
/**
* Edge resolver: no Node builtins exist. Any `node:*` or bare-builtin import is
* routed to a namespace whose loader throws, so `Bun.build` reports failure -
* mirroring what an edge runtime (Cloudflare Workers, etc.) would do.
*/
const noNodeBuiltins = {
name: "no-node-builtins",
setup(build) {
build.onResolve({ filter: /^node:/ }, (args) => {
leaked.push(args.path);
return { path: args.path, namespace: "node-leak" };
});
build.onResolve({ filter: /.*/ }, (args) => {
if (isNodeBuiltin(args.path)) {
leaked.push(args.path);
return { path: args.path, namespace: "node-leak" };
}
return undefined;
});
build.onLoad({ filter: /.*/, namespace: "node-leak" }, (args) => {
throw new Error(`Node builtin "${args.path}" is not available on a browser/edge target`);
});
},
};
const result = await Bun.build({
entrypoints: [ENTRY],
target: "browser",
plugins: [noNodeBuiltins],
});
if (!result.success) {
for (const m of result.logs) console.error(String(m));
if (leaked.length > 0) {
console.error(
`\nFAIL: core pulls in Node builtin(s) [${[...new Set(leaked)].join(", ")}]; ` +
`it cannot bundle Node-free for the edge.`,
);
} else {
console.error("\nFAIL: core did not bundle for a browser/edge target.");
}
process.exit(1);
}
// Redundant second layer: assert no `node:` builtin reference survived in output.
let bad = false;
let totalBytes = 0;
for (const out of result.outputs) {
const code = await out.text();
totalBytes += code.length;
const m = code.match(/(?:require\(|from\s*)["']node:[^"']+["']/);
if (m) {
console.error(`FAIL: bundled core references ${m[0]} (in ${out.path}).`);
bad = true;
}
}
if (bad) {
console.error("\nA Node builtin leaked into the edge bundle; the core is not Node-free.");
process.exit(1);
}
const kib = (totalBytes / 1024).toFixed(1);
console.log(
`OK: core bundled Node-free for target "browser" - ` +
`${result.outputs.length} output(s), ${totalBytes} bytes (${kib} KiB); ` +
`no Node builtins reachable, no "node:" references survived.`,
);
process.exit(0);