@napi-rs/cli 3.9.0 → 3.10.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 (39) hide show
  1. package/README.md +17 -4
  2. package/dist/cli.js +10359 -8729
  3. package/dist/index.cjs +10381 -8751
  4. package/dist/index.d.cts +83 -16
  5. package/dist/index.d.ts +83 -16
  6. package/dist/index.js +10359 -8729
  7. package/docs/wasi.md +318 -4
  8. package/package.json +5 -6
  9. package/src/api/__tests__/__snapshots__/templates.spec.ts.md +4003 -53
  10. package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
  11. package/src/api/__tests__/build-regressions.spec.ts +210 -3
  12. package/src/api/__tests__/build.spec.ts +2333 -3
  13. package/src/api/__tests__/create-npm-dirs.spec.ts +105 -3
  14. package/src/api/__tests__/pre-publish.spec.ts +153 -1
  15. package/src/api/__tests__/templates.spec.ts +1309 -1
  16. package/src/api/build.ts +756 -30
  17. package/src/api/create-npm-dirs.ts +23 -10
  18. package/src/api/new.ts +13 -18
  19. package/src/api/pre-publish.ts +34 -11
  20. package/src/api/rename.ts +10 -21
  21. package/src/api/templates/binding-target.ts +176 -0
  22. package/src/api/templates/index.ts +1 -0
  23. package/src/api/templates/js-binding.ts +54 -10
  24. package/src/api/templates/load-wasi-template.ts +661 -58
  25. package/src/api/templates/wasi-worker-template.ts +36 -31
  26. package/src/commands/build.ts +1 -1
  27. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +18 -28
  28. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  29. package/src/utils/__tests__/misc.spec.ts +4 -0
  30. package/src/utils/__tests__/reconciliation.spec.ts +676 -0
  31. package/src/utils/__tests__/serialize.spec.ts +55 -0
  32. package/src/utils/__tests__/target.spec.ts +221 -0
  33. package/src/utils/__tests__/typegen.spec.ts +115 -0
  34. package/src/utils/config.ts +50 -0
  35. package/src/utils/index.ts +1 -0
  36. package/src/utils/misc.ts +351 -79
  37. package/src/utils/serialize.ts +47 -0
  38. package/src/utils/target.ts +150 -1
  39. package/src/utils/typegen.ts +608 -42
@@ -1,5 +1,12 @@
1
1
  import { wasiLoaderSuffix } from '../../utils/index.js'
2
2
 
3
+ import {
4
+ assertBindingTargetIdentFree,
5
+ BINDING_TARGET_STAMP_HELPER,
6
+ NAPI_BINDING_TARGET_EXPORT,
7
+ NAPI_BINDING_TARGET_STAMP_FN,
8
+ } from './binding-target.js'
9
+
3
10
  function resolveWasiFlavors(wasiFlavors?: string[]): string[] {
4
11
  return wasiFlavors && wasiFlavors.length > 0 ? wasiFlavors : ['wasm32-wasi']
5
12
  }
