@napi-rs/cli 3.5.1 → 3.6.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.5.1",
3
+ "version": "3.6.1",
4
4
  "description": "Cli tools for napi-rs",
5
5
  "author": "LongYinan <lynweklm@gmail.com>",
6
6
  "homepage": "https://napi.rs/",
@@ -66,7 +66,7 @@
66
66
  "@octokit/rest": "^22.0.1",
67
67
  "clipanion": "^4.0.0-rc.4",
68
68
  "colorette": "^2.0.20",
69
- "emnapi": "^1.7.1",
69
+ "emnapi": "^1.9.1",
70
70
  "es-toolkit": "^1.41.0",
71
71
  "js-yaml": "^4.1.0",
72
72
  "obug": "^2.0.0",
@@ -74,18 +74,19 @@
74
74
  "typanion": "^3.14.0"
75
75
  },
76
76
  "devDependencies": {
77
- "@emnapi/runtime": "^1.7.1",
78
- "@oxc-node/core": "^0.0.35",
77
+ "@emnapi/runtime": "^1.9.1",
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
82
  "@types/node": "^24.10.0",
83
83
  "@types/semver": "^7.7.1",
84
- "ava": "^6.4.1",
84
+ "ava": "^7.0.0",
85
85
  "empathic": "^2.0.0",
86
- "env-paths": "^3.0.0",
86
+ "env-paths": "^4.0.0",
87
+ "oxc-parser": "^0.124.0",
87
88
  "prettier": "^3.6.2",
88
- "tsdown": "^0.18.0",
89
+ "tsdown": "^0.21.0",
89
90
  "tslib": "^2.8.1",
90
91
  "typescript": "^5.9.3"
91
92
  },
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
 
@@ -569,6 +545,19 @@ class Builder {
569
545
  'wasm32-wasi-threads',
570
546
  )
571
547
  this.envs.EMNAPI_LINK_DIR = emnapi
548
+ const emnapiVersion = require('emnapi/package.json').version
549
+ const projectRequire = createRequire(join(this.options.cwd, 'package.json'))
550
+ const emnapiCoreVersion = projectRequire('@emnapi/core').version
551
+ const emnapiRuntimeVersion = projectRequire('@emnapi/runtime').version
552
+
553
+ if (
554
+ emnapiVersion !== emnapiCoreVersion ||
555
+ emnapiVersion !== emnapiRuntimeVersion
556
+ ) {
557
+ throw new Error(
558
+ `emnapi version mismatch: emnapi@${emnapiVersion}, @emnapi/core@${emnapiCoreVersion}, @emnapi/runtime@${emnapiRuntimeVersion}. Please ensure all emnapi packages are the same version.`,
559
+ )
560
+ }
572
561
  const { WASI_SDK_PATH } = process.env
573
562
 
574
563
  if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
@@ -964,6 +953,7 @@ class Builder {
964
953
  this.config.wasm?.browser?.fs,
965
954
  this.config.wasm?.browser?.asyncInit,
966
955
  this.config.wasm?.browser?.buffer,
956
+ this.config.wasm?.browser?.errorEvent,
967
957
  ) +
968
958
  `export default __napiModule.exports\n` +
969
959
  idents
