@napi-rs/cli 3.6.0 → 3.6.2

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.0",
3
+ "version": "3.6.2",
4
4
  "description": "Cli tools for napi-rs",
5
5
  "author": "LongYinan <lynweklm@gmail.com>",
6
6
  "homepage": "https://napi.rs/",
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "devDependencies": {
77
77
  "@emnapi/runtime": "^1.9.1",
78
- "@oxc-node/core": "^0.0.35",
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",
@@ -84,7 +84,7 @@
84
84
  "ava": "^7.0.0",
85
85
  "empathic": "^2.0.0",
86
86
  "env-paths": "^4.0.0",
87
- "oxc-parser": "^0.121.0",
87
+ "oxc-parser": "^0.125.0",
88
88
  "prettier": "^3.6.2",
89
89
  "tsdown": "^0.21.0",
90
90
  "tslib": "^2.8.1",
package/src/api/build.ts CHANGED
@@ -381,35 +381,11 @@ class Builder {
381
381
  set = true
382
382
  }
383
383
  } else {
384
- if (
385
- this.target.platform === 'linux' &&
386
- process.platform === 'linux' &&
387
- this.target.arch === process.arch &&
388
- (function (abi: string | null) {
389
- const glibcVersionRuntime =
390
- // @ts-expect-error
391
- process.report?.getReport()?.header?.glibcVersionRuntime
392
- const libc = glibcVersionRuntime ? 'gnu' : 'musl'
393
- return abi === libc
394
- })(this.target.abi)
395
- ) {
396
- debug.warn(
397
- 'You are trying to cross compile to linux target on linux platform which is unnecessary.',
398
- )
399
- } else if (
400
- this.target.platform === 'darwin' &&
401
- process.platform === 'darwin'
402
- ) {
403
- debug.warn(
404
- 'You are trying to cross compile to darwin target on darwin platform which is unnecessary.',
405
- )
406
- } else {
407
- // use cargo-zigbuild to cross compile to other platforms
408
- debug('Use %i', 'cargo-zigbuild')
409
- tryInstallCargoBinary('cargo-zigbuild', 'zigbuild')
410
- this.args.push('zigbuild')
411
- set = true
412
- }
384
+ // use cargo-zigbuild to cross compile to other platforms
385
+ debug('Use %i', 'cargo-zigbuild')
386
+ tryInstallCargoBinary('cargo-zigbuild', 'zigbuild')
387
+ this.args.push('zigbuild')
388
+ set = true
413
389
  }
414
390
  }
415
391
 
@@ -566,7 +542,7 @@ class Builder {
566
542
  require.resolve('emnapi'),
567
543
  '..',
568
544
  'lib',
569
- 'wasm32-wasi-threads',
545
+ 'wasm32-wasip1-threads',
570
546
  )
571
547
  this.envs.EMNAPI_LINK_DIR = emnapi
572
548
  const emnapiVersion = require('emnapi/package.json').version
@@ -617,15 +593,15 @@ class Builder {
617
593
  )
618
594
  this.setEnvIfNotExists(
619
595
  'TARGET_CFLAGS',
620
- `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`,
596
+ `--target=wasm32-wasip1-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`,
621
597
  )
622
598
  this.setEnvIfNotExists(
623
599
  'TARGET_CXXFLAGS',
624
- `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`,
600
+ `--target=wasm32-wasip1-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`,
625
601
  )
626
602
  this.setEnvIfNotExists(
627
603
  `TARGET_LDFLAGS`,
628
- `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`,
604
+ `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasip1-threads`,
629
605
  )
630
606
  }
631
607
  }
@@ -1117,19 +1093,21 @@ export async function generateTypeDef(
1117
1093
  return { exports: [], dts: '' }
1118
1094
  }
1119
1095
 
1120
- for (const file of files) {
1121
- if (!file.isFile()) {
1122
- continue
1123
- }
1124
-
1125
- const { dts: fileDts, exports: fileExports } = await processTypeDef(
1126
- join(options.typeDefDir, file.name),
1127
- options.constEnum ?? true,
1128
- )
1096
+ const typeDefFiles = files
1097
+ .filter((file) => file.isFile())
1098
+ .sort((a, b) => a.name.localeCompare(b.name))
1099
+
1100
+ const processedTypeDefs = await Promise.all(
1101
+ typeDefFiles.map((file) =>
1102
+ processTypeDef(
1103
+ join(options.typeDefDir, file.name),
1104
+ options.constEnum ?? true,
1105
+ ),
1106
+ ),
1107
+ )
1129
1108
 
1130
- dts += fileDts
1131
- exports.push(...fileExports)
1132
- }
1109
+ dts = processedTypeDefs.map(({ dts }) => dts).join('')
1110
+ exports = processedTypeDefs.flatMap(({ exports }) => exports)
1133
1111
 
