space-data-module-sdk 0.5.14 → 0.5.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 CHANGED
@@ -14,11 +14,28 @@ This repository is the source of truth for module-level concerns:
14
14
  - the `sds.bundle` single-file container
15
15
  - deployment authorization plus SDS publication records (`REC`, `PNM`, `ENC`)
16
16
  - the first canonical module hostcall/import ABI surface
17
+ - shared module-level testing/runtime harnesses, including the WasmEdge
18
+ process runner used by package-level validation suites
17
19
 
18
20
  <p align="center">
19
21
  <img src="docs/architecture.svg" alt="Module architecture overview" width="820" />
20
22
  </p>
21
23
 
24
+ ## Shared Harness Ownership
25
+
26
+ This repo owns the generic module-side runtime harnesses used across the stack:
27
+
28
+ - `createModuleHarness(...)` for process and WasmEdge-backed module invocation
29
+ - `resolveModuleHarnessLaunchPlan(...)` for portable launch planning
30
+ - `buildWasmEdgeEmscriptenPthreadRunner(...)` plus the shared native runner
31
+ source and runner-level pthread smoke test
32
+
33
+ Flow-specific standalone harnessing lives in `sdn-flow`, which layers flow
34
+ enqueue/drain behavior on top of its compiled standalone runtime host. Package-
35
+ specific validation suites, including conjunction replay/V&V, are expected to
36
+ sit on top of these shared harnesses instead of defining their own runtime
37
+ process model.
38
+
22
39
  ## Module Artifact Model
23
40
 
24
41
  A compliant module built with this SDK is always a valid `.wasm` artifact with:
@@ -178,6 +195,11 @@ semantics.
178
195
  This repo now exposes a manifest-driven harness generator from
179
196
  `space-data-module-sdk/testing` and two complementary integration suites:
180
197
 
198
+ - shared process-level helpers for command-surface runtimes:
199
+ - `createPluginInvokeProcessClient(...)`
200
+ - `resolveWasmEdgePluginLaunchPlan(...)`
201
+ - `buildWasmEdgeEmscriptenPthreadRunner(...)`
202
+
181
203
  - `npm run test:runtime-matrix`
182
204
  - cross-language runtime smoke across the same WASM in Node.js, Go, Python,
183
205
  Rust, Java, C#, and Swift
@@ -299,6 +321,46 @@ The reference path lives in
299
321
  Standard bundle payloads now include the optional `deployment-plan` JSON entry
300
322
  for resolved protocol installations and producer input bindings.
301
323
 
324
+ ## Publication Protection Extensions
325
+
326
+ Digital signature and encrypted-delivery metadata are publication-layer
327
+ extensions, not a second module format. The canonical module is still:
328
+
329
+ - valid `.wasm`
330
+ - optionally carrying `sds.bundle` as a wasm custom section
331
+ - optionally carrying one appended SDS `REC` trailer after the wasm bytes
332
+
333
+ That trailing `REC` FlatBuffer is the standards-backed container for
334
+ publication records:
335
+
336
+ - `PNM` is the publication notice / digital-signature record
337
+ - identifies the artifact
338
+ - carries the CID and publish timestamp
339
+ - carries signature metadata so a loader can verify who published the module
340
+ - `ENC` is the encrypted-delivery record
341
+ - carries the X25519 / AES-CTR / HKDF parameters needed to decrypt the
342
+ protected transport payload
343
+ - does not change the canonical module ABI or manifest shape
344
+
345
+ The loader contract is always:
346
+
347
+ 1. Start with the protected blob.
348
+ 2. Scan backward for the trailing `REC` footer.
349
+ 3. Parse `REC` using the standards-generated `REC`, `PNM`, and `ENC`
350
+ FlatBuffers from `spacedatastandards.org`.
351
+ 4. Resolve `PNM` for signature/publication metadata.
352
+ 5. Resolve `ENC` if the delivery was encrypted.
353
+ 6. Strip the trailer or decrypt the protected payload.
354
+ 7. Hand the remaining raw wasm bytes to the runtime.
355
+
356
+ Aligned-binary payloads are a separate invoke-ABI optimization. They do not
357
+ replace the canonical FlatBuffer schema. If a port advertises
358
+ `wireFormat: "aligned-binary"`, it must also advertise the regular
359
+ `wireFormat: "flatbuffer"` fallback for the same schema and file identifier in
360
+ the same accepted type set. The publication demo in the local lab uses a
361
+ regular `OMM.fbs` request and a `StateVector.fbs` response that advertises both
362
+ the canonical FlatBuffer fallback and the aligned-binary layout metadata.
363
+
302
364
  ## Module Publication