@@ -978,7 +968,10 @@ class Builder {
978
968
  await writeFileAsync(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
979
969
  await writeFileAsync(
980
970
  browserWorkerPath,
981
- createWasiBrowserWorkerBinding(this.config.wasm?.browser?.fs ?? false),
971
+ createWasiBrowserWorkerBinding(
972
+ this.config.wasm?.browser?.fs ?? false,
973
+ this.config.wasm?.browser?.errorEvent ?? false,
974
+ ),
982
975
  'utf8',
983
976
  )
984
977
  await writeFileAsync(
@@ -1,7 +1,10 @@
1
+ import { createRequire } from 'node:module'
1
2
  import { join, resolve } from 'node:path'
2
3
 
3
4
  import { parse } from 'semver'
4
5
 
6
+ const require = createRequire(import.meta.url)
7
+
5
8
  import {
6
9
  applyDefaultCreateNpmDirsOptions,
7
10
  type CreateNpmDirsOptions,
@@ -22,6 +25,52 @@ export interface PackageMeta {
22
25
  'dist-tags': { [index: string]: string }
23
26
  }
24
27
 
28
+ const WASM_RUNTIME_PACKAGE_NAME = '@napi-rs/wasm-runtime'
29
+
30
+ async function getLatestWasmRuntimeVersion() {
31
+ const npmRegistryBase =
32
+ process.env.npm_config_registry?.replace(/\/?$/, '/') ??
33
+ 'https://registry.npmjs.org/'
34
+ const packageMetadataUrl = `${npmRegistryBase}${WASM_RUNTIME_PACKAGE_NAME}`
35
+ let response: Response
36
+
37
+ try {
38
+ response = await fetch(packageMetadataUrl)
39
+ } catch (error) {
40
+ throw new Error(
41
+ `Failed to fetch ${packageMetadataUrl} while resolving ${WASM_RUNTIME_PACKAGE_NAME}. Check your network connection and npm registry availability.`,
42
+ { cause: error },
43
+ )
44
+ }
45
+
46
+ if (!response.ok) {
47
+ throw new Error(
48
+ `Failed to fetch ${packageMetadataUrl} while resolving ${WASM_RUNTIME_PACKAGE_NAME}: npm registry responded with ${response.status} ${response.statusText || 'Unknown Status'}`,
49
+ )
50
+ }
51
+
52
+ let packageMeta: PackageMeta
53
+
54
+ try {
55
+ packageMeta = (await response.json()) as PackageMeta
56
+ } catch (error) {
57
+ throw new Error(
58
+ `Failed to parse npm registry metadata for ${WASM_RUNTIME_PACKAGE_NAME} from ${packageMetadataUrl}`,
59
+ { cause: error },
60
+ )
61
+ }
62
+
63
+ const latestVersion = packageMeta['dist-tags']?.latest
64
+
65
+ if (typeof latestVersion !== 'string' || latestVersion.trim().length === 0) {
66
+ throw new Error(
67
+ `npm registry metadata for ${WASM_RUNTIME_PACKAGE_NAME} from ${packageMetadataUrl} did not include a latest dist-tag`,
68
+ )
69
+ }
70
+
71
+ return latestVersion.trim()
72
+ }
73
+
25
74
  export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
26
75
  const options = applyDefaultCreateNpmDirsOptions(userOptions)
27
76
 
@@ -57,6 +106,9 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
57
106
  packageJsonPath,
58
107
  options.configPath ? resolve(options.cwd, options.configPath) : undefined,
59
108
  )
109
+ const wasmRuntimeVersion = targets.some((target) => target.arch === 'wasm32')
110
+ ? await getLatestWasmRuntimeVersion()
111
+ : undefined
60
112
 
61
113
  for (const target of targets) {
62
114
  const targetDir = join(npmPath, `${target.platformArchABI}`)
@@ -122,11 +174,11 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
122
174
  node: '>=14.0.0',
123
175
  }
124
176
  }
125
- const wasmRuntime = await fetch(
126
- `https://registry.npmjs.org/@napi-rs/wasm-runtime`,
127
- ).then((res) => res.json() as Promise<PackageMeta>)
177
+ const emnapiVersion = require('emnapi/package.json').version
128
178
  scopedPackageJson.dependencies = {
129
- '@napi-rs/wasm-runtime': `^${wasmRuntime['dist-tags'].latest}`,
179
+ '@napi-rs/wasm-runtime': `^${wasmRuntimeVersion}`,
180
+ '@emnapi/core': emnapiVersion,
181
+ '@emnapi/runtime': emnapiVersion,
130
182
  }
131
183
  }
132
184
 
package/src/api/new.ts CHANGED
@@ -34,24 +34,15 @@ const TEMPLATE_REPOS = {
34
34
  } as const
35
35
 
36
36
  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
- })
37
+ return new Promise<boolean>((resolve) => {
38
+ const cp = exec('git --version')
39
+ cp.on('error', () => {
40
+ resolve(false)
50
41
  })
51
- return true
52
- } catch {
53
- return false
54
- }
42
+ cp.on('exit', (code) => {
43
+ resolve(code === 0)
44
+ })
45
+ })
55
46
  }
