fix(cli): incorrect behaviors (#1626)

* fix(cli): target selector is not available in interactive mode

* fix(cli): js binding file should export

* fix(cli): wrong node engine requirements syntax

* feat(cli): support esm module

* restore js binding implementation in v2

---------

Co-authored-by: LongYinan <lynweklm@gmail.com>
This commit is contained in:
liuyi 2023-06-17 12:49:30 +08:00 committed by GitHub
parent b34cca4d83
commit fb22a5ae07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 154 additions and 55 deletions

View file

@ -103,6 +103,12 @@ const NEW_OPTIONS: CommandSchema = {
description: 'Whether to run the command in dry-run mode',
default: false,
},
{
name: 'esm',
type: 'boolean',
description: 'Whether enable ESM support',
default: false,
},
],
}

View file

@ -35,3 +35,4 @@ new NapiCli().new({
| enableTypeDef | --enable-type-def | boolean | false | true | Whether enable the `type-def` feature for typescript definitions auto-generation |
| enableGithubActions | --enable-github-actions | boolean | false | true | Whether generate preconfigured GitHub Actions workflow |
| dryRun | --dry-run | boolean | false | false | Whether to run the command in dry-run mode |
| esm | --esm | boolean | false | false | Whether enable ESM support |

View file

@ -28,7 +28,7 @@ import {
writeFileAsync,
} from '../utils/index.js'
import { createJsBinding } from './templates/index.js'
import { createCjsBinding } from './templates/index.js'
const debug = debugFactory('build')
@ -452,8 +452,8 @@ class Builder {
// only for cdylib
if (this.cdyLibName) {
await this.generateTypeDef()
await this.writeJsBinding()
const idents = await this.generateTypeDef()
await this.writeJsBinding(idents)
}
return this.outputs
@ -523,12 +523,12 @@ class Builder {
private async generateTypeDef() {
if (!(await fileExists(this.envs.TYPE_DEF_TMP_PATH))) {
return
return []
}
const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
const dts = await processTypeDef(
const { dts, exports } = await processTypeDef(
this.envs.TYPE_DEF_TMP_PATH,
!this.options.noDtsHeader
? this.options.dtsHeader ?? DEFAULT_TYPE_DEF_HEADER
@ -547,21 +547,32 @@ class Builder {
debug.error('Failed to write type def file')
debug.error(e as Error)
}
return exports
}
private async writeJsBinding() {
if (!this.options.platform || this.options.noJsBinding) {
private async writeJsBinding(idents: string[]) {
if (
!this.options.platform ||
this.options.noJsBinding ||
idents.length === 0
) {
return
}
const dest = join(this.outputDir, this.options.jsBinding ?? 'index.js')
const name = parse(this.options.jsBinding ?? 'index.js').name
const js = createJsBinding(this.config.binaryName, this.config.packageName)
const cjs = createCjsBinding(
this.config.binaryName,
this.config.packageName,
idents,
)
try {
const dest = join(this.outputDir, `${name}.js`)
debug('Writing js binding to:')
debug(' %i', dest)
await writeFileAsync(dest, js, 'utf-8')
await writeFileAsync(dest, cjs, 'utf-8')
this.outputs.push({
kind: 'js',
path: dest,

View file

@ -146,6 +146,7 @@ function generatePackageJson(options: NewOptions): Output {
license: options.license,
engineRequirement: napiEngineRequirement(options.minNodeApiVersion),
cliVersion: CLI_VERSION,
esm: options.esm,
}),
}
}

View file

@ -1,8 +1,10 @@
export function createJsBinding(localName: string, pkgName: string): string {
export function createCjsBinding(
localName: string,
pkgName: string,
idents: string[],
): string {
return `// prettier-ignore
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
@ -253,5 +255,8 @@ if (!nativeBinding) {
throw new Error(\`Failed to load native binding\`)
}
${idents
.map((ident) => `module.exports.${ident} = binding.${ident}`)
.join('\n')}
`
}

View file

@ -1,3 +1,5 @@
import { CommonPackageJsonFields } from '../../utils/config.js'
export const createPackageJson = ({
name,
binaryName,
@ -5,6 +7,7 @@ export const createPackageJson = ({
license,
engineRequirement,
cliVersion,
esm,
}: {
name: string
binaryName: string
@ -12,32 +15,54 @@ export const createPackageJson = ({
license: string
engineRequirement: string
cliVersion: string
esm: boolean
}) => {
return `{
"name": "${name}",
"version": "1.0.0",
"main": "index.js",
"types": "index.d.ts",
"license": "${license}",
"engines": {
"node": "${engineRequirement}"
},
"napi": {
"binaryName": "${binaryName}",
"targets": [
${targets.map((t) => `"${t}"`).join(',\n ')}
]
},
"scripts": {
"test": "node -e \\"assert(require('.').sum(1, 2) === 3)\\"",
"build": "napi build --release --platform --strip",
"build:debug": "napi build",
"prepublishOnly": "napi prepublish -t npm",
"artifacts": "napi artifacts",
"version": "napi version"
},
"devDependencies": {
"@napi-rs/cli": "^${cliVersion}"
const content: CommonPackageJsonFields = {
name,
version: '1.0.0',
license,
engines: {
node: engineRequirement,
},
type: 'commonjs',
main: 'index.js',
types: 'index.d.ts',
module: undefined,
exports: undefined,
napi: {
binaryName,
targets,
},
scripts: {
test: 'node -e "assert(require(\'.\').sum(1, 2) === 3)"',
build: 'napi build --release --platform --strip',
'build:debug': 'napi build',
prepublishOnly: 'napi prepublish -t npm',
artifacts: 'napi artifacts',
version: 'napi version',
},
devDependencies: {
'@napi-rs/cli': `^${cliVersion}`,
},
}
}`
if (esm) {
content.type = 'module'
content.main = 'index.cjs'
content.module = 'index.js'
content.exports = {
'.': {
import: {
default: './index.js',
types: './index.d.ts',
},
require: {
default: './index.cjs',
types: './index.d.ts',
},
},
}
}
return JSON.stringify(content, null, 2)
}

View file

@ -40,6 +40,7 @@ export class NewCommand extends BaseNewCommand {
return {
...cmdOptions,
name: await this.fetchName(path.parse(cmdOptions.path).base),
esm: await this.fetchEnableEsm(),
minNodeApiVersion: await this.fetchNapiVersion(),
targets: await this.fetchTargets(),
license: await this.fetchLicense(),
@ -94,10 +95,6 @@ export class NewCommand extends BaseNewCommand {
}
private async fetchTargets(): Promise<TargetTriple[]> {
if (this.enableDefaultTargets) {
return DEFAULT_TARGETS.concat()
}
if (this.enableAllTargets) {
return AVAILABLE_TARGETS.concat()
}
@ -107,7 +104,7 @@ export class NewCommand extends BaseNewCommand {
type: 'checkbox',
loop: false,
message: 'Choose target(s) your crate will be compiled to',
default: DEFAULT_TARGETS,
default: this.enableDefaultTargets ? DEFAULT_TARGETS : [],
choices: AVAILABLE_TARGETS,
})
@ -135,4 +132,15 @@ export class NewCommand extends BaseNewCommand {
return enableGithubActions
}
private async fetchEnableEsm(): Promise<boolean> {
const { esm } = await inquirer.prompt({
name: 'esm',
type: 'confirm',
message: 'Enable ESM support',
default: this.esm,
})
return esm
}
}

View file

@ -51,6 +51,10 @@ export abstract class BaseNewCommand extends Command {
description: 'Whether to run the command in dry-run mode',
})
esm = Option.Boolean('--esm', false, {
description: 'Whether enable ESM support',
})
getOptions() {
return {
path: this.$$path,
@ -63,6 +67,7 @@ export abstract class BaseNewCommand extends Command {
enableTypeDef: this.enableTypeDef,
enableGithubActions: this.enableGithubActions,
dryRun: this.dryRun,
esm: this.esm,
}
}
}
@ -127,6 +132,12 @@ export interface NewOptions {
* @default false
*/
dryRun?: boolean
/**
* Whether enable ESM support
*
* @default false
*/
esm?: boolean
}
export function applyDefaultNewOptions(options: NewOptions) {
@ -139,6 +150,7 @@ export function applyDefaultNewOptions(options: NewOptions) {
enableTypeDef: true,
enableGithubActions: true,
dryRun: false,
esm: false,
...options,
}
}

View file

@ -10,11 +10,11 @@ Generated by [AVA](https://avajs.dev).
[
'>= 8.6.0',
'>= 8.10.0 && < 9 || >= 9.3.0',
'>= 6.14.2 && < 7 || >= 8.11.2 && < 9 || >= 9.11.0',
'>= 10.16.0 && < 11 || >= 11.8.0',
'>= 10.17.0 && < 11 || >= 12.11.0',
'>= 10.20.0 && < 11 || >= 12.17.0 && < 13 || >= 14.0.0',
'>= 10.23.0 && < 11 || >= 12.19.0 && < 13 || >= 14.12.0',
'>= 12.22.0 && < 13 || >= 14.17.0 && < 15 || >= 15.12.0',
'>= 8.10.0 < 9 || >= 9.3.0',
'>= 6.14.2 < 7 || >= 8.11.2 < 9 || >= 9.11.0',
'>= 10.16.0 < 11 || >= 11.8.0',
'>= 10.17.0 < 11 || >= 12.11.0',
'>= 10.20.0 < 11 || >= 12.17.0 < 13 || >= 14.0.0',
'>= 10.23.0 < 11 || >= 12.19.0 < 13 || >= 14.12.0',
'>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0',
]

View file

@ -36,7 +36,7 @@ test('should ident string correctly', (t) => {
})
test('should process type def correctly', async (t) => {
const dts = await processTypeDef(
const { dts } = await processTypeDef(
join(
fileURLToPath(import.meta.url),
'../',

View file

@ -49,7 +49,7 @@ interface UserNapiConfig {
}
}
interface CommonPackageJsonFields {
export interface CommonPackageJsonFields {
name: string
version: string
description?: string
@ -64,6 +64,17 @@ interface CommonPackageJsonFields {
bugs?: any
// eslint-disable-next-line no-use-before-define
napi?: UserNapiConfig
type?: 'module' | 'commonjs'
scripts?: Record<string, string>
// modules
main?: string
module?: string
types?: string
exports?: any
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
export type NapiConfig = Required<

View file

@ -54,6 +54,7 @@ export async function processTypeDef(
intermediateTypeFile: string,
header?: string,
) {
const exports: string[] = []
const defs = await readIntermediateTypeFile(intermediateTypeFile)
const groupedDefs = preprocessTypeDef(defs)
@ -65,8 +66,23 @@ export async function processTypeDef(
if (namespace === TOP_LEVEL_NAMESPACE) {
for (const def of defs) {
dts += prettyPrint(def, 0) + '\n\n'
switch (def.kind) {
case TypeDefKind.Const:
case TypeDefKind.Enum:
case TypeDefKind.Fn:
case TypeDefKind.Struct: {
exports.push(def.name)
if (def.original_name && def.original_name !== def.name) {
exports.push(def.original_name)
}
break
}
default:
break
}
}
} else {
exports.push(namespace)
dts += `export namespace ${namespace} {\n`
for (const def of defs) {
dts += prettyPrint(def, 2) + '\n'
@ -87,7 +103,10 @@ export class ExternalObject<T> {
`
}
return header + dts
return {
dts: header + dts,
exports,
}
}
async function readIntermediateTypeFile(file: string) {

View file

@ -68,7 +68,7 @@ function toEngineRequirement(versions: NodeVersion[]): string {
requirements.push(req)
})
return requirements.join(' && ')
return requirements.join(' ')
}
export function napiEngineRequirement(napiVersion: NapiVersion): string {