@@ -43,11 +50,11 @@ function createWasiFallbackChain(
43
50
  specifier,
44
51
  isPackage,
45
52
  localArtifacts,
46
- }) => ` if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === ${JSON.stringify(flavor)})) {
53
+ }) => ` if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === '${flavor}')) {
47
54
  let candidateError = null
48
55
  let candidateFailed = false
49
56
  try {
50
- candidateError = __napiWasiResolveCandidate('${specifier}', ${isPackage}, ${JSON.stringify(localArtifacts)})
57
+ candidateError = __napiWasiResolveCandidate('${specifier}', ${isPackage}, ${localArtifacts ? `[${localArtifacts.map((artifact) => `'${artifact}'`).join(', ')}]` : 'undefined'})
51
58
  candidateFailed = candidateError !== null
52
59
  if (!candidateFailed) {${
53
60
  isPackage && packageVersion
@@ -62,6 +69,7 @@ function createWasiFallbackChain(
62
69
  }
63
70
  wasiBinding = require('${specifier}')
64
71
  nativeBinding = wasiBinding
72
+ __napiLoadedBindingTarget = '${flavor}'
65
73
  wasiBindingLoaded = true
66
74
  }
67
75
  } catch (err) {
@@ -126,6 +134,7 @@ export function createCjsBinding(
126
134
  wasiFlavors?: string[],
127
135
  localWasiName?: string,
128
136
  ): string {
137
+ assertBindingTargetIdentFree(idents)
129
138
  return `${bindingHeader}
130
139
  ${createCommonBinding(
131
140
  localName,
@@ -134,6 +143,23 @@ ${createCommonBinding(
134
143
  wasiFlavors,
135
144
  localWasiName,
136
145
  )}
146
+ ${BINDING_TARGET_STAMP_HELPER}
147
+ // Stamp before the alias, not after. The guard only reads \`nativeBinding\`
148
+ // (\`hasOwnProperty\` plus a comparison), which is safe against any addon
149
+ // accessor; an assignment is not, because a \`#[napi(module_exports)]\` hook can
150
+ // expose a getter reporting this very value and a setter that throws. So the
151
+ // assignment lands on the loader's own \`module.exports\`, still the original
152
+ // object here, and the alias below replaces it.
153
+ //
154
+ // The assignment is what keeps the marker a statically visible CommonJS export:
155
+ // \`cjs-module-lexer\` is Node's CJS -> ESM named export detection, it cannot see
156
+ // a bare call, and the later \`module.exports = nativeBinding\` does not undo the
157
+ // detection. The assignment itself always succeeds — its target is this
158
+ // loader's own, still extensible \`module.exports\` — and the alias below then
159
+ // discards the value it wrote. What a consumer reads is whatever the guard put
160
+ // on \`nativeBinding\`, so on a frozen binding, where the guard skips, the
161
+ // linked import resolves to \`undefined\`.
162
+ module.exports.${NAPI_BINDING_TARGET_EXPORT} = ${NAPI_BINDING_TARGET_STAMP_FN}(nativeBinding, __napiLoadedBindingTarget)
137
163
  module.exports = nativeBinding
138
164
  ${idents
139
165
  .map((ident) => `module.exports.${ident} = nativeBinding.${ident}`)
@@ -149,11 +175,17 @@ export function createEsmBinding(
149
175
  wasiFlavors?: string[],
150
176
  localWasiName?: string,
151
177
  ): string {
178
+ assertBindingTargetIdentFree(idents)
179
+ // Both branches must carry it, or a zero-ident package silently loses the
180
+ // export.
181
+ const bindingTargetExport = `export const ${NAPI_BINDING_TARGET_EXPORT} = __napiLoadedBindingTarget`
152
182
  const exportsCode =
153
183
  idents.length > 0
154
184
  ? `const { ${idents.join(', ')} } = nativeBinding
155
- ${idents.map((ident) => `export { ${ident} }`).join('\n')}`
156
- : 'export default nativeBinding'
185
+ ${idents.map((ident) => `export { ${ident} }`).join('\n')}
186
+ ${bindingTargetExport}`
187
+ : `export default nativeBinding
188
+ ${bindingTargetExport}`
157
189
  return `${bindingHeader}
158
190
  import { createRequire } from 'module'
159
191
  const require = createRequire(import.meta.url)
@@ -169,8 +201,7 @@ ${exportsCode}
169
201
  `
170
202
  }
171
203
 
172
- const bindingHeader = `// prettier-ignore
173
- /* eslint-disable */
204
+ const bindingHeader = `/* eslint-disable */
174
205
  // @ts-nocheck
175
206
  /* auto-generated by NAPI-RS */
176
207
  `
@@ -214,6 +245,10 @@ ${identLow}}${versionCheck}`
214
245
 
215
246
  return `const { readFileSync } = require('fs')
216
247
  let nativeBinding = null
248
+ // Which artifact actually loaded. The WASI fallback chain overwrites it with
249
+ // the flavor it resolved; the late native retry below leaves it alone because
250
+ // it only runs while no WASI candidate has been loaded.
251
+ let __napiLoadedBindingTarget = 'native'
217
252
  const loadErrors = []
218
253
 
219
254
  const isMusl = () => {
@@ -272,7 +307,16 @@ const isMuslFromChildProcess = () => {
272
307
  function requireNative() {
273
308
  if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
274
309
  try {
275
- return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
310
+ const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
311
+ // The override may be a generated WASI loader, which already reports its
312
+ // own flavor. Adopt it: \`module.exports\` aliases this object, so claiming
313
+ // 'native' would both misreport the artifact and overwrite the loader's
314
+ // marker through the alias.
315
+ __napiLoadedBindingTarget =
316
+ overrideBinding && typeof overrideBinding.${NAPI_BINDING_TARGET_EXPORT} === 'string'
317
+ ? overrideBinding.${NAPI_BINDING_TARGET_EXPORT}
318
+ : 'native'
319
+ return overrideBinding
276
320
  } catch (err) {
277
321
  loadErrors.push(err)
278
322
  }
@@ -287,9 +331,9 @@ function requireNative() {
287
331
  } else if (process.platform === 'win32') {
288
332
  if (process.arch === 'x64') {
289
333
  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')) {
290
- ${requireTuple('win32-x64-gnu')}
334
+ ${requireTuple('win32-x64-gnu', 10)}
291
335
  } else {
292
- ${requireTuple('win32-x64-msvc')}
336
+ ${requireTuple('win32-x64-msvc', 10)}
293
337
  }
294
338
  } else if (process.arch === 'ia32') {
295
339
  ${requireTuple('win32-ia32-msvc')}
@@ -395,7 +439,7 @@ function createLoadErrorChain(errors) {
395
439
  //
396
440
  // NAPI_RS_WASI_FLAVOR selects one exact generated flavor and implies strict
397
441
  // WASI loading. It never crosses into another flavor or falls back to native.
398
- const __napiWasiFlavors = ${JSON.stringify(flavors)}
442
+ const __napiWasiFlavors = [${flavors.map((flavor) => `'${flavor}'`).join(', ')}]
399
443
  const __napiWasiFlavor = process.env.NAPI_RS_WASI_FLAVOR
400
444
  const __napiWasiFlavorRequested =
401
445
  typeof __napiWasiFlavor === 'string' && __napiWasiFlavor.length > 0