@napi-rs/cli 3.7.2 → 3.7.4

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.
Files changed (48) hide show
  1. package/dist/cli.js +240 -64
  2. package/dist/index.cjs +239 -63
  3. package/dist/index.d.cts +5 -5
  4. package/dist/index.d.ts +5 -5
  5. package/dist/index.js +240 -64
  6. package/package.json +3 -3
  7. package/src/api/__tests__/__snapshots__/templates.spec.ts.md +353 -0
  8. package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
  9. package/src/api/__tests__/android-build.spec.ts +113 -0
  10. package/src/api/__tests__/artifacts.spec.ts +95 -0
  11. package/src/api/__tests__/build-regressions.spec.ts +128 -0
  12. package/src/api/__tests__/build.spec.ts +1020 -0
  13. package/src/api/__tests__/create-npm-dirs.spec.ts +561 -0
  14. package/src/api/__tests__/new-node-api-version.spec.ts +87 -0
  15. package/src/api/__tests__/new.spec.ts +527 -0
  16. package/src/api/__tests__/rename.spec.ts +181 -0
  17. package/src/api/__tests__/templates.spec.ts +164 -0
  18. package/src/api/__tests__/universalize.spec.ts +128 -0
  19. package/src/api/artifacts.ts +7 -16
  20. package/src/api/build.ts +354 -111
  21. package/src/api/new.ts +65 -1
  22. package/src/commands/__tests__/new.spec.ts +13 -0
  23. package/src/def/build.ts +6 -6
  24. package/src/utils/__tests__/__fixtures__/napi_type_def +202 -0
  25. package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
  26. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
  27. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
  28. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
  29. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
  30. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
  31. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
  32. package/src/utils/__tests__/__fixtures__/runtime_string_enum_flag +2 -0
  33. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  34. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  35. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +180 -0
  36. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  37. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +1197 -0
  38. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  39. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +22 -0
  40. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  41. package/src/utils/__tests__/config.spec.ts +81 -0
  42. package/src/utils/__tests__/metadata.spec.ts +69 -0
  43. package/src/utils/__tests__/misc.spec.ts +56 -0
  44. package/src/utils/__tests__/target.spec.ts +19 -0
  45. package/src/utils/__tests__/typegen.spec.ts +85 -0
  46. package/src/utils/__tests__/version.spec.ts +7 -0
  47. package/src/utils/metadata.ts +94 -3
  48. package/src/utils/version.ts +2 -0
