@stll/oxlint-config 1.5.2 → 1.6.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.
Files changed (37) hide show
  1. package/README.md +89 -6
  2. package/dist/dependabot-cli.cjs +3 -1
  3. package/dist/dependabot-cli.cjs.map +1 -1
  4. package/dist/dependabot-cli.mjs +4 -2
  5. package/dist/dependabot-cli.mjs.map +1 -1
  6. package/dist/dependabot.cjs +2 -0
  7. package/dist/dependabot.d.cts +17 -2
  8. package/dist/dependabot.d.mts +17 -2
  9. package/dist/dependabot.mjs +2 -2
  10. package/dist/engine-support-cli.cjs +96 -0
  11. package/dist/engine-support-cli.cjs.map +1 -0
  12. package/dist/engine-support-cli.d.cts +1 -0
  13. package/dist/engine-support-cli.d.mts +1 -0
  14. package/dist/engine-support-cli.mjs +94 -0
  15. package/dist/engine-support-cli.mjs.map +1 -0
  16. package/dist/engine-support.cjs +29 -0
  17. package/dist/engine-support.cjs.map +1 -0
  18. package/dist/engine-support.d.cts +21 -0
  19. package/dist/engine-support.d.mts +21 -0
  20. package/dist/engine-support.mjs +26 -0
  21. package/dist/engine-support.mjs.map +1 -0
  22. package/dist/toolchain-dependabot.cjs +119 -13
  23. package/dist/toolchain-dependabot.cjs.map +1 -1
  24. package/dist/toolchain-dependabot.mjs +96 -14
  25. package/dist/toolchain-dependabot.mjs.map +1 -1
  26. package/dist/toolchain-guard.cjs +16 -4
  27. package/dist/toolchain-guard.cjs.map +1 -1
  28. package/dist/toolchain-guard.mjs +17 -5
  29. package/dist/toolchain-guard.mjs.map +1 -1
  30. package/dist/wasi-loader-normalizer-cli.cjs +290 -0
  31. package/dist/wasi-loader-normalizer-cli.cjs.map +1 -0
  32. package/dist/wasi-loader-normalizer-cli.d.cts +1 -0
  33. package/dist/wasi-loader-normalizer-cli.d.mts +1 -0
  34. package/dist/wasi-loader-normalizer-cli.mjs +290 -0
  35. package/dist/wasi-loader-normalizer-cli.mjs.map +1 -0
  36. package/package.json +19 -2
  37. package/toolchain.json +1 -1
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ let node_fs_promises = require("node:fs/promises");
4
+ //#region src/wasi-loader-normalizer.ts
5
+ const GENERATOR_DIRECTIVE = /^\s*(?:\/\/\s*@ts-nocheck\b.*|\/\*\s*(?:eslint-disable(?:\s+[^*]*)?|prettier-ignore)\s*\*\/|\/\/\s*(?:eslint|prettier|biome)-disable\b.*)\r?\n?/gmu;
6
+ const GENERATED_TYPECHECK_OPT_OUT = "// @ts-nocheck -- generated by @napi-rs/cli";
7
+ const ARTIFACT_METADATA_PREFIX = "// napi-rs-artifact-metadata:";
8
+ const GENERATED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {
9
+ let __workerExecArgv = __getWasiWorkerExecArgv()
10
+ while (true) {
11
+ try {
12
+ return new Worker(filename, {
13
+ env: process.env,
14
+ execArgv: __workerExecArgv,
15
+ workerData: {
16
+ hostRoot: __hostRoot,
17
+ rootDir: __rootDir,
18
+ crashFlag: __wasiThreadCrashFlag,
19
+ crashReport: __wasiThreadCrashReport,
20
+ addonCrashFlag: __wasiAddonCrashFlag,
21
+ },
22
+ })
23
+ } catch (error) {
24
+ if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
25
+ throw error
26
+ }
27
+ const __nextWorkerExecArgv =
28
+ __removeInvalidWasiWorkerExecArgv(__workerExecArgv, error)
29
+ if (!__nextWorkerExecArgv) {
30
+ throw error
31
+ }
32
+ __workerExecArgv = __nextWorkerExecArgv
33
+ }
34
+ }
35
+ }`;
36
+ const NORMALIZED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {
37
+ const __workerData = {
38
+ hostRoot: __hostRoot,
39
+ rootDir: __rootDir,
40
+ crashFlag: __wasiThreadCrashFlag,
41
+ crashReport: __wasiThreadCrashReport,
42
+ addonCrashFlag: __wasiAddonCrashFlag,
43
+ }
44
+ const __workerOptions = {
45
+ env: process.env,
46
+ execArgv: __getWasiWorkerExecArgv(),
47
+ workerData: __workerData,
48
+ }
49
+ while (true) {
50
+ try {
51
+ return new Worker(filename, __workerOptions)
52
+ } catch (error) {
53
+ if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
54
+ throw error
55
+ }
56
+ const __nextWorkerExecArgv = __removeInvalidWasiWorkerExecArgv(
57
+ __workerOptions.execArgv,
58
+ error,
59
+ )
60
+ if (!__nextWorkerExecArgv) {
61
+ throw error
62
+ }
63
+ __workerOptions.execArgv = __nextWorkerExecArgv
64
+ }
65
+ }
66
+ }`;
67
+ const GENERATED_BROWSER_WORKER = `const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
68
+ type: 'module',
69
+ })`;
70
+ const NORMALIZED_BROWSER_WORKER = `const __workerData = {
71
+ addonCrashFlag: __wasiAddonCrashFlag,
72
+ }
73
+ const __workerOptions = {
74
+ type: 'module',
75
+ workerData: __workerData,
76
+ }
77
+ const worker = new Worker(
78
+ new URL('./wasi-worker-browser.mjs', import.meta.url),
79
+ __workerOptions,
80
+ )`;
81
+ const SAFE_SCHEDULER_HELPER = `function __invokeWasmCleanupPollScheduler(schedule, settle, reject) {
82
+ let finishedDuringScheduling = false
83
+ let scheduling = true
84
+ const finish = () => {
85
+ if (scheduling) {
86
+ finishedDuringScheduling = true
87
+ return
88
+ }
89
+ settle()
90
+ }
91
+ try {
92
+ schedule(finish)
93
+ } catch (error) {
94
+ reject(error)
95
+ return
96
+ }
97
+ scheduling = false
98
+ if (finishedDuringScheduling) {
99
+ settle()
100
+ }
101
+ }
102
+
103
+ `;
104
+ const RUNTIME_POLL_MARKER = "/**\n * One turn of the runtime-work poll.";
105
+ const UNSAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve) => {
106
+ __scheduleMacrotask(resolve)
107
+ })`;
108
+ const SAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve, reject) => {
109
+ __invokeWasmCleanupPollScheduler(
110
+ __scheduleMacrotask,
111
+ resolve,
112
+ reject,
113
+ )
114
+ })`;
115
+ const UNSAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve) => {
116
+ let settled = false`;
117
+ const SAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve, reject) => {
118
+ let settled = false`;
119
+ const UNSAFE_RUNTIME_POLL_TIMER = `__scheduleTimer(() => {
120
+ pace.arrivals++
121
+ settle()
122
+ }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)`;
123
+ const SAFE_RUNTIME_POLL_TIMER = `__invokeWasmCleanupPollScheduler(
124
+ (finish) => {
125
+ __scheduleTimer(() => {
126
+ pace.arrivals++
127
+ finish()
128
+ }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)
129
+ },
130
+ settle,
131
+ reject,
132
+ )`;
133
+ const UNSAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {
134
+ __scheduleMacrotask(settle)
135
+ return
136
+ }`;
137
+ const SAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {
138
+ __invokeWasmCleanupPollScheduler(
139
+ __scheduleMacrotask,
140
+ settle,
141
+ reject,
142
+ )
143
+ return
144
+ }`;
145
+ const UNSAFE_ASYNC_WORK_POLL = `await new Promise((resolve) => {
146
+ __scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
147
+ })`;
148
+ const SAFE_ASYNC_WORK_POLL = `await new Promise((resolve, reject) => {
149
+ __invokeWasmCleanupPollScheduler(
150
+ (finish) => {
151
+ __scheduleTimer(finish, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
152
+ },
153
+ resolve,
154
+ reject,
155
+ )
156
+ })`;
157
+ const LOADER_KINDS_BY_SUFFIX = {
158
+ ".wasi-browser.js": "browser",
159
+ ".wasi.cjs": "commonjs"
160
+ };
161
+ const BOTH_LOADERS = {
162
+ browser: 1,
163
+ commonjs: 1
164
+ };
165
+ const WASI_LOADER_TRANSFORMATIONS = [
166
+ {
167
+ name: "cleanup handshake",
168
+ anchor: UNSAFE_CLEANUP_HANDSHAKE,
169
+ replacement: SAFE_CLEANUP_HANDSHAKE,
170
+ expectedOccurrences: BOTH_LOADERS
171
+ },
172
+ {
173
+ name: "runtime poll promise",
174
+ anchor: UNSAFE_RUNTIME_POLL_PROMISE,
175
+ replacement: SAFE_RUNTIME_POLL_PROMISE,
176
+ expectedOccurrences: BOTH_LOADERS
177
+ },
178
+ {
179
+ name: "runtime poll timer",
180
+ anchor: UNSAFE_RUNTIME_POLL_TIMER,
181
+ replacement: SAFE_RUNTIME_POLL_TIMER,
182
+ expectedOccurrences: BOTH_LOADERS
183
+ },
184
+ {
185
+ name: "runtime poll macrotask",
186
+ anchor: UNSAFE_RUNTIME_POLL_MACROTASK,
187
+ replacement: SAFE_RUNTIME_POLL_MACROTASK,
188
+ expectedOccurrences: BOTH_LOADERS
189
+ },
190
+ {
191
+ name: "async work poll",
192
+ anchor: UNSAFE_ASYNC_WORK_POLL,
193
+ replacement: SAFE_ASYNC_WORK_POLL,
194
+ expectedOccurrences: BOTH_LOADERS
195
+ },
196
+ {
197
+ name: "worker construction",
198
+ anchor: GENERATED_WASI_WORKER_FACTORY,
199
+ replacement: NORMALIZED_WASI_WORKER_FACTORY,
200
+ expectedOccurrences: {
201
+ browser: 0,
202
+ commonjs: 1
203
+ }
204
+ },
205
+ {
206
+ name: "worker construction",
207
+ anchor: GENERATED_BROWSER_WORKER,
208
+ replacement: NORMALIZED_BROWSER_WORKER,
209
+ expectedOccurrences: {
210
+ browser: 1,
211
+ commonjs: 0
212
+ }
213
+ },
214
+ {
215
+ name: "scheduler helper",
216
+ anchor: RUNTIME_POLL_MARKER,
217
+ replacement: `${SAFE_SCHEDULER_HELPER}${RUNTIME_POLL_MARKER}`,
218
+ expectedOccurrences: BOTH_LOADERS
219
+ }
220
+ ];
221
+ const countOccurrences = (source, value) => source.split(value).length - 1;
222
+ const loaderKindFromFile = (file) => {
223
+ if (file.endsWith(".wasi-browser.js")) return "browser";
224
+ if (file.endsWith(".wasi.cjs")) return "commonjs";
225
+ throw new Error(`Unsupported WASI loader file ${file}: expected a name ending in ${Object.keys(LOADER_KINDS_BY_SUFFIX).join(" or ")}`);
226
+ };
227
+ const applyTransformations = (source, file, loaderKind) => {
228
+ let result = source;
229
+ for (const transformation of WASI_LOADER_TRANSFORMATIONS) {
230
+ const expected = transformation.expectedOccurrences[loaderKind];
231
+ const replacementCount = countOccurrences(result, transformation.replacement);
232
+ const anchorsPerReplacement = countOccurrences(transformation.replacement, transformation.anchor);
233
+ const anchorCount = countOccurrences(result, transformation.anchor) - replacementCount * anchorsPerReplacement;
234
+ if (expected === 0 && anchorCount === 0 && replacementCount === 0) continue;
235
+ if (anchorCount === 0 && replacementCount === expected) continue;
236
+ if (anchorCount !== expected || replacementCount !== 0) throw new Error(`WASI loader transformation "${transformation.name}" did not match ${file}: expected ${expected}, found anchors=${anchorCount}, replacements=${replacementCount}`);
237
+ result = result.replaceAll(transformation.anchor, transformation.replacement);
238
+ }
239
+ return result;
240
+ };
241
+ const addTypecheckHeader = (source) => {
242
+ if (!source.startsWith(ARTIFACT_METADATA_PREFIX)) return `${GENERATED_TYPECHECK_OPT_OUT}\n${source}`;
243
+ const lineEnd = source.indexOf("\n");
244
+ if (lineEnd < 0) return `${source}\n${GENERATED_TYPECHECK_OPT_OUT}\n`;
245
+ return `${source.slice(0, lineEnd + 1)}${GENERATED_TYPECHECK_OPT_OUT}\n${source.slice(lineEnd + 1)}`;
246
+ };
247
+ /** Preserve generated scheduling and add only targeted safety guards. */
248
+ const normalizeGeneratedLoader = (source, { keepGeneratedTypecheckOptOut = false, file = "<input>" } = {}) => {
249
+ if (source.includes("\r\n")) throw new Error(`WASI loader transformation "LF line endings" did not match ${file}`);
250
+ const withoutDirective = source.replace(GENERATOR_DIRECTIVE, "");
251
+ const normalized = applyTransformations(withoutDirective, file, loaderKindFromFile(file)).replace(/[ \t]+$/gmu, "");
252
+ return keepGeneratedTypecheckOptOut ? addTypecheckHeader(normalized) : normalized;
253
+ };
254
+ const usage = "Usage: stll-normalize-wasi-loaders [--check] <browser-loader> <commonjs-loader>";
255
+ const runWasiLoaderNormalizer = async (args) => {
256
+ const check = args.at(0) === "--check";
257
+ const paths = check ? args.slice(1) : args;
258
+ if (paths.length !== 2 || paths.some((path) => path.startsWith("--"))) throw new Error(usage);
259
+ const loaders = await Promise.all(paths.map(async (path) => {
260
+ const source = await (0, node_fs_promises.readFile)(path, "utf8");
261
+ return {
262
+ normalized: normalizeGeneratedLoader(source, {
263
+ keepGeneratedTypecheckOptOut: true,
264
+ file: path
265
+ }),
266
+ path,
267
+ source
268
+ };
269
+ }));
270
+ let drifted = false;
271
+ for (const { normalized, path, source } of loaders) {
272
+ if (source === normalized) continue;
273
+ if (check) {
274
+ process.stderr.write(`${path} is not normalized\n`);
275
+ drifted = true;
276
+ continue;
277
+ }
278
+ await (0, node_fs_promises.writeFile)(path, normalized);
279
+ }
280
+ if (drifted) throw new Error("generated WASI loaders have drifted");
281
+ };
282
+ //#endregion
283
+ //#region src/wasi-loader-normalizer-cli.ts
284
+ runWasiLoaderNormalizer(process.argv.slice(2)).catch((error) => {
285
+ process.stderr.write(`wasi-loader-normalizer: ${error instanceof Error ? error.message : String(error)}\n`);
286
+ process.exitCode = 1;
287
+ });
288
+ //#endregion
289
+
290
+ //# sourceMappingURL=wasi-loader-normalizer-cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasi-loader-normalizer-cli.cjs","names":["readFile","writeFile"],"sources":["../src/wasi-loader-normalizer.ts","../src/wasi-loader-normalizer-cli.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\n\nconst GENERATOR_DIRECTIVE =\n /^\\s*(?:\\/\\/\\s*@ts-nocheck\\b.*|\\/\\*\\s*(?:eslint-disable(?:\\s+[^*]*)?|prettier-ignore)\\s*\\*\\/|\\/\\/\\s*(?:eslint|prettier|biome)-disable\\b.*)\\r?\\n?/gmu;\n\nconst GENERATED_TYPECHECK_OPT_OUT =\n \"// @ts-nocheck -- generated by @napi-rs/cli\";\nconst ARTIFACT_METADATA_PREFIX = \"// napi-rs-artifact-metadata:\";\nconst GENERATED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {\n let __workerExecArgv = __getWasiWorkerExecArgv()\n while (true) {\n try {\n return new Worker(filename, {\n env: process.env,\n execArgv: __workerExecArgv,\n workerData: {\n hostRoot: __hostRoot,\n rootDir: __rootDir,\n crashFlag: __wasiThreadCrashFlag,\n crashReport: __wasiThreadCrashReport,\n addonCrashFlag: __wasiAddonCrashFlag,\n },\n })\n } catch (error) {\n if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {\n throw error\n }\n const __nextWorkerExecArgv =\n __removeInvalidWasiWorkerExecArgv(__workerExecArgv, error)\n if (!__nextWorkerExecArgv) {\n throw error\n }\n __workerExecArgv = __nextWorkerExecArgv\n }\n }\n}`;\nconst NORMALIZED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {\n const __workerData = {\n hostRoot: __hostRoot,\n rootDir: __rootDir,\n crashFlag: __wasiThreadCrashFlag,\n crashReport: __wasiThreadCrashReport,\n addonCrashFlag: __wasiAddonCrashFlag,\n }\n const __workerOptions = {\n env: process.env,\n execArgv: __getWasiWorkerExecArgv(),\n workerData: __workerData,\n }\n while (true) {\n try {\n return new Worker(filename, __workerOptions)\n } catch (error) {\n if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {\n throw error\n }\n const __nextWorkerExecArgv = __removeInvalidWasiWorkerExecArgv(\n __workerOptions.execArgv,\n error,\n )\n if (!__nextWorkerExecArgv) {\n throw error\n }\n __workerOptions.execArgv = __nextWorkerExecArgv\n }\n }\n}`;\nconst GENERATED_BROWSER_WORKER = `const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {\n type: 'module',\n })`;\n\nconst NORMALIZED_BROWSER_WORKER = `const __workerData = {\n addonCrashFlag: __wasiAddonCrashFlag,\n }\n const __workerOptions = {\n type: 'module',\n workerData: __workerData,\n }\n const worker = new Worker(\n new URL('./wasi-worker-browser.mjs', import.meta.url),\n __workerOptions,\n )`;\nconst SAFE_SCHEDULER_HELPER = `function __invokeWasmCleanupPollScheduler(schedule, settle, reject) {\n let finishedDuringScheduling = false\n let scheduling = true\n const finish = () => {\n if (scheduling) {\n finishedDuringScheduling = true\n return\n }\n settle()\n }\n try {\n schedule(finish)\n } catch (error) {\n reject(error)\n return\n }\n scheduling = false\n if (finishedDuringScheduling) {\n settle()\n }\n}\n\n`;\nconst RUNTIME_POLL_MARKER = \"/**\\n * One turn of the runtime-work poll.\";\nconst UNSAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve) => {\n __scheduleMacrotask(resolve)\n })`;\nconst SAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve, reject) => {\n __invokeWasmCleanupPollScheduler(\n __scheduleMacrotask,\n resolve,\n reject,\n )\n })`;\nconst UNSAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve) => {\n let settled = false`;\nconst SAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve, reject) => {\n let settled = false`;\nconst UNSAFE_RUNTIME_POLL_TIMER = `__scheduleTimer(() => {\n pace.arrivals++\n settle()\n }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)`;\nconst SAFE_RUNTIME_POLL_TIMER = `__invokeWasmCleanupPollScheduler(\n (finish) => {\n __scheduleTimer(() => {\n pace.arrivals++\n finish()\n }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)\n },\n settle,\n reject,\n )`;\nconst UNSAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {\n __scheduleMacrotask(settle)\n return\n }`;\nconst SAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {\n __invokeWasmCleanupPollScheduler(\n __scheduleMacrotask,\n settle,\n reject,\n )\n return\n }`;\nconst UNSAFE_ASYNC_WORK_POLL = `await new Promise((resolve) => {\n __scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)\n })`;\nconst SAFE_ASYNC_WORK_POLL = `await new Promise((resolve, reject) => {\n __invokeWasmCleanupPollScheduler(\n (finish) => {\n __scheduleTimer(finish, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)\n },\n resolve,\n reject,\n )\n })`;\ntype Transformation = {\n name: string;\n anchor: string;\n replacement: string;\n expectedOccurrences: Record<LoaderKind, number>;\n};\n\nconst LOADER_KINDS_BY_SUFFIX = {\n \".wasi-browser.js\": \"browser\",\n \".wasi.cjs\": \"commonjs\",\n} as const;\n\ntype LoaderKind = (typeof LOADER_KINDS_BY_SUFFIX)[keyof typeof LOADER_KINDS_BY_SUFFIX];\n\nconst BOTH_LOADERS = { browser: 1, commonjs: 1 } as const;\nconst BROWSER_LOADER = { browser: 1, commonjs: 0 } as const;\nconst COMMONJS_LOADER = { browser: 0, commonjs: 1 } as const;\n\nexport const WASI_LOADER_TRANSFORMATIONS = [\n {\n name: \"cleanup handshake\",\n anchor: UNSAFE_CLEANUP_HANDSHAKE,\n replacement: SAFE_CLEANUP_HANDSHAKE,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll promise\",\n anchor: UNSAFE_RUNTIME_POLL_PROMISE,\n replacement: SAFE_RUNTIME_POLL_PROMISE,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll timer\",\n anchor: UNSAFE_RUNTIME_POLL_TIMER,\n replacement: SAFE_RUNTIME_POLL_TIMER,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll macrotask\",\n anchor: UNSAFE_RUNTIME_POLL_MACROTASK,\n replacement: SAFE_RUNTIME_POLL_MACROTASK,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"async work poll\",\n anchor: UNSAFE_ASYNC_WORK_POLL,\n replacement: SAFE_ASYNC_WORK_POLL,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"worker construction\",\n anchor: GENERATED_WASI_WORKER_FACTORY,\n replacement: NORMALIZED_WASI_WORKER_FACTORY,\n expectedOccurrences: COMMONJS_LOADER,\n },\n {\n name: \"worker construction\",\n anchor: GENERATED_BROWSER_WORKER,\n replacement: NORMALIZED_BROWSER_WORKER,\n expectedOccurrences: BROWSER_LOADER,\n },\n {\n name: \"scheduler helper\",\n anchor: RUNTIME_POLL_MARKER,\n replacement: `${SAFE_SCHEDULER_HELPER}${RUNTIME_POLL_MARKER}`,\n expectedOccurrences: BOTH_LOADERS,\n },\n] satisfies Transformation[];\n\nconst countOccurrences = (source: string, value: string) =>\n source.split(value).length - 1;\n\nconst loaderKindFromFile = (file: string): LoaderKind => {\n if (file.endsWith(\".wasi-browser.js\")) return \"browser\";\n if (file.endsWith(\".wasi.cjs\")) return \"commonjs\";\n throw new Error(\n `Unsupported WASI loader file ${file}: expected a name ending in ${Object.keys(LOADER_KINDS_BY_SUFFIX).join(\" or \")}`,\n );\n};\n\nconst applyTransformations = (\n source: string,\n file: string,\n loaderKind: LoaderKind,\n) => {\n let result = source;\n for (const transformation of WASI_LOADER_TRANSFORMATIONS) {\n const expected = transformation.expectedOccurrences[loaderKind];\n const replacementCount = countOccurrences(\n result,\n transformation.replacement,\n );\n const anchorsPerReplacement = countOccurrences(\n transformation.replacement,\n transformation.anchor,\n );\n const anchorCount =\n countOccurrences(result, transformation.anchor) -\n replacementCount * anchorsPerReplacement;\n\n if (expected === 0 && anchorCount === 0 && replacementCount === 0) continue;\n if (anchorCount === 0 && replacementCount === expected) continue;\n if (anchorCount !== expected || replacementCount !== 0)\n throw new Error(\n `WASI loader transformation \"${transformation.name}\" did not match ${file}: expected ${expected}, found anchors=${anchorCount}, replacements=${replacementCount}`,\n );\n result = result.replaceAll(\n transformation.anchor,\n transformation.replacement,\n );\n }\n return result;\n};\nconst addTypecheckHeader = (source: string) => {\n if (!source.startsWith(ARTIFACT_METADATA_PREFIX)) {\n return `${GENERATED_TYPECHECK_OPT_OUT}\\n${source}`;\n }\n const lineEnd = source.indexOf(\"\\n\");\n if (lineEnd < 0) {\n return `${source}\\n${GENERATED_TYPECHECK_OPT_OUT}\\n`;\n }\n return `${source.slice(0, lineEnd + 1)}${GENERATED_TYPECHECK_OPT_OUT}\\n${source.slice(lineEnd + 1)}`;\n};\n/** Preserve generated scheduling and add only targeted safety guards. */\nexport const normalizeGeneratedLoader = (\n source: string,\n {\n keepGeneratedTypecheckOptOut = false,\n file = \"<input>\",\n }: {\n keepGeneratedTypecheckOptOut?: boolean;\n file?: string;\n } = {},\n) => {\n if (source.includes(\"\\r\\n\"))\n throw new Error(\n `WASI loader transformation \"LF line endings\" did not match ${file}`,\n );\n\n const withoutDirective = source.replace(GENERATOR_DIRECTIVE, \"\");\n const normalized = applyTransformations(\n withoutDirective,\n file,\n loaderKindFromFile(file),\n ).replace(/[ \\t]+$/gmu, \"\");\n\n return keepGeneratedTypecheckOptOut\n ? addTypecheckHeader(normalized)\n : normalized;\n};\n\nconst usage =\n \"Usage: stll-normalize-wasi-loaders [--check] <browser-loader> <commonjs-loader>\";\n\nexport const runWasiLoaderNormalizer = async (args: string[]) => {\n const check = args.at(0) === \"--check\";\n const paths = check ? args.slice(1) : args;\n if (paths.length !== 2 || paths.some((path) => path.startsWith(\"--\")))\n throw new Error(usage);\n\n const loaders = await Promise.all(\n paths.map(async (path) => {\n const source = await readFile(path, \"utf8\");\n const normalized = normalizeGeneratedLoader(source, {\n keepGeneratedTypecheckOptOut: true,\n file: path,\n });\n return { normalized, path, source };\n }),\n );\n\n let drifted = false;\n for (const { normalized, path, source } of loaders) {\n if (source === normalized) continue;\n if (check) {\n process.stderr.write(`${path} is not normalized\\n`);\n drifted = true;\n continue;\n }\n await writeFile(path, normalized);\n }\n if (drifted) throw new Error(\"generated WASI loaders have drifted\");\n};\n","#!/usr/bin/env node\nimport { runWasiLoaderNormalizer } from \"./wasi-loader-normalizer\";\n\nrunWasiLoaderNormalizer(process.argv.slice(2)).catch((error: unknown) => {\n process.stderr.write(\n `wasi-loader-normalizer: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exitCode = 1;\n});\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ //#region src/wasi-loader-normalizer.ts
4
+ const GENERATOR_DIRECTIVE = /^\s*(?:\/\/\s*@ts-nocheck\b.*|\/\*\s*(?:eslint-disable(?:\s+[^*]*)?|prettier-ignore)\s*\*\/|\/\/\s*(?:eslint|prettier|biome)-disable\b.*)\r?\n?/gmu;
5
+ const GENERATED_TYPECHECK_OPT_OUT = "// @ts-nocheck -- generated by @napi-rs/cli";
6
+ const ARTIFACT_METADATA_PREFIX = "// napi-rs-artifact-metadata:";
7
+ const GENERATED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {
8
+ let __workerExecArgv = __getWasiWorkerExecArgv()
9
+ while (true) {
10
+ try {
11
+ return new Worker(filename, {
12
+ env: process.env,
13
+ execArgv: __workerExecArgv,
14
+ workerData: {
15
+ hostRoot: __hostRoot,
16
+ rootDir: __rootDir,
17
+ crashFlag: __wasiThreadCrashFlag,
18
+ crashReport: __wasiThreadCrashReport,
19
+ addonCrashFlag: __wasiAddonCrashFlag,
20
+ },
21
+ })
22
+ } catch (error) {
23
+ if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
24
+ throw error
25
+ }
26
+ const __nextWorkerExecArgv =
27
+ __removeInvalidWasiWorkerExecArgv(__workerExecArgv, error)
28
+ if (!__nextWorkerExecArgv) {
29
+ throw error
30
+ }
31
+ __workerExecArgv = __nextWorkerExecArgv
32
+ }
33
+ }
34
+ }`;
35
+ const NORMALIZED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {
36
+ const __workerData = {
37
+ hostRoot: __hostRoot,
38
+ rootDir: __rootDir,
39
+ crashFlag: __wasiThreadCrashFlag,
40
+ crashReport: __wasiThreadCrashReport,
41
+ addonCrashFlag: __wasiAddonCrashFlag,
42
+ }
43
+ const __workerOptions = {
44
+ env: process.env,
45
+ execArgv: __getWasiWorkerExecArgv(),
46
+ workerData: __workerData,
47
+ }
48
+ while (true) {
49
+ try {
50
+ return new Worker(filename, __workerOptions)
51
+ } catch (error) {
52
+ if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
53
+ throw error
54
+ }
55
+ const __nextWorkerExecArgv = __removeInvalidWasiWorkerExecArgv(
56
+ __workerOptions.execArgv,
57
+ error,
58
+ )
59
+ if (!__nextWorkerExecArgv) {
60
+ throw error
61
+ }
62
+ __workerOptions.execArgv = __nextWorkerExecArgv
63
+ }
64
+ }
65
+ }`;
66
+ const GENERATED_BROWSER_WORKER = `const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
67
+ type: 'module',
68
+ })`;
69
+ const NORMALIZED_BROWSER_WORKER = `const __workerData = {
70
+ addonCrashFlag: __wasiAddonCrashFlag,
71
+ }
72
+ const __workerOptions = {
73
+ type: 'module',
74
+ workerData: __workerData,
75
+ }
76
+ const worker = new Worker(
77
+ new URL('./wasi-worker-browser.mjs', import.meta.url),
78
+ __workerOptions,
79
+ )`;
80
+ const SAFE_SCHEDULER_HELPER = `function __invokeWasmCleanupPollScheduler(schedule, settle, reject) {
81
+ let finishedDuringScheduling = false
82
+ let scheduling = true
83
+ const finish = () => {
84
+ if (scheduling) {
85
+ finishedDuringScheduling = true
86
+ return
87
+ }
88
+ settle()
89
+ }
90
+ try {
91
+ schedule(finish)
92
+ } catch (error) {
93
+ reject(error)
94
+ return
95
+ }
96
+ scheduling = false
97
+ if (finishedDuringScheduling) {
98
+ settle()
99
+ }
100
+ }
101
+
102
+ `;
103
+ const RUNTIME_POLL_MARKER = "/**\n * One turn of the runtime-work poll.";
104
+ const UNSAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve) => {
105
+ __scheduleMacrotask(resolve)
106
+ })`;
107
+ const SAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve, reject) => {
108
+ __invokeWasmCleanupPollScheduler(
109
+ __scheduleMacrotask,
110
+ resolve,
111
+ reject,
112
+ )
113
+ })`;
114
+ const UNSAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve) => {
115
+ let settled = false`;
116
+ const SAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve, reject) => {
117
+ let settled = false`;
118
+ const UNSAFE_RUNTIME_POLL_TIMER = `__scheduleTimer(() => {
119
+ pace.arrivals++
120
+ settle()
121
+ }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)`;
122
+ const SAFE_RUNTIME_POLL_TIMER = `__invokeWasmCleanupPollScheduler(
123
+ (finish) => {
124
+ __scheduleTimer(() => {
125
+ pace.arrivals++
126
+ finish()
127
+ }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)
128
+ },
129
+ settle,
130
+ reject,
131
+ )`;
132
+ const UNSAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {
133
+ __scheduleMacrotask(settle)
134
+ return
135
+ }`;
136
+ const SAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {
137
+ __invokeWasmCleanupPollScheduler(
138
+ __scheduleMacrotask,
139
+ settle,
140
+ reject,
141
+ )
142
+ return
143
+ }`;
144
+ const UNSAFE_ASYNC_WORK_POLL = `await new Promise((resolve) => {
145
+ __scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
146
+ })`;
147
+ const SAFE_ASYNC_WORK_POLL = `await new Promise((resolve, reject) => {
148
+ __invokeWasmCleanupPollScheduler(
149
+ (finish) => {
150
+ __scheduleTimer(finish, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
151
+ },
152
+ resolve,
153
+ reject,
154
+ )
155
+ })`;
156
+ const LOADER_KINDS_BY_SUFFIX = {
157
+ ".wasi-browser.js": "browser",
158
+ ".wasi.cjs": "commonjs"
159
+ };
160
+ const BOTH_LOADERS = {
161
+ browser: 1,
162
+ commonjs: 1
163
+ };
164
+ const WASI_LOADER_TRANSFORMATIONS = [
165
+ {
166
+ name: "cleanup handshake",
167
+ anchor: UNSAFE_CLEANUP_HANDSHAKE,
168
+ replacement: SAFE_CLEANUP_HANDSHAKE,
169
+ expectedOccurrences: BOTH_LOADERS
170
+ },
171
+ {
172
+ name: "runtime poll promise",
173
+ anchor: UNSAFE_RUNTIME_POLL_PROMISE,
174
+ replacement: SAFE_RUNTIME_POLL_PROMISE,
175
+ expectedOccurrences: BOTH_LOADERS
176
+ },
177
+ {
178
+ name: "runtime poll timer",
179
+ anchor: UNSAFE_RUNTIME_POLL_TIMER,
180
+ replacement: SAFE_RUNTIME_POLL_TIMER,
181
+ expectedOccurrences: BOTH_LOADERS
182
+ },
183
+ {
184
+ name: "runtime poll macrotask",
185
+ anchor: UNSAFE_RUNTIME_POLL_MACROTASK,
186
+ replacement: SAFE_RUNTIME_POLL_MACROTASK,
187
+ expectedOccurrences: BOTH_LOADERS
188
+ },
189
+ {
190
+ name: "async work poll",
191
+ anchor: UNSAFE_ASYNC_WORK_POLL,
192
+ replacement: SAFE_ASYNC_WORK_POLL,
193
+ expectedOccurrences: BOTH_LOADERS
194
+ },
195
+ {
196
+ name: "worker construction",
197
+ anchor: GENERATED_WASI_WORKER_FACTORY,
198
+ replacement: NORMALIZED_WASI_WORKER_FACTORY,
199
+ expectedOccurrences: {
200
+ browser: 0,
201
+ commonjs: 1
202
+ }
203
+ },
204
+ {
205
+ name: "worker construction",
206
+ anchor: GENERATED_BROWSER_WORKER,
207
+ replacement: NORMALIZED_BROWSER_WORKER,
208
+ expectedOccurrences: {
209
+ browser: 1,
210
+ commonjs: 0
211
+ }
212
+ },
213
+ {
214
+ name: "scheduler helper",
215
+ anchor: RUNTIME_POLL_MARKER,
216
+ replacement: `${SAFE_SCHEDULER_HELPER}${RUNTIME_POLL_MARKER}`,
217
+ expectedOccurrences: BOTH_LOADERS
218
+ }
219
+ ];
220
+ const countOccurrences = (source, value) => source.split(value).length - 1;
221
+ const loaderKindFromFile = (file) => {
222
+ if (file.endsWith(".wasi-browser.js")) return "browser";
223
+ if (file.endsWith(".wasi.cjs")) return "commonjs";
224
+ throw new Error(`Unsupported WASI loader file ${file}: expected a name ending in ${Object.keys(LOADER_KINDS_BY_SUFFIX).join(" or ")}`);
225
+ };
226
+ const applyTransformations = (source, file, loaderKind) => {
227
+ let result = source;
228
+ for (const transformation of WASI_LOADER_TRANSFORMATIONS) {
229
+ const expected = transformation.expectedOccurrences[loaderKind];
230
+ const replacementCount = countOccurrences(result, transformation.replacement);
231
+ const anchorsPerReplacement = countOccurrences(transformation.replacement, transformation.anchor);
232
+ const anchorCount = countOccurrences(result, transformation.anchor) - replacementCount * anchorsPerReplacement;
233
+ if (expected === 0 && anchorCount === 0 && replacementCount === 0) continue;
234
+ if (anchorCount === 0 && replacementCount === expected) continue;
235
+ if (anchorCount !== expected || replacementCount !== 0) throw new Error(`WASI loader transformation "${transformation.name}" did not match ${file}: expected ${expected}, found anchors=${anchorCount}, replacements=${replacementCount}`);
236
+ result = result.replaceAll(transformation.anchor, transformation.replacement);
237
+ }
238
+ return result;
239
+ };
240
+ const addTypecheckHeader = (source) => {
241
+ if (!source.startsWith(ARTIFACT_METADATA_PREFIX)) return `${GENERATED_TYPECHECK_OPT_OUT}\n${source}`;
242
+ const lineEnd = source.indexOf("\n");
243
+ if (lineEnd < 0) return `${source}\n${GENERATED_TYPECHECK_OPT_OUT}\n`;
244
+ return `${source.slice(0, lineEnd + 1)}${GENERATED_TYPECHECK_OPT_OUT}\n${source.slice(lineEnd + 1)}`;
245
+ };
246
+ /** Preserve generated scheduling and add only targeted safety guards. */
247
+ const normalizeGeneratedLoader = (source, { keepGeneratedTypecheckOptOut = false, file = "<input>" } = {}) => {
248
+ if (source.includes("\r\n")) throw new Error(`WASI loader transformation "LF line endings" did not match ${file}`);
249
+ const withoutDirective = source.replace(GENERATOR_DIRECTIVE, "");
250
+ const normalized = applyTransformations(withoutDirective, file, loaderKindFromFile(file)).replace(/[ \t]+$/gmu, "");
251
+ return keepGeneratedTypecheckOptOut ? addTypecheckHeader(normalized) : normalized;
252
+ };
253
+ const usage = "Usage: stll-normalize-wasi-loaders [--check] <browser-loader> <commonjs-loader>";
254
+ const runWasiLoaderNormalizer = async (args) => {
255
+ const check = args.at(0) === "--check";
256
+ const paths = check ? args.slice(1) : args;
257
+ if (paths.length !== 2 || paths.some((path) => path.startsWith("--"))) throw new Error(usage);
258
+ const loaders = await Promise.all(paths.map(async (path) => {
259
+ const source = await readFile(path, "utf8");
260
+ return {
261
+ normalized: normalizeGeneratedLoader(source, {
262
+ keepGeneratedTypecheckOptOut: true,
263
+ file: path
264
+ }),
265
+ path,
266
+ source
267
+ };
268
+ }));
269
+ let drifted = false;
270
+ for (const { normalized, path, source } of loaders) {
271
+ if (source === normalized) continue;
272
+ if (check) {
273
+ process.stderr.write(`${path} is not normalized\n`);
274
+ drifted = true;
275
+ continue;
276
+ }
277
+ await writeFile(path, normalized);
278
+ }
279
+ if (drifted) throw new Error("generated WASI loaders have drifted");
280
+ };
281
+ //#endregion
282
+ //#region src/wasi-loader-normalizer-cli.ts
283
+ runWasiLoaderNormalizer(process.argv.slice(2)).catch((error) => {
284
+ process.stderr.write(`wasi-loader-normalizer: ${error instanceof Error ? error.message : String(error)}\n`);
285
+ process.exitCode = 1;
286
+ });
287
+ //#endregion
288
+ export {};
289
+
290
+ //# sourceMappingURL=wasi-loader-normalizer-cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasi-loader-normalizer-cli.mjs","names":[],"sources":["../src/wasi-loader-normalizer.ts","../src/wasi-loader-normalizer-cli.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\n\nconst GENERATOR_DIRECTIVE =\n /^\\s*(?:\\/\\/\\s*@ts-nocheck\\b.*|\\/\\*\\s*(?:eslint-disable(?:\\s+[^*]*)?|prettier-ignore)\\s*\\*\\/|\\/\\/\\s*(?:eslint|prettier|biome)-disable\\b.*)\\r?\\n?/gmu;\n\nconst GENERATED_TYPECHECK_OPT_OUT =\n \"// @ts-nocheck -- generated by @napi-rs/cli\";\nconst ARTIFACT_METADATA_PREFIX = \"// napi-rs-artifact-metadata:\";\nconst GENERATED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {\n let __workerExecArgv = __getWasiWorkerExecArgv()\n while (true) {\n try {\n return new Worker(filename, {\n env: process.env,\n execArgv: __workerExecArgv,\n workerData: {\n hostRoot: __hostRoot,\n rootDir: __rootDir,\n crashFlag: __wasiThreadCrashFlag,\n crashReport: __wasiThreadCrashReport,\n addonCrashFlag: __wasiAddonCrashFlag,\n },\n })\n } catch (error) {\n if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {\n throw error\n }\n const __nextWorkerExecArgv =\n __removeInvalidWasiWorkerExecArgv(__workerExecArgv, error)\n if (!__nextWorkerExecArgv) {\n throw error\n }\n __workerExecArgv = __nextWorkerExecArgv\n }\n }\n}`;\nconst NORMALIZED_WASI_WORKER_FACTORY = `function __createWasiWorker(filename) {\n const __workerData = {\n hostRoot: __hostRoot,\n rootDir: __rootDir,\n crashFlag: __wasiThreadCrashFlag,\n crashReport: __wasiThreadCrashReport,\n addonCrashFlag: __wasiAddonCrashFlag,\n }\n const __workerOptions = {\n env: process.env,\n execArgv: __getWasiWorkerExecArgv(),\n workerData: __workerData,\n }\n while (true) {\n try {\n return new Worker(filename, __workerOptions)\n } catch (error) {\n if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {\n throw error\n }\n const __nextWorkerExecArgv = __removeInvalidWasiWorkerExecArgv(\n __workerOptions.execArgv,\n error,\n )\n if (!__nextWorkerExecArgv) {\n throw error\n }\n __workerOptions.execArgv = __nextWorkerExecArgv\n }\n }\n}`;\nconst GENERATED_BROWSER_WORKER = `const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {\n type: 'module',\n })`;\n\nconst NORMALIZED_BROWSER_WORKER = `const __workerData = {\n addonCrashFlag: __wasiAddonCrashFlag,\n }\n const __workerOptions = {\n type: 'module',\n workerData: __workerData,\n }\n const worker = new Worker(\n new URL('./wasi-worker-browser.mjs', import.meta.url),\n __workerOptions,\n )`;\nconst SAFE_SCHEDULER_HELPER = `function __invokeWasmCleanupPollScheduler(schedule, settle, reject) {\n let finishedDuringScheduling = false\n let scheduling = true\n const finish = () => {\n if (scheduling) {\n finishedDuringScheduling = true\n return\n }\n settle()\n }\n try {\n schedule(finish)\n } catch (error) {\n reject(error)\n return\n }\n scheduling = false\n if (finishedDuringScheduling) {\n settle()\n }\n}\n\n`;\nconst RUNTIME_POLL_MARKER = \"/**\\n * One turn of the runtime-work poll.\";\nconst UNSAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve) => {\n __scheduleMacrotask(resolve)\n })`;\nconst SAFE_CLEANUP_HANDSHAKE = `await new Promise((resolve, reject) => {\n __invokeWasmCleanupPollScheduler(\n __scheduleMacrotask,\n resolve,\n reject,\n )\n })`;\nconst UNSAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve) => {\n let settled = false`;\nconst SAFE_RUNTIME_POLL_PROMISE = `return new Promise((resolve, reject) => {\n let settled = false`;\nconst UNSAFE_RUNTIME_POLL_TIMER = `__scheduleTimer(() => {\n pace.arrivals++\n settle()\n }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)`;\nconst SAFE_RUNTIME_POLL_TIMER = `__invokeWasmCleanupPollScheduler(\n (finish) => {\n __scheduleTimer(() => {\n pace.arrivals++\n finish()\n }, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)\n },\n settle,\n reject,\n )`;\nconst UNSAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {\n __scheduleMacrotask(settle)\n return\n }`;\nconst SAFE_RUNTIME_POLL_MACROTASK = `if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {\n __invokeWasmCleanupPollScheduler(\n __scheduleMacrotask,\n settle,\n reject,\n )\n return\n }`;\nconst UNSAFE_ASYNC_WORK_POLL = `await new Promise((resolve) => {\n __scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)\n })`;\nconst SAFE_ASYNC_WORK_POLL = `await new Promise((resolve, reject) => {\n __invokeWasmCleanupPollScheduler(\n (finish) => {\n __scheduleTimer(finish, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)\n },\n resolve,\n reject,\n )\n })`;\ntype Transformation = {\n name: string;\n anchor: string;\n replacement: string;\n expectedOccurrences: Record<LoaderKind, number>;\n};\n\nconst LOADER_KINDS_BY_SUFFIX = {\n \".wasi-browser.js\": \"browser\",\n \".wasi.cjs\": \"commonjs\",\n} as const;\n\ntype LoaderKind = (typeof LOADER_KINDS_BY_SUFFIX)[keyof typeof LOADER_KINDS_BY_SUFFIX];\n\nconst BOTH_LOADERS = { browser: 1, commonjs: 1 } as const;\nconst BROWSER_LOADER = { browser: 1, commonjs: 0 } as const;\nconst COMMONJS_LOADER = { browser: 0, commonjs: 1 } as const;\n\nexport const WASI_LOADER_TRANSFORMATIONS = [\n {\n name: \"cleanup handshake\",\n anchor: UNSAFE_CLEANUP_HANDSHAKE,\n replacement: SAFE_CLEANUP_HANDSHAKE,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll promise\",\n anchor: UNSAFE_RUNTIME_POLL_PROMISE,\n replacement: SAFE_RUNTIME_POLL_PROMISE,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll timer\",\n anchor: UNSAFE_RUNTIME_POLL_TIMER,\n replacement: SAFE_RUNTIME_POLL_TIMER,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"runtime poll macrotask\",\n anchor: UNSAFE_RUNTIME_POLL_MACROTASK,\n replacement: SAFE_RUNTIME_POLL_MACROTASK,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"async work poll\",\n anchor: UNSAFE_ASYNC_WORK_POLL,\n replacement: SAFE_ASYNC_WORK_POLL,\n expectedOccurrences: BOTH_LOADERS,\n },\n {\n name: \"worker construction\",\n anchor: GENERATED_WASI_WORKER_FACTORY,\n replacement: NORMALIZED_WASI_WORKER_FACTORY,\n expectedOccurrences: COMMONJS_LOADER,\n },\n {\n name: \"worker construction\",\n anchor: GENERATED_BROWSER_WORKER,\n replacement: NORMALIZED_BROWSER_WORKER,\n expectedOccurrences: BROWSER_LOADER,\n },\n {\n name: \"scheduler helper\",\n anchor: RUNTIME_POLL_MARKER,\n replacement: `${SAFE_SCHEDULER_HELPER}${RUNTIME_POLL_MARKER}`,\n expectedOccurrences: BOTH_LOADERS,\n },\n] satisfies Transformation[];\n\nconst countOccurrences = (source: string, value: string) =>\n source.split(value).length - 1;\n\nconst loaderKindFromFile = (file: string): LoaderKind => {\n if (file.endsWith(\".wasi-browser.js\")) return \"browser\";\n if (file.endsWith(\".wasi.cjs\")) return \"commonjs\";\n throw new Error(\n `Unsupported WASI loader file ${file}: expected a name ending in ${Object.keys(LOADER_KINDS_BY_SUFFIX).join(\" or \")}`,\n );\n};\n\nconst applyTransformations = (\n source: string,\n file: string,\n loaderKind: LoaderKind,\n) => {\n let result = source;\n for (const transformation of WASI_LOADER_TRANSFORMATIONS) {\n const expected = transformation.expectedOccurrences[loaderKind];\n const replacementCount = countOccurrences(\n result,\n transformation.replacement,\n );\n const anchorsPerReplacement = countOccurrences(\n transformation.replacement,\n transformation.anchor,\n );\n const anchorCount =\n countOccurrences(result, transformation.anchor) -\n replacementCount * anchorsPerReplacement;\n\n if (expected === 0 && anchorCount === 0 && replacementCount === 0) continue;\n if (anchorCount === 0 && replacementCount === expected) continue;\n if (anchorCount !== expected || replacementCount !== 0)\n throw new Error(\n `WASI loader transformation \"${transformation.name}\" did not match ${file}: expected ${expected}, found anchors=${anchorCount}, replacements=${replacementCount}`,\n );\n result = result.replaceAll(\n transformation.anchor,\n transformation.replacement,\n );\n }\n return result;\n};\nconst addTypecheckHeader = (source: string) => {\n if (!source.startsWith(ARTIFACT_METADATA_PREFIX)) {\n return `${GENERATED_TYPECHECK_OPT_OUT}\\n${source}`;\n }\n const lineEnd = source.indexOf(\"\\n\");\n if (lineEnd < 0) {\n return `${source}\\n${GENERATED_TYPECHECK_OPT_OUT}\\n`;\n }\n return `${source.slice(0, lineEnd + 1)}${GENERATED_TYPECHECK_OPT_OUT}\\n${source.slice(lineEnd + 1)}`;\n};\n/** Preserve generated scheduling and add only targeted safety guards. */\nexport const normalizeGeneratedLoader = (\n source: string,\n {\n keepGeneratedTypecheckOptOut = false,\n file = \"<input>\",\n }: {\n keepGeneratedTypecheckOptOut?: boolean;\n file?: string;\n } = {},\n) => {\n if (source.includes(\"\\r\\n\"))\n throw new Error(\n `WASI loader transformation \"LF line endings\" did not match ${file}`,\n );\n\n const withoutDirective = source.replace(GENERATOR_DIRECTIVE, \"\");\n const normalized = applyTransformations(\n withoutDirective,\n file,\n loaderKindFromFile(file),\n ).replace(/[ \\t]+$/gmu, \"\");\n\n return keepGeneratedTypecheckOptOut\n ? addTypecheckHeader(normalized)\n : normalized;\n};\n\nconst usage =\n \"Usage: stll-normalize-wasi-loaders [--check] <browser-loader> <commonjs-loader>\";\n\nexport const runWasiLoaderNormalizer = async (args: string[]) => {\n const check = args.at(0) === \"--check\";\n const paths = check ? args.slice(1) : args;\n if (paths.length !== 2 || paths.some((path) => path.startsWith(\"--\")))\n throw new Error(usage);\n\n const loaders = await Promise.all(\n paths.map(async (path) => {\n const source = await readFile(path, \"utf8\");\n const normalized = normalizeGeneratedLoader(source, {\n keepGeneratedTypecheckOptOut: true,\n file: path,\n });\n return { normalized, path, source };\n }),\n );\n\n let drifted = false;\n for (const { normalized, path, source } of loaders) {\n if (source === normalized) continue;\n if (check) {\n process.stderr.write(`${path} is not normalized\\n`);\n drifted = true;\n continue;\n }\n await writeFile(path, normalized);\n }\n if (drifted) throw new Error(\"generated WASI loaders have drifted\");\n};\n","#!/usr/bin/env node\nimport { runWasiLoaderNormalizer } from \"./wasi-loader-normalizer\";\n\nrunWasiLoaderNormalizer(process.argv.slice(2)).catch((error: unknown) => {\n process.stderr.write(\n `wasi-loader-normalizer: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exitCode = 1;\n});\n"],"mappings":"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"}