1134
1112
  if (dts.indexOf('ExternalObject<') > -1) {
1135
1113
  header += `
@@ -1,9 +1,10 @@
1
1
  import { createRequire } from 'node:module'
2
2
  import { join, resolve } from 'node:path'
3
3
 
4
- import { parse } from 'semver'
4
+ import { Comparator, Range, minVersion, subset } from 'semver'
5
5
 
6
6
  const require = createRequire(import.meta.url)
7
+ const minimumWasiNodeVersion = '>=14.0.0'
7
8
 
8
9
  import {
9
10
  applyDefaultCreateNpmDirsOptions,
@@ -25,6 +26,52 @@ export interface PackageMeta {
25
26
  'dist-tags': { [index: string]: string }
26
27
  }
27
28
 
29
+ const WASM_RUNTIME_PACKAGE_NAME = '@napi-rs/wasm-runtime'
30
+
31
+ async function getLatestWasmRuntimeVersion() {
32
+ const npmRegistryBase =
33
+ process.env.npm_config_registry?.replace(/\/?$/, '/') ??
34
+ 'https://registry.npmjs.org/'
35
+ const packageMetadataUrl = `${npmRegistryBase}${WASM_RUNTIME_PACKAGE_NAME}`
36
+ let response: Response
37
+
38
+ try {
39
+ response = await fetch(packageMetadataUrl)
40
+ } catch (error) {
41
+ throw new Error(
42
+ `Failed to fetch ${packageMetadataUrl} while resolving ${WASM_RUNTIME_PACKAGE_NAME}. Check your network connection and npm registry availability.`,
43
+ { cause: error },
44
+ )
45
+ }
46
+
47
+ if (!response.ok) {
48
+ throw new Error(
49
+ `Failed to fetch ${packageMetadataUrl} while resolving ${WASM_RUNTIME_PACKAGE_NAME}: npm registry responded with ${response.status} ${response.statusText || 'Unknown Status'}`,
50
+ )
51
+ }
52
+
53
+ let packageMeta: PackageMeta
54
+
55
+ try {
56
+ packageMeta = (await response.json()) as PackageMeta
57
+ } catch (error) {
58
+ throw new Error(
59
+ `Failed to parse npm registry metadata for ${WASM_RUNTIME_PACKAGE_NAME} from ${packageMetadataUrl}`,
60
+ { cause: error },
61
+ )
62
+ }
63
+
64
+ const latestVersion = packageMeta['dist-tags']?.latest
65
+
66
+ if (typeof latestVersion !== 'string' || latestVersion.trim().length === 0) {
67
+ throw new Error(
68
+ `npm registry metadata for ${WASM_RUNTIME_PACKAGE_NAME} from ${packageMetadataUrl} did not include a latest dist-tag`,
69
+ )
70
+ }
71
+
72
+ return latestVersion.trim()
73
+ }
74
+
28
75
  export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
29
76
  const options = applyDefaultCreateNpmDirsOptions(userOptions)
30
77
 
@@ -60,6 +107,9 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
60
107
  packageJsonPath,
61
108
  options.configPath ? resolve(options.cwd, options.configPath) : undefined,
62
109
  )
110
+ const wasmRuntimeVersion = targets.some((target) => target.arch === 'wasm32')
111
+ ? await getLatestWasmRuntimeVersion()
112
+ : undefined
63
113
 
64
114
  for (const target of targets) {
65
115
  const targetDir = join(npmPath, `${target.platformArchABI}`)
@@ -107,30 +157,15 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
107
157
  `wasi-worker.mjs`,
108
158
  `wasi-worker-browser.mjs`,
109
159
  )
110
- let needRestrictNodeVersion = true
111
- if (scopedPackageJson.engines?.node) {
112
- try {
113
- const { major } = parse(scopedPackageJson.engines.node) ?? {
114
- major: 0,
115
- }
116
- if (major >= 14) {
117
- needRestrictNodeVersion = false
118
- }
119
- } catch {
120
- // ignore
121
- }
122
- }
123
- if (needRestrictNodeVersion) {
124
- scopedPackageJson.engines = {
125
- node: '>=14.0.0',
126
- }
160
+ scopedPackageJson.engines = {
161
+ ...scopedPackageJson.engines,
162
+ node: scopedPackageJson.engines?.node
163
+ ? restrictWasiNodeEngine(scopedPackageJson.engines.node)
164
+ : minimumWasiNodeVersion,
127
165
  }
128
166
  const emnapiVersion = require('emnapi/package.json').version
129
- const wasmRuntime = await fetch(
130
- `https://registry.npmjs.org/@napi-rs/wasm-runtime`,
131
- ).then((res) => res.json() as Promise<PackageMeta>)
132
167
  scopedPackageJson.dependencies = {
133
- '@napi-rs/wasm-runtime': `^${wasmRuntime['dist-tags'].latest}`,
168
+ '@napi-rs/wasm-runtime': `^${wasmRuntimeVersion}`,
134
169
  '@emnapi/core': emnapiVersion,
135
170
  '@emnapi/runtime': emnapiVersion,
136
171
  }
