@napi-rs/cli 3.6.2 → 3.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napi-rs/cli",
3
- "version": "3.6.2",
3
+ "version": "3.7.1",
4
4
  "description": "Cli tools for napi-rs",
5
5
  "author": "LongYinan <lynweklm@gmail.com>",
6
6
  "homepage": "https://napi.rs/",
@@ -60,35 +60,35 @@
60
60
  "url": "https://github.com/napi-rs/napi-rs/issues"
61
61
  },
62
62
  "dependencies": {
63
- "@inquirer/prompts": "^8.0.0",
63
+ "@inquirer/prompts": "^8.5.2",
64
64
  "@napi-rs/cross-toolchain": "^1.0.3",
65
65
  "@napi-rs/wasm-tools": "^1.0.1",
66
66
  "@octokit/rest": "^22.0.1",
67
67
  "clipanion": "^4.0.0-rc.4",
68
68
  "colorette": "^2.0.20",
69
- "emnapi": "^1.9.1",
70
- "es-toolkit": "^1.41.0",
71
- "js-yaml": "^4.1.0",
72
- "obug": "^2.0.0",
73
- "semver": "^7.7.3",
69
+ "emnapi": "^1.11.0",
70
+ "es-toolkit": "^1.47.0",
71
+ "js-yaml": "^4.2.0",
72
+ "obug": "^2.1.2",
73
+ "semver": "^7.8.2",
74
74
  "typanion": "^3.14.0"
75
75
  },
76
76
  "devDependencies": {
77
- "@emnapi/runtime": "^1.9.1",
77
+ "@emnapi/runtime": "^1.11.0",
78
78
  "@oxc-node/core": "^0.1.0",
79
79
  "@std/toml": "npm:@jsr/std__toml@^1.0.11",
80
80
  "@types/inquirer": "^9.0.9",
81
81
  "@types/js-yaml": "^4.0.9",
82
- "@types/node": "^24.10.0",
82
+ "@types/node": "^25.9.2",
83
83
  "@types/semver": "^7.7.1",
84
- "ava": "^7.0.0",
85
- "empathic": "^2.0.0",
84
+ "ava": "^8.0.1",
85
+ "empathic": "^2.0.1",
86
86
  "env-paths": "^4.0.0",
87
- "oxc-parser": "^0.125.0",
88
- "prettier": "^3.6.2",
89
- "tsdown": "^0.21.0",
87
+ "oxc-parser": "^0.135.0",
88
+ "prettier": "^3.8.3",
89
+ "tsdown": "^0.22.2",
90
90
  "tslib": "^2.8.1",
91
- "typescript": "^5.9.3"
91
+ "typescript": "^6.0.3"
92
92
  },