303
365
 
304
366
  Packages that publish Space Data modules use the canonical `sdn-module`
@@ -402,10 +464,14 @@ npm run start:lab
402
464
 
403
465
  Then open `http://localhost:4318`.
404
466
 
467
+ Use the `Run Publication Demo` button to generate a protected demo artifact and
468
+ inspect the parsed `REC`, `PNM`, and `ENC` records produced by the real
469
+ `spacedatastandards.org` generated message classes.
470
+
405
471
  ## Related Projects
406
472
 
407
473
  - [`spacedatastandards.org`](https://spacedatastandards.org)
408
- - [`@digitalarsenal/sdn-flow`](https://github.com/DigitalArsenal/sdn-flow)
474
+ - [`sdn-flow`](https://github.com/DigitalArsenal/sdn-flow)
409
475
  - [`hd-wallet-wasm`](https://github.com/nicktj-dev/hd-wallet-wasm)
410
476
 
411
477
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -167,134 +167,6 @@ function normalizePayloadWireFormatName(value) {
167
167
  return null;
168
168
  }
169
169
 
170
- function normalizedPluginFamilyName(value) {
171
- if (!isNonEmptyString(value)) {
172
- return null;
173
- }
174
- return value
175
- .trim()
176
- .toLowerCase()
177
- .replace(/-/g, "_");
178
- }
179
-
180
- function typeSetHasDualWireFormats(typeSet) {
181
- if (!Array.isArray(typeSet?.allowedTypes) || typeSet.allowedTypes.length === 0) {
182
- return false;
183
- }
184
- let hasFlatbuffer = false;
185
- let hasAlignedBinary = false;
186
- for (const allowedType of typeSet.allowedTypes) {
187
- const wireFormat = normalizePayloadWireFormatName(allowedType?.wireFormat);
188
- if (wireFormat === "flatbuffer") {
189
- hasFlatbuffer = true;
190
- } else if (wireFormat === "aligned-binary") {
191
- hasAlignedBinary = true;
192
- }
193
- }
194
- return hasFlatbuffer && hasAlignedBinary;
195
- }
196
-
197
- function validateCanonicalDualFormatPort(port, issues, location, methodId, portId) {
198
- if (!port) {
199
- pushIssue(
200
- issues,
201
- "error",
202
- "propagator-missing-canonical-port",
203
- `Canonical propagator method "${methodId}" must declare port "${portId}".`,
204
- location,
205
- );
206
- return;
207
- }
208
- if (!Array.isArray(port.acceptedTypeSets) || port.acceptedTypeSets.length === 0) {
209
- pushIssue(
210
- issues,
211
- "error",
212
- "propagator-missing-canonical-port",
213
- `Canonical propagator method "${methodId}" port "${portId}" must declare acceptedTypeSets.`,
214
- `${location}.acceptedTypeSets`,
215
- );
216
- return;
217
- }
218
- port.acceptedTypeSets.forEach((typeSet, index) => {
219
- if (!typeSetHasDualWireFormats(typeSet)) {
220
- pushIssue(
221
- issues,
222
- "error",
223
- "canonical-port-missing-dual-wire-format",
224
- `Canonical propagator method "${methodId}" port "${portId}" acceptedTypeSet "${typeSet?.setId ?? index}" must accept both regular flatbuffer and aligned-binary payloads.`,
225
- `${location}.acceptedTypeSets[${index}]`,
226
- );
227
- }
228
- });
229
- }
230
-
231
- function validateCanonicalPropagatorContract(manifest, issues, sourceName) {
232
- if (normalizedPluginFamilyName(manifest?.pluginFamily) !== "propagator") {
233
- return;
234
- }
235
-
236
- const methods = Array.isArray(manifest?.methods) ? manifest.methods : [];
237
- const methodRequirements = [
238
- {
239
- methodId: "ingest_omm",
240
- inputPorts: ["omm"],
241
- outputPorts: [],
242
- },
243
- {
244
- methodId: "describe_sources_batch",
245
- inputPorts: ["request"],
246
- outputPorts: ["result"],
247
- },
248
- {
249
- methodId: "propagate_state",
250
- inputPorts: ["request"],
251
- outputPorts: ["state"],
252
- },
253
- ];
254
-
255
- for (const requirement of methodRequirements) {
256
- const methodIndex = methods.findIndex(
257
- (method) => method?.methodId === requirement.methodId,
258
- );
259
- if (methodIndex === -1) {
260
- pushIssue(
261
- issues,
262
- "error",
263
- "propagator-missing-canonical-method",
264
- `Propagator plugins must declare canonical method "${requirement.methodId}".`,
265
- `${sourceName}.methods`,
266
- );
267
- continue;
268
- }
269
- const method = methods[methodIndex];
270
- const location = `${sourceName}.methods[${methodIndex}]`;
271
- for (const portId of requirement.inputPorts) {
272
- const port = Array.isArray(method.inputPorts)
273
- ? method.inputPorts.find((entry) => entry?.portId === portId)
274
- : null;
275
- validateCanonicalDualFormatPort(
276
- port,
277
- issues,
278
- `${location}.inputPorts`,
279
- requirement.methodId,
280
- portId,
281
- );
282
- }
283
- for (const portId of requirement.outputPorts) {
284
- const port = Array.isArray(method.outputPorts)
285
- ? method.outputPorts.find((entry) => entry?.portId === portId)
286
- : null;
287
- validateCanonicalDualFormatPort(
288
- port,
289
- issues,
290
- `${location}.outputPorts`,
291
- requirement.methodId,
292
- portId,
293
- );
294
- }
295
- }
296
- }
297
-
298
170
  function validateStringField(issues, value, location, label) {
299
171
  if (!isNonEmptyString(value)) {
300
172
  pushIssue(issues, "error", "missing-string", `${label} must be a non-empty string.`, location);
@@ -494,7 +366,7 @@ function validateAllowedType(type, issues, location) {
494
366
  "Allowed type fixedStringLength",
495
367
  { min: 0 },
496
368
  );
497
- validateOptionalIntegerField(
369
+ validateIntegerField(
498
370
  issues,
499
371
  type.byteLength,
500
372
  `${location}.byteLength`,
@@ -1387,8 +1259,6 @@ export function validatePluginManifest(manifest, options = {}) {
1387
1259
  }
1388
1260
  });
1389
1261
 
1390
- validateCanonicalPropagatorContract(manifest, issues, sourceName);
1391
-
1392
1262
  if (manifest.timers !== undefined && !Array.isArray(manifest.timers)) {
1393
1263
  pushIssue(
1394
1264
  issues,
package/src/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export type ProtocolRoleName = "handle" | "dial" | "both";
11
11
 
12
12
  export interface PayloadTypeRef {
13
13
  schemaName?: string;
14
- fileIdentifier?: string | null;
14
+ fileIdentifier?: string;
15
15
  schemaHash?: string | number[] | Uint8Array;
16
16
  acceptsAnyFlatbuffer?: boolean;
17
17
  wireFormat?: PayloadWireFormat;
@@ -662,12 +662,29 @@ export type {
662
662
  HarnessInvokeScenario,
663
663
  HarnessRawScenario,
664
664
  ManifestHarnessPlan,
665
+ ModuleHarness,
666
+ ModuleHarnessRuntimeDescriptor,
667
+ PublicationProtectionDemoAlignedType,
668
+ PublicationProtectionDemoSummary,
669
+ PluginInvokeProcessClient,
670
+ PluginInvokeProcessLaunchPlan,
671
+ WasmEdgeRunnerBuildPlan,
665
672
  } from "./testing/index.js";
666
673
 
667
674
  export {
675
+ buildWasmEdgeEmscriptenPthreadRunner,
676
+ buildWasmEdgeSpawnEnv,
677
+ createPublicationProtectionDemoManifest,
678
+ createPublicationProtectionDemoSummary,
679
+ createModuleHarness,
680
+ createPluginInvokeProcessClient,
668
681
  describeCapabilityRuntimeSurface,
669
682
  generateManifestHarnessPlan,
670
683
  materializeHarnessScenario,
684
+ resolveModuleHarnessLaunchPlan,
685
+ resolveWasmEdgeRunnerBuildPlan,
686
+ resolveWasmEdgeRunnerSourcePath,
687
+ resolveWasmEdgePluginLaunchPlan,
671
688
  serializeHarnessPlan,
672
689
  } from "./testing/index.js";
673
690
 
@@ -12,7 +12,7 @@ import { InvokeSurface } from "../generated/orbpro/manifest/invoke-surface.js";
12
12
  import { BufferMutability } from "../generated/orbpro/stream/buffer-mutability.js";
13
13
  import { BufferOwnership } from "../generated/orbpro/stream/buffer-ownership.js";
14
14
  import { FlatBufferTypeRefT } from "../generated/orbpro/stream/flat-buffer-type-ref.js";
15
- import { TypedArenaBuffer, TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
15
+ import { TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
16
16
  import { toUint8Array } from "../runtime/bufferLike.js";
17
17
 
18
18
  function toByteBuffer(data) {
@@ -149,20 +149,22 @@ function normalizeArenaFrame(frame = {}, offset) {
149
149
 
150
150
  function packArenaFrames(frames = []) {
151
151
  const packedFrames = [];
152
- const arena = [];
152
+ const normalizedFrames = [];
153
153
  let offset = 0;
154
154
  for (const frame of frames) {
155
155
  const normalized = normalizeArenaFrame(frame, offset);
156
- for (let index = 0; index < normalized.padding; index += 1) {
157
- arena.push(0);
158
- }
159
- arena.push(...normalized.payload);
160
156
  offset = normalized.buffer.offset + normalized.buffer.size;
161
157
  packedFrames.push(normalized.buffer);
158
+ normalizedFrames.push(normalized);
159
+ }
160
+
161
+ const arena = new Uint8Array(offset);
162
+ for (const normalized of normalizedFrames) {
163
+ arena.set(normalized.payload, normalized.buffer.offset);
162
164
  }
163
165
  return {
164
166
  frames: packedFrames,
165
- arena: Uint8Array.from(arena),
167
+ arena,
166
168
  };
167
169
  }
168
170
 
@@ -1,62 +1,31 @@
1
1
  function cloneSchemaHash(value) {
2
2
  if (value instanceof Uint8Array) {
3
- return value.byteLength > 0 ? new Uint8Array(value) : undefined;
3
+ return new Uint8Array(value);
4
4
  }
5
5
  if (Array.isArray(value)) {
6
- return value.length > 0 ? [...value] : undefined;
6
+ return [...value];
7
7
  }
8
8
  return value ?? undefined;
9
9
  }
10
10
 
11
- function normalizeNullableString(value) {
12
- if (value === undefined || value === null) {
13
- return null;
14
- }
15
- const normalized = String(value).trim();
16
- return normalized.length > 0 ? normalized : null;
17
- }
18
-
19
- function normalizeAlignedMetadataScalar(value) {
20
- if (value === undefined || value === null || value === "") {
21
- return undefined;
22
- }
23
- const numeric = Number(value);
24
- if (Number.isFinite(numeric)) {
25
- return numeric === 0 ? undefined : numeric;
26
- }
27
- return value;
28
- }
29
-
30
11
  export function clonePayloadTypeRef(value = null) {
31
12
  if (!value || typeof value !== "object") {
32
- return { acceptsAnyFlatbuffer: true, fileIdentifier: null };
13
+ return { acceptsAnyFlatbuffer: true };
33
14
  }
34
15
  return {
35
- schemaName: normalizeNullableString(
36
- value.schemaName ?? value.schema_name,
37
- ),
38
- fileIdentifier: normalizeNullableString(
39
- value.fileIdentifier ?? value.file_identifier,
40
- ),
16
+ schemaName: value.schemaName ?? value.schema_name ?? undefined,
17
+ fileIdentifier: value.fileIdentifier ?? value.file_identifier ?? undefined,
41
18
  schemaHash: cloneSchemaHash(value.schemaHash ?? value.schema_hash),
42
19
  acceptsAnyFlatbuffer: Boolean(
43
20
  value.acceptsAnyFlatbuffer ?? value.accepts_any_flatbuffer ?? false,
44
21
  ),
45
- wireFormat: normalizePayloadWireFormatName(
46
- value.wireFormat ?? value.wire_format,
47
- ),
48
- rootTypeName: normalizeNullableString(
49
- value.rootTypeName ?? value.root_type_name,
50
- ),
51
- fixedStringLength: normalizeAlignedMetadataScalar(
52
- value.fixedStringLength ?? value.fixed_string_length,
53
- ),
54
- byteLength: normalizeAlignedMetadataScalar(
55
- value.byteLength ?? value.byte_length,
56
- ),
57
- requiredAlignment: normalizeAlignedMetadataScalar(
58
- value.requiredAlignment ?? value.required_alignment,
59
- ),
22
+ wireFormat: value.wireFormat ?? value.wire_format ?? undefined,
23
+ rootTypeName: value.rootTypeName ?? value.root_type_name ?? undefined,
24
+ fixedStringLength:
25
+ value.fixedStringLength ?? value.fixed_string_length ?? undefined,
26
+ byteLength: value.byteLength ?? value.byte_length ?? undefined,
27
+ requiredAlignment:
28
+ value.requiredAlignment ?? value.required_alignment ?? undefined,
60
29
  };
61
30
  }
62
31
 
@@ -64,12 +33,6 @@ export function normalizePayloadWireFormatName(value) {
64
33
  if (value === undefined || value === null || value === "") {
65
34
  return null;
66
35
  }
67
- if (value === 1 || value === "1") {
68
- return "aligned-binary";
69
- }
70
- if (value === 0 || value === "0") {
71
- return "flatbuffer";
72
- }
73
36
  const normalized = String(value).trim().toLowerCase().replace(/_/g, "-");
74
37
  if (normalized === "aligned-binary") {
75
38
  return "aligned-binary";
@@ -0,0 +1,214 @@
1
+ import { accessSync, existsSync } from "node:fs";
2
+ import { execFile } from "node:child_process";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { promisify } from "node:util";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ function normalizeResolvedPath(value) {
13
+ if (typeof value !== "string" || value.trim().length === 0) {
14
+ return null;
15
+ }
16
+ return path.resolve(value);
17
+ }
18
+
19
+ function ensureReadableSync(targetPath, label) {
20
+ try {
21
+ accessSync(targetPath);
22
+ } catch {
23
+ throw new Error(`${label} not found: ${targetPath}`);
24
+ }
25
+ }
26
+
27
+ function resolveWasmEdgeSharedLibraryFilename() {
28
+ if (process.platform === "darwin") {
29
+ return "libwasmedge.0.dylib";
30
+ }
31
+ if (process.platform === "win32") {
32
+ return "wasmedge.dll";
33
+ }
34
+ return "libwasmedge.so.0";
35
+ }
36
+
37
+ function resolveGeneratedIncludeDir(requestedIncludeDir) {
38
+ const directHeaderPath = path.join(
39
+ requestedIncludeDir,
40
+ "wasmedge",
41
+ "enum_configure.h",
42
+ );
43
+ if (existsSync(directHeaderPath)) {
44
+ return requestedIncludeDir;
45
+ }
46
+
47
+ const repoRoot = path.resolve(requestedIncludeDir, "..", "..");
48
+ const generatedDir = path.join(repoRoot, "build", "include", "api");
49
+ if (existsSync(path.join(generatedDir, "wasmedge", "enum_configure.h"))) {
50
+ return generatedDir;
51
+ }
52
+
53
+ throw new Error(
54
+ `WasmEdge include directory does not contain wasmedge/enum_configure.h: ${requestedIncludeDir}`,
55
+ );
56
+ }
57
+
58
+ export function resolveWasmEdgeRunnerSourcePath() {
59
+ return path.resolve(
60
+ __dirname,
61
+ "native",
62
+ "wasmedge_emscripten_pthread_runner.c",
63
+ );
64
+ }
65
+
66
+ export function resolveWasmEdgeRunnerBuildPlan(options = {}) {
67
+ const requestedIncludeDir = normalizeResolvedPath(
68
+ options.wasmedgeIncludeDir ?? process.env.WASMEDGE_INCLUDE_DIR,
69
+ );
70
+ const wasmedgeLibDir = normalizeResolvedPath(
71
+ options.wasmedgeLibDir ?? process.env.WASMEDGE_LIB_DIR,
72
+ );
73
+ const outputPath = normalizeResolvedPath(
74
+ options.outputPath ?? options.output,
75
+ );
76
+
77
+ if (!requestedIncludeDir) {
78
+ throw new Error(
79
+ "Missing WasmEdge include directory. Set wasmedgeIncludeDir, --wasmedge-include-dir, or WASMEDGE_INCLUDE_DIR.",
80
+ );
81
+ }
82
+ if (!wasmedgeLibDir) {
83
+ throw new Error(
84
+ "Missing WasmEdge library directory. Set wasmedgeLibDir, --wasmedge-lib-dir, or WASMEDGE_LIB_DIR.",
85
+ );
86
+ }
87
+ if (!outputPath) {
88
+ throw new Error("Missing runner outputPath.");
89
+ }
90
+
91
+ const runnerSourcePath = resolveWasmEdgeRunnerSourcePath();
92
+ const wasmedgeSharedLibraryPath = path.join(
93
+ wasmedgeLibDir,
94
+ resolveWasmEdgeSharedLibraryFilename(),
95
+ );
96
+
97
+ return {
98
+ runnerSourcePath,
99
+ requestedIncludeDir,
100
+ wasmedgeIncludeDir: requestedIncludeDir,
101
+ wasmedgeLibDir,
102
+ wasmedgeSharedLibraryPath,
103
+ outputPath,
104
+ compilerCommand:
105
+ process.platform === "darwin" ? "xcrun" : process.env.CC ?? "cc",
106
+ compilerArgs:
107
+ process.platform === "darwin"
108
+ ? [
109
+ "clang",
110
+ runnerSourcePath,
111
+ "-std=c11",
112
+ "-O2",
113
+ "-pthread",
114
+ "-Wall",
115
+ "-Wextra",
116
+ "-Werror",
117
+ "-Wno-unused-parameter",
118
+ "-Wno-error=visibility",
119
+ `-I${requestedIncludeDir}`,
120
+ `-L${wasmedgeLibDir}`,
121
+ "-lwasmedge",
122
+ `-Wl,-rpath,${wasmedgeLibDir}`,
123
+ "-o",
124
+ outputPath,
125
+ ]
126
+ : [
127
+ runnerSourcePath,
128
+ "-std=c11",
129
+ "-O2",
130
+ "-pthread",
131
+ "-Wall",
132
+ "-Wextra",
133
+ "-Werror",
134
+ "-Wno-unused-parameter",
135
+ "-Wno-error=visibility",
136
+ `-I${requestedIncludeDir}`,
137
+ `-L${wasmedgeLibDir}`,
138
+ "-lwasmedge",
139
+ `-Wl,-rpath,${wasmedgeLibDir}`,
140
+ "-o",
141
+ outputPath,
142
+ ],
143
+ };
144
+ }
145
+
146
+ export async function buildWasmEdgeEmscriptenPthreadRunner(options = {}) {
147
+ const basePlan = resolveWasmEdgeRunnerBuildPlan(options);
148
+ const wasmedgeIncludeDir = resolveGeneratedIncludeDir(
149
+ basePlan.requestedIncludeDir,
150
+ );
151
+ const compilerArgs =
152
+ process.platform === "darwin"
153
+ ? [
154
+ "clang",
155
+ basePlan.runnerSourcePath,
156
+ "-std=c11",
157
+ "-O2",
158
+ "-pthread",
159
+ "-Wall",
160
+ "-Wextra",
161
+ "-Werror",
162
+ "-Wno-unused-parameter",
163
+ "-Wno-error=visibility",
164
+ `-I${wasmedgeIncludeDir}`,
165
+ `-L${basePlan.wasmedgeLibDir}`,
166
+ "-lwasmedge",
167
+ `-Wl,-rpath,${basePlan.wasmedgeLibDir}`,
168
+ "-o",
169
+ basePlan.outputPath,
170
+ ]
171
+ : [
172
+ basePlan.runnerSourcePath,
173
+ "-std=c11",
174
+ "-O2",
175
+ "-pthread",
176
+ "-Wall",
177
+ "-Wextra",
178
+ "-Werror",
179
+ "-Wno-unused-parameter",
180
+ "-Wno-error=visibility",
181
+ `-I${wasmedgeIncludeDir}`,
182
+ `-L${basePlan.wasmedgeLibDir}`,
183
+ "-lwasmedge",
184
+ `-Wl,-rpath,${basePlan.wasmedgeLibDir}`,
185
+ "-o",
186
+ basePlan.outputPath,
187
+ ];
188
+ const plan = {
189
+ ...basePlan,
190
+ wasmedgeIncludeDir,
191
+ compilerArgs,
192
+ };
193
+
194
+ ensureReadableSync(plan.runnerSourcePath, "Runner source");
195
+ ensureReadableSync(plan.wasmedgeIncludeDir, "WasmEdge include directory");
196
+ ensureReadableSync(plan.wasmedgeLibDir, "WasmEdge library directory");
197
+ ensureReadableSync(
198
+ plan.wasmedgeSharedLibraryPath,
199
+ "WasmEdge shared library",
200
+ );
201
+
202
+ await execFileAsync(plan.compilerCommand, plan.compilerArgs, {
203
+ cwd: options.cwd ?? process.cwd(),
204
+ });
205
+ if (process.platform === "darwin") {
206
+ await execFileAsync("install_name_tool", [
207
+ "-change",
208
+ "@rpath/libwasmedge.0.dylib",
209
+ plan.wasmedgeSharedLibraryPath,
210
+ plan.outputPath,
211
+ ]);
212
+ }
213
+ return plan.outputPath;
214
+ }