@@ -160,3 +195,69 @@ function readme(packageName: string, target: Target) {
160
195
  This is the **${target.triple}** binary for \`${packageName}\`
161
196
  `
162
197
  }
198
+
199
+ function restrictWasiNodeEngine(nodeRange: string) {
200
+ try {
201
+ if (subset(nodeRange, minimumWasiNodeVersion)) {
202
+ return nodeRange
203
+ }
204
+
205
+ if (subset(minimumWasiNodeVersion, nodeRange)) {
206
+ return minimumWasiNodeVersion
207
+ }
208
+
209
+ const minimumComparator = new Comparator(minimumWasiNodeVersion)
210
+ const restrictedRangeSets = new Range(nodeRange).set
211
+ .map((comparators) =>
212
+ normalizeComparatorSet([...comparators, minimumComparator]),
213
+ )
214
+ .filter(
215
+ (candidate): candidate is string =>
216
+ candidate !== undefined && minVersion(candidate) !== null,
217
+ )
218
+
219
+ if (restrictedRangeSets.length > 0) {
220
+ return restrictedRangeSets.join(' || ')
221
+ }
222
+ } catch {
223
+ // ignore
224
+ }
225
+
226
+ return minimumWasiNodeVersion
227
+ }
228
+
229
+ function normalizeComparatorSet(comparators: Comparator[]) {
230
+ const exactMatch = comparators.find(({ operator }) => operator === '')
231
+ if (exactMatch) {
232
+ return comparators.every((comparator) => comparator.test(exactMatch.semver))
233
+ ? exactMatch.value
234
+ : undefined
235
+ }
236
+
237
+ let lowerBound: Comparator | undefined
238
+ let upperBound: Comparator | undefined
239
+
240
+ for (const comparator of comparators) {
241
+ if (comparator.operator === '>' || comparator.operator === '>=') {
242
+ if (
243
+ !lowerBound ||
244
+ comparator.semver.compare(lowerBound.semver) > 0 ||
245
+ (comparator.semver.compare(lowerBound.semver) === 0 &&
246
+ comparator.operator === '>')
247
+ ) {
248
+ lowerBound = comparator
249
+ }
250
+ } else if (comparator.operator === '<' || comparator.operator === '<=') {
251
+ if (
252
+ !upperBound ||
253
+ comparator.semver.compare(upperBound.semver) < 0 ||
254
+ (comparator.semver.compare(upperBound.semver) === 0 &&
255
+ comparator.operator === '<')
256
+ ) {
257
+ upperBound = comparator
258
+ }
259
+ }
260
+ }
261
+
262
+ return [lowerBound?.value, upperBound?.value].filter(Boolean).join(' ')
263
+ }
package/src/api/new.ts CHANGED
@@ -4,6 +4,7 @@ import { homedir } from 'node:os'
4
4
  import path from 'node:path'
5
5
  import { promises as fs } from 'node:fs'
6
6
 
7
+ import { parse as parseToml, stringify as stringifyToml } from '@std/toml'
7
8
  import { load as yamlLoad, dump as yamlDump } from 'js-yaml'
8
9
 
