@napi-rs/cli 3.7.3 → 3.8.0

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 (51) hide show
  1. package/README.md +33 -0
  2. package/cli.mjs +9 -12
  3. package/dist/cli.js +9277 -1313
  4. package/dist/index.cjs +9266 -1302
  5. package/dist/index.d.cts +176 -158
  6. package/dist/index.d.ts +176 -158
  7. package/dist/index.js +9277 -1313
  8. package/docs/artifacts.md +33 -0
  9. package/docs/build.md +58 -0
  10. package/docs/create-npm-dirs.md +32 -0
  11. package/docs/new.md +39 -0
  12. package/docs/pre-publish.md +37 -0
  13. package/docs/rename.md +37 -0
  14. package/docs/universalize.md +31 -0
  15. package/docs/version.md +31 -0
  16. package/docs/wasi.md +161 -0
  17. package/package.json +11 -10
  18. package/src/api/__tests__/__snapshots__/templates.spec.ts.md +1019 -114
  19. package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
  20. package/src/api/__tests__/artifacts.spec.ts +17 -0
  21. package/src/api/__tests__/create-npm-dirs.spec.ts +14 -15
  22. package/src/api/__tests__/new.spec.ts +41 -5
  23. package/src/api/artifacts.ts +934 -108
  24. package/src/api/build.ts +1325 -194
  25. package/src/api/create-npm-dirs.ts +472 -101
  26. package/src/api/new.ts +354 -35
  27. package/src/api/pre-publish.ts +2845 -99
  28. package/src/api/rename.ts +1638 -124
  29. package/src/api/templates/js-binding.ts +216 -38
  30. package/src/api/templates/load-wasi-template.ts +1641 -111
  31. package/src/api/templates/wasi-worker-template.ts +32 -3
  32. package/src/commands/rename.ts +8 -3
  33. package/src/def/artifacts.ts +2 -2
  34. package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
  35. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
  36. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
  37. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
  38. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
  39. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
  40. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
  41. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +2 -2
  42. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  43. package/src/utils/__tests__/metadata.spec.ts +43 -2
  44. package/src/utils/__tests__/misc.spec.ts +124 -3
  45. package/src/utils/__tests__/version.spec.ts +47 -1
  46. package/src/utils/config.ts +15 -2
  47. package/src/utils/metadata.ts +206 -7
  48. package/src/utils/misc.ts +6079 -0
  49. package/src/utils/target.ts +66 -8
  50. package/src/utils/typegen.ts +1223 -63
  51. package/src/utils/version.ts +96 -0
@@ -1,5 +1,10 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import fs from 'node:fs'
3
+ import { dirname, resolve } from 'node:path'
4
+
5
+ import { debugFactory } from './log.js'
6
+
7
+ const debug = debugFactory('metadata')
3
8
 
4
9
  export type CrateTargetKind =
5
10
  | 'bin'
