@pulse-compute/provider-fastly 0.0.0 → 1.0.0-beta.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/README.md +52 -1
- package/dist/config-schema.json +73 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -5
- package/src/build/canonical-target.js +436 -0
- package/src/build/entities-native.js +564 -0
- package/src/build/fixed-memory-runtime.as.ts +148 -0
- package/src/build/native-http-effects.js +871 -0
- package/src/build/native-http-shell.js +703 -0
- package/src/build/native-platform-capabilities.js +2109 -0
- package/src/compiler/assets-package-out-parity-proof.js +338 -0
- package/src/compiler/assets-provider-proof.js +499 -0
- package/src/compiler/config-provider-resolution.js +573 -0
- package/src/compiler/fastly-adapter.js +564 -0
- package/src/compiler/fastly-command-entry.js +390 -0
- package/src/compiler/fastly-config-secret-serve.js +323 -0
- package/src/compiler/fastly-hostcall-binding.js +598 -0
- package/src/compiler/fastly-readiness.js +477 -0
- package/src/compiler/index.js +40 -0
- package/src/compiler/lifecycle-parity-contract.js +70 -0
- package/src/compiler/lifecycle-parity-proof.js +260 -0
- package/src/compiler/route-handler-effect-proof.js +255 -0
- package/src/compiler/runtime-provider-kv.js +237 -0
- package/src/compiler/runtime-provider-shape.js +109 -0
- package/src/config-api.js +63 -0
- package/src/config-schema.d.ts +13 -0
- package/src/config-schema.js +27 -0
- package/src/config-schema.json +73 -0
- package/src/index.js +3 -0
- package/src/index.ts +118 -0
- package/src/javascript/assets-adapter.js +122 -0
- package/src/javascript/bindings-adapter.js +237 -0
- package/src/javascript/errors.js +26 -0
- package/src/javascript/fetch-adapter.js +93 -0
- package/src/javascript/grip-broadcast.js +261 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +55 -0
- package/src/javascript/local-loader.js +138 -0
- package/src/javascript/logging.js +26 -0
- package/src/javascript/package-effects.js +68 -0
- package/src/javascript/runtime-host.js +108 -0
- package/src/javascript/sha256.js +128 -0
- package/src/javascript/source-package.js +776 -0
- package/src/javascript/support.js +142 -0
- package/src/javascript/target-support-policy.js +260 -0
- package/src/javascript/target.js +73 -0
- package/src/javascript/test-runtime.js +407 -0
- package/src/provider-contract.js +72 -0
- package/src/runtime/assets-provider.js +464 -0
- package/src/runtime/canonical-api-runtime.js +597 -0
- package/src/runtime/index.js +28 -0
- package/src/runtime/provider-primitives-contract.js +272 -0
- package/src/runtime/provider-primitives.js +82 -0
- package/src/runtime/route-handler-effects.js +507 -0
- package/src/testing/fastly-cli.js +538 -0
- package/src/testing/native-http-effects-host.js +390 -0
- package/src/testing/native-http-shell-host.js +190 -0
- package/src/testing/native-platform-capabilities-host.js +599 -0
- package/src/toolchain/config.js +128 -0
- package/src/toolchain/index.js +343 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
|
|
10
|
+
const { resolveAsc } = require('@pulse-compute/wasm-build-support/assemblyscript-compile');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function loadContractsDiagnostics() {
|
|
14
|
+
try {
|
|
15
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
18
|
+
return require('../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PHASE = '13C.1';
|
|
25
|
+
const FASTLY_COMMAND_ENTRY_VERSION = 'pulsewasm.fastly-command-entry.v1';
|
|
26
|
+
const FASTLY_START_EXPORT_AUDIT_VERSION = 'pulsewasm.fastly-start-export-audit.v1';
|
|
27
|
+
const FASTLY_COMMAND_IMPORT_AUDIT_VERSION = 'pulsewasm.fastly-command-import-audit.v1';
|
|
28
|
+
const FASTLY_SERVE_GATE_VERSION = 'pulsewasm.fastly-serve-gate.v1';
|
|
29
|
+
const FASTLY_COMMAND_SMOKE_VERSION = 'pulsewasm.fastly-command-smoke.v1';
|
|
30
|
+
|
|
31
|
+
function sha256Text(text) { return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex'); }
|
|
32
|
+
function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
|
|
33
|
+
function makeFile(file, text) { return { file, text, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) }; }
|
|
34
|
+
function normalizeSlash(value) { return String(value || '').replace(/\\/g, '/'); }
|
|
35
|
+
function truncate(value, max = 12000) { const text = String(value || ''); return text.length > max ? `${text.slice(0, max)}\n…<truncated ${text.length - max} chars>` : text; }
|
|
36
|
+
function diag(code, message, hint, details) {
|
|
37
|
+
return normalizeDiagnostic({ phase: 'fastly-command-entry', severity: 'error', code, message, hint, details, loc: { file: '<fastly-command-entry>' } });
|
|
38
|
+
}
|
|
39
|
+
function fileRecord(filePath, cwd, kind) {
|
|
40
|
+
const buffer = fs.readFileSync(filePath);
|
|
41
|
+
return { file: normalizeSlash(path.relative(cwd, filePath)), kind, bytes: buffer.length, sha256: sha256Buffer(buffer) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildFastlyCommandEntrySource() {
|
|
45
|
+
return `/* Generated by PulseWasm.
|
|
46
|
+
* Fastly command-style entrypoint proof.
|
|
47
|
+
* This module is Fastly-target-specific. It deliberately exports _start.
|
|
48
|
+
* It is not the generic PulseWasm runtime and it is not an SDK dependency.
|
|
49
|
+
*/
|
|
50
|
+
type FastlyStatus = i32;
|
|
51
|
+
type BodyHandle = u32;
|
|
52
|
+
type BodyWriteEnd = u32;
|
|
53
|
+
type ResponseHandle = u32;
|
|
54
|
+
type ptr = usize;
|
|
55
|
+
type strptr = ArrayBuffer;
|
|
56
|
+
|
|
57
|
+
@external("fastly_http_body", "new")
|
|
58
|
+
declare function fastly_http_body_new(handle_out: ptr): FastlyStatus;
|
|
59
|
+
|
|
60
|
+
@external("fastly_http_body", "write")
|
|
61
|
+
declare function fastly_http_body_write(
|
|
62
|
+
body_handle: BodyHandle,
|
|
63
|
+
buf: strptr,
|
|
64
|
+
buf_len: usize,
|
|
65
|
+
end: BodyWriteEnd,
|
|
66
|
+
nwritten_out: ptr
|
|
67
|
+
): FastlyStatus;
|
|
68
|
+
|
|
69
|
+
@external("fastly_http_resp", "new")
|
|
70
|
+
declare function fastly_http_resp_new(resp_handle_out: ptr): FastlyStatus;
|
|
71
|
+
|
|
72
|
+
@external("fastly_http_resp", "status_set")
|
|
73
|
+
declare function fastly_http_resp_status_set(resp_handle: ResponseHandle, status: u16): FastlyStatus;
|
|
74
|
+
|
|
75
|
+
@external("fastly_http_resp", "header_insert")
|
|
76
|
+
declare function fastly_http_resp_header_insert(
|
|
77
|
+
resp_handle: ResponseHandle,
|
|
78
|
+
name: strptr,
|
|
79
|
+
name_len: usize,
|
|
80
|
+
value: strptr,
|
|
81
|
+
value_len: usize
|
|
82
|
+
): FastlyStatus;
|
|
83
|
+
|
|
84
|
+
@external("fastly_http_resp", "send_downstream")
|
|
85
|
+
declare function fastly_http_resp_send_downstream(
|
|
86
|
+
resp_handle: ResponseHandle,
|
|
87
|
+
body_handle: BodyHandle,
|
|
88
|
+
streaming: u32
|
|
89
|
+
): FastlyStatus;
|
|
90
|
+
|
|
91
|
+
function check(status: FastlyStatus): void {
|
|
92
|
+
if (status != 0) unreachable();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function outPtr(): usize {
|
|
96
|
+
const ptr = __alloc(4);
|
|
97
|
+
store<u32>(ptr, 0);
|
|
98
|
+
return ptr;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeHeader(resp: ResponseHandle, name: string, value: string): void {
|
|
102
|
+
const encodedName = String.UTF8.encode(name, false);
|
|
103
|
+
const encodedValue = String.UTF8.encode(value, false);
|
|
104
|
+
check(fastly_http_resp_header_insert(resp, encodedName, encodedName.byteLength, encodedValue, encodedValue.byteLength));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function writeBody(body: BodyHandle, text: string): void {
|
|
108
|
+
const bytes = String.UTF8.encode(text, false);
|
|
109
|
+
const writtenOut = outPtr();
|
|
110
|
+
check(fastly_http_body_write(body, bytes, bytes.byteLength, 0, writtenOut));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function __pulsewasm_abort(message: string | null, fileName: string | null, line: u32, column: u32): void {
|
|
114
|
+
unreachable();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function _start(): void {
|
|
118
|
+
const responseOut = outPtr();
|
|
119
|
+
check(fastly_http_resp_new(responseOut));
|
|
120
|
+
const response = load<ResponseHandle>(responseOut);
|
|
121
|
+
|
|
122
|
+
const bodyOut = outPtr();
|
|
123
|
+
check(fastly_http_body_new(bodyOut));
|
|
124
|
+
const body = load<BodyHandle>(bodyOut);
|
|
125
|
+
|
|
126
|
+
check(fastly_http_resp_status_set(response, 200));
|
|
127
|
+
writeHeader(response, "Content-Type", "text/plain; charset=utf-8");
|
|
128
|
+
writeHeader(response, "X-PulseWasm-Fastly", "13C.1");
|
|
129
|
+
writeBody(body, "pulsewasm fastly command entrypoint ok\\n");
|
|
130
|
+
check(fastly_http_resp_send_downstream(response, body, 0));
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildFastlyToml() {
|
|
136
|
+
return `manifest_version = 3
|
|
137
|
+
name = "pulsewasm-fastly-command-proof"
|
|
138
|
+
description = "PulseWasm Fastly command entrypoint proof."
|
|
139
|
+
language = "other"
|
|
140
|
+
|
|
141
|
+
[scripts]
|
|
142
|
+
build = "echo 'PulseWasm custom build: use fastly compute pack --wasm-binary artifacts/generated/fastly/pulsewasm-compute.wasm'"
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildReadme() {
|
|
147
|
+
return `# PulseWasm Fastly Command Entrypoint
|
|
148
|
+
|
|
149
|
+
This scaffold fixes the Fastly target shape discovered by \`fastly compute serve\`:
|
|
150
|
+
|
|
151
|
+
\`\`\`text
|
|
152
|
+
failed to find function export '_start'
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
The generated Wasm is a command-style Fastly Compute candidate with an exported \`_start\` and a minimal downstream response path using Fastly hostcall imports.
|
|
156
|
+
|
|
157
|
+
## Candidate commands
|
|
158
|
+
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
fastly compute pack --wasm-binary artifacts/generated/fastly/pulsewasm-compute.wasm
|
|
161
|
+
fastly compute serve
|
|
162
|
+
curl -i http://127.0.0.1:7676/
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
## Boundary
|
|
166
|
+
|
|
167
|
+
This phase proves command target shape. It does not yet wire the full PulseWasm compiled runtime into Fastly hostcalls.
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function compileCommandEntry({ cwd, outDir, sourceText }) {
|
|
172
|
+
const diagnostics = [];
|
|
173
|
+
const asc = resolveAsc(cwd);
|
|
174
|
+
if (!asc) {
|
|
175
|
+
diagnostics.push(diag('PULSEWASM_FASTLY_COMMAND_ASC_MISSING', 'AssemblyScript compiler dependency was not found.', 'Run npm install before generating the Fastly command entrypoint.'));
|
|
176
|
+
return { diagnostics, warnings: [], outputFiles: [] };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulsewasm-fastly-command-'));
|
|
180
|
+
const srcDir = path.join(stagingDir, 'generated', 'fastly');
|
|
181
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
182
|
+
const entry = path.join(srcDir, 'fastly-entry.as.ts');
|
|
183
|
+
fs.writeFileSync(entry, sourceText, 'utf8');
|
|
184
|
+
|
|
185
|
+
const outputDir = path.join(outDir || getDefaultArtifactsDir(cwd), 'generated', 'fastly-command');
|
|
186
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
187
|
+
const wasmFile = path.join(outputDir, 'pulsewasm-fastly-command.wasm');
|
|
188
|
+
const ascArgs = [
|
|
189
|
+
asc.script,
|
|
190
|
+
'generated/fastly/fastly-entry.as.ts',
|
|
191
|
+
'--outFile', wasmFile,
|
|
192
|
+
'--runtime', 'stub',
|
|
193
|
+
'--noAssert',
|
|
194
|
+
'--optimize',
|
|
195
|
+
'--use', 'abort=generated/fastly/fastly-entry.as/__pulsewasm_abort'
|
|
196
|
+
];
|
|
197
|
+
const proc = spawnSync(asc.executable, ascArgs, { cwd: stagingDir, encoding: 'utf8', maxBuffer: 1024 * 1024 * 16 });
|
|
198
|
+
if (proc.error || proc.status !== 0) {
|
|
199
|
+
diagnostics.push(diag('PULSEWASM_FASTLY_COMMAND_AS_COMPILE_FAILED', `Fastly command entry AssemblyScript compile failed${proc.status !== undefined ? ` with exit code ${proc.status}` : ''}.`, 'Inspect fastly-command-smoke.json stderr/stdout.', { stdout: truncate(proc.stdout), stderr: truncate(proc.stderr), exitCode: proc.status }));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const outputFiles = [];
|
|
203
|
+
if (fs.existsSync(wasmFile)) outputFiles.push(fileRecord(wasmFile, cwd, 'fastly-command-wasm'));
|
|
204
|
+
return {
|
|
205
|
+
diagnostics,
|
|
206
|
+
warnings: [],
|
|
207
|
+
wasmFile,
|
|
208
|
+
outputFiles,
|
|
209
|
+
command: { executable: asc.display, args: ascArgs.slice(1).map((arg) => path.isAbsolute(arg) ? normalizeSlash(path.relative(cwd, arg)) : arg) },
|
|
210
|
+
result: { exitCode: proc.status, stdout: truncate(proc.stdout), stderr: truncate(proc.stderr) }
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function wasmAudit(wasmFile) {
|
|
215
|
+
if (!wasmFile || !fs.existsSync(wasmFile)) return { imports: [], exports: [], hasStart: false, unexpectedImports: [] };
|
|
216
|
+
const mod = new WebAssembly.Module(fs.readFileSync(wasmFile));
|
|
217
|
+
const imports = WebAssembly.Module.imports(mod).map((item) => ({ module: item.module, name: item.name, kind: item.kind }));
|
|
218
|
+
const exports = WebAssembly.Module.exports(mod).map((item) => ({ name: item.name, kind: item.kind }));
|
|
219
|
+
const expected = new Set([
|
|
220
|
+
'fastly_http_body::new',
|
|
221
|
+
'fastly_http_body::write',
|
|
222
|
+
'fastly_http_resp::new',
|
|
223
|
+
'fastly_http_resp::status_set',
|
|
224
|
+
'fastly_http_resp::header_insert',
|
|
225
|
+
'fastly_http_resp::send_downstream'
|
|
226
|
+
]);
|
|
227
|
+
const unexpectedImports = imports.filter((item) => !expected.has(`${item.module}::${item.name}`));
|
|
228
|
+
return { imports, exports, hasStart: exports.some((item) => item.name === '_start' && item.kind === 'function'), unexpectedImports };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function buildArtifacts({ generatedBy, compile, audit, cwd, sourceFile, wasmFile }) {
|
|
232
|
+
const importAudit = normalizeArtifact({
|
|
233
|
+
version: FASTLY_COMMAND_IMPORT_AUDIT_VERSION,
|
|
234
|
+
generatedBy,
|
|
235
|
+
phase: PHASE,
|
|
236
|
+
status: audit.unexpectedImports.length === 0 ? 'ok' : 'error',
|
|
237
|
+
imports: audit.imports,
|
|
238
|
+
unexpectedImports: audit.unexpectedImports,
|
|
239
|
+
policy: {
|
|
240
|
+
envAbortForbidden: true,
|
|
241
|
+
expectedFastlyImportsOnly: true,
|
|
242
|
+
packIsNotRuntimeGate: true
|
|
243
|
+
},
|
|
244
|
+
summary: {
|
|
245
|
+
imports: audit.imports.length,
|
|
246
|
+
unexpectedImports: audit.unexpectedImports.length,
|
|
247
|
+
envAbort: audit.imports.some((item) => item.module === 'env' && item.name === 'abort')
|
|
248
|
+
}
|
|
249
|
+
}, cwd);
|
|
250
|
+
|
|
251
|
+
const startAudit = normalizeArtifact({
|
|
252
|
+
version: FASTLY_START_EXPORT_AUDIT_VERSION,
|
|
253
|
+
generatedBy,
|
|
254
|
+
phase: PHASE,
|
|
255
|
+
status: audit.hasStart ? 'ok' : 'error',
|
|
256
|
+
exports: audit.exports,
|
|
257
|
+
requiredExport: '_start',
|
|
258
|
+
summary: { hasStart: audit.hasStart, exports: audit.exports.length }
|
|
259
|
+
}, cwd);
|
|
260
|
+
|
|
261
|
+
const commandSmoke = normalizeArtifact({
|
|
262
|
+
version: FASTLY_COMMAND_SMOKE_VERSION,
|
|
263
|
+
generatedBy,
|
|
264
|
+
phase: PHASE,
|
|
265
|
+
status: compile.diagnostics.length === 0 && audit.hasStart && audit.unexpectedImports.length === 0 ? 'ok' : 'error',
|
|
266
|
+
compile: compile.result,
|
|
267
|
+
command: compile.command,
|
|
268
|
+
wasm: wasmFile && fs.existsSync(wasmFile) ? fileRecord(wasmFile, cwd, 'fastly-command-wasm') : undefined,
|
|
269
|
+
smoke: {
|
|
270
|
+
executedUnderFastlyServe: false,
|
|
271
|
+
commandShapeCompiled: compile.diagnostics.length === 0,
|
|
272
|
+
startExportPresent: audit.hasStart,
|
|
273
|
+
importsExpectedOnly: audit.unexpectedImports.length === 0
|
|
274
|
+
},
|
|
275
|
+
summary: {
|
|
276
|
+
compiled: compile.diagnostics.length === 0,
|
|
277
|
+
hasStart: audit.hasStart,
|
|
278
|
+
unexpectedImports: audit.unexpectedImports.length,
|
|
279
|
+
serveExecuted: false
|
|
280
|
+
}
|
|
281
|
+
}, cwd);
|
|
282
|
+
|
|
283
|
+
const serveGate = normalizeArtifact({
|
|
284
|
+
version: FASTLY_SERVE_GATE_VERSION,
|
|
285
|
+
generatedBy,
|
|
286
|
+
phase: PHASE,
|
|
287
|
+
status: audit.hasStart && audit.unexpectedImports.length === 0 ? 'ready-for-serve' : 'error',
|
|
288
|
+
gatePolicy: {
|
|
289
|
+
packSuccessDoesNotProveRuntimeCompatibility: true,
|
|
290
|
+
serveIsFirstRuntimeGate: true,
|
|
291
|
+
requiredBeforeReadiness: ['_start export', 'expected Fastly imports only', 'fastly compute serve smoke request']
|
|
292
|
+
},
|
|
293
|
+
candidateCommands: [
|
|
294
|
+
'fastly compute pack --wasm-binary artifacts/generated/fastly/pulsewasm-compute.wasm',
|
|
295
|
+
'fastly compute serve',
|
|
296
|
+
'curl -i http://127.0.0.1:7676/'
|
|
297
|
+
],
|
|
298
|
+
summary: {
|
|
299
|
+
packReady: true,
|
|
300
|
+
serveReady: audit.hasStart && audit.unexpectedImports.length === 0,
|
|
301
|
+
serveExecuted: false,
|
|
302
|
+
remoteDeployReady: false
|
|
303
|
+
}
|
|
304
|
+
}, cwd);
|
|
305
|
+
|
|
306
|
+
const commandEntry = normalizeArtifact({
|
|
307
|
+
version: FASTLY_COMMAND_ENTRY_VERSION,
|
|
308
|
+
generatedBy,
|
|
309
|
+
phase: PHASE,
|
|
310
|
+
status: commandSmoke.status,
|
|
311
|
+
posture: {
|
|
312
|
+
fastlySpecificTargetShape: true,
|
|
313
|
+
commandStyleWasm: true,
|
|
314
|
+
startExportRequired: true,
|
|
315
|
+
genericPulseRuntimeUntouched: true,
|
|
316
|
+
realFastlyHostcalls: true,
|
|
317
|
+
sdkDependency: false,
|
|
318
|
+
sdkReferenceOnly: true,
|
|
319
|
+
oldAssemblyScriptSdkRevived: false,
|
|
320
|
+
serveExecuted: false,
|
|
321
|
+
remoteDeployExecuted: false
|
|
322
|
+
},
|
|
323
|
+
source: sourceFile ? { file: sourceFile.file, bytes: sourceFile.bytes, sha256: sourceFile.sha256 } : undefined,
|
|
324
|
+
linkedArtifacts: {
|
|
325
|
+
commandSmoke: FASTLY_COMMAND_SMOKE_VERSION,
|
|
326
|
+
startExportAudit: FASTLY_START_EXPORT_AUDIT_VERSION,
|
|
327
|
+
importAudit: FASTLY_COMMAND_IMPORT_AUDIT_VERSION,
|
|
328
|
+
serveGate: FASTLY_SERVE_GATE_VERSION
|
|
329
|
+
},
|
|
330
|
+
summary: {
|
|
331
|
+
compiled: commandSmoke.summary.compiled,
|
|
332
|
+
hasStart: audit.hasStart,
|
|
333
|
+
imports: audit.imports.length,
|
|
334
|
+
unexpectedImports: audit.unexpectedImports.length,
|
|
335
|
+
envAbort: importAudit.summary.envAbort,
|
|
336
|
+
wasmBytes: wasmFile && fs.existsSync(wasmFile) ? fs.statSync(wasmFile).size : 0,
|
|
337
|
+
packReady: true,
|
|
338
|
+
serveExecuted: false,
|
|
339
|
+
diagnostics: compile.diagnostics.length + audit.unexpectedImports.length + (audit.hasStart ? 0 : 1)
|
|
340
|
+
}
|
|
341
|
+
}, cwd);
|
|
342
|
+
|
|
343
|
+
return { commandEntry, startAudit, importAudit, serveGate, commandSmoke };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function buildMarkdown(artifact) {
|
|
347
|
+
return `# PulseWasm Fastly Command Entrypoint\n\nStatus: **${artifact.status}**\n\n## Purpose\n\nFastly/Viceroy \`serve\` expects a command-style Compute guest with an exported \`_start\` function. The Fastly target generates that command entrypoint instead of reusing the generic library-style PulseWasm module.\n\n## Boundary\n\n- \`pack\` is package-shape validation.\n- \`serve\` is the runtime compatibility gate.\n- \`_start\` belongs to the Fastly adapter target, not the generic runtime.\n\n## Candidate commands\n\n\`\`\`bash\nfastly compute pack --wasm-binary artifacts/generated/fastly/pulsewasm-compute.wasm\nfastly compute serve\ncurl -i http://127.0.0.1:7676/\n\`\`\`\n\n## Summary\n\n\`\`\`json\n${JSON.stringify(artifact.summary, null, 2)}\n\`\`\`\n`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function buildFastlyCommandEntry({ fastlyAdapter, fastlyHostcallBinding } = {}, options = {}) {
|
|
351
|
+
const cwd = options.cwd || process.cwd();
|
|
352
|
+
const outDir = options.outDir || getDefaultArtifactsDir(cwd);
|
|
353
|
+
const generatedBy = options.generatedBy || PACKAGE_VERSION;
|
|
354
|
+
const diagnostics = [];
|
|
355
|
+
const sourceText = buildFastlyCommandEntrySource();
|
|
356
|
+
const sourceFile = makeFile('generated/fastly/fastly-entry.as.ts', sourceText);
|
|
357
|
+
const compile = compileCommandEntry({ cwd, outDir, sourceText });
|
|
358
|
+
diagnostics.push(...compile.diagnostics);
|
|
359
|
+
const audit = wasmAudit(compile.wasmFile);
|
|
360
|
+
if (!audit.hasStart) diagnostics.push(diag('PULSEWASM_FASTLY_START_EXPORT_MISSING', 'Fastly command candidate does not export _start.', 'The Fastly target adapter must export _start for fastly compute serve.'));
|
|
361
|
+
if (audit.unexpectedImports.length > 0) diagnostics.push(diag('PULSEWASM_FASTLY_COMMAND_UNEXPECTED_IMPORT', 'Fastly command candidate has unexpected imports.', 'Only expected Fastly hostcall imports are allowed at this phase.', { unexpectedImports: audit.unexpectedImports }));
|
|
362
|
+
const artifacts = buildArtifacts({ generatedBy, compile, audit, cwd, sourceFile, wasmFile: compile.wasmFile });
|
|
363
|
+
const files = [
|
|
364
|
+
sourceFile,
|
|
365
|
+
makeFile('generated/fastly/fastly.toml', buildFastlyToml()),
|
|
366
|
+
makeFile('generated/fastly/README.md', buildReadme()),
|
|
367
|
+
makeFile('generated/host/fastly-command-entry.md', buildMarkdown(artifacts.commandEntry))
|
|
368
|
+
];
|
|
369
|
+
const outputFiles = [];
|
|
370
|
+
if (compile.wasmFile && fs.existsSync(compile.wasmFile)) {
|
|
371
|
+
const fastlyDir = path.join(outDir, 'generated', 'fastly');
|
|
372
|
+
fs.mkdirSync(fastlyDir, { recursive: true });
|
|
373
|
+
const computeWasm = path.join(fastlyDir, 'pulsewasm-compute.wasm');
|
|
374
|
+
fs.copyFileSync(compile.wasmFile, computeWasm);
|
|
375
|
+
outputFiles.push(fileRecord(compile.wasmFile, cwd, 'fastly-command-wasm'));
|
|
376
|
+
outputFiles.push(fileRecord(computeWasm, cwd, 'fastly-compute-candidate-wasm'));
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
artifact: artifacts.commandEntry,
|
|
380
|
+
startExportAudit: artifacts.startAudit,
|
|
381
|
+
importAudit: artifacts.importAudit,
|
|
382
|
+
serveGate: artifacts.serveGate,
|
|
383
|
+
commandSmoke: artifacts.commandSmoke,
|
|
384
|
+
files,
|
|
385
|
+
outputFiles,
|
|
386
|
+
diagnostics
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
module.exports = { buildFastlyCommandEntry };
|