56
47
 
57
48
  async function ensureCacheDir(
@@ -132,7 +123,7 @@ async function copyDirectory(
132
123
  !includeWasiBindings &&
133
124
  (entry.name.endsWith('.wasi-browser.js') ||
134
125
  entry.name.endsWith('.wasi.cjs') ||
135
- entry.name.endsWith('wasi-worker.browser.mjs ') ||
126
+ entry.name.endsWith('wasi-worker-browser.mjs') ||
136
127
  entry.name.endsWith('wasi-worker.mjs') ||
137
128
  entry.name.endsWith('browser.js'))
138
129
  ) {
package/src/api/rename.ts CHANGED
@@ -45,8 +45,16 @@ export async function renameProject(userOptions: RenameOptions) {
45
45
  const configPath = resolve(options.cwd, options.configPath)
46
46
  const configContent = await readFileAsync(configPath, 'utf8')
47
47
  const configData = JSON.parse(configContent)
48
- configData.binaryName = options.binaryName
49
- configData.packageName = options.packageName
48
+ merge(
49
+ configData,
50
+ omitBy(
51
+ {
52
+ binaryName: options.binaryName,
53
+ packageName: options.packageName,
54
+ },
55
+ isNil,
56
+ ),
57
+ )
50
58
  await writeFileAsync(configPath, JSON.stringify(configData, null, 2))
51
59
  }
52
60
 
@@ -73,7 +81,7 @@ export async function renameProject(userOptions: RenameOptions) {
73
81
  const updatedTomlContent = stringifyToml(cargoToml)
74
82
 
75
83
  await writeFileAsync(cargoTomlPath, updatedTomlContent)
76
- if (oldName !== options.binaryName) {
84
+ if (options.binaryName && oldName !== options.binaryName) {
77
85
  const githubActionsPath = find.dir('.github', {
78
86
  cwd: options.cwd,
79
87
  })
@@ -5,6 +5,7 @@ export const createWasiBrowserBinding = (
5
5
  fs = false,
6
6
  asyncInit = false,
7
7
  buffer = false,
8
+ errorEvent = false,
8
9
  ) => {
9
10
  const fsImport = fs
10
11
  ? buffer
@@ -32,6 +33,15 @@ const __wasi = new __WASI({
32
33
  ? ` worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n`
33
34
  : ''
34
35
 
36
+ const workerErrorHandler = errorEvent
37
+ ? ` worker.addEventListener('error', (event) => {
38
+ if (event.data && typeof event.data === 'object' && event.data.type === 'error') {
39
+ window.dispatchEvent(new CustomEvent('napi-rs-worker-error', { detail: event.data }))
40
+ }
41
+ })
42
+ `
43
+ : ''
44
+
35
45
  const emnapiInjectBuffer = buffer
36
46
  ? '__emnapiContext.feature.Buffer = Buffer'
37
47
  : ''
@@ -77,6 +87,7 @@ const {
77
87
  type: 'module',
78
88
  })
79
89
  ${workerFsHandler}
90
+ ${workerErrorHandler}
80
91
  return worker
81
92
  },
82
93
  overwriteImports(importObject) {
@@ -63,13 +63,19 @@ globalThis.onmessage = function (e) {
63
63
  };
64
64
  `
65
65
 
66
- export const createWasiBrowserWorkerBinding = (fs: boolean) => {
66
+ export const createWasiBrowserWorkerBinding = (
67
+ fs: boolean,
68
+ errorEvent: boolean,
69
+ ) => {
67
70
  const fsImport = fs
68
71
  ? `import { instantiateNapiModuleSync, MessageHandler, WASI, createFsProxy } from '@napi-rs/wasm-runtime'
69
72
  import { memfsExported as __memfsExported } from '@napi-rs/wasm-runtime/fs'
70
73
 
71
74
  const fs = createFsProxy(__memfsExported)`
72
75
  : `import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime'`
76
+ const errorOutputsAppend = errorEvent
77
+ ? `\n errorOutputs.push([...arguments])`
78
+ : ''
73
79
  const wasiCreation = fs
74
80
  ? `const wasi = new WASI({
75
81
  fs,
@@ -83,6 +89,7 @@ const fs = createFsProxy(__memfsExported)`
83
89
  printErr: function() {
84
90
  // eslint-disable-next-line no-console
85
91
  console.error.apply(console, arguments)
92
+ ${errorOutputsAppend}
86
93
  },
87
94
  })`
88
95
  : `const wasi = new WASI({
@@ -93,10 +100,19 @@ const fs = createFsProxy(__memfsExported)`
93
100
  printErr: function() {
94
101
  // eslint-disable-next-line no-console
95
102
  console.error.apply(console, arguments)
103
+ ${errorOutputsAppend}
96
104
  },
97
105
  })`
106
+ const errorHandler = errorEvent
107
+ ? `onError(error) {
108
+ postMessage({ type: 'error', error, errorOutputs })
109
+ errorOutputs.length = 0
110
+ }`
111
+ : ''
98
112
  return `${fsImport}
99
113
 
114
+ const errorOutputs = []
115
+
100
116
  const handler = new MessageHandler({
101
117
  onLoad({ wasmModule, wasmMemory }) {
102
118
  ${wasiCreation}
@@ -113,6 +129,7 @@ const handler = new MessageHandler({
113
129
  },
114
130
  })
115
131
  },
132
+ ${errorHandler}
116
133
  })
117
134
 
118
135
  globalThis.onmessage = function (e) {
@@ -0,0 +1,15 @@
1
+ import { Command } from 'clipanion'
2
+
3
+ import { CLI_VERSION } from '../utils/misc.js'
4
+
5
+ /**
6
+ * A command that prints the version of the CLI.
7
+ *
8
+ * Paths: `-v`, `--version`
9
+ */
10
+ export class CliVersionCommand extends Command<any> {
11
+ static paths = [[`-v`], [`--version`]]
12
+ async execute() {
13
+ await this.context.stdout.write(`${CLI_VERSION}\n`)
14
+ }
15
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import { universalizeBinaries } from './api/universalize.js'
10
10
  import { version } from './api/version.js'
11
11
  import { ArtifactsCommand } from './commands/artifacts.js'
12
12
  import { BuildCommand } from './commands/build.js'
13
+ import { CliVersionCommand } from './commands/cli-version.js'
13
14
  import { CreateNpmDirsCommand } from './commands/create-npm-dirs.js'
14
15
  import { HelpCommand } from './commands/help.js'
15
16
  import { NewCommand } from './commands/new.js'
@@ -33,6 +34,7 @@ cli.register(RenameCommand)
33
34
  cli.register(PrePublishCommand)
34
35
  cli.register(VersionCommand)
35
36
  cli.register(HelpCommand)
37
+ cli.register(CliVersionCommand)
36
38
 
37
39
  /**
38
40
  *
@@ -83,6 +83,10 @@ export interface UserNapiConfig {
83
83
  * Whether to inject `buffer` to emnapi context
84
84
  */
85
85
  buffer?: boolean
86
+ /**
87
+ * Whether to emit custom events for errors in worker
88
+ */
89
+ errorEvent?: boolean
86
90
  }
87
91
  }
88
92
 
@@ -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