@napi-rs/cli 3.7.4 → 3.8.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.
Files changed (43) hide show
  1. package/README.md +33 -0
  2. package/cli.mjs +9 -12
  3. package/dist/cli.js +9249 -1312
  4. package/dist/index.cjs +9269 -1332
  5. package/dist/index.d.cts +175 -157
  6. package/dist/index.d.ts +175 -157
  7. package/dist/index.js +9249 -1312
  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 +60 -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 +1317 -188
  25. package/src/api/create-npm-dirs.ts +475 -102
  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__/__snapshots__/target.spec.ts.md +2 -2
  35. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  36. package/src/utils/__tests__/misc.spec.ts +124 -3
  37. package/src/utils/__tests__/version.spec.ts +47 -1
  38. package/src/utils/config.ts +15 -2
  39. package/src/utils/metadata.ts +155 -47
  40. package/src/utils/misc.ts +6079 -0
  41. package/src/utils/target.ts +66 -8
  42. package/src/utils/typegen.ts +1223 -63
  43. package/src/utils/version.ts +96 -0
@@ -1,11 +1,139 @@
1
+ import { wasiLoaderSuffix } from '../../utils/index.js'
2
+
3
+ function resolveWasiFlavors(wasiFlavors?: string[]): string[] {
4
+ return wasiFlavors && wasiFlavors.length > 0 ? wasiFlavors : ['wasm32-wasi']
5
+ }
6
+
7
+ /**
8
+ * Generate the WASI fallback `require` chain for `binding.cjs`/`binding.js`.
9
+ *
10
+ * Local candidates are tried before installed package fallbacks. Within each
11
+ * group, candidates retain the declared flavor order (threaded flavors are
12
+ * expected first), and the chain stops at the FIRST successfully loaded
13
+ * binding.
14
+ */
15
+ function createWasiFallbackChain(
16
+ localName: string,
17
+ pkgName: string,
18
+ flavors: string[],
19
+ packageVersion?: string,
20
+ localWasiName = `./${localName}`,
21
+ ): string {
22
+ const candidates = [
23
+ ...flavors.map((flavor) => ({
24
+ flavor,
25
+ specifier: `${localWasiName}.${wasiLoaderSuffix(flavor)}.cjs`,
26
+ isPackage: false,
27
+ localArtifacts: [
28
+ `${localWasiName}.${flavor}.debug.wasm`,
29
+ `${localWasiName}.${flavor}.wasm`,
30
+ ],
31
+ })),
32
+ ...flavors.map((flavor) => ({
33
+ flavor,
34
+ specifier: `${pkgName}-${flavor}`,
35
+ isPackage: true,
36
+ localArtifacts: undefined,
37
+ })),
38
+ ]
39
+ const chain = candidates
40
+ .map(
41
+ ({
42
+ flavor,
43
+ specifier,
44
+ isPackage,
45
+ localArtifacts,
46
+ }) => ` if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === ${JSON.stringify(flavor)})) {
47
+ let candidateError = null
48
+ let candidateFailed = false
49
+ try {
50
+ candidateError = __napiWasiResolveCandidate('${specifier}', ${isPackage}, ${JSON.stringify(localArtifacts)})
51
+ candidateFailed = candidateError !== null
52
+ if (!candidateFailed) {${
53
+ isPackage && packageVersion
54
+ ? `
55
+ if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
56
+ const bindingPackageVersion = require('${specifier}/package.json').version
57
+ if (bindingPackageVersion !== '${packageVersion}') {
58
+ throw new Error(\`WASI binding package version mismatch, expected ${packageVersion} but got \${bindingPackageVersion}. You can reinstall dependencies to fix this issue.\`)
59
+ }
60
+ }`
61
+ : ''
62
+ }
63
+ wasiBinding = require('${specifier}')
64
+ nativeBinding = wasiBinding
65
+ wasiBindingLoaded = true
66
+ }
67
+ } catch (err) {
68
+ candidateError = err
69
+ candidateFailed = true
70
+ }
71
+ if (candidateFailed) {
72
+ wasiBindingErrors.push(candidateError)
73
+ loadErrors.push(candidateError)
74
+ }
75
+ }`,
76
+ )
77
+ .join('\n')
78
+ return ` const __napiWasiResolveCandidate = (specifier, isPackage, localArtifacts) => {
79
+ try {
80
+ require.resolve(specifier)
81
+ } catch (resolveError) {
82
+ if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') {
83
+ throw resolveError
84
+ }
85
+ if (isPackage) {
86
+ try {
87
+ require.resolve(specifier + '/package.json')
88
+ } catch (packageError) {
89
+ if (packageError && packageError.code === 'MODULE_NOT_FOUND') {
90
+ return resolveError
91
+ }
92
+ // An exports restriction proves the package exists even when its
93
+ // package.json is not public. Preserve the root resolution failure.
94
+ throw resolveError
95
+ }
96
+ // The package exists but its main/export target is broken.
97
+ throw resolveError
98
+ }
99
+ return resolveError
100
+ }
101
+ if (localArtifacts) {
102
+ let artifactError = null
103
+ for (let i = 0; i < localArtifacts.length; i++) {
104
+ try {
105
+ require.resolve(localArtifacts[i])
106
+ return null
107
+ } catch (resolveError) {
108
+ if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') {
109
+ throw resolveError
110
+ }
111
+ artifactError = resolveError
112
+ }
113
+ }
114
+ return artifactError
115
+ }
116
+ return null
117
+ }
118
+ ${chain}`
119
+ }
120
+
1
121
  export function createCjsBinding(
2
122
  localName: string,
3
123
  pkgName: string,
4
124
  idents: string[],
5
125
  packageVersion?: string,
126
+ wasiFlavors?: string[],
127
+ localWasiName?: string,
6
128
  ): string {
7
129
  return `${bindingHeader}