93
93
  "peerDependencies": {
94
94
  "@emnapi/runtime": "^1.7.1"
@@ -105,12 +105,12 @@
105
105
  "scripts": {
106
106
  "codegen": "node --import @oxc-node/core/register ./codegen/index.ts",
107
107
  "build": "tsdown",
108
- "test": "node --import @oxc-node/core/register ../node_modules/ava/entrypoints/cli.mjs"
108
+ "test": "node --import @oxc-node/core/register ../node_modules/ava/entrypoints/cli.js"
109
109
  },
110
110
  "ava": {
111
- "extensions": {
112
- "ts": "module"
113
- },
111
+ "extensions": [
112
+ "ts"
113
+ ],
114
114
  "timeout": "2m",
115
115
  "workerThreads": false,
116
116
  "files": [
package/src/api/build.ts CHANGED
@@ -874,6 +874,8 @@ class Builder {
874
874
  configDtsHeader: this.config.dtsHeader,
875
875
  configDtsHeaderFile: this.config.dtsHeaderFile,
876
876
  constEnum: this.options.constEnum ?? this.config.constEnum,
877
+ runtimeStringEnum:
878
+ this.options.runtimeStringEnum ?? this.config.runtimeStringEnum,
877
879
  cwd: this.options.cwd,
878
880
  })
879
881
 
@@ -1050,9 +1052,16 @@ export interface GenerateTypeDefOptions {
1050
1052
  configDtsHeader?: string
1051
1053
  configDtsHeaderFile?: string
1052
1054
  constEnum?: boolean
1055
+ runtimeStringEnum?: boolean
1053
1056
  cwd: string
1054
1057
  }
1055
1058
 
1059
+ /**
1060
+ * Walk the napi-derive intermediate type-def directory, render every entry
1061
+ * into TypeScript via {@link processTypeDef}, and return the concatenated
1062
+ * `.d.ts` source plus the list of identifiers to re-export from
1063
+ * `index.js`.
1064
+ */
1056
1065
  export async function generateTypeDef(
1057
1066
  options: GenerateTypeDefOptions,
1058
1067
  ): Promise<{ exports: string[]; dts: string }> {
@@ -1097,11 +1106,20 @@ export async function generateTypeDef(
1097
1106
  .filter((file) => file.isFile())
1098
1107
  .sort((a, b) => a.name.localeCompare(b.name))
1099
1108
 
1109
+ const constEnum = options.constEnum ?? true
1110
+ const runtimeStringEnum = options.runtimeStringEnum ?? false
1111
+ if (runtimeStringEnum && constEnum) {
1112
+ debug.warn(
1113
+ '`--runtime-string-enum` has no effect when `--const-enum` is enabled (the default). Pass `--no-const-enum` to activate runtime string enum emission.',
1114
+ )
1115
+ }
1116
+
1100
1117
  const processedTypeDefs = await Promise.all(
1101
1118
  typeDefFiles.map((file) =>
1102
1119
  processTypeDef(
1103
1120
  join(options.typeDefDir, file.name),
1104
- options.constEnum ?? true,
1121
+ constEnum,
1122
+ runtimeStringEnum,
1105
1123
  ),
1106
1124
  ),
1107
1125
  )
package/src/api/new.ts CHANGED
@@ -193,7 +193,7 @@ async function updateCargoTomlTypeDef(
193
193
  ? { version: napiDeriveDependency }
194
194
  : { ...napiDeriveDependency }
195
195
 
196
- const existingFeatures = Array.isArray(dependencyConfig.features)
196
+ const existingFeatures: string[] = Array.isArray(dependencyConfig.features)
197
197
  ? dependencyConfig.features.filter(
198
198
  (feature: unknown): feature is string => typeof feature === 'string',
199
199
  )
@@ -227,23 +227,33 @@ function requireNative() {
227
227
 
228
228
  nativeBinding = requireNative()
229
229
 
230
- if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
230
+ // NAPI_RS_FORCE_WASI is a tri-state flag:
231
+ // 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
234
+ // Treating any non-empty string as truthy (the historical behavior) meant
235
+ // NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered
236
+ // the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file.
237
+ const forceWasi =
238
+ process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error'
239
+
240
+ if (!nativeBinding || forceWasi) {
231
241
  let wasiBinding = null
232
242
  let wasiBindingError = null
233
243
  try {
234
244
  wasiBinding = require('./${localName}.wasi.cjs')
235
245
  nativeBinding = wasiBinding
236
246
  } catch (err) {
237
- if (process.env.NAPI_RS_FORCE_WASI) {
247
+ if (forceWasi) {
238
248
  wasiBindingError = err
239
249
  }
240
250
  }
241
- if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
251
+ if (!nativeBinding || forceWasi) {
242
252
  try {
243
253
  wasiBinding = require('${pkgName}-wasm32-wasi')
244
254
  nativeBinding = wasiBinding
245
255
  } catch (err) {
246
- if (process.env.NAPI_RS_FORCE_WASI) {
256
+ if (forceWasi) {
247
257
  if (!wasiBindingError) {
248
258
  wasiBindingError = err
249
259
  } else {
@@ -34,7 +34,7 @@ const __wasi = new __WASI({
34
34
  : ''
35
35
 
36
36
  const workerErrorHandler = errorEvent
37
- ? ` worker.addEventListener('error', (event) => {
37
+ ? ` worker.addEventListener('message', (event) => {
38
38
  if (event.data && typeof event.data === 'object' && event.data.type === 'error') {
39
39
  window.dispatchEvent(new CustomEvent('napi-rs-worker-error', { detail: event.data }))
40
40
  }
@@ -109,10 +109,9 @@ const fs = createFsProxy(__memfsExported)`
109
109
  errorOutputs.length = 0
110
110
  }`
111
111
  : ''
112
+ const errorOutputsDecl = errorEvent ? `\nconst errorOutputs = []\n` : ''
112
113
  return `${fsImport}
113
-
114
- const errorOutputs = []
115
-
114
+ ${errorOutputsDecl}
116
115
  const handler = new MessageHandler({
117
116
  onLoad({ wasmModule, wasmMemory }) {
118
117
  ${wasiCreation}
@@ -12,14 +12,32 @@ import { UniArchsByPlatform } from '../utils/target.js'
12
12
 
13
13
  const debug = debugFactory('universalize')
14
14
 
15
+ function createDarwinUniversalBinary(inputs: string[], output: string) {
16
+ const result = spawnSync('lipo', ['-create', '-output', output, ...inputs], {
17
+ stdio: 'inherit',
18
+ })
19
+
20
+ if (result.error) {
21
+ throw result.error
22
+ }
23
+
24
+ if (result.signal) {
25
+ throw new Error(
26
+ `Failed to create universal binary '${output}': lipo terminated with signal ${result.signal}.`,
27
+ )
28
+ }
29
+
30
+ if (result.status !== 0) {
31
+ throw new Error(
32
+ `Failed to create universal binary '${output}': lipo exited with status ${result.status}.`,
33
+ )
34
+ }
35
+ }
36
+
15
37
  const universalizers: Partial<
16
38
  Record<NodeJS.Platform, (inputs: string[], output: string) => void>
17
39
  > = {
18
- darwin: (inputs, output) => {
19
- spawnSync('lipo', ['-create', '-output', output, ...inputs], {
20
- stdio: 'inherit',
21
- })
22
- },
40
+ darwin: createDarwinUniversalBinary,
23
41
  }
24
42
 
25
43
  export async function universalizeBinaries(userOptions: UniversalizeOptions) {
@@ -77,5 +95,9 @@ export async function universalizeBinaries(userOptions: UniversalizeOptions) {
77
95
 
78
96
  universalizers[process.platform]?.(srcFiles, output)
79
97
 
98
+ if (!(await fileExists(output))) {
99
+ throw new Error(`Universal binary was not created: ${output}`)
100
+ }
101
+
80
102
  debug(`Produced universal binary: ${output}`)
81
103
  }
package/src/def/build.ts CHANGED
@@ -55,6 +55,11 @@ export abstract class BaseBuildCommand extends Command {
55
55
  description: 'Whether generate const enum for typescript bindings',
56
56
  })
57
57
 
58
+ runtimeStringEnum?: boolean = Option.Boolean('--runtime-string-enum', {
59
+ description:
60
+ 'Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union.',
61
+ })
62
+
58
63
  jsBinding?: string = Option.String('--js', {
59
64
  description:
60
65
  'Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`.',
@@ -157,6 +162,7 @@ export abstract class BaseBuildCommand extends Command {
157
162
  platform: this.platform,
158
163
  jsPackageName: this.jsPackageName,
159
164
  constEnum: this.constEnum,
165
+ runtimeStringEnum: this.runtimeStringEnum,
160
166
  jsBinding: this.jsBinding,
161
167
  noJsBinding: this.noJsBinding,
162
168
  dts: this.dts,
@@ -225,6 +231,10 @@ export interface BuildOptions {
225
231
  * Whether generate const enum for typescript bindings
226
232
  */
227
233
  constEnum?: boolean
234
+ /**
235
+ * Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union.
236
+ */
237
+ runtimeStringEnum?: boolean
228
238
  /**
229
239
  * Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`.
230
240
  */
@@ -42,6 +42,11 @@ export interface UserNapiConfig {
42
42
  */
43
43
  constEnum?: boolean
44
44
 
45
+ /**
46
+ * Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union.
47
+ */
48
+ runtimeStringEnum?: boolean
49
+
45
50
  /**
46
51
  * dts header prepend to the generated dts file
47
52
  */
@@ -155,7 +160,10 @@ export interface CommonPackageJsonFields {
155
160
  export type NapiConfig = Required<
156
161
  Pick<UserNapiConfig, 'binaryName' | 'packageName' | 'npmClient'>
157
162
  > &
158
- Pick<UserNapiConfig, 'wasm' | 'dtsHeader' | 'dtsHeaderFile' | 'constEnum'> & {
163
+ Pick<
164
+ UserNapiConfig,
165
+ 'wasm' | 'dtsHeader' | 'dtsHeaderFile' | 'constEnum' | 'runtimeStringEnum'
166
+ > & {
159
167
  targets: Target[]
160
168
  packageJson: CommonPackageJsonFields
161
169
  }
@@ -29,9 +29,26 @@ interface TypeDefLine {
29
29
  js_mod?: string
30
30
  }
31
31
 
32
+ /**
33
+ * Render a single intermediate type-def line as the TypeScript source it
34
+ * should produce in `index.d.ts`.
35
+ *
36
+ * @param line - The intermediate type-def entry to render.
37
+ * @param constEnum - When true, emit numeric and string `#[napi]` enums as
38
+ * `const enum`. When false (`--no-const-enum`), numeric enums become
39
+ * regular runtime enums and string enums fall back to a type-only union
40
+ * unless `runtimeStringEnum` is also true.
41
+ * @param runtimeStringEnum - When true under `--no-const-enum`, emit
42
+ * `#[napi(string_enum)]` as a runtime enum (`export declare enum`)
43
+ * instead of a type-only union. No-op when `constEnum` is true.
44
+ * @param ident - Indentation level applied to the rendered output.
45
+ * @param ambient - When true, emit declarations in the ambient form used
46
+ * inside `declare namespace` blocks (e.g. drop the `declare` keyword).
47
+ */
32
48
  function prettyPrint(
33
49
  line: TypeDefLine,
34
50
  constEnum: boolean,
51
+ runtimeStringEnum: boolean,
35
52
  ident: number,
36
53
  ambient = false,
37
54
  ): string {
@@ -45,18 +62,22 @@ function prettyPrint(
45
62
  s += `export type ${line.name} = \n${line.def}`
46
63
  break
47
64
 
48
- case TypeDefKind.Enum:
65
+ case TypeDefKind.Enum: {
49
66
  const enumName = constEnum ? 'const enum' : 'enum'
50
67
  s += `${exportDeclare(ambient)} ${enumName} ${line.name} {\n${line.def}\n}`
51
68
  break
69
+ }
52
70
 
53
- case TypeDefKind.StringEnum:
71
+ case TypeDefKind.StringEnum: {
54
72
  if (constEnum) {
55
73
  s += `${exportDeclare(ambient)} const enum ${line.name} {\n${line.def}\n}`
74
+ } else if (runtimeStringEnum) {
75
+ s += `${exportDeclare(ambient)} enum ${line.name} {\n${line.def}\n}`
56
76
  } else {
57
77
  s += `export type ${line.name} = ${line.def.replaceAll(/.*=/g, '').replaceAll(',', '|')};`
58
78
  }
59
79
  break
80
+ }
60
81
 
61
82
  case TypeDefKind.Struct:
62
83
  const extendsDef = line.extends ? ` extends ${line.extends}` : ''
@@ -97,9 +118,20 @@ function exportDeclare(ambient: boolean): string {
97
118
  return 'export declare'
98
119
  }
99
120
 
121
+ /**
122
+ * Read the napi-derive-emitted intermediate type-def file and render its
123
+ * entries into the `index.d.ts` source string plus the list of names to
124
+ * re-export from `index.js`.
125
+ *
126
+ * @param intermediateTypeFile - Path to the JSONL type-def file produced
127
+ * by napi-derive (one entry per `#[napi]` item).
128
+ * @param constEnum - See {@link prettyPrint}.
129
+ * @param runtimeStringEnum - See {@link prettyPrint}. Defaults to `false`.
130
+ */
100
131
  export async function processTypeDef(
101
132
  intermediateTypeFile: string,
102
133
  constEnum: boolean,
134
+ runtimeStringEnum: boolean = false,
103
135
  ) {
104
136
  const exports: string[] = []
105
137
  const defs = await readIntermediateTypeFile(intermediateTypeFile)
@@ -126,7 +158,7 @@ export async function processTypeDef(
126
158
  default:
127
159
  break
128
160
  }
129
- return prettyPrint(def, constEnum, 0)
161
+ return prettyPrint(def, constEnum, runtimeStringEnum, 0)
130
162
  })
131
163
  .join('\n\n')
132
164
  } else {
@@ -134,7 +166,8 @@ export async function processTypeDef(
134
166
  let declaration = ''
135
167
  declaration += `export declare namespace ${namespace} {\n`
136
168
  for (const def of defs) {
137
- declaration += prettyPrint(def, constEnum, 2, true) + '\n'
169
+ declaration +=
170
+ prettyPrint(def, constEnum, runtimeStringEnum, 2, true) + '\n'
138
171
  }
139
172
  declaration += '}'
140
173
  return declaration