@@ -28,7 +33,7 @@ export interface Crate {
28
33
  manifest_path: string
29
34
  dependencies: Array<{
30
35
  name: string
31
- source: string
36
+ source: string | null
32
37
  req: string
33
38
  kind: string | null
34
39
  rename: string | null
@@ -40,24 +45,218 @@ export interface Crate {
40
45
  }>
41
46
  }
42
47
 
48
+ export interface CargoResolveNode {
49
+ id: string
50
+ dependencies: string[]
51
+ deps: Array<{
52
+ name: string
53
+ pkg: string
54
+ dep_kinds: Array<{
55
+ kind: string | null
56
+ target: string | null
57
+ }>
58
+ }>
59
+ features: string[]
60
+ }
61
+
43
62
  export interface CargoWorkspaceMetadata {
44
63
  version: number
45
64
  packages: Crate[]
46
65
  workspace_members: string[]
47
66
  target_directory: string
48
67
  workspace_root: string
68
+ resolve: {
69
+ root: string | null
70
+ nodes: CargoResolveNode[]
71
+ } | null
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 in the current
77
+ // build never emits a type-def file, so force-building it would invalidate cargo's
78
+ // fingerprint on every build without ever satisfying the type-def check. Fall back
79
+ // to declared dependencies 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
+ interface ParseMetadataOptions {
103
+ allFeatures?: boolean
104
+ cargoOptions?: string[]
105
+ cwd?: string
106
+ featurePackage?: string
107
+ features?: string[]
108
+ filterPlatform?: string
109
+ noDefaultFeatures?: boolean
110
+ }
111
+
112
+ export function createCargoMetadataInvocation(
113
+ manifestPath: string,
114
+ options: ParseMetadataOptions = {},
115
+ ) {
116
+ const globalArgs: string[] = []
117
+ const metadataArgs = [
118
+ 'metadata',
119
+ '--manifest-path',
120
+ manifestPath,
121
+ '--format-version',
122
+ '1',
123
+ ]
124
+ const features = [
125
+ ...new Set(
126
+ (options.features ?? []).flatMap((feature) =>
127
+ feature.split(/[,\s]+/).filter(Boolean),
128
+ ),
129
+ ),
130
+ ]
131
+ let allFeatures = options.allFeatures === true
132
+ let noDefaultFeatures = options.noDefaultFeatures === true
133
+ const cargoOptions = options.cargoOptions ?? []
134
+ for (let index = 0; index < cargoOptions.length; index += 1) {
135
+ const option = cargoOptions[index]
136
+ if (option === '--config' || option === '-Z') {
137
+ const value = cargoOptions[index + 1]
138
+ if (value !== undefined) {
139
+ globalArgs.push(option, value)
140
+ index += 1
141
+ }
142
+ } else if (option.startsWith('--config=') || option.startsWith('-Z')) {
143
+ globalArgs.push(option)
144
+ } else if (option === '--features' || option === '-F') {
145
+ const value = cargoOptions[index + 1]
146
+ if (value !== undefined) {
147
+ features.push(...value.split(/[,\s]+/).filter(Boolean))
148
+ index += 1
149
+ }
150
+ } else if (option.startsWith('--features=') || option.startsWith('-F=')) {
151
+ features.push(
152
+ ...option
153
+ .slice(option.indexOf('=') + 1)
154
+ .split(/[,\s]+/)
155
+ .filter(Boolean),
156
+ )
157
+ } else if (option.startsWith('-F') && option.length > 2) {
158
+ features.push(
159
+ ...option
160
+ .slice(2)
161
+ .split(/[,\s]+/)
162
+ .filter(Boolean),
163
+ )
164
+ } else if (option === '--all-features') {
165
+ allFeatures = true
166
+ } else if (option === '--no-default-features') {
167
+ noDefaultFeatures = true
168
+ } else if (
169
+ option === '--locked' ||
170
+ option === '--offline' ||
171
+ option === '--frozen'
172
+ ) {
173
+ metadataArgs.push(option)
174
+ }
175
+ }
176
+ const selectedFeatures = [
177
+ ...new Set(
178
+ features.map((feature) =>
179
+ options.featurePackage && !feature.includes('/')
180
+ ? `${options.featurePackage}/${feature}`
181
+ : feature,
182
+ ),
183
+ ),
184
+ ].sort()
185
+ if (selectedFeatures.length > 0) {
186
+ metadataArgs.push('--features', selectedFeatures.join(','))
187
+ }
188
+ if (allFeatures) {
189
+ metadataArgs.push('--all-features')
190
+ }
191
+ if (noDefaultFeatures) {
192
+ metadataArgs.push('--no-default-features')
193
+ }
194
+ if (options.filterPlatform) {
195
+ metadataArgs.push('--filter-platform', options.filterPlatform)
196
+ }
197
+
198
+ return {
199
+ // Deliberately NOT `process.env.CARGO ?? 'cargo'`: `$CARGO` only overrides
200
+ // the cargo/cross binary spawned for the actual build (see the Builder in
201
+ // api/build.ts) — tests poison `$CARGO` with a nonexistent path to assert
202
+ // no real build is spawned, and `cross metadata` is not a thing either.
203
+ // Metadata always comes from the host `cargo`.
204
+ command: 'cargo',
205
+ args: [...globalArgs, ...metadataArgs],
206
+ cwd: resolve(options.cwd ?? dirname(manifestPath)),
207
+ }
208
+ }
209
+
210
+ export async function parseMetadata(
211
+ manifestPath: string,
212
+ options: ParseMetadataOptions = {},
213
+ ) {
52
214
  if (!fs.existsSync(manifestPath)) {
53
215
  throw new Error(`No crate found in manifest: ${manifestPath}`)
54
216
  }
55
217
 
56
- const childProcess = spawn(
57
- 'cargo',
58
- ['metadata', '--manifest-path', manifestPath, '--format-version', '1'],
59
- { stdio: 'pipe' },
60
- )
218
+ const invocation = createCargoMetadataInvocation(manifestPath, options)
219
+ // Resolving with the build's feature flags keeps `metadata.resolve` in sync
220
+ // with what `cargo build` will actually compile. Feature flags are rejected
221
+ // in some setups (e.g. a virtual workspace root manifest), so fall back to a
222
+ // plain invocation without them.
223
+ const featurelessArgs = stripFeatureArgs(invocation.args)
224
+ if (featurelessArgs.length !== invocation.args.length) {
225
+ try {
226
+ return await execCargoMetadata(invocation)
227
+ } catch (e) {
228
+ debug.warn(
229
+ `cargo metadata failed with feature flags, retrying without them: ${e}`,
230
+ )
231
+ }
232
+ return execCargoMetadata({ ...invocation, args: featurelessArgs })
233
+ }
234
+ return execCargoMetadata(invocation)
235
+ }
236
+
237
+ function stripFeatureArgs(args: string[]): string[] {
238
+ const stripped: string[] = []
239
+ for (let index = 0; index < args.length; index += 1) {
240
+ const arg = args[index]
241
+ if (arg === '--features') {
242
+ // also skip the flag's value
243
+ index += 1
244
+ } else if (arg !== '--all-features' && arg !== '--no-default-features') {
245
+ stripped.push(arg)
246
+ }
247
+ }
248
+ return stripped
249
+ }
250
+
251
+ async function execCargoMetadata(invocation: {
252
+ command: string
253
+ args: string[]
254
+ cwd: string
255
+ }) {
256
+ const childProcess = spawn(invocation.command, invocation.args, {
257
+ cwd: invocation.cwd,
258
+ stdio: 'pipe',
259
+ })
61
260
 
62
261
  let stdout = ''
63
262
  let stderr = ''