@napi-rs/cli 3.8.5 → 3.9.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.
package/docs/build.md CHANGED
@@ -22,37 +22,39 @@ new NapiCli().build({
22
22
 
23
23
  ## Options
24
24
 
25
- | Options | CLI Options | type | required | default | description |
26
- | ----------------- | --------------------- | -------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | | --help,-h | | | | get help |
28
- | target | --target,-t | string | false | | Build for the target triple, bypassed to `cargo build --target` |
29
- | cwd | --cwd | string | false | | The working directory of where napi command will be executed in, all other paths options are relative to this path |
30
- | manifestPath | --manifest-path | string | false | | Path to `Cargo.toml` |
31
- | configPath | --config-path,-c | string | false | | Path to `napi` config json file |
32
- | packageJsonPath | --package-json-path | string | false | | Path to `package.json` |
33
- | targetDir | --target-dir | string | false | | Directory for all crate generated artifacts, see `cargo build --target-dir` |
34
- | outputDir | --output-dir,-o | string | false | | Path to where all the built files would be put. Default to the crate folder |
35
- | platform | --platform | boolean | false | | Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node` |
36
- | jsPackageName | --js-package-name | string | false | | Package name in generated js binding file. Only works with `--platform` flag |
37
- | constEnum | --const-enum | boolean | false | | Whether generate const enum for typescript bindings |
38
- | runtimeStringEnum | --runtime-string-enum | boolean | false | | Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union. |
39
- | jsBinding | --js | string | false | | Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`. |
40
- | noJsBinding | --no-js | boolean | false | | Whether to disable the generation JS binding file. Only works with `--platform` flag. |
41
- | dts | --dts | string | false | | Path and filename of generated type def file. Relative to `--output-dir` |
42
- | dtsHeader | --dts-header | string | false | | Custom file header for generated type def file. Only works when `typedef` feature enabled. |
43
- | noDtsHeader | --no-dts-header | boolean | false | | Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled. |
44
- | dtsCache | --dts-cache | boolean | false | true | Whether to enable the dts cache, default to true |
45
- | esm | --esm | boolean | false | | Whether to emit an ESM JS binding file instead of CJS format. Only works with `--platform` flag. |
46
- | strip | --strip,-s | boolean | false | | Whether strip the library to achieve the minimum file size |
47
- | release | --release,-r | boolean | false | | Build in release mode |
48
- | verbose | --verbose,-v | boolean | false | | Verbosely log build command trace |
49
- | bin | --bin | string | false | | Build only the specified binary |
50
- | package | --package,-p | string | false | | Build the specified library or the one at cwd |
51
- | profile | --profile | string | false | | Build artifacts with the specified profile |
52
- | crossCompile | --cross-compile,-x | boolean | false | | [experimental] cross compile by replacing the cargo subcommand: Windows MSVC targets from a non-Windows host build with `cargo-xwin` (`windows-gnu` targets are rejected, `cargo-xwin` cannot handle them), non-Windows targets build with `cargo-zigbuild` (requires `zig` on PATH). The selected subcommand is auto-installed on first use. Cannot be combined with `--use-cross`, `--use-napi-cross` or `--watch` |
53
- | useCross | --use-cross | boolean | false | | [experimental] not recommended, prefer `--cross-compile` or `--use-napi-cross`: build in a Docker or Podman container with [cross](https://github.com/cross-rs/cross), which must be installed manually and needs a running container engine. Cannot be combined with `--cross-compile`, `--use-napi-cross` or `--watch` |
54
- | useNapiCross | --use-napi-cross | boolean | false | | [experimental] download a prebuilt gcc cross toolchain from `@napi-rs/cross-toolchain` (glibc 2.17) and set linker and C compiler environment variables. Linux glibc targets only (x64, arm64, armv7, ppc64le, s390x) on a Linux x64 or arm64 host, any other target or host errors. Cannot be combined with `--cross-compile` or `--use-cross` |
55
- | watch | --watch,-w | boolean | false | | watch the crate changes and build continuously with `cargo-watch` crates |
56
- | features | --features,-F | string[] | false | | Space-separated list of features to activate |
57
- | allFeatures | --all-features | boolean | false | | Activate all available features |
58
- | noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
25
+ | Options | CLI Options | type | required | default | description |
26
+ | ----------------- | --------------------- | ------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | | --help,-h | | | | get help |
28
+ | target | --target,-t | string | false | | Build for the target triple, bypassed to `cargo build --target` |
29
+ | cwd | --cwd | string | false | | The working directory of where napi command will be executed in, all other paths options are relative to this path |
30
+ | manifestPath | --manifest-path | string | false | | Path to `Cargo.toml` |
31
+ | configPath | --config-path,-c | string | false | | Path to `napi` config json file |
32
+ | packageJsonPath | --package-json-path | string | false | | Path to `package.json` |
33
+ | targetDir | --target-dir | string | false | | Directory for all crate generated artifacts, see `cargo build --target-dir` |
34
+ | outputDir | --output-dir,-o | string | false | | Path to where all the built files would be put. Default to the crate folder |
35
+ | platform | --platform | boolean | false | | Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node` |
36
+ | jsPackageName | --js-package-name | string | false | | Package name in generated js binding file. Only works with `--platform` flag |
37
+ | constEnum | --const-enum | boolean | false | | Whether generate const enum for typescript bindings |
38
+ | runtimeStringEnum | --runtime-string-enum | boolean | false | | Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union. |
39
+ | jsBinding | --js,--js-binding | string | false | | Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`. |
40
+ | noJsBinding | --no-js | boolean | false | | Whether to disable the generation JS binding file. Only works with `--platform` flag. |
41
+ | dts | --dts | string | false | | Path and filename of generated type def file. Relative to `--output-dir` |
42
+ | dtsHeader | --dts-header | string | false | | Custom file header for generated type def file. Only works when `typedef` feature enabled. |
43
+ | noDtsHeader | --no-dts-header | boolean | false | | Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled. |
44
+ | dtsCache | --dts-cache | boolean | false | true | Whether to enable the dts cache, default to true |
45
+ | format | --format | 'esm' \| 'commonjs' | false | | The module format of the generated JS binding file. Only works with `--platform` flag. Defaults to `commonjs`. |
46
+ | esm | --esm | boolean | false | | Alias for `--format esm`. |
47
+ | commonjs | --commonjs | boolean | false | | Alias for `--format commonjs`. |
48
+ | strip | --strip,-s | boolean | false | | Whether strip the library to achieve the minimum file size |
49
+ | release | --release,-r | boolean | false | | Build in release mode |
50
+ | verbose | --verbose,-v | boolean | false | | Verbosely log build command trace |
51
+ | bin | --bin | string | false | | Build only the specified binary |
52
+ | package | --package,-p | string | false | | Build the specified library or the one at cwd |
53
+ | profile | --profile | string | false | | Build artifacts with the specified profile |
54
+ | crossCompile | --cross-compile,-x | boolean | false | | [experimental] cross compile by replacing the cargo subcommand: Windows MSVC targets from a non-Windows host build with `cargo-xwin` (`windows-gnu` targets are rejected, `cargo-xwin` cannot handle them), non-Windows targets build with `cargo-zigbuild` (requires `zig` on PATH). The selected subcommand is auto-installed on first use. Cannot be combined with `--use-cross`, `--use-napi-cross` or `--watch` |
55
+ | useCross | --use-cross | boolean | false | | [experimental] not recommended, prefer `--cross-compile` or `--use-napi-cross`: build in a Docker or Podman container with [cross](https://github.com/cross-rs/cross), which must be installed manually and needs a running container engine. Cannot be combined with `--cross-compile`, `--use-napi-cross` or `--watch` |
56
+ | useNapiCross | --use-napi-cross | boolean | false | | [experimental] download a prebuilt gcc cross toolchain from `@napi-rs/cross-toolchain` (glibc 2.17) and set linker and C compiler environment variables. Linux glibc targets only (x64, arm64, armv7, ppc64le, s390x) on a Linux x64 or arm64 host, any other target or host errors. Cannot be combined with `--cross-compile` or `--use-cross` |
57
+ | watch | --watch,-w | boolean | false | | watch the crate changes and build continuously with `cargo-watch` crates |
58
+ | features | --features,-F | string[] | false | | Space-separated list of features to activate |
59
+ | allFeatures | --all-features | boolean | false | | Activate all available features |
60
+ | noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napi-rs/cli",
3
- "version": "3.8.5",
3
+ "version": "3.9.0",
4
4
  "description": "Cli tools for napi-rs",
5
5
  "author": "LongYinan <lynweklm@gmail.com>",
6
6
  "homepage": "https://napi.rs/",
@@ -63,11 +63,10 @@
63
63
  "dependencies": {
64
64
  "@inquirer/prompts": "^8.5.2",
65
65
  "@napi-rs/cross-toolchain": "^1.0.3",
66
- "@napi-rs/wasm-tools": "^1.0.1",
66
+ "@napi-rs/wasm-tools": "^1.1.0",
67
67
  "@octokit/rest": "^22.0.1",
68
68
  "clipanion": "^4.0.0-rc.4",
69
69
  "colorette": "^2.0.20",
70
- "emnapi": "2.0.0-alpha.3",
71
70
  "es-toolkit": "^1.47.0",
72
71
  "js-yaml": "^4.2.0",
73
72
  "obug": "^2.1.2",
@@ -76,7 +75,8 @@
76
75
  "typescript": "^6.0.3"
77
76
  },
78
77
  "devDependencies": {
79
- "@emnapi/runtime": "2.0.0-alpha.3",
78
+ "@emnapi/core": "^2.0.0-alpha.4",
79
+ "@emnapi/runtime": "^2.0.0-alpha.4",
80
80
  "@oxc-node/core": "^0.1.0",
81
81
  "@std/toml": "npm:@jsr/std__toml@^1.0.11",
82
82
  "@types/inquirer": "^9.0.9",
@@ -84,19 +84,28 @@
84
84
  "@types/node": "^25.9.2",
85
85
  "@types/semver": "^7.7.1",
86
86
  "ava": "^8.0.1",
87
+ "emnapi": "^2.0.0-alpha.4",
87
88
  "empathic": "^2.0.1",
88
89
  "env-paths": "^4.0.0",
89
- "oxc-parser": "^0.143.0",
90
+ "oxc-parser": "^0.148.0",
90
91
  "prettier": "^3.8.3",
91
92
  "tsdown": "^0.22.2",
92
93
  "tslib": "^2.8.1"
93
94
  },
94
95
  "peerDependencies": {
95
- "@emnapi/runtime": "2.0.0-alpha.3"
96
+ "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4",
97
+ "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4",
98
+ "emnapi": "^1.7.1 || ^2.0.0-alpha.4"
96
99
  },
97
100
  "peerDependenciesMeta": {
101
+ "@emnapi/core": {
102
+ "optional": true
103
+ },
98
104
  "@emnapi/runtime": {
99
105
  "optional": true
106
+ },
107
+ "emnapi": {
108
+ "optional": true
100
109
  }
101
110
  },
102
111
  "funding": {
@@ -27,6 +27,7 @@ import {
27
27
  buildProject,
28
28
  generateTypeDef,
29
29
  napiCrossToolchainEnvs,
30
+ resolveBuildFormat,
30
31
  validateCrossCompileFlags,
31
32
  validateNapiCrossSupport,
32
33
  writeJsBinding,
@@ -197,6 +198,95 @@ napi-build = { path = "${napiBuildPath}" }
197
198
  t.regex(jsContent, /module\.exports\.sum = nativeBinding\.sum/)
198
199
  })
199
200
 
201
+ test('writeJsBinding uses the explicit format independently of the filename', async (t) => {
202
+ const { projectDir } = t.context
203
+ const commonjsPath = join(projectDir, 'binding.cjs')
204
+ const esmPath = join(projectDir, 'binding.js')
205
+ const legacyEsmPath = join(projectDir, 'legacy.js')
206
+
207
+ await writeJsBinding({
208
+ platform: true,
209
+ idents: ['sum'],
210
+ binaryName: 'build-integration',
211
+ packageName: 'build-integration',
212
+ version: '0.1.0',
213
+ outputDir: projectDir,
214
+ jsBinding: 'binding.cjs',
215
+ format: 'commonjs',
216
+ })
217
+ await writeJsBinding({
218
+ platform: true,
219
+ idents: ['sum'],
220
+ binaryName: 'build-integration',
221
+ packageName: 'build-integration',
222
+ version: '0.1.0',
223
+ outputDir: projectDir,
224
+ jsBinding: 'binding.js',
225
+ format: 'esm',
226
+ })
227
+ await writeJsBinding({
228
+ platform: true,
229
+ idents: ['sum'],
230
+ binaryName: 'build-integration',
231
+ packageName: 'build-integration',
232
+ version: '0.1.0',
233
+ outputDir: projectDir,
234
+ jsBinding: 'legacy.js',
235
+ esm: true,
236
+ })
237
+
238
+ const [commonjs, esm, legacyEsm] = await Promise.all([
239
+ readFile(commonjsPath, 'utf8'),
240
+ readFile(esmPath, 'utf8'),
241
+ readFile(legacyEsmPath, 'utf8'),
242
+ ])
243
+
244
+ t.regex(commonjs, /module\.exports\.sum = nativeBinding\.sum/)
245
+ t.regex(esm, /export \{ sum \}/)
246
+ t.regex(legacyEsm, /export \{ sum \}/)
247
+ })
248
+
249
+ test('resolveBuildFormat handles defaults, aliases, and conflicts', (t) => {
250
+ const validCases = [
251
+ { options: {}, expected: 'commonjs' },
252
+ { options: { format: 'esm' }, expected: 'esm' },
253
+ { options: { format: 'commonjs' }, expected: 'commonjs' },
254
+ { options: { esm: true }, expected: 'esm' },
255
+ { options: { commonjs: true }, expected: 'commonjs' },
256
+ ] as const
257
+
258
+ for (const { options, expected } of validCases) {
259
+ t.is(resolveBuildFormat(options), expected)
260
+ }
261
+
262
+ const invalidCases = [
263
+ {
264
+ options: { esm: true, commonjs: true },
265
+ message: /`--esm` and `--commonjs` cannot be used together/,
266
+ },
267
+ {
268
+ options: { format: 'esm', commonjs: true },
269
+ message: /`--format esm` cannot be used with `--commonjs`/,
270
+ },
271
+ {
272
+ options: { format: 'commonjs', esm: true },
273
+ message: /`--format commonjs` cannot be used with `--esm`/,
274
+ },
275
+ {
276
+ options: { format: 'invalid' },
277
+ message: /Invalid build format "invalid"/,
278
+ },
279
+ {
280
+ options: { format: '' },
281
+ message: /Invalid build format ""/,
282
+ },
283
+ ] as const
284
+
285
+ for (const { options, message } of invalidCases) {
286
+ t.throws(() => resolveBuildFormat(options), { message })
287
+ }
288
+ })
289
+
200
290
  test('generateTypeDef preserves deterministic file order', async (t) => {
201
291
  const { projectDir, typeDefDir } = t.context
202
292
 
@@ -294,3 +294,25 @@ test('createEsmBinding is Node 12 compatible', (t) => {
294
294
  t.false(code.includes('?.'), 'ESM loader must not use optional chaining')
295
295
  t.false(code.includes('??'), 'ESM loader must not use nullish coalescing')
296
296
  })
297
+
298
+ test('createEsmBinding builds native addon loading on portable ESM primitives', (t) => {
299
+ const code = createEsmBinding('test', '@scope/test', ['sum'])
300
+ assertValidJS(t, code, 'esm')
301
+ t.true(
302
+ code.includes(`import { createRequire } from 'module'`),
303
+ 'ESM loader must import createRequire so native .node addons can be loaded without the CommonJS `require` global',
304
+ )
305
+ t.true(
306
+ code.includes('const require = createRequire(import.meta.url)'),
307
+ 'ESM loader must derive `require` from the current module, not rely on a CommonJS global',
308
+ )
309
+ // `@napi-rs/cli` is a valid Node package but is not running under Deno. Deno
310
+ // rejects `URL.pathname` treated as a filesystem path, which is exactly how a
311
+ // `__dirname` built from `new URL('.', import.meta.url).pathname` behaves on
312
+ // Windows (percent-encoded, forward-slash separated). The ESM native loader
313
+ // never used `__dirname` anyway, so it must not emit that construct at all.
314
+ t.false(
315
+ /new URL\(['"]\.['"], import\.meta\.url\)\.pathname/.test(code),
316
+ 'ESM loader must not treat URL.pathname as a filesystem path',
317
+ )
318
+ })
package/src/api/build.ts CHANGED
@@ -84,8 +84,13 @@ type WasiBindingMetadata = {
84
84
  bindingTypeDef?: string
85
85
  }
86
86
 
87
+ export type BuildFormat = 'esm' | 'commonjs'
88
+
87
89
  type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
88
- type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
90
+ type ParsedBuildOptions = Omit<BuildOptions, 'cwd' | 'format'> & {
91
+ cwd: string
92
+ format: BuildFormat
93
+ }
89
94
 
90
95
  export const WASI_ARTIFACT_METADATA_PREFIX = '// napi-rs-artifact-metadata:'
91
96
 
@@ -569,12 +574,57 @@ function readWasmU32(binary: Uint8Array, start: number) {
569
574
  throw new Error('Invalid WebAssembly unsigned LEB128 value')
570
575
  }
571
576
 
577
+ type BuildFormatOptions = {
578
+ format?: string
579
+ esm?: boolean
580
+ commonjs?: boolean
581
+ }
582
+
583
+ /**
584
+ * Resolve the explicit build format and its legacy CLI/API aliases.
585
+ *
586
+ * CommonJS is the historical default. The aliases are intentionally kept
587
+ * separate from `format` because `--esm` and `--commonjs` are boolean flags,
588
+ * while `--format` takes a value.
589
+ */
590
+ export function resolveBuildFormat(options: BuildFormatOptions): BuildFormat {
591
+ let format: BuildFormat | undefined
592
+ if (options.format !== undefined) {
593
+ if (options.format === 'esm' || options.format === 'commonjs') {
594
+ format = options.format
595
+ } else {
596
+ throw new Error(
597
+ `Invalid build format ${JSON.stringify(options.format)}. Expected \`esm\` or \`commonjs\`.`,
598
+ )
599
+ }
600
+ }
601
+
602
+ if (options.esm && options.commonjs) {
603
+ throw new Error('`--esm` and `--commonjs` cannot be used together.')
604
+ }
605
+
606
+ const aliasFormat = options.esm
607
+ ? 'esm'
608
+ : options.commonjs
609
+ ? 'commonjs'
610
+ : undefined
611
+ if (format && aliasFormat && format !== aliasFormat) {
612
+ throw new Error(
613
+ `\`--format ${format}\` cannot be used with \`--${aliasFormat}\`.`,
614
+ )
615
+ }
616
+
617
+ return format ?? aliasFormat ?? 'commonjs'
618
+ }
619
+
620
+ /** Build the configured NAPI-RS crate and generate its output artifacts. */
572
621
  export async function buildProject(rawOptions: BuildOptions) {
573
622
  debug('napi build command receive options: %O', rawOptions)
574
623
 
575
624
  const options: ParsedBuildOptions = {
576
625
  dtsCache: true,
577
626
  ...rawOptions,
627
+ format: resolveBuildFormat(rawOptions),
578
628
  cwd: rawOptions.cwd ?? process.cwd(),
579
629
  }
580
630
 
@@ -2003,7 +2053,7 @@ class Builder {
2003
2053
  noJsBinding: this.options.noJsBinding,
2004
2054
  idents,
2005
2055
  jsBinding: this.options.jsBinding,
2006
- esm: this.options.esm,
2056
+ format: this.options.format,
2007
2057
  binaryName: this.config.binaryName,
2008
2058
  packageName: this.options.jsPackageName ?? this.config.packageName,
2009
2059
  version: process.env.npm_new_version ?? this.config.packageJson.version,
@@ -2352,7 +2402,9 @@ export interface WriteJsBindingOptions {
2352
2402
  noJsBinding?: boolean
2353
2403
  idents: string[]
2354
2404
  jsBinding?: string
2405
+ format?: BuildFormat
2355
2406
  esm?: boolean
2407
+ commonjs?: boolean
2356
2408
  binaryName: string
2357
2409
  packageName: string
2358
2410
  version: string
@@ -2366,6 +2418,7 @@ export interface WriteJsBindingOptions {
2366
2418
  wasiFlavors?: string[]
2367
2419
  }
2368
2420
 
2421
+ /** Write a platform binding loader in the requested module format. */
2369
2422
  export async function writeJsBinding(
2370
2423
  options: WriteJsBindingOptions,
2371
2424
  ): Promise<Output | undefined> {
@@ -2389,7 +2442,8 @@ export async function writeJsBinding(
2389
2442
  ? localWasiName
2390
2443
  : `./${localWasiName}`
2391
2444
 
2392
- const createBinding = options.esm ? createEsmBinding : createCjsBinding
2445
+ const createBinding =
2446
+ resolveBuildFormat(options) === 'esm' ? createEsmBinding : createCjsBinding
2393
2447
  const binding = createBinding(
2394
2448
  options.binaryName,
2395
2449
  options.packageName,
@@ -157,7 +157,6 @@ ${idents.map((ident) => `export { ${ident} }`).join('\n')}`
157
157
  return `${bindingHeader}
158
158
  import { createRequire } from 'module'
159
159
  const require = createRequire(import.meta.url)
160
- const __dirname = new URL('.', import.meta.url).pathname
161
160
 
162
161
  ${createCommonBinding(
163
162
  localName,
@@ -0,0 +1,24 @@
1
+ import test from 'ava'
2
+
3
+ import { createBuildCommand } from '../../index.js'
4
+
5
+ test('build supports explicit format and compatibility aliases', (t) => {
6
+ const explicit = createBuildCommand([
7
+ '--format',
8
+ 'esm',
9
+ '--js-binding',
10
+ 'index.mjs',
11
+ ])
12
+
13
+ t.is(explicit.format, 'esm')
14
+ t.is(explicit.jsBinding, 'index.mjs')
15
+
16
+ t.true(createBuildCommand(['--esm']).esm)
17
+ t.true(createBuildCommand(['--commonjs']).commonjs)
18
+ })
19
+
20
+ test('build rejects unsupported formats', (t) => {
21
+ t.throws(() => createBuildCommand(['--format', 'umd']), {
22
+ message: /Invalid value for --format/,
23
+ })
24
+ })
package/src/def/build.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // This file is generated by codegen/index.ts
2
2
  // Do not edit this file manually
3
3
  import { Command, Option } from 'clipanion'
4
+ import * as typanion from 'typanion'
4
5
 
5
6
  export abstract class BaseBuildCommand extends Command {
6
7
  static paths = [['build']]
@@ -60,7 +61,7 @@ export abstract class BaseBuildCommand extends Command {
60
61
  'Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union.',
61
62
  })
62
63
 
63
- jsBinding?: string = Option.String('--js', {
64
+ jsBinding?: string = Option.String('--js,--js-binding', {
64
65
  description:
65
66
  'Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`.',
66
67
  })
@@ -89,9 +90,21 @@ export abstract class BaseBuildCommand extends Command {
89
90
  description: 'Whether to enable the dts cache, default to true',
90
91
  })
91
92
 
92
- esm?: boolean = Option.Boolean('--esm', {
93
+ format?: 'esm' | 'commonjs' = Option.String('--format', {
94
+ validator: typanion.isOneOf([
95
+ typanion.isLiteral('esm'),
96
+ typanion.isLiteral('commonjs'),
97
+ ]),
93
98
  description:
94
- 'Whether to emit an ESM JS binding file instead of CJS format. Only works with `--platform` flag.',
99
+ 'The module format of the generated JS binding file. Only works with `--platform` flag. Defaults to `commonjs`.',
100
+ })
101
+
102
+ esm?: boolean = Option.Boolean('--esm', {
103
+ description: 'Alias for `--format esm`.',
104
+ })
105
+
106
+ commonjs?: boolean = Option.Boolean('--commonjs', {
107
+ description: 'Alias for `--format commonjs`.',
95
108
  })
96
109
 
97
110
  strip?: boolean = Option.Boolean('--strip,-s', {
@@ -150,6 +163,9 @@ export abstract class BaseBuildCommand extends Command {
150
163
  description: 'Do not activate the `default` feature',
151
164
  })
152
165
 
166
+ /**
167
+ * Return the parsed build options.
168
+ */
153
169
  getOptions() {
154
170
  return {
155
171
  target: this.target,
@@ -169,7 +185,9 @@ export abstract class BaseBuildCommand extends Command {
169
185
  dtsHeader: this.dtsHeader,
170
186
  noDtsHeader: this.noDtsHeader,
171
187
  dtsCache: this.dtsCache,
188
+ format: this.format,
172
189
  esm: this.esm,
190
+ commonjs: this.commonjs,
173
191
  strip: this.strip,
174
192
  release: this.release,
175
193
  verbose: this.verbose,
@@ -262,9 +280,17 @@ export interface BuildOptions {
262
280
  */
263
281
  dtsCache?: boolean
264
282
  /**
265
- * Whether to emit an ESM JS binding file instead of CJS format. Only works with `--platform` flag.
283
+ * The module format of the generated JS binding file. Only works with `--platform` flag. Defaults to `commonjs`.
284
+ */
285
+ format?: 'esm' | 'commonjs'
286
+ /**
287
+ * Alias for `--format esm`.
266
288
  */
267
289
  esm?: boolean
290
+ /**
291
+ * Alias for `--format commonjs`.
292
+ */
293
+ commonjs?: boolean
268
294
  /**
269
295
  * Whether strip the library to achieve the minimum file size
270
296
  */