@pumpkin-plugins/build 0.0.0 → 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,1788 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import * as fs9 from "node:fs";
5
+ import { createRequire as createRequire4 } from "node:module";
6
+ import * as os from "node:os";
7
+ import * as path9 from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { Command } from "@commander-js/extra-typings";
10
+ import { ensurePumpkinWit } from "@pumpkin-plugins/pumpkin-api/wit";
11
+ import { CommanderError } from "commander";
12
+
13
+ // src/bundle.ts
14
+ import { execFileSync as execFileSync2 } from "node:child_process";
15
+ import * as fs4 from "node:fs";
16
+ import { createRequire as createRequire2 } from "node:module";
17
+ import * as path4 from "node:path";
18
+ import { buildStaticHermesComponent, staticHermesImportsPlugin } from "@pumpkin-plugins/static-hermes-runtime";
19
+ import * as esbuild from "esbuild";
20
+
21
+ // src/errors.ts
22
+ var BuildError = class extends Error {
23
+ };
24
+ function runtimeBuildError(runtime, error) {
25
+ const output = capturedOutput(error);
26
+ const status = errorField(error, "status");
27
+ const statusLabel = typeof status === "number" ? ` (exit ${status})` : "";
28
+ const details = output.length > 0 ? `:
29
+ ${output.join("\n")}` : `: ${errorMessage(error)}`;
30
+ return new BuildError(`${runtime} componentizer failed${statusLabel}${details}`);
31
+ }
32
+ function capturedOutput(error) {
33
+ const output = [errorField(error, "stderr"), errorField(error, "stdout")].map((value) => typeof value === "string" || Buffer.isBuffer(value) ? String(value).trim() : "").filter(Boolean);
34
+ return output;
35
+ }
36
+ function errorField(error, name) {
37
+ return typeof error === "object" && error !== null && name in error ? error[name] : void 0;
38
+ }
39
+ function errorMessage(error) {
40
+ return error instanceof Error ? error.message : String(error);
41
+ }
42
+
43
+ // src/repo.ts
44
+ import * as fs from "node:fs";
45
+ import * as path from "node:path";
46
+ function findRepoRoot(from) {
47
+ for (let dir = path.resolve(from); ; dir = path.dirname(dir)) {
48
+ if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;
49
+ if (path.dirname(dir) === dir) return path.resolve(from);
50
+ }
51
+ }
52
+ function cacheDir(from) {
53
+ return process.env.PUMPKIN_PLUGINS_CACHE_DIR || path.join(findRepoRoot(from), ".cache");
54
+ }
55
+
56
+ // src/types.ts
57
+ import { execFileSync } from "node:child_process";
58
+ import * as fs2 from "node:fs";
59
+ import { createRequire } from "node:module";
60
+ import * as path2 from "node:path";
61
+ var require2 = createRequire(import.meta.url);
62
+ function generateTypes(witDir, outDir) {
63
+ const jco = resolveJcoCli();
64
+ const output = path2.resolve(outDir);
65
+ const parent = path2.dirname(output);
66
+ fs2.mkdirSync(parent, { recursive: true });
67
+ const temporary = fs2.mkdtempSync(path2.join(parent, `.${path2.basename(output)}-`));
68
+ const generated = path2.join(temporary, "generated");
69
+ let preserveTemporary = false;
70
+ try {
71
+ fs2.mkdirSync(generated);
72
+ execFileSync(
73
+ process.execPath,
74
+ [jco, "guest-types", witDir, "-n", "plugin", "-o", generated, "--name", "index", "--quiet"],
75
+ {
76
+ stdio: "inherit"
77
+ }
78
+ );
79
+ publishTypes(generated, output, temporary, () => {
80
+ preserveTemporary = true;
81
+ });
82
+ } finally {
83
+ if (!preserveTemporary) fs2.rmSync(temporary, { recursive: true, force: true });
84
+ }
85
+ }
86
+ function resolveJcoCli() {
87
+ let entry;
88
+ try {
89
+ entry = require2.resolve("@bytecodealliance/jco");
90
+ } catch {
91
+ throw new Error("@bytecodealliance/jco is missing from @pumpkin-plugins/build dependencies");
92
+ }
93
+ for (let directory = path2.dirname(entry); ; directory = path2.dirname(directory)) {
94
+ const packagePath = path2.join(directory, "package.json");
95
+ if (fs2.existsSync(packagePath)) {
96
+ const packageJson = JSON.parse(fs2.readFileSync(packagePath, "utf8"));
97
+ if (packageJson.name === "@bytecodealliance/jco") {
98
+ const command = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.jco;
99
+ if (!command) throw new Error("@bytecodealliance/jco package metadata does not declare its jco binary");
100
+ const binaryPath = path2.resolve(directory, command);
101
+ if (!fs2.existsSync(binaryPath))
102
+ throw new Error(`jco binary is missing from its package: ${binaryPath}`);
103
+ return binaryPath;
104
+ }
105
+ }
106
+ const parent = path2.dirname(directory);
107
+ if (parent === directory) break;
108
+ }
109
+ throw new Error(`could not locate @bytecodealliance/jco package metadata from ${entry}`);
110
+ }
111
+ function publishTypes(generated, output, temporary, preserveForRecovery) {
112
+ if (!fs2.existsSync(output)) {
113
+ fs2.renameSync(generated, output);
114
+ return;
115
+ }
116
+ if (!fs2.statSync(output).isDirectory()) throw new Error(`type output is not a directory: ${output}`);
117
+ const previous = path2.join(temporary, "previous");
118
+ fs2.renameSync(output, previous);
119
+ try {
120
+ fs2.renameSync(generated, output);
121
+ } catch (publishError) {
122
+ try {
123
+ fs2.renameSync(previous, output);
124
+ } catch (restoreError) {
125
+ preserveForRecovery();
126
+ throw new AggregateError(
127
+ [publishError, restoreError],
128
+ `could not publish or restore ${output}; previous declarations preserved at ${previous}`
129
+ );
130
+ }
131
+ throw publishError;
132
+ }
133
+ }
134
+
135
+ // src/wit-method-usage.ts
136
+ import * as fs3 from "node:fs";
137
+ import * as path3 from "node:path";
138
+ import * as ts from "typescript";
139
+ function findUsedWitMethods(entry, additionalTypesDir) {
140
+ const absoluteEntry = path3.resolve(entry);
141
+ if (!fs3.existsSync(absoluteEntry)) return void 0;
142
+ const configPath = ts.findConfigFile(path3.dirname(absoluteEntry), ts.sys.fileExists, "tsconfig.json");
143
+ let parsed;
144
+ if (configPath) {
145
+ const config = ts.readConfigFile(configPath, ts.sys.readFile);
146
+ if (config.error) throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, "\n"));
147
+ parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path3.dirname(configPath));
148
+ } else {
149
+ parsed = ts.parseJsonConfigFileContent({}, ts.sys, path3.dirname(absoluteEntry));
150
+ }
151
+ if (parsed.errors.length) {
152
+ throw new Error(
153
+ parsed.errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n")
154
+ );
155
+ }
156
+ const additionalTypeFiles = additionalTypesDir ? ts.sys.readDirectory(additionalTypesDir, [".d.ts"], void 0, ["**/*.d.ts"]) : [];
157
+ const options = {
158
+ ...configPath ? {} : {
159
+ target: ts.ScriptTarget.ES2022,
160
+ module: ts.ModuleKind.ESNext,
161
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
162
+ strict: true
163
+ },
164
+ ...parsed.options,
165
+ allowJs: true,
166
+ maxNodeModuleJsDepth: 100
167
+ };
168
+ const implementationFiles = /* @__PURE__ */ new Set();
169
+ const runtimeHost = {
170
+ ...ts.sys,
171
+ fileExists: (fileName) => !/\.d\.[cm]?ts$/.test(fileName) && ts.sys.fileExists(fileName)
172
+ };
173
+ let incomplete = false;
174
+ collectImplementations(absoluteEntry);
175
+ const program = ts.createProgram(
176
+ [
177
+ .../* @__PURE__ */ new Set([
178
+ ...implementationFiles,
179
+ ...parsed.fileNames.filter((fileName) => /\.d\.[cm]?ts$/.test(fileName)),
180
+ ...additionalTypeFiles
181
+ ])
182
+ ],
183
+ options
184
+ );
185
+ const checker = program.getTypeChecker();
186
+ const hostModules = /* @__PURE__ */ new Set();
187
+ for (const module of checker.getAmbientModules()) {
188
+ for (const declaration of module.declarations ?? []) {
189
+ if (ts.isModuleDeclaration(declaration) && ts.isStringLiteral(declaration.name))
190
+ hostModules.add(declaration.name.text);
191
+ }
192
+ }
193
+ const pumpkinMethods = /* @__PURE__ */ new Set();
194
+ const wasiMethods = /* @__PURE__ */ new Set();
195
+ const eventTypes = /* @__PURE__ */ new Set();
196
+ let incompleteEventTypes = false;
197
+ const witDeclarations = /* @__PURE__ */ new Map();
198
+ const calledValues = /* @__PURE__ */ new Set();
199
+ const constructedValues = /* @__PURE__ */ new Set();
200
+ let valueUsagesIndexed = false;
201
+ for (const source of program.getSourceFiles()) {
202
+ if (!pumpkinInterfaceName(source.fileName) && !wasiModuleSpecifier(source)) continue;
203
+ indexDeclarations(source);
204
+ }
205
+ for (const source of program.getSourceFiles()) {
206
+ if (!isPluginSource(source)) continue;
207
+ visit(source);
208
+ }
209
+ if (incomplete) return void 0;
210
+ return {
211
+ pumpkinMethods: [...pumpkinMethods].sort(),
212
+ wasiMethods: [...wasiMethods].sort(),
213
+ eventTypes: incompleteEventTypes ? void 0 : [...eventTypes].sort()
214
+ };
215
+ function indexDeclarations(node) {
216
+ if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isFunctionDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
217
+ const declarations = witDeclarations.get(node.name.text) ?? [];
218
+ declarations.push(node);
219
+ witDeclarations.set(node.name.text, declarations);
220
+ }
221
+ ts.forEachChild(node, indexDeclarations);
222
+ }
223
+ function collectImplementations(fileName) {
224
+ if (implementationFiles.has(fileName)) return;
225
+ implementationFiles.add(fileName);
226
+ const content = ts.sys.readFile(fileName);
227
+ if (content === void 0) {
228
+ incomplete = true;
229
+ return;
230
+ }
231
+ const source = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
232
+ inspect(source);
233
+ function inspect(node) {
234
+ let specifier;
235
+ if (ts.isImportDeclaration(node)) {
236
+ const clause = node.importClause;
237
+ if (clause?.isTypeOnly) return;
238
+ if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings) && !clause.name && clause.namedBindings.elements.length > 0 && clause.namedBindings.elements.every((item) => item.isTypeOnly))
239
+ return;
240
+ specifier = node.moduleSpecifier;
241
+ } else if (ts.isExportDeclaration(node) && !node.isTypeOnly) {
242
+ if (node.exportClause && ts.isNamedExports(node.exportClause) && node.exportClause.elements.length > 0 && node.exportClause.elements.every((item) => item.isTypeOnly))
243
+ return;
244
+ specifier = node.moduleSpecifier;
245
+ } else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require")) {
246
+ specifier = node.arguments[0];
247
+ if (!specifier || !ts.isStringLiteralLike(specifier)) incomplete = true;
248
+ }
249
+ if (specifier && ts.isStringLiteralLike(specifier) && !/^(?:pumpkin:|wasi:|node:)/.test(specifier.text)) {
250
+ const resolution = ts.resolveModuleName(specifier.text, fileName, options, runtimeHost).resolvedModule;
251
+ if (resolution && !/\.d\.[cm]?ts$/.test(resolution.resolvedFileName))
252
+ collectImplementations(resolution.resolvedFileName);
253
+ else {
254
+ const declaration = ts.resolveModuleName(specifier.text, fileName, options, ts.sys).resolvedModule;
255
+ if (!declaration || !pumpkinInterfaceName(declaration.resolvedFileName)) incomplete = true;
256
+ }
257
+ }
258
+ ts.forEachChild(node, inspect);
259
+ }
260
+ }
261
+ function visit(node) {
262
+ const moduleSpecifier = runtimeModuleSpecifier(node);
263
+ if (moduleSpecifier && ts.isStringLiteralLike(moduleSpecifier) && /^(?:pumpkin:|wasi:)/.test(moduleSpecifier.text) && !hostModules.has(moduleSpecifier.text) && !ts.resolveModuleName(moduleSpecifier.text, moduleSpecifier.getSourceFile().fileName, options, ts.sys).resolvedModule) {
264
+ incomplete = true;
265
+ }
266
+ if (ts.isCallExpression(node) && registerEventMember(node.expression) === "registerEvent") {
267
+ if (!isPluginEventForwarder(node)) collectRegisteredEventType(node);
268
+ } else if ((ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && registerEventMember(node) === "registerEvent" && !(ts.isCallExpression(node.parent) && node.parent.expression === node) && !isPluginEventForwarder(node)) {
269
+ incompleteEventTypes = true;
270
+ }
271
+ if (ts.isPropertyAccessExpression(node)) collectMethod(node.name, node.expression, node.name.text);
272
+ else if (ts.isElementAccessExpression(node)) {
273
+ const keyType = checker.getTypeAtLocation(node.argumentExpression);
274
+ const keys = keyType.isUnion() ? keyType.types : [keyType];
275
+ if (keys.every((key) => key.isStringLiteral())) {
276
+ if (keys.some((key) => key.isStringLiteral() && key.value === "registerEvent") && isUnknownDynamicHostCall(node)) {
277
+ incompleteEventTypes = true;
278
+ }
279
+ for (const key of keys) {
280
+ if (key.isStringLiteral()) collectMethod(node.argumentExpression, node.expression, key.value);
281
+ }
282
+ } else {
283
+ const receiverType = checker.getTypeAtLocation(node.expression);
284
+ if ((receiverType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) || checker.getPropertyOfType(receiverType, "registerEvent")) && isUnknownDynamicHostCall(node)) {
285
+ incompleteEventTypes = true;
286
+ }
287
+ collectAllMembers(receiverType, node.argumentExpression, node.expression);
288
+ }
289
+ } else if (ts.isIdentifier(node)) collectMethod(node);
290
+ if (ts.isNewExpression(node)) collectConstructor(node.expression);
291
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)) collectReferencedWitConstructor(node);
292
+ if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer) {
293
+ for (const binding of node.name.elements) {
294
+ if (binding.dotDotDotToken) {
295
+ collectAllMembers(checker.getTypeAtLocation(node.initializer), binding.name, node.initializer);
296
+ continue;
297
+ }
298
+ const name = binding.propertyName ?? binding.name;
299
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
300
+ if (name.text === "registerEvent") incompleteEventTypes = true;
301
+ collectMethod(name, node.initializer, name.text);
302
+ } else collectAllMembers(checker.getTypeAtLocation(node.initializer), name, node.initializer);
303
+ }
304
+ }
305
+ ts.forEachChild(node, visit);
306
+ }
307
+ function registerEventMember(expression) {
308
+ if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
309
+ if (ts.isElementAccessExpression(expression) && expression.argumentExpression) {
310
+ const keyType = checker.getTypeAtLocation(expression.argumentExpression);
311
+ if (keyType.isStringLiteral()) return keyType.value;
312
+ }
313
+ return void 0;
314
+ }
315
+ function collectRegisteredEventType(call) {
316
+ const eventType = call.arguments[1];
317
+ if (!eventType || ts.isSpreadElement(eventType)) {
318
+ incompleteEventTypes = true;
319
+ return;
320
+ }
321
+ const type = checker.getTypeAtLocation(eventType);
322
+ if (!type.isStringLiteral()) {
323
+ incompleteEventTypes = true;
324
+ return;
325
+ }
326
+ eventTypes.add(type.value);
327
+ }
328
+ function isPluginEventForwarder(node) {
329
+ if (!ts.isCallExpression(node)) return false;
330
+ const access = node.expression;
331
+ if (!ts.isPropertyAccessExpression(access) && !ts.isElementAccessExpression(access)) return false;
332
+ const receiver = access.expression;
333
+ if (!ts.isIdentifier(receiver)) return false;
334
+ for (let parent = node.parent; parent; parent = parent.parent) {
335
+ if (!ts.isMethodDeclaration(parent)) continue;
336
+ const contextParameter = parent.parameters[0]?.name;
337
+ return ts.isIdentifier(parent.name) && parent.name.text === "registerEvent" && ts.isClassDeclaration(parent.parent) && parent.parent.name?.text === "Plugin" && contextParameter !== void 0 && ts.isIdentifier(contextParameter) && checker.getSymbolAtLocation(receiver) === checker.getSymbolAtLocation(contextParameter);
338
+ }
339
+ return false;
340
+ }
341
+ function runtimeModuleSpecifier(node) {
342
+ if (ts.isImportDeclaration(node)) {
343
+ const clause = node.importClause;
344
+ if (clause?.isTypeOnly || clause?.namedBindings && ts.isNamedImports(clause.namedBindings) && !clause.name && clause.namedBindings.elements.length > 0 && clause.namedBindings.elements.every((item) => item.isTypeOnly))
345
+ return void 0;
346
+ return node.moduleSpecifier;
347
+ }
348
+ if (ts.isExportDeclaration(node)) {
349
+ if (node.isTypeOnly || node.exportClause && ts.isNamedExports(node.exportClause) && node.exportClause.elements.length > 0 && node.exportClause.elements.every((item) => item.isTypeOnly))
350
+ return void 0;
351
+ return node.moduleSpecifier;
352
+ }
353
+ if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require"))
354
+ return node.arguments[0];
355
+ return void 0;
356
+ }
357
+ function collectAllMembers(type, name, receiver) {
358
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
359
+ if (!ts.isElementAccessExpression(name.parent) || isUnknownDynamicHostCall(name.parent)) incomplete = true;
360
+ return;
361
+ }
362
+ if (type.isUnionOrIntersection()) {
363
+ for (const member of type.types) collectAllMembers(member, name, receiver);
364
+ return;
365
+ }
366
+ for (const member of checker.getPropertiesOfType(type)) collectMethod(name, receiver, member.name);
367
+ }
368
+ function isUnknownDynamicHostCall(access) {
369
+ let expression = access;
370
+ while (ts.isParenthesizedExpression(expression.parent) || ts.isAsExpression(expression.parent) || ts.isTypeAssertionExpression(expression.parent) || ts.isNonNullExpression(expression.parent) || ts.isSatisfiesExpression(expression.parent)) {
371
+ expression = expression.parent;
372
+ }
373
+ const parent = expression.parent;
374
+ if (ts.isCallExpression(parent) && parent.expression === expression) return true;
375
+ if (ts.isNewExpression(parent) && parent.expression === expression)
376
+ return !isRuntimeDependencySource(access.getSourceFile());
377
+ if (ts.isPropertyAccessExpression(parent) && parent.expression === expression && ["apply", "bind", "call"].includes(parent.name.text) && ts.isCallExpression(parent.parent) && parent.parent.expression === parent)
378
+ return true;
379
+ if (!ts.isVariableDeclaration(parent) || parent.initializer !== expression || !ts.isIdentifier(parent.name))
380
+ return false;
381
+ const symbol = checker.getSymbolAtLocation(parent.name);
382
+ if (!symbol) return false;
383
+ indexValueUsages();
384
+ if (calledValues.has(symbol)) return true;
385
+ return constructedValues.has(symbol) && !isRuntimeDependencySource(access.getSourceFile());
386
+ }
387
+ function indexValueUsages() {
388
+ if (valueUsagesIndexed) return;
389
+ valueUsagesIndexed = true;
390
+ for (const source of program.getSourceFiles()) {
391
+ if (!isPluginSource(source)) continue;
392
+ findUses(source);
393
+ }
394
+ function findUses(node) {
395
+ if (ts.isCallExpression(node)) {
396
+ const expression = node.expression;
397
+ const target = ts.isPropertyAccessExpression(expression) ? ["apply", "bind", "call"].includes(expression.name.text) ? expression.expression : expression : expression;
398
+ if (ts.isIdentifier(target)) {
399
+ const symbol = checker.getSymbolAtLocation(target);
400
+ if (symbol) calledValues.add(symbol);
401
+ }
402
+ } else if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
403
+ const symbol = checker.getSymbolAtLocation(node.expression);
404
+ if (symbol) constructedValues.add(symbol);
405
+ }
406
+ ts.forEachChild(node, findUses);
407
+ }
408
+ }
409
+ function collectReferencedWitConstructor(expression) {
410
+ if (isTypeOnlyReference(expression)) return;
411
+ if (ts.isIdentifier(expression) && ts.isPropertyAccessExpression(expression.parent) && expression.parent.expression === expression)
412
+ return;
413
+ const symbol = ts.isIdentifier(expression) && ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
414
+ if (!symbol) return;
415
+ const resolvedSymbol = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
416
+ for (const declaration of resolvedSymbol.declarations ?? []) {
417
+ if (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration))
418
+ collectWitConstructor(declaration);
419
+ }
420
+ }
421
+ function isTypeOnlyReference(expression) {
422
+ for (let parent = expression.parent; parent; parent = parent.parent) {
423
+ if (ts.isTypeNode(parent) || ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent)) return true;
424
+ if (ts.isExpression(parent)) return false;
425
+ }
426
+ return false;
427
+ }
428
+ function collectMethod(name, receiver, memberName) {
429
+ const symbol = checker.getSymbolAtLocation(name);
430
+ const resolvedSymbol = symbol && symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
431
+ const declarations = new Set(resolvedSymbol?.declarations ?? []);
432
+ if (receiver && memberName) {
433
+ const receiverType = checker.getTypeAtLocation(receiver);
434
+ collectInheritedMethodDeclarations(receiverType, memberName, declarations);
435
+ if (receiverType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
436
+ for (const declaration of witDeclarations.get(memberName) ?? []) declarations.add(declaration);
437
+ }
438
+ }
439
+ for (const declaration of declarations) collectWitMethod(declaration);
440
+ }
441
+ function collectInheritedMethodDeclarations(type, memberName, declarations, visited = /* @__PURE__ */ new Set()) {
442
+ if (visited.has(type)) return;
443
+ visited.add(type);
444
+ if (type.isUnionOrIntersection()) {
445
+ for (const member of type.types)
446
+ collectInheritedMethodDeclarations(member, memberName, declarations, visited);
447
+ }
448
+ for (const declaration of checker.getPropertyOfType(type, memberName)?.declarations ?? [])
449
+ declarations.add(declaration);
450
+ if ((type.flags & ts.TypeFlags.Object) === 0) return;
451
+ const objectType = type;
452
+ if ((objectType.objectFlags & ts.ObjectFlags.Interface) === 0) return;
453
+ for (const base of checker.getBaseTypes(objectType))
454
+ collectInheritedMethodDeclarations(base, memberName, declarations, visited);
455
+ }
456
+ function collectWitMethod(declaration) {
457
+ const source = declaration.getSourceFile();
458
+ const wasiSpecifier = wasiModuleSpecifier(source);
459
+ if (wasiSpecifier) {
460
+ collectWasiMethod(wasiSpecifier, declaration);
461
+ return;
462
+ }
463
+ const interfaceName = pumpkinInterfaceName(source.fileName);
464
+ if (!interfaceName) return;
465
+ if (ts.isFunctionDeclaration(declaration)) {
466
+ const declarationName2 = declaration.name;
467
+ if (!declarationName2 || !ts.isIdentifier(declarationName2)) return;
468
+ pumpkinMethods.add(`${interfaceName}.${toWitName(declarationName2.text)}`);
469
+ return;
470
+ }
471
+ if (!ts.isMethodDeclaration(declaration) && !ts.isMethodSignature(declaration)) return;
472
+ const declarationName = declaration.name;
473
+ if (!ts.isIdentifier(declarationName)) return;
474
+ const parent = declaration.parent;
475
+ if (!ts.isClassDeclaration(parent) && !ts.isClassExpression(parent) && !ts.isInterfaceDeclaration(parent))
476
+ return;
477
+ const resourceName = parent.name;
478
+ if (!resourceName || !ts.isIdentifier(resourceName)) return;
479
+ pumpkinMethods.add(`${interfaceName}.${toWitName(resourceName.text)}.${toWitName(declarationName.text)}`);
480
+ }
481
+ function collectConstructor(expression) {
482
+ const name = ts.isPropertyAccessExpression(expression) ? expression.name : expression;
483
+ const type = checker.getTypeAtLocation(expression);
484
+ const symbol = checker.getSymbolAtLocation(name);
485
+ const symbols = /* @__PURE__ */ new Set();
486
+ for (const candidate of [symbol, type.getSymbol()]) {
487
+ if (!candidate) continue;
488
+ symbols.add(candidate.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(candidate) : candidate);
489
+ }
490
+ let resolved = false;
491
+ for (const candidate of symbols) {
492
+ for (const declaration of candidate.declarations ?? []) {
493
+ if (!ts.isClassDeclaration(declaration) && !ts.isClassExpression(declaration)) continue;
494
+ collectWitConstructor(declaration);
495
+ resolved = true;
496
+ }
497
+ }
498
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
499
+ if (!isRuntimeDependencySource(expression.getSourceFile())) incomplete = true;
500
+ return;
501
+ }
502
+ if (type.isUnionOrIntersection()) {
503
+ for (const member of type.types)
504
+ resolved = collectConstructorType(member, expression.getSourceFile()) || resolved;
505
+ } else {
506
+ resolved = collectConstructorType(type, expression.getSourceFile()) || resolved;
507
+ }
508
+ if (!resolved) {
509
+ if (!isRuntimeDependencySource(expression.getSourceFile())) incomplete = true;
510
+ }
511
+ }
512
+ function collectConstructorType(type, source, visited = /* @__PURE__ */ new Set()) {
513
+ if (visited.has(type)) return true;
514
+ visited.add(type);
515
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
516
+ if (!isRuntimeDependencySource(source)) incomplete = true;
517
+ return false;
518
+ }
519
+ if (type.isUnionOrIntersection()) {
520
+ let resolved2 = true;
521
+ for (const member of type.types) resolved2 = collectConstructorType(member, source, visited) && resolved2;
522
+ return resolved2;
523
+ }
524
+ let resolved = false;
525
+ let classResolved = false;
526
+ const symbol = type.getSymbol();
527
+ if (symbol) {
528
+ const candidate = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
529
+ for (const declaration of candidate.declarations ?? []) {
530
+ if (!ts.isClassDeclaration(declaration) && !ts.isClassExpression(declaration)) continue;
531
+ collectWitConstructor(declaration);
532
+ resolved = true;
533
+ classResolved = true;
534
+ }
535
+ }
536
+ const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Construct);
537
+ if (signatures.length > 0) {
538
+ let signaturesResolved = true;
539
+ for (const signature of signatures) {
540
+ const declaration = signature.getDeclaration();
541
+ const parent = declaration?.parent;
542
+ if (parent && (ts.isClassDeclaration(parent) || ts.isClassExpression(parent))) {
543
+ collectWitConstructor(parent);
544
+ continue;
545
+ }
546
+ if (classResolved) continue;
547
+ if (declaration && isKnownNonHostConstructor(declaration)) continue;
548
+ signaturesResolved = false;
549
+ }
550
+ if (!signaturesResolved) incomplete = true;
551
+ resolved = signaturesResolved || resolved;
552
+ }
553
+ return resolved;
554
+ }
555
+ function collectWitConstructor(declaration) {
556
+ if (!declaration.name) return;
557
+ const wasiSpecifier = wasiModuleSpecifier(declaration.getSourceFile());
558
+ if (wasiSpecifier) {
559
+ wasiMethods.add(`${wasiSpecifier}#${toWitName(declaration.name.text)}.constructor`);
560
+ return;
561
+ }
562
+ const interfaceName = pumpkinInterfaceName(declaration.getSourceFile().fileName);
563
+ if (interfaceName) pumpkinMethods.add(`${interfaceName}.${toWitName(declaration.name.text)}.constructor`);
564
+ }
565
+ function isKnownNonHostConstructor(declaration) {
566
+ const source = declaration.getSourceFile();
567
+ if (/[/\\]lib\.[^/\\]+\.d\.ts$/.test(source.fileName)) return true;
568
+ return !source.isDeclarationFile && (ts.isFunctionDeclaration(declaration) || ts.isFunctionExpression(declaration));
569
+ }
570
+ function collectWasiMethod(specifier, declaration) {
571
+ if (ts.isFunctionDeclaration(declaration) && declaration.name) {
572
+ wasiMethods.add(`${specifier}#${toWitName(declaration.name.text)}`);
573
+ return;
574
+ }
575
+ if (!ts.isMethodDeclaration(declaration) && !ts.isMethodSignature(declaration)) return;
576
+ const memberName = declaration.name;
577
+ const resource = declaration.parent;
578
+ if (!memberName || !ts.isIdentifier(memberName)) return;
579
+ if (!ts.isClassDeclaration(resource) && !ts.isClassExpression(resource) && !ts.isInterfaceDeclaration(resource))
580
+ return;
581
+ if (!resource.name) return;
582
+ wasiMethods.add(`${specifier}#${toWitName(resource.name.text)}.${toWitName(memberName.text)}`);
583
+ }
584
+ }
585
+ function isPluginSource(source) {
586
+ return !source.isDeclarationFile && !/\.(?:test|spec)\.[cm]?tsx?$/.test(source.fileName);
587
+ }
588
+ function isRuntimeDependencySource(source) {
589
+ return /(?:^|[/\\])node_modules[/\\]/.test(source.fileName);
590
+ }
591
+ function pumpkinInterfaceName(fileName) {
592
+ return /(?:^|[/\\])interfaces[/\\]pumpkin-plugin-([\w-]+)\.d\.ts$/.exec(fileName)?.[1];
593
+ }
594
+ function wasiModuleSpecifier(source) {
595
+ for (const statement of source.statements) {
596
+ if (ts.isModuleDeclaration(statement) && ts.isStringLiteral(statement.name) && statement.name.text.startsWith("wasi:"))
597
+ return statement.name.text;
598
+ }
599
+ return void 0;
600
+ }
601
+ function toWitName(name) {
602
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
603
+ }
604
+
605
+ // src/bundle.ts
606
+ var require3 = createRequire2(import.meta.url);
607
+ var buildPackageJsonPath = require3.resolve("../package.json");
608
+ var buildPackageRepositoryUrl = packageRepositoryUrl(require3(buildPackageJsonPath));
609
+ var MAX_PRODUCTION_COMPONENT_BYTES = 5 * 1024 * 1024;
610
+ var TEMPORARY_DIRECTORY_PREFIX = ".pumpkin-build-";
611
+ var LEGACY_TEMPORARY_DIRECTORY_MAX_AGE_MS = 60 * 60 * 1e3;
612
+ var activeTemporaryDirectories = /* @__PURE__ */ new Set();
613
+ async function bundlePlugin(options) {
614
+ const output = path4.resolve(options.output);
615
+ const outputDir = path4.dirname(output);
616
+ fs4.mkdirSync(outputDir, { recursive: true });
617
+ removeAbandonedTemporaryDirectories(outputDir);
618
+ const temporaryDir = fs4.mkdtempSync(path4.join(outputDir, `${TEMPORARY_DIRECTORY_PREFIX}${process.pid}-`));
619
+ activeTemporaryDirectories.add(temporaryDir);
620
+ const bundle = path4.join(temporaryDir, "bundle.js");
621
+ const component = path4.join(temporaryDir, "plugin.wasm");
622
+ const engine = options.engine ?? "static-hermes";
623
+ const quietRuntimeOutput = options.quietRuntimeOutput ?? false;
624
+ const projectRoot = path4.resolve(options.projectRoot ?? process.cwd());
625
+ const resolvedWasiImports = /* @__PURE__ */ new Set();
626
+ let preserveTemporaryDir = false;
627
+ try {
628
+ const buildResult = await esbuild.build({
629
+ entryPoints: [options.entry],
630
+ bundle: true,
631
+ outfile: bundle,
632
+ metafile: engine === "static-hermes",
633
+ format: engine === "quickjs" ? "esm" : "iife",
634
+ ...engine === "static-hermes" ? {
635
+ globalName: "__pumpkinPlugin",
636
+ plugins: [
637
+ staticHermesImportsPlugin({
638
+ resolveDir: path4.dirname(path4.resolve(options.entry)),
639
+ workspaceRoot: projectRoot,
640
+ witDir: options.witDir,
641
+ developmentMode: options.developmentMode ?? false,
642
+ onWasiImport: (specifier) => resolvedWasiImports.add(specifier)
643
+ })
644
+ ]
645
+ } : {},
646
+ target: "es2022",
647
+ minify: !options.developmentMode,
648
+ treeShaking: true,
649
+ external: ["pumpkin:plugin/*", "wasi:*"],
650
+ define: {
651
+ __PLUGIN_VERSION__: JSON.stringify(options.version),
652
+ __PUMPKIN_DEV_MODE__: JSON.stringify(options.developmentMode ?? false),
653
+ "process.env.NODE_ENV": JSON.stringify(options.developmentMode ? "development" : "production")
654
+ }
655
+ });
656
+ if (engine === "static-hermes") {
657
+ const source = fs4.readFileSync(bundle, "utf8");
658
+ const unsupportedWasiImports = staticHermesWasiImports(buildResult.metafile).filter(
659
+ (specifier) => !resolvedWasiImports.has(specifier)
660
+ );
661
+ if (unsupportedWasiImports.length > 0) {
662
+ throw new Error(
663
+ `Static Hermes JavaScript bundle imports WASI interface${unsupportedWasiImports.length === 1 ? "" : "s"} without a JSI adapter: ${unsupportedWasiImports.join(", ")}`
664
+ );
665
+ }
666
+ fs4.writeFileSync(bundle, unwrapStaticHermesExports(source));
667
+ const generatedTypes = path4.join(temporaryDir, "wit-types");
668
+ generateTypes(options.witDir, generatedTypes);
669
+ const usedWitMethods = findUsedWitMethods(options.entry, generatedTypes);
670
+ options.onOutput?.({
671
+ type: "componentize",
672
+ engine,
673
+ ...options.staticHermesRuntime === void 0 ? {} : { runtimeVariant: options.staticHermesRuntime },
674
+ ...options.staticHermesGc === void 0 ? {} : { gcVariant: options.staticHermesGc },
675
+ ...options.parseTypeScript ? { parseTypeScript: true } : {},
676
+ ...engine === "static-hermes" && !quietRuntimeOutput ? {
677
+ selections: {
678
+ wasiImports: [...resolvedWasiImports],
679
+ ...usedWitMethods === void 0 ? {} : {
680
+ wasiMethods: usedWitMethods.wasiMethods,
681
+ apiMethods: usedWitMethods.pumpkinMethods,
682
+ ...usedWitMethods.eventTypes === void 0 ? {} : { eventTypes: usedWitMethods.eventTypes }
683
+ }
684
+ }
685
+ } : {}
686
+ });
687
+ try {
688
+ buildStaticHermesComponent({
689
+ bundle,
690
+ witDir: options.witDir,
691
+ output: component,
692
+ workspaceRoot: projectRoot,
693
+ developmentMode: options.developmentMode ?? false,
694
+ wasiImports: [...resolvedWasiImports],
695
+ ...quietRuntimeOutput ? { quietOutput: true } : {},
696
+ ...options.staticHermesRuntime === void 0 ? {} : { runtimeVariant: options.staticHermesRuntime },
697
+ ...options.staticHermesGc === void 0 ? {} : { gcVariant: options.staticHermesGc },
698
+ ...options.parseTypeScript ? { parseTypeScript: true } : {},
699
+ ...usedWitMethods === void 0 ? {} : {
700
+ apiMethods: usedWitMethods.pumpkinMethods,
701
+ wasiMethods: usedWitMethods.wasiMethods,
702
+ ...usedWitMethods.eventTypes === void 0 ? {} : { eventTypes: usedWitMethods.eventTypes }
703
+ }
704
+ });
705
+ } catch (error) {
706
+ if (!quietRuntimeOutput) throw error;
707
+ throw runtimeBuildError("Static Hermes", error);
708
+ }
709
+ } else {
710
+ const componentizerPackage = path4.dirname(require3.resolve("@di-framework/componentize-qjs/package.json"));
711
+ options.onOutput?.({ type: "componentize", engine });
712
+ try {
713
+ execFileSync2(
714
+ process.execPath,
715
+ [
716
+ path4.join(componentizerPackage, "bin/componentize-qjs.cjs"),
717
+ "--world",
718
+ "plugin",
719
+ "--wit",
720
+ options.witDir,
721
+ "--js",
722
+ bundle,
723
+ "--runtime",
724
+ path4.join(import.meta.dirname, "../runtime/quickjs-runtime-bigint.wasm"),
725
+ "--output",
726
+ component
727
+ ],
728
+ {
729
+ stdio: quietRuntimeOutput ? ["inherit", "ignore", "pipe"] : "inherit",
730
+ ...quietRuntimeOutput ? { maxBuffer: 16 * 1024 * 1024 } : {}
731
+ }
732
+ );
733
+ } catch (error) {
734
+ if (!quietRuntimeOutput) throw error;
735
+ throw runtimeBuildError("QuickJS", error);
736
+ }
737
+ }
738
+ const size = fs4.statSync(component).size;
739
+ if (!options.developmentMode && size > MAX_PRODUCTION_COMPONENT_BYTES) {
740
+ const message = `Production WebAssembly component is ${size} bytes; maximum is ${MAX_PRODUCTION_COMPONENT_BYTES} bytes (5 MiB)`;
741
+ if (isRepositoryProject(projectRoot)) throw new Error(message);
742
+ options.onOutput?.({ type: "warning", message });
743
+ }
744
+ publishComponent(component, output, temporaryDir, () => {
745
+ preserveTemporaryDir = true;
746
+ });
747
+ return size;
748
+ } finally {
749
+ activeTemporaryDirectories.delete(temporaryDir);
750
+ if (!preserveTemporaryDir) fs4.rmSync(temporaryDir, { recursive: true, force: true });
751
+ }
752
+ }
753
+ function isRepositoryProject(projectRoot) {
754
+ if (buildPackageRepositoryUrl === void 0) return false;
755
+ let projectManifest;
756
+ try {
757
+ projectManifest = JSON.parse(fs4.readFileSync(path4.join(findRepoRoot(projectRoot), "package.json"), "utf8"));
758
+ } catch {
759
+ return false;
760
+ }
761
+ const projectRepositoryUrl = packageRepositoryUrl(projectManifest);
762
+ return projectRepositoryUrl !== void 0 && normalizeRepositoryUrl(projectRepositoryUrl) === normalizeRepositoryUrl(buildPackageRepositoryUrl);
763
+ }
764
+ function packageRepositoryUrl(manifest) {
765
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) return void 0;
766
+ const repository = manifest.repository;
767
+ if (typeof repository === "string") return repository;
768
+ if (typeof repository !== "object" || repository === null || Array.isArray(repository)) return void 0;
769
+ const url = repository.url;
770
+ return typeof url === "string" ? url : void 0;
771
+ }
772
+ function normalizeRepositoryUrl(url) {
773
+ return url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/\/$/, "");
774
+ }
775
+ function removeAbandonedTemporaryDirectories(outputDir) {
776
+ for (const entry of fs4.readdirSync(outputDir, { withFileTypes: true })) {
777
+ if (!entry.isDirectory() || !entry.name.startsWith(TEMPORARY_DIRECTORY_PREFIX)) continue;
778
+ const temporaryDir = path4.join(outputDir, entry.name);
779
+ if (activeTemporaryDirectories.has(temporaryDir) || fs4.existsSync(path4.join(temporaryDir, "previous.wasm"))) {
780
+ continue;
781
+ }
782
+ const owner = /^\.pumpkin-build-(\d+)-/.exec(entry.name);
783
+ if (owner) {
784
+ const pid = Number(owner[1]);
785
+ if (pid !== process.pid && isProcessRunning(pid)) continue;
786
+ fs4.rmSync(temporaryDir, { recursive: true, force: true });
787
+ continue;
788
+ }
789
+ if (Date.now() - fs4.statSync(temporaryDir).mtimeMs >= LEGACY_TEMPORARY_DIRECTORY_MAX_AGE_MS) {
790
+ fs4.rmSync(temporaryDir, { recursive: true, force: true });
791
+ }
792
+ }
793
+ }
794
+ function isProcessRunning(pid) {
795
+ try {
796
+ process.kill(pid, 0);
797
+ return true;
798
+ } catch (error) {
799
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EPERM";
800
+ }
801
+ }
802
+ function staticHermesWasiImports(metafile) {
803
+ const imports = Object.values(metafile?.outputs ?? {}).flatMap((output) => output.imports);
804
+ return [
805
+ ...new Set(
806
+ imports.filter((entry) => entry.external && entry.path.startsWith("wasi:")).map((entry) => entry.path)
807
+ )
808
+ ];
809
+ }
810
+ function unwrapStaticHermesExports(source) {
811
+ const matches = [...source.matchAll(/return\s+[$\w]+\(([$\w]+)\);(\s*}\)\(\);)/g)];
812
+ const match = matches.at(-1);
813
+ if (!match?.index) throw new Error("Static Hermes bundle has an unsupported esbuild IIFE export wrapper");
814
+ const end = match.index + match[0].length;
815
+ const suffix = source.slice(end);
816
+ if (!isTrailingLicenseComment(suffix)) {
817
+ throw new Error("Static Hermes bundle has unsupported code after its esbuild IIFE export wrapper");
818
+ }
819
+ return `${source.slice(0, match.index)}return ${match[1]};${match[2]}${suffix}`;
820
+ }
821
+ function isTrailingLicenseComment(source) {
822
+ let remaining = source.trim();
823
+ while (remaining) {
824
+ if (!remaining.startsWith("/*!")) return false;
825
+ const end = remaining.indexOf("*/", 3);
826
+ if (end < 0) return false;
827
+ remaining = remaining.slice(end + 2).trim();
828
+ }
829
+ return true;
830
+ }
831
+ function publishComponent(component, output, temporaryDir, preserveForRecovery) {
832
+ try {
833
+ fs4.renameSync(component, output);
834
+ } catch (error) {
835
+ if (!isReplaceError(error) || !fs4.existsSync(output)) throw error;
836
+ const existing = fs4.lstatSync(output);
837
+ if (!existing.isFile() && !existing.isSymbolicLink()) throw error;
838
+ const previous = path4.join(temporaryDir, "previous.wasm");
839
+ fs4.renameSync(output, previous);
840
+ try {
841
+ fs4.renameSync(component, output);
842
+ } catch (publishError) {
843
+ try {
844
+ fs4.renameSync(previous, output);
845
+ } catch (restoreError) {
846
+ preserveForRecovery();
847
+ throw new AggregateError(
848
+ [publishError, restoreError],
849
+ `could not publish or restore ${output}; previous artifact preserved at ${previous}`
850
+ );
851
+ }
852
+ throw publishError;
853
+ }
854
+ }
855
+ }
856
+ function isReplaceError(error) {
857
+ return typeof error === "object" && error !== null && "code" in error && (error.code === "EEXIST" || error.code === "EPERM" || error.code === "EACCES");
858
+ }
859
+
860
+ // src/capabilities.ts
861
+ var WASI_VERSION = "0.2.3";
862
+ var CAPABILITIES = {
863
+ http: ["http/types", "http/outgoing-handler"],
864
+ filesystem: ["filesystem/types", "filesystem/preopens"],
865
+ sockets: ["sockets/network", "sockets/instance-network", "sockets/tcp", "sockets/tcp-create-socket"],
866
+ udp: ["sockets/network", "sockets/instance-network", "sockets/udp", "sockets/udp-create-socket"]
867
+ };
868
+ var SUPPORT_IMPORTS = ["io/poll", "io/streams", "clocks/monotonic-clock"];
869
+ function isCapability(name) {
870
+ return Object.hasOwn(CAPABILITIES, name);
871
+ }
872
+ function wasiInterfaces(capabilities) {
873
+ if (capabilities.length === 0) return [];
874
+ return [.../* @__PURE__ */ new Set([...capabilities.flatMap((c) => CAPABILITIES[c]), ...SUPPORT_IMPORTS])];
875
+ }
876
+
877
+ // ../minecraft-colors/src/index.ts
878
+ var COLORS = {
879
+ black: "0",
880
+ darkBlue: "1",
881
+ darkGreen: "2",
882
+ darkAqua: "3",
883
+ darkRed: "4",
884
+ darkPurple: "5",
885
+ gold: "6",
886
+ gray: "7",
887
+ darkGray: "8",
888
+ blue: "9",
889
+ green: "a",
890
+ aqua: "b",
891
+ red: "c",
892
+ lightPurple: "d",
893
+ yellow: "e",
894
+ white: "f"
895
+ };
896
+ var FORMATS = {
897
+ obfuscated: "k",
898
+ bold: "l",
899
+ strikethrough: "m",
900
+ underline: "n",
901
+ italic: "o"
902
+ };
903
+ var ANSI_COLORS = {
904
+ black: 30,
905
+ darkBlue: 34,
906
+ darkGreen: 32,
907
+ darkAqua: 36,
908
+ darkRed: 31,
909
+ darkPurple: 35,
910
+ gold: 33,
911
+ gray: 37,
912
+ darkGray: 90,
913
+ blue: 94,
914
+ green: 92,
915
+ aqua: 96,
916
+ red: 91,
917
+ lightPurple: 95,
918
+ yellow: 93,
919
+ white: 97
920
+ };
921
+ var ANSI_FORMATS = {
922
+ obfuscated: 8,
923
+ bold: 1,
924
+ strikethrough: 9,
925
+ underline: 4,
926
+ italic: 3
927
+ };
928
+ var NAMED_COLORS = {
929
+ error: { color: "darkRed", format: "bold" },
930
+ identifier: { color: "yellow" },
931
+ name: { color: "darkAqua" },
932
+ namespace: { color: "darkGreen" },
933
+ number: { color: "blue" },
934
+ permission: { color: "yellow", format: "bold" },
935
+ port: { color: "yellow" },
936
+ url: { color: "darkAqua" },
937
+ uuid: { color: "yellow" },
938
+ value: { color: "gold" },
939
+ version: { color: "green" }
940
+ };
941
+ function createFormatter(output, state = { formats: [] }) {
942
+ const formatter = ((text) => {
943
+ text = String(text);
944
+ if (state.legacyColorPrefix === void 0 && state.ansiColorPrefix === void 0 && state.formats.length === 0)
945
+ return text;
946
+ const formats = Object.keys(FORMATS).filter((format) => state.formats.includes(format));
947
+ if (output === "ansi") {
948
+ const codes = formats.map((format) => ANSI_FORMATS[format]);
949
+ if (state.ansiColorPrefix !== void 0) codes.push(...state.ansiColorPrefix.split(";").map(Number));
950
+ return `\x1B[${codes.join(";")}m${text}\x1B[0m`;
951
+ }
952
+ const formatCodes = formats.map((format) => `\xA7${FORMATS[format]}`).join("");
953
+ return `${state.legacyColorPrefix ?? ""}${formatCodes}${text}\xA7r`;
954
+ });
955
+ for (const color2 of Object.keys(COLORS)) {
956
+ Object.defineProperty(formatter, color2, {
957
+ get: () => createFormatter(output, {
958
+ ...state,
959
+ legacyColorPrefix: `\xA7${COLORS[color2]}`,
960
+ ansiColorPrefix: String(ANSI_COLORS[color2])
961
+ })
962
+ });
963
+ }
964
+ for (const format of Object.keys(FORMATS)) {
965
+ Object.defineProperty(formatter, format, {
966
+ get: () => createFormatter(output, {
967
+ ...state,
968
+ formats: state.formats.includes(format) ? state.formats : [...state.formats, format]
969
+ })
970
+ });
971
+ }
972
+ Object.defineProperty(formatter, "hex", {
973
+ value: (color2) => {
974
+ const digits = color2.startsWith("#") ? color2.slice(1) : color2;
975
+ if (!/^[\da-f]{6}$/i.test(digits)) throw new RangeError("Expected a six-digit hex color");
976
+ const hexDigits = [...digits.toLowerCase()];
977
+ const legacyColorPrefix = `\xA7x${hexDigits.map((digit) => `\xA7${digit}`).join("")}`;
978
+ const ansiColorPrefix = `38;2;${parseInt(digits.slice(0, 2), 16)};${parseInt(digits.slice(2, 4), 16)};${parseInt(digits.slice(4, 6), 16)}`;
979
+ return createFormatter(output, { ...state, legacyColorPrefix, ansiColorPrefix });
980
+ }
981
+ });
982
+ return formatter;
983
+ }
984
+ var color = createFormatter("minecraft");
985
+ var ansi = createFormatter("ansi");
986
+ function addNamedFormatters(formatter, output) {
987
+ Object.defineProperty(formatter, "named", {
988
+ value: Object.fromEntries(
989
+ Object.entries(NAMED_COLORS).map(([role, definition]) => {
990
+ const formats = "format" in definition ? [definition.format] : [];
991
+ return [
992
+ role,
993
+ createFormatter(output, {
994
+ legacyColorPrefix: `\xA7${COLORS[definition.color]}`,
995
+ ansiColorPrefix: String(ANSI_COLORS[definition.color]),
996
+ formats
997
+ })
998
+ ];
999
+ })
1000
+ ),
1001
+ enumerable: true
1002
+ });
1003
+ }
1004
+ addNamedFormatters(color, "minecraft");
1005
+ addNamedFormatters(ansi, "ansi");
1006
+
1007
+ // src/output.ts
1008
+ function formatBuildOutput(message) {
1009
+ if (message.type === "error" || message.type === "warning") {
1010
+ const formatLine = message.type === "error" ? ansi.named.error : ansi.named.value;
1011
+ return message.message.split(/\r?\n/).map((line, index) => {
1012
+ const prefix = index === 0 ? `${message.type === "error" ? "Error" : "Warning"}: ` : " ";
1013
+ return `[build] ${prefix}${formatLine(line)}`;
1014
+ }).join("\n");
1015
+ }
1016
+ if (message.type === "built") {
1017
+ const size = `${(message.sizeBytes / 1024).toFixed(0)} KiB`;
1018
+ return `[build] Built ${ansi.named.name(message.output)} (${ansi.named.number(size)})`;
1019
+ }
1020
+ const runtime = ansi.named.name(
1021
+ runtimeLabel(message.engine, message.runtimeVariant, message.gcVariant, message.parseTypeScript ?? false)
1022
+ );
1023
+ if (message.type === "bundle") return `[build] Bundling ${ansi.named.name(message.entry)} with ${runtime}...`;
1024
+ const output = `[build] Componentizing with ${runtime}...`;
1025
+ if (message.selections === void 0) return output;
1026
+ return [
1027
+ output,
1028
+ formatSelectionOption("--wasi-imports", message.selections.wasiImports, "no imports"),
1029
+ formatSelectionOption("--wasi-methods", message.selections.wasiMethods, "all imported WASI operations"),
1030
+ formatSelectionOption("--api-methods", message.selections.apiMethods, "all imported Pumpkin API operations"),
1031
+ formatSelectionOption("--event-types", message.selections.eventTypes, "all event variants")
1032
+ ].join("\n");
1033
+ }
1034
+ function printBuildOutput(message) {
1035
+ const formatted = formatBuildOutput(message);
1036
+ if (message.type === "error") console.error(formatted);
1037
+ else if (message.type === "warning") console.warn(formatted);
1038
+ else console.log(formatted);
1039
+ }
1040
+ function runtimeLabel(engine, runtimeVariant, gcVariant, parseTypeScript) {
1041
+ if (engine === "quickjs") return "QuickJS";
1042
+ const options = [runtimeVariant, gcVariant ?? "hades", ...parseTypeScript ? ["-parse-ts"] : []].filter(
1043
+ (option) => option !== void 0
1044
+ );
1045
+ return `Static Hermes${options.length === 0 ? "" : ` (${options.join(", ")})`}`;
1046
+ }
1047
+ function formatSelectionOption(option, selection, omittedValue) {
1048
+ const value = selection === void 0 ? `(omitted; ${omittedValue})` : JSON.stringify(selection);
1049
+ return `[build] ${ansi.named.name(option)} ${ansi.named.value(value)}`;
1050
+ }
1051
+
1052
+ // src/plugin-config.ts
1053
+ import * as fs5 from "node:fs";
1054
+ import * as path5 from "node:path";
1055
+ function readPluginConfig(pluginDir) {
1056
+ let pkg;
1057
+ try {
1058
+ pkg = JSON.parse(fs5.readFileSync(path5.join(pluginDir, "package.json"), "utf8"));
1059
+ } catch (error) {
1060
+ if (error instanceof SyntaxError) throw new BuildError(`package.json is not valid JSON (${error.message})`);
1061
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1062
+ throw new BuildError(`could not read package.json in ${pluginDir}`);
1063
+ }
1064
+ throw error;
1065
+ }
1066
+ if (!isRecord(pkg)) throw new BuildError("package.json must contain an object");
1067
+ const config = pkg.pumpkinPlugin;
1068
+ if (!isRecord(config)) throw new BuildError('package.json needs a "pumpkinPlugin" object');
1069
+ if (typeof pkg.version !== "string" || pkg.version.length === 0) {
1070
+ throw new BuildError('package.json needs a non-empty "version" string');
1071
+ }
1072
+ const entry = optionalRelativePath(config.entry, "entry");
1073
+ const output = optionalRelativePath(config.output, "output");
1074
+ const engine = config.engine === void 0 ? "static-hermes" : config.engine;
1075
+ if (engine !== "quickjs" && engine !== "static-hermes") {
1076
+ throw new BuildError('"pumpkinPlugin.engine" must be "quickjs" or "static-hermes"');
1077
+ }
1078
+ const staticHermesRuntime = config.staticHermesRuntime;
1079
+ if (staticHermesRuntime !== void 0) {
1080
+ if (engine !== "static-hermes")
1081
+ throw new BuildError('"pumpkinPlugin.staticHermesRuntime" requires "engine": "static-hermes"');
1082
+ if (staticHermesRuntime !== "full" && staticHermesRuntime !== "lean") {
1083
+ throw new BuildError('"pumpkinPlugin.staticHermesRuntime" must be "full" or "lean"');
1084
+ }
1085
+ }
1086
+ const configuredStaticHermesGc = config.staticHermesGc;
1087
+ if (configuredStaticHermesGc !== void 0) {
1088
+ if (engine !== "static-hermes")
1089
+ throw new BuildError('"pumpkinPlugin.staticHermesGc" requires "engine": "static-hermes"');
1090
+ if (configuredStaticHermesGc !== "hades" && configuredStaticHermesGc !== "malloc") {
1091
+ throw new BuildError('"pumpkinPlugin.staticHermesGc" must be "hades" or "malloc"');
1092
+ }
1093
+ }
1094
+ const staticHermesGc = engine === "static-hermes" ? configuredStaticHermesGc ?? "hades" : void 0;
1095
+ const wasiValue = config.wasi === void 0 ? [] : config.wasi;
1096
+ if (!Array.isArray(wasiValue)) throw new BuildError('"pumpkinPlugin.wasi" must be an array of capability names');
1097
+ const wasi = [];
1098
+ for (const name of wasiValue) {
1099
+ if (typeof name !== "string") {
1100
+ throw new BuildError('"pumpkinPlugin.wasi" must contain only capability names');
1101
+ }
1102
+ if (!isCapability(name)) {
1103
+ throw new BuildError(`unknown wasi capability "${name}" (known: ${Object.keys(CAPABILITIES).join(", ")})`);
1104
+ }
1105
+ wasi.push(name);
1106
+ }
1107
+ return {
1108
+ entry,
1109
+ output,
1110
+ engine,
1111
+ ...staticHermesRuntime === void 0 ? {} : { staticHermesRuntime },
1112
+ ...staticHermesGc === void 0 ? {} : { staticHermesGc },
1113
+ wasi,
1114
+ version: pkg.version
1115
+ };
1116
+ }
1117
+ function requireBuildFields(config) {
1118
+ if (!config.entry || !config.output) {
1119
+ throw new BuildError('package.json needs "pumpkinPlugin": { "entry": ..., "output": ... } to build');
1120
+ }
1121
+ return {
1122
+ entry: requiredRelativePath(config.entry, "entry"),
1123
+ output: requiredRelativePath(config.output, "output")
1124
+ };
1125
+ }
1126
+ function optionalRelativePath(value, name) {
1127
+ if (value === void 0) return void 0;
1128
+ if (typeof value !== "string") throw new BuildError(`"pumpkinPlugin.${name}" must be a relative path`);
1129
+ return requiredRelativePath(value, name);
1130
+ }
1131
+ function requiredRelativePath(value, name) {
1132
+ if (typeof value !== "string") throw new BuildError(`"pumpkinPlugin.${name}" must be a relative path`);
1133
+ const normalized = path5.posix.normalize(value.replaceAll("\\", "/"));
1134
+ if (value.length === 0 || value.includes("\0") || path5.isAbsolute(value) || path5.win32.isAbsolute(value) || path5.win32.parse(value).root !== "" || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
1135
+ throw new BuildError(`"pumpkinPlugin.${name}" must be a path inside the package`);
1136
+ }
1137
+ if (name === "output" && path5.posix.extname(normalized).toLowerCase() !== ".wasm") {
1138
+ throw new BuildError('"pumpkinPlugin.output" must name a .wasm file');
1139
+ }
1140
+ return normalized;
1141
+ }
1142
+ function isRecord(value) {
1143
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1144
+ }
1145
+
1146
+ // src/update-check.ts
1147
+ import { createRequire as createRequire3 } from "node:module";
1148
+ import { latestVersion, ONE_DAY } from "@badisi/latest-version";
1149
+ var packageMetadata = createRequire3(import.meta.url)("../package.json");
1150
+ var packageName = packageMetadata.name;
1151
+ var repositoryUrl = packageMetadata.repository.url.replace(/^git\+/, "").replace(/\.git$/, "");
1152
+ var releaseComponent = packageName.split("/").at(-1) ?? packageName;
1153
+ var requestTimeout = 5e3;
1154
+ function isBuildUpdateCheckAllowed(args, environment, isTTY) {
1155
+ return isTTY && !environment.CI && environment.PUMPKIN_DEV_MODE !== "1" && environment.PUMPKIN_PLUGINS_NO_UPDATE_NOTIFIER !== "1" && !args.some((argument) => ["--help", "-h", "--version", "-V", "--no-update-notifier"].includes(argument));
1156
+ }
1157
+ async function startBuildUpdateCheck(args, options) {
1158
+ if (!isBuildUpdateCheckAllowed(args, options.environment, options.isTTY)) return;
1159
+ try {
1160
+ const result = await (options.lookup ?? latestVersion)(packageName, {
1161
+ useCache: true,
1162
+ cacheMaxAge: ONE_DAY,
1163
+ requestOptions: { timeout: requestTimeout }
1164
+ });
1165
+ const currentVersion = result.local;
1166
+ const availableUpdate = result.updatesAvailable === false ? void 0 : result.updatesAvailable.local;
1167
+ const availableVersion = availableUpdate ? result.latest : void 0;
1168
+ if (result.error || currentVersion === void 0 || availableVersion === void 0) return;
1169
+ (options.write ?? ((notice) => process.stderr.write(notice)))(
1170
+ formatBuildUpdateNotice(currentVersion, availableVersion)
1171
+ );
1172
+ } catch {
1173
+ }
1174
+ }
1175
+ function formatBuildUpdateNotice(currentVersion, latestVersion2) {
1176
+ const content = [
1177
+ "",
1178
+ `Update available v${currentVersion} \u226B v${latestVersion2}`,
1179
+ `Changelog: ${new URL(`releases/tag/${releaseComponent}-v${latestVersion2}`, `${repositoryUrl}/`).href}`,
1180
+ 'Run "pnpm dlx @pumpkin-plugins/codemod update-build ." to update',
1181
+ "",
1182
+ "Follow Filiph Sandstr\xF6m for updates: https://x.com/filiphsandstrom",
1183
+ ""
1184
+ ];
1185
+ const contentWidth = Math.max(...content.map((line) => line.length));
1186
+ const border = "\u2500".repeat(contentWidth + 2);
1187
+ const body = content.map((line) => {
1188
+ const leftPadding = Math.floor((contentWidth - line.length) / 2);
1189
+ const centeredLine = `${" ".repeat(leftPadding)}${line}${" ".repeat(contentWidth - line.length - leftPadding)}`;
1190
+ return `\u2502 ${centeredLine} \u2502`;
1191
+ });
1192
+ return [`\u256D${border}\u256E`, ...body, `\u2570${border}\u256F`, ""].join("\n");
1193
+ }
1194
+
1195
+ // src/wasi-wit.ts
1196
+ import { createHash } from "node:crypto";
1197
+ import * as fs7 from "node:fs";
1198
+ import * as path7 from "node:path";
1199
+
1200
+ // src/wasi-wit-lock.ts
1201
+ import { randomUUID } from "node:crypto";
1202
+ import * as fs6 from "node:fs";
1203
+ import * as path6 from "node:path";
1204
+ var WIT_LOCK_TIMEOUT_MS = 3e4;
1205
+ var WIT_LOCK_RETRY_MS = 25;
1206
+ var WIT_LOCK_STALE_MS = 6e4;
1207
+ async function withWasiWitFileLock(lockPath, operation) {
1208
+ fs6.mkdirSync(path6.dirname(lockPath), { recursive: true });
1209
+ const ownerToken = randomUUID();
1210
+ const reclaimLockPath = `${lockPath}.reclaim`;
1211
+ const deadline = Date.now() + WIT_LOCK_TIMEOUT_MS;
1212
+ while (true) {
1213
+ try {
1214
+ writeWasiWitLock(lockPath, ownerToken);
1215
+ break;
1216
+ } catch (error) {
1217
+ if (errorCode(error) !== "EEXIST") throw error;
1218
+ if (isStaleWasiWitLock(lockPath)) {
1219
+ const reclaimToken = tryAcquireWasiWitReclaimLock(reclaimLockPath);
1220
+ if (reclaimToken !== null) {
1221
+ let acquiredLock = false;
1222
+ try {
1223
+ if (isStaleWasiWitLock(lockPath)) {
1224
+ fs6.rmSync(lockPath, { force: true });
1225
+ try {
1226
+ writeWasiWitLock(lockPath, ownerToken);
1227
+ acquiredLock = true;
1228
+ } catch (writeError) {
1229
+ if (errorCode(writeError) !== "EEXIST") throw writeError;
1230
+ }
1231
+ }
1232
+ } finally {
1233
+ releaseWasiWitFileLock(reclaimLockPath, reclaimToken);
1234
+ }
1235
+ if (acquiredLock) return operationWithWasiWitLock(lockPath, ownerToken, operation);
1236
+ continue;
1237
+ }
1238
+ }
1239
+ if (Date.now() >= deadline) {
1240
+ if (fs6.existsSync(reclaimLockPath)) {
1241
+ throw new BuildError(
1242
+ `timed out waiting for the WASI WIT cache lock at ${lockPath}; inspect recovery claim ${reclaimLockPath} and remove it only after confirming no recovery is active`
1243
+ );
1244
+ }
1245
+ throw new BuildError(`timed out waiting for the WASI WIT cache lock at ${lockPath}`);
1246
+ }
1247
+ await new Promise((resolve7) => setTimeout(resolve7, WIT_LOCK_RETRY_MS));
1248
+ }
1249
+ }
1250
+ return operationWithWasiWitLock(lockPath, ownerToken, operation);
1251
+ }
1252
+ async function operationWithWasiWitLock(lockPath, ownerToken, operation) {
1253
+ try {
1254
+ return await operation();
1255
+ } finally {
1256
+ releaseWasiWitFileLock(lockPath, ownerToken);
1257
+ }
1258
+ }
1259
+ function writeWasiWitLock(lockPath, ownerToken) {
1260
+ const fileDescriptor = fs6.openSync(lockPath, "wx");
1261
+ let createdFile;
1262
+ try {
1263
+ createdFile = fs6.fstatSync(fileDescriptor);
1264
+ fs6.writeFileSync(fileDescriptor, JSON.stringify({ pid: process.pid, token: ownerToken }));
1265
+ fs6.closeSync(fileDescriptor);
1266
+ } catch (error) {
1267
+ try {
1268
+ fs6.closeSync(fileDescriptor);
1269
+ } catch {
1270
+ }
1271
+ if (createdFile !== void 0) {
1272
+ try {
1273
+ removeWasiWitLockIfOwned(lockPath, createdFile);
1274
+ } catch (cleanupError) {
1275
+ throw new AggregateError([error, cleanupError], "failed to write and clean up the WASI WIT lock", {
1276
+ cause: error
1277
+ });
1278
+ }
1279
+ }
1280
+ throw error;
1281
+ }
1282
+ }
1283
+ function removeWasiWitLockIfOwned(lockPath, createdFile) {
1284
+ let currentFile;
1285
+ try {
1286
+ currentFile = fs6.lstatSync(lockPath);
1287
+ } catch (error) {
1288
+ if (errorCode(error) === "ENOENT") return;
1289
+ throw error;
1290
+ }
1291
+ if (currentFile.isFile() && currentFile.dev === createdFile.dev && currentFile.ino === createdFile.ino) {
1292
+ fs6.rmSync(lockPath, { force: true });
1293
+ }
1294
+ }
1295
+ function tryAcquireWasiWitReclaimLock(lockPath) {
1296
+ const ownerToken = randomUUID();
1297
+ try {
1298
+ writeWasiWitLock(lockPath, ownerToken);
1299
+ return ownerToken;
1300
+ } catch (error) {
1301
+ if (errorCode(error) !== "EEXIST") throw error;
1302
+ if (!removeStaleWasiWitLock(lockPath)) return null;
1303
+ try {
1304
+ writeWasiWitLock(lockPath, ownerToken);
1305
+ return ownerToken;
1306
+ } catch (writeError) {
1307
+ if (errorCode(writeError) === "EEXIST") return null;
1308
+ throw writeError;
1309
+ }
1310
+ }
1311
+ }
1312
+ function removeStaleWasiWitLock(lockPath) {
1313
+ let originalFile;
1314
+ try {
1315
+ originalFile = fs6.lstatSync(lockPath);
1316
+ } catch (error) {
1317
+ if (errorCode(error) === "ENOENT") return true;
1318
+ throw error;
1319
+ }
1320
+ let originalContents;
1321
+ try {
1322
+ originalContents = fs6.readFileSync(lockPath, "utf8");
1323
+ } catch (error) {
1324
+ if (errorCode(error) === "ENOENT") return true;
1325
+ throw error;
1326
+ }
1327
+ if (!isStaleWasiWitLock(lockPath)) return false;
1328
+ let currentFile;
1329
+ try {
1330
+ currentFile = fs6.lstatSync(lockPath);
1331
+ } catch (error) {
1332
+ if (errorCode(error) === "ENOENT") return true;
1333
+ throw error;
1334
+ }
1335
+ if (currentFile.dev !== originalFile.dev || currentFile.ino !== originalFile.ino) return false;
1336
+ let currentContents;
1337
+ try {
1338
+ currentContents = fs6.readFileSync(lockPath, "utf8");
1339
+ } catch (error) {
1340
+ if (errorCode(error) === "ENOENT") return true;
1341
+ throw error;
1342
+ }
1343
+ if (currentContents !== originalContents || !isStaleWasiWitLock(lockPath)) return false;
1344
+ fs6.rmSync(lockPath, { force: true });
1345
+ return true;
1346
+ }
1347
+ function isStaleWasiWitLock(lockPath) {
1348
+ let contents;
1349
+ try {
1350
+ contents = fs6.readFileSync(lockPath, "utf8");
1351
+ } catch (error) {
1352
+ if (errorCode(error) === "ENOENT") return true;
1353
+ throw error;
1354
+ }
1355
+ try {
1356
+ const owner = JSON.parse(contents);
1357
+ if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
1358
+ try {
1359
+ process.kill(owner.pid, 0);
1360
+ return false;
1361
+ } catch (error) {
1362
+ return errorCode(error) === "ESRCH";
1363
+ }
1364
+ }
1365
+ } catch (error) {
1366
+ if (!(error instanceof SyntaxError)) throw error;
1367
+ }
1368
+ return Date.now() - fs6.statSync(lockPath).mtimeMs > WIT_LOCK_STALE_MS;
1369
+ }
1370
+ function releaseWasiWitFileLock(lockPath, ownerToken) {
1371
+ let contents;
1372
+ try {
1373
+ contents = fs6.readFileSync(lockPath, "utf8");
1374
+ } catch (error) {
1375
+ if (errorCode(error) === "ENOENT") return;
1376
+ throw error;
1377
+ }
1378
+ try {
1379
+ const owner = JSON.parse(contents);
1380
+ if (owner.token === ownerToken) fs6.rmSync(lockPath, { force: true });
1381
+ } catch (error) {
1382
+ if (!(error instanceof SyntaxError)) throw error;
1383
+ }
1384
+ }
1385
+ function errorCode(error) {
1386
+ if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
1387
+ return typeof error.code === "string" ? error.code : void 0;
1388
+ }
1389
+
1390
+ // src/wasi-wit.ts
1391
+ var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
1392
+ function isRecord2(value) {
1393
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1394
+ }
1395
+ function isSafeWitPath(file) {
1396
+ if (!file.endsWith(".wit") || file.includes("\\") || file.includes(":") || file.includes("\0") || path7.posix.isAbsolute(file) || path7.win32.isAbsolute(file)) {
1397
+ return false;
1398
+ }
1399
+ return file.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
1400
+ }
1401
+ function assertWitLockEntries(kind, entries) {
1402
+ if (!isRecord2(entries)) throw new BuildError(`WASI WIT lock ${kind} entries must be an object`);
1403
+ for (const [file, entry] of Object.entries(entries)) {
1404
+ if (!isSafeWitPath(file)) throw new BuildError(`unsafe WASI WIT ${kind} path: ${file}`);
1405
+ if (typeof entry !== "string") throw new BuildError(`WASI WIT ${kind} entry ${file} must be a string`);
1406
+ }
1407
+ }
1408
+ function validateWasiWitLock(lock) {
1409
+ if (!isRecord2(lock)) throw new BuildError("WASI WIT lock must be an object");
1410
+ const version = lock.version;
1411
+ if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version)) {
1412
+ throw new BuildError("unsafe WASI WIT lock version; expected a numeric x.y.z release");
1413
+ }
1414
+ const files = lock.files;
1415
+ assertWitLockEntries("file", files);
1416
+ for (const [file, hash] of Object.entries(files)) {
1417
+ if (!/^[0-9a-f]{64}$/.test(hash)) throw new BuildError(`WASI WIT file ${file} has an invalid SHA-256 hash`);
1418
+ }
1419
+ const sources = lock.sources;
1420
+ if (sources !== void 0) {
1421
+ assertWitLockEntries("source", sources);
1422
+ for (const file of Object.keys(sources)) {
1423
+ if (!Object.hasOwn(files, file))
1424
+ throw new BuildError(`WASI WIT source ${file} is missing from the files lock`);
1425
+ }
1426
+ }
1427
+ const generatedFiles = lock.generatedFiles;
1428
+ if (generatedFiles !== void 0) {
1429
+ assertWitLockEntries("generated-file", generatedFiles);
1430
+ for (const [file, contents] of Object.entries(generatedFiles)) {
1431
+ const expectedHash = files[file];
1432
+ if (!Object.hasOwn(files, file)) {
1433
+ throw new BuildError(`generated WIT file ${file} is missing from the files lock`);
1434
+ }
1435
+ if (sha256(new TextEncoder().encode(contents)) !== expectedHash) {
1436
+ throw new BuildError(`generated WIT file ${file} does not match its pinned SHA-256 hash`);
1437
+ }
1438
+ }
1439
+ }
1440
+ }
1441
+ var defaultFetch = async (url) => {
1442
+ const res = await fetch(url);
1443
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1444
+ return new Uint8Array(await res.arrayBuffer());
1445
+ };
1446
+ function readLock() {
1447
+ return JSON.parse(fs7.readFileSync(path7.join(import.meta.dirname, "../wasi-wit.lock.json"), "utf8"));
1448
+ }
1449
+ function isCached(file, expected) {
1450
+ try {
1451
+ if (!fs7.lstatSync(file).isFile()) return false;
1452
+ return sha256(fs7.readFileSync(file)) === expected;
1453
+ } catch {
1454
+ return false;
1455
+ }
1456
+ }
1457
+ async function ensureWasiWit(options) {
1458
+ const lock = options.lock ?? readLock();
1459
+ validateWasiWitLock(lock);
1460
+ const fetchFile = options.fetchFile ?? defaultFetch;
1461
+ const dir = path7.join(options.cacheDir, "wasi-wit", lock.version);
1462
+ const generatedFiles = lock.generatedFiles ?? {};
1463
+ const missing = Object.entries(lock.files).filter(([rel, hash]) => !isCached(path7.join(dir, rel), hash));
1464
+ await Promise.all(
1465
+ missing.map(async ([rel, hash]) => {
1466
+ let bytes;
1467
+ const generated = generatedFiles[rel];
1468
+ if (generated !== void 0) {
1469
+ bytes = new TextEncoder().encode(generated);
1470
+ } else {
1471
+ const url = lock.sources?.[rel] ?? `https://raw.githubusercontent.com/WebAssembly/WASI/v${lock.version}/wasip2/${rel}`;
1472
+ try {
1473
+ bytes = await fetchFile(url);
1474
+ } catch (err) {
1475
+ throw new BuildError(
1476
+ `could not download ${url} (${err instanceof Error ? err.message : err}). The first build needs network access; the files are cached in ${dir} afterwards.`
1477
+ );
1478
+ }
1479
+ }
1480
+ const actual = sha256(bytes);
1481
+ if (actual !== hash) {
1482
+ const source = generated === void 0 ? lock.sources?.[rel] ?? `WASI v${lock.version}` : "the lock";
1483
+ throw new BuildError(`${rel} from ${source} has hash ${actual}, expected ${hash}`);
1484
+ }
1485
+ await writeAtomically(path7.join(dir, rel), bytes, hash, options.cacheDir);
1486
+ })
1487
+ );
1488
+ return dir;
1489
+ }
1490
+ async function writeAtomically(file, bytes, expectedHash, stagingDirectory) {
1491
+ const parent = path7.dirname(file);
1492
+ fs7.mkdirSync(parent, { recursive: true });
1493
+ fs7.mkdirSync(stagingDirectory, { recursive: true });
1494
+ const temporaryDirectory = fs7.mkdtempSync(path7.join(stagingDirectory, ".wasi-wit-"));
1495
+ const temporary = path7.join(temporaryDirectory, path7.basename(file));
1496
+ try {
1497
+ fs7.writeFileSync(temporary, bytes);
1498
+ const lockName = createHash("sha256").update(path7.resolve(file)).digest("hex");
1499
+ await withWasiWitFileLock(path7.join(stagingDirectory, `.wasi-wit-${lockName}.lock`), () => {
1500
+ if (isCached(file, expectedHash)) return;
1501
+ fs7.rmSync(file, { recursive: true, force: true });
1502
+ fs7.renameSync(temporary, file);
1503
+ });
1504
+ } finally {
1505
+ fs7.rmSync(temporaryDirectory, { recursive: true, force: true });
1506
+ }
1507
+ }
1508
+
1509
+ // src/wit.ts
1510
+ import * as fs8 from "node:fs";
1511
+ import * as path8 from "node:path";
1512
+ function injectWasiImports(pluginWit, interfaces) {
1513
+ const firstExport = /^[ \t]*export /m.exec(pluginWit);
1514
+ if (!firstExport) throw new BuildError("could not find the exports in plugin.wit; the API layout changed");
1515
+ const imports = interfaces.map((i) => {
1516
+ const [pkg, name] = i.split("/");
1517
+ return ` import wasi:${pkg}/${name}@${WASI_VERSION};`;
1518
+ });
1519
+ const header = ` // --- WASI (added by pumpkin-plugins-build) ---
1520
+ ${imports.join("\n")}
1521
+
1522
+ `;
1523
+ return `${pluginWit.slice(0, firstExport.index)}${header}${pluginWit.slice(firstExport.index)}`;
1524
+ }
1525
+ function prepareWit(options) {
1526
+ if (options.interfaces.length === 0) return options.apiWit;
1527
+ const { wasiWit } = options;
1528
+ if (!wasiWit) {
1529
+ throw new BuildError("the WASI WIT files are needed but were not provided");
1530
+ }
1531
+ const parent = path8.dirname(options.target);
1532
+ fs8.mkdirSync(parent, { recursive: true });
1533
+ const temporary = fs8.mkdtempSync(path8.join(parent, `.${path8.basename(options.target)}-`));
1534
+ try {
1535
+ retryTransientCopy(
1536
+ () => fs8.cpSync(options.apiWit, temporary, { recursive: true }),
1537
+ () => {
1538
+ fs8.rmSync(temporary, { recursive: true, force: true });
1539
+ fs8.mkdirSync(temporary);
1540
+ }
1541
+ );
1542
+ if (options.wasiFiles) {
1543
+ for (const rel of options.wasiFiles) {
1544
+ const target = path8.join(temporary, "deps", rel);
1545
+ fs8.mkdirSync(path8.dirname(target), { recursive: true });
1546
+ fs8.copyFileSync(path8.join(wasiWit, rel), target);
1547
+ }
1548
+ } else {
1549
+ const deps = path8.join(temporary, "deps");
1550
+ retryTransientCopy(
1551
+ () => fs8.cpSync(wasiWit, deps, { recursive: true }),
1552
+ () => fs8.rmSync(deps, { recursive: true, force: true })
1553
+ );
1554
+ }
1555
+ const pluginWit = path8.join(temporary, "plugin.wit");
1556
+ fs8.writeFileSync(pluginWit, injectWasiImports(fs8.readFileSync(pluginWit, "utf8"), options.interfaces));
1557
+ fs8.rmSync(options.target, { recursive: true, force: true });
1558
+ fs8.renameSync(temporary, options.target);
1559
+ } catch (error) {
1560
+ fs8.rmSync(temporary, { recursive: true, force: true });
1561
+ throw error;
1562
+ }
1563
+ return options.target;
1564
+ }
1565
+ function publishWitSnapshot(source, target) {
1566
+ const parent = path8.dirname(target);
1567
+ fs8.mkdirSync(parent, { recursive: true });
1568
+ const temporary = fs8.mkdtempSync(path8.join(parent, `.${path8.basename(target)}-`));
1569
+ let backup;
1570
+ try {
1571
+ fs8.cpSync(source, temporary, { recursive: true });
1572
+ if (fs8.existsSync(target)) {
1573
+ backup = fs8.mkdtempSync(path8.join(parent, `.${path8.basename(target)}-backup-`));
1574
+ fs8.rmSync(backup, { recursive: true, force: true });
1575
+ fs8.renameSync(target, backup);
1576
+ }
1577
+ try {
1578
+ fs8.renameSync(temporary, target);
1579
+ } catch (error) {
1580
+ if (backup !== void 0) {
1581
+ try {
1582
+ fs8.renameSync(backup, target);
1583
+ backup = void 0;
1584
+ } catch (restoreError) {
1585
+ throw new AggregateError(
1586
+ [error, restoreError],
1587
+ `could not publish WIT snapshot; previous snapshot remains at ${backup}`
1588
+ );
1589
+ }
1590
+ }
1591
+ throw error;
1592
+ }
1593
+ if (backup !== void 0) fs8.rmSync(backup, { recursive: true, force: true });
1594
+ } catch (error) {
1595
+ fs8.rmSync(temporary, { recursive: true, force: true });
1596
+ throw error;
1597
+ }
1598
+ }
1599
+ function retryTransientCopy(copy, reset) {
1600
+ for (let attempt = 0; ; attempt += 1) {
1601
+ try {
1602
+ copy();
1603
+ return;
1604
+ } catch (error) {
1605
+ if (!isInputOutputError(error) || attempt === 2) throw error;
1606
+ reset();
1607
+ }
1608
+ }
1609
+ }
1610
+ function isInputOutputError(error) {
1611
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EIO";
1612
+ }
1613
+
1614
+ // src/cli.ts
1615
+ async function run(args, pluginDir) {
1616
+ const command = new Command().name("pumpkin-plugins-build").description("Build the Pumpkin plugin in the current directory.").version(createRequire4(import.meta.url)("../package.json").version).option("--types-only", "Generate guest declarations without building a component").option("--parse-ts", "Use Hermes TypeScript parsing for the component bundle").option("--no-update-notifier", "Skip the npm update check").configureOutput({ outputError: () => {
1617
+ } }).exitOverride();
1618
+ try {
1619
+ command.parse(args, { from: "user" });
1620
+ } catch (error) {
1621
+ if (!(error instanceof CommanderError)) throw error;
1622
+ if (error.exitCode === 0) return;
1623
+ throw new BuildError(error.message.replace(/^error: /, ""));
1624
+ }
1625
+ const { typesOnly, parseTs } = command.opts();
1626
+ const config = readPluginConfig(pluginDir);
1627
+ if (parseTs && config.engine !== "static-hermes") {
1628
+ throw new BuildError("--parse-ts requires Static Hermes");
1629
+ }
1630
+ const buildFields = typesOnly ? void 0 : requireBuildFields(config);
1631
+ const buildDir = path9.join(pluginDir, "build");
1632
+ const apiWit = resolveApiWit(pluginDir);
1633
+ const interfaces = wasiInterfaces(config.wasi);
1634
+ const wasiLock = readLock();
1635
+ const wasiWit = interfaces.length > 0 ? await ensureWasiWit({ cacheDir: cacheDir(pluginDir), lock: wasiLock }) : void 0;
1636
+ const wasiFiles = Object.keys(wasiLock.files);
1637
+ if (!buildFields) {
1638
+ let witDir = apiWit;
1639
+ let temporaryWitRoot2;
1640
+ try {
1641
+ if (interfaces.length > 0) {
1642
+ temporaryWitRoot2 = fs9.mkdtempSync(path9.join(os.tmpdir(), "pumpkin-plugin-types-"));
1643
+ witDir = prepareWit({
1644
+ apiWit,
1645
+ interfaces,
1646
+ wasiWit,
1647
+ wasiFiles,
1648
+ target: path9.join(temporaryWitRoot2, "wit")
1649
+ });
1650
+ }
1651
+ generateTypes(witDir, path9.join(buildDir, "types", "bindings"));
1652
+ fs9.rmSync(path9.join(buildDir, "types", "wit"), { recursive: true, force: true });
1653
+ } finally {
1654
+ if (temporaryWitRoot2 !== void 0) fs9.rmSync(temporaryWitRoot2, { recursive: true, force: true });
1655
+ }
1656
+ return;
1657
+ }
1658
+ const { entry, output } = buildFields;
1659
+ const temporaryWitRoot = interfaces.length > 0 ? fs9.mkdtempSync(path9.join(os.tmpdir(), "pumpkin-plugin-build-wit-")) : void 0;
1660
+ try {
1661
+ const witDir = prepareWit({
1662
+ apiWit,
1663
+ interfaces,
1664
+ wasiWit,
1665
+ wasiFiles,
1666
+ target: temporaryWitRoot === void 0 ? path9.join(buildDir, "wit") : path9.join(temporaryWitRoot, "wit")
1667
+ });
1668
+ const witSnapshot = path9.join(buildDir, "wit");
1669
+ await withWasiWitFileLock(path9.join(buildDir, ".wit-publish.lock"), () => {
1670
+ if (temporaryWitRoot !== void 0) publishWitSnapshot(witDir, witSnapshot);
1671
+ else fs9.rmSync(witSnapshot, { recursive: true, force: true });
1672
+ });
1673
+ const engine = config.engine ?? "static-hermes";
1674
+ const staticHermesRuntime = engine === "static-hermes" ? process.env.STATIC_HERMES_ENABLE_LEAN_ARCHIVE === "0" ? "full" : config.staticHermesRuntime ?? "lean" : void 0;
1675
+ const staticHermesGc = engine === "static-hermes" ? config.staticHermesGc ?? "hades" : void 0;
1676
+ printBuildOutput({
1677
+ type: "bundle",
1678
+ entry,
1679
+ engine,
1680
+ ...staticHermesRuntime === void 0 ? {} : { runtimeVariant: staticHermesRuntime },
1681
+ ...staticHermesGc === void 0 ? {} : { gcVariant: staticHermesGc },
1682
+ ...parseTs ? { parseTypeScript: true } : {}
1683
+ });
1684
+ const size = await bundlePlugin({
1685
+ projectRoot: pluginDir,
1686
+ entry: path9.resolve(pluginDir, entry),
1687
+ output: path9.resolve(pluginDir, output),
1688
+ witDir,
1689
+ version: config.version,
1690
+ engine,
1691
+ quietRuntimeOutput: process.env.PUMPKIN_PLUGINS_VERBOSE_BUILD !== "1",
1692
+ onOutput: printBuildOutput,
1693
+ ...staticHermesRuntime === void 0 ? {} : { staticHermesRuntime },
1694
+ ...staticHermesGc === void 0 ? {} : { staticHermesGc },
1695
+ ...parseTs ? { parseTypeScript: true } : {},
1696
+ developmentMode: process.env.PUMPKIN_DEV_MODE === "1"
1697
+ });
1698
+ printBuildOutput({ type: "built", output, sizeBytes: size });
1699
+ } finally {
1700
+ if (temporaryWitRoot !== void 0) fs9.rmSync(temporaryWitRoot, { recursive: true, force: true });
1701
+ }
1702
+ }
1703
+ async function executeCli(args, pluginDir, options = {}) {
1704
+ const startUpdateCheck = options.startUpdateCheck ?? startBuildUpdateCheck;
1705
+ const environment = options.environment ?? process.env;
1706
+ const isTTY = options.isTTY ?? process.stdout.isTTY === true;
1707
+ if (isBuildUpdateCheckAllowed(args, environment, isTTY)) {
1708
+ void startUpdateCheck(args, { environment, isTTY }).catch(() => void 0);
1709
+ }
1710
+ await main(args, pluginDir);
1711
+ }
1712
+ function resolveApiWit(pluginDir, resolvePackageJson = (root, packageSpecifier) => createRequire4(path9.join(root, "package.json")).resolve(packageSpecifier)) {
1713
+ let packageJson;
1714
+ try {
1715
+ const canonicalSpecifier = "@pumpkin-plugins/pumpkin-api/package.json";
1716
+ const { aliases, hasCanonicalDependency } = apiPackageSpecifiers(pluginDir);
1717
+ const packageSpecifiers = hasCanonicalDependency || aliases.length === 0 ? [canonicalSpecifier, ...aliases] : [...aliases, canonicalSpecifier];
1718
+ let resolvedPackageJson;
1719
+ for (const packageSpecifier of packageSpecifiers) {
1720
+ try {
1721
+ resolvedPackageJson = resolvePackageJson(pluginDir, packageSpecifier);
1722
+ break;
1723
+ } catch {
1724
+ }
1725
+ }
1726
+ if (resolvedPackageJson === void 0) throw new Error("API package manifest was not resolved");
1727
+ packageJson = resolvedPackageJson;
1728
+ } catch {
1729
+ throw new BuildError(
1730
+ "cannot resolve @pumpkin-plugins/pumpkin-api or its declared package alias from this plugin; add it to the plugin dependencies and run your package manager install"
1731
+ );
1732
+ }
1733
+ const apiRoot = path9.dirname(packageJson);
1734
+ const lockFile = path9.join(apiRoot, "wit.lock.json");
1735
+ try {
1736
+ return ensurePumpkinWit({ lockFile, cacheRoot: cacheDir(pluginDir) });
1737
+ } catch (error) {
1738
+ throw new BuildError(
1739
+ `could not resolve pinned WIT for @pumpkin-plugins/pumpkin-api: ${error instanceof Error ? error.message : String(error)}`
1740
+ );
1741
+ }
1742
+ }
1743
+ function apiPackageSpecifiers(pluginDir) {
1744
+ let packageManifest;
1745
+ try {
1746
+ packageManifest = JSON.parse(fs9.readFileSync(path9.join(pluginDir, "package.json"), "utf8"));
1747
+ } catch {
1748
+ return { aliases: [], hasCanonicalDependency: false };
1749
+ }
1750
+ if (typeof packageManifest !== "object" || packageManifest === null || Array.isArray(packageManifest)) {
1751
+ return { aliases: [], hasCanonicalDependency: false };
1752
+ }
1753
+ const dependencies = packageManifest;
1754
+ const aliases = /* @__PURE__ */ new Set();
1755
+ let hasCanonicalDependency = false;
1756
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
1757
+ const declarations = dependencies[field];
1758
+ if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) continue;
1759
+ for (const [packageName2, specification] of Object.entries(declarations)) {
1760
+ if (packageName2 === "@pumpkin-plugins/pumpkin-api") {
1761
+ hasCanonicalDependency = true;
1762
+ continue;
1763
+ }
1764
+ if (typeof specification === "string" && /^npm:@pumpkin-plugins\/pumpkin-api(?:@|$)/.test(specification)) {
1765
+ aliases.add(`${packageName2}/package.json`);
1766
+ }
1767
+ }
1768
+ }
1769
+ return { aliases: [...aliases], hasCanonicalDependency };
1770
+ }
1771
+ async function main(args, pluginDir) {
1772
+ try {
1773
+ await run(args, pluginDir);
1774
+ } catch (err) {
1775
+ if (!(err instanceof BuildError)) throw err;
1776
+ printBuildOutput({ type: "error", message: err.message });
1777
+ process.exitCode = 1;
1778
+ }
1779
+ }
1780
+ if (process.argv[1] && fs9.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
1781
+ await executeCli(process.argv.slice(2), process.cwd());
1782
+ }
1783
+ export {
1784
+ executeCli,
1785
+ main,
1786
+ resolveApiWit,
1787
+ run
1788
+ };