9
10
  import {
@@ -15,6 +16,7 @@ import {
15
16
  debugFactory,
16
17
  DEFAULT_TARGETS,
17
18
  mkdirAsync,
19
+ parseTriple,
18
20
  readdirAsync,
19
21
  statAsync,
20
22
  type SupportedPackageManager,
@@ -33,25 +35,18 @@ const TEMPLATE_REPOS = {
33
35
  pnpm: 'https://github.com/napi-rs/package-template-pnpm',
34
36
  } as const
35
37
 
38
+ const WASI_TARGET = 'wasm32-wasip1-threads'
39
+
36
40
  async function checkGitCommand(): Promise<boolean> {
37
- try {
38
- await new Promise((resolve) => {
39
- const cp = exec('git --version')
40
- cp.on('error', () => {
41
- resolve(false)
42
- })
43
- cp.on('exit', (code) => {
44
- if (code === 0) {
45
- resolve(true)
46
- } else {
47
- resolve(false)
48
- }
49
- })
41
+ return new Promise<boolean>((resolve) => {
42
+ const cp = exec('git --version')
43
+ cp.on('error', () => {
44
+ resolve(false)
50
45
  })
51
- return true
52
- } catch {
53
- return false
54
- }
46
+ cp.on('exit', (code) => {
47
+ resolve(code === 0)
48
+ })
49
+ })
55
50
  }
56
51
 
57
52
  async function ensureCacheDir(
@@ -132,9 +127,9 @@ async function copyDirectory(
132
127
  !includeWasiBindings &&
133
128
  (entry.name.endsWith('.wasi-browser.js') ||
134
129
  entry.name.endsWith('.wasi.cjs') ||
135
- entry.name.endsWith('wasi-worker.browser.mjs ') ||
130
+ entry.name.endsWith('wasi-worker-browser.mjs') ||
136
131
  entry.name.endsWith('wasi-worker.mjs') ||
137
- entry.name.endsWith('browser.js'))
132
+ entry.name === 'browser.js')
138
133
  ) {
139
134
  continue
140
135
  }
@@ -149,6 +144,7 @@ async function filterTargetsInPackageJson(
149
144
  ): Promise<void> {
150
145
  const content = await fs.readFile(filePath, 'utf-8')
151
146
  const packageJson = JSON.parse(content)
147
+ const includeWasiBindings = enabledTargets.includes(WASI_TARGET)
152
148
 
153
149
  // Filter napi.targets
154
150
  if (packageJson.napi?.targets) {
@@ -157,9 +153,63 @@ async function filterTargetsInPackageJson(
157
153
  )
158
154
  }
159
155
 
156
+ if (!includeWasiBindings) {
157
+ if (
158
+ packageJson.browser === 'browser.js' ||
159
+ packageJson.browser === './browser.js'
160
+ ) {
161
+ delete packageJson.browser
162
+ }
163
+
164
+ if (Array.isArray(packageJson.files)) {
165
+ packageJson.files = packageJson.files.filter(
166
+ (file: unknown) => file !== 'browser.js' && file !== './browser.js',
167
+ )
168
+ }
169
+ }
170
+
160
171
  await fs.writeFile(filePath, JSON.stringify(packageJson, null, 2) + '\n')
161
172
  }
162
173
 
174
+ async function updateCargoTomlTypeDef(
175
+ filePath: string,
176
+ enableTypeDef: boolean,
177
+ ): Promise<void> {
178
+ if (enableTypeDef) {
179
+ return
180
+ }
181
+
182
+ const content = await fs.readFile(filePath, 'utf-8')
183
+ const cargoToml = parseToml(content) as Record<string, any>
184
+ const dependencies = cargoToml.dependencies
185
+
186
+ if (!dependencies || !dependencies['napi-derive']) {
187
+ return
188
+ }
189
+
190
+ const napiDeriveDependency = dependencies['napi-derive']
191
+ const dependencyConfig =
192
+ typeof napiDeriveDependency === 'string'
193
+ ? { version: napiDeriveDependency }
194
+ : { ...napiDeriveDependency }
195
+
196
+ const existingFeatures = Array.isArray(dependencyConfig.features)
197
+ ? dependencyConfig.features.filter(
198
+ (feature: unknown): feature is string => typeof feature === 'string',
199
+ )
200
+ : []
201
+
202
+ dependencyConfig['default-features'] = false
203
+ dependencyConfig.features = [
204
+ 'strict',
205
+ ...existingFeatures.filter((feature) => feature !== 'strict'),
206
+ ].filter((feature) => feature !== 'type-def')
207
+
208
+ dependencies['napi-derive'] = dependencyConfig
209
+
210
+ await fs.writeFile(filePath, stringifyToml(cargoToml))
211
+ }
212
+
163
213
  async function filterTargetsInGithubActions(
164
214
  filePath: string,
165
215
  enabledTargets: string[],
@@ -167,13 +217,6 @@ async function filterTargetsInGithubActions(
167
217
  const content = await fs.readFile(filePath, 'utf-8')
168
218
  const yaml = yamlLoad(content) as any
169
219
 
170
- const macOSAndWindowsTargets = new Set([
171
- 'x86_64-pc-windows-msvc',
172
- 'x86_64-pc-windows-gnu',
173
- 'aarch64-pc-windows-msvc',
174
- 'x86_64-apple-darwin',
175
- ])
176
-
177
220
  const linuxTargets = new Set([
178
221
  'x86_64-unknown-linux-gnu',
179
222
  'x86_64-unknown-linux-musl',
@@ -193,6 +236,10 @@ async function filterTargetsInGithubActions(
193
236
  const hasLinuxTargets = enabledTargets.some((target) =>
194
237
  linuxTargets.has(target),
195
238
  )
239
+ const hasMacOSOrWindowsTargets = enabledTargets.some((target) => {
240
+ const platform = parseTriple(target).platform
241
+ return platform === 'darwin' || platform === 'win32'
242
+ })
196
243
 
197
244
  // Filter the matrix configurations in the build job
198
245
  if (yaml?.jobs?.build?.strategy?.matrix?.settings) {
@@ -207,7 +254,7 @@ async function filterTargetsInGithubActions(
207
254
 
208
255
  const jobsToRemove: string[] = []
209
256
 
210
- if (enabledTargets.every((target) => !macOSAndWindowsTargets.has(target))) {
257
+ if (!hasMacOSOrWindowsTargets) {
211
258
  jobsToRemove.push('test-macOS-windows-binding')
212
259
  } else {
213
260
  // Filter the matrix configurations in the test-macOS-windows-binding job
@@ -246,7 +293,7 @@ async function filterTargetsInGithubActions(
246
293
  }
247
294
  }
248
295
 
249
- if (!enabledTargets.includes('wasm32-wasip1-threads')) {
296
+ if (!enabledTargets.includes(WASI_TARGET)) {
250
297
  jobsToRemove.push('test-wasi')
251
298
  }
252
299
 
@@ -322,11 +369,9 @@ function processOptions(options: RawNewOptions) {
322
369
  const out = execSync(`rustup target list`, {
323
370
  encoding: 'utf8',
324
371
  })
325
- if (out.includes('wasm32-wasip1-threads')) {
372
+ if (out.includes(WASI_TARGET)) {
326
373
  options.targets = options.targets.map((target) =>
327
- target === 'wasm32-wasi-preview1-threads'
328
- ? 'wasm32-wasip1-threads'
329
- : target,
374
+ target === 'wasm32-wasi-preview1-threads' ? WASI_TARGET : target,
330
375
  )
331
376
  }
332
377
  }
@@ -366,7 +411,7 @@ export async function newProject(userOptions: RawNewOptions) {
366
411
  await copyDirectory(
367
412
  templatePath,
368
413
  options.path,
369
- options.targets.includes('wasm32-wasip1-threads'),
414
+ options.targets.includes(WASI_TARGET),
370
415
  )
371
416
 
372
417
  // Rename project using the rename API
@@ -376,6 +421,11 @@ export async function newProject(userOptions: RawNewOptions) {
376
421
  binaryName: getBinaryName(options.name),
377
422
  })
378
423
 
424
+ const cargoTomlPath = path.join(options.path, 'Cargo.toml')
425
+ if (existsSync(cargoTomlPath)) {
426
+ await updateCargoTomlTypeDef(cargoTomlPath, options.enableTypeDef)
427
+ }
428
+
379
429
  // Filter targets in package.json
380
430
  const packageJsonPath = path.join(options.path, 'package.json')
381
431
  if (existsSync(packageJsonPath)) {
@@ -85,6 +85,7 @@ export async function prePublish(userOptions: PrePublishOptions) {
85
85
 
86
86
  function getRepoInfo(packageName: string, version: string) {
87
87
  const headCommit = execSync('git log -1 --pretty=%B', {
88
+ cwd: options.cwd,
88
89
  encoding: 'utf-8',
89
90
  }).trim()
90
91
 
package/src/api/rename.ts CHANGED
@@ -41,12 +41,32 @@ export async function renameProject(userOptions: RenameOptions) {
41
41
  },
42
42
  )
43
43
 
44
+ if (options.repository) {
45
+ if (
46
+ packageJsonData.repository &&
47
+ typeof packageJsonData.repository === 'object' &&
48
+ !Array.isArray(packageJsonData.repository)
49
+ ) {
50
+ packageJsonData.repository.url = options.repository
51
+ } else {
52
+ packageJsonData.repository = options.repository
53
+ }
54
+ }
55
+
44
56
  if (options.configPath) {
45
57
  const configPath = resolve(options.cwd, options.configPath)
46
58
  const configContent = await readFileAsync(configPath, 'utf8')
47
59
  const configData = JSON.parse(configContent)
48
- configData.binaryName = options.binaryName
49
- configData.packageName = options.packageName
60
+ merge(
61
+ configData,
62
+ omitBy(
63
+ {
64
+ binaryName: options.binaryName,
65
+ packageName: options.packageName,
66
+ },
67
+ isNil,
68
+ ),
69
+ )
50
70
  await writeFileAsync(configPath, JSON.stringify(configData, null, 2))
51
71
  }
52
72
 
@@ -73,7 +93,7 @@ export async function renameProject(userOptions: RenameOptions) {
73
93
  const updatedTomlContent = stringifyToml(cargoToml)
74
94
 
75
95
  await writeFileAsync(cargoTomlPath, updatedTomlContent)
76
- if (oldName !== options.binaryName) {
96
+ if (options.binaryName && oldName !== options.binaryName) {
77
97
  const githubActionsPath = find.dir('.github', {
78
98
  cwd: options.cwd,
79
99
  })
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process'
2
- import { join, resolve } from 'node:path'
2
+ import { resolve } from 'node:path'
3
3
 
4
4
  import {
5
5
  applyDefaultUniversalizeOptions,
@@ -25,7 +25,7 @@ const universalizers: Partial<
25
25
  export async function universalizeBinaries(userOptions: UniversalizeOptions) {
26
26
  const options = applyDefaultUniversalizeOptions(userOptions)
27
27
 
28
- const packageJsonPath = join(options.cwd, options.packageJsonPath)
28
+ const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
29
29
 
30
30
  const config = await readNapiConfig(
31
31
  packageJsonPath,
@@ -35,6 +35,7 @@ export class BuildCommand extends BaseBuildCommand {
35
35
  } catch (e) {
36
36
  debug.error(`Failed to pipe output file ${output.path} to command`)
37
37
  debug.error(e)
38
+ throw e
38
39
  }
39
40
  }
40
41
  }
@@ -11,10 +11,20 @@ import {
11
11
  DEFAULT_TARGETS,
12
12
  type TargetTriple,
13
13
  } from '../utils/index.js'
14
- import { napiEngineRequirement } from '../utils/version.js'
14
+ import {
15
+ napiEngineRequirement,
16
+ SUPPORTED_NAPI_VERSIONS,
17
+ } from '../utils/version.js'
15
18
 
16
19
  const debug = debugFactory('new')
17
20
 
21
+ export function getNapiVersionChoices() {
22
+ return SUPPORTED_NAPI_VERSIONS.map((version) => ({
23
+ name: `napi${version} (${napiEngineRequirement(version)})`,
24
+ value: version,
25
+ }))
26
+ }
27
+
18
28
  export class NewCommand extends BaseNewCommand {
19
29
  interactive = Option.Boolean('--interactive,-i', true, {
20
30
  description:
@@ -77,10 +87,7 @@ export class NewCommand extends BaseNewCommand {
77
87
  message: 'Minimum node-api version (with node version requirement)',
78
88
  loop: false,
79
89
  pageSize: 10,
80
- choices: Array.from({ length: 8 }, (_, i) => ({
81
- name: `napi${i + 1} (${napiEngineRequirement(i + 1)})`,
82
- value: i + 1,
83
- })),
90
+ choices: getNapiVersionChoices(),
84
91
  // choice index
85
92
  default: this.minNodeApiVersion - 1,
86
93
  })
@@ -62,7 +62,7 @@ export async function parseMetadata(manifestPath: string) {
62
62
  let stdout = ''
63
63
  let stderr = ''
64
64
  let status = 0
65
- let error = null
65
+ let error: Error | null = null
66
66
 
67
67
  childProcess.stdout.on('data', (data) => {
68
68
  stdout += data
@@ -72,6 +72,10 @@ export async function parseMetadata(manifestPath: string) {
72
72
  stderr += data
73
73
  })
74
74
 
75
+ childProcess.on('error', (err) => {
76
+ error = err
77
+ })
78
+
75
79
  await new Promise<void>((resolve) => {
76
80
  childProcess.on('close', (code) => {
77
81
  status = code ?? 0