@econ-v1/app-sdk 7.0.77 → 7.0.79
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/README.md +35 -0
- package/dist/burger/index.js +2 -0
- package/dist/burger-build/aliases.d.ts +28 -0
- package/dist/burger-build/build.d.ts +19 -0
- package/dist/burger-build/bun-version.d.ts +8 -0
- package/dist/burger-build/cli.js +844 -0
- package/dist/burger-build/gate.d.ts +28 -0
- package/dist/burger-build/index.d.ts +9 -0
- package/dist/burger-build/index.js +785 -0
- package/dist/burger-build/plugin.d.ts +25 -0
- package/dist/burger-build/test-prepare.d.ts +68 -0
- package/dist/burger-types/aliases.d.ts +20 -0
- package/dist/burger-types/globals.d.ts +66 -0
- package/dist/burger-types/index.d.ts +3 -0
- package/dist/burger-types/modules.d.ts +436 -0
- package/dist/index.d.ts +11 -18
- package/dist/index.js +11 -11
- package/dist/otel-burger.d.ts +5 -0
- package/dist/platform/bun.d.ts +34 -0
- package/dist/platform/burger.d.ts +47 -0
- package/dist/platform/conformance.d.ts +25 -0
- package/dist/platform/types.d.ts +47 -0
- package/package.json +41 -3
- package/dist/__tests__/lease-drain-socket.integration.test.d.ts +0 -1
- package/dist/__tests__/lease-drain.fixture.d.ts +0 -1
- package/dist/__tests__/otel.test.d.ts +0 -1
- package/dist/__tests__/worker-context.test.d.ts +0 -1
- package/dist/__tests__/worker-exit.fixture.d.ts +0 -1
- package/dist/__tests__/worker-exit.integration.test.d.ts +0 -1
- package/dist/index.test.d.ts +0 -1
- package/dist/managed-v1.test.d.ts +0 -1
- package/dist/memory.test.d.ts +0 -1
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../burger-build/src/build.ts
|
|
3
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
4
|
+
import { basename, join } from "path";
|
|
5
|
+
|
|
6
|
+
// ../burger-build/src/bun-version.ts
|
|
7
|
+
var MIN_BUN_VERSION = "1.4.2";
|
|
8
|
+
function assertSupportedBun(version = Bun.version) {
|
|
9
|
+
const release = version.split("-")[0];
|
|
10
|
+
if (!Bun.semver.satisfies(release, `>=${MIN_BUN_VERSION}`)) {
|
|
11
|
+
throw new Error(`burger-build needs Bun >= ${MIN_BUN_VERSION} (found ${version}); run \`bun upgrade\``);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ../burger-build/src/gate.ts
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
import { extname, isAbsolute, relative, resolve } from "path";
|
|
18
|
+
var FS_SPECIFIERS = new Set([
|
|
19
|
+
"fs",
|
|
20
|
+
"node:fs",
|
|
21
|
+
"fs/promises",
|
|
22
|
+
"node:fs/promises",
|
|
23
|
+
"burger:fs",
|
|
24
|
+
"burger:fs/promises"
|
|
25
|
+
]);
|
|
26
|
+
var BUN_GLOBAL = /(?<![\w$.'"`])Bun\s*\.\s*[A-Za-z_$]|\bglobalThis\s*\.\s*Bun\b/;
|
|
27
|
+
var IMPORT_META_PATH = /\bimport\.meta\.(?:url|dir|dirname|filename|path)\b/;
|
|
28
|
+
var NATIVE_LOADER_PACKAGE = /[\\/]node_modules[\\/](?:bindings|node-gyp-build)[\\/]/;
|
|
29
|
+
function metafileKeyToPath(key, cwd) {
|
|
30
|
+
if (isAbsolute(key))
|
|
31
|
+
return key;
|
|
32
|
+
const colon = key.indexOf(":");
|
|
33
|
+
const slash = key.indexOf("/");
|
|
34
|
+
if (colon > 0 && (slash === -1 || colon < slash))
|
|
35
|
+
return key;
|
|
36
|
+
return resolve(cwd, key);
|
|
37
|
+
}
|
|
38
|
+
function importerGraph(metafile, cwd) {
|
|
39
|
+
const parents = new Map;
|
|
40
|
+
for (const [key, input] of Object.entries(metafile.inputs)) {
|
|
41
|
+
const importer = metafileKeyToPath(key, cwd);
|
|
42
|
+
for (const record of input.imports) {
|
|
43
|
+
if (record.external)
|
|
44
|
+
continue;
|
|
45
|
+
const child = metafileKeyToPath(record.path, cwd);
|
|
46
|
+
if (child !== importer && !parents.has(child)) {
|
|
47
|
+
parents.set(child, importer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return parents;
|
|
52
|
+
}
|
|
53
|
+
function importerChain(file, parents) {
|
|
54
|
+
const chain = [file];
|
|
55
|
+
const seen = new Set(chain);
|
|
56
|
+
let current = parents.get(file);
|
|
57
|
+
while (current !== undefined && !seen.has(current)) {
|
|
58
|
+
chain.unshift(current);
|
|
59
|
+
seen.add(current);
|
|
60
|
+
current = parents.get(current);
|
|
61
|
+
}
|
|
62
|
+
return chain;
|
|
63
|
+
}
|
|
64
|
+
function loaderFor(file) {
|
|
65
|
+
switch (extname(file)) {
|
|
66
|
+
case ".ts":
|
|
67
|
+
case ".mts":
|
|
68
|
+
case ".cts":
|
|
69
|
+
return "ts";
|
|
70
|
+
case ".tsx":
|
|
71
|
+
return "tsx";
|
|
72
|
+
case ".jsx":
|
|
73
|
+
return "jsx";
|
|
74
|
+
case ".js":
|
|
75
|
+
case ".mjs":
|
|
76
|
+
case ".cjs":
|
|
77
|
+
return "js";
|
|
78
|
+
default:
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function codeOf(file, source) {
|
|
83
|
+
const loader = loaderFor(file);
|
|
84
|
+
if (!loader)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
return new Bun.Transpiler({ loader }).transformSync(source);
|
|
88
|
+
} catch {
|
|
89
|
+
return source;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function scanModule(file, code, importedSpecifiers) {
|
|
93
|
+
const messages = [];
|
|
94
|
+
if (BUN_GLOBAL.test(code)) {
|
|
95
|
+
messages.push("uses the Bun global (Bun.*), which does not exist in Burger");
|
|
96
|
+
}
|
|
97
|
+
if (IMPORT_META_PATH.test(code) && importedSpecifiers.some((specifier) => FS_SPECIFIERS.has(specifier))) {
|
|
98
|
+
messages.push("reads files relative to import.meta; bundling moves this module, so the files are not found at runtime \u2014 import the data statically instead");
|
|
99
|
+
}
|
|
100
|
+
if (NATIVE_LOADER_PACKAGE.test(file)) {
|
|
101
|
+
messages.push("loads a native addon (bindings/node-gyp-build), which Burger cannot run");
|
|
102
|
+
}
|
|
103
|
+
return messages;
|
|
104
|
+
}
|
|
105
|
+
function collectProblems(metafile, violations, cwd) {
|
|
106
|
+
const parents = importerGraph(metafile, cwd);
|
|
107
|
+
const problems = [];
|
|
108
|
+
for (const violation of violations) {
|
|
109
|
+
problems.push({
|
|
110
|
+
message: `import "${violation.specifier}": ${violation.reason}`,
|
|
111
|
+
chain: importerChain(violation.importer, parents)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
for (const [key, input] of Object.entries(metafile.inputs)) {
|
|
115
|
+
const file = metafileKeyToPath(key, cwd);
|
|
116
|
+
if (!isAbsolute(file))
|
|
117
|
+
continue;
|
|
118
|
+
let source;
|
|
119
|
+
try {
|
|
120
|
+
source = readFileSync(file, "utf8");
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const code = codeOf(file, source);
|
|
125
|
+
if (code === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const specifiers = input.imports.flatMap((record) => record.original === undefined ? [record.path] : [record.path, record.original]);
|
|
128
|
+
for (const message of scanModule(file, code, specifiers)) {
|
|
129
|
+
problems.push({ message, chain: importerChain(file, parents) });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return problems;
|
|
133
|
+
}
|
|
134
|
+
function formatProblems(problems, cwd) {
|
|
135
|
+
const lines = [`burger-build: ${problems.length} problem(s) \u2014 this code cannot run on Burger:`];
|
|
136
|
+
for (const problem of problems) {
|
|
137
|
+
lines.push(` \u2717 ${problem.message}`);
|
|
138
|
+
problem.chain.forEach((file, index) => {
|
|
139
|
+
const shown = isAbsolute(file) ? relative(cwd, file) : file;
|
|
140
|
+
lines.push(index === 0 ? ` ${shown}` : ` \u2192 ${shown}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return lines.join(`
|
|
144
|
+
`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../burger-build/src/aliases.ts
|
|
148
|
+
var BURGER_MODULES = [
|
|
149
|
+
"burger:host",
|
|
150
|
+
"burger:fs",
|
|
151
|
+
"burger:fs/promises",
|
|
152
|
+
"burger:path",
|
|
153
|
+
"burger:os",
|
|
154
|
+
"burger:worker_threads",
|
|
155
|
+
"burger:crypto",
|
|
156
|
+
"burger:buffer",
|
|
157
|
+
"burger:sqlite",
|
|
158
|
+
"burger:test"
|
|
159
|
+
];
|
|
160
|
+
var ALIASES = new Map([
|
|
161
|
+
["node:fs", "burger:fs"],
|
|
162
|
+
["fs", "burger:fs"],
|
|
163
|
+
["node:fs/promises", "burger:fs/promises"],
|
|
164
|
+
["fs/promises", "burger:fs/promises"],
|
|
165
|
+
["node:path", "burger:path"],
|
|
166
|
+
["path", "burger:path"],
|
|
167
|
+
["node:os", "burger:os"],
|
|
168
|
+
["os", "burger:os"],
|
|
169
|
+
["node:worker_threads", "burger:worker_threads"],
|
|
170
|
+
["worker_threads", "burger:worker_threads"],
|
|
171
|
+
["node:crypto", "burger:crypto"],
|
|
172
|
+
["crypto", "burger:crypto"],
|
|
173
|
+
["node:buffer", "burger:buffer"],
|
|
174
|
+
["buffer", "burger:buffer"],
|
|
175
|
+
["bun:sqlite", "burger:sqlite"],
|
|
176
|
+
["bun:test", "burger:test"]
|
|
177
|
+
]);
|
|
178
|
+
var NODE_BUILTINS = new Set([
|
|
179
|
+
"_http_agent",
|
|
180
|
+
"_http_client",
|
|
181
|
+
"_http_common",
|
|
182
|
+
"_http_incoming",
|
|
183
|
+
"_http_outgoing",
|
|
184
|
+
"_http_server",
|
|
185
|
+
"_stream_duplex",
|
|
186
|
+
"_stream_passthrough",
|
|
187
|
+
"_stream_readable",
|
|
188
|
+
"_stream_transform",
|
|
189
|
+
"_stream_wrap",
|
|
190
|
+
"_stream_writable",
|
|
191
|
+
"_tls_common",
|
|
192
|
+
"_tls_wrap",
|
|
193
|
+
"assert",
|
|
194
|
+
"assert/strict",
|
|
195
|
+
"async_hooks",
|
|
196
|
+
"buffer",
|
|
197
|
+
"child_process",
|
|
198
|
+
"cluster",
|
|
199
|
+
"console",
|
|
200
|
+
"constants",
|
|
201
|
+
"crypto",
|
|
202
|
+
"dgram",
|
|
203
|
+
"diagnostics_channel",
|
|
204
|
+
"dns",
|
|
205
|
+
"dns/promises",
|
|
206
|
+
"domain",
|
|
207
|
+
"events",
|
|
208
|
+
"fs",
|
|
209
|
+
"fs/promises",
|
|
210
|
+
"http",
|
|
211
|
+
"http2",
|
|
212
|
+
"https",
|
|
213
|
+
"inspector",
|
|
214
|
+
"inspector/promises",
|
|
215
|
+
"module",
|
|
216
|
+
"net",
|
|
217
|
+
"os",
|
|
218
|
+
"path",
|
|
219
|
+
"path/posix",
|
|
220
|
+
"path/win32",
|
|
221
|
+
"perf_hooks",
|
|
222
|
+
"process",
|
|
223
|
+
"punycode",
|
|
224
|
+
"querystring",
|
|
225
|
+
"readline",
|
|
226
|
+
"readline/promises",
|
|
227
|
+
"repl",
|
|
228
|
+
"stream",
|
|
229
|
+
"stream/consumers",
|
|
230
|
+
"stream/promises",
|
|
231
|
+
"stream/web",
|
|
232
|
+
"string_decoder",
|
|
233
|
+
"sys",
|
|
234
|
+
"timers",
|
|
235
|
+
"timers/promises",
|
|
236
|
+
"tls",
|
|
237
|
+
"trace_events",
|
|
238
|
+
"tty",
|
|
239
|
+
"url",
|
|
240
|
+
"util",
|
|
241
|
+
"util/types",
|
|
242
|
+
"v8",
|
|
243
|
+
"vm",
|
|
244
|
+
"wasi",
|
|
245
|
+
"worker_threads",
|
|
246
|
+
"zlib"
|
|
247
|
+
]);
|
|
248
|
+
var BURGER_EXPORTS = {
|
|
249
|
+
"burger:host": ["appId", "dataDir", "logDir", "entryPoint", "config", "connect", "memoryUsage", "exit"],
|
|
250
|
+
"burger:fs": [
|
|
251
|
+
"existsSync",
|
|
252
|
+
"readFileSync",
|
|
253
|
+
"writeFileSync",
|
|
254
|
+
"appendFileSync",
|
|
255
|
+
"mkdirSync",
|
|
256
|
+
"readdirSync",
|
|
257
|
+
"statSync",
|
|
258
|
+
"rmSync",
|
|
259
|
+
"renameSync",
|
|
260
|
+
"chmodSync",
|
|
261
|
+
"unlinkSync",
|
|
262
|
+
"mkdtempSync",
|
|
263
|
+
"promises"
|
|
264
|
+
],
|
|
265
|
+
"burger:fs/promises": [
|
|
266
|
+
"exists",
|
|
267
|
+
"readFile",
|
|
268
|
+
"writeFile",
|
|
269
|
+
"appendFile",
|
|
270
|
+
"mkdir",
|
|
271
|
+
"readdir",
|
|
272
|
+
"stat",
|
|
273
|
+
"rm",
|
|
274
|
+
"rename",
|
|
275
|
+
"chmod",
|
|
276
|
+
"unlink",
|
|
277
|
+
"mkdtemp"
|
|
278
|
+
],
|
|
279
|
+
"burger:path": [
|
|
280
|
+
"join",
|
|
281
|
+
"resolve",
|
|
282
|
+
"dirname",
|
|
283
|
+
"basename",
|
|
284
|
+
"extname",
|
|
285
|
+
"relative",
|
|
286
|
+
"normalize",
|
|
287
|
+
"isAbsolute",
|
|
288
|
+
"sep",
|
|
289
|
+
"delimiter",
|
|
290
|
+
"parse",
|
|
291
|
+
"format"
|
|
292
|
+
],
|
|
293
|
+
"burger:os": ["platform", "arch", "cpus", "totalmem", "freemem", "hostname", "tmpdir", "homedir", "EOL"],
|
|
294
|
+
"burger:worker_threads": ["isMainThread", "workerData", "parentPort", "threadId"],
|
|
295
|
+
"burger:crypto": ["randomUUID", "randomBytes", "createHash", "createHmac", "timingSafeEqual"],
|
|
296
|
+
"burger:buffer": ["Buffer"],
|
|
297
|
+
"burger:sqlite": ["Database"],
|
|
298
|
+
"burger:test": [
|
|
299
|
+
"describe",
|
|
300
|
+
"test",
|
|
301
|
+
"it",
|
|
302
|
+
"beforeEach",
|
|
303
|
+
"afterEach",
|
|
304
|
+
"beforeAll",
|
|
305
|
+
"afterAll",
|
|
306
|
+
"expect",
|
|
307
|
+
"mock",
|
|
308
|
+
"setDefaultTimeout"
|
|
309
|
+
]
|
|
310
|
+
};
|
|
311
|
+
var BURGER_MODULE_SET = new Set(BURGER_MODULES);
|
|
312
|
+
function classifySpecifier(specifier) {
|
|
313
|
+
if (BURGER_MODULE_SET.has(specifier)) {
|
|
314
|
+
return { kind: "burger", target: specifier };
|
|
315
|
+
}
|
|
316
|
+
const alias = ALIASES.get(specifier);
|
|
317
|
+
if (alias) {
|
|
318
|
+
return { kind: "burger", target: alias };
|
|
319
|
+
}
|
|
320
|
+
if (specifier.startsWith("burger:")) {
|
|
321
|
+
return { kind: "forbidden", reason: `"${specifier}" is not a Burger Phase 1 module` };
|
|
322
|
+
}
|
|
323
|
+
if (specifier.startsWith("node:")) {
|
|
324
|
+
return { kind: "forbidden", reason: `"${specifier}" is not provided by Burger Phase 1` };
|
|
325
|
+
}
|
|
326
|
+
if (specifier === "bun" || specifier.startsWith("bun:")) {
|
|
327
|
+
return { kind: "forbidden", reason: `"${specifier}" is a Bun runtime module` };
|
|
328
|
+
}
|
|
329
|
+
if (NODE_BUILTINS.has(specifier)) {
|
|
330
|
+
return { kind: "forbidden", reason: `"${specifier}" is a Node built-in not provided by Burger Phase 1` };
|
|
331
|
+
}
|
|
332
|
+
if (specifier.endsWith(".node")) {
|
|
333
|
+
return { kind: "forbidden", reason: `"${specifier}" is a native addon` };
|
|
334
|
+
}
|
|
335
|
+
return { kind: "other" };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ../burger-build/src/plugin.ts
|
|
339
|
+
var BRIDGE_NAMESPACE = "burger-require";
|
|
340
|
+
var BRIDGE_PREFIX = "cjs-bridge/";
|
|
341
|
+
function bridgeSource(target) {
|
|
342
|
+
const lines = [`import * as m from ${JSON.stringify(target)};`];
|
|
343
|
+
for (const name of BURGER_EXPORTS[target]) {
|
|
344
|
+
lines.push(`export const ${name} = m.${name};`);
|
|
345
|
+
}
|
|
346
|
+
return lines.join(`
|
|
347
|
+
`) + `
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
function burgerResolvePlugin(state) {
|
|
351
|
+
return {
|
|
352
|
+
name: "burger-resolve",
|
|
353
|
+
setup(build) {
|
|
354
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
355
|
+
if (args.kind === "entry-point-build" || args.kind === "entry-point-run") {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const specifierClass = classifySpecifier(args.path);
|
|
359
|
+
if (specifierClass.kind === "other") {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (specifierClass.kind === "forbidden") {
|
|
363
|
+
state.violations.push({ specifier: args.path, importer: args.importer, reason: specifierClass.reason });
|
|
364
|
+
return { path: args.path, external: true };
|
|
365
|
+
}
|
|
366
|
+
if (args.kind === "require-call") {
|
|
367
|
+
return { path: BRIDGE_PREFIX + specifierClass.target, namespace: BRIDGE_NAMESPACE };
|
|
368
|
+
}
|
|
369
|
+
return { path: specifierClass.target, external: true };
|
|
370
|
+
});
|
|
371
|
+
build.onLoad({ filter: /.*/, namespace: BRIDGE_NAMESPACE }, (args) => ({
|
|
372
|
+
contents: bridgeSource(args.path.slice(BRIDGE_PREFIX.length)),
|
|
373
|
+
loader: "js"
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ../burger-build/src/build.ts
|
|
380
|
+
async function buildApp(options) {
|
|
381
|
+
assertSupportedBun();
|
|
382
|
+
const cwd = process.cwd();
|
|
383
|
+
const state = { violations: [] };
|
|
384
|
+
const result = await Bun.build({
|
|
385
|
+
entrypoints: [options.entry],
|
|
386
|
+
target: "browser",
|
|
387
|
+
format: "esm",
|
|
388
|
+
conditions: ["burger"],
|
|
389
|
+
minify: options.minify,
|
|
390
|
+
splitting: false,
|
|
391
|
+
metafile: true,
|
|
392
|
+
throw: false,
|
|
393
|
+
naming: "[name].js",
|
|
394
|
+
plugins: [burgerResolvePlugin(state)]
|
|
395
|
+
});
|
|
396
|
+
if (!result.success) {
|
|
397
|
+
return { ok: false, report: result.logs.map((log) => String(log)).join(`
|
|
398
|
+
`), problems: [] };
|
|
399
|
+
}
|
|
400
|
+
const problems = collectProblems(result.metafile, state.violations, cwd);
|
|
401
|
+
if (problems.length > 0) {
|
|
402
|
+
return { ok: false, report: formatProblems(problems, cwd), problems };
|
|
403
|
+
}
|
|
404
|
+
mkdirSync(options.outdir, { recursive: true });
|
|
405
|
+
const outputs = [];
|
|
406
|
+
for (const artifact of result.outputs) {
|
|
407
|
+
const target = join(options.outdir, basename(artifact.path));
|
|
408
|
+
writeFileSync(target, await artifact.text());
|
|
409
|
+
outputs.push(target);
|
|
410
|
+
}
|
|
411
|
+
return { ok: true, outputs };
|
|
412
|
+
}
|
|
413
|
+
// ../burger-build/src/test-prepare.ts
|
|
414
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
415
|
+
import { dirname, extname as extname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep } from "path";
|
|
416
|
+
var CODE_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx"]);
|
|
417
|
+
var RESOLVE_SUFFIXES = [".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx"];
|
|
418
|
+
var TEST_FILE = /\.test\.ts$/;
|
|
419
|
+
var SKIPPED_DIRECTORIES = new Set(["node_modules", "dist"]);
|
|
420
|
+
var UI_DIRECTORY = "ui";
|
|
421
|
+
var MOCK_MODULE_CALL = /\bmock\.module\(\s*(["'])([^"'\n]+)\1/g;
|
|
422
|
+
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
423
|
+
var RESERVED = new Set([
|
|
424
|
+
"await",
|
|
425
|
+
"break",
|
|
426
|
+
"case",
|
|
427
|
+
"catch",
|
|
428
|
+
"class",
|
|
429
|
+
"const",
|
|
430
|
+
"continue",
|
|
431
|
+
"debugger",
|
|
432
|
+
"default",
|
|
433
|
+
"delete",
|
|
434
|
+
"do",
|
|
435
|
+
"else",
|
|
436
|
+
"enum",
|
|
437
|
+
"export",
|
|
438
|
+
"extends",
|
|
439
|
+
"false",
|
|
440
|
+
"finally",
|
|
441
|
+
"for",
|
|
442
|
+
"function",
|
|
443
|
+
"if",
|
|
444
|
+
"implements",
|
|
445
|
+
"import",
|
|
446
|
+
"in",
|
|
447
|
+
"instanceof",
|
|
448
|
+
"interface",
|
|
449
|
+
"let",
|
|
450
|
+
"new",
|
|
451
|
+
"null",
|
|
452
|
+
"package",
|
|
453
|
+
"private",
|
|
454
|
+
"protected",
|
|
455
|
+
"public",
|
|
456
|
+
"return",
|
|
457
|
+
"static",
|
|
458
|
+
"super",
|
|
459
|
+
"switch",
|
|
460
|
+
"this",
|
|
461
|
+
"throw",
|
|
462
|
+
"true",
|
|
463
|
+
"try",
|
|
464
|
+
"typeof",
|
|
465
|
+
"var",
|
|
466
|
+
"void",
|
|
467
|
+
"while",
|
|
468
|
+
"with",
|
|
469
|
+
"yield",
|
|
470
|
+
"__esModule"
|
|
471
|
+
]);
|
|
472
|
+
function isCodeFile(path) {
|
|
473
|
+
return CODE_EXTENSIONS.has(extname2(path)) && !path.endsWith(".d.ts");
|
|
474
|
+
}
|
|
475
|
+
function walkCode(dir, appRoot, into) {
|
|
476
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
477
|
+
if (SKIPPED_DIRECTORIES.has(entry.name) || entry.name.startsWith("."))
|
|
478
|
+
continue;
|
|
479
|
+
if (entry.name === UI_DIRECTORY && dir === appRoot)
|
|
480
|
+
continue;
|
|
481
|
+
const path = join2(dir, entry.name);
|
|
482
|
+
if (entry.isDirectory())
|
|
483
|
+
walkCode(path, appRoot, into);
|
|
484
|
+
else if (entry.isFile() && isCodeFile(path))
|
|
485
|
+
into.push(path);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function commonAncestor(dirs) {
|
|
489
|
+
const split = dirs.map((dir) => dir.split(sep));
|
|
490
|
+
const first = split[0];
|
|
491
|
+
let length = first.length;
|
|
492
|
+
for (const parts of split.slice(1)) {
|
|
493
|
+
let i = 0;
|
|
494
|
+
while (i < length && i < parts.length && parts[i] === first[i])
|
|
495
|
+
i += 1;
|
|
496
|
+
length = i;
|
|
497
|
+
}
|
|
498
|
+
return first.slice(0, length).join(sep) || sep;
|
|
499
|
+
}
|
|
500
|
+
function isInside(child, parent) {
|
|
501
|
+
const rel = relative2(parent, child);
|
|
502
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
503
|
+
}
|
|
504
|
+
function preparedPath(relativePath) {
|
|
505
|
+
const ext = extname2(relativePath);
|
|
506
|
+
return (CODE_EXTENSIONS.has(ext) ? relativePath.slice(0, -ext.length) : relativePath) + ".js";
|
|
507
|
+
}
|
|
508
|
+
function relativeSpecifier(fromDir, to) {
|
|
509
|
+
const rel = relative2(fromDir, to).split(sep).join("/");
|
|
510
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
511
|
+
}
|
|
512
|
+
function resolveRelativeFile(importerDir, specifier) {
|
|
513
|
+
const base = resolve2(importerDir, specifier);
|
|
514
|
+
const candidates = [
|
|
515
|
+
base,
|
|
516
|
+
...RESOLVE_SUFFIXES.map((suffix) => base + suffix),
|
|
517
|
+
...RESOLVE_SUFFIXES.map((suffix) => join2(base, `index${suffix}`))
|
|
518
|
+
];
|
|
519
|
+
return candidates.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
|
|
520
|
+
}
|
|
521
|
+
function dependencyFileName(specifier) {
|
|
522
|
+
return specifier.replace(/^@/, "").replace(/[\\/]/g, "__") + ".js";
|
|
523
|
+
}
|
|
524
|
+
function rewriteSpecifier(ws, importer, specifier) {
|
|
525
|
+
const specifierClass = classifySpecifier(specifier);
|
|
526
|
+
if (specifierClass.kind === "burger")
|
|
527
|
+
return specifierClass.target;
|
|
528
|
+
if (specifierClass.kind === "forbidden") {
|
|
529
|
+
ws.problems.push({ message: `import "${specifier}": ${specifierClass.reason}`, chain: [importer] });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (specifier.startsWith(".") || isAbsolute2(specifier)) {
|
|
533
|
+
const target = resolveRelativeFile(dirname(importer), specifier);
|
|
534
|
+
if (target === undefined) {
|
|
535
|
+
ws.problems.push({ message: `cannot resolve "${specifier}"`, chain: [importer] });
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (extname2(target) === ".json") {
|
|
539
|
+
if (!isInside(target, ws.root)) {
|
|
540
|
+
ws.problems.push({ message: `"${specifier}" is outside ${ws.root}`, chain: [importer] });
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
ws.jsonFiles.add(target);
|
|
544
|
+
return `${relativeSpecifier(dirname(importer), target)}.js`;
|
|
545
|
+
}
|
|
546
|
+
if (!ws.sources.has(target)) {
|
|
547
|
+
ws.problems.push({
|
|
548
|
+
message: `"${specifier}" resolves to ${target}, which is not under --src or --tests`,
|
|
549
|
+
chain: [importer]
|
|
550
|
+
});
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
return preparedPath(relativeSpecifier(dirname(importer), target));
|
|
554
|
+
}
|
|
555
|
+
if (specifier.startsWith("#")) {
|
|
556
|
+
ws.problems.push({
|
|
557
|
+
message: `package.json "imports" specifier "${specifier}" is not supported in prepared tests`,
|
|
558
|
+
chain: [importer]
|
|
559
|
+
});
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
ws.dependencies.add(specifier);
|
|
563
|
+
return specifier;
|
|
564
|
+
}
|
|
565
|
+
function moduleIdentity(importer, specifier) {
|
|
566
|
+
const specifierClass = classifySpecifier(specifier);
|
|
567
|
+
if (specifierClass.kind === "burger")
|
|
568
|
+
return specifierClass.target;
|
|
569
|
+
if (specifier.startsWith(".") || isAbsolute2(specifier)) {
|
|
570
|
+
return resolveRelativeFile(dirname(importer), specifier) ?? resolve2(dirname(importer), specifier);
|
|
571
|
+
}
|
|
572
|
+
return specifier;
|
|
573
|
+
}
|
|
574
|
+
function staticallyImportedMocks(file, code, imports) {
|
|
575
|
+
const staticImports = new Map;
|
|
576
|
+
for (const record of imports) {
|
|
577
|
+
if (record.kind === "import-statement")
|
|
578
|
+
staticImports.set(moduleIdentity(file, record.path), record.path);
|
|
579
|
+
}
|
|
580
|
+
const messages = [];
|
|
581
|
+
const reported = new Set;
|
|
582
|
+
for (const match of code.matchAll(MOCK_MODULE_CALL)) {
|
|
583
|
+
const mocked = match[2];
|
|
584
|
+
const identity = moduleIdentity(file, mocked);
|
|
585
|
+
const imported = staticImports.get(identity);
|
|
586
|
+
if (imported === undefined || reported.has(identity))
|
|
587
|
+
continue;
|
|
588
|
+
reported.add(identity);
|
|
589
|
+
messages.push(`statically imports "${imported}" and also mocks it with mock.module("${mocked}"); ` + "Burger links static imports before mock.module runs, so the test would use the real module. " + `Call mock.module first, then load the module with \`await import("${imported}")\``);
|
|
590
|
+
}
|
|
591
|
+
return messages;
|
|
592
|
+
}
|
|
593
|
+
function sourcesPlugin(ws) {
|
|
594
|
+
return {
|
|
595
|
+
name: "burger-test-prepare",
|
|
596
|
+
setup(build) {
|
|
597
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
598
|
+
if (args.kind === "entry-point-build" || args.kind === "entry-point-run")
|
|
599
|
+
return;
|
|
600
|
+
if (args.kind === "require-call") {
|
|
601
|
+
ws.problems.push({
|
|
602
|
+
message: `require("${args.path}") cannot run in a Burger module; use import`,
|
|
603
|
+
chain: [args.importer]
|
|
604
|
+
});
|
|
605
|
+
return { path: args.path, external: true };
|
|
606
|
+
}
|
|
607
|
+
return { path: rewriteSpecifier(ws, args.importer, args.path) ?? args.path, external: true };
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function loaderFor2(path) {
|
|
613
|
+
const ext = extname2(path);
|
|
614
|
+
if (ext === ".tsx")
|
|
615
|
+
return "tsx";
|
|
616
|
+
if (ext === ".jsx")
|
|
617
|
+
return "jsx";
|
|
618
|
+
if (ext === ".js" || ext === ".mjs")
|
|
619
|
+
return "js";
|
|
620
|
+
return "ts";
|
|
621
|
+
}
|
|
622
|
+
async function bundleDependency(specifier, shimDir, cwd) {
|
|
623
|
+
const shim = join2(shimDir, dependencyFileName(specifier));
|
|
624
|
+
const quoted = JSON.stringify(specifier);
|
|
625
|
+
writeFileSync2(shim, `export * from ${quoted};
|
|
626
|
+
import * as __burger_ns from ${quoted};
|
|
627
|
+
export default __burger_ns.default;
|
|
628
|
+
`);
|
|
629
|
+
const build = async () => {
|
|
630
|
+
const gate = { violations: [] };
|
|
631
|
+
const result = await Bun.build({
|
|
632
|
+
entrypoints: [shim],
|
|
633
|
+
target: "browser",
|
|
634
|
+
format: "esm",
|
|
635
|
+
conditions: ["burger"],
|
|
636
|
+
splitting: false,
|
|
637
|
+
metafile: true,
|
|
638
|
+
throw: false,
|
|
639
|
+
plugins: [burgerResolvePlugin(gate)]
|
|
640
|
+
});
|
|
641
|
+
return { gate, result };
|
|
642
|
+
};
|
|
643
|
+
let { gate, result } = await build();
|
|
644
|
+
if (!result.success) {
|
|
645
|
+
return { problems: [{ message: `cannot bundle dependency "${specifier}": ${result.logs.map(String).join("; ")}`, chain: [shim] }] };
|
|
646
|
+
}
|
|
647
|
+
const metafile = result.metafile;
|
|
648
|
+
const shimKey = Object.keys(metafile.inputs).find((key) => metafileKeyToPath(key, cwd) === shim);
|
|
649
|
+
const entryRecord = shimKey ? metafile.inputs[shimKey].imports.find((record) => !record.external) : undefined;
|
|
650
|
+
const entryInput = entryRecord ? metafile.inputs[entryRecord.path] : undefined;
|
|
651
|
+
if (entryInput?.format === "cjs") {
|
|
652
|
+
const probe = Bun.spawnSync([process.execPath, "-e", `process.stdout.write(JSON.stringify(Object.keys(require(${quoted}) ?? {})))`], { cwd: shimDir, stdout: "pipe", stderr: "pipe" });
|
|
653
|
+
if (probe.exitCode !== 0) {
|
|
654
|
+
return { problems: [{ message: `cannot read the exports of CommonJS dependency "${specifier}": ${probe.stderr.toString()}`, chain: [shim] }] };
|
|
655
|
+
}
|
|
656
|
+
const names = JSON.parse(probe.stdout.toString()).filter((name) => IDENTIFIER.test(name) && !RESERVED.has(name));
|
|
657
|
+
const named = names.length > 0 ? `export const { ${names.join(", ")} } = __burger_cjs;
|
|
658
|
+
` : "";
|
|
659
|
+
writeFileSync2(shim, `import __burger_cjs from ${quoted};
|
|
660
|
+
export default __burger_cjs;
|
|
661
|
+
${named}`);
|
|
662
|
+
({ gate, result } = await build());
|
|
663
|
+
if (!result.success) {
|
|
664
|
+
return { problems: [{ message: `cannot bundle dependency "${specifier}": ${result.logs.map(String).join("; ")}`, chain: [shim] }] };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const problems = collectProblems(result.metafile, gate.violations, cwd);
|
|
668
|
+
if (problems.length > 0)
|
|
669
|
+
return { problems };
|
|
670
|
+
return { code: await result.outputs[0].text() };
|
|
671
|
+
}
|
|
672
|
+
async function prepareTests(options) {
|
|
673
|
+
assertSupportedBun();
|
|
674
|
+
const cwd = process.cwd();
|
|
675
|
+
const src = resolve2(options.src);
|
|
676
|
+
const tests = options.tests === undefined ? undefined : resolve2(options.tests);
|
|
677
|
+
const out = resolve2(options.out);
|
|
678
|
+
for (const dir of [src, tests]) {
|
|
679
|
+
if (dir !== undefined && !(existsSync(dir) && statSync(dir).isDirectory())) {
|
|
680
|
+
return { ok: false, report: `burger-build: ${dir} is not a directory` };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const inputs = tests === undefined ? [src] : [src, tests];
|
|
684
|
+
if (inputs.some((dir) => isInside(dir, out))) {
|
|
685
|
+
return { ok: false, report: `burger-build: --out ${out} must not contain --src or --tests` };
|
|
686
|
+
}
|
|
687
|
+
const root = commonAncestor(inputs);
|
|
688
|
+
const files = [];
|
|
689
|
+
for (const dir of new Set(inputs))
|
|
690
|
+
walkCode(dir, cwd, files);
|
|
691
|
+
files.sort();
|
|
692
|
+
const byOutput = new Map;
|
|
693
|
+
for (const file of files) {
|
|
694
|
+
const output = preparedPath(relative2(root, file));
|
|
695
|
+
const clash = byOutput.get(output);
|
|
696
|
+
if (clash !== undefined) {
|
|
697
|
+
return { ok: false, report: `burger-build: ${clash} and ${file} would both be prepared as ${output}` };
|
|
698
|
+
}
|
|
699
|
+
byOutput.set(output, file);
|
|
700
|
+
}
|
|
701
|
+
rmSync(out, { recursive: true, force: true });
|
|
702
|
+
mkdirSync2(out, { recursive: true });
|
|
703
|
+
const ws = {
|
|
704
|
+
root,
|
|
705
|
+
sources: new Set(files),
|
|
706
|
+
jsonFiles: new Set,
|
|
707
|
+
dependencies: new Set,
|
|
708
|
+
problems: []
|
|
709
|
+
};
|
|
710
|
+
if (files.length > 0) {
|
|
711
|
+
const result = await Bun.build({
|
|
712
|
+
entrypoints: files,
|
|
713
|
+
root,
|
|
714
|
+
outdir: out,
|
|
715
|
+
target: "browser",
|
|
716
|
+
format: "esm",
|
|
717
|
+
conditions: ["burger"],
|
|
718
|
+
splitting: false,
|
|
719
|
+
minify: false,
|
|
720
|
+
throw: false,
|
|
721
|
+
naming: "[dir]/[name].js",
|
|
722
|
+
plugins: [sourcesPlugin(ws)]
|
|
723
|
+
});
|
|
724
|
+
if (!result.success) {
|
|
725
|
+
return { ok: false, report: result.logs.map(String).join(`
|
|
726
|
+
`) };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
for (const file of files) {
|
|
730
|
+
const output = join2(out, preparedPath(relative2(root, file)));
|
|
731
|
+
const prepared = readFileSync2(output, "utf8").replace(MOCK_MODULE_CALL, (call, quote, specifier) => {
|
|
732
|
+
const rewritten = rewriteSpecifier(ws, file, specifier);
|
|
733
|
+
return rewritten === undefined ? call : call.replace(`${quote}${specifier}${quote}`, JSON.stringify(rewritten));
|
|
734
|
+
});
|
|
735
|
+
writeFileSync2(output, prepared);
|
|
736
|
+
const source = readFileSync2(file, "utf8");
|
|
737
|
+
const code = codeOf(file, source) ?? source;
|
|
738
|
+
const imports = new Bun.Transpiler({ loader: loaderFor2(file) }).scan(source).imports;
|
|
739
|
+
const specifiers = imports.map((record) => record.path);
|
|
740
|
+
for (const message of [...scanModule(file, code, specifiers), ...staticallyImportedMocks(file, code, imports)]) {
|
|
741
|
+
ws.problems.push({ message, chain: [file] });
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
for (const json of ws.jsonFiles) {
|
|
745
|
+
const target = join2(out, `${relative2(root, json)}.js`);
|
|
746
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
747
|
+
const value = JSON.parse(readFileSync2(json, "utf8"));
|
|
748
|
+
writeFileSync2(target, `export default ${JSON.stringify(value)};
|
|
749
|
+
`);
|
|
750
|
+
}
|
|
751
|
+
const importMap = { imports: {} };
|
|
752
|
+
if (ws.dependencies.size > 0) {
|
|
753
|
+
const shimDir = join2(root, "node_modules", ".cache", "burger-build", "shims");
|
|
754
|
+
mkdirSync2(shimDir, { recursive: true });
|
|
755
|
+
mkdirSync2(join2(out, "node_modules"), { recursive: true });
|
|
756
|
+
for (const specifier of [...ws.dependencies].sort()) {
|
|
757
|
+
const bundled = await bundleDependency(specifier, shimDir, cwd);
|
|
758
|
+
if ("problems" in bundled) {
|
|
759
|
+
ws.problems.push(...bundled.problems);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const target = join2(out, "node_modules", dependencyFileName(specifier));
|
|
763
|
+
writeFileSync2(target, bundled.code);
|
|
764
|
+
importMap.imports[specifier] = target;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (ws.problems.length > 0) {
|
|
768
|
+
return { ok: false, report: formatProblems(ws.problems, cwd) };
|
|
769
|
+
}
|
|
770
|
+
const importMapPath = join2(out, "importmap.json");
|
|
771
|
+
writeFileSync2(importMapPath, `${JSON.stringify(importMap, null, 2)}
|
|
772
|
+
`);
|
|
773
|
+
const plan = {
|
|
774
|
+
importMap: importMapPath,
|
|
775
|
+
tests: files.filter((file) => TEST_FILE.test(file)).map((file) => ({ source: file, output: join2(out, preparedPath(relative2(root, file))) }))
|
|
776
|
+
};
|
|
777
|
+
writeFileSync2(join2(out, "test-plan.json"), `${JSON.stringify(plan, null, 2)}
|
|
778
|
+
`);
|
|
779
|
+
return { ok: true, plan, importMap };
|
|
780
|
+
}
|
|
781
|
+
export {
|
|
782
|
+
MIN_BUN_VERSION,
|
|
783
|
+
buildApp,
|
|
784
|
+
prepareTests
|
|
785
|
+
};
|