@napi-rs/cli 3.9.1 → 3.10.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 +17 -4
- package/dist/cli.js +1594 -180
- package/dist/index.cjs +1594 -180
- package/dist/index.d.cts +69 -2
- package/dist/index.d.ts +69 -2
- package/dist/index.js +1594 -180
- package/docs/wasi.md +335 -4
- package/package.json +3 -3
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +4361 -15
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build-regressions.spec.ts +210 -3
- package/src/api/__tests__/build.spec.ts +2139 -3
- package/src/api/__tests__/create-npm-dirs.spec.ts +101 -0
- package/src/api/__tests__/pre-publish.spec.ts +153 -1
- package/src/api/__tests__/templates.spec.ts +1352 -0
- package/src/api/build.ts +637 -31
- package/src/api/create-npm-dirs.ts +21 -9
- package/src/api/pre-publish.ts +29 -1
- package/src/api/templates/binding-target.ts +176 -0
- package/src/api/templates/index.ts +1 -0
- package/src/api/templates/js-binding.ts +48 -3
- package/src/api/templates/load-wasi-template.ts +747 -45
- package/src/utils/__tests__/reconciliation.spec.ts +683 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/misc.ts +347 -74
- package/src/utils/typegen.ts +367 -12
|
@@ -497,6 +497,107 @@ test.serial(
|
|
|
497
497
|
},
|
|
498
498
|
)
|
|
499
499
|
|
|
500
|
+
test.serial(
|
|
501
|
+
'should declare @napi-rs/async-runtime when napi.wasm.asyncRuntime is enabled',
|
|
502
|
+
async (t) => {
|
|
503
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
504
|
+
const registryServer = await startRegistryServer({
|
|
505
|
+
'dist-tags': {
|
|
506
|
+
latest: '0.1.0',
|
|
507
|
+
},
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
process.env.npm_config_registry = `${registryServer.origin}/npm`
|
|
511
|
+
|
|
512
|
+
const packageJson = {
|
|
513
|
+
name: 'test-async-runtime-dep',
|
|
514
|
+
version: '1.0.0',
|
|
515
|
+
napi: {
|
|
516
|
+
binaryName: 'test-async-runtime-dep',
|
|
517
|
+
targets: ['wasm32-wasip1', 'wasm32-wasip1-threads'],
|
|
518
|
+
wasm: {
|
|
519
|
+
asyncRuntime: true,
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
await createNpmDirs({
|
|
528
|
+
cwd: tmpDir,
|
|
529
|
+
packageJsonPath: 'package.json',
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
for (const packageDir of ['wasm32-wasip1', 'wasm32-wasi']) {
|
|
533
|
+
const scopedPackageJson = JSON.parse(
|
|
534
|
+
await readFile(
|
|
535
|
+
join(tmpDir, 'npm', packageDir, 'package.json'),
|
|
536
|
+
'utf-8',
|
|
537
|
+
),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
t.is(scopedPackageJson.dependencies['@napi-rs/async-runtime'], '^0.1.0')
|
|
541
|
+
}
|
|
542
|
+
t.deepEqual(registryServer.requests.sort(), [
|
|
543
|
+
'/npm/@napi-rs/async-runtime',
|
|
544
|
+
'/npm/@napi-rs/wasm-runtime',
|
|
545
|
+
])
|
|
546
|
+
} finally {
|
|
547
|
+
await registryServer.close()
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
test.serial(
|
|
553
|
+
'should not resolve @napi-rs/async-runtime when the flag is unset',
|
|
554
|
+
async (t) => {
|
|
555
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
556
|
+
const registryServer = await startRegistryServer({
|
|
557
|
+
'dist-tags': {
|
|
558
|
+
latest: '0.1.0',
|
|
559
|
+
},
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
process.env.npm_config_registry = `${registryServer.origin}/npm`
|
|
563
|
+
|
|
564
|
+
const packageJson = {
|
|
565
|
+
name: 'test-async-runtime-dep-off',
|
|
566
|
+
version: '1.0.0',
|
|
567
|
+
napi: {
|
|
568
|
+
binaryName: 'test-async-runtime-dep-off',
|
|
569
|
+
targets: ['wasm32-wasip1', 'wasm32-wasip1-threads'],
|
|
570
|
+
},
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
await createNpmDirs({
|
|
577
|
+
cwd: tmpDir,
|
|
578
|
+
packageJsonPath: 'package.json',
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
for (const packageDir of ['wasm32-wasip1', 'wasm32-wasi']) {
|
|
582
|
+
const scopedPackageJson = JSON.parse(
|
|
583
|
+
await readFile(
|
|
584
|
+
join(tmpDir, 'npm', packageDir, 'package.json'),
|
|
585
|
+
'utf-8',
|
|
586
|
+
),
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
t.is(
|
|
590
|
+
scopedPackageJson.dependencies['@napi-rs/async-runtime'],
|
|
591
|
+
undefined,
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
t.deepEqual(registryServer.requests, ['/npm/@napi-rs/wasm-runtime'])
|
|
595
|
+
} finally {
|
|
596
|
+
await registryServer.close()
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
)
|
|
600
|
+
|
|
500
601
|
test.serial(
|
|
501
602
|
'should reject an empty latest dist-tag when resolving wasm runtime metadata',
|
|
502
603
|
async (t) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdtempSync } from 'node:fs'
|
|
2
|
-
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
3
4
|
import { tmpdir } from 'node:os'
|
|
4
5
|
import { dirname, join } from 'node:path'
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
resolveRootPublisher,
|
|
13
14
|
rootPublisherRewritesRootExports,
|
|
14
15
|
sniffRewritingPublisherFromUserAgent,
|
|
16
|
+
validateReleasePackageContents,
|
|
15
17
|
validateRootFacadePacklist,
|
|
16
18
|
} from '../pre-publish.js'
|
|
17
19
|
import { parseTriple } from '../../utils/index.js'
|
|
@@ -558,3 +560,153 @@ test('still rejects publishConfig.exports references omitted by npm pack', async
|
|
|
558
560
|
)
|
|
559
561
|
}
|
|
560
562
|
})
|
|
563
|
+
|
|
564
|
+
// The generated WASI loaders only reach `@napi-rs/async-runtime` when
|
|
565
|
+
// `napi.wasm.asyncRuntime` is on, and `create-npm-dirs` only declares it in the
|
|
566
|
+
// same case. A project that flips the flag without rerunning `create-npm-dirs`
|
|
567
|
+
// therefore publishes a loader importing a package its own manifest never
|
|
568
|
+
// declares, which `npm install` never fetches: the addon then fails at load on
|
|
569
|
+
// every consumer. `pre-publish` has to catch that pair before it ships.
|
|
570
|
+
const requireFromSpec = createRequire(import.meta.url)
|
|
571
|
+
const EMNAPI_VERSION = requireFromSpec('emnapi/package.json').version as string
|
|
572
|
+
const BINARY_NAME = 'pkg'
|
|
573
|
+
const WASI_PLATFORM_ARCH_ABI = 'wasm32-wasi'
|
|
574
|
+
const ASYNC_RUNTIME_PACKAGE = '@napi-rs/async-runtime'
|
|
575
|
+
|
|
576
|
+
interface WasiReleasePackageOptions {
|
|
577
|
+
loadersImportAsyncRuntime: boolean
|
|
578
|
+
declareAsyncRuntime: boolean
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function createWasiReleasePackage({
|
|
582
|
+
loadersImportAsyncRuntime,
|
|
583
|
+
declareAsyncRuntime,
|
|
584
|
+
}: WasiReleasePackageOptions) {
|
|
585
|
+
const rootDir = mkdtempSync(join(tmpdir(), 'napi-wasi-release-spec-'))
|
|
586
|
+
const pkgDir = join(rootDir, 'npm', 'wasm32-wasi')
|
|
587
|
+
await mkdir(pkgDir, { recursive: true })
|
|
588
|
+
|
|
589
|
+
const artifact = `${BINARY_NAME}.${WASI_PLATFORM_ARCH_ABI}.wasm`
|
|
590
|
+
const main = `${BINARY_NAME}.wasi.cjs`
|
|
591
|
+
const types = `${BINARY_NAME}.wasi.d.cts`
|
|
592
|
+
const browser = `${BINARY_NAME}.wasi-browser.js`
|
|
593
|
+
const files = [
|
|
594
|
+
artifact,
|
|
595
|
+
main,
|
|
596
|
+
types,
|
|
597
|
+
browser,
|
|
598
|
+
'wasi-worker.mjs',
|
|
599
|
+
'wasi-worker-browser.mjs',
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
const cjsLoader = `const { instantiateNapiModuleSync } = require('@napi-rs/wasm-runtime')
|
|
603
|
+
${
|
|
604
|
+
loadersImportAsyncRuntime
|
|
605
|
+
? `const {
|
|
606
|
+
installCurrentThreadHosts: __installCurrentThreadHosts,
|
|
607
|
+
} = require('${ASYNC_RUNTIME_PACKAGE}')
|
|
608
|
+
`
|
|
609
|
+
: ''
|
|
610
|
+
}module.exports = { instantiateNapiModuleSync }
|
|
611
|
+
`
|
|
612
|
+
const browserLoader = `import { instantiateNapiModule } from '@napi-rs/wasm-runtime'
|
|
613
|
+
${
|
|
614
|
+
loadersImportAsyncRuntime
|
|
615
|
+
? `import { installCurrentThreadHosts as __installCurrentThreadHosts } from '${ASYNC_RUNTIME_PACKAGE}'
|
|
616
|
+
`
|
|
617
|
+
: ''
|
|
618
|
+
}export default instantiateNapiModule
|
|
619
|
+
`
|
|
620
|
+
|
|
621
|
+
await writeFile(join(pkgDir, artifact), '\0asm')
|
|
622
|
+
await writeFile(join(pkgDir, main), cjsLoader)
|
|
623
|
+
await writeFile(join(pkgDir, types), 'export declare function noop(): void\n')
|
|
624
|
+
await writeFile(join(pkgDir, browser), browserLoader)
|
|
625
|
+
await writeFile(join(pkgDir, 'wasi-worker.mjs'), '// worker\n')
|
|
626
|
+
await writeFile(join(pkgDir, 'wasi-worker-browser.mjs'), '// worker\n')
|
|
627
|
+
|
|
628
|
+
await writeFile(
|
|
629
|
+
join(pkgDir, 'package.json'),
|
|
630
|
+
JSON.stringify({
|
|
631
|
+
name: `${PACKAGE_NAME}-${WASI_PLATFORM_ARCH_ABI}`,
|
|
632
|
+
version: VERSION,
|
|
633
|
+
type: 'module',
|
|
634
|
+
main,
|
|
635
|
+
types,
|
|
636
|
+
browser,
|
|
637
|
+
files,
|
|
638
|
+
dependencies: {
|
|
639
|
+
'@napi-rs/wasm-runtime': '^1.0.0',
|
|
640
|
+
'@emnapi/core': EMNAPI_VERSION,
|
|
641
|
+
'@emnapi/runtime': EMNAPI_VERSION,
|
|
642
|
+
...(declareAsyncRuntime ? { [ASYNC_RUNTIME_PACKAGE]: '^0.1.0' } : {}),
|
|
643
|
+
},
|
|
644
|
+
}),
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
return { rootDir, pkgDir }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function validateWasiReleasePackage(pkgDir: string, rootDir: string) {
|
|
651
|
+
return validateReleasePackageContents({
|
|
652
|
+
pkgDir,
|
|
653
|
+
rootDir,
|
|
654
|
+
packageName: PACKAGE_NAME,
|
|
655
|
+
binaryName: BINARY_NAME,
|
|
656
|
+
target: WASI_TARGET,
|
|
657
|
+
requireDirectBufferDependency: false,
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
test('rejects a WASI release package whose loaders import @napi-rs/async-runtime without declaring it', async (t) => {
|
|
662
|
+
const { rootDir, pkgDir } = await createWasiReleasePackage({
|
|
663
|
+
loadersImportAsyncRuntime: true,
|
|
664
|
+
declareAsyncRuntime: false,
|
|
665
|
+
})
|
|
666
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
667
|
+
|
|
668
|
+
await t.throwsAsync(() => validateWasiReleasePackage(pkgDir, rootDir), {
|
|
669
|
+
message: `Release package ${PACKAGE_NAME}-${WASI_PLATFORM_ARCH_ABI} must declare dependency ${ASYNC_RUNTIME_PACKAGE}`,
|
|
670
|
+
})
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
test('accepts a WASI release package that declares the @napi-rs/async-runtime its loaders import', async (t) => {
|
|
674
|
+
const { rootDir, pkgDir } = await createWasiReleasePackage({
|
|
675
|
+
loadersImportAsyncRuntime: true,
|
|
676
|
+
declareAsyncRuntime: true,
|
|
677
|
+
})
|
|
678
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
679
|
+
|
|
680
|
+
await t.notThrowsAsync(() => validateWasiReleasePackage(pkgDir, rootDir))
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
test('rejects a declared @napi-rs/async-runtime that no WASI loader imports', async (t) => {
|
|
684
|
+
// Mirrors the `buffer` precedent: the dependency is required when a loader
|
|
685
|
+
// imports it and forbidden when none does, so a flavor never carries an
|
|
686
|
+
// install every consumer pays for and nothing loads.
|
|
687
|
+
const { rootDir, pkgDir } = await createWasiReleasePackage({
|
|
688
|
+
loadersImportAsyncRuntime: false,
|
|
689
|
+
declareAsyncRuntime: true,
|
|
690
|
+
})
|
|
691
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
692
|
+
|
|
693
|
+
await t.throwsAsync(() => validateWasiReleasePackage(pkgDir, rootDir), {
|
|
694
|
+
message: `Release package ${PACKAGE_NAME}-${WASI_PLATFORM_ARCH_ABI} must omit ${ASYNC_RUNTIME_PACKAGE} when its loaders do not import it`,
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
test('rejects an invalid @napi-rs/async-runtime range', async (t) => {
|
|
699
|
+
const { rootDir, pkgDir } = await createWasiReleasePackage({
|
|
700
|
+
loadersImportAsyncRuntime: true,
|
|
701
|
+
declareAsyncRuntime: true,
|
|
702
|
+
})
|
|
703
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
704
|
+
const packageJsonPath = join(pkgDir, 'package.json')
|
|
705
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
706
|
+
packageJson.dependencies[ASYNC_RUNTIME_PACKAGE] = 'not a range'
|
|
707
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson))
|
|
708
|
+
|
|
709
|
+
await t.throwsAsync(() => validateWasiReleasePackage(pkgDir, rootDir), {
|
|
710
|
+
message: `Release package ${PACKAGE_NAME}-${WASI_PLATFORM_ARCH_ABI} has invalid ${ASYNC_RUNTIME_PACKAGE} dependency not a range`,
|
|
711
|
+
})
|
|
712
|
+
})
|