@napi-rs/cli 3.8.2 → 3.8.3
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 +28 -12
- package/dist/index.cjs +28 -12
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -12
- package/docs/wasi.md +14 -0
- package/package.json +2 -2
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +27 -9
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/pre-publish.spec.ts +36 -1
- package/src/api/__tests__/templates.spec.ts +48 -0
- package/src/api/pre-publish.ts +18 -12
- package/src/api/templates/load-wasi-template.ts +47 -4
package/docs/wasi.md
CHANGED
|
@@ -181,3 +181,17 @@ The deferred `./workerd` loader defaults to 1,024 pages (64 MiB), leaving
|
|
|
181
181
|
headroom under workerd's 128 MiB isolate limit. An explicit
|
|
182
182
|
`napi.wasm.initialMemory` value applies to every loader, so keep it within the
|
|
183
183
|
target isolate's limit after measuring the addon's actual requirements.
|
|
184
|
+
|
|
185
|
+
Threaded browser loaders always pre-create a pool of wasi-threads workers at
|
|
186
|
+
module initialization, sized as `asyncWorkPoolSize + hardwareConcurrency`
|
|
187
|
+
(logical cores, floored at 2, with a fallback for privacy-fuzzed values),
|
|
188
|
+
and therefore always initialize asynchronously. The `asyncWorkPoolSize`
|
|
189
|
+
reservation is included because emnapi's async-work pool draws its workers
|
|
190
|
+
from the same reuse pool; without it, async work could starve the pool
|
|
191
|
+
before addon thread spawns. This is what allows addon Rust code to spawn
|
|
192
|
+
threads from inside a blocking call: a browser cannot start a worker until
|
|
193
|
+
the blocking thread returns to its event loop, so a thread spawned mid-call
|
|
194
|
+
would never boot and the caller would deadlock waiting for it. With a
|
|
195
|
+
pre-created pool, spawning is only a message to an already-running worker,
|
|
196
|
+
and if the pool is exhausted the fallback allocates a fresh worker that
|
|
197
|
+
boots once the spawning parent returns to its event loop.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@napi-rs/cli",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.3",
|
|
4
4
|
"description": "Cli tools for napi-rs",
|
|
5
5
|
"author": "LongYinan <lynweklm@gmail.com>",
|
|
6
6
|
"homepage": "https://napi.rs/",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"ava": "^8.0.1",
|
|
87
87
|
"empathic": "^2.0.1",
|
|
88
88
|
"env-paths": "^4.0.0",
|
|
89
|
-
"oxc-parser": "^0.
|
|
89
|
+
"oxc-parser": "^0.143.0",
|
|
90
90
|
"prettier": "^3.8.3",
|
|
91
91
|
"tsdown": "^0.22.2",
|
|
92
92
|
"tslib": "^2.8.1"
|
|
@@ -12,7 +12,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
12
12
|
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,␊
|
|
13
13
|
emnapiTSFNPlugin as __emnapiTSFNPlugin,␊
|
|
14
14
|
createOnMessage as __wasmCreateOnMessageForFsProxy,␊
|
|
15
|
-
|
|
15
|
+
instantiateNapiModule as __emnapiInstantiateNapiModule,␊
|
|
16
16
|
WASI as __WASI,␊
|
|
17
17
|
} from '@napi-rs/wasm-runtime'␊
|
|
18
18
|
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'␊
|
|
@@ -42,6 +42,11 @@ Generated by [AVA](https://avajs.dev).
|
|
|
42
42
|
maximum: 65536,␊
|
|
43
43
|
shared: true,␊
|
|
44
44
|
})␊
|
|
45
|
+
const __asyncWorkPoolSize = 4␊
|
|
46
|
+
const __workerPoolSize = Math.max(␊
|
|
47
|
+
2,␊
|
|
48
|
+
globalThis.navigator?.hardwareConcurrency ?? 4,␊
|
|
49
|
+
)␊
|
|
45
50
|
␊
|
|
46
51
|
let __emnapiContext␊
|
|
47
52
|
␊
|
|
@@ -314,9 +319,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
314
319
|
instance: __napiInstance,␊
|
|
315
320
|
module: __wasiModule,␊
|
|
316
321
|
napiModule: __napiModule,␊
|
|
317
|
-
} =
|
|
322
|
+
} = await __emnapiInstantiateNapiModule(__wasmFile, {␊
|
|
318
323
|
context: __emnapiContext,␊
|
|
319
|
-
asyncWorkPoolSize:
|
|
324
|
+
asyncWorkPoolSize: __asyncWorkPoolSize,␊
|
|
325
|
+
reuseWorker: { size: __asyncWorkPoolSize + __workerPoolSize },␊
|
|
320
326
|
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],␊
|
|
321
327
|
wasi: __wasi,␊
|
|
322
328
|
onCreateWorker() {␊
|
|
@@ -361,7 +367,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
361
367
|
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,␊
|
|
362
368
|
emnapiTSFNPlugin as __emnapiTSFNPlugin,␊
|
|
363
369
|
createOnMessage as __wasmCreateOnMessageForFsProxy,␊
|
|
364
|
-
|
|
370
|
+
instantiateNapiModule as __emnapiInstantiateNapiModule,␊
|
|
365
371
|
WASI as __WASI,␊
|
|
366
372
|
} from '@napi-rs/wasm-runtime'␊
|
|
367
373
|
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'␊
|
|
@@ -391,6 +397,11 @@ Generated by [AVA](https://avajs.dev).
|
|
|
391
397
|
maximum: 65536,␊
|
|
392
398
|
shared: true,␊
|
|
393
399
|
})␊
|
|
400
|
+
const __asyncWorkPoolSize = 4␊
|
|
401
|
+
const __workerPoolSize = Math.max(␊
|
|
402
|
+
2,␊
|
|
403
|
+
globalThis.navigator?.hardwareConcurrency ?? 4,␊
|
|
404
|
+
)␊
|
|
394
405
|
␊
|
|
395
406
|
let __emnapiContext␊
|
|
396
407
|
␊
|
|
@@ -663,9 +674,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
663
674
|
instance: __napiInstance,␊
|
|
664
675
|
module: __wasiModule,␊
|
|
665
676
|
napiModule: __napiModule,␊
|
|
666
|
-
} =
|
|
677
|
+
} = await __emnapiInstantiateNapiModule(__wasmFile, {␊
|
|
667
678
|
context: __emnapiContext,␊
|
|
668
|
-
asyncWorkPoolSize:
|
|
679
|
+
asyncWorkPoolSize: __asyncWorkPoolSize,␊
|
|
680
|
+
reuseWorker: { size: __asyncWorkPoolSize + __workerPoolSize },␊
|
|
669
681
|
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],␊
|
|
670
682
|
wasi: __wasi,␊
|
|
671
683
|
onCreateWorker() {␊
|
|
@@ -723,7 +735,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
723
735
|
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,␊
|
|
724
736
|
emnapiTSFNPlugin as __emnapiTSFNPlugin,␊
|
|
725
737
|
createOnMessage as __wasmCreateOnMessageForFsProxy,␊
|
|
726
|
-
|
|
738
|
+
instantiateNapiModule as __emnapiInstantiateNapiModule,␊
|
|
727
739
|
WASI as __WASI,␊
|
|
728
740
|
} from '@napi-rs/wasm-runtime'␊
|
|
729
741
|
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'␊
|
|
@@ -759,6 +771,11 @@ Generated by [AVA](https://avajs.dev).
|
|
|
759
771
|
maximum: 65536,␊
|
|
760
772
|
shared: true,␊
|
|
761
773
|
})␊
|
|
774
|
+
const __asyncWorkPoolSize = 4␊
|
|
775
|
+
const __workerPoolSize = Math.max(␊
|
|
776
|
+
2,␊
|
|
777
|
+
globalThis.navigator?.hardwareConcurrency ?? 4,␊
|
|
778
|
+
)␊
|
|
762
779
|
␊
|
|
763
780
|
let __emnapiContext␊
|
|
764
781
|
␊
|
|
@@ -1031,9 +1048,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
1031
1048
|
instance: __napiInstance,␊
|
|
1032
1049
|
module: __wasiModule,␊
|
|
1033
1050
|
napiModule: __napiModule,␊
|
|
1034
|
-
} =
|
|
1051
|
+
} = await __emnapiInstantiateNapiModule(__wasmFile, {␊
|
|
1035
1052
|
context: __emnapiContext,␊
|
|
1036
|
-
asyncWorkPoolSize:
|
|
1053
|
+
asyncWorkPoolSize: __asyncWorkPoolSize,␊
|
|
1054
|
+
reuseWorker: { size: __asyncWorkPoolSize + __workerPoolSize },␊
|
|
1037
1055
|
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],␊
|
|
1038
1056
|
wasi: __wasi,␊
|
|
1039
1057
|
onCreateWorker() {␊
|
|
Binary file
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import test from 'ava'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
parseNpmPackFiles,
|
|
5
|
+
resolveRootOptionalDependencies,
|
|
6
|
+
} from '../pre-publish.js'
|
|
4
7
|
import { parseTriple } from '../../utils/index.js'
|
|
5
8
|
|
|
6
9
|
const PACKAGE_NAME = '@scope/pkg'
|
|
@@ -104,3 +107,35 @@ test('preserves unmanaged optionalDependencies', (t) => {
|
|
|
104
107
|
},
|
|
105
108
|
)
|
|
106
109
|
})
|
|
110
|
+
|
|
111
|
+
const NPM_PACK_FILES = [
|
|
112
|
+
{ path: './package.json' },
|
|
113
|
+
{ path: 'dist\\index.js' },
|
|
114
|
+
{ path: 42 },
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
test('parses npm 11 pack JSON output', (t) => {
|
|
118
|
+
t.deepEqual(
|
|
119
|
+
[...parseNpmPackFiles(JSON.stringify([{ files: NPM_PACK_FILES }]))],
|
|
120
|
+
['package.json', 'dist/index.js'],
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('parses npm 12 pack JSON output', (t) => {
|
|
125
|
+
t.deepEqual(
|
|
126
|
+
[
|
|
127
|
+
...parseNpmPackFiles(
|
|
128
|
+
JSON.stringify({
|
|
129
|
+
[PACKAGE_NAME]: { files: NPM_PACK_FILES },
|
|
130
|
+
}),
|
|
131
|
+
),
|
|
132
|
+
],
|
|
133
|
+
['package.json', 'dist/index.js'],
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('rejects an unexpected npm pack JSON result', (t) => {
|
|
138
|
+
t.throws(() => parseNpmPackFiles(JSON.stringify({ files: [] })), {
|
|
139
|
+
message: 'npm pack returned an unexpected JSON result',
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -13,6 +13,54 @@ test('createWasiBrowserBinding default', (t) => {
|
|
|
13
13
|
t.snapshot(createWasiBrowserBinding('test-wasi'))
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
+
test('createWasiBrowserBinding threaded builds size worker pools from hardwareConcurrency', (t) => {
|
|
17
|
+
const binding = createWasiBrowserBinding(
|
|
18
|
+
'test-wasi',
|
|
19
|
+
4000,
|
|
20
|
+
65536,
|
|
21
|
+
false,
|
|
22
|
+
false, // asyncInit: false — threaded builds still init asynchronously
|
|
23
|
+
false,
|
|
24
|
+
false,
|
|
25
|
+
true,
|
|
26
|
+
)
|
|
27
|
+
t.true(binding.includes('const __asyncWorkPoolSize = 4'))
|
|
28
|
+
t.true(binding.includes('const __workerPoolSize = Math.max('))
|
|
29
|
+
t.true(binding.includes('globalThis.navigator?.hardwareConcurrency ?? 4'))
|
|
30
|
+
// The reuse pool includes the async-work reservation: the async pool
|
|
31
|
+
// draws from the same reuse pool, so without it exhaustion would starve
|
|
32
|
+
// addon thread spawns. No `strict`: at exhaustion the fallback worker
|
|
33
|
+
// boots once the parent returns to its event loop, which is the correct
|
|
34
|
+
// behavior for spawn-and-return workloads.
|
|
35
|
+
t.true(
|
|
36
|
+
binding.includes(
|
|
37
|
+
'reuseWorker: { size: __asyncWorkPoolSize + __workerPoolSize },',
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
t.false(binding.includes('strict'))
|
|
41
|
+
t.true(binding.includes('asyncWorkPoolSize: __asyncWorkPoolSize,'))
|
|
42
|
+
t.true(binding.includes('await __emnapiInstantiateNapiModule('))
|
|
43
|
+
t.false(binding.includes('__emnapiInstantiateNapiModuleSync(__wasmFile'))
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('createWasiBrowserBinding threadless keeps sync init and no pool', (t) => {
|
|
47
|
+
const binding = createWasiBrowserBinding(
|
|
48
|
+
'test-wasi',
|
|
49
|
+
4000,
|
|
50
|
+
65536,
|
|
51
|
+
false,
|
|
52
|
+
false,
|
|
53
|
+
false,
|
|
54
|
+
false,
|
|
55
|
+
false,
|
|
56
|
+
)
|
|
57
|
+
t.false(binding.includes('__workerPoolSize'))
|
|
58
|
+
t.false(binding.includes('hardwareConcurrency'))
|
|
59
|
+
t.false(binding.includes('reuseWorker'))
|
|
60
|
+
t.true(binding.includes('asyncWorkPoolSize: 0,'))
|
|
61
|
+
t.true(binding.includes('__emnapiInstantiateNapiModuleSync(__wasmFile'))
|
|
62
|
+
})
|
|
63
|
+
|
|
16
64
|
test('createWasiBrowserBinding with errorEvent', (t) => {
|
|
17
65
|
t.snapshot(
|
|
18
66
|
createWasiBrowserBinding(
|
package/src/api/pre-publish.ts
CHANGED
|
@@ -1691,18 +1691,7 @@ function readNpmPackFiles(packageDir: string, packageDescription: string) {
|
|
|
1691
1691
|
maxBuffer: 64 * 1024 * 1024,
|
|
1692
1692
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1693
1693
|
})
|
|
1694
|
-
|
|
1695
|
-
files?: { path?: unknown }[]
|
|
1696
|
-
}[]
|
|
1697
|
-
if (!Array.isArray(packResult) || !Array.isArray(packResult[0]?.files)) {
|
|
1698
|
-
throw new Error('npm pack returned an unexpected JSON result')
|
|
1699
|
-
}
|
|
1700
|
-
return new Set(
|
|
1701
|
-
packResult[0].files
|
|
1702
|
-
.map(({ path }) => path)
|
|
1703
|
-
.filter((path): path is string => typeof path === 'string')
|
|
1704
|
-
.map((path) => path.replaceAll('\\', '/').replace(/^\.\//, '')),
|
|
1705
|
-
)
|
|
1694
|
+
return parseNpmPackFiles(output)
|
|
1706
1695
|
} catch (error) {
|
|
1707
1696
|
throw new Error(
|
|
1708
1697
|
`Failed to validate the ${packageDescription} with npm pack --dry-run. Ensure npm is available and the package can be packed.`,
|
|
@@ -1711,6 +1700,23 @@ function readNpmPackFiles(packageDir: string, packageDescription: string) {
|
|
|
1711
1700
|
}
|
|
1712
1701
|
}
|
|
1713
1702
|
|
|
1703
|
+
export function parseNpmPackFiles(output: string) {
|
|
1704
|
+
const parsedResult: unknown = JSON.parse(output)
|
|
1705
|
+
const packResults = Array.isArray(parsedResult)
|
|
1706
|
+
? parsedResult
|
|
1707
|
+
: Object.values(asRecord(parsedResult) ?? {})
|
|
1708
|
+
const files = asRecord(packResults[0])?.files
|
|
1709
|
+
if (!Array.isArray(files)) {
|
|
1710
|
+
throw new Error('npm pack returned an unexpected JSON result')
|
|
1711
|
+
}
|
|
1712
|
+
return new Set(
|
|
1713
|
+
files
|
|
1714
|
+
.map((file) => asRecord(file)?.path)
|
|
1715
|
+
.filter((path): path is string => typeof path === 'string')
|
|
1716
|
+
.map((path) => path.replaceAll('\\', '/').replace(/^\.\//, '')),
|
|
1717
|
+
)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1714
1720
|
function validateRootFacadePacklist(
|
|
1715
1721
|
rootDir: string,
|
|
1716
1722
|
rootPackageFiles: string[],
|
|
@@ -271,6 +271,10 @@ export const createWasiBrowserBinding = (
|
|
|
271
271
|
errorEvent = false,
|
|
272
272
|
threads = true,
|
|
273
273
|
) => {
|
|
274
|
+
// Threaded builds always get a pre-created worker pool (see
|
|
275
|
+
// `reuseWorkerOption` below), and pool pre-creation is asynchronous, so
|
|
276
|
+
// they always initialize asynchronously.
|
|
277
|
+
const effectiveAsyncInit = asyncInit || threads
|
|
274
278
|
const fsImport = fs
|
|
275
279
|
? buffer
|
|
276
280
|
? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'`
|
|
@@ -317,17 +321,55 @@ const __wasi = new __WASI({
|
|
|
317
321
|
const emnapiInjectBuffer = buffer
|
|
318
322
|
? ' __emnapiContext.features.Buffer = Buffer\n'
|
|
319
323
|
: ''
|
|
320
|
-
const emnapiInstantiateImport =
|
|
324
|
+
const emnapiInstantiateImport = effectiveAsyncInit
|
|
321
325
|
? `instantiateNapiModule as __emnapiInstantiateNapiModule`
|
|
322
326
|
: `instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync`
|
|
323
|
-
const emnapiInstantiateCall =
|
|
327
|
+
const emnapiInstantiateCall = effectiveAsyncInit
|
|
324
328
|
? `await __emnapiInstantiateNapiModule`
|
|
325
329
|
: `__emnapiInstantiateNapiModuleSync`
|
|
330
|
+
// The `reuseWorker` pool is what lets addon Rust code spawn threads while
|
|
331
|
+
// the calling thread is blocked inside the wasm call: a browser cannot
|
|
332
|
+
// start a worker until the blocking thread returns to its event loop, so
|
|
333
|
+
// a thread spawned mid-call can never boot and the caller deadlocks
|
|
334
|
+
// waiting for it. With a pre-created pool, spawning is only a message to
|
|
335
|
+
// an already-running worker.
|
|
336
|
+
//
|
|
337
|
+
// Its size comes from `navigator.hardwareConcurrency` at runtime (logical
|
|
338
|
+
// cores, floored at 2, with a fallback for privacy-fuzzed or missing
|
|
339
|
+
// values): a constant undersizes both ends of the range — big desktops
|
|
340
|
+
// leave parallelism on the table, and a fuzzed "2 cores" would
|
|
341
|
+
// oversubscribe.
|
|
342
|
+
//
|
|
343
|
+
// The reuse pool is sized as `__asyncWorkPoolSize + __workerPoolSize`
|
|
344
|
+
// because emnapi's async-work pool draws its workers from the SAME reuse
|
|
345
|
+
// pool: the async reservation must be included or async-work
|
|
346
|
+
// initialization can starve the reuse pool before addon threads spawn.
|
|
347
|
+
//
|
|
348
|
+
// `strict` is deliberately NOT set. Review suggested it so exhaustion
|
|
349
|
+
// errors instead of allocating a fresh worker, but it breaks
|
|
350
|
+
// spawn-and-return workloads (e.g. `testWorkers` in examples/napi, which
|
|
351
|
+
// spawns workers and joins them on a helper thread): at exhaustion their
|
|
352
|
+
// `std::thread::spawn` panics on EAGAIN. Without `strict` the fallback
|
|
353
|
+
// allocates a fresh worker, which boots normally once the spawning
|
|
354
|
+
// parent returns to its event loop — and for joins inside a blocked
|
|
355
|
+
// call, the pre-created pool is what those calls draw from anyway.
|
|
356
|
+
const workerPoolSizeBinding = threads
|
|
357
|
+
? `const __asyncWorkPoolSize = 4
|
|
358
|
+
const __workerPoolSize = Math.max(
|
|
359
|
+
2,
|
|
360
|
+
globalThis.navigator?.hardwareConcurrency ?? 4,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
`
|
|
364
|
+
: ''
|
|
365
|
+
const reuseWorkerOption = threads
|
|
366
|
+
? ` reuseWorker: { size: __asyncWorkPoolSize + __workerPoolSize },\n`
|
|
367
|
+
: ''
|
|
326
368
|
const workerRuntimeImport = threads
|
|
327
369
|
? ` createOnMessage as __wasmCreateOnMessageForFsProxy,\n`
|
|
328
370
|
: ''
|
|
329
371
|
const memoryName = threads ? '__sharedMemory' : '__wasmMemory'
|
|
330
|
-
const asyncWorkPoolOption = ` asyncWorkPoolSize: ${threads ?
|
|
372
|
+
const asyncWorkPoolOption = ` asyncWorkPoolSize: ${threads ? '__asyncWorkPoolSize' : 0},
|
|
331
373
|
`
|
|
332
374
|
// Every build links a "basic" emnapi archive without the C async-work and
|
|
333
375
|
// threadsafe-function implementations (the `emnapi-napi-rs(-mt)` archives shipped by the emnapi package), so the
|
|
@@ -380,7 +422,7 @@ const ${memoryName} = new WebAssembly.Memory({
|
|
|
380
422
|
maximum: ${maximumMemory},
|
|
381
423
|
${threads ? ' shared: true,\n' : ''}\
|
|
382
424
|
})
|
|
383
|
-
|
|
425
|
+
${workerPoolSizeBinding}\
|
|
384
426
|
let __emnapiContext
|
|
385
427
|
${emnapiContextLifecycle}
|
|
386
428
|
let __wasiModule
|
|
@@ -397,6 +439,7 @@ try {
|
|
|
397
439
|
} = ${emnapiInstantiateCall}(__wasmFile, {
|
|
398
440
|
context: __emnapiContext,
|
|
399
441
|
${asyncWorkPoolOption}\
|
|
442
|
+
${reuseWorkerOption}\
|
|
400
443
|
${emnapiPluginOption}\
|
|
401
444
|
wasi: __wasi,
|
|
402
445
|
${workerOption}\
|