@@ -0,0 +1,22 @@
1
+ # Snapshot report for `src/utils/__tests__/version.spec.ts`
2
+
3
+ The actual snapshot is saved in `version.spec.ts.snap`.
4
+
5
+ Generated by [AVA](https://avajs.dev).
6
+
7
+ ## should generate correct napi engine requirement
8
+
9
+ > Snapshot 1
10
+
11
+ [
12
+ '>= 8.6.0 < 9 || >= 9.0.0 < 10 || >= 10.0.0',
13
+ '>= 8.10.0 < 9 || >= 9.3.0 < 10 || >= 10.0.0',
14
+ '>= 6.14.2 < 7 || >= 8.11.2 < 9 || >= 9.11.0 < 10 || >= 10.0.0',
15
+ '>= 10.16.0 < 11 || >= 11.8.0 < 12 || >= 12.0.0',
16
+ '>= 10.17.0 < 11 || >= 12.11.0 < 13 || >= 13.0.0',
17
+ '>= 10.20.0 < 11 || >= 12.17.0 < 13 || >= 14.0.0',
18
+ '>= 10.23.0 < 11 || >= 12.19.0 < 13 || >= 14.12.0 < 15 || >= 15.0.0',
19
+ '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0',
20
+ '>= 18.17.0 < 19 || >= 20.3.0 < 21 || >= 21.1.0',
21
+ '>= 22.14.0 < 23 || >= 23.6.0',
22
+ ]
@@ -0,0 +1,81 @@
1
+ import { unlink, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ import ava, { type TestFn } from 'ava'
6
+
7
+ import {
8
+ type CommonPackageJsonFields,
9
+ type UserNapiConfig,
10
+ readNapiConfig,
11
+ } from '../config.js'
12
+
13
+ const NON_EXISTENT_FILE = 'non-existent-file'
14
+
15
+ const test = ava as TestFn<{
16
+ configPath: string
17
+ packageJson: string
18
+ pkgJson: CommonPackageJsonFields
19
+ config: UserNapiConfig
20
+ }>
21
+
22
+ test.before(async (t) => {
23
+ const tmp = tmpdir()
24
+ const configPath = join(tmp, 'napi.json')
25
+ const packageJson = join(tmp, 'package.json')
26
+ const pkgJson = {
27
+ name: '@napi-rs/testing',
28
+ version: '0.0.0',
29
+ napi: {
30
+ binaryName: 'testing',
31
+ packageName: '@napi-rs/testing',
32
+ targets: [
33
+ 'x86_64-unknown-linux-gnu',
34
+ 'x86_64-pc-windows-msvc',
35
+ 'x86_64-apple-darwin',
36
+ ],
37
+ },
38
+ }
39
+ await writeFile(packageJson, JSON.stringify(pkgJson, null, 2))
40
+ const config = {
41
+ binaryName: 'testing',
42
+ packageName: '@node-rs/testing',
43
+ targets: [
44
+ 'x86_64-unknown-linux-gnu',
45
+ 'x86_64-apple-darwin',
46
+ 'aarch64-apple-darwin',
47
+ ],
48
+ }
49
+ await writeFile(configPath, JSON.stringify(config, null, 2))
50
+ t.context = { configPath, config, packageJson, pkgJson }
51
+ })
52
+
53
+ test.after(async (t) => {
54
+ await unlink(t.context.configPath)
55
+ await unlink(t.context.packageJson)
56
+ })
57
+
58
+ test('should throw if package.json not found', async (t) => {
59
+ await t.throwsAsync(() => readNapiConfig(NON_EXISTENT_FILE), {
60
+ message: `package.json not found at ${NON_EXISTENT_FILE}`,
61
+ })
62
+ })
63
+
64
+ test('should throw if napi.json not found', async (t) => {
65
+ const { packageJson } = t.context
66
+ await t.throwsAsync(() => readNapiConfig(packageJson, NON_EXISTENT_FILE), {
67
+ message: `NAPI-RS config not found at ${NON_EXISTENT_FILE}`,
68
+ })
69
+ })
70
+
71
+ test('should be able to read config from package.json', async (t) => {
72
+ const { packageJson } = t.context
73
+ const config = await readNapiConfig(packageJson)
74
+ t.snapshot(config)
75
+ })
76
+
77
+ test('should be able to read config from napi.json', async (t) => {
78
+ const { packageJson, configPath } = t.context
79
+ const config = await readNapiConfig(packageJson, configPath)
80
+ t.snapshot(config)
81
+ })
@@ -0,0 +1,69 @@
1
+ import { unlink, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ import test from 'ava'
7
+
8
+ import { getNapiDeriveDependentCrates, parseMetadata } from '../metadata.js'
9
+
10
+ const FIXTURE_WORKSPACE = join(
11
+ dirname(fileURLToPath(import.meta.url)),
12
+ '__fixtures__',
13
+ 'optional-napi-derive',
14
+ )
15
+
16
+ test('should surface child process startup errors', async (t) => {
17
+ const manifestPath = join(
18
+ tmpdir(),
19
+ `napi-rs-metadata-${process.pid}-${Date.now()}.toml`,
20
+ )
21
+ const originalPath = process.env.PATH
22
+
23
+ await writeFile(manifestPath, '[package]\nname = "test"\nversion = "0.0.0"\n')
24
+ process.env.PATH = ''
25
+
26
+ try {
27
+ const error = await t.throwsAsync(() => parseMetadata(manifestPath))
28
+ t.truthy(error)
29
+ t.is(error?.message, 'cargo metadata failed to run')
30
+ t.is((error?.cause as NodeJS.ErrnoException | undefined)?.code, 'ENOENT')
31
+ } finally {
32
+ process.env.PATH = originalPath
33
+ await unlink(manifestPath)
34
+ }
35
+ })
36
+
37
+ test('should exclude crates whose optional napi-derive dependency is disabled', async (t) => {
38
+ const metadata = await parseMetadata(
39
+ join(FIXTURE_WORKSPACE, 'main-crate', 'Cargo.toml'),
40
+ )
41
+ const dependentCrates = getNapiDeriveDependentCrates(metadata).map(
42
+ (crate) => crate.name,
43
+ )
44
+ t.deepEqual(dependentCrates, ['main-crate'])
45
+ })
46
+
47
+ test('should include crates whose optional napi-derive dependency is enabled via build features', async (t) => {
48
+ const metadata = await parseMetadata(
49
+ join(FIXTURE_WORKSPACE, 'with-optional-derive', 'Cargo.toml'),
50
+ {
51
+ features: ['node'],
52
+ },
53
+ )
54
+ const dependentCrates = getNapiDeriveDependentCrates(metadata).map(
55
+ (crate) => crate.name,
56
+ )
57
+ t.true(dependentCrates.includes('with-optional-derive'))
58
+ })
59
+
60
+ test('should fall back to declared dependencies when the resolve graph is unavailable', async (t) => {
61
+ const metadata = await parseMetadata(
62
+ join(FIXTURE_WORKSPACE, 'main-crate', 'Cargo.toml'),
63
+ )
64
+ metadata.resolve = null
65
+ const dependentCrates = getNapiDeriveDependentCrates(metadata)
66
+ .map((crate) => crate.name)
67
+ .sort()
68
+ t.deepEqual(dependentCrates, ['main-crate', 'with-optional-derive'])
69
+ })
@@ -0,0 +1,56 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+
6
+ import ava, { type TestFn } from 'ava'
7
+
8
+ import { updatePackageJson } from '../misc.js'
9
+
10
+ const test = ava as TestFn<{
11
+ tmpDir: string
12
+ }>
13
+
14
+ test.beforeEach(async (t) => {
15
+ t.context = {
16
+ tmpDir: await mkdtemp(join(tmpdir(), 'napi-rs-misc-spec-')),
17
+ }
18
+ })
19
+
20
+ test.afterEach.always(async (t) => {
21
+ if (existsSync(t.context.tmpDir)) {
22
+ await rm(t.context.tmpDir, { recursive: true, force: true })
23
+ }
24
+ })
25
+
26
+ test('updatePackageJson merges nested objects instead of overwriting them', async (t) => {
27
+ const packageJsonPath = join(t.context.tmpDir, 'package.json')
28
+
29
+ await writeFile(
30
+ packageJsonPath,
31
+ JSON.stringify(
32
+ {
33
+ name: 'fixture',
34
+ version: '1.0.0',
35
+ optionalDependencies: {
36
+ fsevents: '^2.3.3',
37
+ },
38
+ },
39
+ null,
40
+ 2,
41
+ ),
42
+ )
43
+
44
+ await updatePackageJson(packageJsonPath, {
45
+ optionalDependencies: {
46
+ '@napi-rs/fixture-darwin-arm64': '1.0.1',
47
+ },
48
+ })
49
+
50
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
51
+
52
+ t.deepEqual(packageJson.optionalDependencies, {
53
+ fsevents: '^2.3.3',
54
+ '@napi-rs/fixture-darwin-arm64': '1.0.1',
55
+ })
56
+ })
@@ -0,0 +1,19 @@
1
+ import os from 'os'
2
+
3
+ import test from 'ava'
4
+
5
+ import {
6
+ parseTriple,
7
+ getSystemDefaultTarget,
8
+ AVAILABLE_TARGETS,
9
+ } from '../target.js'
10
+
11
+ test('should parse triple correctly', (t) => {
12
+ t.snapshot(AVAILABLE_TARGETS.map(parseTriple))
13
+ })
14
+
15
+ test('should get system default target correctly', (t) => {
16
+ const target = getSystemDefaultTarget()
17
+
18
+ t.is(target.platform, os.platform())
19
+ })
@@ -0,0 +1,85 @@
1
+ import { join } from 'path'
2
+ import { fileURLToPath } from 'url'
3
+
4
+ import test from 'ava'
5
+
6
+ import { correctStringIdent, processTypeDef } from '../typegen.js'
7
+
8
+ test('should ident string correctly', (t) => {
9
+ const input = `
10
+ /**
11
+ * should keep
12
+ * class A {
13
+ * foo = () => {}
14
+ * bar = () => {}
15
+ * }
16
+ */
17
+ class A {
18
+ foo() {
19
+ a = b
20
+ }
21
+
22
+ bar = () => {
23
+
24
+ }
25
+ boz = 1
26
+ }
27
+
28
+ namespace B {
29
+ namespace C {
30
+ type D = A
31
+ }
32
+ }
33
+ `
34
+ t.snapshot(correctStringIdent(input, 0), 'original ident is 0')
35
+ t.snapshot(correctStringIdent(input, 2), 'original ident is 2')
36
+ })
37
+
38
+ test('should process type def correctly', async (t) => {
39
+ const { dts } = await processTypeDef(
40
+ join(
41
+ fileURLToPath(import.meta.url),
42
+ '../',
43
+ '__fixtures__',
44
+ 'napi_type_def',
45
+ ),
46
+ true,
47
+ )
48
+
49
+ t.snapshot(dts)
50
+ })
51
+
52
+ test('should process type def with noConstEnum correctly', async (t) => {
53
+ const { dts } = await processTypeDef(
54
+ join(
55
+ fileURLToPath(import.meta.url),
56
+ '../',
57
+ '__fixtures__',
58
+ 'napi_type_def',
59
+ ),
60
+ false,
61
+ )
62
+
63
+ t.snapshot(dts)
64
+ })
65
+
66
+ // The next two tests use a minimal fixture (one numeric + one string
67
+ // enum) to keep snapshots small and focused on the flag's behavior.
68
+ const flagFixture = join(
69
+ fileURLToPath(import.meta.url),
70
+ '../',
71
+ '__fixtures__',
72
+ 'runtime_string_enum_flag',
73
+ )
74
+
75
+ test('should process type def with noConstEnum and runtimeStringEnum correctly', async (t) => {
76
+ const { dts } = await processTypeDef(flagFixture, false, true)
77
+
78
+ t.snapshot(dts)
79
+ })
80
+
81
+ test('runtimeStringEnum is a no-op when constEnum is set', async (t) => {
82
+ const { dts } = await processTypeDef(flagFixture, true, true)
83
+
84
+ t.snapshot(dts)
85
+ })
@@ -0,0 +1,7 @@
1
+ import test from 'ava'
2
+
3
+ import { napiEngineRequirement, SUPPORTED_NAPI_VERSIONS } from '../version.js'
4
+
5
+ test('should generate correct napi engine requirement', (t) => {
6
+ t.snapshot(SUPPORTED_NAPI_VERSIONS.map(napiEngineRequirement))
7
+ })
@@ -1,6 +1,10 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import fs from 'node:fs'
3
3
 
4
+ import { debugFactory } from './log.js'
5
+
6
+ const debug = debugFactory('metadata')
7
+
4
8
  export type CrateTargetKind =
5
9
  | 'bin'
6
10
  | 'example'
@@ -40,23 +44,110 @@ export interface Crate {
40
44
  }>
41
45
  }
42
46
 
47
+ export interface CrateResolveNode {
48
+ id: string
49
+ deps: Array<{
50
+ name: string
51
+ pkg: string
52
+ }>
53
+ features: string[]
54
+ }
55
+
43
56
  export interface CargoWorkspaceMetadata {
44
57
  version: number
45
58
  packages: Crate[]
46
59
  workspace_members: string[]
47
60
  target_directory: string
48
61
  workspace_root: string
62
+ resolve?: {
63
+ nodes: CrateResolveNode[]
64
+ root: string | null
65
+ } | null
66
+ }
67
+
68
+ export interface CargoFeatureOptions {
69
+ features?: string[]
70
+ allFeatures?: boolean
71
+ noDefaultFeatures?: boolean
72
+ }
73
+
74
+ // Crates that will actually compile `napi-derive` in this build, and are therefore
75
+ // expected to emit a type-def intermediate file. Prefer the resolved dependency graph:
76
+ // a crate whose `napi-derive` dependency is optional and stays disabled never emits a
77
+ // type-def file, so force-building it would invalidate cargo's fingerprint on every
78
+ // build without ever satisfying the type-def check. Fall back to declared dependencies
79
+ // when the resolve graph is unavailable.
80
+ export function getNapiDeriveDependentCrates(
81
+ metadata: CargoWorkspaceMetadata,
82
+ ): Crate[] {
83
+ const resolveNodes = metadata.resolve?.nodes
84
+ if (!resolveNodes) {
85
+ return metadata.packages.filter((crate) =>
86
+ crate.dependencies.some((d) => d.name === 'napi-derive'),
87
+ )
88
+ }
89
+ const napiDerivePackageIds = new Set(
90
+ metadata.packages.filter((p) => p.name === 'napi-derive').map((p) => p.id),
91
+ )
92
+ const dependentPackageIds = new Set(
93
+ resolveNodes
94
+ .filter((node) =>
95
+ node.deps.some((dep) => napiDerivePackageIds.has(dep.pkg)),
96
+ )
97
+ .map((node) => node.id),
98
+ )
99
+ return metadata.packages.filter((crate) => dependentPackageIds.has(crate.id))
49
100
  }
50
101
 
51
- export async function parseMetadata(manifestPath: string) {
102
+ export async function parseMetadata(
103
+ manifestPath: string,
104
+ featureOptions?: CargoFeatureOptions,
105
+ ) {
52
106
  if (!fs.existsSync(manifestPath)) {
53
107
  throw new Error(`No crate found in manifest: ${manifestPath}`)
54
108
  }
55
109
 
110
+ const featureArgs: string[] = []
111
+ if (featureOptions?.allFeatures) {
112
+ featureArgs.push('--all-features')
113
+ }
114
+ if (featureOptions?.noDefaultFeatures) {
115
+ featureArgs.push('--no-default-features')
116
+ }
117
+ if (featureOptions?.features?.length) {
118
+ featureArgs.push('--features', featureOptions.features.join(','))
119
+ }
120
+
121
+ // Resolving with the build's feature flags keeps `metadata.resolve` in sync with what
122
+ // `cargo build` will actually compile. Feature flags are rejected in some setups
123
+ // (e.g. a virtual workspace root manifest), so fall back to a plain invocation.
124
+ if (featureArgs.length) {
125
+ try {
126
+ return await execCargoMetadata(manifestPath, featureArgs)
127
+ } catch (e) {
128
+ debug.warn(
129
+ `cargo metadata failed with feature flags, retrying without them: ${e}`,
130
+ )
131
+ }
132
+ }
133
+
134
+ return execCargoMetadata(manifestPath, [])
135
+ }
136
+
137
+ async function execCargoMetadata(manifestPath: string, extraArgs: string[]) {
56
138
  const childProcess = spawn(
57
139
  'cargo',
58
- ['metadata', '--manifest-path', manifestPath, '--format-version', '1'],
59
- { stdio: 'pipe' },
140
+ [
141
+ 'metadata',
142
+ '--manifest-path',
143
+ manifestPath,
144
+ '--format-version',
145
+ '1',
146
+ ...extraArgs,
147
+ ],
148
+ {
149
+ stdio: 'pipe',
150
+ },
60
151
  )
61
152
 
62
153
  let stdout = ''
@@ -8,6 +8,7 @@ export enum NapiVersion {
8
8
  Napi7,
9
9
  Napi8,
10
10
  Napi9,
11
+ Napi10,
11
12
  }
12
13
 
13
14
  /// because node support new napi version in some minor version updates, so we might meet such situation:
@@ -23,6 +24,7 @@ const NAPI_VERSION_MATRIX = new Map<NapiVersion, string>([
23
24
  [NapiVersion.Napi7, '10.23.0 | 12.19.0 | 14.12.0 | 15.0.0'],
24
25
  [NapiVersion.Napi8, '12.22.0 | 14.17.0 | 15.12.0 | 16.0.0'],
25
26
  [NapiVersion.Napi9, '18.17.0 | 20.3.0 | 21.1.0'],
27
+ [NapiVersion.Napi10, '22.14.0 | 23.6.0'],
26
28
  ])
27
29
 
28
30
  export const SUPPORTED_NAPI_VERSIONS = Object.values(NapiVersion).filter(