@swifttui/build 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +77 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/src/build/buildAppWasm.d.ts +21 -0
- package/dist/src/build/buildAppWasm.js +61 -0
- package/dist/src/build/buildAppWasm.js.map +1 -0
- package/dist/src/build/buildSwiftTUIWebApp.d.ts +10 -0
- package/dist/src/build/buildSwiftTUIWebApp.js +23 -0
- package/dist/src/build/buildSwiftTUIWebApp.js.map +1 -0
- package/dist/src/build/generateSceneManifest.d.ts +13 -0
- package/dist/src/build/generateSceneManifest.js +28 -0
- package/dist/src/build/generateSceneManifest.js.map +1 -0
- package/dist/src/build/optimizePackagedWasm.js +17 -0
- package/dist/src/build/optimizePackagedWasm.js.map +1 -0
- package/dist/src/build/resolveSwiftArtifacts.d.ts +39 -0
- package/dist/src/build/resolveSwiftArtifacts.js +161 -0
- package/dist/src/build/resolveSwiftArtifacts.js.map +1 -0
- package/dist/src/build/runCommand.js +55 -0
- package/dist/src/build/runCommand.js.map +1 -0
- package/dist/src/build/stripPackagedWasm.js +16 -0
- package/dist/src/build/stripPackagedWasm.js.map +1 -0
- package/dist/src/build/swiftCommandPrefix.js +14 -0
- package/dist/src/build/swiftCommandPrefix.js.map +1 -0
- package/dist/src/build/wasmTypeDiagnostics.js +204 -0
- package/dist/src/build/wasmTypeDiagnostics.js.map +1 -0
- package/package.json +25 -11
- package/AGENTS.md +0 -44
- package/cli.ts +0 -98
- package/index.ts +0 -4
- package/src/build/buildAppWasm.test.ts +0 -178
- package/src/build/buildAppWasm.ts +0 -107
- package/src/build/buildSwiftTUIWebApp.ts +0 -22
- package/src/build/generateSceneManifest.ts +0 -46
- package/src/build/optimizePackagedWasm.ts +0 -20
- package/src/build/resolveSwiftArtifacts.test.ts +0 -90
- package/src/build/resolveSwiftArtifacts.ts +0 -253
- package/src/build/runCommand.ts +0 -81
- package/src/build/stripPackagedWasm.ts +0 -19
- package/src/build/swiftCommandPrefix.ts +0 -9
- package/src/build/wasmTypeDiagnostics.ts +0 -313
- package/tsconfig.json +0 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runCommand.js","names":[],"sources":["../../../src/build/runCommand.ts"],"sourcesContent":["import { accessSync } from \"node:fs\";\nimport { delimiter, join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nexport interface RunCommandOptions {\n cwd?: string;\n env?: Record<string, string | undefined>;\n}\n\nexport async function runCommand(\n cmd: string[],\n options: RunCommandOptions = {}\n): Promise<string> {\n const executable = cmd[0];\n if (!executable) {\n throw new Error(\"cannot run an empty command\");\n }\n\n const proc = spawn(executable, cmd.slice(1), {\n cwd: options.cwd,\n env: normalizeEnvironment(options.env),\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n stdoutChunks.push(chunk);\n });\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n const exitCode = await new Promise<number | null>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", resolve);\n });\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (exitCode !== 0) {\n throw new Error([stdout, stderr].filter(Boolean).join(\"\\n\").trim() || `command failed: ${cmd.join(\" \")}`);\n }\n\n return stdout;\n}\n\nexport function findExecutable(\n name: string,\n pathValue: string | undefined = process.env.PATH\n): string | undefined {\n for (const directory of pathValue?.split(delimiter) ?? []) {\n if (!directory) {\n continue;\n }\n const candidate = join(directory, name);\n try {\n accessSync(candidate);\n return candidate;\n } catch {\n continue;\n }\n }\n return undefined;\n}\n\nfunction normalizeEnvironment(\n env: Record<string, string | undefined> | undefined\n): NodeJS.ProcessEnv | undefined {\n if (!env) {\n return undefined;\n }\n\n const normalized: NodeJS.ProcessEnv = {};\n for (const [key, value] of Object.entries(env)) {\n if (value !== undefined) {\n normalized[key] = value;\n }\n }\n return normalized;\n}\n"],"mappings":";;;;AASA,eAAsB,WACpB,KACA,UAA6B,CAAC,GACb;CACjB,MAAM,aAAa,IAAI;CACvB,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,OAAO,MAAM,YAAY,IAAI,MAAM,CAAC,GAAG;EAC3C,KAAK,QAAQ;EACb,KAAK,qBAAqB,QAAQ,GAAG;EACrC,OAAO;GAAC;GAAU;GAAQ;EAAM;CAClC,CAAC;CAED,MAAM,eAAyB,CAAC;CAChC,MAAM,eAAyB,CAAC;CAChC,KAAK,OAAO,GAAG,SAAS,UAAkB;EACxC,aAAa,KAAK,KAAK;CACzB,CAAC;CACD,KAAK,OAAO,GAAG,SAAS,UAAkB;EACxC,aAAa,KAAK,KAAK;CACzB,CAAC;CAED,MAAM,WAAW,MAAM,IAAI,SAAwB,SAAS,WAAW;EACrE,KAAK,GAAG,SAAS,MAAM;EACvB,KAAK,GAAG,SAAS,OAAO;CAC1B,CAAC;CACD,MAAM,SAAS,OAAO,OAAO,YAAY,CAAC,CAAC,SAAS;CACpD,MAAM,SAAS,OAAO,OAAO,YAAY,CAAC,CAAC,SAAS;CAEpD,IAAI,aAAa,GACf,MAAM,IAAI,MAAM,CAAC,QAAQ,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,mBAAmB,IAAI,KAAK,GAAG,GAAG;CAG1G,OAAO;AACT;AAEA,SAAgB,eACd,MACA,YAAgC,QAAQ,IAAI,MACxB;CACpB,KAAK,MAAM,aAAa,WAAW,MAAM,SAAS,KAAK,CAAC,GAAG;EACzD,IAAI,CAAC,WACH;EAEF,MAAM,YAAY,KAAK,WAAW,IAAI;EACtC,IAAI;GACF,WAAW,SAAS;GACpB,OAAO;EACT,QAAQ;GACN;EACF;CACF;AAEF;AAEA,SAAS,qBACP,KAC+B;CAC/B,IAAI,CAAC,KACH;CAGF,MAAM,aAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAC3C,IAAI,UAAU,KAAA,GACZ,WAAW,OAAO;CAGtB,OAAO;AACT"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { findExecutable, runCommand } from "./runCommand.js";
|
|
2
|
+
//#region src/build/stripPackagedWasm.ts
|
|
3
|
+
async function stripPackagedWasm(wasmPath) {
|
|
4
|
+
const objcopyPath = findExecutable("llvm-objcopy");
|
|
5
|
+
if (!objcopyPath) throw new Error("missing llvm-objcopy in PATH; install the swiftly-managed toolchain before packaging wasm");
|
|
6
|
+
await runCommand([
|
|
7
|
+
objcopyPath,
|
|
8
|
+
"--strip-debug",
|
|
9
|
+
"--remove-section=name",
|
|
10
|
+
wasmPath
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { stripPackagedWasm };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=stripPackagedWasm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripPackagedWasm.js","names":[],"sources":["../../../src/build/stripPackagedWasm.ts"],"sourcesContent":["import { findExecutable, runCommand } from \"./runCommand.ts\";\n\nexport async function stripPackagedWasm(\n wasmPath: string\n): Promise<void> {\n const objcopyPath = findExecutable(\"llvm-objcopy\");\n if (!objcopyPath) {\n throw new Error(\n \"missing llvm-objcopy in PATH; install the swiftly-managed toolchain before packaging wasm\"\n );\n }\n\n await runCommand([\n objcopyPath,\n \"--strip-debug\",\n \"--remove-section=name\",\n wasmPath,\n ]);\n}\n"],"mappings":";;AAEA,eAAsB,kBACpB,UACe;CACf,MAAM,cAAc,eAAe,cAAc;CACjD,IAAI,CAAC,aACH,MAAM,IAAI,MACR,2FACF;CAGF,MAAM,WAAW;EACf;EACA;EACA;EACA;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { findExecutable } from "./runCommand.js";
|
|
2
|
+
//#region src/build/swiftCommandPrefix.ts
|
|
3
|
+
function swiftCommandPrefix() {
|
|
4
|
+
if (findExecutable("swiftly")) return [
|
|
5
|
+
"swiftly",
|
|
6
|
+
"run",
|
|
7
|
+
"swift"
|
|
8
|
+
];
|
|
9
|
+
return ["swift"];
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { swiftCommandPrefix };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=swiftCommandPrefix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swiftCommandPrefix.js","names":[],"sources":["../../../src/build/swiftCommandPrefix.ts"],"sourcesContent":["import { findExecutable } from \"./runCommand.ts\";\n\nexport function swiftCommandPrefix(): string[] {\n if (findExecutable(\"swiftly\")) {\n return [\"swiftly\", \"run\", \"swift\"];\n }\n\n return [\"swift\"];\n}\n"],"mappings":";;AAEA,SAAgB,qBAA+B;CAC7C,IAAI,eAAe,SAAS,GAC1B,OAAO;EAAC;EAAW;EAAO;CAAO;CAGnC,OAAO,CAAC,OAAO;AACjB"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
//#region src/build/wasmTypeDiagnostics.ts
|
|
3
|
+
const wasmMagic = [
|
|
4
|
+
0,
|
|
5
|
+
97,
|
|
6
|
+
115,
|
|
7
|
+
109
|
|
8
|
+
];
|
|
9
|
+
const wasmVersion = [
|
|
10
|
+
1,
|
|
11
|
+
0,
|
|
12
|
+
0,
|
|
13
|
+
0
|
|
14
|
+
];
|
|
15
|
+
const typeSectionID = 1;
|
|
16
|
+
const functionTypeTag = 96;
|
|
17
|
+
const browserMaxTypeParameterCount = 1e3;
|
|
18
|
+
function formatWasmTypeDiagnostics(bytes) {
|
|
19
|
+
const summary = summarizeWasmTypes(bytes);
|
|
20
|
+
const components = [`size=${bytes.byteLength} bytes`, `sha256=${createHash("sha256").update(bytes).digest("hex")}`];
|
|
21
|
+
if (summary.typeCount !== void 0) components.push(`typeCount=${summary.typeCount}`);
|
|
22
|
+
if (summary.maxTypeParameterCount !== void 0) components.push(`maxTypeParameterCount=${summary.maxTypeParameterCount}`);
|
|
23
|
+
if (summary.maxTypeParameterTypeIndex !== void 0) components.push(`maxTypeParameterTypeIndex=${summary.maxTypeParameterTypeIndex}`);
|
|
24
|
+
if (summary.overBrowserLimitTypes.length > 0) components.push(`overBrowserLimitTypes=${summary.overBrowserLimitTypes.join(",")}`);
|
|
25
|
+
if (summary.topParameterCounts.length > 0) components.push(`largestTypes=${summary.topParameterCounts.map(formatLargestTypeSummary).join(";")}`);
|
|
26
|
+
if (summary.note) components.push(`note=${summary.note}`);
|
|
27
|
+
return `wasm diagnostics: ${components.join(", ")}`;
|
|
28
|
+
}
|
|
29
|
+
function formatLargestTypeSummary(summary) {
|
|
30
|
+
return `${summary.typeIndex}:${summary.parameterCount}`;
|
|
31
|
+
}
|
|
32
|
+
function summarizeWasmTypes(bytes) {
|
|
33
|
+
if (!hasExpectedPrefix(bytes, 0, wasmMagic)) return {
|
|
34
|
+
note: "missing wasm magic header",
|
|
35
|
+
overBrowserLimitTypes: [],
|
|
36
|
+
topParameterCounts: []
|
|
37
|
+
};
|
|
38
|
+
if (!hasExpectedPrefix(bytes, wasmMagic.length, wasmVersion)) return {
|
|
39
|
+
note: "missing wasm version header",
|
|
40
|
+
overBrowserLimitTypes: [],
|
|
41
|
+
topParameterCounts: []
|
|
42
|
+
};
|
|
43
|
+
let offset = 8;
|
|
44
|
+
while (offset < bytes.length) {
|
|
45
|
+
let sectionHeader;
|
|
46
|
+
try {
|
|
47
|
+
sectionHeader = readSectionHeader(bytes, offset);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
note: `failed to read section header at byte ${offset}: ${describeError(error)}`,
|
|
51
|
+
overBrowserLimitTypes: [],
|
|
52
|
+
topParameterCounts: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (sectionHeader.id === typeSectionID) return summarizeTypeSection(bytes, sectionHeader.startOffset, sectionHeader.nextOffset);
|
|
56
|
+
offset = sectionHeader.nextOffset;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
note: "module has no type section",
|
|
60
|
+
overBrowserLimitTypes: [],
|
|
61
|
+
topParameterCounts: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function summarizeTypeSection(bytes, startOffset, endOffset) {
|
|
65
|
+
let offset = startOffset;
|
|
66
|
+
let typeCount;
|
|
67
|
+
try {
|
|
68
|
+
const result = readUnsignedLEB128(bytes, offset);
|
|
69
|
+
typeCount = result.value;
|
|
70
|
+
offset = result.nextOffset;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
note: `failed to read type vector length: ${describeError(error)}`,
|
|
74
|
+
overBrowserLimitTypes: [],
|
|
75
|
+
topParameterCounts: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
let maxTypeParameterCount = -1;
|
|
79
|
+
let maxTypeParameterTypeIndex = -1;
|
|
80
|
+
const overBrowserLimitTypes = [];
|
|
81
|
+
const topParameterCounts = [];
|
|
82
|
+
for (let typeIndex = 0; typeIndex < typeCount; typeIndex += 1) {
|
|
83
|
+
if (offset >= endOffset) return {
|
|
84
|
+
note: `type section ended early while reading type ${typeIndex}`,
|
|
85
|
+
overBrowserLimitTypes,
|
|
86
|
+
topParameterCounts,
|
|
87
|
+
typeCount
|
|
88
|
+
};
|
|
89
|
+
const typeTag = bytes[offset];
|
|
90
|
+
offset += 1;
|
|
91
|
+
if (typeTag !== functionTypeTag) return {
|
|
92
|
+
note: `unexpected type tag 0x${typeTag.toString(16)} at type ${typeIndex}`,
|
|
93
|
+
overBrowserLimitTypes,
|
|
94
|
+
topParameterCounts,
|
|
95
|
+
typeCount
|
|
96
|
+
};
|
|
97
|
+
let parameterCount;
|
|
98
|
+
try {
|
|
99
|
+
const result = readUnsignedLEB128(bytes, offset);
|
|
100
|
+
parameterCount = result.value;
|
|
101
|
+
offset = result.nextOffset;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
note: `failed to read parameter count for type ${typeIndex}: ${describeError(error)}`,
|
|
105
|
+
overBrowserLimitTypes,
|
|
106
|
+
topParameterCounts,
|
|
107
|
+
typeCount
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (parameterCount > maxTypeParameterCount) {
|
|
111
|
+
maxTypeParameterCount = parameterCount;
|
|
112
|
+
maxTypeParameterTypeIndex = typeIndex;
|
|
113
|
+
}
|
|
114
|
+
if (parameterCount > browserMaxTypeParameterCount) overBrowserLimitTypes.push(typeIndex);
|
|
115
|
+
updateLargestTypeSummaries(topParameterCounts, typeIndex, parameterCount);
|
|
116
|
+
offset += parameterCount;
|
|
117
|
+
if (offset > endOffset) return {
|
|
118
|
+
note: `parameter vector for type ${typeIndex} overruns type section`,
|
|
119
|
+
overBrowserLimitTypes,
|
|
120
|
+
topParameterCounts,
|
|
121
|
+
typeCount
|
|
122
|
+
};
|
|
123
|
+
let resultCount;
|
|
124
|
+
try {
|
|
125
|
+
const result = readUnsignedLEB128(bytes, offset);
|
|
126
|
+
resultCount = result.value;
|
|
127
|
+
offset = result.nextOffset;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
note: `failed to read result count for type ${typeIndex}: ${describeError(error)}`,
|
|
131
|
+
overBrowserLimitTypes,
|
|
132
|
+
topParameterCounts,
|
|
133
|
+
typeCount
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
offset += resultCount;
|
|
137
|
+
if (offset > endOffset) return {
|
|
138
|
+
note: `result vector for type ${typeIndex} overruns type section`,
|
|
139
|
+
overBrowserLimitTypes,
|
|
140
|
+
topParameterCounts,
|
|
141
|
+
typeCount
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
maxTypeParameterCount: maxTypeParameterCount >= 0 ? maxTypeParameterCount : void 0,
|
|
146
|
+
maxTypeParameterTypeIndex: maxTypeParameterTypeIndex >= 0 ? maxTypeParameterTypeIndex : void 0,
|
|
147
|
+
overBrowserLimitTypes,
|
|
148
|
+
topParameterCounts,
|
|
149
|
+
typeCount
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function updateLargestTypeSummaries(summaries, typeIndex, parameterCount) {
|
|
153
|
+
summaries.push({
|
|
154
|
+
parameterCount,
|
|
155
|
+
typeIndex
|
|
156
|
+
});
|
|
157
|
+
summaries.sort((left, right) => {
|
|
158
|
+
if (right.parameterCount === left.parameterCount) return left.typeIndex - right.typeIndex;
|
|
159
|
+
return right.parameterCount - left.parameterCount;
|
|
160
|
+
});
|
|
161
|
+
summaries.splice(5);
|
|
162
|
+
}
|
|
163
|
+
function readSectionHeader(bytes, offset) {
|
|
164
|
+
if (offset >= bytes.length) throw new Error("unexpected end of file");
|
|
165
|
+
const id = bytes[offset];
|
|
166
|
+
const size = readUnsignedLEB128(bytes, offset + 1);
|
|
167
|
+
const startOffset = size.nextOffset;
|
|
168
|
+
const nextOffset = startOffset + size.value;
|
|
169
|
+
if (nextOffset > bytes.length) throw new Error(`section ${id} overruns file bounds`);
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
size: size.value,
|
|
173
|
+
startOffset,
|
|
174
|
+
nextOffset
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function readUnsignedLEB128(bytes, offset) {
|
|
178
|
+
let shift = 0;
|
|
179
|
+
let value = 0;
|
|
180
|
+
let nextOffset = offset;
|
|
181
|
+
while (nextOffset < bytes.length) {
|
|
182
|
+
const byte = bytes[nextOffset];
|
|
183
|
+
nextOffset += 1;
|
|
184
|
+
value |= (byte & 127) << shift;
|
|
185
|
+
if ((byte & 128) === 0) return {
|
|
186
|
+
nextOffset,
|
|
187
|
+
value
|
|
188
|
+
};
|
|
189
|
+
shift += 7;
|
|
190
|
+
}
|
|
191
|
+
throw new Error("unterminated LEB128 value");
|
|
192
|
+
}
|
|
193
|
+
function hasExpectedPrefix(bytes, startOffset, expected) {
|
|
194
|
+
if (startOffset + expected.length > bytes.length) return false;
|
|
195
|
+
for (let index = 0; index < expected.length; index += 1) if (bytes[startOffset + index] !== expected[index]) return false;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function describeError(error) {
|
|
199
|
+
return error instanceof Error ? error.message : String(error);
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { formatWasmTypeDiagnostics };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=wasmTypeDiagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasmTypeDiagnostics.js","names":[],"sources":["../../../src/build/wasmTypeDiagnostics.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\nconst wasmMagic = [0x00, 0x61, 0x73, 0x6d] as const;\nconst wasmVersion = [0x01, 0x00, 0x00, 0x00] as const;\nconst typeSectionID = 1;\nconst functionTypeTag = 0x60;\nconst browserMaxTypeParameterCount = 1000;\n\ninterface SectionHeader {\n id: number;\n size: number;\n startOffset: number;\n nextOffset: number;\n}\n\ninterface ReadUnsignedLEB128Result {\n nextOffset: number;\n value: number;\n}\n\ninterface WasmTypeSummary {\n maxTypeParameterCount?: number;\n maxTypeParameterTypeIndex?: number;\n note?: string;\n overBrowserLimitTypes: number[];\n topParameterCounts: Array<{\n parameterCount: number;\n typeIndex: number;\n }>;\n typeCount?: number;\n}\n\nexport function formatWasmTypeDiagnostics(\n bytes: Uint8Array\n): string {\n const summary = summarizeWasmTypes(bytes);\n const components = [\n `size=${bytes.byteLength} bytes`,\n `sha256=${createHash(\"sha256\").update(bytes).digest(\"hex\")}`,\n ];\n\n if (summary.typeCount !== undefined) {\n components.push(`typeCount=${summary.typeCount}`);\n }\n if (summary.maxTypeParameterCount !== undefined) {\n components.push(`maxTypeParameterCount=${summary.maxTypeParameterCount}`);\n }\n if (summary.maxTypeParameterTypeIndex !== undefined) {\n components.push(`maxTypeParameterTypeIndex=${summary.maxTypeParameterTypeIndex}`);\n }\n if (summary.overBrowserLimitTypes.length > 0) {\n components.push(`overBrowserLimitTypes=${summary.overBrowserLimitTypes.join(\",\")}`);\n }\n if (summary.topParameterCounts.length > 0) {\n components.push(\n `largestTypes=${summary.topParameterCounts.map(formatLargestTypeSummary).join(\";\")}`\n );\n }\n if (summary.note) {\n components.push(`note=${summary.note}`);\n }\n\n return `wasm diagnostics: ${components.join(\", \")}`;\n}\n\nfunction formatLargestTypeSummary(\n summary: { parameterCount: number; typeIndex: number }\n): string {\n return `${summary.typeIndex}:${summary.parameterCount}`;\n}\n\nfunction summarizeWasmTypes(\n bytes: Uint8Array\n): WasmTypeSummary {\n if (!hasExpectedPrefix(bytes, 0, wasmMagic)) {\n return {\n note: \"missing wasm magic header\",\n overBrowserLimitTypes: [],\n topParameterCounts: [],\n };\n }\n if (!hasExpectedPrefix(bytes, wasmMagic.length, wasmVersion)) {\n return {\n note: \"missing wasm version header\",\n overBrowserLimitTypes: [],\n topParameterCounts: [],\n };\n }\n\n let offset = 8;\n while (offset < bytes.length) {\n let sectionHeader: SectionHeader;\n try {\n sectionHeader = readSectionHeader(bytes, offset);\n } catch (error) {\n return {\n note: `failed to read section header at byte ${offset}: ${describeError(error)}`,\n overBrowserLimitTypes: [],\n topParameterCounts: [],\n };\n }\n\n if (sectionHeader.id === typeSectionID) {\n return summarizeTypeSection(bytes, sectionHeader.startOffset, sectionHeader.nextOffset);\n }\n\n offset = sectionHeader.nextOffset;\n }\n\n return {\n note: \"module has no type section\",\n overBrowserLimitTypes: [],\n topParameterCounts: [],\n };\n}\n\nfunction summarizeTypeSection(\n bytes: Uint8Array,\n startOffset: number,\n endOffset: number\n): WasmTypeSummary {\n let offset = startOffset;\n let typeCount: number;\n\n try {\n const result = readUnsignedLEB128(bytes, offset);\n typeCount = result.value;\n offset = result.nextOffset;\n } catch (error) {\n return {\n note: `failed to read type vector length: ${describeError(error)}`,\n overBrowserLimitTypes: [],\n topParameterCounts: [],\n };\n }\n\n let maxTypeParameterCount = -1;\n let maxTypeParameterTypeIndex = -1;\n const overBrowserLimitTypes: number[] = [];\n const topParameterCounts: Array<{ parameterCount: number; typeIndex: number }> = [];\n\n for (let typeIndex = 0; typeIndex < typeCount; typeIndex += 1) {\n if (offset >= endOffset) {\n return {\n note: `type section ended early while reading type ${typeIndex}`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n\n const typeTag = bytes[offset];\n offset += 1;\n if (typeTag !== functionTypeTag) {\n return {\n note: `unexpected type tag 0x${typeTag.toString(16)} at type ${typeIndex}`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n\n let parameterCount: number;\n try {\n const result = readUnsignedLEB128(bytes, offset);\n parameterCount = result.value;\n offset = result.nextOffset;\n } catch (error) {\n return {\n note: `failed to read parameter count for type ${typeIndex}: ${describeError(error)}`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n\n if (parameterCount > maxTypeParameterCount) {\n maxTypeParameterCount = parameterCount;\n maxTypeParameterTypeIndex = typeIndex;\n }\n if (parameterCount > browserMaxTypeParameterCount) {\n overBrowserLimitTypes.push(typeIndex);\n }\n updateLargestTypeSummaries(topParameterCounts, typeIndex, parameterCount);\n\n offset += parameterCount;\n if (offset > endOffset) {\n return {\n note: `parameter vector for type ${typeIndex} overruns type section`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n\n let resultCount: number;\n try {\n const result = readUnsignedLEB128(bytes, offset);\n resultCount = result.value;\n offset = result.nextOffset;\n } catch (error) {\n return {\n note: `failed to read result count for type ${typeIndex}: ${describeError(error)}`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n\n offset += resultCount;\n if (offset > endOffset) {\n return {\n note: `result vector for type ${typeIndex} overruns type section`,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n }\n }\n\n return {\n maxTypeParameterCount: maxTypeParameterCount >= 0 ? maxTypeParameterCount : undefined,\n maxTypeParameterTypeIndex: maxTypeParameterTypeIndex >= 0 ? maxTypeParameterTypeIndex : undefined,\n overBrowserLimitTypes,\n topParameterCounts,\n typeCount,\n };\n}\n\nfunction updateLargestTypeSummaries(\n summaries: Array<{ parameterCount: number; typeIndex: number }>,\n typeIndex: number,\n parameterCount: number\n): void {\n summaries.push({ parameterCount, typeIndex });\n summaries.sort((left, right) => {\n if (right.parameterCount === left.parameterCount) {\n return left.typeIndex - right.typeIndex;\n }\n return right.parameterCount - left.parameterCount;\n });\n summaries.splice(5);\n}\n\nfunction readSectionHeader(\n bytes: Uint8Array,\n offset: number\n): SectionHeader {\n if (offset >= bytes.length) {\n throw new Error(\"unexpected end of file\");\n }\n\n const id = bytes[offset];\n const size = readUnsignedLEB128(bytes, offset + 1);\n const startOffset = size.nextOffset;\n const nextOffset = startOffset + size.value;\n if (nextOffset > bytes.length) {\n throw new Error(`section ${id} overruns file bounds`);\n }\n\n return {\n id,\n size: size.value,\n startOffset,\n nextOffset,\n };\n}\n\nfunction readUnsignedLEB128(\n bytes: Uint8Array,\n offset: number\n): ReadUnsignedLEB128Result {\n let shift = 0;\n let value = 0;\n let nextOffset = offset;\n\n while (nextOffset < bytes.length) {\n const byte = bytes[nextOffset];\n nextOffset += 1;\n value |= (byte & 0x7f) << shift;\n if ((byte & 0x80) === 0) {\n return {\n nextOffset,\n value,\n };\n }\n shift += 7;\n }\n\n throw new Error(\"unterminated LEB128 value\");\n}\n\nfunction hasExpectedPrefix(\n bytes: Uint8Array,\n startOffset: number,\n expected: readonly number[]\n): boolean {\n if (startOffset + expected.length > bytes.length) {\n return false;\n }\n\n for (let index = 0; index < expected.length; index += 1) {\n if (bytes[startOffset + index] !== expected[index]) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;AAEA,MAAM,YAAY;CAAC;CAAM;CAAM;CAAM;AAAI;AACzC,MAAM,cAAc;CAAC;CAAM;CAAM;CAAM;AAAI;AAC3C,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,+BAA+B;AA0BrC,SAAgB,0BACd,OACQ;CACR,MAAM,UAAU,mBAAmB,KAAK;CACxC,MAAM,aAAa,CACjB,QAAQ,MAAM,WAAW,SACzB,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,GAC3D;CAEA,IAAI,QAAQ,cAAc,KAAA,GACxB,WAAW,KAAK,aAAa,QAAQ,WAAW;CAElD,IAAI,QAAQ,0BAA0B,KAAA,GACpC,WAAW,KAAK,yBAAyB,QAAQ,uBAAuB;CAE1E,IAAI,QAAQ,8BAA8B,KAAA,GACxC,WAAW,KAAK,6BAA6B,QAAQ,2BAA2B;CAElF,IAAI,QAAQ,sBAAsB,SAAS,GACzC,WAAW,KAAK,yBAAyB,QAAQ,sBAAsB,KAAK,GAAG,GAAG;CAEpF,IAAI,QAAQ,mBAAmB,SAAS,GACtC,WAAW,KACT,gBAAgB,QAAQ,mBAAmB,IAAI,wBAAwB,CAAC,CAAC,KAAK,GAAG,GACnF;CAEF,IAAI,QAAQ,MACV,WAAW,KAAK,QAAQ,QAAQ,MAAM;CAGxC,OAAO,qBAAqB,WAAW,KAAK,IAAI;AAClD;AAEA,SAAS,yBACP,SACQ;CACR,OAAO,GAAG,QAAQ,UAAU,GAAG,QAAQ;AACzC;AAEA,SAAS,mBACP,OACiB;CACjB,IAAI,CAAC,kBAAkB,OAAO,GAAG,SAAS,GACxC,OAAO;EACL,MAAM;EACN,uBAAuB,CAAC;EACxB,oBAAoB,CAAC;CACvB;CAEF,IAAI,CAAC,kBAAkB,OAAO,UAAU,QAAQ,WAAW,GACzD,OAAO;EACL,MAAM;EACN,uBAAuB,CAAC;EACxB,oBAAoB,CAAC;CACvB;CAGF,IAAI,SAAS;CACb,OAAO,SAAS,MAAM,QAAQ;EAC5B,IAAI;EACJ,IAAI;GACF,gBAAgB,kBAAkB,OAAO,MAAM;EACjD,SAAS,OAAO;GACd,OAAO;IACL,MAAM,yCAAyC,OAAO,IAAI,cAAc,KAAK;IAC7E,uBAAuB,CAAC;IACxB,oBAAoB,CAAC;GACvB;EACF;EAEA,IAAI,cAAc,OAAO,eACvB,OAAO,qBAAqB,OAAO,cAAc,aAAa,cAAc,UAAU;EAGxF,SAAS,cAAc;CACzB;CAEA,OAAO;EACL,MAAM;EACN,uBAAuB,CAAC;EACxB,oBAAoB,CAAC;CACvB;AACF;AAEA,SAAS,qBACP,OACA,aACA,WACiB;CACjB,IAAI,SAAS;CACb,IAAI;CAEJ,IAAI;EACF,MAAM,SAAS,mBAAmB,OAAO,MAAM;EAC/C,YAAY,OAAO;EACnB,SAAS,OAAO;CAClB,SAAS,OAAO;EACd,OAAO;GACL,MAAM,sCAAsC,cAAc,KAAK;GAC/D,uBAAuB,CAAC;GACxB,oBAAoB,CAAC;EACvB;CACF;CAEA,IAAI,wBAAwB;CAC5B,IAAI,4BAA4B;CAChC,MAAM,wBAAkC,CAAC;CACzC,MAAM,qBAA2E,CAAC;CAElF,KAAK,IAAI,YAAY,GAAG,YAAY,WAAW,aAAa,GAAG;EAC7D,IAAI,UAAU,WACZ,OAAO;GACL,MAAM,+CAA+C;GACrD;GACA;GACA;EACF;EAGF,MAAM,UAAU,MAAM;EACtB,UAAU;EACV,IAAI,YAAY,iBACd,OAAO;GACL,MAAM,yBAAyB,QAAQ,SAAS,EAAE,EAAE,WAAW;GAC/D;GACA;GACA;EACF;EAGF,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,mBAAmB,OAAO,MAAM;GAC/C,iBAAiB,OAAO;GACxB,SAAS,OAAO;EAClB,SAAS,OAAO;GACd,OAAO;IACL,MAAM,2CAA2C,UAAU,IAAI,cAAc,KAAK;IAClF;IACA;IACA;GACF;EACF;EAEA,IAAI,iBAAiB,uBAAuB;GAC1C,wBAAwB;GACxB,4BAA4B;EAC9B;EACA,IAAI,iBAAiB,8BACnB,sBAAsB,KAAK,SAAS;EAEtC,2BAA2B,oBAAoB,WAAW,cAAc;EAExE,UAAU;EACV,IAAI,SAAS,WACX,OAAO;GACL,MAAM,6BAA6B,UAAU;GAC7C;GACA;GACA;EACF;EAGF,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,mBAAmB,OAAO,MAAM;GAC/C,cAAc,OAAO;GACrB,SAAS,OAAO;EAClB,SAAS,OAAO;GACd,OAAO;IACL,MAAM,wCAAwC,UAAU,IAAI,cAAc,KAAK;IAC/E;IACA;IACA;GACF;EACF;EAEA,UAAU;EACV,IAAI,SAAS,WACX,OAAO;GACL,MAAM,0BAA0B,UAAU;GAC1C;GACA;GACA;EACF;CAEJ;CAEA,OAAO;EACL,uBAAuB,yBAAyB,IAAI,wBAAwB,KAAA;EAC5E,2BAA2B,6BAA6B,IAAI,4BAA4B,KAAA;EACxF;EACA;EACA;CACF;AACF;AAEA,SAAS,2BACP,WACA,WACA,gBACM;CACN,UAAU,KAAK;EAAE;EAAgB;CAAU,CAAC;CAC5C,UAAU,MAAM,MAAM,UAAU;EAC9B,IAAI,MAAM,mBAAmB,KAAK,gBAChC,OAAO,KAAK,YAAY,MAAM;EAEhC,OAAO,MAAM,iBAAiB,KAAK;CACrC,CAAC;CACD,UAAU,OAAO,CAAC;AACpB;AAEA,SAAS,kBACP,OACA,QACe;CACf,IAAI,UAAU,MAAM,QAClB,MAAM,IAAI,MAAM,wBAAwB;CAG1C,MAAM,KAAK,MAAM;CACjB,MAAM,OAAO,mBAAmB,OAAO,SAAS,CAAC;CACjD,MAAM,cAAc,KAAK;CACzB,MAAM,aAAa,cAAc,KAAK;CACtC,IAAI,aAAa,MAAM,QACrB,MAAM,IAAI,MAAM,WAAW,GAAG,sBAAsB;CAGtD,OAAO;EACL;EACA,MAAM,KAAK;EACX;EACA;CACF;AACF;AAEA,SAAS,mBACP,OACA,QAC0B;CAC1B,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,IAAI,aAAa;CAEjB,OAAO,aAAa,MAAM,QAAQ;EAChC,MAAM,OAAO,MAAM;EACnB,cAAc;EACd,UAAU,OAAO,QAAS;EAC1B,KAAK,OAAO,SAAU,GACpB,OAAO;GACL;GACA;EACF;EAEF,SAAS;CACX;CAEA,MAAM,IAAI,MAAM,2BAA2B;AAC7C;AAEA,SAAS,kBACP,OACA,aACA,UACS;CACT,IAAI,cAAc,SAAS,SAAS,MAAM,QACxC,OAAO;CAGT,KAAK,IAAI,QAAQ,GAAG,QAAQ,SAAS,QAAQ,SAAS,GACpD,IAAI,MAAM,cAAc,WAAW,SAAS,QAC1C,OAAO;CAIX,OAAO;AACT;AAEA,SAAS,cAAc,OAAwB;CAC7C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swifttui/build",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
6
13
|
"bin": {
|
|
7
|
-
"swifttui-web": "./cli.
|
|
14
|
+
"swifttui-web": "./dist/cli.js"
|
|
8
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
9
19
|
"exports": {
|
|
10
|
-
".":
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
11
25
|
},
|
|
12
|
-
"type": "module",
|
|
13
26
|
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
14
28
|
"build:manifest": "bun run cli.ts build:manifest",
|
|
15
29
|
"build:wasm": "bun run cli.ts build:wasm",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"prepublishOnly": "tsdown"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
18
35
|
},
|
|
19
36
|
"dependencies": {
|
|
20
|
-
"@swifttui/web": "0.0.
|
|
37
|
+
"@swifttui/web": "0.0.15"
|
|
21
38
|
},
|
|
22
39
|
"devDependencies": {
|
|
23
40
|
"@types/bun": "1.3.13"
|
|
24
|
-
},
|
|
25
|
-
"peerDependencies": {
|
|
26
|
-
"typescript": "^5"
|
|
27
41
|
}
|
|
28
42
|
}
|
package/AGENTS.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
Guidance for agentic assistants working in **`@swifttui/build`**. Keep this
|
|
4
|
-
concise; [`README.md`](README.md) is the full reference.
|
|
5
|
-
|
|
6
|
-
## What this package is
|
|
7
|
-
|
|
8
|
-
The **build/packaging tooling** for SwiftTUI browser apps — the sibling of
|
|
9
|
-
[`@swifttui/web`](../web) (which owns the runtime). It captures the Swift app's
|
|
10
|
-
scene manifest and packages its WASI/wasm artifact for the browser. Exposes the
|
|
11
|
-
`swifttui-web` CLI (see `bin` in `package.json`) and a programmatic `index.ts`.
|
|
12
|
-
|
|
13
|
-
Keep the split clean: **packaging/build steps live here; browser-safe runtime
|
|
14
|
-
APIs live in `@swifttui/web`.** This package depends on `@swifttui/web`.
|
|
15
|
-
|
|
16
|
-
## Toolchains
|
|
17
|
-
|
|
18
|
-
- **Bun** for the CLI, bundling, and tests.
|
|
19
|
-
- **`swiftly`** Swift 6.3.1 for the wasm build it invokes
|
|
20
|
-
(`swiftly run swift ...`), not bare `swift`.
|
|
21
|
-
|
|
22
|
-
## Commands
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bun test # package tests
|
|
26
|
-
bun run build:manifest -- --app <Exe> # capture TUIGUI_MODE=manifest output
|
|
27
|
-
bun run build:wasm -- --app <Exe> # copy + validate the app's wasm
|
|
28
|
-
bun run build -- --app <Exe> # manifest + wasm
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
`build:wasm`/`build` default to `--configuration release`; pass
|
|
32
|
-
`--configuration debug` for local debug wasm.
|
|
33
|
-
|
|
34
|
-
## Gotcha
|
|
35
|
-
|
|
36
|
-
WASI release builds need specific flags (`-Osize` plus
|
|
37
|
-
`-disable-llvm-merge-functions-pass`) to stay under the browser WebAssembly
|
|
38
|
-
API's 1000-parameter limit. The canonical command lives in this package's build
|
|
39
|
-
code — don't hand-roll the swift invocation. See
|
|
40
|
-
[`WebExample`](../../../swift-tui-examples/WebExample) for the full rationale.
|
|
41
|
-
|
|
42
|
-
## Conventions
|
|
43
|
-
|
|
44
|
-
`AGENTS.md` is the real file; `CLAUDE.md` is a symlink to it. Edit `AGENTS.md`.
|
package/cli.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
buildAppWasm,
|
|
6
|
-
buildSwiftTUIWebApp,
|
|
7
|
-
generateSceneManifest,
|
|
8
|
-
type WasmBuildConfiguration,
|
|
9
|
-
} from "./index.ts";
|
|
10
|
-
|
|
11
|
-
void runCli(process.argv.slice(2));
|
|
12
|
-
|
|
13
|
-
async function runCli(argv: string[]): Promise<void> {
|
|
14
|
-
const command = argv[0] ?? "build";
|
|
15
|
-
const flags = parseFlags(argv.slice(1));
|
|
16
|
-
const packagePath = resolve(flags["package-path"] ?? "../../");
|
|
17
|
-
const distPath = resolve(flags["dist"] ?? "./dist");
|
|
18
|
-
const appExecutable = flags.app ?? flags.product ?? flags["app-product"] ?? "";
|
|
19
|
-
const configuration = parseWasmBuildConfiguration(flags.configuration ?? "release");
|
|
20
|
-
|
|
21
|
-
switch (command) {
|
|
22
|
-
case "build:manifest":
|
|
23
|
-
assertAppExecutable(appExecutable);
|
|
24
|
-
await generateSceneManifest({
|
|
25
|
-
packagePath,
|
|
26
|
-
outputPath: resolve(distPath, "scene-manifest.json"),
|
|
27
|
-
appExecutable,
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
case "build:wasm":
|
|
31
|
-
assertAppExecutable(appExecutable);
|
|
32
|
-
await buildAppWasm({
|
|
33
|
-
configuration,
|
|
34
|
-
packagePath,
|
|
35
|
-
outputDirectory: distPath,
|
|
36
|
-
product: appExecutable,
|
|
37
|
-
});
|
|
38
|
-
return;
|
|
39
|
-
case "build":
|
|
40
|
-
assertAppExecutable(appExecutable);
|
|
41
|
-
await buildSwiftTUIWebApp({
|
|
42
|
-
configuration,
|
|
43
|
-
packagePath,
|
|
44
|
-
outputDirectory: distPath,
|
|
45
|
-
product: appExecutable,
|
|
46
|
-
});
|
|
47
|
-
return;
|
|
48
|
-
default:
|
|
49
|
-
throw new Error(`unknown command: ${command}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function parseFlags(
|
|
54
|
-
argv: string[]
|
|
55
|
-
): Record<string, string> {
|
|
56
|
-
const flags: Record<string, string> = {};
|
|
57
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
58
|
-
const value = argv[index];
|
|
59
|
-
if (!value.startsWith("--")) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const equalsIndex = value.indexOf("=");
|
|
63
|
-
if (equalsIndex !== -1) {
|
|
64
|
-
flags[value.slice(2, equalsIndex)] = value.slice(equalsIndex + 1);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const name = value.slice(2);
|
|
68
|
-
const next = argv[index + 1];
|
|
69
|
-
if (next && !next.startsWith("--")) {
|
|
70
|
-
flags[name] = next;
|
|
71
|
-
index += 1;
|
|
72
|
-
} else {
|
|
73
|
-
flags[name] = "true";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return flags;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function parseWasmBuildConfiguration(
|
|
80
|
-
value: string
|
|
81
|
-
): WasmBuildConfiguration {
|
|
82
|
-
switch (value) {
|
|
83
|
-
case "debug":
|
|
84
|
-
return "debug";
|
|
85
|
-
case "release":
|
|
86
|
-
return "release";
|
|
87
|
-
default:
|
|
88
|
-
throw new Error(`unsupported wasm build configuration: ${value}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function assertAppExecutable(
|
|
93
|
-
value: string
|
|
94
|
-
): asserts value is string {
|
|
95
|
-
if (!value) {
|
|
96
|
-
throw new Error("missing --app or --product flag");
|
|
97
|
-
}
|
|
98
|
-
}
|
package/index.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { afterEach, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { packageBrowserValidatedWasm } from "./buildAppWasm.ts";
|
|
6
|
-
|
|
7
|
-
const minimalWasmBytes = new Uint8Array([
|
|
8
|
-
0x00, 0x61, 0x73, 0x6d,
|
|
9
|
-
0x01, 0x00, 0x00, 0x00,
|
|
10
|
-
]);
|
|
11
|
-
|
|
12
|
-
const temporaryDirectories: string[] = [];
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
for (const directory of temporaryDirectories.splice(0)) {
|
|
16
|
-
await rm(directory, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("falls back to the original wasm when strip tooling throws", async () => {
|
|
21
|
-
const fixture = await createFixture();
|
|
22
|
-
const warnings: string[] = [];
|
|
23
|
-
|
|
24
|
-
await packageBrowserValidatedWasm({
|
|
25
|
-
optimize: async () => {},
|
|
26
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
27
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
28
|
-
strip: async () => {
|
|
29
|
-
throw new Error("missing llvm-objcopy");
|
|
30
|
-
},
|
|
31
|
-
onWarning: (warning) => warnings.push(warning),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
expect(await Bun.file(fixture.outputWasmPath).bytes())
|
|
35
|
-
.toEqual(minimalWasmBytes);
|
|
36
|
-
expect(warnings).toHaveLength(1);
|
|
37
|
-
expect(warnings[0]).toContain("keeping unstripped wasm");
|
|
38
|
-
expect(warnings[0]).toContain("missing llvm-objcopy");
|
|
39
|
-
await WebAssembly.compile(await Bun.file(fixture.outputWasmPath).arrayBuffer());
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("falls back to the original wasm when stripping corrupts the artifact", async () => {
|
|
43
|
-
const fixture = await createFixture();
|
|
44
|
-
const warnings: string[] = [];
|
|
45
|
-
|
|
46
|
-
await packageBrowserValidatedWasm({
|
|
47
|
-
optimize: async () => {},
|
|
48
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
49
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
50
|
-
strip: async (wasmPath) => {
|
|
51
|
-
await Bun.write(wasmPath, new Uint8Array([0x00, 0x61, 0x73, 0x6d]));
|
|
52
|
-
},
|
|
53
|
-
onWarning: (warning) => warnings.push(warning),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
expect(await Bun.file(fixture.outputWasmPath).bytes())
|
|
57
|
-
.toEqual(minimalWasmBytes);
|
|
58
|
-
expect(warnings).toHaveLength(1);
|
|
59
|
-
expect(warnings[0])
|
|
60
|
-
.toContain("stripped wasm does not parse in browser WebAssembly");
|
|
61
|
-
await WebAssembly.compile(await Bun.file(fixture.outputWasmPath).arrayBuffer());
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("fails when the source wasm itself is not browser-parseable", async () => {
|
|
65
|
-
const fixture = await createFixture(buildHugeFunctionTypeWasm(1001));
|
|
66
|
-
|
|
67
|
-
await expect(
|
|
68
|
-
packageBrowserValidatedWasm({
|
|
69
|
-
optimize: async () => {},
|
|
70
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
71
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
72
|
-
strip: async () => {},
|
|
73
|
-
})
|
|
74
|
-
).rejects.toThrow("generated wasm does not parse in browser WebAssembly");
|
|
75
|
-
await expect(
|
|
76
|
-
packageBrowserValidatedWasm({
|
|
77
|
-
optimize: async () => {},
|
|
78
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
79
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
80
|
-
strip: async () => {},
|
|
81
|
-
})
|
|
82
|
-
).rejects.toThrow("maxTypeParameterCount=1001");
|
|
83
|
-
await expect(
|
|
84
|
-
packageBrowserValidatedWasm({
|
|
85
|
-
optimize: async () => {},
|
|
86
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
87
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
88
|
-
strip: async () => {},
|
|
89
|
-
})
|
|
90
|
-
).rejects.toThrow("overBrowserLimitTypes=0");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("uses optimized wasm when the raw compiler output is not browser-parseable", async () => {
|
|
94
|
-
const fixture = await createFixture(buildHugeFunctionTypeWasm(1001));
|
|
95
|
-
|
|
96
|
-
await packageBrowserValidatedWasm({
|
|
97
|
-
optimize: async (wasmPath) => {
|
|
98
|
-
await Bun.write(wasmPath, minimalWasmBytes);
|
|
99
|
-
},
|
|
100
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
101
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
102
|
-
strip: async () => {},
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
expect(await Bun.file(fixture.outputWasmPath).bytes())
|
|
106
|
-
.toEqual(minimalWasmBytes);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("reports the optimization failure when the raw wasm is still invalid", async () => {
|
|
110
|
-
const fixture = await createFixture(buildHugeFunctionTypeWasm(1001));
|
|
111
|
-
|
|
112
|
-
await expect(
|
|
113
|
-
packageBrowserValidatedWasm({
|
|
114
|
-
optimize: async () => {
|
|
115
|
-
throw new Error("missing wasm-opt");
|
|
116
|
-
},
|
|
117
|
-
sourceWasmPath: fixture.sourceWasmPath,
|
|
118
|
-
outputWasmPath: fixture.outputWasmPath,
|
|
119
|
-
strip: async () => {},
|
|
120
|
-
})
|
|
121
|
-
).rejects.toThrow("wasm optimization step failed: missing wasm-opt");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
async function createFixture(
|
|
125
|
-
sourceBytes: Uint8Array = minimalWasmBytes
|
|
126
|
-
): Promise<{ sourceWasmPath: string; outputWasmPath: string }> {
|
|
127
|
-
const directory = await mkdtemp(join(tmpdir(), "webhost-wasm-"));
|
|
128
|
-
temporaryDirectories.push(directory);
|
|
129
|
-
|
|
130
|
-
const sourceWasmPath = join(directory, "source.wasm");
|
|
131
|
-
const outputWasmPath = join(directory, "output.wasm");
|
|
132
|
-
await Bun.write(sourceWasmPath, sourceBytes);
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
sourceWasmPath,
|
|
136
|
-
outputWasmPath,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function buildHugeFunctionTypeWasm(
|
|
141
|
-
parameterCount: number
|
|
142
|
-
): Uint8Array {
|
|
143
|
-
const payload = [
|
|
144
|
-
...encodeUnsignedLEB128(1),
|
|
145
|
-
0x60,
|
|
146
|
-
...encodeUnsignedLEB128(parameterCount),
|
|
147
|
-
...new Array<number>(parameterCount).fill(0x7f),
|
|
148
|
-
0x00,
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
return new Uint8Array([
|
|
152
|
-
...minimalWasmBytes,
|
|
153
|
-
0x01,
|
|
154
|
-
...encodeUnsignedLEB128(payload.length),
|
|
155
|
-
...payload,
|
|
156
|
-
]);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function encodeUnsignedLEB128(
|
|
160
|
-
value: number
|
|
161
|
-
): number[] {
|
|
162
|
-
if (value < 0) {
|
|
163
|
-
throw new Error("LEB128 values must be non-negative");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const bytes: number[] = [];
|
|
167
|
-
let remaining = value;
|
|
168
|
-
do {
|
|
169
|
-
let byte = remaining & 0x7f;
|
|
170
|
-
remaining >>>= 7;
|
|
171
|
-
if (remaining !== 0) {
|
|
172
|
-
byte |= 0x80;
|
|
173
|
-
}
|
|
174
|
-
bytes.push(byte);
|
|
175
|
-
} while (remaining !== 0);
|
|
176
|
-
|
|
177
|
-
return bytes;
|
|
178
|
-
}
|