8
- ${createCommonBinding(localName, pkgName, packageVersion)}
130
+ ${createCommonBinding(
131
+ localName,
132
+ pkgName,
133
+ packageVersion,
134
+ wasiFlavors,
135
+ localWasiName,
136
+ )}
9
137
  module.exports = nativeBinding
10
138
  ${idents
11
139
  .map((ident) => `module.exports.${ident} = nativeBinding.${ident}`)
@@ -18,15 +146,27 @@ export function createEsmBinding(
18
146
  pkgName: string,
19
147
  idents: string[],
20
148
  packageVersion?: string,
149
+ wasiFlavors?: string[],
150
+ localWasiName?: string,
21
151
  ): string {
152
+ const exportsCode =
153
+ idents.length > 0
154
+ ? `const { ${idents.join(', ')} } = nativeBinding
155
+ ${idents.map((ident) => `export { ${ident} }`).join('\n')}`
156
+ : 'export default nativeBinding'
22
157
  return `${bindingHeader}
23
158
  import { createRequire } from 'module'
24
159
  const require = createRequire(import.meta.url)
25
160
  const __dirname = new URL('.', import.meta.url).pathname
26
161
 
27
- ${createCommonBinding(localName, pkgName, packageVersion)}
28
- const { ${idents.join(', ')} } = nativeBinding
29
- ${idents.map((ident) => `export { ${ident} }`).join('\n')}
162
+ ${createCommonBinding(
163
+ localName,
164
+ pkgName,
165
+ packageVersion,
166
+ wasiFlavors,
167
+ localWasiName,
168
+ )}
169
+ ${exportsCode}
30
170
  `
31
171
  }
32
172
 
@@ -40,6 +180,8 @@ function createCommonBinding(
40
180
  localName: string,
41
181
  pkgName: string,
42
182
  packageVersion?: string,
183
+ wasiFlavors?: string[],
184
+ localWasiName?: string,
43
185
  ): string {
44
186
  function requireTuple(tuple: string, identSize = 8) {
45
187
  const identLow = ' '.repeat(identSize - 2)
@@ -69,6 +211,8 @@ ${ident}loadErrors.push(e)
69
211
  ${identLow}}${versionCheck}`
70
212
  }
71
213
 
214
+ const flavors = resolveWasiFlavors(wasiFlavors)
215
+
72
216
  return `const { readFileSync } = require('fs')
73
217
  let nativeBinding = null
74
218
  const loadErrors = []
@@ -225,47 +369,84 @@ function requireNative() {
225
369
  }
226
370
  }
227
371
 
228
- nativeBinding = requireNative()
372
+ function createLoadErrorChain(errors) {
373
+ return errors.reduce((previous, current) => {
374
+ let message
375
+ try {
376
+ message =
377
+ current && typeof current.message === 'string'
378
+ ? current.message
379
+ : String(current)
380
+ } catch {
381
+ message = 'Unknown error'
382
+ }
383
+ const error = new Error(message)
384
+ error.cause = previous
385
+ return error
386
+ }, null)
387
+ }
229
388
 
230
389
  // NAPI_RS_FORCE_WASI is a tri-state flag:
231
390
  // unset / any other value → native binding preferred, WASI is only a fallback
232
- // 'true' → force WASI fallback even if native loaded
233
- // 'error' → force WASI and throw if no WASI binding is found
391
+ // 'true' → prefer WASI, but retain native as a lazy fallback
392
+ // 'error' → require WASI without initializing a native fallback
234
393
  // Treating any non-empty string as truthy (the historical behavior) meant
235
394
  // NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered
236
395
  // the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file.
396
+ //
397
+ // NAPI_RS_WASI_FLAVOR selects one exact generated flavor and implies strict
398
+ // WASI loading. It never crosses into another flavor or falls back to native.
399
+ const __napiWasiFlavors = ${JSON.stringify(flavors)}
400
+ const __napiWasiFlavor = process.env.NAPI_RS_WASI_FLAVOR
401
+ const __napiWasiFlavorRequested =
402
+ typeof __napiWasiFlavor === 'string' && __napiWasiFlavor.length > 0
403
+ if (
404
+ __napiWasiFlavorRequested &&
405
+ __napiWasiFlavors.indexOf(__napiWasiFlavor) === -1
406
+ ) {
407
+ throw new Error(
408
+ 'Unsupported WASI flavor "' +
409
+ __napiWasiFlavor +
410
+ '". Available flavors: ' +
411
+ __napiWasiFlavors.join(', '),
412
+ )
413
+ }
414
+ const forceWasiError = process.env.NAPI_RS_FORCE_WASI === 'error'
237
415
  const forceWasi =
238
- process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error'
416
+ process.env.NAPI_RS_FORCE_WASI === 'true' ||
417
+ forceWasiError ||
418
+ __napiWasiFlavorRequested
419
+
420
+ if (!forceWasi) {
421
+ nativeBinding = requireNative()
422
+ }
239
423
 
240
424
  if (!nativeBinding || forceWasi) {
241
425
  let wasiBinding = null
242
- let wasiBindingError = null
243
- try {
244
- wasiBinding = require('./${localName}.wasi.cjs')
245
- nativeBinding = wasiBinding
246
- } catch (err) {
247
- if (forceWasi) {
248
- wasiBindingError = err
249
- }
250
- }
251
- if (!nativeBinding || forceWasi) {
252
- try {
253
- wasiBinding = require('${pkgName}-wasm32-wasi')
254
- nativeBinding = wasiBinding
255
- } catch (err) {
256
- if (forceWasi) {
257
- if (!wasiBindingError) {
258
- wasiBindingError = err
259
- } else {
260
- wasiBindingError.cause = err
261
- }
262
- loadErrors.push(err)
263
- }
264
- }
426
+ let wasiBindingLoaded = false
427
+ const wasiBindingErrors = []
428
+ ${createWasiFallbackChain(
429
+ localName,
430
+ pkgName,
431
+ flavors,
432
+ packageVersion,
433
+ localWasiName,
434
+ )}
435
+ if (
436
+ !wasiBindingLoaded &&
437
+ forceWasi &&
438
+ !forceWasiError &&
439
+ !__napiWasiFlavorRequested
440
+ ) {
441
+ nativeBinding = requireNative()
265
442
  }
266
- if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
267
- const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
268
- error.cause = wasiBindingError
443
+ if ((forceWasiError || __napiWasiFlavorRequested) && !wasiBindingLoaded) {
444
+ const error = new Error(
445
+ __napiWasiFlavorRequested
446
+ ? 'WASI binding for flavor "' + __napiWasiFlavor + '" not found'
447
+ : 'WASI binding not found and NAPI_RS_FORCE_WASI is set to error',
448
+ )
449
+ error.cause = createLoadErrorChain(wasiBindingErrors)
269
450
  throw error
270
451
  }
271
452
  }
@@ -279,10 +460,7 @@ if (!nativeBinding) {
279
460
  )
280
461
  // assign instead of the \`new Error(message, { cause })\` options form,
281
462
  // which Node < 16.9 silently ignores
282
- error.cause = loadErrors.reduce((err, cur) => {
283
- cur.cause = err
284
- return cur
285
- })
463
+ error.cause = createLoadErrorChain(loadErrors)
286
464
  throw error
287
465
  }
288
466
  throw new Error(\`Failed to load native binding\`)