@napi-rs/cli 3.8.6 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +9085 -8737
- package/dist/index.cjs +9092 -8744
- package/dist/index.d.cts +37 -17
- package/dist/index.d.ts +37 -17
- package/dist/index.js +9085 -8737
- package/docs/build.md +36 -34
- package/package.json +4 -5
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +9 -13
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build.spec.ts +284 -0
- package/src/api/__tests__/create-npm-dirs.spec.ts +4 -3
- package/src/api/__tests__/templates.spec.ts +84 -1
- package/src/api/build.ts +181 -7
- package/src/api/create-npm-dirs.ts +2 -1
- package/src/api/new.ts +13 -18
- package/src/api/pre-publish.ts +5 -10
- package/src/api/rename.ts +10 -21
- package/src/api/templates/js-binding.ts +7 -9
- package/src/api/templates/load-wasi-template.ts +20 -17
- package/src/api/templates/wasi-worker-template.ts +36 -31
- package/src/commands/__tests__/build.spec.ts +24 -0
- package/src/commands/build.ts +1 -1
- package/src/def/build.ts +30 -4
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +18 -28
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
- package/src/utils/__tests__/misc.spec.ts +4 -0
- package/src/utils/__tests__/serialize.spec.ts +55 -0
- package/src/utils/__tests__/target.spec.ts +221 -0
- package/src/utils/__tests__/typegen.spec.ts +115 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +4 -5
- package/src/utils/serialize.ts +47 -0
- package/src/utils/target.ts +150 -1
- package/src/utils/typegen.ts +241 -30
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
createWasiBrowserBinding,
|
|
8
8
|
createWasiDeferredBrowserBinding,
|
|
9
9
|
} from '../templates/load-wasi-template.js'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createWasiBrowserWorkerBinding,
|
|
12
|
+
WASI_WORKER_TEMPLATE,
|
|
13
|
+
} from '../templates/wasi-worker-template.js'
|
|
11
14
|
|
|
12
15
|
const test = ava
|
|
13
16
|
|
|
@@ -228,6 +231,36 @@ const wasiLoaderCases: Array<{ name: string; code: string }> = [
|
|
|
228
231
|
{ name: 'deferred/workerd', code: createWasiDeferredBrowserBinding('test') },
|
|
229
232
|
]
|
|
230
233
|
|
|
234
|
+
test('Node WASI loader uses an accessible host root on Android', (t) => {
|
|
235
|
+
const code = createWasiBinding('test', '@scope/test')
|
|
236
|
+
assertValidJS(t, code, 'Node WASI loader')
|
|
237
|
+
t.true(code.includes('const __cwd = process.cwd()'))
|
|
238
|
+
t.true(code.includes("process.platform === 'android' ? __cwd : __rootDir"))
|
|
239
|
+
t.true(code.includes('[__rootDir]: __hostRoot'))
|
|
240
|
+
t.true(code.includes('[__hostRoot]: __hostRoot'))
|
|
241
|
+
t.true(
|
|
242
|
+
code.includes('workerData: { hostRoot: __hostRoot, rootDir: __rootDir }'),
|
|
243
|
+
)
|
|
244
|
+
t.false(code.includes('[__rootDir]: __rootDir'))
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('Node WASI worker uses an accessible host root on Android', (t) => {
|
|
248
|
+
assertValidJS(t, WASI_WORKER_TEMPLATE, 'Node WASI worker')
|
|
249
|
+
t.true(
|
|
250
|
+
WASI_WORKER_TEMPLATE.includes(
|
|
251
|
+
"workerData && typeof workerData.rootDir === 'string' && workerData.rootDir",
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
t.true(
|
|
255
|
+
WASI_WORKER_TEMPLATE.includes(
|
|
256
|
+
"workerData && typeof workerData.hostRoot === 'string' && workerData.hostRoot",
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
t.true(WASI_WORKER_TEMPLATE.includes('[__rootDir]: __hostRoot'))
|
|
260
|
+
t.true(WASI_WORKER_TEMPLATE.includes('[__hostRoot]: __hostRoot'))
|
|
261
|
+
t.false(WASI_WORKER_TEMPLATE.includes('[__rootDir]: __rootDir'))
|
|
262
|
+
})
|
|
263
|
+
|
|
231
264
|
for (const { name, code } of wasiLoaderCases) {
|
|
232
265
|
test(`WASI loader waits for queued settlements before destroy: ${name}`, (t) => {
|
|
233
266
|
t.true(
|
|
@@ -284,6 +317,34 @@ function initializationRollbackBody(code: string): string {
|
|
|
284
317
|
return code.slice(deferredStart, code.indexOf('throw error', deferredStart))
|
|
285
318
|
}
|
|
286
319
|
|
|
320
|
+
test('createCjsBinding uses one statement dialect', (t) => {
|
|
321
|
+
const code = createCjsBinding('test', '@scope/test', ['sum'], '1.0.0', [
|
|
322
|
+
'wasm32-wasi',
|
|
323
|
+
])
|
|
324
|
+
t.false(
|
|
325
|
+
code.includes('NAPI_RS_NATIVE_LIBRARY_PATH);'),
|
|
326
|
+
'native library require must not use a leftover semicolon',
|
|
327
|
+
)
|
|
328
|
+
t.true(code.includes("const __napiWasiFlavors = ['wasm32-wasi']"))
|
|
329
|
+
t.false(code.includes('"wasm32-wasi"'))
|
|
330
|
+
t.true(code.includes("return require('./test.win32-x64-gnu.node')"))
|
|
331
|
+
const win32Gnu = code.slice(
|
|
332
|
+
code.indexOf("process.arch === 'x64'"),
|
|
333
|
+
code.indexOf("process.arch === 'ia32'"),
|
|
334
|
+
)
|
|
335
|
+
t.true(
|
|
336
|
+
win32Gnu.includes(" return require('./test.win32-x64-gnu.node')"),
|
|
337
|
+
'win32-x64 gnu local require must be indented inside try',
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
test('WASI worker template matches the CJS/ESM quote and semicolon dialect', (t) => {
|
|
342
|
+
t.true(WASI_WORKER_TEMPLATE.includes("import fs from 'node:fs'"))
|
|
343
|
+
t.false(WASI_WORKER_TEMPLATE.includes('from "node:fs"'))
|
|
344
|
+
t.false(WASI_WORKER_TEMPLATE.includes("from 'node:fs';"))
|
|
345
|
+
t.true(WASI_WORKER_TEMPLATE.includes('memory: wasmMemory,'))
|
|
346
|
+
})
|
|
347
|
+
|
|
287
348
|
test('createEsmBinding is Node 12 compatible', (t) => {
|
|
288
349
|
const code = createEsmBinding('test', '@scope/test', ['sum'])
|
|
289
350
|
assertValidJS(t, code, 'esm')
|
|
@@ -294,3 +355,25 @@ test('createEsmBinding is Node 12 compatible', (t) => {
|
|
|
294
355
|
t.false(code.includes('?.'), 'ESM loader must not use optional chaining')
|
|
295
356
|
t.false(code.includes('??'), 'ESM loader must not use nullish coalescing')
|
|
296
357
|
})
|
|
358
|
+
|
|
359
|
+
test('createEsmBinding builds native addon loading on portable ESM primitives', (t) => {
|
|
360
|
+
const code = createEsmBinding('test', '@scope/test', ['sum'])
|
|
361
|
+
assertValidJS(t, code, 'esm')
|
|
362
|
+
t.true(
|
|
363
|
+
code.includes(`import { createRequire } from 'module'`),
|
|
364
|
+
'ESM loader must import createRequire so native .node addons can be loaded without the CommonJS `require` global',
|
|
365
|
+
)
|
|
366
|
+
t.true(
|
|
367
|
+
code.includes('const require = createRequire(import.meta.url)'),
|
|
368
|
+
'ESM loader must derive `require` from the current module, not rely on a CommonJS global',
|
|
369
|
+
)
|
|
370
|
+
// `@napi-rs/cli` is a valid Node package but is not running under Deno. Deno
|
|
371
|
+
// rejects `URL.pathname` treated as a filesystem path, which is exactly how a
|
|
372
|
+
// `__dirname` built from `new URL('.', import.meta.url).pathname` behaves on
|
|
373
|
+
// Windows (percent-encoded, forward-slash separated). The ESM native loader
|
|
374
|
+
// never used `__dirname` anyway, so it must not emit that construct at all.
|
|
375
|
+
t.false(
|
|
376
|
+
/new URL\(['"]\.['"], import\.meta\.url\)\.pathname/.test(code),
|
|
377
|
+
'ESM loader must not treat URL.pathname as a filesystem path',
|
|
378
|
+
)
|
|
379
|
+
})
|
package/src/api/build.ts
CHANGED
|
@@ -45,7 +45,10 @@ import {
|
|
|
45
45
|
targetToEnvVar,
|
|
46
46
|
tryInstallCargoBinary,
|
|
47
47
|
unlinkAsync,
|
|
48
|
+
rustBundledWasiLibc,
|
|
49
|
+
wasiLibcHasNewFutexAbi,
|
|
48
50
|
wasiLoaderSuffix,
|
|
51
|
+
wasiSdkMajorVersion,
|
|
49
52
|
wasiTargetHasThreads,
|
|
50
53
|
writeFileAtomic,
|
|
51
54
|
withFileSystemReconciliation,
|
|
@@ -84,8 +87,13 @@ type WasiBindingMetadata = {
|
|
|
84
87
|
bindingTypeDef?: string
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
export type BuildFormat = 'esm' | 'commonjs'
|
|
91
|
+
|
|
87
92
|
type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
|
|
88
|
-
type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & {
|
|
93
|
+
type ParsedBuildOptions = Omit<BuildOptions, 'cwd' | 'format'> & {
|
|
94
|
+
cwd: string
|
|
95
|
+
format: BuildFormat
|
|
96
|
+
}
|
|
89
97
|
|
|
90
98
|
export const WASI_ARTIFACT_METADATA_PREFIX = '// napi-rs-artifact-metadata:'
|
|
91
99
|
|
|
@@ -218,6 +226,83 @@ export function getTypeDefCacheFolder(options: {
|
|
|
218
226
|
return join(options.targetDir, 'napi-rs', `${options.crateName}-${hash}`)
|
|
219
227
|
}
|
|
220
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Name of the emnapi archive directory built against the wasi-sdk 34 ABI.
|
|
231
|
+
* Only the threaded target has one: the signature change it carries lives in
|
|
232
|
+
* the threads-only futex code.
|
|
233
|
+
*/
|
|
234
|
+
export const EMNAPI_WASI_SDK_34_LINK_DIR = 'wasm32-wasip1-threads-wasi-sdk-34'
|
|
235
|
+
|
|
236
|
+
export interface EmnapiLinkDirSelection {
|
|
237
|
+
/** Directory name under `emnapi/lib` whose archives should be linked. */
|
|
238
|
+
linkDirName: string
|
|
239
|
+
/** Detected wasi-sdk major version, `null` when no wasi-sdk is configured. */
|
|
240
|
+
wasiSdkMajor: number | null
|
|
241
|
+
/** `true` when the toolchain needs the wasi-sdk 34 archives. */
|
|
242
|
+
needsWasiSdk34: boolean
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Pick the emnapi archive directory for a WASI build.
|
|
247
|
+
*
|
|
248
|
+
* wasi-libc removed the unused `int op` parameter from
|
|
249
|
+
* `__wasilibc_futex_wait_atomic_wait` / `__wasilibc_futex_wait_maybe_busy`,
|
|
250
|
+
* and wasi-sdk 34 is the first release shipping the 3-argument signature.
|
|
251
|
+
* Linking the legacy 4-argument archives against a wasi-sdk >= 34 sysroot
|
|
252
|
+
* makes `wasm-ld` report `function signature mismatch` and emit an invalid
|
|
253
|
+
* wasm module, so emnapi publishes a second archive set for the new ABI and
|
|
254
|
+
* the directory has to be chosen by wasi-sdk version, not by target triple.
|
|
255
|
+
*
|
|
256
|
+
* Without `WASI_SDK_PATH`, cargo links through `rust-lld` against the
|
|
257
|
+
* wasi-libc bundled with the Rust standard library, so that archive is
|
|
258
|
+
* probed instead. Rust picked up wasi-sdk 34 in rust-lang/rust#161773, which
|
|
259
|
+
* lands in 1.100, and a toolchain carrying it needs the new archives even
|
|
260
|
+
* though no wasi-sdk is configured.
|
|
261
|
+
*
|
|
262
|
+
* The legacy directory stays the default whenever the ABI cannot be
|
|
263
|
+
* determined, and older emnapi releases ship it alone.
|
|
264
|
+
*/
|
|
265
|
+
export function selectEmnapiLinkDir(
|
|
266
|
+
emnapiLibDir: string,
|
|
267
|
+
wasiTarget: string,
|
|
268
|
+
hasThreads: boolean,
|
|
269
|
+
wasiSdkPath: string | undefined,
|
|
270
|
+
{
|
|
271
|
+
cwd,
|
|
272
|
+
rustWasiLibc,
|
|
273
|
+
}: {
|
|
274
|
+
// The directory Cargo is spawned in. It selects the toolchain, so the
|
|
275
|
+
// sysroot probe has to use it too.
|
|
276
|
+
cwd?: string
|
|
277
|
+
// Omit to resolve the Rust sysroot lazily, so a configured wasi-sdk never
|
|
278
|
+
// pays for a `rustc` invocation. Pass `null` to skip the probe.
|
|
279
|
+
rustWasiLibc?: string | null
|
|
280
|
+
} = {},
|
|
281
|
+
): EmnapiLinkDirSelection {
|
|
282
|
+
const usingWasiSdk = Boolean(wasiSdkPath) && existsSync(wasiSdkPath!)
|
|
283
|
+
const wasiSdkMajor = usingWasiSdk ? wasiSdkMajorVersion(wasiSdkPath!) : null
|
|
284
|
+
// Whichever wasi-libc actually gets linked decides the ABI: the wasi-sdk
|
|
285
|
+
// sysroot when one is configured, otherwise the copy bundled with the Rust
|
|
286
|
+
// standard library. Only the threaded target has a second archive set, so
|
|
287
|
+
// `hasThreads` gates the probe as well as the result — a threadless build
|
|
288
|
+
// must not pay for a `rustc` invocation and an archive read it cannot use.
|
|
289
|
+
const needsWasiSdk34 =
|
|
290
|
+
hasThreads &&
|
|
291
|
+
(usingWasiSdk
|
|
292
|
+
? wasiSdkMajor !== null && wasiSdkMajor >= 34
|
|
293
|
+
: wasiLibcHasNewFutexAbi(
|
|
294
|
+
rustWasiLibc === undefined
|
|
295
|
+
? rustBundledWasiLibc(wasiTarget, cwd)
|
|
296
|
+
: rustWasiLibc,
|
|
297
|
+
) === true)
|
|
298
|
+
const linkDirName =
|
|
299
|
+
needsWasiSdk34 &&
|
|
300
|
+
existsSync(join(emnapiLibDir, EMNAPI_WASI_SDK_34_LINK_DIR))
|
|
301
|
+
? EMNAPI_WASI_SDK_34_LINK_DIR
|
|
302
|
+
: wasiTarget
|
|
303
|
+
return { linkDirName, wasiSdkMajor, needsWasiSdk34 }
|
|
304
|
+
}
|
|
305
|
+
|
|
221
306
|
export function createWasiCompilerFlags(
|
|
222
307
|
wasiSdkPath: string,
|
|
223
308
|
wasiTarget: string,
|
|
@@ -569,12 +654,57 @@ function readWasmU32(binary: Uint8Array, start: number) {
|
|
|
569
654
|
throw new Error('Invalid WebAssembly unsigned LEB128 value')
|
|
570
655
|
}
|
|
571
656
|
|
|
657
|
+
type BuildFormatOptions = {
|
|
658
|
+
format?: string
|
|
659
|
+
esm?: boolean
|
|
660
|
+
commonjs?: boolean
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Resolve the explicit build format and its legacy CLI/API aliases.
|
|
665
|
+
*
|
|
666
|
+
* CommonJS is the historical default. The aliases are intentionally kept
|
|
667
|
+
* separate from `format` because `--esm` and `--commonjs` are boolean flags,
|
|
668
|
+
* while `--format` takes a value.
|
|
669
|
+
*/
|
|
670
|
+
export function resolveBuildFormat(options: BuildFormatOptions): BuildFormat {
|
|
671
|
+
let format: BuildFormat | undefined
|
|
672
|
+
if (options.format !== undefined) {
|
|
673
|
+
if (options.format === 'esm' || options.format === 'commonjs') {
|
|
674
|
+
format = options.format
|
|
675
|
+
} else {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`Invalid build format ${JSON.stringify(options.format)}. Expected \`esm\` or \`commonjs\`.`,
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (options.esm && options.commonjs) {
|
|
683
|
+
throw new Error('`--esm` and `--commonjs` cannot be used together.')
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const aliasFormat = options.esm
|
|
687
|
+
? 'esm'
|
|
688
|
+
: options.commonjs
|
|
689
|
+
? 'commonjs'
|
|
690
|
+
: undefined
|
|
691
|
+
if (format && aliasFormat && format !== aliasFormat) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
`\`--format ${format}\` cannot be used with \`--${aliasFormat}\`.`,
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return format ?? aliasFormat ?? 'commonjs'
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Build the configured NAPI-RS crate and generate its output artifacts. */
|
|
572
701
|
export async function buildProject(rawOptions: BuildOptions) {
|
|
573
702
|
debug('napi build command receive options: %O', rawOptions)
|
|
574
703
|
|
|
575
704
|
const options: ParsedBuildOptions = {
|
|
576
705
|
dtsCache: true,
|
|
577
706
|
...rawOptions,
|
|
707
|
+
format: resolveBuildFormat(rawOptions),
|
|
578
708
|
cwd: rawOptions.cwd ?? process.cwd(),
|
|
579
709
|
}
|
|
580
710
|
|
|
@@ -1411,16 +1541,40 @@ class Builder {
|
|
|
1411
1541
|
private setWasiEnv() {
|
|
1412
1542
|
const hasThreads = wasiTargetHasThreads(this.target)
|
|
1413
1543
|
const wasiTarget = hasThreads ? 'wasm32-wasip1-threads' : 'wasm32-wasip1'
|
|
1414
|
-
const
|
|
1544
|
+
const emnapiLibDir = join(require.resolve('emnapi'), '..', 'lib')
|
|
1415
1545
|
const emnapiVersion = require('emnapi/package.json').version
|
|
1546
|
+
const { WASI_SDK_PATH } = process.env
|
|
1547
|
+
// `wasiTarget` stays the real target triple, because it is what the clang
|
|
1548
|
+
// driver is told to build for below. The emnapi archive directory is a
|
|
1549
|
+
// separate choice: emnapi ships two archive sets for the threaded target,
|
|
1550
|
+
// one per wasi-libc futex ABI. See `selectEmnapiLinkDir`.
|
|
1551
|
+
const { linkDirName, wasiSdkMajor, needsWasiSdk34 } = selectEmnapiLinkDir(
|
|
1552
|
+
emnapiLibDir,
|
|
1553
|
+
wasiTarget,
|
|
1554
|
+
hasThreads,
|
|
1555
|
+
WASI_SDK_PATH,
|
|
1556
|
+
{ cwd: this.options.cwd },
|
|
1557
|
+
)
|
|
1558
|
+
const emnapi = join(emnapiLibDir, linkDirName)
|
|
1416
1559
|
// Keep this in sync with `emnapi_link_library` in `crates/build/src/wasi.rs`.
|
|
1417
1560
|
const emnapiArchive = join(
|
|
1418
1561
|
emnapi,
|
|
1419
1562
|
hasThreads ? 'libemnapi-napi-rs-mt.a' : 'libemnapi-basic-napi-rs.a',
|
|
1420
1563
|
)
|
|
1564
|
+
const fellBackToLegacy =
|
|
1565
|
+
needsWasiSdk34 && linkDirName !== EMNAPI_WASI_SDK_34_LINK_DIR
|
|
1421
1566
|
if (!existsSync(emnapiArchive)) {
|
|
1422
1567
|
throw new Error(
|
|
1423
|
-
|
|
1568
|
+
fellBackToLegacy
|
|
1569
|
+
? `emnapi@${emnapiVersion} does not ship the ${EMNAPI_WASI_SDK_34_LINK_DIR} archives that wasi-sdk ${wasiSdkMajor} requires, and the ${linkDirName} archive it fell back to is missing too at ${emnapiArchive}. Upgrade emnapi to a version that ships the wasi-sdk 34 archives.`
|
|
1570
|
+
: needsWasiSdk34
|
|
1571
|
+
? `emnapi@${emnapiVersion} is missing the ${linkDirName} archive required by napi-rs at ${emnapiArchive}. wasi-sdk ${wasiSdkMajor} needs those archives, so upgrade emnapi to a version that ships them.`
|
|
1572
|
+
: `emnapi@${emnapiVersion} is missing the ${linkDirName} archive required by napi-rs at ${emnapiArchive}. Install emnapi v2 with support for this target.`,
|
|
1573
|
+
)
|
|
1574
|
+
}
|
|
1575
|
+
if (fellBackToLegacy) {
|
|
1576
|
+
debug.warn(
|
|
1577
|
+
`emnapi@${emnapiVersion} does not ship the ${EMNAPI_WASI_SDK_34_LINK_DIR} archives that wasi-sdk ${wasiSdkMajor} requires. Falling back to ${linkDirName}, which links the legacy wasi-libc futex ABI and may fail with \`wasm-ld: function signature mismatch\`. Upgrade emnapi to fix this.`,
|
|
1424
1578
|
)
|
|
1425
1579
|
}
|
|
1426
1580
|
this.envs.EMNAPI_LINK_DIR = emnapi
|
|
@@ -1449,7 +1603,6 @@ class Builder {
|
|
|
1449
1603
|
`emnapi version mismatch: emnapi@${emnapiVersion}, @emnapi/core@${emnapiCoreVersion}, @emnapi/runtime@${emnapiRuntimeVersion}. Please ensure all emnapi packages are the same version.`,
|
|
1450
1604
|
)
|
|
1451
1605
|
}
|
|
1452
|
-
const { WASI_SDK_PATH } = process.env
|
|
1453
1606
|
|
|
1454
1607
|
if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
|
|
1455
1608
|
this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(
|
|
@@ -2003,7 +2156,7 @@ class Builder {
|
|
|
2003
2156
|
noJsBinding: this.options.noJsBinding,
|
|
2004
2157
|
idents,
|
|
2005
2158
|
jsBinding: this.options.jsBinding,
|
|
2006
|
-
|
|
2159
|
+
format: this.options.format,
|
|
2007
2160
|
binaryName: this.config.binaryName,
|
|
2008
2161
|
packageName: this.options.jsPackageName ?? this.config.packageName,
|
|
2009
2162
|
version: process.env.npm_new_version ?? this.config.packageJson.version,
|
|
@@ -2352,7 +2505,9 @@ export interface WriteJsBindingOptions {
|
|
|
2352
2505
|
noJsBinding?: boolean
|
|
2353
2506
|
idents: string[]
|
|
2354
2507
|
jsBinding?: string
|
|
2508
|
+
format?: BuildFormat
|
|
2355
2509
|
esm?: boolean
|
|
2510
|
+
commonjs?: boolean
|
|
2356
2511
|
binaryName: string
|
|
2357
2512
|
packageName: string
|
|
2358
2513
|
version: string
|
|
@@ -2366,6 +2521,7 @@ export interface WriteJsBindingOptions {
|
|
|
2366
2521
|
wasiFlavors?: string[]
|
|
2367
2522
|
}
|
|
2368
2523
|
|
|
2524
|
+
/** Write a platform binding loader in the requested module format. */
|
|
2369
2525
|
export async function writeJsBinding(
|
|
2370
2526
|
options: WriteJsBindingOptions,
|
|
2371
2527
|
): Promise<Output | undefined> {
|
|
@@ -2389,7 +2545,8 @@ export async function writeJsBinding(
|
|
|
2389
2545
|
? localWasiName
|
|
2390
2546
|
: `./${localWasiName}`
|
|
2391
2547
|
|
|
2392
|
-
const createBinding =
|
|
2548
|
+
const createBinding =
|
|
2549
|
+
resolveBuildFormat(options) === 'esm' ? createEsmBinding : createCjsBinding
|
|
2393
2550
|
const binding = createBinding(
|
|
2394
2551
|
options.binaryName,
|
|
2395
2552
|
options.packageName,
|
|
@@ -2506,10 +2663,27 @@ export declare class ExternalObject<T> {
|
|
|
2506
2663
|
|
|
2507
2664
|
if (dts.indexOf('TypedArray') > -1) {
|
|
2508
2665
|
header += `
|
|
2509
|
-
export type TypedArray =
|
|
2666
|
+
export type TypedArray =
|
|
2667
|
+
| Int8Array
|
|
2668
|
+
| Uint8Array
|
|
2669
|
+
| Uint8ClampedArray
|
|
2670
|
+
| Int16Array
|
|
2671
|
+
| Uint16Array
|
|
2672
|
+
| Int32Array
|
|
2673
|
+
| Uint32Array
|
|
2674
|
+
| Float32Array
|
|
2675
|
+
| Float64Array
|
|
2676
|
+
| BigInt64Array
|
|
2677
|
+
| BigUint64Array
|
|
2510
2678
|
`
|
|
2511
2679
|
}
|
|
2512
2680
|
|
|
2681
|
+
if (header && !header.endsWith('\n')) {
|
|
2682
|
+
header += '\n'
|
|
2683
|
+
}
|
|
2684
|
+
if (header && !header.endsWith('\n\n')) {
|
|
2685
|
+
header += '\n'
|
|
2686
|
+
}
|
|
2513
2687
|
dts = header + dts
|
|
2514
2688
|
const dtsWithTypeImports = rewriteTypeImportReferences(
|
|
2515
2689
|
header + dtsWithTypeImportMarkers,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
pick,
|
|
29
29
|
resolvePackageReconciliationPaths,
|
|
30
30
|
restrictWasiNodeEngine,
|
|
31
|
+
serializeJson,
|
|
31
32
|
wasiLoaderSuffix,
|
|
32
33
|
wasiTargetHasThreads,
|
|
33
34
|
withFileSystemReconciliation,
|
|
@@ -574,7 +575,7 @@ async function createNpmDirsUnlocked(
|
|
|
574
575
|
|
|
575
576
|
const targetPackageJson = join(targetDir, 'package.json')
|
|
576
577
|
pendingWrites.push({
|
|
577
|
-
content:
|
|
578
|
+
content: serializeJson(scopedPackageJson),
|
|
578
579
|
destination: targetPackageJson,
|
|
579
580
|
})
|
|
580
581
|
if (wasmModuleTypeDef) {
|
package/src/api/new.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { homedir } from 'node:os'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { promises as fs } from 'node:fs'
|
|
6
6
|
|
|
7
|
-
import { parse as parseToml
|
|
8
|
-
import { load as yamlLoad
|
|
7
|
+
import { parse as parseToml } from '@std/toml'
|
|
8
|
+
import { load as yamlLoad } from 'js-yaml'
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
applyDefaultNewOptions,
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
AVAILABLE_TARGETS,
|
|
16
16
|
CLI_VERSION,
|
|
17
17
|
debugFactory,
|
|
18
|
+
serializeJson,
|
|
19
|
+
serializeToml,
|
|
20
|
+
serializeYaml,
|
|
18
21
|
DEFAULT_TARGETS,
|
|
19
22
|
getWasiTarget,
|
|
20
23
|
mkdirAsync,
|
|
@@ -347,7 +350,7 @@ async function filterTargetsInPackageJson(
|
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
|
|
350
|
-
await fs.writeFile(filePath,
|
|
353
|
+
await fs.writeFile(filePath, serializeJson(packageJson))
|
|
351
354
|
await updateGeneratedWasiAttributes(
|
|
352
355
|
path.join(path.dirname(filePath), '.gitattributes'),
|
|
353
356
|
packageJson.napi.binaryName,
|
|
@@ -381,10 +384,10 @@ async function updateGeneratedWasiAttributes(
|
|
|
381
384
|
generatedFiles.add(`${binaryName}.${suffix}-deferred.d.ts`)
|
|
382
385
|
}
|
|
383
386
|
}
|
|
387
|
+
while (lines[lines.length - 1] === '') {
|
|
388
|
+
lines.pop()
|
|
389
|
+
}
|
|
384
390
|
if (generatedFiles.size > 0) {
|
|
385
|
-
while (lines[lines.length - 1] === '') {
|
|
386
|
-
lines.pop()
|
|
387
|
-
}
|
|
388
391
|
lines.push(
|
|
389
392
|
'',
|
|
390
393
|
...[...generatedFiles].map((file) => `${file} linguist-detectable=false`),
|
|
@@ -429,7 +432,7 @@ async function updateCargoTomlTypeDef(
|
|
|
429
432
|
|
|
430
433
|
dependencies['napi-derive'] = dependencyConfig
|
|
431
434
|
|
|
432
|
-
await fs.writeFile(filePath,
|
|
435
|
+
await fs.writeFile(filePath, serializeToml(cargoToml))
|
|
433
436
|
}
|
|
434
437
|
|
|
435
438
|
export async function updateCargoTomlNodeApiVersion(
|
|
@@ -475,7 +478,7 @@ export async function updateCargoTomlNodeApiVersion(
|
|
|
475
478
|
|
|
476
479
|
dependencies.napi = dependencyConfig
|
|
477
480
|
|
|
478
|
-
await fs.writeFile(filePath,
|
|
481
|
+
await fs.writeFile(filePath, serializeToml(cargoToml))
|
|
479
482
|
}
|
|
480
483
|
|
|
481
484
|
async function filterTargetsInGithubActions(
|
|
@@ -683,12 +686,7 @@ async function filterTargetsInGithubActions(
|
|
|
683
686
|
}
|
|
684
687
|
|
|
685
688
|
// Write back the filtered YAML
|
|
686
|
-
|
|
687
|
-
lineWidth: -1,
|
|
688
|
-
noRefs: true,
|
|
689
|
-
sortKeys: false,
|
|
690
|
-
})
|
|
691
|
-
await fs.writeFile(filePath, updatedYaml)
|
|
689
|
+
await fs.writeFile(filePath, serializeYaml(yaml))
|
|
692
690
|
}
|
|
693
691
|
|
|
694
692
|
function processOptions(options: RawNewOptions) {
|
|
@@ -853,10 +851,7 @@ export async function newProject(userOptions: RawNewOptions) {
|
|
|
853
851
|
)
|
|
854
852
|
}
|
|
855
853
|
|
|
856
|
-
await fs.writeFile(
|
|
857
|
-
packageJsonPath,
|
|
858
|
-
JSON.stringify(pkgJson, null, 2) + '\n',
|
|
859
|
-
)
|
|
854
|
+
await fs.writeFile(packageJsonPath, serializeJson(pkgJson))
|
|
860
855
|
} catch (error) {
|
|
861
856
|
throw new Error(`Failed to create project: ${error}`)
|
|
862
857
|
}
|
package/src/api/pre-publish.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
withFileSystemReconciliation,
|
|
59
59
|
AVAILABLE_TARGETS,
|
|
60
60
|
parseTriple,
|
|
61
|
+
serializeJson,
|
|
61
62
|
type CommonPackageJsonFields,
|
|
62
63
|
type FileSystemTransactionWrite,
|
|
63
64
|
type RootPublisher,
|
|
@@ -2267,7 +2268,7 @@ async function registerPublicationWorkspaces(
|
|
|
2267
2268
|
} else {
|
|
2268
2269
|
manifest.workspaces = releaseWorkspaces
|
|
2269
2270
|
}
|
|
2270
|
-
await writeFileAtomic(manifestPath,
|
|
2271
|
+
await writeFileAtomic(manifestPath, serializeJson(manifest))
|
|
2271
2272
|
}
|
|
2272
2273
|
|
|
2273
2274
|
async function createPublicationExecutionPackage(
|
|
@@ -2391,10 +2392,7 @@ async function materializeRootReleasePlan(
|
|
|
2391
2392
|
}
|
|
2392
2393
|
}
|
|
2393
2394
|
syncThreadlessWasiRootFacadeManifest(updatedPackageJson, plan.packageJson)
|
|
2394
|
-
await writeFileAtomic(
|
|
2395
|
-
packageJsonPath,
|
|
2396
|
-
JSON.stringify(updatedPackageJson, null, 2),
|
|
2397
|
-
)
|
|
2395
|
+
await writeFileAtomic(packageJsonPath, serializeJson(updatedPackageJson))
|
|
2398
2396
|
|
|
2399
2397
|
const generatedFiles = new Set(plan.facade?.generatedFiles ?? [])
|
|
2400
2398
|
await Promise.all(
|
|
@@ -2492,7 +2490,7 @@ async function stageReleasePackage(
|
|
|
2492
2490
|
const packageJsonPath = join(stagedPkgDir, 'package.json')
|
|
2493
2491
|
const packageJson = JSON.parse(await readFileAsync(packageJsonPath, 'utf8'))
|
|
2494
2492
|
packageJson.version = packageVersion
|
|
2495
|
-
await writeFileAtomic(packageJsonPath,
|
|
2493
|
+
await writeFileAtomic(packageJsonPath, serializeJson(packageJson))
|
|
2496
2494
|
return {
|
|
2497
2495
|
pkgDir: options.pkgDir,
|
|
2498
2496
|
rootDir: options.rootDir,
|
|
@@ -2705,10 +2703,7 @@ async function validateReleasePackageContents({
|
|
|
2705
2703
|
if (updateManifest) {
|
|
2706
2704
|
packageFiles.splice(0, packageFiles.length, ...declarationClosure.files)
|
|
2707
2705
|
packageJson.files = packageFiles
|
|
2708
|
-
await writeFileAtomic(
|
|
2709
|
-
packageJsonPath,
|
|
2710
|
-
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
2711
|
-
)
|
|
2706
|
+
await writeFileAtomic(packageJsonPath, serializeJson(packageJson))
|
|
2712
2707
|
}
|
|
2713
2708
|
|
|
2714
2709
|
const publicFiles = new Set<string>(packageFiles)
|
package/src/api/rename.ts
CHANGED
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
sep,
|
|
13
13
|
} from 'node:path'
|
|
14
14
|
|
|
15
|
-
import { parse as parseToml
|
|
16
|
-
import { load as yamlParse
|
|
15
|
+
import { parse as parseToml } from '@std/toml'
|
|
16
|
+
import { load as yamlParse } from 'js-yaml'
|
|
17
17
|
import { isNil, omitBy, pick } from 'es-toolkit'
|
|
18
18
|
|
|
19
19
|
import { applyDefaultRenameOptions, type RenameOptions } from '../def/rename.js'
|
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
readFileAsync,
|
|
24
24
|
resolvePackageReconciliationPaths,
|
|
25
25
|
type Target,
|
|
26
|
+
serializeJson,
|
|
27
|
+
serializeToml,
|
|
28
|
+
serializeYaml,
|
|
26
29
|
wasiLoaderSuffix,
|
|
27
30
|
wasiTargetHasThreads,
|
|
28
31
|
withPackageFileSystemReconciliation,
|
|
@@ -1265,10 +1268,6 @@ function updateNapiConfigRecord(record: JsonRecord, options: RenameOptions) {
|
|
|
1265
1268
|
}
|
|
1266
1269
|
}
|
|
1267
1270
|
|
|
1268
|
-
function serializeJsonLike(content: string, value: unknown) {
|
|
1269
|
-
return `${JSON.stringify(value, null, 2)}${content.endsWith('\n') ? '\n' : ''}`
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
1271
|
function sanitizeCargoPackageName(binaryName: string) {
|
|
1273
1272
|
return binaryName.replace(/[^A-Za-z0-9_]/g, '_').toLowerCase()
|
|
1274
1273
|
}
|
|
@@ -1458,8 +1457,7 @@ async function renameProjectUnlocked(
|
|
|
1458
1457
|
const plan = new RenameTransactionPlan()
|
|
1459
1458
|
plan.addWrite(
|
|
1460
1459
|
packageJsonPath,
|
|
1461
|
-
|
|
1462
|
-
packageJsonContent,
|
|
1460
|
+
serializeJson(
|
|
1463
1461
|
rewritePackageManifest(
|
|
1464
1462
|
packageJsonData,
|
|
1465
1463
|
managedWasiRenames,
|
|
@@ -1477,11 +1475,7 @@ async function renameProjectUnlocked(
|
|
|
1477
1475
|
throw new Error(`NAPI config must contain a JSON object: ${configPath}`)
|
|
1478
1476
|
}
|
|
1479
1477
|
updateNapiConfigRecord(configData, options)
|
|
1480
|
-
plan.addWrite(
|
|
1481
|
-
configPath,
|
|
1482
|
-
serializeJsonLike(configContent, configData),
|
|
1483
|
-
configMode,
|
|
1484
|
-
)
|
|
1478
|
+
plan.addWrite(configPath, serializeJson(configData), configMode)
|
|
1485
1479
|
}
|
|
1486
1480
|
|
|
1487
1481
|
if (binaryNameChanged) {
|
|
@@ -1495,7 +1489,7 @@ async function renameProjectUnlocked(
|
|
|
1495
1489
|
const cargoToml = parseToml(tomlContent) as any
|
|
1496
1490
|
if (cargoToml.package) {
|
|
1497
1491
|
cargoToml.package.name = sanitizeCargoPackageName(newName)
|
|
1498
|
-
plan.addWrite(cargoTomlPath,
|
|
1492
|
+
plan.addWrite(cargoTomlPath, serializeToml(cargoToml), cargoTomlMode)
|
|
1499
1493
|
}
|
|
1500
1494
|
|
|
1501
1495
|
const workflowPath = join(projectRoot, '.github', 'workflows', 'CI.yml')
|
|
@@ -1515,11 +1509,7 @@ async function renameProjectUnlocked(
|
|
|
1515
1509
|
workflowData.env.APP_NAME = newName
|
|
1516
1510
|
plan.addWrite(
|
|
1517
1511
|
canonicalWorkflowPath,
|
|
1518
|
-
|
|
1519
|
-
lineWidth: -1,
|
|
1520
|
-
noRefs: true,
|
|
1521
|
-
sortKeys: false,
|
|
1522
|
-
}),
|
|
1512
|
+
serializeYaml(workflowData),
|
|
1523
1513
|
workflowMode,
|
|
1524
1514
|
)
|
|
1525
1515
|
}
|
|
@@ -1567,8 +1557,7 @@ async function renameProjectUnlocked(
|
|
|
1567
1557
|
`Managed package manifest must contain a JSON object: ${targetPackageJsonPath}`,
|
|
1568
1558
|
)
|
|
1569
1559
|
}
|
|
1570
|
-
const updatedContent =
|
|
1571
|
-
content,
|
|
1560
|
+
const updatedContent = serializeJson(
|
|
1572
1561
|
rewritePackageManifest(
|
|
1573
1562
|
manifest,
|
|
1574
1563
|
managedWasiRenames,
|
|
@@ -43,11 +43,11 @@ function createWasiFallbackChain(
|
|
|
43
43
|
specifier,
|
|
44
44
|
isPackage,
|
|
45
45
|
localArtifacts,
|
|
46
|
-
}) => ` if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === ${
|
|
46
|
+
}) => ` if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === '${flavor}')) {
|
|
47
47
|
let candidateError = null
|
|
48
48
|
let candidateFailed = false
|
|
49
49
|
try {
|
|
50
|
-
candidateError = __napiWasiResolveCandidate('${specifier}', ${isPackage}, ${
|
|
50
|
+
candidateError = __napiWasiResolveCandidate('${specifier}', ${isPackage}, ${localArtifacts ? `[${localArtifacts.map((artifact) => `'${artifact}'`).join(', ')}]` : 'undefined'})
|
|
51
51
|
candidateFailed = candidateError !== null
|
|
52
52
|
if (!candidateFailed) {${
|
|
53
53
|
isPackage && packageVersion
|
|
@@ -157,7 +157,6 @@ ${idents.map((ident) => `export { ${ident} }`).join('\n')}`
|
|
|
157
157
|
return `${bindingHeader}
|
|
158
158
|
import { createRequire } from 'module'
|
|
159
159
|
const require = createRequire(import.meta.url)
|
|
160
|
-
const __dirname = new URL('.', import.meta.url).pathname
|
|
161
160
|
|
|
162
161
|
${createCommonBinding(
|
|
163
162
|
localName,
|
|
@@ -170,8 +169,7 @@ ${exportsCode}
|
|
|
170
169
|
`
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
const bindingHeader =
|
|
174
|
-
/* eslint-disable */
|
|
172
|
+
const bindingHeader = `/* eslint-disable */
|
|
175
173
|
// @ts-nocheck
|
|
176
174
|
/* auto-generated by NAPI-RS */
|
|
177
175
|
`
|
|
@@ -273,7 +271,7 @@ const isMuslFromChildProcess = () => {
|
|
|
273
271
|
function requireNative() {
|
|
274
272
|
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
|
275
273
|
try {
|
|
276
|
-
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
|
274
|
+
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
|
277
275
|
} catch (err) {
|
|
278
276
|
loadErrors.push(err)
|
|
279
277
|
}
|
|
@@ -288,9 +286,9 @@ function requireNative() {
|
|
|
288
286
|
} else if (process.platform === 'win32') {
|
|
289
287
|
if (process.arch === 'x64') {
|
|
290
288
|
if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) {
|
|
291
|
-
${requireTuple('win32-x64-gnu')}
|
|
289
|
+
${requireTuple('win32-x64-gnu', 10)}
|
|
292
290
|
} else {
|
|
293
|
-
${requireTuple('win32-x64-msvc')}
|
|
291
|
+
${requireTuple('win32-x64-msvc', 10)}
|
|
294
292
|
}
|
|
295
293
|
} else if (process.arch === 'ia32') {
|
|
296
294
|
${requireTuple('win32-ia32-msvc')}
|
|
@@ -396,7 +394,7 @@ function createLoadErrorChain(errors) {
|
|
|
396
394
|
//
|
|
397
395
|
// NAPI_RS_WASI_FLAVOR selects one exact generated flavor and implies strict
|
|
398
396
|
// WASI loading. It never crosses into another flavor or falls back to native.
|
|
399
|
-
const __napiWasiFlavors = ${
|
|
397
|
+
const __napiWasiFlavors = [${flavors.map((flavor) => `'${flavor}'`).join(', ')}]
|
|
400
398
|
const __napiWasiFlavor = process.env.NAPI_RS_WASI_FLAVOR
|
|
401
399
|
const __napiWasiFlavorRequested =
|
|
402
400
|
typeof __napiWasiFlavor === 'string' && __napiWasiFlavor.length > 0
|