feat(cli): brand new cli tool with both cli and programmatical usage (#1492)
BREAKING CHANGE: requires node >= 16 and some cli options have been renamed
This commit is contained in:
parent
7c4dc2a2bd
commit
a781a4f27e
194 changed files with 8805 additions and 4158 deletions
|
@ -14,3 +14,4 @@ target
|
|||
scripts
|
||||
triples/index.js
|
||||
rollup.config.js
|
||||
crates/cli/index.js
|
|
@ -192,7 +192,7 @@ rules:
|
|||
|
||||
overrides:
|
||||
- files:
|
||||
- ./cli/**/*.ts
|
||||
- ./**/*.ts
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
parserOptions:
|
||||
|
|
5
.github/workflows/android-armv7.yml
vendored
5
.github/workflows/android-armv7.yml
vendored
|
@ -47,13 +47,10 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Cross build
|
||||
run: |
|
||||
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
|
||||
yarn build:test:android:armv7
|
||||
yarn build:test -- --target armv7-linux-androideabi
|
||||
du -sh examples/napi/index.node
|
||||
${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip examples/napi/index.node
|
||||
du -sh examples/napi/index.node
|
||||
|
|
5
.github/workflows/android.yml
vendored
5
.github/workflows/android.yml
vendored
|
@ -47,10 +47,7 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Cross build native tests
|
||||
run: |
|
||||
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
|
||||
yarn build:test:android
|
||||
yarn build:test -- --target aarch64-linux-android
|
||||
|
|
6
.github/workflows/asan.yml
vendored
6
.github/workflows/asan.yml
vendored
|
@ -44,12 +44,10 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Unit tests with address sanitizer
|
||||
run: |
|
||||
yarn build:test:asan
|
||||
yarn workspace @examples/napi build -- -Z build-std
|
||||
yarn workspace @examples/compat-mode build -- -Z build-std
|
||||
LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/9/libasan.so yarn test
|
||||
env:
|
||||
RUST_TARGET: x86_64-unknown-linux-gnu
|
||||
|
|
3
.github/workflows/bench.yaml
vendored
3
.github/workflows/bench.yaml
vendored
|
@ -46,9 +46,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build ts'
|
||||
run: yarn build
|
||||
|
||||
- name: 'Build bench'
|
||||
run: yarn build:bench
|
||||
|
||||
|
|
10
.github/workflows/cli-binary.yml
vendored
10
.github/workflows/cli-binary.yml
vendored
|
@ -44,9 +44,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --mode=skip-build --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Build and run binary
|
||||
run: |
|
||||
yarn workspace binary build
|
||||
|
@ -54,13 +51,6 @@ jobs:
|
|||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
- name: Pass -p and --cargo-name to build
|
||||
run: |
|
||||
node ./cli/scripts/index.js build -p napi-examples-binary --cargo-name napi-examples-binary
|
||||
./napi-examples-binary
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
||||
|
|
6
.github/workflows/linux-aarch64-musl.yaml
vendored
6
.github/workflows/linux-aarch64-musl.yaml
vendored
|
@ -30,9 +30,6 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Cross build native tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
|
@ -40,8 +37,7 @@ jobs:
|
|||
options: -v ${{ github.workspace }}:/napi-rs -w /napi-rs
|
||||
run: |
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc
|
||||
yarn workspace compat-mode-examples build --target aarch64-unknown-linux-musl
|
||||
yarn workspace examples build --target aarch64-unknown-linux-musl
|
||||
yarn build:test -- --target aarch64-unknown-linux-musl
|
||||
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
|
|
5
.github/workflows/linux-aarch64.yaml
vendored
5
.github/workflows/linux-aarch64.yaml
vendored
|
@ -50,11 +50,8 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Cross build native tests
|
||||
run: yarn build:test:aarch64
|
||||
run: yarn build:test -- --target aarch64-unknown-linux-gnu --cross-compile
|
||||
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
|
|
5
.github/workflows/linux-armv7.yaml
vendored
5
.github/workflows/linux-armv7.yaml
vendored
|
@ -51,11 +51,8 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Cross build native tests
|
||||
run: yarn build:test:armv7
|
||||
run: yarn build:test -- --target armv7-unknown-linux-gnueabihf --cross-compile
|
||||
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
|
|
5
.github/workflows/linux-musl.yaml
vendored
5
.github/workflows/linux-musl.yaml
vendored
|
@ -30,9 +30,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
|
@ -40,5 +37,5 @@ jobs:
|
|||
options: -v ${{ github.workspace }}:/napi-rs -w /napi-rs
|
||||
run: |
|
||||
cargo check -vvv
|
||||
yarn build:test
|
||||
yarn build:test -- --target x86_64-unknown-linux-musl
|
||||
yarn test
|
||||
|
|
3
.github/workflows/memory-test.yml
vendored
3
.github/workflows/memory-test.yml
vendored
|
@ -49,9 +49,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: 'Pull docker image'
|
||||
run: docker pull node:lts-slim
|
||||
|
||||
|
|
3
.github/workflows/msrv.yml
vendored
3
.github/workflows/msrv.yml
vendored
|
@ -43,9 +43,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --mode=skip-build --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
|
6
.github/workflows/test.yaml
vendored
6
.github/workflows/test.yaml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: ['14', '16', '18']
|
||||
node: ['16', '18']
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
name: stable - ${{ matrix.os }} - node@${{ matrix.node }}
|
||||
|
@ -51,9 +51,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --mode=skip-build --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
@ -62,6 +59,7 @@ jobs:
|
|||
|
||||
- name: Unit tests
|
||||
run: |
|
||||
yarn test:cli
|
||||
yarn build:test
|
||||
yarn test --verbose
|
||||
yarn tsc -p examples/napi/tsconfig.json --noEmit
|
||||
|
|
7
.github/workflows/windows-arm.yml
vendored
7
.github/workflows/windows-arm.yml
vendored
|
@ -30,9 +30,6 @@ jobs:
|
|||
- name: 'Install dependencies'
|
||||
run: yarn install --mode=skip-build --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Install
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
@ -55,7 +52,9 @@ jobs:
|
|||
args: --all --bins --examples --tests --target aarch64-pc-windows-msvc -vvv
|
||||
|
||||
- name: Build release target
|
||||
run: cargo build --release --target aarch64-pc-windows-msvc
|
||||
run: |
|
||||
yarn workspace @examples/napi build --target aarch64-pc-windows-msvc --release
|
||||
yarn workspace @examples/compat-mode build --target aarch64-pc-windows-msvc --release
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
|
|
10
.github/workflows/windows-i686.yml
vendored
10
.github/workflows/windows-i686.yml
vendored
|
@ -31,9 +31,6 @@ jobs:
|
|||
run: |
|
||||
yarn install --mode=skip-build --immutable
|
||||
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
|
||||
- name: Install
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
@ -55,6 +52,11 @@ jobs:
|
|||
command: check
|
||||
args: --all --bins --examples --tests --target i686-pc-windows-msvc -vvv
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
yarn workspace @examples/napi build --target i686-pc-windows-msvc --release
|
||||
yarn workspace @examples/compat-mode build --target i686-pc-windows-msvc --release
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
|
@ -64,8 +66,6 @@ jobs:
|
|||
|
||||
- name: Build Tests
|
||||
run: |
|
||||
yarn workspace compat-mode-examples build-i686 --release
|
||||
yarn workspace examples build-i686 --release
|
||||
yarn test --verbose
|
||||
node ./node_modules/electron/install.js
|
||||
yarn test:electron
|
||||
|
|
18
.github/workflows/zig.yaml
vendored
18
.github/workflows/zig.yaml
vendored
|
@ -56,17 +56,15 @@ jobs:
|
|||
version: 0.10.1
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
- name: 'Build TypeScript'
|
||||
run: yarn build
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
# Testing for compatibility with node v12.x
|
||||
node-version: 12
|
||||
- name: Cross build native tests
|
||||
- name: install MacOS SDK
|
||||
if: contains(matrix.target, 'apple')
|
||||
run: |
|
||||
yarn workspace compat-mode-examples build --target ${{ matrix.target }} --zig
|
||||
yarn workspace examples build --target ${{ matrix.target }} --zig
|
||||
curl -L "https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz" | tar -J -x -C /opt
|
||||
- name: Cross build native tests
|
||||
env:
|
||||
SDKROOT: /opt/MacOSX11.3.sdk
|
||||
run: |
|
||||
yarn build:test -- --target ${{ matrix.target }} --cross-compile
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { cpus } from 'os'
|
||||
|
||||
const configuration = {
|
||||
extensions: ['ts', 'tsx'],
|
||||
files: ['cli/**/*.spec.ts', 'examples/**/__test__/**/*.spec.ts'],
|
||||
require: ['ts-node/register/transpile-only'],
|
||||
environmentVariables: {
|
||||
TS_NODE_PROJECT: './examples/tsconfig.json',
|
||||
},
|
||||
timeout: '5m',
|
||||
workerThreads: true,
|
||||
concurrency: process.env.CI ? 2 : cpus().length,
|
||||
failFast: false,
|
||||
verbose: !!process.env.CI,
|
||||
}
|
||||
|
||||
if (parseInt(process.versions.napi, 10) < 4) {
|
||||
configuration.compileEnhancements = false
|
||||
}
|
||||
|
||||
export default configuration
|
|
@ -3,9 +3,10 @@
|
|||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "node ../cli/scripts/index.js build --js false --release"
|
||||
"build": "napi-raw build --js false --release"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "workspace:*",
|
||||
"benny": "^3.7.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,88 +9,27 @@
|
|||
|
||||
> Cli tools for napi-rs
|
||||
|
||||
```sh
|
||||
# or npm, pnpm
|
||||
yarn add @napi-rs/cli -D
|
||||
yarn napi build
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | desc | docs |
|
||||
| --------------- | -------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| new | create new napi-rs project | [./docs/new.md](./docs/new.md) |
|
||||
| build | build napi-rs project | [./docs/build.md](./docs/build.md) |
|
||||
| create-npm-dirs | Create npm package dirs for different platforms | [./docs/create-npm-dirs](./docs/create-npm-dirs.md) |
|
||||
| artifacts | Copy artifacts from Github Actions into specified dir | [./docs/artifacts.md](./docs/artifacts.md) |
|
||||
| rename | Rename the napi-rs project | [./docs/rename.md](./docs/rename.md) |
|
||||
| universalize | Combile built binaries into one universal binary | [./docs/universalize.md](./docs/universalize.md) |
|
||||
| version | Update version in created npm packages by `create-npm-dirs` | [./docs/version.md](./docs/version.md) |
|
||||
| pre-publish | Update package.json and copy addons into per platform packages | [./docs/pre-publish.md](./docs/pre-publish.md) |
|
||||
|
||||
### Debug mode
|
||||
|
||||
```bash
|
||||
DEBUG="napi:*" napi [command]
|
||||
```
|
||||
|
||||
### `napi build`
|
||||
|
||||
> Build command. Build rust codes and copy the dynamic lib binary file to the dist dir.
|
||||
|
||||
#### `--platform`
|
||||
|
||||
> default `false`
|
||||
|
||||
Append `platform-arch-[abi]` name to dist file. eg: `index.darwin-x64.node`.
|
||||
|
||||
#### `--release`
|
||||
|
||||
> default `false`
|
||||
|
||||
Is release build. This flag will be passed to `Cargo` directly.
|
||||
|
||||
#### `--features`
|
||||
|
||||
> default `''`
|
||||
|
||||
Cargo features, passthrough to `cargo build` command.
|
||||
|
||||
#### `--config,-c`
|
||||
|
||||
> default `package.json`
|
||||
|
||||
`napi-rs` config file name. `napi-rs` config example :
|
||||
|
||||
```js
|
||||
{
|
||||
"name": "@native-binding/fib",
|
||||
"version": "0.1.0",
|
||||
"napi": {
|
||||
"name": "fib", // binary name
|
||||
"triples": {
|
||||
"defaults": true, // default true, if this value is true, will build `x86_64-pc-windows-msvc`, `x86_64-apple-darwin` and `x86_64-unknown-linux-gnu`
|
||||
"additional": [
|
||||
"x86_64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
"aarch64-unknown-linux-gnu"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `--cargo-name`
|
||||
|
||||
> default `undefined`
|
||||
|
||||
If not set, cli will read the `package.name` field in `Cargo.toml` under `process.cwd()`. The `-` in the name will be replaced with `_`.
|
||||
|
||||
#### `--target`
|
||||
|
||||
> default `undefined`
|
||||
|
||||
> Note you should have `rustup` installed if omit the `--target` flag. The `@napi-rs/cli` will try to find the default target on your system via `rustup` if no `--target` specified.
|
||||
|
||||
You can also define this value using the `RUST_TARGET` environment variable.
|
||||
|
||||
This value will be passed to `Cargo build` command directly. eg: `napi build --target x86_64-unknown-linux-musl`
|
||||
|
||||
#### `--cargo-flags`
|
||||
|
||||
> default `undefined`
|
||||
|
||||
Other flags you want pass to `Cargo build`.
|
||||
|
||||
#### `--cargo-cwd`
|
||||
|
||||
> default `undefined`
|
||||
|
||||
This flag can be used to build binaries that are not in the current directory. The path that is passed to this flag should be relative to the current directory.
|
||||
|
||||
### `napi artifacts`
|
||||
|
||||
> Copy artifact files in Github actions.
|
||||
|
|
10
cli/ava.config.mjs
Normal file
10
cli/ava.config.mjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
extensions: {
|
||||
ts: 'module',
|
||||
},
|
||||
files: ['**/__tests__/**/*.spec.ts'],
|
||||
nodeArguments: ['--loader=ts-node/esm/transpile-only'],
|
||||
environmentVariables: {
|
||||
TS_NODE_PROJECT: './tsconfig.json',
|
||||
},
|
||||
}
|
12
cli/cli.mjs
Executable file
12
cli/cli.mjs
Executable file
|
@ -0,0 +1,12 @@
|
|||
import { execSync } from 'child_process'
|
||||
import { resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
execSync(
|
||||
`node --loader ts-node/esm/transpile-only ${resolve(fileURLToPath(import.meta.url), '../src/cli.ts')} ${process.argv
|
||||
.slice(2)
|
||||
.join(' ')}`,
|
||||
{
|
||||
stdio: 'inherit',
|
||||
},
|
||||
)
|
505
cli/codegen/commands.ts
Normal file
505
cli/codegen/commands.ts
Normal file
|
@ -0,0 +1,505 @@
|
|||
export interface ArgSchema {
|
||||
name: string
|
||||
type: 'string'
|
||||
description: string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
export interface OptionSchema {
|
||||
name: string
|
||||
type: string
|
||||
description: string
|
||||
required?: boolean
|
||||
default?: any
|
||||
short?: string
|
||||
long?: string
|
||||
}
|
||||
|
||||
export interface CommandSchema {
|
||||
name: string
|
||||
description: string
|
||||
args: ArgSchema[]
|
||||
options: OptionSchema[]
|
||||
}
|
||||
|
||||
export type CommandDefineSchema = CommandSchema[]
|
||||
|
||||
const NEW_OPTIONS: CommandSchema = {
|
||||
name: 'new',
|
||||
description: 'Create a new project with pre-configured boilerplate',
|
||||
args: [
|
||||
{
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
description: 'The path where the napi-rs project will be created.',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
description:
|
||||
'The name of the project, default to the name of the directory if not provided',
|
||||
short: 'n',
|
||||
},
|
||||
{
|
||||
name: 'minNodeApiVersion',
|
||||
type: 'number',
|
||||
description: 'The minimum Node-API version to support',
|
||||
default: 4,
|
||||
short: 'v',
|
||||
long: 'min-node-api',
|
||||
},
|
||||
// will support it later
|
||||
// {
|
||||
// name: 'packageManager',
|
||||
// type: 'string',
|
||||
// description: 'The package manager to use',
|
||||
// default: "'yarn'",
|
||||
// },
|
||||
{
|
||||
name: 'license',
|
||||
type: 'string',
|
||||
description: 'License for open-sourced project',
|
||||
short: 'l',
|
||||
default: "'MIT'",
|
||||
},
|
||||
{
|
||||
name: 'targets',
|
||||
type: 'string[]',
|
||||
description: 'All targets the crate will be compiled for.',
|
||||
short: 't',
|
||||
default: '[]',
|
||||
},
|
||||
{
|
||||
name: 'enableDefaultTargets',
|
||||
type: 'boolean',
|
||||
description: 'Whether enable default targets',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
name: 'enableAllTargets',
|
||||
type: 'boolean',
|
||||
description: 'Whether enable all targets',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
name: 'enableTypeDef',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether enable the `type-def` feature for typescript definitions auto-generation',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
name: 'enableGithubActions',
|
||||
type: 'boolean',
|
||||
description: 'Whether generate preconfigured GitHub Actions workflow',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
name: 'dryRun',
|
||||
type: 'boolean',
|
||||
description: 'Whether to run the command in dry-run mode',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const BUILD_OPTIONS: CommandSchema = {
|
||||
name: 'build',
|
||||
description: 'Build the napi-rs project',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'target',
|
||||
type: 'string',
|
||||
description:
|
||||
'Build for the target triple, bypassed to `cargo build --target`',
|
||||
short: 't',
|
||||
},
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
},
|
||||
{
|
||||
name: 'manifestPath',
|
||||
type: 'string',
|
||||
description: 'Path to `Cargo.toml`',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
},
|
||||
{
|
||||
name: 'targetDir',
|
||||
type: 'string',
|
||||
description:
|
||||
'Directory for all crate generated artifacts, see `cargo build --target-dir`',
|
||||
},
|
||||
{
|
||||
name: 'outputDir',
|
||||
type: 'string',
|
||||
description:
|
||||
'Path to where all the built files would be put. Default to the crate folder',
|
||||
short: 'o',
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node`',
|
||||
},
|
||||
{
|
||||
name: 'jsPackageName',
|
||||
type: 'string',
|
||||
description:
|
||||
'Package name in generated js binding file. Only works with `--platform` flag',
|
||||
},
|
||||
{
|
||||
name: 'jsBinding',
|
||||
type: 'string',
|
||||
description:
|
||||
'Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output_dir`.',
|
||||
long: 'js',
|
||||
},
|
||||
{
|
||||
name: 'noJsBinding',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether to disable the generation JS binding file. Only works with `--platform` flag.',
|
||||
long: 'no-js',
|
||||
},
|
||||
{
|
||||
name: 'dts',
|
||||
type: 'string',
|
||||
description:
|
||||
'Path and filename of generated type def file. Relative to `--output_dir`',
|
||||
},
|
||||
{
|
||||
name: 'dtsHeader',
|
||||
type: 'string',
|
||||
description:
|
||||
'Custom file header for generated type def file. Only works when `typedef` feature enabled.',
|
||||
},
|
||||
{
|
||||
name: 'noDtsHeader',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled.',
|
||||
},
|
||||
{
|
||||
name: 'strip',
|
||||
type: 'boolean',
|
||||
description: 'Whether strip the library to achieve the minimum file size',
|
||||
short: 's',
|
||||
},
|
||||
{
|
||||
name: 'release',
|
||||
type: 'boolean',
|
||||
description: 'Build in release mode',
|
||||
short: 'r',
|
||||
},
|
||||
{
|
||||
name: 'verbose',
|
||||
type: 'boolean',
|
||||
description: 'Verbosely log build command trace',
|
||||
short: 'v',
|
||||
},
|
||||
{
|
||||
name: 'bin',
|
||||
type: 'string',
|
||||
description: 'Build only the specified binary',
|
||||
},
|
||||
{
|
||||
name: 'package',
|
||||
type: 'string',
|
||||
description: 'Build the specified library or the one at cwd',
|
||||
short: 'p',
|
||||
},
|
||||
{
|
||||
name: 'crossCompile',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'[experimental] cross-compile for the specified target with `cargo-xwin` on windows and `cargo-zigbuild` on other platform',
|
||||
short: 'x',
|
||||
},
|
||||
{
|
||||
name: 'watch',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'watch the crate changes and build continiously with `cargo-watch` crates',
|
||||
short: 'w',
|
||||
},
|
||||
{
|
||||
name: 'features',
|
||||
type: 'string[]',
|
||||
description: 'Space-separated list of features to activate',
|
||||
short: 'F',
|
||||
},
|
||||
{
|
||||
name: 'allFeatures',
|
||||
type: 'boolean',
|
||||
description: 'Activate all available features',
|
||||
},
|
||||
{
|
||||
name: 'noDefaultFeatures',
|
||||
type: 'boolean',
|
||||
description: 'Do not activate the `default` feature',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const ARTIFACTS_OPTIONS: CommandSchema = {
|
||||
name: 'artifacts',
|
||||
description:
|
||||
'Copy artifacts from Github Actions into npm packages and ready to publish',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'outputDir',
|
||||
type: 'string',
|
||||
description:
|
||||
'Path to the folder where all built `.node` files put, same as `--output-dir` of build command',
|
||||
short: 'o',
|
||||
default: "'./'",
|
||||
},
|
||||
{
|
||||
name: 'npmDir',
|
||||
type: 'string',
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
default: "'npm'",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const CREATE_NPM_DIRS_OPTIONS: CommandSchema = {
|
||||
name: 'createNpmDirs',
|
||||
description: 'Create npm package dirs for different platforms',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'npmDir',
|
||||
type: 'string',
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
default: "'npm'",
|
||||
},
|
||||
{
|
||||
name: 'dryRun',
|
||||
type: 'boolean',
|
||||
description: 'Dry run without touching file system',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const RENAME_OPTIONS: CommandSchema = {
|
||||
name: 'rename',
|
||||
description: 'Rename the napi-rs project',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'npmDir',
|
||||
type: 'string',
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
default: "'npm'",
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
description: 'The new name of the project',
|
||||
short: 'n',
|
||||
},
|
||||
{
|
||||
name: 'binaryName',
|
||||
type: 'string',
|
||||
description: 'The new binary name *.node files',
|
||||
short: 'b',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
type: 'string',
|
||||
description: 'The new package name of the project',
|
||||
},
|
||||
{
|
||||
name: 'manifestPath',
|
||||
type: 'string',
|
||||
description: 'Path to `Cargo.toml`',
|
||||
default: "'Cargo.toml'",
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
type: 'string',
|
||||
description: 'The new repository of the project',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'The new description of the project',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const UNIVERSALIZE_OPTIONS: CommandSchema = {
|
||||
name: 'universalize',
|
||||
description: 'Combile built binaries into one universal binary',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'outputDir',
|
||||
type: 'string',
|
||||
description:
|
||||
'Path to the folder where all built `.node` files put, same as `--output-dir` of build command',
|
||||
short: 'o',
|
||||
default: "'./'",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const VERSION_OPTIONS: CommandSchema = {
|
||||
name: 'version',
|
||||
description: 'Update version in created npm packages',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'npmDir',
|
||||
type: 'string',
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
default: "'npm'",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const PRE_PUBLISH_OPTIONS: CommandSchema = {
|
||||
name: 'prePublish',
|
||||
description: 'Update package.json and copy addons into per platform packages',
|
||||
args: [],
|
||||
options: [
|
||||
{
|
||||
name: 'cwd',
|
||||
type: 'string',
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
default: 'process.cwd()',
|
||||
},
|
||||
{
|
||||
name: 'packageJsonPath',
|
||||
type: 'string',
|
||||
description: 'Path to `package.json`',
|
||||
default: "'package.json'",
|
||||
},
|
||||
{
|
||||
name: 'npmDir',
|
||||
type: 'string',
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
default: "'npm'",
|
||||
},
|
||||
{
|
||||
name: 'tagStyle',
|
||||
type: "'npm' | 'lerna'",
|
||||
description: 'git tag style, `npm` or `lerna`',
|
||||
default: "'lerna'",
|
||||
},
|
||||
{
|
||||
name: 'ghRelease',
|
||||
type: 'boolean',
|
||||
description: 'Whether create GitHub release',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
name: 'ghReleaseName',
|
||||
type: 'string',
|
||||
description: 'GitHub release name',
|
||||
},
|
||||
{
|
||||
name: 'ghReleaseId',
|
||||
type: 'string',
|
||||
description: 'Existing GitHub release id',
|
||||
},
|
||||
{
|
||||
name: 'dryRun',
|
||||
type: 'boolean',
|
||||
description: 'Dry run without touching file system',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const commandDefines: CommandDefineSchema = [
|
||||
NEW_OPTIONS,
|
||||
BUILD_OPTIONS,
|
||||
ARTIFACTS_OPTIONS,
|
||||
CREATE_NPM_DIRS_OPTIONS,
|
||||
RENAME_OPTIONS,
|
||||
UNIVERSALIZE_OPTIONS,
|
||||
VERSION_OPTIONS,
|
||||
PRE_PUBLISH_OPTIONS,
|
||||
]
|
279
cli/codegen/index.ts
Normal file
279
cli/codegen/index.ts
Normal file
|
@ -0,0 +1,279 @@
|
|||
import { execSync } from 'child_process'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { kebabCase, startCase } from 'lodash-es'
|
||||
|
||||
import { commandDefines, CommandSchema, OptionSchema } from './commands.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const defFolder = path.join(__filename, '../../src/def')
|
||||
const docsTargetFolder = path.join(__filename, '../../docs')
|
||||
|
||||
function PascalCase(str: string) {
|
||||
return startCase(str).replace(/\s/g, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* convert command definition to command options interface
|
||||
*/
|
||||
function generateOptionsDef(command: CommandSchema) {
|
||||
const optionsName = `${PascalCase(command.name)}Options`
|
||||
|
||||
const optLines: string[] = []
|
||||
|
||||
optLines.push('/**')
|
||||
optLines.push(` * ${command.description}`)
|
||||
optLines.push(' */')
|
||||
optLines.push(`export interface ${optionsName} {`)
|
||||
command.args.forEach((arg) => {
|
||||
optLines.push(' /**')
|
||||
optLines.push(` * ${arg.description}`)
|
||||
optLines.push(' */')
|
||||
optLines.push(` ${arg.name}${arg.required ? '' : '?'}: ${arg.type}`)
|
||||
})
|
||||
|
||||
command.options.forEach((opt) => {
|
||||
optLines.push(' /**')
|
||||
optLines.push(` * ${opt.description}`)
|
||||
if (typeof opt.default !== 'undefined') {
|
||||
optLines.push(' *')
|
||||
optLines.push(` * @default ${opt.default}`)
|
||||
}
|
||||
optLines.push(' */')
|
||||
optLines.push(` ${opt.name}${opt.required ? '' : '?'}: ${opt.type}`)
|
||||
})
|
||||
|
||||
optLines.push('}\n')
|
||||
|
||||
if (command.options.some((opt) => typeof opt.default !== 'undefined')) {
|
||||
optLines.push(
|
||||
`export function applyDefault${optionsName}(options: ${optionsName}) {`,
|
||||
)
|
||||
optLines.push(` return {`)
|
||||
command.options.forEach((opt) => {
|
||||
if (typeof opt.default !== 'undefined') {
|
||||
optLines.push(` ${opt.name}: ${opt.default},`)
|
||||
}
|
||||
})
|
||||
optLines.push(' ...options,')
|
||||
optLines.push(' }')
|
||||
optLines.push('}\n')
|
||||
}
|
||||
|
||||
return optLines.join('\n')
|
||||
}
|
||||
|
||||
function getOptionDescriptor(opt: OptionSchema) {
|
||||
let desc = `--${opt.long ?? kebabCase(opt.name)}`
|
||||
if (opt.short) {
|
||||
desc += `,-${opt.short}`
|
||||
}
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
function generateCommandDef(command: CommandSchema) {
|
||||
const commandPath = kebabCase(command.name)
|
||||
const avoidList = ['path', 'name']
|
||||
|
||||
const avoidName = (name: string) => {
|
||||
return avoidList.includes(name) ? '$$' + name : name
|
||||
}
|
||||
|
||||
const prepare: string[] = []
|
||||
const cmdLines: string[] = []
|
||||
|
||||
cmdLines.push(`
|
||||
export abstract class Base${PascalCase(command.name)}Command extends Command {
|
||||
static paths = [['${commandPath}']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: '${command.description}',
|
||||
})\n`)
|
||||
|
||||
command.args.forEach((arg) => {
|
||||
cmdLines.push(
|
||||
` ${avoidName(arg.name)} = Option.String({ required: ${
|
||||
arg.required ?? false
|
||||
} })`,
|
||||
)
|
||||
})
|
||||
|
||||
cmdLines.push('')
|
||||
|
||||
command.options.forEach((opt) => {
|
||||
const optName = avoidName(opt.name)
|
||||
let optionType = ''
|
||||
|
||||
switch (opt.type) {
|
||||
case 'number':
|
||||
optionType = 'String'
|
||||
prepare.push("import * as typanion from 'typanion'")
|
||||
break
|
||||
case 'boolean':
|
||||
optionType = 'Boolean'
|
||||
break
|
||||
case 'string[]':
|
||||
optionType = 'Array'
|
||||
break
|
||||
case 'string':
|
||||
default:
|
||||
optionType = 'String'
|
||||
}
|
||||
|
||||
const optionDesc = getOptionDescriptor(opt)
|
||||
|
||||
if (opt.required) {
|
||||
cmdLines.push(` ${optName} = Option.${optionType}('${optionDesc}', {`)
|
||||
cmdLines.push(' required: true,')
|
||||
} else if (typeof opt.default !== 'undefined') {
|
||||
const defaultValue =
|
||||
typeof opt.default === 'number'
|
||||
? `'${opt.default.toString()}'`
|
||||
: opt.default
|
||||
cmdLines.push(` ${optName} = Option.${optionType}(`)
|
||||
cmdLines.push(` '${optionDesc}',`)
|
||||
cmdLines.push(` ${defaultValue},`)
|
||||
cmdLines.push(` {`)
|
||||
} else {
|
||||
cmdLines.push(
|
||||
` ${optName}?: ${opt.type} = Option.${optionType}('${optionDesc}', {`,
|
||||
)
|
||||
}
|
||||
|
||||
if (opt.type === 'number') {
|
||||
cmdLines.push(' validator: typanion.isNumber(),')
|
||||
}
|
||||
|
||||
cmdLines.push(` description: '${opt.description}'`)
|
||||
cmdLines.push(' })\n')
|
||||
})
|
||||
|
||||
cmdLines.push(` getOptions() {`)
|
||||
cmdLines.push(` return {`)
|
||||
command.args
|
||||
.map(({ name }) => name)
|
||||
.concat(command.options.map(({ name }) => name))
|
||||
.forEach((name) => {
|
||||
cmdLines.push(` ${name}: this.${avoidName(name)},`)
|
||||
})
|
||||
cmdLines.push(' }')
|
||||
cmdLines.push(' }')
|
||||
|
||||
cmdLines.push('}\n')
|
||||
|
||||
return prepare.join('\n') + '\n' + cmdLines.join('\n')
|
||||
}
|
||||
|
||||
function generateDocs(command: CommandSchema, targetFolder: string): string {
|
||||
const docsFileName = kebabCase(command.name)
|
||||
const docsFile = path.join(targetFolder, `${docsFileName}.md`)
|
||||
|
||||
const options: string[] = []
|
||||
|
||||
command.args.forEach((arg) => {
|
||||
options.push(
|
||||
[
|
||||
'',
|
||||
arg.name,
|
||||
`<${kebabCase(arg.name)}>`,
|
||||
arg.required ? 'true' : 'false',
|
||||
arg.type,
|
||||
'',
|
||||
arg.description,
|
||||
'',
|
||||
].join('|'),
|
||||
)
|
||||
})
|
||||
|
||||
command.options.forEach((opt) => {
|
||||
options.push(
|
||||
[
|
||||
'',
|
||||
opt.name,
|
||||
getOptionDescriptor(opt),
|
||||
opt.type.replace(/\|/g, '\\|'),
|
||||
opt.required ? 'true' : 'false',
|
||||
opt.default ?? '',
|
||||
opt.description,
|
||||
'',
|
||||
].join('|'),
|
||||
)
|
||||
})
|
||||
|
||||
const content = `# ${startCase(command.name)}
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
${command.description}
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`sh
|
||||
# CLI
|
||||
napi ${kebabCase(command.name)}${command.args.reduce(
|
||||
(h, arg) => h + ` <${arg.name}>`,
|
||||
'',
|
||||
)} [--options]
|
||||
\`\`\`
|
||||
|
||||
\`\`\`typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().${command.name}({
|
||||
// options
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| ------- | ----------- | ---- | -------- | ------- | ----------- |
|
||||
| | --help,-h | | | | get help |
|
||||
${options.join('\n')}
|
||||
`
|
||||
|
||||
// make sure the target folder exists
|
||||
fs.mkdirSync(targetFolder, { recursive: true })
|
||||
// write file
|
||||
fs.writeFileSync(docsFile, content)
|
||||
|
||||
return docsFile
|
||||
}
|
||||
|
||||
function generateDef(cmd: CommandSchema, folder: string): string {
|
||||
const defFileName = kebabCase(cmd.name)
|
||||
const defFilePath = path.join(folder, `${defFileName}.ts`)
|
||||
|
||||
const def = `// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
${generateCommandDef(cmd)}
|
||||
|
||||
${generateOptionsDef(cmd)}
|
||||
`
|
||||
|
||||
// make sure the target folder exists
|
||||
fs.mkdirSync(folder, { recursive: true })
|
||||
// write file
|
||||
fs.writeFileSync(defFilePath, def)
|
||||
|
||||
return defFilePath
|
||||
}
|
||||
|
||||
function codegen() {
|
||||
const outputs: string[] = []
|
||||
commandDefines.forEach((command) => {
|
||||
outputs.push(generateDef(command, defFolder))
|
||||
outputs.push(generateDocs(command, docsTargetFolder))
|
||||
})
|
||||
|
||||
outputs.forEach((output) => {
|
||||
execSync(`yarn prettier -w ${output}`)
|
||||
})
|
||||
}
|
||||
|
||||
codegen()
|
31
cli/docs/artifacts.md
Normal file
31
cli/docs/artifacts.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Artifacts
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Copy artifacts from Github Actions into npm packages and ready to publish
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi artifacts [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().artifacts({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| outputDir | --output-dir,-o | string | false | './' | Path to the folder where all built `.node` files put, same as `--output-dir` of build command |
|
||||
| npmDir | --npm-dir | string | false | 'npm' | Path to the folder where the npm packages put |
|
50
cli/docs/build.md
Normal file
50
cli/docs/build.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Build
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Build the napi-rs project
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi build [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().build({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| ----------------- | --------------------- | -------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| | --help,-h | | | | get help |
|
||||
| target | --target,-t | string | false | | Build for the target triple, bypassed to `cargo build --target` |
|
||||
| cwd | --cwd | string | false | | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| manifestPath | --manifest-path | string | false | | Path to `Cargo.toml` |
|
||||
| packageJsonPath | --package-json-path | string | false | | Path to `package.json` |
|
||||
| targetDir | --target-dir | string | false | | Directory for all crate generated artifacts, see `cargo build --target-dir` |
|
||||
| outputDir | --output-dir,-o | string | false | | Path to where all the built files would be put. Default to the crate folder |
|
||||
| platform | --platform | boolean | false | | Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node` |
|
||||
| jsPackageName | --js-package-name | string | false | | Package name in generated js binding file. Only works with `--platform` flag |
|
||||
| jsBinding | --js | string | false | | Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output_dir`. |
|
||||
| noJsBinding | --no-js | boolean | false | | Whether to disable the generation JS binding file. Only works with `--platform` flag. |
|
||||
| dts | --dts | string | false | | Path and filename of generated type def file. Relative to `--output_dir` |
|
||||
| dtsHeader | --dts-header | string | false | | Custom file header for generated type def file. Only works when `typedef` feature enabled. |
|
||||
| noDtsHeader | --no-dts-header | boolean | false | | Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled. |
|
||||
| strip | --strip,-s | boolean | false | | Whether strip the library to achieve the minimum file size |
|
||||
| release | --release,-r | boolean | false | | Build in release mode |
|
||||
| verbose | --verbose,-v | boolean | false | | Verbosely log build command trace |
|
||||
| bin | --bin | string | false | | Build only the specified binary |
|
||||
| package | --package,-p | string | false | | Build the specified library or the one at cwd |
|
||||
| crossCompile | --cross-compile,-x | boolean | false | | [experimental] cross-compile for the specified target with `cargo-xwin` on windows and `cargo-zigbuild` on other platform |
|
||||
| watch | --watch,-w | boolean | false | | watch the crate changes and build continiously with `cargo-watch` crates |
|
||||
| features | --features,-F | string[] | false | | Space-separated list of features to activate |
|
||||
| allFeatures | --all-features | boolean | false | | Activate all available features |
|
||||
| noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
|
31
cli/docs/create-npm-dirs.md
Normal file
31
cli/docs/create-npm-dirs.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Create Npm Dirs
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Create npm package dirs for different platforms
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi create-npm-dirs [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().createNpmDirs({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| npmDir | --npm-dir | string | false | 'npm' | Path to the folder where the npm packages put |
|
||||
| dryRun | --dry-run | boolean | false | false | Dry run without touching file system |
|
37
cli/docs/new.md
Normal file
37
cli/docs/new.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# New
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Create a new project with pre-configured boilerplate
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi new <path> [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().new({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| -------------------- | ------------------------ | -------- | -------- | ------- | -------------------------------------------------------------------------------- |
|
||||
| | --help,-h | | | | get help |
|
||||
| path | <path> | true | string | | The path where the napi-rs project will be created. |
|
||||
| name | --name,-n | string | false | | The name of the project, default to the name of the directory if not provided |
|
||||
| minNodeApiVersion | --min-node-api,-v | number | false | 4 | The minimum Node-API version to support |
|
||||
| license | --license,-l | string | false | 'MIT' | License for open-sourced project |
|
||||
| targets | --targets,-t | string[] | false | [] | All targets the crate will be compiled for. |
|
||||
| enableDefaultTargets | --enable-default-targets | boolean | false | true | Whether enable default targets |
|
||||
| enableAllTargets | --enable-all-targets | boolean | false | false | Whether enable all targets |
|
||||
| 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 |
|
35
cli/docs/pre-publish.md
Normal file
35
cli/docs/pre-publish.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Pre Publish
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Update package.json and copy addons into per platform packages
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi pre-publish [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().prePublish({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ---------------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| npmDir | --npm-dir | string | false | 'npm' | Path to the folder where the npm packages put |
|
||||
| tagStyle | --tag-style | 'npm' \| 'lerna' | false | 'lerna' | git tag style, `npm` or `lerna` |
|
||||
| ghRelease | --gh-release | boolean | false | true | Whether create GitHub release |
|
||||
| ghReleaseName | --gh-release-name | string | false | | GitHub release name |
|
||||
| ghReleaseId | --gh-release-id | string | false | | Existing GitHub release id |
|
||||
| dryRun | --dry-run | boolean | false | false | Dry run without touching file system |
|
36
cli/docs/rename.md
Normal file
36
cli/docs/rename.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Rename
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Rename the napi-rs project
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi rename [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().rename({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| npmDir | --npm-dir | string | false | 'npm' | Path to the folder where the npm packages put |
|
||||
| name | --name,-n | string | false | | The new name of the project |
|
||||
| binaryName | --binary-name,-b | string | false | | The new binary name \*.node files |
|
||||
| packageName | --package-name | string | false | | The new package name of the project |
|
||||
| manifestPath | --manifest-path | string | false | 'Cargo.toml' | Path to `Cargo.toml` |
|
||||
| repository | --repository | string | false | | The new repository of the project |
|
||||
| description | --description | string | false | | The new description of the project |
|
30
cli/docs/universalize.md
Normal file
30
cli/docs/universalize.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Universalize
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Combile built binaries into one universal binary
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi universalize [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().universalize({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| outputDir | --output-dir,-o | string | false | './' | Path to the folder where all built `.node` files put, same as `--output-dir` of build command |
|
30
cli/docs/version.md
Normal file
30
cli/docs/version.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Version
|
||||
|
||||
> This file is generated by cli/codegen. Do not edit this file manually.
|
||||
|
||||
Update version in created npm packages
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# CLI
|
||||
napi version [--options]
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Programatically
|
||||
import { NapiCli } from '@napi-rs/cli'
|
||||
|
||||
new NapiCli().version({
|
||||
// options
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Options | CLI Options | type | required | default | description |
|
||||
| --------------- | ------------------- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| | --help,-h | | | | get help |
|
||||
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
||||
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
||||
| npmDir | --npm-dir | string | false | 'npm' | Path to the folder where the npm packages put |
|
11
cli/esbuild.mjs
Normal file
11
cli/esbuild.mjs
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as esbuild from 'esbuild'
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: ['./dist/index.js'],
|
||||
outfile: './dist/index.cjs',
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
define: {
|
||||
'import.meta.url': '__filename',
|
||||
},
|
||||
})
|
|
@ -2,30 +2,57 @@
|
|||
"name": "@napi-rs/cli",
|
||||
"version": "2.15.2",
|
||||
"description": "Cli tools for napi-rs",
|
||||
"author": "LongYinan <lynweklm@gmail.com>",
|
||||
"homepage": "https://github.com/napi-rs/napi-rs",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"bin": {
|
||||
"napi": "./dist/cli.js",
|
||||
"napi-raw": "./cli.mjs"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"default": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"./package.json": {
|
||||
"import": "./package.json",
|
||||
"require": "./package.json"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"keywords": [
|
||||
"cli",
|
||||
"rust",
|
||||
"napi",
|
||||
"n-api",
|
||||
"node-api",
|
||||
"node-addon",
|
||||
"neon"
|
||||
],
|
||||
"author": "LongYinan <lynweklm@gmail.com>",
|
||||
"homepage": "https://github.com/napi-rs/napi-rs",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"napi": "./scripts/index.js"
|
||||
},
|
||||
"files": [
|
||||
"scripts"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "LongYinan",
|
||||
"email": "lynweklm@gmail.com",
|
||||
"homepage": "https://github.com/Brooooooklyn"
|
||||
},
|
||||
{
|
||||
"name": "forehalo",
|
||||
"homepage": "https://github.com/forehalo"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
|
@ -39,26 +66,34 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/napi-rs/napi-rs/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"clipanion": "^3.2.0",
|
||||
"colorette": "^2.0.19",
|
||||
"debug": "^4.3.4",
|
||||
"inquirer": "^9.1.5",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"typanion": "^3.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@types/inquirer": "^9.0.3",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"clipanion": "^3.1.0",
|
||||
"colorette": "^2.0.19",
|
||||
"core-js": "^3.27.1",
|
||||
"debug": "^4.3.4",
|
||||
"env-paths": "^3.0.0",
|
||||
"fdir": "^5.3.0",
|
||||
"inquirer": "^9.1.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"toml": "^3.0.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typanion": "^3.12.1"
|
||||
"ava": "^5.2.0",
|
||||
"esbuild": "^0.17.14",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"scripts": {
|
||||
"codegen": "node --loader ts-node/esm/transpile-only ./codegen/index.ts",
|
||||
"build": "tsc && yarn build:cjs",
|
||||
"build:cjs": "node ./esbuild.mjs",
|
||||
"test": "ava"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
import test from 'ava'
|
||||
|
||||
import { parseTriple } from '../parse-triple'
|
||||
|
||||
const triples = [
|
||||
{
|
||||
name: 'x86_64-unknown-linux-musl',
|
||||
expected: {
|
||||
abi: 'musl',
|
||||
arch: 'x64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-x64-musl',
|
||||
raw: 'x86_64-unknown-linux-musl',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'x86_64-unknown-linux-gnu',
|
||||
expected: {
|
||||
abi: 'gnu',
|
||||
arch: 'x64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-x64-gnu',
|
||||
raw: 'x86_64-unknown-linux-gnu',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'x86_64-pc-windows-msvc',
|
||||
expected: {
|
||||
abi: 'msvc',
|
||||
arch: 'x64',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-x64-msvc',
|
||||
raw: 'x86_64-pc-windows-msvc',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'x86_64-apple-darwin',
|
||||
expected: {
|
||||
abi: null,
|
||||
arch: 'x64',
|
||||
platform: 'darwin',
|
||||
platformArchABI: 'darwin-x64',
|
||||
raw: 'x86_64-apple-darwin',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'i686-pc-windows-msvc',
|
||||
expected: {
|
||||
abi: 'msvc',
|
||||
arch: 'ia32',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-ia32-msvc',
|
||||
raw: 'i686-pc-windows-msvc',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'x86_64-unknown-freebsd',
|
||||
expected: {
|
||||
abi: null,
|
||||
arch: 'x64',
|
||||
platform: 'freebsd',
|
||||
platformArchABI: 'freebsd-x64',
|
||||
raw: 'x86_64-unknown-freebsd',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'aarch64-unknown-linux-gnu',
|
||||
expected: {
|
||||
abi: 'gnu',
|
||||
arch: 'arm64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-arm64-gnu',
|
||||
raw: 'aarch64-unknown-linux-gnu',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'aarch64-pc-windows-msvc',
|
||||
expected: {
|
||||
abi: 'msvc',
|
||||
arch: 'arm64',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-arm64-msvc',
|
||||
raw: 'aarch64-pc-windows-msvc',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'armv7-unknown-linux-gnueabihf',
|
||||
expected: {
|
||||
abi: 'gnueabihf',
|
||||
arch: 'arm',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-arm-gnueabihf',
|
||||
raw: 'armv7-unknown-linux-gnueabihf',
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
name: 'aarch64-linux-android',
|
||||
expected: {
|
||||
abi: null,
|
||||
arch: 'arm64',
|
||||
platform: 'android',
|
||||
platformArchABI: 'android-arm64',
|
||||
raw: 'aarch64-linux-android',
|
||||
},
|
||||
} as const,
|
||||
{
|
||||
name: 'armv7-linux-androideabi',
|
||||
expected: {
|
||||
abi: 'eabi',
|
||||
arch: 'arm',
|
||||
platform: 'android',
|
||||
platformArchABI: 'android-arm-eabi',
|
||||
raw: 'armv7-linux-androideabi',
|
||||
},
|
||||
} as const,
|
||||
{
|
||||
name: 'universal-apple-darwin',
|
||||
expected: {
|
||||
abi: null,
|
||||
arch: 'universal',
|
||||
platform: 'darwin',
|
||||
platformArchABI: 'darwin-universal',
|
||||
raw: 'universal-apple-darwin',
|
||||
},
|
||||
} as const,
|
||||
]
|
||||
|
||||
for (const triple of triples) {
|
||||
test(`should parse ${triple.name}`, (t) => {
|
||||
t.deepEqual(parseTriple(triple.name), triple.expected)
|
||||
})
|
||||
}
|
93
cli/src/api/artifacts.ts
Normal file
93
cli/src/api/artifacts.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { join, parse, resolve } from 'path'
|
||||
|
||||
import * as colors from 'colorette'
|
||||
|
||||
import {
|
||||
applyDefaultArtifactsOptions,
|
||||
ArtifactsOptions,
|
||||
} from '../def/artifacts.js'
|
||||
import {
|
||||
readNapiConfig,
|
||||
debugFactory,
|
||||
readFileAsync,
|
||||
writeFileAsync,
|
||||
UniArchsByPlatform,
|
||||
readdirAsync,
|
||||
} from '../utils/index.js'
|
||||
|
||||
const debug = debugFactory('artifacts')
|
||||
|
||||
export async function collectArtifacts(userOptions: ArtifactsOptions) {
|
||||
const options = applyDefaultArtifactsOptions(userOptions)
|
||||
|
||||
const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
|
||||
const { targets, binaryName } = await readNapiConfig(packageJsonPath)
|
||||
|
||||
const distDirs = targets.map((platform) =>
|
||||
resolve(options.cwd, options.npmDir, platform.platformArchABI),
|
||||
)
|
||||
|
||||
const universalSourceBins = new Set(
|
||||
targets
|
||||
.filter((platform) => platform.arch === 'universal')
|
||||
.flatMap((p) =>
|
||||
UniArchsByPlatform[p.platform]?.map((a) => `${p.platform}-${a}`),
|
||||
)
|
||||
.filter(Boolean) as string[],
|
||||
)
|
||||
|
||||
await collectNodeBinaries(resolve(options.cwd, options.outputDir)).then(
|
||||
(output) =>
|
||||
Promise.all(
|
||||
output.map(async (filePath) => {
|
||||
debug.info(`Read [${colors.yellowBright(filePath)}]`)
|
||||
const sourceContent = await readFileAsync(filePath)
|
||||
const parsedName = parse(filePath)
|
||||
const [_binaryName, platformArchABI] = parsedName.name.split('.')
|
||||
if (_binaryName !== binaryName) {
|
||||
debug.warn(
|
||||
`[${_binaryName}] is not matched with [${binaryName}], skip`,
|
||||
)
|
||||
return
|
||||
}
|
||||
const dir = distDirs.find((dir) => dir.includes(platformArchABI))
|
||||
if (!dir && universalSourceBins.has(platformArchABI)) {
|
||||
debug.warn(
|
||||
`[${platformArchABI}] has no dist dir but it is source bin for universal arch, skip`,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!dir) {
|
||||
throw new Error(`No dist dir found for ${filePath}`)
|
||||
}
|
||||
|
||||
const distFilePath = join(dir, parsedName.base)
|
||||
debug.info(
|
||||
`Write file content to [${colors.yellowBright(distFilePath)}]`,
|
||||
)
|
||||
await writeFileAsync(distFilePath, sourceContent)
|
||||
const distFilePathLocal = join(
|
||||
parse(packageJsonPath).dir,
|
||||
parsedName.base,
|
||||
)
|
||||
debug.info(
|
||||
`Write file content to [${colors.yellowBright(distFilePathLocal)}]`,
|
||||
)
|
||||
await writeFileAsync(distFilePathLocal, sourceContent)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async function collectNodeBinaries(root: string) {
|
||||
const files = await readdirAsync(root, { withFileTypes: true })
|
||||
const nodeBinaries = files
|
||||
.filter((file) => file.isFile() && file.name.endsWith('.node'))
|
||||
.map((file) => join(root, file.name))
|
||||
|
||||
const dirs = files.filter((file) => file.isDirectory())
|
||||
for (const dir of dirs) {
|
||||
nodeBinaries.push(...(await collectNodeBinaries(join(root, dir.name))))
|
||||
}
|
||||
return nodeBinaries
|
||||
}
|
549
cli/src/api/build.ts
Normal file
549
cli/src/api/build.ts
Normal file
|
@ -0,0 +1,549 @@
|
|||
import { spawn } from 'child_process'
|
||||
import { createHash } from 'crypto'
|
||||
import { tmpdir } from 'os'
|
||||
import { parse, join, resolve } from 'path'
|
||||
|
||||
import * as colors from 'colorette'
|
||||
|
||||
import { BuildOptions as RawBuildOptions } from '../def/build.js'
|
||||
import {
|
||||
CLI_VERSION,
|
||||
copyFileAsync,
|
||||
Crate,
|
||||
debugFactory,
|
||||
DEFAULT_TYPE_DEF_HEADER,
|
||||
fileExists,
|
||||
getSystemDefaultTarget,
|
||||
getTargetLinker,
|
||||
mkdirAsync,
|
||||
NapiConfig,
|
||||
parseMetadata,
|
||||
parseTriple,
|
||||
processTypeDef,
|
||||
readNapiConfig,
|
||||
Target,
|
||||
targetToEnvVar,
|
||||
tryInstallCargoBinary,
|
||||
unlinkAsync,
|
||||
writeFileAsync,
|
||||
} from '../utils/index.js'
|
||||
|
||||
import { createJsBinding } from './templates/index.js'
|
||||
|
||||
const debug = debugFactory('build')
|
||||
|
||||
type OutputKind = 'js' | 'dts' | 'node' | 'exe'
|
||||
type Output = {
|
||||
kind: OutputKind
|
||||
path: string
|
||||
}
|
||||
|
||||
type BuildOptions = RawBuildOptions & {
|
||||
cargoOptions?: string[]
|
||||
}
|
||||
|
||||
export async function buildProject(options: BuildOptions) {
|
||||
debug('napi build command receive options: %O', options)
|
||||
|
||||
const cwd = options.cwd ?? process.cwd()
|
||||
|
||||
const resolvePath = (...paths: string[]) => resolve(cwd, ...paths)
|
||||
|
||||
const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
|
||||
const metadata = parseMetadata(manifestPath)
|
||||
|
||||
const pkg = metadata.packages.find((p) => {
|
||||
// package with given name
|
||||
if (options.package) {
|
||||
return p.name === options.package
|
||||
} else {
|
||||
return p.manifest_path === manifestPath
|
||||
}
|
||||
})
|
||||
|
||||
if (!pkg) {
|
||||
throw new Error(
|
||||
'Unable to find crate to build. It seems you are trying to build a crate in a workspace, try using `--package` option to specify the package to build.',
|
||||
)
|
||||
}
|
||||
|
||||
const crateDir = parse(pkg.manifest_path).dir
|
||||
|
||||
const builder = new Builder(
|
||||
options,
|
||||
pkg,
|
||||
cwd,
|
||||
options.target
|
||||
? parseTriple(options.target)
|
||||
: process.env.CARGO_BUILD_TARGET
|
||||
? parseTriple(process.env.CARGO_BUILD_TARGET)
|
||||
: getSystemDefaultTarget(),
|
||||
crateDir,
|
||||
resolvePath(options.outputDir ?? crateDir),
|
||||
options.targetDir ??
|
||||
process.env.CARGO_BUILD_TARGET_DIR ??
|
||||
metadata.target_directory,
|
||||
await readNapiConfig(
|
||||
resolvePath(options.packageJsonPath ?? 'package.json'),
|
||||
),
|
||||
)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private readonly args: string[] = []
|
||||
private readonly envs: Record<string, string> = {}
|
||||
private readonly outputs: Output[] = []
|
||||
|
||||
constructor(
|
||||
private readonly options: BuildOptions,
|
||||
private readonly crate: Crate,
|
||||
private readonly cwd: string,
|
||||
private readonly target: Target,
|
||||
private readonly crateDir: string,
|
||||
private readonly outputDir: string,
|
||||
private readonly targetDir: string,
|
||||
private readonly config: NapiConfig,
|
||||
) {}
|
||||
|
||||
get cdyLibName() {
|
||||
return this.crate.targets.find((t) => t.crate_types.includes('cdylib'))
|
||||
?.name
|
||||
}
|
||||
|
||||
get binName() {
|
||||
return (
|
||||
this.options.bin ??
|
||||
// only available if not cdylib or bin name specified
|
||||
(this.cdyLibName
|
||||
? null
|
||||
: this.crate.targets.find((t) => t.crate_types.includes('bin'))?.name)
|
||||
)
|
||||
}
|
||||
|
||||
build() {
|
||||
if (!this.cdyLibName) {
|
||||
const warning =
|
||||
'Missing `crate-type = ["cdylib"]` in [lib] config. The build result will not be available as node addon.'
|
||||
|
||||
if (this.binName) {
|
||||
debug.warn(warning)
|
||||
} else {
|
||||
throw new Error(warning)
|
||||
}
|
||||
}
|
||||
|
||||
return this.pickBinary()
|
||||
.setPackage()
|
||||
.setFeatures()
|
||||
.setTarget()
|
||||
.setEnvs()
|
||||
.setBypassArgs()
|
||||
.exec()
|
||||
}
|
||||
|
||||
private exec() {
|
||||
debug(`Start building crate: ${this.crate.name}`)
|
||||
debug(' %i', `cargo ${this.args.join(' ')}`)
|
||||
|
||||
const controller = new AbortController()
|
||||
|
||||
const buildTask = new Promise<void>((resolve, reject) => {
|
||||
const buildProcess = spawn('cargo', this.args, {
|
||||
env: {
|
||||
...process.env,
|
||||
...this.envs,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
cwd: this.cwd,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
buildProcess.once('exit', (code) => {
|
||||
if (code === 0) {
|
||||
debug('%i', `Build crate ${this.crate.name} successfully!`)
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`Build failed with exit code ${code}`))
|
||||
}
|
||||
})
|
||||
|
||||
buildProcess.once('error', (e) => {
|
||||
reject(
|
||||
new Error(`Build failed with error: ${e.message}`, {
|
||||
cause: e,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
task: buildTask.then(() => this.postBuild()),
|
||||
abort: () => controller.abort(),
|
||||
}
|
||||
}
|
||||
|
||||
private pickBinary() {
|
||||
let set = false
|
||||
if (this.options.watch) {
|
||||
if (process.env.CI) {
|
||||
debug.warn('Watch mode is not supported in CI environment')
|
||||
} else {
|
||||
debug('Use %i', 'cargo-watch')
|
||||
tryInstallCargoBinary('cargo-watch', 'watch')
|
||||
// yarn napi watch --target x86_64-unknown-linux-gnu [--cross-compile]
|
||||
// ===>
|
||||
// cargo watch [...] -- build --target x86_64-unknown-linux-gnu
|
||||
// cargo watch [...] -- zigbuild --target x86_64-unknown-linux-gnu
|
||||
this.args.push(
|
||||
'watch',
|
||||
'--why',
|
||||
'-i',
|
||||
'*.{js,ts,node}',
|
||||
'-w',
|
||||
this.crateDir,
|
||||
'--',
|
||||
'cargo',
|
||||
)
|
||||
set = true
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.crossCompile) {
|
||||
if (this.target.platform === 'win32') {
|
||||
if (process.platform === 'win32') {
|
||||
debug.warn(
|
||||
'You are trying to cross compile to win32 platform on win32 platform which is unnecessary.',
|
||||
)
|
||||
} else {
|
||||
// use cargo-xwin to cross compile to win32 platform
|
||||
debug('Use %i', 'cargo-xwin')
|
||||
tryInstallCargoBinary('cargo-xwin', 'xwin')
|
||||
this.args.push('xwin', 'build')
|
||||
if (this.target.arch === 'ia32') {
|
||||
this.envs.XWIN_ARCH = 'x86'
|
||||
}
|
||||
set = true
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
this.target.platform === 'linux' &&
|
||||
process.platform === 'linux' &&
|
||||
this.target.arch === process.arch &&
|
||||
(function (abi: string | null) {
|
||||
const glibcVersionRuntime =
|
||||
// @ts-expect-error
|
||||
process.report?.getReport()?.header?.glibcVersionRuntime
|
||||
const libc = glibcVersionRuntime ? 'gnu' : 'musl'
|
||||
return abi === libc
|
||||
})(this.target.abi)
|
||||
) {
|
||||
debug.warn(
|
||||
'You are trying to cross compile to linux target on linux platform which is unnecessary.',
|
||||
)
|
||||
} else if (
|
||||
this.target.platform === 'darwin' &&
|
||||
process.platform === 'darwin'
|
||||
) {
|
||||
debug.warn(
|
||||
'You are trying to cross compile to darwin target on darwin platform which is unnecessary.',
|
||||
)
|
||||
} else {
|
||||
// use cargo-zigbuild to cross compile to other platforms
|
||||
debug('Use %i', 'cargo-zigbuild')
|
||||
tryInstallCargoBinary('cargo-zigbuild', 'zigbuild')
|
||||
this.args.push('zigbuild')
|
||||
set = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!set) {
|
||||
this.args.push('build')
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private setPackage() {
|
||||
const args = []
|
||||
|
||||
if (this.options.package) {
|
||||
args.push('--package', this.options.package)
|
||||
}
|
||||
|
||||
if (this.binName) {
|
||||
args.push('--bin', this.binName)
|
||||
}
|
||||
|
||||
if (args.length) {
|
||||
debug('Set package flags: ')
|
||||
debug(' %O', args)
|
||||
this.args.push(...args)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private setTarget() {
|
||||
debug('Set compiling target to: ')
|
||||
debug(' %i', this.target.triple)
|
||||
|
||||
this.args.push('--target', this.target.triple)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private setEnvs() {
|
||||
// type definition intermediate file
|
||||
this.envs.TYPE_DEF_TMP_PATH = this.getIntermediateTypeFile()
|
||||
this.envs.CARGO_CFG_NAPI_RS_CLI_VERSION = CLI_VERSION
|
||||
|
||||
// RUSTFLAGS
|
||||
let rustflags =
|
||||
process.env.RUSTFLAGS ?? process.env.CARGO_BUILD_RUSTFLAGS ?? ''
|
||||
|
||||
if (
|
||||
this.target.abi?.includes('musl') &&
|
||||
!rustflags.includes('target-feature=-crt-static')
|
||||
) {
|
||||
rustflags += ' -C target-feature=-crt-static'
|
||||
}
|
||||
|
||||
if (this.options.strip && !rustflags.includes('link-arg=-s')) {
|
||||
rustflags += ' -C link-arg=-s'
|
||||
}
|
||||
|
||||
if (rustflags.length) {
|
||||
this.envs.RUSTFLAGS = rustflags
|
||||
}
|
||||
// END RUSTFLAGS
|
||||
|
||||
// LINKER
|
||||
const linker = getTargetLinker(this.target.triple)
|
||||
if (
|
||||
linker &&
|
||||
!process.env.RUSTC_LINKER &&
|
||||
!process.env[`CARGET_TARGET_${targetToEnvVar(this.target.triple)}_LINKER`]
|
||||
) {
|
||||
this.envs.RUSTC_LINKER = linker
|
||||
}
|
||||
|
||||
if (this.target.platform === 'android') {
|
||||
const { ANDROID_NDK_LATEST_HOME } = process.env
|
||||
if (!ANDROID_NDK_LATEST_HOME) {
|
||||
debug.warn(
|
||||
`${colors.red(
|
||||
'ANDROID_NDK_LATEST_HOME',
|
||||
)} environment variable is missing`,
|
||||
)
|
||||
}
|
||||
|
||||
const targetArch = this.target.arch === 'arm' ? 'armv7a' : 'aarch64'
|
||||
const targetPlatform =
|
||||
this.target.arch === 'arm' ? 'androideabi24' : 'android24'
|
||||
Object.assign(this.envs, {
|
||||
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-android24-clang`,
|
||||
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
|
||||
CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
|
||||
CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
|
||||
AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`,
|
||||
ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
|
||||
PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${process.env.PATH}`,
|
||||
})
|
||||
}
|
||||
// END LINKER
|
||||
|
||||
debug('Set envs: ')
|
||||
Object.entries(this.envs).forEach(([k, v]) => {
|
||||
debug(' %i', `${k}=${v}`)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private setFeatures() {
|
||||
const args = []
|
||||
if (this.options.allFeatures) {
|
||||
args.push('--all-features')
|
||||
} else if (this.options.noDefaultFeatures) {
|
||||
args.push('--no-default-features')
|
||||
} else if (this.options.features) {
|
||||
args.push('--features', ...this.options.features)
|
||||
}
|
||||
|
||||
debug('Set features flags: ')
|
||||
debug(' %O', args)
|
||||
this.args.push(...args)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private setBypassArgs() {
|
||||
if (this.options.release) {
|
||||
this.args.push('--release')
|
||||
}
|
||||
|
||||
if (this.options.verbose) {
|
||||
this.args.push('--verbose')
|
||||
}
|
||||
|
||||
if (this.options.targetDir) {
|
||||
this.args.push('--target-dir', this.options.targetDir)
|
||||
}
|
||||
|
||||
if (this.options.cargoOptions?.length) {
|
||||
this.args.push(...this.options.cargoOptions)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private getIntermediateTypeFile() {
|
||||
return join(
|
||||
tmpdir(),
|
||||
`${this.crate.name}-${createHash('sha256')
|
||||
.update(this.crate.manifest_path)
|
||||
.update(CLI_VERSION)
|
||||
.digest('hex')
|
||||
.substring(0, 8)}.napi_type_def.tmp`,
|
||||
)
|
||||
}
|
||||
|
||||
private async postBuild() {
|
||||
try {
|
||||
debug(`Try to create output directory:`)
|
||||
debug(' %i', this.outputDir)
|
||||
await mkdirAsync(this.outputDir, { recursive: true })
|
||||
debug(`Output directory created`)
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create output directory ${this.outputDir}`, {
|
||||
cause: e,
|
||||
})
|
||||
}
|
||||
|
||||
await this.copyArtifact()
|
||||
|
||||
// only for cdylib
|
||||
if (this.cdyLibName) {
|
||||
await this.generateTypeDef()
|
||||
await this.writeJsBinding()
|
||||
}
|
||||
|
||||
return this.outputs
|
||||
}
|
||||
|
||||
private async copyArtifact() {
|
||||
const [srcName, destName] = this.getArtifactNames()
|
||||
if (!srcName || !destName) {
|
||||
return
|
||||
}
|
||||
|
||||
const src = join(
|
||||
this.targetDir,
|
||||
this.target.triple,
|
||||
this.options.release ? 'release' : 'debug',
|
||||
srcName,
|
||||
)
|
||||
const dest = join(this.outputDir, destName)
|
||||
|
||||
try {
|
||||
if (await fileExists(dest)) {
|
||||
debug('Old artifact found, remove it first')
|
||||
await unlinkAsync(dest)
|
||||
}
|
||||
debug('Copy artifact to:')
|
||||
debug(' %i', dest)
|
||||
await copyFileAsync(src, dest)
|
||||
this.outputs.push({
|
||||
kind: dest.endsWith('.node') ? 'node' : 'exe',
|
||||
path: dest,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error('Failed to copy artifact', {
|
||||
cause: e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private getArtifactNames() {
|
||||
if (this.cdyLibName) {
|
||||
const cdyLib = this.cdyLibName.replace(/-/g, '_')
|
||||
|
||||
const srcName =
|
||||
this.target.platform === 'darwin'
|
||||
? `lib${cdyLib}.dylib`
|
||||
: this.target.platform === 'win32'
|
||||
? `${cdyLib}.dll`
|
||||
: `lib${cdyLib}.so`
|
||||
|
||||
let destName = this.config.binaryName
|
||||
// add platform suffix to binary name
|
||||
// index[.linux-x64-gnu].node
|
||||
// ^^^^^^^^^^^^^^
|
||||
if (this.options.platform) {
|
||||
destName += `.${this.target.platformArchABI}`
|
||||
}
|
||||
destName += '.node'
|
||||
|
||||
return [srcName, destName]
|
||||
} else if (this.binName) {
|
||||
const srcName =
|
||||
this.target.platform === 'win32' ? `${this.binName}.exe` : this.binName
|
||||
|
||||
return [srcName, srcName]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private async generateTypeDef() {
|
||||
if (!(await fileExists(this.envs.TYPE_DEF_TMP_PATH))) {
|
||||
return
|
||||
}
|
||||
|
||||
const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
|
||||
|
||||
const dts = await processTypeDef(
|
||||
this.envs.TYPE_DEF_TMP_PATH,
|
||||
!this.options.noDtsHeader
|
||||
? this.options.dtsHeader ?? DEFAULT_TYPE_DEF_HEADER
|
||||
: '',
|
||||
)
|
||||
|
||||
try {
|
||||
debug('Writing type def to:')
|
||||
debug(' %i', dest)
|
||||
await writeFileAsync(dest, dts, 'utf-8')
|
||||
this.outputs.push({
|
||||
kind: 'dts',
|
||||
path: dest,
|
||||
})
|
||||
} catch (e) {
|
||||
debug.error('Failed to write type def file')
|
||||
debug.error(e as Error)
|
||||
}
|
||||
}
|
||||
|
||||
private async writeJsBinding() {
|
||||
if (!this.options.platform || this.options.noJsBinding) {
|
||||
return
|
||||
}
|
||||
|
||||
const dest = join(this.outputDir, this.options.jsBinding ?? 'index.js')
|
||||
|
||||
const js = createJsBinding(this.config.binaryName, this.config.packageName)
|
||||
|
||||
try {
|
||||
debug('Writing js binding to:')
|
||||
debug(' %i', dest)
|
||||
await writeFileAsync(dest, js, 'utf-8')
|
||||
this.outputs.push({
|
||||
kind: 'js',
|
||||
path: dest,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error('Failed to write js binding file', { cause: e })
|
||||
}
|
||||
}
|
||||
}
|
105
cli/src/api/create-npm-dirs.ts
Normal file
105
cli/src/api/create-npm-dirs.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { join, resolve } from 'path'
|
||||
|
||||
import {
|
||||
applyDefaultCreateNpmDirsOptions,
|
||||
CreateNpmDirsOptions,
|
||||
} from '../def/create-npm-dirs.js'
|
||||
import {
|
||||
debugFactory,
|
||||
readNapiConfig,
|
||||
mkdirAsync as rawMkdirAsync,
|
||||
pick,
|
||||
writeFileAsync as rawWriteFileAsync,
|
||||
Target,
|
||||
} from '../utils/index.js'
|
||||
|
||||
const debug = debugFactory('create-npm-dirs')
|
||||
|
||||
export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
|
||||
const options = applyDefaultCreateNpmDirsOptions(userOptions)
|
||||
|
||||
async function mkdirAsync(dir: string) {
|
||||
debug('Try to create dir: %i', dir)
|
||||
if (options.dryRun) {
|
||||
return
|
||||
}
|
||||
|
||||
await rawMkdirAsync(dir, {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
async function writeFileAsync(file: string, content: string) {
|
||||
debug('Writing file %i', file)
|
||||
|
||||
if (options.dryRun) {
|
||||
debug(content)
|
||||
return
|
||||
}
|
||||
|
||||
await rawWriteFileAsync(file, content)
|
||||
}
|
||||
|
||||
const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
|
||||
const npmPath = resolve(options.cwd, options.npmDir)
|
||||
|
||||
debug(`Read content from [${packageJsonPath}]`)
|
||||
|
||||
const { targets, binaryName, packageName, packageJson } =
|
||||
await readNapiConfig(packageJsonPath)
|
||||
|
||||
for (const target of targets) {
|
||||
const targetDir = join(npmPath, `${target.platformArchABI}`)
|
||||
await mkdirAsync(targetDir)
|
||||
|
||||
const binaryFileName = `${binaryName}.${target.platformArchABI}.node`
|
||||
const scopedPackageJson = {
|
||||
name: `${packageName}-${target.platformArchABI}`,
|
||||
version: packageJson.version,
|
||||
os: [target.platform],
|
||||
cpu: target.arch !== 'universal' ? [target.arch] : undefined,
|
||||
main: binaryFileName,
|
||||
files: [binaryFileName],
|
||||
...pick(
|
||||
packageJson,
|
||||
'description',
|
||||
'keywords',
|
||||
'author',
|
||||
'authors',
|
||||
'homepage',
|
||||
'license',
|
||||
'engines',
|
||||
'publishConfig',
|
||||
'repository',
|
||||
'bugs',
|
||||
),
|
||||
}
|
||||
|
||||
// Only works with yarn 3.1+
|
||||
// https://github.com/yarnpkg/berry/pull/3981
|
||||
if (target.abi === 'gnu') {
|
||||
// @ts-expect-error
|
||||
scopedPackageJson.libc = ['glibc']
|
||||
} else if (target.abi === 'musl') {
|
||||
// @ts-expect-error
|
||||
scopedPackageJson.libc = ['musl']
|
||||
}
|
||||
|
||||
const targetPackageJson = join(targetDir, 'package.json')
|
||||
await writeFileAsync(
|
||||
targetPackageJson,
|
||||
JSON.stringify(scopedPackageJson, null, 2),
|
||||
)
|
||||
const targetReadme = join(targetDir, 'README.md')
|
||||
await writeFileAsync(targetReadme, readme(packageName, target))
|
||||
|
||||
debug.info(`${packageName}-${target.platformArchABI} created`)
|
||||
}
|
||||
}
|
||||
|
||||
function readme(packageName: string, target: Target) {
|
||||
return `# \`${packageName}-${target.platformArchABI}\`
|
||||
|
||||
This is the **${target.triple}** binary for \`${packageName}\`
|
||||
`
|
||||
}
|
206
cli/src/api/new.ts
Normal file
206
cli/src/api/new.ts
Normal file
|
@ -0,0 +1,206 @@
|
|||
import path from 'path'
|
||||
|
||||
import {
|
||||
applyDefaultNewOptions,
|
||||
NewOptions as RawNewOptions,
|
||||
} from '../def/new.js'
|
||||
import {
|
||||
AVAILABLE_TARGETS,
|
||||
CLI_VERSION,
|
||||
debugFactory,
|
||||
DEFAULT_TARGETS,
|
||||
mkdirAsync,
|
||||
writeFileAsync,
|
||||
} from '../utils/index.js'
|
||||
import { napiEngineRequirement } from '../utils/version.js'
|
||||
|
||||
import {
|
||||
createBuildRs,
|
||||
createCargoToml,
|
||||
createGithubActionsCIYml,
|
||||
createLibRs,
|
||||
createPackageJson,
|
||||
gitIgnore,
|
||||
npmIgnore,
|
||||
} from './templates/index.js'
|
||||
|
||||
const debug = debugFactory('new')
|
||||
|
||||
interface Output {
|
||||
target: string
|
||||
content: string
|
||||
}
|
||||
|
||||
type NewOptions = Required<RawNewOptions>
|
||||
|
||||
function processOptions(options: RawNewOptions) {
|
||||
debug('Processing options...')
|
||||
options.path = path.resolve(process.cwd(), options.path)
|
||||
debug(`Resolved target path to: ${options.path}`)
|
||||
|
||||
if (!options.name) {
|
||||
options.name = path.parse(options.path).base
|
||||
debug(`No project name provided, fix it to dir name: ${options.name}`)
|
||||
}
|
||||
|
||||
if (!options.targets?.length) {
|
||||
if (options.enableAllTargets) {
|
||||
options.targets = AVAILABLE_TARGETS.concat()
|
||||
debug('Enable all targets')
|
||||
} else if (options.enableDefaultTargets) {
|
||||
options.targets = DEFAULT_TARGETS.concat()
|
||||
debug('Enable default targets')
|
||||
} else {
|
||||
throw new Error('At least one target must be enabled')
|
||||
}
|
||||
}
|
||||
|
||||
return applyDefaultNewOptions(options) as NewOptions
|
||||
}
|
||||
|
||||
export async function newProject(userOptions: RawNewOptions) {
|
||||
debug('Will create napi-rs project with given options:')
|
||||
debug(userOptions)
|
||||
|
||||
const options = processOptions(userOptions)
|
||||
|
||||
debug('Targets to be enabled:')
|
||||
debug(options.targets)
|
||||
|
||||
const outputs = generateFiles(options)
|
||||
|
||||
try {
|
||||
debug(`Try to create target directory: ${options.path}`)
|
||||
if (!options.dryRun) {
|
||||
await mkdirAsync(options.path, { recursive: true })
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create target directory: ${options.path}`, {
|
||||
cause: e,
|
||||
})
|
||||
}
|
||||
|
||||
await dumpOutputs(outputs, options.dryRun)
|
||||
debug(`Project created at: ${options.path}`)
|
||||
}
|
||||
|
||||
function generateFiles(options: NewOptions): Output[] {
|
||||
return [
|
||||
generateCargoToml,
|
||||
generateLibRs,
|
||||
generateBuildRs,
|
||||
generatePackageJson,
|
||||
generateGithubWorkflow,
|
||||
generateIgnoreFiles,
|
||||
].flatMap((generator) => {
|
||||
const output = generator(options)
|
||||
|
||||
if (!output) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (Array.isArray(output)) {
|
||||
return output.map((o) => ({
|
||||
...o,
|
||||
target: path.join(options.path, o.target),
|
||||
}))
|
||||
} else {
|
||||
return [{ ...output, target: path.join(options.path, output.target) }]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function generateCargoToml(options: NewOptions): Output {
|
||||
return {
|
||||
target: './Cargo.toml',
|
||||
content: createCargoToml({
|
||||
name: options.name,
|
||||
license: options.license,
|
||||
features: [`napi${options.minNodeApiVersion}`],
|
||||
deriveFeatures: options.enableTypeDef ? ['type-def'] : [],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
function generateLibRs(_options: NewOptions): Output {
|
||||
return {
|
||||
target: './src/lib.rs',
|
||||
content: createLibRs(),
|
||||
}
|
||||
}
|
||||
|
||||
function generateBuildRs(_options: NewOptions): Output {
|
||||
return {
|
||||
target: './build.rs',
|
||||
content: createBuildRs(),
|
||||
}
|
||||
}
|
||||
|
||||
function generatePackageJson(options: NewOptions): Output {
|
||||
return {
|
||||
target: './package.json',
|
||||
content: createPackageJson({
|
||||
name: options.name,
|
||||
binaryName: getBinaryName(options.name),
|
||||
targets: options.targets,
|
||||
license: options.license,
|
||||
engineRequirement: napiEngineRequirement(options.minNodeApiVersion),
|
||||
cliVersion: CLI_VERSION,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
function generateGithubWorkflow(options: NewOptions): Output | null {
|
||||
if (!options.enableGithubActions) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
target: './.github/workflows/ci.yml',
|
||||
content: createGithubActionsCIYml(
|
||||
getBinaryName(options.name),
|
||||
options.targets,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function generateIgnoreFiles(_options: NewOptions): Output[] {
|
||||
return [
|
||||
{
|
||||
target: './.gitignore',
|
||||
content: gitIgnore,
|
||||
},
|
||||
{
|
||||
target: './.npmignore',
|
||||
content: npmIgnore,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async function dumpOutputs(outputs: Output[], dryRun?: boolean) {
|
||||
for (const output of outputs) {
|
||||
if (!output) {
|
||||
continue
|
||||
}
|
||||
|
||||
debug(`Writing project file: ${output.target}`)
|
||||
// only output content to logger instead of writing to file system
|
||||
if (dryRun) {
|
||||
debug(output.content)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await mkdirAsync(path.dirname(output.target), { recursive: true })
|
||||
await writeFileAsync(output.target, output.content, 'utf-8')
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to write file: ${output.target}`, { cause: e })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBinaryName(name: string): string {
|
||||
return name.split('/').pop()!
|
||||
}
|
||||
|
||||
export { NewOptions }
|
216
cli/src/api/pre-publish.ts
Normal file
216
cli/src/api/pre-publish.ts
Normal file
|
@ -0,0 +1,216 @@
|
|||
import { execSync } from 'child_process'
|
||||
import { existsSync, statSync } from 'fs'
|
||||
import { join, relative, resolve } from 'path'
|
||||
|
||||
import { Octokit } from '@octokit/rest'
|
||||
|
||||
import {
|
||||
applyDefaultPrePublishOptions,
|
||||
PrePublishOptions,
|
||||
} from '../def/pre-publish.js'
|
||||
import {
|
||||
readFileAsync,
|
||||
readNapiConfig,
|
||||
debugFactory,
|
||||
updatePackageJson,
|
||||
} from '../utils/index.js'
|
||||
|
||||
import { version } from './version.js'
|
||||
|
||||
const debug = debugFactory('pre-publish')
|
||||
|
||||
interface PackageInfo {
|
||||
name: string
|
||||
version: string
|
||||
tag: string
|
||||
}
|
||||
|
||||
export async function prePublish(userOptions: PrePublishOptions) {
|
||||
debug('Receive pre-publish options:')
|
||||
debug(' %O', userOptions)
|
||||
|
||||
const options = applyDefaultPrePublishOptions(userOptions)
|
||||
|
||||
const packageJsonPath = relative(options.cwd, options.packageJsonPath)
|
||||
|
||||
const { packageJson, targets, packageName, binaryName, npmClient } =
|
||||
await readNapiConfig(packageJsonPath)
|
||||
|
||||
async function createGhRelease(packageName: string, version: string) {
|
||||
if (!options.ghRelease) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
const { repo, owner, pkgInfo, octokit } = getRepoInfo(packageName, version)
|
||||
|
||||
if (!repo || !owner) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
try {
|
||||
await octokit.repos.createRelease({
|
||||
owner,
|
||||
repo,
|
||||
tag_name: pkgInfo.tag,
|
||||
name: options.ghReleaseName,
|
||||
prerelease:
|
||||
version.includes('alpha') ||
|
||||
version.includes('beta') ||
|
||||
version.includes('rc'),
|
||||
})
|
||||
} catch (e) {
|
||||
debug(
|
||||
`Params: ${JSON.stringify(
|
||||
{ owner, repo, tag_name: pkgInfo.tag },
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
)
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
return { owner, repo, pkgInfo, octokit }
|
||||
}
|
||||
|
||||
function getRepoInfo(packageName: string, version: string) {
|
||||
const headCommit = execSync('git log -1 --pretty=%B', {
|
||||
encoding: 'utf-8',
|
||||
}).trim()
|
||||
|
||||
const { GITHUB_REPOSITORY } = process.env
|
||||
if (!GITHUB_REPOSITORY) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
debug(`Github repository: ${GITHUB_REPOSITORY}`)
|
||||
const [owner, repo] = GITHUB_REPOSITORY.split('/')
|
||||
const octokit = new Octokit({
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
})
|
||||
let pkgInfo: PackageInfo | undefined
|
||||
if (options.tagStyle === 'lerna') {
|
||||
const packagesToPublish = headCommit
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line, index) => line.length && index)
|
||||
.map((line) => line.substring(2))
|
||||
.map(parseTag)
|
||||
|
||||
pkgInfo = packagesToPublish.find(
|
||||
(pkgInfo) => pkgInfo.name === packageName,
|
||||
)
|
||||
|
||||
if (!pkgInfo) {
|
||||
throw new TypeError(
|
||||
`No release commit found with ${packageName}, original commit info: ${headCommit}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
pkgInfo = {
|
||||
tag: `v${version}`,
|
||||
version,
|
||||
name: packageName,
|
||||
}
|
||||
}
|
||||
return { owner, repo, pkgInfo, octokit }
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
await version(userOptions)
|
||||
await updatePackageJson(packageJsonPath, {
|
||||
optionalDependencies: targets.reduce((deps, target) => {
|
||||
deps[`${packageName}-${target.platformArchABI}`] = packageJson.version
|
||||
|
||||
return deps
|
||||
}, {} as Record<string, string>),
|
||||
})
|
||||
}
|
||||
|
||||
const { owner, repo, pkgInfo, octokit } = options.ghReleaseId
|
||||
? getRepoInfo(packageName, packageJson.version)
|
||||
: await createGhRelease(packageName, packageJson.version)
|
||||
|
||||
for (const target of targets) {
|
||||
const pkgDir = resolve(
|
||||
options.cwd,
|
||||
options.npmDir,
|
||||
`${target.platformArchABI}`,
|
||||
)
|
||||
const filename = `${binaryName}.${target.platformArchABI}.node`
|
||||
const dstPath = join(pkgDir, filename)
|
||||
|
||||
if (!options.dryRun) {
|
||||
if (!existsSync(dstPath)) {
|
||||
debug.warn(`%s doesn't exist`, dstPath)
|
||||
continue
|
||||
}
|
||||
|
||||
execSync(`${npmClient} publish`, {
|
||||
cwd: pkgDir,
|
||||
env: process.env,
|
||||
})
|
||||
|
||||
if (options.ghRelease && repo && owner) {
|
||||
debug.info(`Creating GitHub release ${pkgInfo.tag}`)
|
||||
try {
|
||||
const releaseId = options.ghReleaseId
|
||||
? Number(options.ghReleaseId)
|
||||
: (
|
||||
await octokit!.repos.getReleaseByTag({
|
||||
repo: repo,
|
||||
owner: owner,
|
||||
tag: pkgInfo.tag,
|
||||
})
|
||||
).data.id
|
||||
const dstFileStats = statSync(dstPath)
|
||||
const assetInfo = await octokit!.repos.uploadReleaseAsset({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
name: filename,
|
||||
release_id: releaseId,
|
||||
mediaType: { format: 'raw' },
|
||||
headers: {
|
||||
'content-length': dstFileStats.size,
|
||||
'content-type': 'application/octet-stream',
|
||||
},
|
||||
data: await readFileAsync(dstPath, { encoding: 'utf-8' }),
|
||||
})
|
||||
debug.info(`GitHub release created`)
|
||||
debug.info(`Download URL: %s`, assetInfo.data.browser_download_url)
|
||||
} catch (e) {
|
||||
debug.error(
|
||||
`Param: ${JSON.stringify(
|
||||
{ owner, repo, tag: pkgInfo.tag, filename: dstPath },
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
)
|
||||
debug.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseTag(tag: string) {
|
||||
const segments = tag.split('@')
|
||||
const version = segments.pop()!
|
||||
const name = segments.join('@')
|
||||
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
tag,
|
||||
}
|
||||
}
|
51
cli/src/api/rename.ts
Normal file
51
cli/src/api/rename.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { resolve } from 'path'
|
||||
|
||||
import { isNil, merge, omitBy, pick } from 'lodash-es'
|
||||
|
||||
import { applyDefaultRenameOptions, RenameOptions } from '../def/rename.js'
|
||||
import { readFileAsync, writeFileAsync } from '../utils/index.js'
|
||||
|
||||
import { createNpmDirs } from './create-npm-dirs.js'
|
||||
|
||||
export async function renameProject(userOptions: RenameOptions) {
|
||||
const options = applyDefaultRenameOptions(userOptions)
|
||||
|
||||
const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
|
||||
const cargoTomlPath = resolve(options.cwd, options.manifestPath)
|
||||
|
||||
const packageJsonContent = await readFileAsync(packageJsonPath, 'utf8')
|
||||
const packageJsonData = JSON.parse(packageJsonContent)
|
||||
|
||||
merge(
|
||||
packageJsonData,
|
||||
omitBy(pick(options, ['name', 'description', 'author', 'license']), isNil),
|
||||
{
|
||||
napi: omitBy(
|
||||
{
|
||||
binaryName: options.binaryName,
|
||||
packageName: options.packageName,
|
||||
},
|
||||
isNil,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
await writeFileAsync(
|
||||
packageJsonPath,
|
||||
JSON.stringify(packageJsonData, null, 2),
|
||||
)
|
||||
|
||||
let tomlContent = await readFileAsync(cargoTomlPath, 'utf8')
|
||||
tomlContent = tomlContent.replace(
|
||||
/name\s?=\s?"([\w+])"/,
|
||||
`name = "${options.binaryName}"`,
|
||||
)
|
||||
await writeFileAsync(cargoTomlPath, tomlContent)
|
||||
|
||||
await createNpmDirs({
|
||||
cwd: options.cwd,
|
||||
packageJsonPath: options.packageJsonPath,
|
||||
npmDir: options.npmDir,
|
||||
dryRun: false,
|
||||
})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export const GitIgnore = `# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
export const gitIgnore = `# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
### Node ###
|
|
@ -1,4 +1,4 @@
|
|||
export const NPMIgnoreFiles = `target
|
||||
export const npmIgnore = `target
|
||||
Cargo.lock
|
||||
.cargo
|
||||
.github
|
4
cli/src/api/templates/build.rs.ts
Normal file
4
cli/src/api/templates/build.rs.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const createBuildRs = () => `fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
`
|
35
cli/src/api/templates/cargo.toml.ts
Normal file
35
cli/src/api/templates/cargo.toml.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
export const createCargoToml = ({
|
||||
name,
|
||||
license,
|
||||
features,
|
||||
deriveFeatures,
|
||||
}: {
|
||||
name: string
|
||||
license: string
|
||||
features: string[]
|
||||
deriveFeatures: string[]
|
||||
}) => `[package]
|
||||
name = "${name.replace('@', '').replace('/', '_').toLowerCase()}"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
license = "${license}"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies.napi]
|
||||
version = "2"
|
||||
default-features = false
|
||||
# see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||
features = ${JSON.stringify(features)}
|
||||
|
||||
[dependencies.napi-derive]
|
||||
version = "2"
|
||||
features = ${JSON.stringify(deriveFeatures)}
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
`
|
|
@ -1,9 +1,8 @@
|
|||
export const YAML = (app: string) => `
|
||||
export const YAML = () => `
|
||||
name: CI
|
||||
|
||||
env:
|
||||
DEBUG: 'napi:*'
|
||||
APP_NAME: '${app}'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
|
||||
on:
|
||||
|
@ -30,14 +29,14 @@ jobs:
|
|||
- host: macos-latest
|
||||
target: 'x86_64-apple-darwin'
|
||||
build: |
|
||||
yarn build
|
||||
yarn build --platform
|
||||
strip -x *.node
|
||||
- host: windows-latest
|
||||
build: yarn build
|
||||
build: yarn build --platform
|
||||
target: 'x86_64-pc-windows-msvc'
|
||||
- host: windows-latest
|
||||
build: |
|
||||
yarn build --target i686-pc-windows-msvc
|
||||
yarn build --platform --target i686-pc-windows-msvc
|
||||
yarn test
|
||||
target: 'i686-pc-windows-msvc'
|
||||
- host: ubuntu-latest
|
||||
|
@ -45,26 +44,26 @@ jobs:
|
|||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: >-
|
||||
set -e &&\n
|
||||
yarn build --target x86_64-unknown-linux-gnu &&\n
|
||||
yarn build --platform --target x86_64-unknown-linux-gnu &&\n
|
||||
strip *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-musl'
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: >-
|
||||
set -e &&
|
||||
yarn build &&
|
||||
yarn build --platform &&
|
||||
strip *.node
|
||||
- host: macos-latest
|
||||
target: 'aarch64-apple-darwin'
|
||||
build: |
|
||||
yarn build --target aarch64-apple-darwin
|
||||
yarn build --platform --target aarch64-apple-darwin
|
||||
strip -x *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'aarch64-unknown-linux-gnu'
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
build: >-
|
||||
set -e &&\n
|
||||
yarn build --target aarch64-unknown-linux-gnu &&\n
|
||||
yarn build --platform --target aarch64-unknown-linux-gnu &&\n
|
||||
aarch64-unknown-linux-gnu-strip *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'armv7-unknown-linux-gnueabihf'
|
||||
|
@ -72,17 +71,17 @@ jobs:
|
|||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabihf -y
|
||||
build: |
|
||||
yarn build --target armv7-unknown-linux-gnueabihf
|
||||
yarn build --platform --target armv7-unknown-linux-gnueabihf
|
||||
arm-linux-gnueabihf-strip *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'aarch64-linux-android'
|
||||
build: |
|
||||
yarn build --target aarch64-linux-android
|
||||
yarn build --platform --target aarch64-linux-android
|
||||
\${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'armv7-linux-androideabi'
|
||||
build: |
|
||||
yarn build --target armv7-linux-androideabi
|
||||
yarn build --platform --target armv7-linux-androideabi
|
||||
\${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node
|
||||
- host: ubuntu-latest
|
||||
target: 'aarch64-unknown-linux-musl'
|
||||
|
@ -90,11 +89,11 @@ jobs:
|
|||
build: >-
|
||||
set -e &&\n
|
||||
rustup target add aarch64-unknown-linux-musl &&\n
|
||||
yarn build --target aarch64-unknown-linux-musl &&\n
|
||||
yarn build --platform --target aarch64-unknown-linux-musl &&\n
|
||||
/aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node
|
||||
- host: windows-latest
|
||||
target: 'aarch64-pc-windows-msvc'
|
||||
build: yarn build --target aarch64-pc-windows-msvc
|
||||
build: yarn build --platform --target aarch64-pc-windows-msvc
|
||||
|
||||
name: stable - \${{ matrix.settings.target }} - node@18
|
||||
runs-on: \${{ matrix.settings.host }}
|
||||
|
@ -172,7 +171,7 @@ jobs:
|
|||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bindings-\${{ matrix.settings.target }}
|
||||
path: \${{ env.APP_NAME }}.*.node
|
||||
path: "*.node"
|
||||
if-no-files-found: error
|
||||
|
||||
build-freebsd:
|
||||
|
@ -223,7 +222,7 @@ jobs:
|
|||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bindings-freebsd
|
||||
path: \${{ env.APP_NAME }}.*.node
|
||||
path: "*.node"
|
||||
if-no-files-found: error
|
||||
|
||||
test-macOS-windows-binding:
|
||||
|
@ -508,7 +507,7 @@ jobs:
|
|||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bindings-universal-apple-darwin
|
||||
path: \${{ env.APP_NAME }}.*.node
|
||||
path: "*.node"
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
|
@ -1,8 +1,12 @@
|
|||
import { load, dump } from 'js-yaml'
|
||||
|
||||
import { NodeArchToCpu, UniArchsByPlatform, parseTriple } from '../parse-triple'
|
||||
import {
|
||||
NodeArchToCpu,
|
||||
UniArchsByPlatform,
|
||||
parseTriple,
|
||||
} from '../../utils/index.js'
|
||||
|
||||
import { YAML } from './ci-template'
|
||||
import { YAML } from './ci-template.js'
|
||||
|
||||
const BUILD_FREEBSD = 'build-freebsd'
|
||||
const TEST_MACOS_WINDOWS = 'test-macOS-windows-binding'
|
||||
|
@ -29,7 +33,9 @@ export const createGithubActionsCIYml = (
|
|||
return [t]
|
||||
}),
|
||||
)
|
||||
const fullTemplate = load(YAML(binaryName)) as any
|
||||
|
||||
const fullTemplate = load(YAML()) as any
|
||||
|
||||
const requiredSteps = []
|
||||
const enableWindowsX86 = allTargets.has('x86_64-pc-windows-msvc')
|
||||
const enableMacOSX86 = allTargets.has('x86_64-apple-darwin')
|
||||
|
@ -40,7 +46,6 @@ export const createGithubActionsCIYml = (
|
|||
const enableLinuxArm7 = allTargets.has('armv7-unknown-linux-gnueabihf')
|
||||
const enableFreeBSD = allTargets.has('x86_64-unknown-freebsd')
|
||||
const enableMacOSUni = allTargets.has('universal-apple-darwin')
|
||||
fullTemplate.env.APP_NAME = binaryName
|
||||
fullTemplate.jobs.build.strategy.matrix.settings =
|
||||
fullTemplate.jobs.build.strategy.matrix.settings.filter(
|
||||
({ target }: { target: string }) => allTargets.has(target),
|
8
cli/src/api/templates/index.ts
Normal file
8
cli/src/api/templates/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export * from './.gitignore.js'
|
||||
export * from './.npmignore.js'
|
||||
export * from './build.rs.js'
|
||||
export * from './cargo.toml.js'
|
||||
export * from './ci.yml.js'
|
||||
export * from './lib.rs.js'
|
||||
export * from './package.json.js'
|
||||
export * from './js-binding.js'
|
130
cli/src/api/templates/js-binding.ts
Normal file
130
cli/src/api/templates/js-binding.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */
|
||||
|
||||
function loadNapiModule(binaryName: string, packageName: string) {
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
const { platform, arch } = process
|
||||
|
||||
const candidates: string[] = []
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process')
|
||||
.execSync('which ldd')
|
||||
.toString()
|
||||
.trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
candidates.push('android-arm64')
|
||||
break
|
||||
case 'arm':
|
||||
candidates.push('android-arm-eabi')
|
||||
break
|
||||
}
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
candidates.push('win32-x64-msvc')
|
||||
break
|
||||
case 'ia32':
|
||||
candidates.push('win32-ia32-msvc')
|
||||
break
|
||||
case 'arm64':
|
||||
candidates.push('win32-arm64-msvc')
|
||||
break
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
candidates.push('darwin-universal')
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
candidates.push('darwin-x64')
|
||||
break
|
||||
case 'arm64':
|
||||
candidates.push('darwin-arm64')
|
||||
break
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch === 'x64') {
|
||||
candidates.push('freebsd-x64')
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
candidates.push('linux-x64-musl')
|
||||
} else {
|
||||
candidates.push('linux-x64-gnu')
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
candidates.push('linux-arm64-musl')
|
||||
} else {
|
||||
candidates.push('linux-arm64-gnu')
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
candidates.push('linux-arm-gnueabihf')
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
let nativeBinding: any
|
||||
let loadError: any
|
||||
|
||||
for (const suffix of candidates) {
|
||||
const localPath = join(__dirname, `${binaryName}.${suffix}.node`)
|
||||
const pkgPath = `${packageName}-${suffix}`
|
||||
|
||||
try {
|
||||
if (existsSync(localPath)) {
|
||||
nativeBinding = require(localPath)
|
||||
} else {
|
||||
nativeBinding = require(pkgPath)
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
continue
|
||||
}
|
||||
|
||||
loadError = null
|
||||
break
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
|
||||
return nativeBinding
|
||||
}
|
||||
|
||||
export function createJsBinding(localName: string, pkgName: string): string {
|
||||
return `${loadNapiModule.toString()}
|
||||
|
||||
module.exports = loadNapiModule('${localName}', '${pkgName}')
|
||||
`
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export const LibRs = `#![deny(clippy::all)]
|
||||
export const createLibRs = () => `#![deny(clippy::all)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
44
cli/src/api/templates/package.json.ts
Normal file
44
cli/src/api/templates/package.json.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export const createPackageJson = ({
|
||||
name,
|
||||
binaryName,
|
||||
targets,
|
||||
license,
|
||||
engineRequirement,
|
||||
cliVersion,
|
||||
}: {
|
||||
name: string
|
||||
binaryName: string
|
||||
targets: string[]
|
||||
license: string
|
||||
engineRequirement: string
|
||||
cliVersion: string
|
||||
}) => {
|
||||
return `{
|
||||
"name": "${name}",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"license": "${license}",
|
||||
"engines": {
|
||||
"node": "${engineRequirement}"
|
||||
},
|
||||
"napi": {
|
||||
"name": "${binaryName}",
|
||||
"targets": [
|
||||
${targets.map((t) => `"${t}"`).join(',\n ')}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "yarn build:debug --platform && 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",
|
||||
"universal": "napi universal",
|
||||
"version": "napi version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^${cliVersion}"
|
||||
}
|
||||
}`
|
||||
}
|
76
cli/src/api/universalize.ts
Normal file
76
cli/src/api/universalize.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { spawnSync } from 'child_process'
|
||||
import { join, resolve } from 'path'
|
||||
|
||||
import {
|
||||
applyDefaultUniversalizeOptions,
|
||||
UniversalizeOptions,
|
||||
} from '../def/universalize.js'
|
||||
import { readNapiConfig } from '../utils/config.js'
|
||||
import { debugFactory } from '../utils/log.js'
|
||||
import { fileExists } from '../utils/misc.js'
|
||||
import { UniArchsByPlatform } from '../utils/target.js'
|
||||
|
||||
const debug = debugFactory('universalize')
|
||||
|
||||
const universalizers: Partial<
|
||||
Record<NodeJS.Platform, (inputs: string[], output: string) => void>
|
||||
> = {
|
||||
darwin: (inputs, output) => {
|
||||
spawnSync('lipo', ['-create', '-output', output, ...inputs], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export async function universalizeBinaries(userOptions: UniversalizeOptions) {
|
||||
const options = applyDefaultUniversalizeOptions(userOptions)
|
||||
|
||||
const packageJsonPath = join(options.cwd, options.packageJsonPath)
|
||||
|
||||
const config = await readNapiConfig(packageJsonPath)
|
||||
|
||||
const target = config.targets.find(
|
||||
(t) => t.platform === process.platform && t.arch === 'universal',
|
||||
)
|
||||
|
||||
if (!target) {
|
||||
throw new Error(
|
||||
`'universal' arch for platform '${process.platform}' not found in config!`,
|
||||
)
|
||||
}
|
||||
|
||||
const srcFiles = UniArchsByPlatform[process.platform]?.map(
|
||||
(arch) => `${config.binaryName}.${process.platform}-${arch}.node`,
|
||||
)
|
||||
|
||||
if (!srcFiles || !universalizers[process.platform]) {
|
||||
throw new Error(
|
||||
`'universal' arch for platform '${process.platform}' not supported.`,
|
||||
)
|
||||
}
|
||||
|
||||
debug(`Looking up source binaries to combine: `)
|
||||
debug(' %O', srcFiles)
|
||||
|
||||
const srcFileLookup = await Promise.all(
|
||||
srcFiles.map((f) => fileExists(resolve(options.cwd, options.outputDir, f))),
|
||||
)
|
||||
|
||||
const notFoundFiles = srcFiles.filter((_, i) => !srcFileLookup[i])
|
||||
|
||||
if (notFoundFiles.length) {
|
||||
throw new Error(
|
||||
`Some binary files were not found: ${JSON.stringify(notFoundFiles)}`,
|
||||
)
|
||||
}
|
||||
|
||||
const output = resolve(
|
||||
options.cwd,
|
||||
options.outputDir,
|
||||
`${config.binaryName}.${process.platform}-universal.node`,
|
||||
)
|
||||
|
||||
universalizers[process.platform]?.(srcFiles, output)
|
||||
|
||||
debug(`Produced universal binary: ${output}`)
|
||||
}
|
26
cli/src/api/version.ts
Normal file
26
cli/src/api/version.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { join, resolve } from 'path'
|
||||
|
||||
import { applyDefaultVersionOptions, VersionOptions } from '../def/version.js'
|
||||
import {
|
||||
readNapiConfig,
|
||||
debugFactory,
|
||||
updatePackageJson,
|
||||
} from '../utils/index.js'
|
||||
|
||||
const debug = debugFactory('version')
|
||||
|
||||
export async function version(userOptions: VersionOptions) {
|
||||
const options = applyDefaultVersionOptions(userOptions)
|
||||
const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
|
||||
|
||||
const config = await readNapiConfig(packageJsonPath)
|
||||
|
||||
for (const target of config.targets) {
|
||||
const pkgDir = resolve(options.cwd, options.npmDir, target.platformArchABI)
|
||||
|
||||
debug(`Update version to %i in [%i]`, config.packageJson.version, pkgDir)
|
||||
await updatePackageJson(join(pkgDir, 'package.json'), {
|
||||
version: config.packageJson.version,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
export const ARM_FEATURES_H = `/* Macros to test for CPU features on ARM. Generic ARM version.
|
||||
Copyright (C) 2012-2022 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
The GNU C Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the GNU C Library. If not, see
|
||||
<https://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef _ARM_ARM_FEATURES_H
|
||||
#define _ARM_ARM_FEATURES_H 1
|
||||
|
||||
/* An OS-specific arm-features.h file should define ARM_HAVE_VFP to
|
||||
an appropriate expression for testing at runtime whether the VFP
|
||||
hardware is present. We'll then redefine it to a constant if we
|
||||
know at compile time that we can assume VFP. */
|
||||
|
||||
#ifndef __SOFTFP__
|
||||
/* The compiler is generating VFP instructions, so we're already
|
||||
assuming the hardware exists. */
|
||||
# undef ARM_HAVE_VFP
|
||||
# define ARM_HAVE_VFP 1
|
||||
#endif
|
||||
|
||||
/* An OS-specific arm-features.h file may define ARM_ASSUME_NO_IWMMXT
|
||||
to indicate at compile time that iWMMXt hardware is never present
|
||||
at runtime (or that we never care about its state) and so need not
|
||||
be checked for. */
|
||||
|
||||
/* A more-specific arm-features.h file may define ARM_ALWAYS_BX to indicate
|
||||
that instructions using pc as a destination register must never be used,
|
||||
so a "bx" (or "blx") instruction is always required. */
|
||||
|
||||
/* The log2 of the minimum alignment required for an address that
|
||||
is the target of a computed branch (i.e. a "bx" instruction).
|
||||
A more-specific arm-features.h file may define this to set a more
|
||||
stringent requirement.
|
||||
Using this only makes sense for code in ARM mode (where instructions
|
||||
always have a fixed size of four bytes), or for Thumb-mode code that is
|
||||
specifically aligning all the related branch targets to match (since
|
||||
Thumb instructions might be either two or four bytes). */
|
||||
#ifndef ARM_BX_ALIGN_LOG2
|
||||
# define ARM_BX_ALIGN_LOG2 2
|
||||
#endif
|
||||
|
||||
/* An OS-specific arm-features.h file may define ARM_NO_INDEX_REGISTER to
|
||||
indicate that the two-register addressing modes must never be used. */
|
||||
|
||||
#endif /* arm-features.h */
|
||||
`
|
|
@ -1,89 +0,0 @@
|
|||
import { join, parse } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
import { fdir } from 'fdir'
|
||||
|
||||
import { getNapiConfig } from './consts'
|
||||
import { debugFactory } from './debug'
|
||||
import { UniArchsByPlatform } from './parse-triple'
|
||||
import { readFileAsync, writeFileAsync } from './utils'
|
||||
|
||||
const debug = debugFactory('artifacts')
|
||||
|
||||
export class ArtifactsCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description: 'Copy artifacts from Github Actions into specified dir',
|
||||
})
|
||||
|
||||
static paths = [['artifacts']]
|
||||
|
||||
sourceDir = Option.String('-d,--dir', 'artifacts')
|
||||
|
||||
distDir = Option.String('--dist', 'npm')
|
||||
|
||||
configFileName?: string = Option.String('-c,--config')
|
||||
|
||||
async execute() {
|
||||
const { platforms, binaryName, packageJsonPath } = getNapiConfig(
|
||||
this.configFileName,
|
||||
)
|
||||
|
||||
const packageJsonDir = parse(packageJsonPath).dir
|
||||
|
||||
const sourceApi = new fdir()
|
||||
.withFullPaths()
|
||||
.crawl(join(process.cwd(), this.sourceDir))
|
||||
|
||||
const distDirs = platforms.map((platform) =>
|
||||
join(process.cwd(), this.distDir, platform.platformArchABI),
|
||||
)
|
||||
|
||||
const universalSourceBins = new Set(
|
||||
platforms
|
||||
.filter((platform) => platform.arch === 'universal')
|
||||
.flatMap((p) =>
|
||||
UniArchsByPlatform[p.platform].map((a) => `${p.platform}-${a}`),
|
||||
),
|
||||
)
|
||||
|
||||
await sourceApi.withPromise().then((output) =>
|
||||
Promise.all(
|
||||
(output as string[]).map(async (filePath) => {
|
||||
debug(`Read [${chalk.yellowBright(filePath)}]`)
|
||||
const sourceContent = await readFileAsync(filePath)
|
||||
const parsedName = parse(filePath)
|
||||
const [_binaryName, platformArchABI] = parsedName.name.split('.')
|
||||
if (_binaryName !== binaryName) {
|
||||
debug(
|
||||
`[${chalk.yellowBright(
|
||||
_binaryName,
|
||||
)}] is not matched with [${chalk.greenBright(binaryName)}], skip`,
|
||||
)
|
||||
return
|
||||
}
|
||||
const dir = distDirs.find((dir) => dir.includes(platformArchABI))
|
||||
if (!dir && universalSourceBins.has(platformArchABI)) {
|
||||
debug(
|
||||
`[${chalk.yellowBright(
|
||||
platformArchABI,
|
||||
)}] has no dist dir but it is source bin for universal arch, skip`,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!dir) {
|
||||
throw new TypeError(`No dist dir found for ${filePath}`)
|
||||
}
|
||||
const distFilePath = join(dir, parsedName.base)
|
||||
debug(`Write file content to [${chalk.yellowBright(distFilePath)}]`)
|
||||
await writeFileAsync(distFilePath, sourceContent)
|
||||
const distFilePathLocal = join(packageJsonDir, parsedName.base)
|
||||
debug(
|
||||
`Write file content to [${chalk.yellowBright(distFilePathLocal)}]`,
|
||||
)
|
||||
await writeFileAsync(distFilePathLocal, sourceContent)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
921
cli/src/build.ts
921
cli/src/build.ts
|
@ -1,921 +0,0 @@
|
|||
import { execSync } from 'child_process'
|
||||
import { createHash } from 'crypto'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { join, parse, sep } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
import envPaths from 'env-paths'
|
||||
import { groupBy } from 'lodash-es'
|
||||
|
||||
import { version } from '../package.json'
|
||||
|
||||
import { ARM_FEATURES_H } from './arm-features.h'
|
||||
import { getNapiConfig } from './consts'
|
||||
import { debugFactory } from './debug'
|
||||
import { createJsBinding } from './js-binding-template'
|
||||
import { getHostTargetTriple, parseTriple } from './parse-triple'
|
||||
import {
|
||||
copyFileAsync,
|
||||
mkdirAsync,
|
||||
readFileAsync,
|
||||
unlinkAsync,
|
||||
writeFileAsync,
|
||||
} from './utils'
|
||||
|
||||
const debug = debugFactory('build')
|
||||
|
||||
const ZIG_PLATFORM_TARGET_MAP = {
|
||||
'x86_64-unknown-linux-musl': 'x86_64-linux-musl',
|
||||
'x86_64-unknown-linux-gnu': 'x86_64-linux-gnu',
|
||||
// Doesn't support Windows MSVC for now
|
||||
// 'x86_64-pc-windows-gnu': 'x86_64-windows-gnu',
|
||||
// https://github.com/ziglang/zig/issues/1759
|
||||
// 'x86_64-unknown-freebsd': 'x86_64-freebsd',
|
||||
'x86_64-apple-darwin': 'x86_64-macos',
|
||||
'aarch64-apple-darwin': 'aarch64-macos',
|
||||
'aarch64-unknown-linux-gnu': 'aarch64-linux-gnu',
|
||||
'aarch64-unknown-linux-musl': 'aarch64-linux-musl',
|
||||
'armv7-unknown-linux-gnueabihf': 'arm-linux-gnueabihf',
|
||||
}
|
||||
|
||||
const DEFAULT_GLIBC_TARGET = process.env.GLIBC_ABI_TARGET ?? '2.17'
|
||||
|
||||
const SHEBANG_NODE = process.platform === 'win32' ? '' : '#!/usr/bin/env node\n'
|
||||
const SHEBANG_SH = process.platform === 'win32' ? '' : '#!/usr/bin/env sh\n'
|
||||
|
||||
function processZigLinkerArgs(platform: string, args: string[]) {
|
||||
if (platform.includes('apple')) {
|
||||
const newArgs = args.filter(
|
||||
(arg, index) =>
|
||||
!arg.startsWith('-Wl,-exported_symbols_list') &&
|
||||
arg !== '-Wl,-dylib' &&
|
||||
arg !== '-liconv' &&
|
||||
arg !== '-Wl,-dead_strip' &&
|
||||
!(arg === '-framework' && args[index + 1] === 'CoreFoundation') &&
|
||||
!(arg === 'CoreFoundation' && args[index - 1] === '-framework'),
|
||||
)
|
||||
newArgs.push('-Wl,"-undefined=dynamic_lookup"', '-dead_strip', '-lunwind')
|
||||
return newArgs
|
||||
}
|
||||
if (platform.includes('linux')) {
|
||||
return args
|
||||
.map((arg) => {
|
||||
if (arg === '-lgcc_s') {
|
||||
return '-lunwind'
|
||||
}
|
||||
return arg
|
||||
})
|
||||
.filter((arg) => arg !== '-march=armv7-a')
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
export class BuildCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description: 'Build and copy native module into specified dir',
|
||||
})
|
||||
|
||||
static paths = [['build']]
|
||||
|
||||
appendPlatformToFilename = Option.Boolean(`--platform`, false, {
|
||||
description: `Add platform triple to the .node file. ${chalk.green(
|
||||
'[name].linux-x64-gnu.node',
|
||||
)} for example`,
|
||||
})
|
||||
|
||||
isRelease = Option.Boolean(`--release`, false, {
|
||||
description: `Bypass to ${chalk.green('cargo build --release')}`,
|
||||
})
|
||||
|
||||
configFileName?: string = Option.String('--config,-c', {
|
||||
description: `napi config path, only JSON format accepted. Default to ${chalk.underline(
|
||||
chalk.green('package.json'),
|
||||
)}`,
|
||||
})
|
||||
|
||||
cargoName?: string = Option.String('--cargo-name', {
|
||||
description: `Override the ${chalk.green(
|
||||
'name',
|
||||
)} field in ${chalk.underline(chalk.yellowBright('Cargo.toml'))}`,
|
||||
})
|
||||
|
||||
targetTripleDir = Option.String(
|
||||
'--target',
|
||||
process.env.RUST_TARGET ?? process.env.CARGO_BUILD_TARGET ?? '',
|
||||
{
|
||||
description: `Bypass to ${chalk.green('cargo build --target')}`,
|
||||
},
|
||||
)
|
||||
|
||||
features?: string = Option.String('--features', {
|
||||
description: `Bypass to ${chalk.green('cargo build --features')}`,
|
||||
})
|
||||
|
||||
bin?: string = Option.String('--bin', {
|
||||
description: `Bypass to ${chalk.green('cargo build --bin')}`,
|
||||
})
|
||||
|
||||
dts?: string = Option.String('--dts', 'index.d.ts', {
|
||||
description: `The filename and path of ${chalk.green(
|
||||
'.d.ts',
|
||||
)} file, relative to cwd`,
|
||||
})
|
||||
|
||||
constEnum?: boolean = Option.Boolean('--const-enum', true, {
|
||||
description: `Generate ${chalk.green(
|
||||
'const enum',
|
||||
)} in .d.ts file or not, default is ${chalk.green('true')}`,
|
||||
})
|
||||
|
||||
noDtsHeader = Option.Boolean('--no-dts-header', false, {
|
||||
description: `Don't generate ${chalk.green('.d.ts')} header`,
|
||||
})
|
||||
|
||||
project = Option.String('-p', {
|
||||
description: `Bypass to ${chalk.green('cargo -p')}`,
|
||||
})
|
||||
|
||||
cargoFlags = Option.String('--cargo-flags', '', {
|
||||
description: `All the others flag passed to ${chalk.yellow('cargo build')}`,
|
||||
})
|
||||
|
||||
jsBinding = Option.String('--js', 'index.js', {
|
||||
description: `Path to the JS binding file, pass ${chalk.underline(
|
||||
chalk.yellow('false'),
|
||||
)} to disable it. Only affect if ${chalk.green('--target')} is specified.`,
|
||||
})
|
||||
|
||||
jsPackageName = Option.String('--js-package-name', {
|
||||
description: `Package name in generated js binding file, Only affect if ${chalk.green(
|
||||
'--target',
|
||||
)} specified and ${chalk.green('--js')} is not false.`,
|
||||
required: false,
|
||||
})
|
||||
|
||||
cargoCwd?: string = Option.String('--cargo-cwd', {
|
||||
description: `The cwd of ${chalk.underline(
|
||||
chalk.yellow('Cargo.toml'),
|
||||
)} file`,
|
||||
})
|
||||
|
||||
pipe?: string = Option.String('--pipe', {
|
||||
description: `Pipe [${chalk.green(
|
||||
'.js/.ts',
|
||||
)}] files to this command, eg ${chalk.green('prettier -w')}`,
|
||||
})
|
||||
|
||||
// https://github.com/napi-rs/napi-rs/issues/297
|
||||
disableWindowsX32Optimize?: boolean = Option.Boolean(
|
||||
'--disable-windows-x32-optimize',
|
||||
false,
|
||||
{
|
||||
description: `Disable windows x32 ${chalk.green(
|
||||
'lto',
|
||||
)} and increase ${chalk.green(
|
||||
'codegen-units',
|
||||
)}. Disabled by default. See ${chalk.underline(
|
||||
chalk.blue('https://github.com/napi-rs/napi-rs/issues/297'),
|
||||
)}`,
|
||||
},
|
||||
)
|
||||
|
||||
destDir = Option.String({
|
||||
required: false,
|
||||
})
|
||||
|
||||
useZig = Option.Boolean(`--zig`, false, {
|
||||
description: `Use ${chalk.green('zig')} as linker ${chalk.yellowBright(
|
||||
'(Experimental)',
|
||||
)}`,
|
||||
})
|
||||
|
||||
zigABIVersion = Option.String(`--zig-abi-suffix`, {
|
||||
description: `The suffix of the ${chalk.green(
|
||||
'zig --target',
|
||||
)} ABI version. Eg. ${chalk.cyan(
|
||||
'--target x86_64-unknown-linux-gnu',
|
||||
)} ${chalk.green('--zig-abi-suffix=2.17')}`,
|
||||
})
|
||||
|
||||
zigLinkOnly = Option.Boolean(`--zig-link-only`, false, {
|
||||
description: `Only link the library with ${chalk.green('zig')}`,
|
||||
})
|
||||
|
||||
isStrip = Option.Boolean(`--strip`, false, {
|
||||
description: `${chalk.green('Strip')} the library for minimum file size`,
|
||||
})
|
||||
|
||||
async execute() {
|
||||
const cwd = this.cargoCwd
|
||||
? join(process.cwd(), this.cargoCwd)
|
||||
: process.cwd()
|
||||
const cargoTomlPath = join(cwd, 'Cargo.toml')
|
||||
|
||||
let cargoMetadata: any
|
||||
|
||||
try {
|
||||
debug('Start parse toml')
|
||||
cargoMetadata = JSON.parse(
|
||||
execSync(
|
||||
`cargo metadata --format-version 1 --manifest-path "${cargoTomlPath}"`,
|
||||
{
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 1024 * 1024 * 10,
|
||||
},
|
||||
).toString('utf8'),
|
||||
)
|
||||
} catch (e) {
|
||||
throw new TypeError('Could not parse the Cargo.toml: ' + e)
|
||||
}
|
||||
const packages = cargoMetadata.packages
|
||||
|
||||
let cargoPackageName: string
|
||||
if (this.cargoName) {
|
||||
cargoPackageName = this.cargoName
|
||||
} else {
|
||||
const root = cargoMetadata.resolve.root
|
||||
if (root) {
|
||||
const rootPackage = packages.find((p: { id: string }) => p.id === root)
|
||||
cargoPackageName = rootPackage.name
|
||||
} else {
|
||||
throw new TypeError('No package.name field in Cargo.toml')
|
||||
}
|
||||
}
|
||||
|
||||
const cargoPackage = packages.find(
|
||||
(p: { name: string }) => p.name === cargoPackageName,
|
||||
)
|
||||
if (
|
||||
!this.bin &&
|
||||
cargoPackage?.targets?.length === 1 &&
|
||||
cargoPackage?.targets[0].kind.length === 1 &&
|
||||
cargoPackage?.targets[0].kind[0] === 'bin'
|
||||
) {
|
||||
this.bin = cargoPackageName
|
||||
}
|
||||
const releaseFlag = this.isRelease ? `--release` : ''
|
||||
|
||||
const targetFlag = this.targetTripleDir
|
||||
? `--target ${this.targetTripleDir}`
|
||||
: ''
|
||||
const featuresFlag = this.features ? `--features ${this.features}` : ''
|
||||
const binFlag = this.bin ? `--bin ${this.bin}` : ''
|
||||
const triple = this.targetTripleDir
|
||||
? parseTriple(this.targetTripleDir)
|
||||
: getHostTargetTriple()
|
||||
debug(`Current triple is: ${chalk.green(triple.raw)}`)
|
||||
const pFlag = this.project ? `-p ${this.project}` : ''
|
||||
const externalFlags = [
|
||||
releaseFlag,
|
||||
targetFlag,
|
||||
featuresFlag,
|
||||
binFlag,
|
||||
pFlag,
|
||||
this.cargoFlags,
|
||||
]
|
||||
.filter((flag) => Boolean(flag))
|
||||
.join(' ')
|
||||
const additionalEnv = {}
|
||||
const isCrossForWin =
|
||||
triple.platform === 'win32' && process.platform !== 'win32'
|
||||
const isCrossForLinux =
|
||||
triple.platform === 'linux' &&
|
||||
(process.platform !== 'linux' ||
|
||||
triple.arch !== process.arch ||
|
||||
(function () {
|
||||
const glibcVersionRuntime =
|
||||
// @ts-expect-error
|
||||
process.report?.getReport()?.header?.glibcVersionRuntime
|
||||
const libc = glibcVersionRuntime ? 'gnu' : 'musl'
|
||||
return triple.abi !== libc
|
||||
})())
|
||||
const isCrossForMacOS =
|
||||
triple.platform === 'darwin' && process.platform !== 'darwin'
|
||||
const cargo = process.env.CARGO ?? (isCrossForWin ? 'cargo-xwin' : 'cargo')
|
||||
if (isCrossForWin && triple.arch === 'ia32') {
|
||||
additionalEnv['XWIN_ARCH'] = 'x86'
|
||||
}
|
||||
const cargoCommand = `${cargo} build ${externalFlags}`
|
||||
debug(`Run ${chalk.green(cargoCommand)}`)
|
||||
|
||||
const rustflags = process.env.RUSTFLAGS
|
||||
? process.env.RUSTFLAGS.split(' ')
|
||||
: []
|
||||
if (triple.raw.includes('musl') && !this.bin) {
|
||||
if (!rustflags.includes('target-feature=-crt-static')) {
|
||||
rustflags.push('-C target-feature=-crt-static')
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isStrip && !rustflags.includes('-C link-arg=-s')) {
|
||||
rustflags.push('-C link-arg=-s')
|
||||
}
|
||||
|
||||
let useZig = false
|
||||
if (this.useZig || isCrossForLinux || isCrossForMacOS) {
|
||||
try {
|
||||
execSync('zig version')
|
||||
useZig = true
|
||||
} catch (e) {
|
||||
if (this.useZig) {
|
||||
throw new TypeError(
|
||||
`Could not find ${chalk.green('zig')} on the PATH`,
|
||||
)
|
||||
} else {
|
||||
debug(
|
||||
`Could not find ${chalk.green(
|
||||
'zig',
|
||||
)} on the PATH, fallback to normal linker`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useZig) {
|
||||
const zigABIVersion =
|
||||
this.zigABIVersion ??
|
||||
(isCrossForLinux && triple.abi === 'gnu' ? DEFAULT_GLIBC_TARGET : null)
|
||||
const mappedZigTarget = ZIG_PLATFORM_TARGET_MAP[triple.raw]
|
||||
const zigTarget = `${mappedZigTarget}${
|
||||
zigABIVersion ? `.${zigABIVersion}` : ''
|
||||
}`
|
||||
debug(`Using Zig with target ${chalk.green(zigTarget)}`)
|
||||
if (!mappedZigTarget) {
|
||||
throw new Error(`${triple.raw} can not be cross compiled by zig`)
|
||||
}
|
||||
const paths = envPaths('napi-rs')
|
||||
const shellFileExt = process.platform === 'win32' ? 'cmd' : 'sh'
|
||||
const linkerWrapperShell = join(
|
||||
paths.cache,
|
||||
`zig-linker-${triple.raw}.${shellFileExt}`,
|
||||
)
|
||||
const CCWrapperShell = join(
|
||||
paths.cache,
|
||||
`zig-cc-${triple.raw}.${shellFileExt}`,
|
||||
)
|
||||
const CXXWrapperShell = join(
|
||||
paths.cache,
|
||||
`zig-cxx-${triple.raw}.${shellFileExt}`,
|
||||
)
|
||||
const linkerWrapper = join(paths.cache, `zig-cc-${triple.raw}.js`)
|
||||
mkdirSync(paths.cache, { recursive: true })
|
||||
const forwardArgs = process.platform === 'win32' ? '"%*"' : '$@'
|
||||
if (triple.arch === 'arm') {
|
||||
await patchArmFeaturesHForArmTargets()
|
||||
}
|
||||
await writeFileAsync(
|
||||
linkerWrapperShell,
|
||||
process.platform === 'win32'
|
||||
? `@IF EXIST "%~dp0\\node.exe" (
|
||||
"%~dp0\\node.exe" "${linkerWrapper}" %*
|
||||
) ELSE (
|
||||
@SETLOCAL
|
||||
@SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
node "${linkerWrapper}" %*
|
||||
)`
|
||||
: `${SHEBANG_SH}node ${linkerWrapper} ${forwardArgs}`,
|
||||
{
|
||||
mode: '777',
|
||||
},
|
||||
)
|
||||
await writeFileAsync(
|
||||
CCWrapperShell,
|
||||
`${SHEBANG_SH}node ${linkerWrapper} cc ${forwardArgs}`,
|
||||
{
|
||||
mode: '777',
|
||||
},
|
||||
)
|
||||
await writeFileAsync(
|
||||
CXXWrapperShell,
|
||||
`${SHEBANG_SH}node ${linkerWrapper} c++ ${forwardArgs}`,
|
||||
{
|
||||
mode: '777',
|
||||
},
|
||||
)
|
||||
|
||||
await writeFileAsync(
|
||||
linkerWrapper,
|
||||
`${SHEBANG_NODE}const{writeFileSync} = require('fs')\n${processZigLinkerArgs.toString()}\nconst {status} = require('child_process').spawnSync('zig', [process.argv[2] === "c++" || process.argv[2] === "cc" ? "" : "cc", ...processZigLinkerArgs('${
|
||||
triple.raw
|
||||
}', process.argv.slice(2)), '-target', '${zigTarget}'], { stdio: 'inherit', shell: true })\nwriteFileSync('${linkerWrapper.replaceAll(
|
||||
'\\',
|
||||
'/',
|
||||
)}.args.log', processZigLinkerArgs('${
|
||||
triple.raw
|
||||
}', process.argv.slice(2)).join(' '))\n\nprocess.exit(status || 0)\n`,
|
||||
{
|
||||
mode: '777',
|
||||
},
|
||||
)
|
||||
const envTarget = triple.raw.replaceAll('-', '_').toUpperCase()
|
||||
if (!this.zigLinkOnly) {
|
||||
Object.assign(additionalEnv, {
|
||||
CC: CCWrapperShell,
|
||||
CXX: CXXWrapperShell,
|
||||
TARGET_CC: CCWrapperShell,
|
||||
TARGET_CXX: CXXWrapperShell,
|
||||
})
|
||||
}
|
||||
additionalEnv[`CARGO_TARGET_${envTarget}_LINKER`] = linkerWrapperShell
|
||||
}
|
||||
debug(`Platform: ${JSON.stringify(triple, null, 2)}`)
|
||||
if (triple.platform === 'android') {
|
||||
const { ANDROID_NDK_LATEST_HOME } = process.env
|
||||
if (!ANDROID_NDK_LATEST_HOME) {
|
||||
console.info(
|
||||
`${chalk.yellow(
|
||||
'ANDROID_NDK_LATEST_HOME',
|
||||
)} environment variable is missing`,
|
||||
)
|
||||
}
|
||||
const targetArch = triple.arch === 'arm' ? 'armv7a' : 'aarch64'
|
||||
const targetPlatform =
|
||||
triple.arch === 'arm' ? 'androideabi24' : 'android24'
|
||||
Object.assign(additionalEnv, {
|
||||
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-android24-clang`,
|
||||
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
|
||||
CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
|
||||
CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
|
||||
AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`,
|
||||
ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
|
||||
PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${process.env.PATH}`,
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
binaryName,
|
||||
packageName,
|
||||
tsConstEnum: tsConstEnumFromConfig,
|
||||
} = getNapiConfig(this.configFileName)
|
||||
const tsConstEnum = this.constEnum ?? tsConstEnumFromConfig
|
||||
if (triple.platform === 'wasi') {
|
||||
try {
|
||||
const emnapiDir = require.resolve('emnapi')
|
||||
const linkDir = join(emnapiDir, '..', 'lib', 'wasm32-wasi')
|
||||
additionalEnv['EMNAPI_LINK_DIR'] = linkDir
|
||||
rustflags.push('-Z wasi-exec-model=reactor')
|
||||
} catch (e) {
|
||||
const err = new Error(`Could not find emnapi, please install emnapi`)
|
||||
err.cause = e
|
||||
throw err
|
||||
}
|
||||
}
|
||||
if (rustflags.length > 0) {
|
||||
additionalEnv['RUSTFLAGS'] = rustflags.join(' ')
|
||||
}
|
||||
|
||||
let cargoArtifactName = this.cargoName
|
||||
if (!cargoArtifactName) {
|
||||
if (this.bin) {
|
||||
cargoArtifactName = cargoPackageName
|
||||
} else {
|
||||
cargoArtifactName = cargoPackageName.replace(/-/g, '_')
|
||||
}
|
||||
|
||||
if (
|
||||
!this.bin &&
|
||||
!cargoPackage.targets.some((target: { crate_types: string[] }) =>
|
||||
target.crate_types.includes('cdylib'),
|
||||
)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Missing ${chalk.green('crate-type = ["cdylib"]')} in ${chalk.green(
|
||||
'[lib]',
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.bin) {
|
||||
debug(`Binary name: ${chalk.greenBright(cargoArtifactName)}`)
|
||||
} else {
|
||||
debug(`Dylib name: ${chalk.greenBright(cargoArtifactName)}`)
|
||||
}
|
||||
const cwdSha = createHash('sha256')
|
||||
.update(process.cwd())
|
||||
.update(version)
|
||||
.digest('hex')
|
||||
.substring(0, 8)
|
||||
const intermediateTypeFile = join(
|
||||
tmpdir(),
|
||||
`${cargoArtifactName}-${cwdSha}.napi_type_def.tmp`,
|
||||
)
|
||||
const intermediateWasiRegisterFile = join(
|
||||
tmpdir(),
|
||||
`${cargoArtifactName}-${cwdSha}.napi_wasi_register.tmp`,
|
||||
)
|
||||
debug(`intermediate type def file: ${intermediateTypeFile}`)
|
||||
|
||||
const commandEnv = {
|
||||
...process.env,
|
||||
...additionalEnv,
|
||||
TYPE_DEF_TMP_PATH: intermediateTypeFile,
|
||||
WASI_REGISTER_TMP_PATH: intermediateWasiRegisterFile,
|
||||
CARGO_CFG_NAPI_RS_CLI_VERSION: version,
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(cargoCommand, {
|
||||
env: commandEnv,
|
||||
stdio: 'inherit',
|
||||
cwd,
|
||||
})
|
||||
} catch (e) {
|
||||
if (cargo === 'cargo-xwin') {
|
||||
console.warn(
|
||||
`You are cross compiling ${chalk.underline(
|
||||
triple.raw,
|
||||
)} target on ${chalk.green(process.platform)} host`,
|
||||
)
|
||||
} else if (isCrossForLinux || isCrossForMacOS) {
|
||||
console.warn(
|
||||
`You are cross compiling ${chalk.underline(
|
||||
triple.raw,
|
||||
)} on ${chalk.green(process.platform)} host`,
|
||||
)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
const platform = triple.platform
|
||||
let libExt = ''
|
||||
|
||||
debug(`Platform: ${chalk.greenBright(platform)}`)
|
||||
|
||||
// Platform based massaging for build commands
|
||||
if (!this.bin) {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
libExt = '.dylib'
|
||||
cargoArtifactName = `lib${cargoArtifactName}`
|
||||
break
|
||||
case 'win32':
|
||||
libExt = '.dll'
|
||||
break
|
||||
case 'linux':
|
||||
case 'freebsd':
|
||||
case 'openbsd':
|
||||
case 'android':
|
||||
case 'sunos':
|
||||
cargoArtifactName = `lib${cargoArtifactName}`
|
||||
libExt = '.so'
|
||||
break
|
||||
default:
|
||||
throw new TypeError(
|
||||
'Operating system not currently supported or recognized by the build script',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const targetRootDir =
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
process.env.CARGO_TARGET_DIR ||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
process.env.CARGO_BUILD_TARGET_DIR ||
|
||||
(await findUp(cwd))
|
||||
|
||||
if (!targetRootDir) {
|
||||
throw new TypeError('No target dir found')
|
||||
}
|
||||
|
||||
const targetDir = join(
|
||||
this.targetTripleDir,
|
||||
this.isRelease ? 'release' : 'debug',
|
||||
)
|
||||
|
||||
const platformName = this.appendPlatformToFilename
|
||||
? `.${triple.platformArchABI}`
|
||||
: ''
|
||||
|
||||
debug(`Platform name: ${platformName || chalk.green('[Empty]')}`)
|
||||
const distFileName = this.bin
|
||||
? cargoArtifactName!
|
||||
: `${binaryName}${platformName}.node`
|
||||
|
||||
const distModulePath = join(this.destDir ?? '.', distFileName)
|
||||
|
||||
const parsedDist = parse(distModulePath)
|
||||
|
||||
if (parsedDist.dir && !existsSync(parsedDist.dir)) {
|
||||
await mkdirAsync(parsedDist.dir, { recursive: true }).catch((e) => {
|
||||
console.warn(
|
||||
chalk.bgYellowBright(
|
||||
`Create dir [${parsedDist.dir}] failed, reason: ${e.message}`,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const sourcePath = join(
|
||||
targetRootDir,
|
||||
targetDir,
|
||||
`${cargoArtifactName}${libExt}`,
|
||||
)
|
||||
|
||||
if (existsSync(distModulePath)) {
|
||||
debug(`remove old binary [${chalk.yellowBright(distModulePath)}]`)
|
||||
await unlinkAsync(distModulePath)
|
||||
}
|
||||
|
||||
debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`)
|
||||
await copyFileAsync(sourcePath, distModulePath)
|
||||
|
||||
if (!this.bin) {
|
||||
const dtsFilePath = join(
|
||||
process.cwd(),
|
||||
this.destDir ?? '.',
|
||||
this.dts ?? 'index.d.ts',
|
||||
)
|
||||
|
||||
const jsBindingFilePath =
|
||||
this.jsBinding &&
|
||||
this.jsBinding !== 'false' &&
|
||||
this.appendPlatformToFilename
|
||||
? join(process.cwd(), this.destDir ?? '.', this.jsBinding)
|
||||
: null
|
||||
const idents = await processIntermediateTypeFile(
|
||||
intermediateTypeFile,
|
||||
dtsFilePath,
|
||||
this.noDtsHeader,
|
||||
tsConstEnum,
|
||||
)
|
||||
await writeJsBinding(
|
||||
binaryName,
|
||||
this.jsPackageName ?? packageName,
|
||||
jsBindingFilePath,
|
||||
idents,
|
||||
)
|
||||
if (this.pipe) {
|
||||
if (jsBindingFilePath) {
|
||||
const pipeCommand = `${this.pipe} ${jsBindingFilePath}`
|
||||
console.info(`Run ${chalk.green(pipeCommand)}`)
|
||||
try {
|
||||
execSync(pipeCommand, { stdio: 'inherit', env: commandEnv })
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
chalk.bgYellowBright(
|
||||
'Pipe the js binding file to command failed',
|
||||
),
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
const pipeCommand = `${this.pipe} ${dtsFilePath}`
|
||||
console.info(`Run ${chalk.green(pipeCommand)}`)
|
||||
try {
|
||||
execSync(pipeCommand, { stdio: 'inherit', env: commandEnv })
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
chalk.bgYellowBright('Pipe the dts file to command failed'),
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function findUp(dir = process.cwd()): Promise<string | null> {
|
||||
const dist = join(dir, 'target')
|
||||
if (existsSync(dist)) {
|
||||
return dist
|
||||
}
|
||||
const dirs = dir.split(sep)
|
||||
if (dirs.length < 2) {
|
||||
return null
|
||||
}
|
||||
dirs.pop()
|
||||
return findUp(dirs.join(sep))
|
||||
}
|
||||
|
||||
interface TypeDef {
|
||||
kind: 'fn' | 'struct' | 'impl' | 'enum' | 'interface'
|
||||
name: string
|
||||
original_name?: string
|
||||
def: string
|
||||
js_mod?: string
|
||||
js_doc: string
|
||||
}
|
||||
|
||||
async function processIntermediateTypeFile(
|
||||
source: string,
|
||||
target: string,
|
||||
noDtsHeader: boolean,
|
||||
tsConstEnum: boolean,
|
||||
): Promise<string[]> {
|
||||
const idents: string[] = []
|
||||
if (!existsSync(source)) {
|
||||
debug(`do not find tmp type file. skip type generation`)
|
||||
return idents
|
||||
}
|
||||
|
||||
const tmpFile = await readFileAsync(source, 'utf8')
|
||||
const lines = tmpFile
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.map((line) => {
|
||||
// compatible with old version
|
||||
if (line.startsWith('{')) {
|
||||
return line
|
||||
} else {
|
||||
const [_crateName, ...rest] = line.split(':')
|
||||
return rest.join(':')
|
||||
}
|
||||
})
|
||||
|
||||
if (!lines.length) {
|
||||
return idents
|
||||
}
|
||||
|
||||
const allDefs = lines.map((line) => JSON.parse(line) as TypeDef)
|
||||
|
||||
function convertDefs(defs: TypeDef[], nested = false): string {
|
||||
const classes = new Map<
|
||||
string,
|
||||
{ def: string; js_doc: string; original_name?: string }
|
||||
>()
|
||||
const impls = new Map<string, string>()
|
||||
let dts = ''
|
||||
const nest = nested ? 2 : 0
|
||||
|
||||
defs.forEach((def) => {
|
||||
switch (def.kind) {
|
||||
case 'struct':
|
||||
if (!nested) {
|
||||
idents.push(def.name)
|
||||
}
|
||||
classes.set(def.name, {
|
||||
original_name: def.original_name,
|
||||
def: def.def,
|
||||
js_doc: def.js_doc,
|
||||
})
|
||||
break
|
||||
case 'impl':
|
||||
const existed = impls.get(def.name)
|
||||
impls.set(
|
||||
def.name,
|
||||
`${existed ? existed + '\n' : ''}${def.js_doc}${def.def}`,
|
||||
)
|
||||
break
|
||||
case 'interface':
|
||||
dts +=
|
||||
indentLines(`${def.js_doc}export interface ${def.name} {`, nest) +
|
||||
'\n'
|
||||
dts += indentLines(def.def, nest + 2) + '\n'
|
||||
dts += indentLines(`}`, nest) + '\n'
|
||||
break
|
||||
case 'enum':
|
||||
if (!nested) {
|
||||
idents.push(def.name)
|
||||
}
|
||||
const enumPrefix = tsConstEnum ? ' const' : ''
|
||||
dts +=
|
||||
indentLines(
|
||||
`${def.js_doc}export${enumPrefix} enum ${def.name} {`,
|
||||
nest,
|
||||
) + '\n'
|
||||
dts += indentLines(def.def, nest + 2) + '\n'
|
||||
dts += indentLines(`}`, nest) + '\n'
|
||||
break
|
||||
default:
|
||||
if (!nested) {
|
||||
idents.push(def.name)
|
||||
}
|
||||
dts += indentLines(`${def.js_doc}${def.def}`, nest) + '\n'
|
||||
}
|
||||
})
|
||||
|
||||
for (const [name, { js_doc, def, original_name }] of classes.entries()) {
|
||||
const implDef = impls.get(name)
|
||||
|
||||
if (original_name && name !== original_name) {
|
||||
dts += indentLines(`export type ${original_name} = ${name}\n`, nest)
|
||||
}
|
||||
|
||||
dts += indentLines(`${js_doc}export class ${name} {`, nest)
|
||||
|
||||
if (def) {
|
||||
dts += '\n' + indentLines(def, nest + 2)
|
||||
}
|
||||
|
||||
if (implDef) {
|
||||
dts += '\n' + indentLines(implDef, nest + 2)
|
||||
}
|
||||
|
||||
if (def || implDef) {
|
||||
dts += '\n'
|
||||
} else {
|
||||
dts += ` `
|
||||
}
|
||||
|
||||
dts += indentLines(`}`, nest) + '\n'
|
||||
}
|
||||
|
||||
return dts
|
||||
}
|
||||
|
||||
const topLevelDef = convertDefs(allDefs.filter((def) => !def.js_mod))
|
||||
|
||||
const namespaceDefs = Object.entries(
|
||||
groupBy(
|
||||
allDefs.filter((def) => def.js_mod),
|
||||
'js_mod',
|
||||
),
|
||||
).reduce((acc, [mod, defs]) => {
|
||||
idents.push(mod)
|
||||
return acc + `export namespace ${mod} {\n${convertDefs(defs, true)}}\n`
|
||||
}, '')
|
||||
|
||||
const dtsHeader = noDtsHeader
|
||||
? ''
|
||||
: `/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/* auto-generated by NAPI-RS */\n
|
||||
`
|
||||
|
||||
const externalDef =
|
||||
topLevelDef.indexOf('ExternalObject<') > -1 ||
|
||||
namespaceDefs.indexOf('ExternalObject<') > -1
|
||||
? `export class ExternalObject<T> {
|
||||
readonly '': {
|
||||
readonly '': unique symbol
|
||||
[K: symbol]: T
|
||||
}
|
||||
}\n`
|
||||
: ''
|
||||
|
||||
await writeFileAsync(
|
||||
target,
|
||||
dtsHeader + externalDef + topLevelDef + namespaceDefs,
|
||||
'utf8',
|
||||
)
|
||||
return idents
|
||||
}
|
||||
|
||||
function indentLines(input: string, spaces: number) {
|
||||
return input
|
||||
.split('\n')
|
||||
.map(
|
||||
(line) =>
|
||||
''.padEnd(spaces, ' ') +
|
||||
(line.startsWith(' *') ? line.trimEnd() : line.trim()),
|
||||
)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
async function writeJsBinding(
|
||||
localName: string,
|
||||
packageName: string,
|
||||
distFileName: string | null,
|
||||
idents: string[],
|
||||
) {
|
||||
if (distFileName && idents.length) {
|
||||
const template = createJsBinding(localName, packageName)
|
||||
const declareCodes = `const { ${idents.join(', ')} } = nativeBinding\n`
|
||||
const exportsCode = idents.reduce(
|
||||
(acc, cur) => `${acc}\nmodule.exports.${cur} = ${cur}`,
|
||||
'',
|
||||
)
|
||||
await writeFileAsync(
|
||||
distFileName,
|
||||
template + declareCodes + exportsCode + '\n',
|
||||
'utf8',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function patchArmFeaturesHForArmTargets() {
|
||||
let zigExePath: string
|
||||
let zigLibDir: string | undefined
|
||||
try {
|
||||
const zigEnv = JSON.parse(execSync(`zig env`, { encoding: 'utf8' }).trim())
|
||||
zigExePath = zigEnv['zig_exe']
|
||||
zigLibDir = zigEnv['lib_dir']
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'Cannot get zig env correctly, please ensure the zig is installed correctly on your system',
|
||||
)
|
||||
}
|
||||
try {
|
||||
const p = zigLibDir
|
||||
? join(zigLibDir, 'libc/glibc/sysdeps/arm/arm-features.h')
|
||||
: join(zigExePath, '../lib/libc/glibc/sysdeps/arm/arm-features.h')
|
||||
if (!existsSync(p)) {
|
||||
await writeFileAsync(p, ARM_FEATURES_H, {
|
||||
mode: 0o644,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
Error(
|
||||
`Cannot patch arm-features.h, error: ${
|
||||
(e as Error).message || e
|
||||
}. See: https://github.com/ziglang/zig/issues/3287`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
29
cli/src/cli.ts
Normal file
29
cli/src/cli.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Cli } from 'clipanion'
|
||||
|
||||
import { ArtifactsCommand } from './commands/artifacts.js'
|
||||
import { BuildCommand } from './commands/build.js'
|
||||
import { CreateNpmDirsCommand } from './commands/create-npm-dirs.js'
|
||||
import { HelpCommand } from './commands/help.js'
|
||||
import { NewCommand } from './commands/new.js'
|
||||
import { PrePublishCommand } from './commands/pre-publish.js'
|
||||
import { RenameCommand } from './commands/rename.js'
|
||||
import { UniversalizeCommand } from './commands/universalize.js'
|
||||
import { VersionCommand } from './commands/version.js'
|
||||
import { CLI_VERSION } from './utils/misc.js'
|
||||
|
||||
const cli = new Cli({
|
||||
binaryName: 'napi',
|
||||
binaryVersion: CLI_VERSION,
|
||||
})
|
||||
|
||||
cli.register(NewCommand)
|
||||
cli.register(BuildCommand)
|
||||
cli.register(CreateNpmDirsCommand)
|
||||
cli.register(ArtifactsCommand)
|
||||
cli.register(UniversalizeCommand)
|
||||
cli.register(RenameCommand)
|
||||
cli.register(PrePublishCommand)
|
||||
cli.register(VersionCommand)
|
||||
cli.register(HelpCommand)
|
||||
|
||||
void cli.runExit(process.argv.slice(2))
|
23
cli/src/commands/artifacts.ts
Normal file
23
cli/src/commands/artifacts.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Command } from 'clipanion'
|
||||
|
||||
import { collectArtifacts } from '../api/artifacts.js'
|
||||
import { BaseArtifactsCommand } from '../def/artifacts.js'
|
||||
|
||||
export class ArtifactsCommand extends BaseArtifactsCommand {
|
||||
static usage = Command.Usage({
|
||||
description: 'Copy artifacts from Github Actions into specified dir',
|
||||
examples: [
|
||||
[
|
||||
'$0 artifacts --dir . --dist ./npm',
|
||||
`Copy [binaryName].[platform].node under current dir(.) into packages under npm dir.
|
||||
e.g: index.linux-x64-gnu.node --> ./npm/linux-x64-gnu/index.node`,
|
||||
],
|
||||
],
|
||||
})
|
||||
|
||||
static paths = [['artifacts']]
|
||||
|
||||
async execute() {
|
||||
await collectArtifacts(this.getOptions())
|
||||
}
|
||||
}
|
42
cli/src/commands/build.ts
Normal file
42
cli/src/commands/build.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { execSync } from 'child_process'
|
||||
|
||||
import { Option } from 'clipanion'
|
||||
|
||||
import { buildProject } from '../api/build.js'
|
||||
import { BaseBuildCommand } from '../def/build.js'
|
||||
import { debugFactory } from '../utils/index.js'
|
||||
|
||||
const debug = debugFactory('build')
|
||||
|
||||
export class BuildCommand extends BaseBuildCommand {
|
||||
pipe = Option.String('--pipe', {
|
||||
description:
|
||||
'Pipe all outputs file to given command. e.g. `napi build --pipe "npx prettier --write"`',
|
||||
})
|
||||
|
||||
cargoOptions = Option.Rest()
|
||||
|
||||
async execute() {
|
||||
const { task } = await buildProject({
|
||||
...this.getOptions(),
|
||||
cargoOptions: this.cargoOptions,
|
||||
})
|
||||
|
||||
const outputs = await task
|
||||
|
||||
if (this.pipe) {
|
||||
for (const output of outputs) {
|
||||
debug('Piping output file to command: %s', this.pipe)
|
||||
try {
|
||||
execSync(`${this.pipe} ${output.path}`, {
|
||||
stdio: 'inherit',
|
||||
cwd: this.cwd,
|
||||
})
|
||||
} catch (e) {
|
||||
debug.error(`Failed to pipe output file ${output.path} to command`)
|
||||
debug.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
cli/src/commands/create-npm-dirs.ts
Normal file
8
cli/src/commands/create-npm-dirs.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { createNpmDirs } from '../api/create-npm-dirs.js'
|
||||
import { BaseCreateNpmDirsCommand } from '../def/create-npm-dirs.js'
|
||||
|
||||
export class CreateNpmDirsCommand extends BaseCreateNpmDirsCommand {
|
||||
async execute() {
|
||||
await createNpmDirs(this.getOptions())
|
||||
}
|
||||
}
|
138
cli/src/commands/new.ts
Normal file
138
cli/src/commands/new.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import path from 'path'
|
||||
|
||||
import { Option } from 'clipanion'
|
||||
import inquirer from 'inquirer'
|
||||
|
||||
import { newProject } from '../api/new.js'
|
||||
import { BaseNewCommand } from '../def/new.js'
|
||||
import {
|
||||
AVAILABLE_TARGETS,
|
||||
debugFactory,
|
||||
DEFAULT_TARGETS,
|
||||
TargetTriple,
|
||||
} from '../utils/index.js'
|
||||
import { napiEngineRequirement } from '../utils/version.js'
|
||||
|
||||
const debug = debugFactory('new')
|
||||
|
||||
export class NewCommand extends BaseNewCommand {
|
||||
interactive = Option.Boolean('--interactive,-i', false, {
|
||||
description:
|
||||
'Ask project basic information interactively without just using the default.',
|
||||
})
|
||||
|
||||
async execute() {
|
||||
try {
|
||||
const options = await this.fetchOptions()
|
||||
await newProject(options)
|
||||
return 0
|
||||
} catch (e) {
|
||||
debug('Failed to create new project')
|
||||
debug.error(e)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchOptions() {
|
||||
const cmdOptions = super.getOptions()
|
||||
|
||||
if (this.interactive) {
|
||||
return {
|
||||
...cmdOptions,
|
||||
name: await this.fetchName(path.parse(cmdOptions.path).base),
|
||||
minNodeApiVersion: await this.fetchNapiVersion(),
|
||||
targets: await this.fetchTargets(),
|
||||
license: await this.fetchLicense(),
|
||||
enableTypeDef: await this.fetchTypeDef(),
|
||||
enableGithubActions: await this.fetchGithubActions(),
|
||||
}
|
||||
}
|
||||
|
||||
return cmdOptions
|
||||
}
|
||||
|
||||
private async fetchName(defaultName: string): Promise<string> {
|
||||
return (
|
||||
this.$$name ??
|
||||
(await inquirer
|
||||
.prompt({
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Package name (the name field in your package.json file)',
|
||||
default: defaultName,
|
||||
})
|
||||
.then(({ name }) => name))
|
||||
)
|
||||
}
|
||||
|
||||
private async fetchLicense(): Promise<string> {
|
||||
return inquirer
|
||||
.prompt({
|
||||
type: 'input',
|
||||
name: 'license',
|
||||
message: 'License for open-sourced project',
|
||||
default: this.license,
|
||||
})
|
||||
.then(({ license }) => license)
|
||||
}
|
||||
|
||||
private async fetchNapiVersion(): Promise<number> {
|
||||
return inquirer
|
||||
.prompt({
|
||||
type: 'list',
|
||||
name: 'minNodeApiVersion',
|
||||
message: 'Minimum node-api version (with node version requirement)',
|
||||
loop: false,
|
||||
choices: new Array(8).fill(0).map((_, i) => ({
|
||||
name: `napi${i + 1} (${napiEngineRequirement(i + 1)})`,
|
||||
value: i + 1,
|
||||
})),
|
||||
// choice index
|
||||
default: this.minNodeApiVersion - 1,
|
||||
})
|
||||
.then(({ minNodeApiVersion }) => minNodeApiVersion)
|
||||
}
|
||||
|
||||
private async fetchTargets(): Promise<TargetTriple[]> {
|
||||
if (this.enableDefaultTargets) {
|
||||
return DEFAULT_TARGETS.concat()
|
||||
}
|
||||
|
||||
if (this.enableAllTargets) {
|
||||
return AVAILABLE_TARGETS.concat()
|
||||
}
|
||||
|
||||
const { targets } = await inquirer.prompt({
|
||||
name: 'targets',
|
||||
type: 'checkbox',
|
||||
loop: false,
|
||||
message: 'Choose target(s) your crate will be compiled to',
|
||||
default: DEFAULT_TARGETS,
|
||||
choices: AVAILABLE_TARGETS,
|
||||
})
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
private async fetchTypeDef(): Promise<boolean> {
|
||||
const { enableTypeDef } = await inquirer.prompt({
|
||||
name: 'enableTypeDef',
|
||||
type: 'confirm',
|
||||
message: 'Enable type definition auto-generation',
|
||||
default: this.enableTypeDef,
|
||||
})
|
||||
|
||||
return enableTypeDef
|
||||
}
|
||||
|
||||
private async fetchGithubActions(): Promise<boolean> {
|
||||
const { enableGithubActions } = await inquirer.prompt({
|
||||
name: 'enableGithubActions',
|
||||
type: 'confirm',
|
||||
message: 'Enable Github Actions CI',
|
||||
default: this.enableGithubActions,
|
||||
})
|
||||
|
||||
return enableGithubActions
|
||||
}
|
||||
}
|
9
cli/src/commands/pre-publish.ts
Normal file
9
cli/src/commands/pre-publish.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { prePublish } from '../api/pre-publish.js'
|
||||
import { BasePrePublishCommand } from '../def/pre-publish.js'
|
||||
|
||||
export class PrePublishCommand extends BasePrePublishCommand {
|
||||
async execute() {
|
||||
// @ts-expect-error const 'npm' | 'lerna' to string
|
||||
await prePublish(this.getOptions())
|
||||
}
|
||||
}
|
8
cli/src/commands/rename.ts
Normal file
8
cli/src/commands/rename.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { renameProject } from '../api/rename.js'
|
||||
import { BaseRenameCommand } from '../def/rename.js'
|
||||
|
||||
export class RenameCommand extends BaseRenameCommand {
|
||||
async execute() {
|
||||
await renameProject(this.getOptions())
|
||||
}
|
||||
}
|
8
cli/src/commands/universalize.ts
Normal file
8
cli/src/commands/universalize.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { universalizeBinaries } from '../api/universalize.js'
|
||||
import { BaseUniversalizeCommand } from '../def/universalize.js'
|
||||
|
||||
export class UniversalizeCommand extends BaseUniversalizeCommand {
|
||||
async execute() {
|
||||
await universalizeBinaries(this.getOptions())
|
||||
}
|
||||
}
|
8
cli/src/commands/version.ts
Normal file
8
cli/src/commands/version.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { version } from '../api/version.js'
|
||||
import { BaseVersionCommand } from '../def/version.js'
|
||||
|
||||
export class VersionCommand extends BaseVersionCommand {
|
||||
async execute() {
|
||||
await version(this.getOptions())
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import { join } from 'path'
|
||||
|
||||
import { DefaultPlatforms, PlatformDetail, parseTriple } from './parse-triple'
|
||||
|
||||
export function getNapiConfig(
|
||||
packageJson = 'package.json',
|
||||
cwd = process.cwd(),
|
||||
) {
|
||||
const packageJsonPath = join(cwd, packageJson)
|
||||
|
||||
const pkgJson = require(packageJsonPath)
|
||||
const { version: packageVersion, napi, name } = pkgJson
|
||||
const additionPlatforms: PlatformDetail[] = (
|
||||
napi?.triples?.additional ?? []
|
||||
).map(parseTriple)
|
||||
const defaultPlatforms =
|
||||
napi?.triples?.defaults === false ? [] : [...DefaultPlatforms]
|
||||
const tsConstEnum: boolean = napi?.ts?.constEnum ?? true
|
||||
const platforms = [...defaultPlatforms, ...additionPlatforms]
|
||||
const releaseVersion = process.env.RELEASE_VERSION
|
||||
const releaseVersionWithoutPrefix = releaseVersion?.startsWith('v')
|
||||
? releaseVersion.substring(1)
|
||||
: releaseVersion
|
||||
const version = releaseVersionWithoutPrefix ?? packageVersion
|
||||
const packageName = napi?.package?.name ?? name
|
||||
const npmClient: string = napi?.npmClient ?? 'npm'
|
||||
|
||||
const binaryName: string = napi?.name ?? 'index'
|
||||
|
||||
return {
|
||||
platforms,
|
||||
version,
|
||||
packageName,
|
||||
binaryName,
|
||||
packageJsonPath,
|
||||
content: pkgJson,
|
||||
npmClient,
|
||||
tsConstEnum,
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
import { mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
|
||||
import { getNapiConfig } from './consts'
|
||||
import { debugFactory } from './debug'
|
||||
import { PlatformDetail } from './parse-triple'
|
||||
import { writeFileAsync, pick } from './utils'
|
||||
|
||||
const debug = debugFactory('create-npm-dir')
|
||||
|
||||
export class CreateNpmDirCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description: 'Create npm packages dir for platforms',
|
||||
})
|
||||
|
||||
static paths = [['create-npm-dir']]
|
||||
|
||||
static create = async (
|
||||
config: string,
|
||||
targetDirPath: string,
|
||||
cwd: string,
|
||||
) => {
|
||||
const pkgJsonDir = config
|
||||
debug(`Read content from [${chalk.yellowBright(pkgJsonDir)}]`)
|
||||
const { platforms, packageName, version, binaryName, content } =
|
||||
getNapiConfig(pkgJsonDir, cwd)
|
||||
|
||||
for (const platformDetail of platforms) {
|
||||
const targetDir = join(
|
||||
targetDirPath,
|
||||
'npm',
|
||||
`${platformDetail.platformArchABI}`,
|
||||
)
|
||||
mkdirSync(targetDir, {
|
||||
recursive: true,
|
||||
})
|
||||
const binaryFileName = `${binaryName}.${platformDetail.platformArchABI}.node`
|
||||
const targetPackageJson = join(targetDir, 'package.json')
|
||||
debug(`Write file [${chalk.yellowBright(targetPackageJson)}]`)
|
||||
const packageJson: {
|
||||
name: string
|
||||
libc?: string[]
|
||||
} = {
|
||||
name: `${packageName}-${platformDetail.platformArchABI}`,
|
||||
version,
|
||||
os: [platformDetail.platform],
|
||||
cpu:
|
||||
platformDetail.arch !== 'universal'
|
||||
? [platformDetail.arch]
|
||||
: undefined,
|
||||
main: binaryFileName,
|
||||
files: [binaryFileName],
|
||||
...pick(
|
||||
content,
|
||||
'description',
|
||||
'keywords',
|
||||
'author',
|
||||
'authors',
|
||||
'homepage',
|
||||
'license',
|
||||
'engines',
|
||||
'publishConfig',
|
||||
'repository',
|
||||
'bugs',
|
||||
),
|
||||
}
|
||||
// Only works with yarn 3.1+
|
||||
// https://github.com/yarnpkg/berry/pull/3981
|
||||
if (platformDetail.abi === 'gnu') {
|
||||
packageJson.libc = ['glibc']
|
||||
} else if (platformDetail.abi === 'musl') {
|
||||
packageJson.libc = ['musl']
|
||||
}
|
||||
await writeFileAsync(
|
||||
targetPackageJson,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
)
|
||||
const targetReadme = join(targetDir, 'README.md')
|
||||
debug(`Write target README.md [${chalk.yellowBright(targetReadme)}]`)
|
||||
await writeFileAsync(targetReadme, readme(packageName, platformDetail))
|
||||
}
|
||||
}
|
||||
|
||||
targetDir: string = Option.String('-t,--target')!
|
||||
|
||||
config = Option.String('-c,--config', 'package.json')
|
||||
|
||||
async execute() {
|
||||
await CreateNpmDirCommand.create(
|
||||
this.config,
|
||||
join(process.cwd(), this.targetDir),
|
||||
process.cwd(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function readme(packageName: string, platformDetail: PlatformDetail) {
|
||||
return `# \`${packageName}-${platformDetail.platformArchABI}\`
|
||||
|
||||
This is the **${platformDetail.raw}** binary for \`${packageName}\`
|
||||
`
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import debug from 'debug'
|
||||
|
||||
export const debugFactory = (namespace: string) => debug(`napi:${namespace}`)
|
79
cli/src/def/artifacts.ts
Normal file
79
cli/src/def/artifacts.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseArtifactsCommand extends Command {
|
||||
static paths = [['artifacts']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description:
|
||||
'Copy artifacts from Github Actions into npm packages and ready to publish',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
outputDir = Option.String('--output-dir,-o', './', {
|
||||
description:
|
||||
'Path to the folder where all built `.node` files put, same as `--output-dir` of build command',
|
||||
})
|
||||
|
||||
npmDir = Option.String('--npm-dir', 'npm', {
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
outputDir: this.outputDir,
|
||||
npmDir: this.npmDir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy artifacts from Github Actions into npm packages and ready to publish
|
||||
*/
|
||||
export interface ArtifactsOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where all built `.node` files put, same as `--output-dir` of build command
|
||||
*
|
||||
* @default './'
|
||||
*/
|
||||
outputDir?: string
|
||||
/**
|
||||
* Path to the folder where the npm packages put
|
||||
*
|
||||
* @default 'npm'
|
||||
*/
|
||||
npmDir?: string
|
||||
}
|
||||
|
||||
export function applyDefaultArtifactsOptions(options: ArtifactsOptions) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
outputDir: './',
|
||||
npmDir: 'npm',
|
||||
...options,
|
||||
}
|
||||
}
|
242
cli/src/def/build.ts
Normal file
242
cli/src/def/build.ts
Normal file
|
@ -0,0 +1,242 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseBuildCommand extends Command {
|
||||
static paths = [['build']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Build the napi-rs project',
|
||||
})
|
||||
|
||||
target?: string = Option.String('--target,-t', {
|
||||
description:
|
||||
'Build for the target triple, bypassed to `cargo build --target`',
|
||||
})
|
||||
|
||||
cwd?: string = Option.String('--cwd', {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
manifestPath?: string = Option.String('--manifest-path', {
|
||||
description: 'Path to `Cargo.toml`',
|
||||
})
|
||||
|
||||
packageJsonPath?: string = Option.String('--package-json-path', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
targetDir?: string = Option.String('--target-dir', {
|
||||
description:
|
||||
'Directory for all crate generated artifacts, see `cargo build --target-dir`',
|
||||
})
|
||||
|
||||
outputDir?: string = Option.String('--output-dir,-o', {
|
||||
description:
|
||||
'Path to where all the built files would be put. Default to the crate folder',
|
||||
})
|
||||
|
||||
platform?: boolean = Option.Boolean('--platform', {
|
||||
description:
|
||||
'Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node`',
|
||||
})
|
||||
|
||||
jsPackageName?: string = Option.String('--js-package-name', {
|
||||
description:
|
||||
'Package name in generated js binding file. Only works with `--platform` flag',
|
||||
})
|
||||
|
||||
jsBinding?: string = Option.String('--js', {
|
||||
description:
|
||||
'Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output_dir`.',
|
||||
})
|
||||
|
||||
noJsBinding?: boolean = Option.Boolean('--no-js', {
|
||||
description:
|
||||
'Whether to disable the generation JS binding file. Only works with `--platform` flag.',
|
||||
})
|
||||
|
||||
dts?: string = Option.String('--dts', {
|
||||
description:
|
||||
'Path and filename of generated type def file. Relative to `--output_dir`',
|
||||
})
|
||||
|
||||
dtsHeader?: string = Option.String('--dts-header', {
|
||||
description:
|
||||
'Custom file header for generated type def file. Only works when `typedef` feature enabled.',
|
||||
})
|
||||
|
||||
noDtsHeader?: boolean = Option.Boolean('--no-dts-header', {
|
||||
description:
|
||||
'Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled.',
|
||||
})
|
||||
|
||||
strip?: boolean = Option.Boolean('--strip,-s', {
|
||||
description: 'Whether strip the library to achieve the minimum file size',
|
||||
})
|
||||
|
||||
release?: boolean = Option.Boolean('--release,-r', {
|
||||
description: 'Build in release mode',
|
||||
})
|
||||
|
||||
verbose?: boolean = Option.Boolean('--verbose,-v', {
|
||||
description: 'Verbosely log build command trace',
|
||||
})
|
||||
|
||||
bin?: string = Option.String('--bin', {
|
||||
description: 'Build only the specified binary',
|
||||
})
|
||||
|
||||
package?: string = Option.String('--package,-p', {
|
||||
description: 'Build the specified library or the one at cwd',
|
||||
})
|
||||
|
||||
crossCompile?: boolean = Option.Boolean('--cross-compile,-x', {
|
||||
description:
|
||||
'[experimental] cross-compile for the specified target with `cargo-xwin` on windows and `cargo-zigbuild` on other platform',
|
||||
})
|
||||
|
||||
watch?: boolean = Option.Boolean('--watch,-w', {
|
||||
description:
|
||||
'watch the crate changes and build continiously with `cargo-watch` crates',
|
||||
})
|
||||
|
||||
features?: string[] = Option.Array('--features,-F', {
|
||||
description: 'Space-separated list of features to activate',
|
||||
})
|
||||
|
||||
allFeatures?: boolean = Option.Boolean('--all-features', {
|
||||
description: 'Activate all available features',
|
||||
})
|
||||
|
||||
noDefaultFeatures?: boolean = Option.Boolean('--no-default-features', {
|
||||
description: 'Do not activate the `default` feature',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
target: this.target,
|
||||
cwd: this.cwd,
|
||||
manifestPath: this.manifestPath,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
targetDir: this.targetDir,
|
||||
outputDir: this.outputDir,
|
||||
platform: this.platform,
|
||||
jsPackageName: this.jsPackageName,
|
||||
jsBinding: this.jsBinding,
|
||||
noJsBinding: this.noJsBinding,
|
||||
dts: this.dts,
|
||||
dtsHeader: this.dtsHeader,
|
||||
noDtsHeader: this.noDtsHeader,
|
||||
strip: this.strip,
|
||||
release: this.release,
|
||||
verbose: this.verbose,
|
||||
bin: this.bin,
|
||||
package: this.package,
|
||||
crossCompile: this.crossCompile,
|
||||
watch: this.watch,
|
||||
features: this.features,
|
||||
allFeatures: this.allFeatures,
|
||||
noDefaultFeatures: this.noDefaultFeatures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the napi-rs project
|
||||
*/
|
||||
export interface BuildOptions {
|
||||
/**
|
||||
* Build for the target triple, bypassed to `cargo build --target`
|
||||
*/
|
||||
target?: string
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `Cargo.toml`
|
||||
*/
|
||||
manifestPath?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Directory for all crate generated artifacts, see `cargo build --target-dir`
|
||||
*/
|
||||
targetDir?: string
|
||||
/**
|
||||
* Path to where all the built files would be put. Default to the crate folder
|
||||
*/
|
||||
outputDir?: string
|
||||
/**
|
||||
* Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node`
|
||||
*/
|
||||
platform?: boolean
|
||||
/**
|
||||
* Package name in generated js binding file. Only works with `--platform` flag
|
||||
*/
|
||||
jsPackageName?: string
|
||||
/**
|
||||
* Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output_dir`.
|
||||
*/
|
||||
jsBinding?: string
|
||||
/**
|
||||
* Whether to disable the generation JS binding file. Only works with `--platform` flag.
|
||||
*/
|
||||
noJsBinding?: boolean
|
||||
/**
|
||||
* Path and filename of generated type def file. Relative to `--output_dir`
|
||||
*/
|
||||
dts?: string
|
||||
/**
|
||||
* Custom file header for generated type def file. Only works when `typedef` feature enabled.
|
||||
*/
|
||||
dtsHeader?: string
|
||||
/**
|
||||
* Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled.
|
||||
*/
|
||||
noDtsHeader?: boolean
|
||||
/**
|
||||
* Whether strip the library to achieve the minimum file size
|
||||
*/
|
||||
strip?: boolean
|
||||
/**
|
||||
* Build in release mode
|
||||
*/
|
||||
release?: boolean
|
||||
/**
|
||||
* Verbosely log build command trace
|
||||
*/
|
||||
verbose?: boolean
|
||||
/**
|
||||
* Build only the specified binary
|
||||
*/
|
||||
bin?: string
|
||||
/**
|
||||
* Build the specified library or the one at cwd
|
||||
*/
|
||||
package?: string
|
||||
/**
|
||||
* [experimental] cross-compile for the specified target with `cargo-xwin` on windows and `cargo-zigbuild` on other platform
|
||||
*/
|
||||
crossCompile?: boolean
|
||||
/**
|
||||
* watch the crate changes and build continiously with `cargo-watch` crates
|
||||
*/
|
||||
watch?: boolean
|
||||
/**
|
||||
* Space-separated list of features to activate
|
||||
*/
|
||||
features?: string[]
|
||||
/**
|
||||
* Activate all available features
|
||||
*/
|
||||
allFeatures?: boolean
|
||||
/**
|
||||
* Do not activate the `default` feature
|
||||
*/
|
||||
noDefaultFeatures?: boolean
|
||||
}
|
79
cli/src/def/create-npm-dirs.ts
Normal file
79
cli/src/def/create-npm-dirs.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseCreateNpmDirsCommand extends Command {
|
||||
static paths = [['create-npm-dirs']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Create npm package dirs for different platforms',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
npmDir = Option.String('--npm-dir', 'npm', {
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
})
|
||||
|
||||
dryRun = Option.Boolean('--dry-run', false, {
|
||||
description: 'Dry run without touching file system',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
npmDir: this.npmDir,
|
||||
dryRun: this.dryRun,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create npm package dirs for different platforms
|
||||
*/
|
||||
export interface CreateNpmDirsOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where the npm packages put
|
||||
*
|
||||
* @default 'npm'
|
||||
*/
|
||||
npmDir?: string
|
||||
/**
|
||||
* Dry run without touching file system
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
export function applyDefaultCreateNpmDirsOptions(
|
||||
options: CreateNpmDirsOptions,
|
||||
) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
npmDir: 'npm',
|
||||
dryRun: false,
|
||||
...options,
|
||||
}
|
||||
}
|
144
cli/src/def/new.ts
Normal file
144
cli/src/def/new.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as typanion from 'typanion'
|
||||
|
||||
export abstract class BaseNewCommand extends Command {
|
||||
static paths = [['new']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Create a new project with pre-configured boilerplate',
|
||||
})
|
||||
|
||||
$$path = Option.String({ required: true })
|
||||
|
||||
$$name?: string = Option.String('--name,-n', {
|
||||
description:
|
||||
'The name of the project, default to the name of the directory if not provided',
|
||||
})
|
||||
|
||||
minNodeApiVersion = Option.String('--min-node-api,-v', '4', {
|
||||
validator: typanion.isNumber(),
|
||||
description: 'The minimum Node-API version to support',
|
||||
})
|
||||
|
||||
license = Option.String('--license,-l', 'MIT', {
|
||||
description: 'License for open-sourced project',
|
||||
})
|
||||
|
||||
targets = Option.Array('--targets,-t', [], {
|
||||
description: 'All targets the crate will be compiled for.',
|
||||
})
|
||||
|
||||
enableDefaultTargets = Option.Boolean('--enable-default-targets', true, {
|
||||
description: 'Whether enable default targets',
|
||||
})
|
||||
|
||||
enableAllTargets = Option.Boolean('--enable-all-targets', false, {
|
||||
description: 'Whether enable all targets',
|
||||
})
|
||||
|
||||
enableTypeDef = Option.Boolean('--enable-type-def', true, {
|
||||
description:
|
||||
'Whether enable the `type-def` feature for typescript definitions auto-generation',
|
||||
})
|
||||
|
||||
enableGithubActions = Option.Boolean('--enable-github-actions', true, {
|
||||
description: 'Whether generate preconfigured GitHub Actions workflow',
|
||||
})
|
||||
|
||||
dryRun = Option.Boolean('--dry-run', false, {
|
||||
description: 'Whether to run the command in dry-run mode',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
path: this.$$path,
|
||||
name: this.$$name,
|
||||
minNodeApiVersion: this.minNodeApiVersion,
|
||||
license: this.license,
|
||||
targets: this.targets,
|
||||
enableDefaultTargets: this.enableDefaultTargets,
|
||||
enableAllTargets: this.enableAllTargets,
|
||||
enableTypeDef: this.enableTypeDef,
|
||||
enableGithubActions: this.enableGithubActions,
|
||||
dryRun: this.dryRun,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project with pre-configured boilerplate
|
||||
*/
|
||||
export interface NewOptions {
|
||||
/**
|
||||
* The path where the napi-rs project will be created.
|
||||
*/
|
||||
path: string
|
||||
/**
|
||||
* The name of the project, default to the name of the directory if not provided
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* The minimum Node-API version to support
|
||||
*
|
||||
* @default 4
|
||||
*/
|
||||
minNodeApiVersion?: number
|
||||
/**
|
||||
* License for open-sourced project
|
||||
*
|
||||
* @default 'MIT'
|
||||
*/
|
||||
license?: string
|
||||
/**
|
||||
* All targets the crate will be compiled for.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
targets?: string[]
|
||||
/**
|
||||
* Whether enable default targets
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableDefaultTargets?: boolean
|
||||
/**
|
||||
* Whether enable all targets
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
enableAllTargets?: boolean
|
||||
/**
|
||||
* Whether enable the `type-def` feature for typescript definitions auto-generation
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableTypeDef?: boolean
|
||||
/**
|
||||
* Whether generate preconfigured GitHub Actions workflow
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableGithubActions?: boolean
|
||||
/**
|
||||
* Whether to run the command in dry-run mode
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
export function applyDefaultNewOptions(options: NewOptions) {
|
||||
return {
|
||||
minNodeApiVersion: 4,
|
||||
license: 'MIT',
|
||||
targets: [],
|
||||
enableDefaultTargets: true,
|
||||
enableAllTargets: false,
|
||||
enableTypeDef: true,
|
||||
enableGithubActions: true,
|
||||
dryRun: false,
|
||||
...options,
|
||||
}
|
||||
}
|
120
cli/src/def/pre-publish.ts
Normal file
120
cli/src/def/pre-publish.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BasePrePublishCommand extends Command {
|
||||
static paths = [['pre-publish']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description:
|
||||
'Update package.json and copy addons into per platform packages',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
npmDir = Option.String('--npm-dir', 'npm', {
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
})
|
||||
|
||||
tagStyle = Option.String('--tag-style', 'lerna', {
|
||||
description: 'git tag style, `npm` or `lerna`',
|
||||
})
|
||||
|
||||
ghRelease = Option.Boolean('--gh-release', true, {
|
||||
description: 'Whether create GitHub release',
|
||||
})
|
||||
|
||||
ghReleaseName?: string = Option.String('--gh-release-name', {
|
||||
description: 'GitHub release name',
|
||||
})
|
||||
|
||||
ghReleaseId?: string = Option.String('--gh-release-id', {
|
||||
description: 'Existing GitHub release id',
|
||||
})
|
||||
|
||||
dryRun = Option.Boolean('--dry-run', false, {
|
||||
description: 'Dry run without touching file system',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
npmDir: this.npmDir,
|
||||
tagStyle: this.tagStyle,
|
||||
ghRelease: this.ghRelease,
|
||||
ghReleaseName: this.ghReleaseName,
|
||||
ghReleaseId: this.ghReleaseId,
|
||||
dryRun: this.dryRun,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update package.json and copy addons into per platform packages
|
||||
*/
|
||||
export interface PrePublishOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where the npm packages put
|
||||
*
|
||||
* @default 'npm'
|
||||
*/
|
||||
npmDir?: string
|
||||
/**
|
||||
* git tag style, `npm` or `lerna`
|
||||
*
|
||||
* @default 'lerna'
|
||||
*/
|
||||
tagStyle?: 'npm' | 'lerna'
|
||||
/**
|
||||
* Whether create GitHub release
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
ghRelease?: boolean
|
||||
/**
|
||||
* GitHub release name
|
||||
*/
|
||||
ghReleaseName?: string
|
||||
/**
|
||||
* Existing GitHub release id
|
||||
*/
|
||||
ghReleaseId?: string
|
||||
/**
|
||||
* Dry run without touching file system
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
export function applyDefaultPrePublishOptions(options: PrePublishOptions) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
npmDir: 'npm',
|
||||
tagStyle: 'lerna',
|
||||
ghRelease: true,
|
||||
dryRun: false,
|
||||
...options,
|
||||
}
|
||||
}
|
122
cli/src/def/rename.ts
Normal file
122
cli/src/def/rename.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseRenameCommand extends Command {
|
||||
static paths = [['rename']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Rename the napi-rs project',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
npmDir = Option.String('--npm-dir', 'npm', {
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
})
|
||||
|
||||
$$name?: string = Option.String('--name,-n', {
|
||||
description: 'The new name of the project',
|
||||
})
|
||||
|
||||
binaryName?: string = Option.String('--binary-name,-b', {
|
||||
description: 'The new binary name *.node files',
|
||||
})
|
||||
|
||||
packageName?: string = Option.String('--package-name', {
|
||||
description: 'The new package name of the project',
|
||||
})
|
||||
|
||||
manifestPath = Option.String('--manifest-path', 'Cargo.toml', {
|
||||
description: 'Path to `Cargo.toml`',
|
||||
})
|
||||
|
||||
repository?: string = Option.String('--repository', {
|
||||
description: 'The new repository of the project',
|
||||
})
|
||||
|
||||
description?: string = Option.String('--description', {
|
||||
description: 'The new description of the project',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
npmDir: this.npmDir,
|
||||
name: this.$$name,
|
||||
binaryName: this.binaryName,
|
||||
packageName: this.packageName,
|
||||
manifestPath: this.manifestPath,
|
||||
repository: this.repository,
|
||||
description: this.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the napi-rs project
|
||||
*/
|
||||
export interface RenameOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where the npm packages put
|
||||
*
|
||||
* @default 'npm'
|
||||
*/
|
||||
npmDir?: string
|
||||
/**
|
||||
* The new name of the project
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* The new binary name *.node files
|
||||
*/
|
||||
binaryName?: string
|
||||
/**
|
||||
* The new package name of the project
|
||||
*/
|
||||
packageName?: string
|
||||
/**
|
||||
* Path to `Cargo.toml`
|
||||
*
|
||||
* @default 'Cargo.toml'
|
||||
*/
|
||||
manifestPath?: string
|
||||
/**
|
||||
* The new repository of the project
|
||||
*/
|
||||
repository?: string
|
||||
/**
|
||||
* The new description of the project
|
||||
*/
|
||||
description?: string
|
||||
}
|
||||
|
||||
export function applyDefaultRenameOptions(options: RenameOptions) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
npmDir: 'npm',
|
||||
manifestPath: 'Cargo.toml',
|
||||
...options,
|
||||
}
|
||||
}
|
66
cli/src/def/universalize.ts
Normal file
66
cli/src/def/universalize.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseUniversalizeCommand extends Command {
|
||||
static paths = [['universalize']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Combile built binaries into one universal binary',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
outputDir = Option.String('--output-dir,-o', './', {
|
||||
description:
|
||||
'Path to the folder where all built `.node` files put, same as `--output-dir` of build command',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
outputDir: this.outputDir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combile built binaries into one universal binary
|
||||
*/
|
||||
export interface UniversalizeOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where all built `.node` files put, same as `--output-dir` of build command
|
||||
*
|
||||
* @default './'
|
||||
*/
|
||||
outputDir?: string
|
||||
}
|
||||
|
||||
export function applyDefaultUniversalizeOptions(options: UniversalizeOptions) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
outputDir: './',
|
||||
...options,
|
||||
}
|
||||
}
|
65
cli/src/def/version.ts
Normal file
65
cli/src/def/version.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
// This file is generated by codegen/index.ts
|
||||
// Do not edit this file manually
|
||||
import { Command, Option } from 'clipanion'
|
||||
|
||||
export abstract class BaseVersionCommand extends Command {
|
||||
static paths = [['version']]
|
||||
|
||||
static usage = Command.Usage({
|
||||
description: 'Update version in created npm packages',
|
||||
})
|
||||
|
||||
cwd = Option.String('--cwd', process.cwd(), {
|
||||
description:
|
||||
'The working directory of where napi command will be executed in, all other paths options are relative to this path',
|
||||
})
|
||||
|
||||
packageJsonPath = Option.String('--package-json-path', 'package.json', {
|
||||
description: 'Path to `package.json`',
|
||||
})
|
||||
|
||||
npmDir = Option.String('--npm-dir', 'npm', {
|
||||
description: 'Path to the folder where the npm packages put',
|
||||
})
|
||||
|
||||
getOptions() {
|
||||
return {
|
||||
cwd: this.cwd,
|
||||
packageJsonPath: this.packageJsonPath,
|
||||
npmDir: this.npmDir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update version in created npm packages
|
||||
*/
|
||||
export interface VersionOptions {
|
||||
/**
|
||||
* The working directory of where napi command will be executed in, all other paths options are relative to this path
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*
|
||||
* @default 'package.json'
|
||||
*/
|
||||
packageJsonPath?: string
|
||||
/**
|
||||
* Path to the folder where the npm packages put
|
||||
*
|
||||
* @default 'npm'
|
||||
*/
|
||||
npmDir?: string
|
||||
}
|
||||
|
||||
export function applyDefaultVersionOptions(options: VersionOptions) {
|
||||
return {
|
||||
cwd: process.cwd(),
|
||||
packageJsonPath: 'package.json',
|
||||
npmDir: 'npm',
|
||||
...options,
|
||||
}
|
||||
}
|
|
@ -1,42 +1,31 @@
|
|||
import 'core-js/es/string/replace-all'
|
||||
import { collectArtifacts } from './api/artifacts.js'
|
||||
import { buildProject } from './api/build.js'
|
||||
import { createNpmDirs } from './api/create-npm-dirs.js'
|
||||
import { newProject } from './api/new.js'
|
||||
import { prePublish } from './api/pre-publish.js'
|
||||
import { renameProject } from './api/rename.js'
|
||||
import { universalizeBinaries } from './api/universalize.js'
|
||||
import { version } from './api/version.js'
|
||||
|
||||
import { Cli } from 'clipanion'
|
||||
|
||||
import { version } from '../package.json'
|
||||
|
||||
import { ArtifactsCommand } from './artifacts'
|
||||
import { BuildCommand } from './build'
|
||||
import { CreateNpmDirCommand } from './create-npm-dir'
|
||||
import { HelpCommand } from './help'
|
||||
import { NewProjectCommand } from './new'
|
||||
import { PrePublishCommand } from './pre-publish'
|
||||
import { RenameCommand } from './rename'
|
||||
import { UniversalCommand } from './universal'
|
||||
import { VersionCommand } from './version'
|
||||
|
||||
const cli = new Cli({
|
||||
binaryName: 'napi',
|
||||
binaryVersion: version,
|
||||
})
|
||||
|
||||
cli.register(ArtifactsCommand)
|
||||
cli.register(BuildCommand)
|
||||
cli.register(CreateNpmDirCommand)
|
||||
cli.register(PrePublishCommand)
|
||||
cli.register(VersionCommand)
|
||||
cli.register(UniversalCommand)
|
||||
cli.register(NewProjectCommand)
|
||||
cli.register(RenameCommand)
|
||||
cli.register(HelpCommand)
|
||||
|
||||
cli
|
||||
.run(process.argv.slice(2), {
|
||||
...Cli.defaultContext,
|
||||
})
|
||||
.then((status) => {
|
||||
process.exit(status)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
/**
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```ts
|
||||
* const cli = new NapiCli()
|
||||
*
|
||||
* cli.build({
|
||||
* cwd: '/path/to/your/project',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export class NapiCli {
|
||||
artifacts = collectArtifacts
|
||||
new = newProject
|
||||
build = buildProject
|
||||
createNpmDirs = createNpmDirs
|
||||
prePublish = prePublish
|
||||
rename = renameProject
|
||||
universalize = universalizeBinaries
|
||||
version = version
|
||||
}
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
export const createJsBinding = (
|
||||
localName: string,
|
||||
pkgName: string,
|
||||
) => `/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
let nativeBinding = null
|
||||
let localFileExisted = false
|
||||
let loadError = null
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(join(__dirname, '${localName}.android-arm64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(join(__dirname, '${localName}.android-arm-eabi.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(\`Unsupported architecture on Android \${arch}\`)
|
||||
}
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.win32-x64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'ia32':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.win32-ia32-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.win32-arm64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(\`Unsupported architecture on Windows: \${arch}\`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
localFileExisted = existsSync(join(__dirname, '${localName}.darwin-universal.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.darwin-universal.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-darwin-universal')
|
||||
}
|
||||
break
|
||||
} catch {}
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(join(__dirname, '${localName}.darwin-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.darwin-arm64.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(\`Unsupported architecture on macOS: \${arch}\`)
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch !== 'x64') {
|
||||
throw new Error(\`Unsupported architecture on FreeBSD: \${arch}\`)
|
||||
}
|
||||
localFileExisted = existsSync(join(__dirname, '${localName}.freebsd-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-freebsd-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, '${localName}.linux-arm-gnueabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./${localName}.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('${pkgName}-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(\`Unsupported architecture on Linux: \${arch}\`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(\`Unsupported OS: \${platform}, architecture: \${arch}\`)
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError
|
||||
}
|
||||
throw new Error(\`Failed to load native binding\`)
|
||||
}
|
||||
|
||||
`
|
|
@ -1,9 +0,0 @@
|
|||
export const createCargoConfig = (enableLinuxArm8Musl: boolean) => {
|
||||
const result: string[] = []
|
||||
if (enableLinuxArm8Musl) {
|
||||
result.push(`[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-linux-musl-gcc"
|
||||
rustflags = ["-C", "target-feature=-crt-static"]`)
|
||||
}
|
||||
return result.join('\n')
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
export const createCargoContent = (name: string) => `[package]
|
||||
edition = "2021"
|
||||
name = "${name.replace('@', '').replace('/', '_').toLowerCase()}"
|
||||
version = "0.0.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||
napi = { version = "NAPI_VERSION", default-features = false, features = ["napi4"] }
|
||||
napi-derive = "NAPI_DERIVE_VERSION"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "NAPI_BUILD_VERSION"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
`
|
|
@ -1,230 +0,0 @@
|
|||
import { writeFileSync, mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
import inquirer from 'inquirer'
|
||||
|
||||
import { CreateNpmDirCommand } from '../create-npm-dir'
|
||||
import { debugFactory } from '../debug'
|
||||
import { DefaultPlatforms } from '../parse-triple'
|
||||
import { spawn } from '../spawn'
|
||||
|
||||
import { createCargoContent } from './cargo'
|
||||
import { createCargoConfig } from './cargo-config'
|
||||
import { createGithubActionsCIYml } from './ci-yml'
|
||||
import { GitIgnore } from './gitignore-template'
|
||||
import { LibRs } from './lib-rs'
|
||||
import { NPMIgnoreFiles } from './npmignore'
|
||||
import { createPackageJson } from './package'
|
||||
|
||||
const NAME_PROMOTE_NAME = 'Package name'
|
||||
const DIR_PROMOTE_NAME = 'Dir name'
|
||||
const ENABLE_GITHUB_ACTIONS_PROMOTE_NAME = 'Enable github actions'
|
||||
|
||||
const debug = debugFactory('create')
|
||||
|
||||
const BUILD_RS = `extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
`
|
||||
|
||||
const SupportedPlatforms: string[] = [
|
||||
'aarch64-apple-darwin',
|
||||
'aarch64-linux-android',
|
||||
'aarch64-unknown-linux-gnu',
|
||||
'aarch64-unknown-linux-musl',
|
||||
'aarch64-pc-windows-msvc',
|
||||
'armv7-unknown-linux-gnueabihf',
|
||||
'x86_64-apple-darwin',
|
||||
'x86_64-pc-windows-msvc',
|
||||
'x86_64-unknown-linux-gnu',
|
||||
'x86_64-unknown-linux-musl',
|
||||
'x86_64-unknown-freebsd',
|
||||
'i686-pc-windows-msvc',
|
||||
'armv7-linux-androideabi',
|
||||
'universal-apple-darwin',
|
||||
]
|
||||
|
||||
export class NewProjectCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description: 'Create a new project from scratch',
|
||||
})
|
||||
|
||||
static paths = [['new']]
|
||||
|
||||
name?: string = Option.String({
|
||||
name: '-n,--name',
|
||||
required: false,
|
||||
})
|
||||
|
||||
dirname?: string = Option.String({
|
||||
name: '-d,--dirname',
|
||||
required: false,
|
||||
})
|
||||
|
||||
targets?: string[] = Option.Array('--targets,-t')
|
||||
|
||||
dryRun = Option.Boolean(`--dry-run`, false)
|
||||
|
||||
enableGithubActions?: boolean = Option.Boolean(`--enable-github-actions`)
|
||||
|
||||
async execute() {
|
||||
await this.getName()
|
||||
if (!this.dirname) {
|
||||
const [scope, name] = this.name?.split('/') ?? []
|
||||
const defaultProjectDir = name ?? scope
|
||||
const dirAnswer = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: DIR_PROMOTE_NAME,
|
||||
default: defaultProjectDir,
|
||||
})
|
||||
|
||||
this.dirname = dirAnswer[DIR_PROMOTE_NAME]
|
||||
}
|
||||
|
||||
if (!this.targets) {
|
||||
const { targets } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'targets',
|
||||
message: 'Choose targets you want to support',
|
||||
default: DefaultPlatforms.map((p) => p.raw),
|
||||
choices: SupportedPlatforms,
|
||||
},
|
||||
])
|
||||
|
||||
if (!targets.length) {
|
||||
throw new TypeError('At least choose one target')
|
||||
}
|
||||
|
||||
this.targets = targets
|
||||
}
|
||||
|
||||
if (this.enableGithubActions === undefined) {
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: ENABLE_GITHUB_ACTIONS_PROMOTE_NAME,
|
||||
message: 'Enable github actions?',
|
||||
default: true,
|
||||
choices: SupportedPlatforms,
|
||||
},
|
||||
])
|
||||
this.enableGithubActions = answer[ENABLE_GITHUB_ACTIONS_PROMOTE_NAME]
|
||||
}
|
||||
|
||||
debug(`Running command: ${chalk.green('[${command}]')}`)
|
||||
if (!this.dryRun) {
|
||||
mkdirSync(join(process.cwd(), this.dirname!), {
|
||||
recursive: true,
|
||||
})
|
||||
mkdirSync(join(process.cwd(), this.dirname!, 'src'), {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
const [s, pkgName] = this.name!.split('/')
|
||||
const binaryName = pkgName ?? s
|
||||
|
||||
this.writeFile('Cargo.toml', createCargoContent(this.name!))
|
||||
this.writeFile('.npmignore', NPMIgnoreFiles)
|
||||
this.writeFile('build.rs', BUILD_RS)
|
||||
this.writeFile(
|
||||
'package.json',
|
||||
JSON.stringify(
|
||||
createPackageJson(this.name!, binaryName, this.targets!),
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
this.writeFile('src/lib.rs', LibRs)
|
||||
|
||||
mkdirSync(join(process.cwd(), this.dirname!, '__test__'), {
|
||||
recursive: true,
|
||||
})
|
||||
this.writeFile(
|
||||
'__test__/index.spec.mjs',
|
||||
`import test from 'ava'
|
||||
|
||||
import { sum } from '../index.js'
|
||||
|
||||
test('sum from native', (t) => {
|
||||
t.is(sum(1, 2), 3)
|
||||
})
|
||||
`,
|
||||
)
|
||||
|
||||
if (this.enableGithubActions) {
|
||||
const githubDir = join(process.cwd(), this.dirname!, '.github')
|
||||
const workflowsDir = join(githubDir, 'workflows')
|
||||
if (!this.dryRun) {
|
||||
mkdirSync(githubDir, { recursive: true })
|
||||
mkdirSync(workflowsDir, { recursive: true })
|
||||
}
|
||||
this.writeFile(
|
||||
join('.github', 'workflows', 'CI.yml'),
|
||||
createGithubActionsCIYml(binaryName, this.targets!),
|
||||
)
|
||||
}
|
||||
|
||||
await CreateNpmDirCommand.create(
|
||||
'package.json',
|
||||
join(process.cwd(), this.dirname!),
|
||||
join(process.cwd(), this.dirname!),
|
||||
)
|
||||
|
||||
const enableLinuxArm8Musl = this.targets!.includes(
|
||||
'aarch64-unknown-linux-musl',
|
||||
)
|
||||
const cargoConfig = createCargoConfig(enableLinuxArm8Musl)
|
||||
if (cargoConfig.length) {
|
||||
const configDir = join(process.cwd(), this.dirname!, '.cargo')
|
||||
if (!this.dryRun) {
|
||||
mkdirSync(configDir, { recursive: true })
|
||||
this.writeFile(join('.cargo', 'config.toml'), cargoConfig)
|
||||
}
|
||||
}
|
||||
this.writeFile(
|
||||
'rustfmt.toml',
|
||||
`tab_spaces = 2
|
||||
edition = "2021"
|
||||
`,
|
||||
)
|
||||
this.writeFile('.gitignore', GitIgnore)
|
||||
this.writeFile('.yarnrc.yml', 'nodeLinker: node-modules')
|
||||
await spawn(`yarn set version stable`, {
|
||||
cwd: join(process.cwd(), this.dirname!),
|
||||
})
|
||||
await spawn(`yarn install`, {
|
||||
cwd: join(process.cwd(), this.dirname!),
|
||||
})
|
||||
}
|
||||
|
||||
private writeFile(path: string, content: string) {
|
||||
const distDir = join(process.cwd(), this.dirname!)
|
||||
this.context.stdout.write(chalk.green(`Writing ${chalk.blue(path)}\n`))
|
||||
if (!this.dryRun) {
|
||||
writeFileSync(join(distDir, path), content)
|
||||
}
|
||||
}
|
||||
|
||||
private async getName() {
|
||||
if (!this.name) {
|
||||
const nameAnswer = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: NAME_PROMOTE_NAME,
|
||||
suffix: ' (The name filed in your package.json)',
|
||||
})
|
||||
|
||||
const name = nameAnswer[NAME_PROMOTE_NAME]
|
||||
if (!name) {
|
||||
await this.getName()
|
||||
} else {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
import { version } from '../../package.json'
|
||||
import { DefaultPlatforms } from '../parse-triple'
|
||||
|
||||
export const createPackageJson = (
|
||||
name: string,
|
||||
binaryName: string,
|
||||
targets: string[],
|
||||
) => {
|
||||
const pkgContent = {
|
||||
name,
|
||||
version: '0.0.0',
|
||||
main: 'index.js',
|
||||
types: 'index.d.ts',
|
||||
napi: {
|
||||
name: binaryName,
|
||||
},
|
||||
license: 'MIT',
|
||||
devDependencies: {
|
||||
'@napi-rs/cli': `^${version}`,
|
||||
ava: '^5.1.1',
|
||||
},
|
||||
ava: {
|
||||
timeout: '3m',
|
||||
},
|
||||
engines: {
|
||||
node: '>= 10',
|
||||
},
|
||||
scripts: {
|
||||
artifacts: 'napi artifacts',
|
||||
build: 'napi build --platform --release',
|
||||
'build:debug': 'napi build --platform',
|
||||
prepublishOnly: 'napi prepublish -t npm',
|
||||
test: 'ava',
|
||||
universal: 'napi universal',
|
||||
version: 'napi version',
|
||||
},
|
||||
}
|
||||
|
||||
const triples: any = {}
|
||||
|
||||
const defaultTargetsSupported = DefaultPlatforms.every((p) =>
|
||||
targets!.includes(p.raw),
|
||||
)
|
||||
|
||||
const isOnlyDefaultTargets =
|
||||
targets.length === 3 &&
|
||||
DefaultPlatforms.every((p) => targets.includes(p.raw))
|
||||
|
||||
if (!isOnlyDefaultTargets) {
|
||||
if (!defaultTargetsSupported) {
|
||||
triples.defaults = false
|
||||
triples.additional = targets
|
||||
} else {
|
||||
triples.additional = targets.filter(
|
||||
(t) => !DefaultPlatforms.map((p) => p.raw).includes(t),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
pkgContent.napi.triples = triples
|
||||
|
||||
return pkgContent
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
import { existsSync, statSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
import { Octokit } from '@octokit/rest'
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
|
||||
import { getNapiConfig } from './consts'
|
||||
import { debugFactory } from './debug'
|
||||
import { spawn } from './spawn'
|
||||
import { updatePackageJson } from './update-package'
|
||||
import { readFileAsync } from './utils'
|
||||
import { VersionCommand } from './version'
|
||||
|
||||
const debug = debugFactory('prepublish')
|
||||
|
||||
interface PackageInfo {
|
||||
name: string
|
||||
version: string
|
||||
tag: string
|
||||
}
|
||||
|
||||
export class PrePublishCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description:
|
||||
'Update package.json and copy addons into per platform packages',
|
||||
})
|
||||
|
||||
static paths = [['prepublish']]
|
||||
|
||||
prefix = Option.String(`-p,--prefix`, 'npm')
|
||||
|
||||
tagStyle: 'npm' | 'lerna' = Option.String('--tagstyle,-t', 'lerna')
|
||||
|
||||
configFileName?: string = Option.String('-c,--config')
|
||||
|
||||
isDryRun = Option.Boolean('--dry-run', false)
|
||||
|
||||
skipGHRelease = Option.Boolean('--skip-gh-release', false)
|
||||
|
||||
ghReleaseName?: string = Option.String('--gh-release-name')
|
||||
|
||||
existingReleaseId?: string = Option.String('--gh-release-id')
|
||||
|
||||
async execute() {
|
||||
const {
|
||||
packageJsonPath,
|
||||
platforms,
|
||||
version,
|
||||
packageName,
|
||||
binaryName,
|
||||
npmClient,
|
||||
} = getNapiConfig(this.configFileName)
|
||||
debug(`Update optionalDependencies in [${packageJsonPath}]`)
|
||||
if (!this.isDryRun) {
|
||||
await VersionCommand.updatePackageJson(this.prefix, this.configFileName)
|
||||
await updatePackageJson(packageJsonPath, {
|
||||
optionalDependencies: platforms.reduce(
|
||||
(acc: Record<string, string>, cur) => {
|
||||
acc[`${packageName}-${cur.platformArchABI}`] = `${version}`
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const { owner, repo, pkgInfo, octokit } = this.existingReleaseId
|
||||
? await this.getRepoInfo(packageName, version)
|
||||
: await this.createGhRelease(packageName, version)
|
||||
|
||||
for (const platformDetail of platforms) {
|
||||
const pkgDir = join(
|
||||
process.cwd(),
|
||||
this.prefix,
|
||||
`${platformDetail.platformArchABI}`,
|
||||
)
|
||||
const filename = `${binaryName}.${platformDetail.platformArchABI}.node`
|
||||
const dstPath = join(pkgDir, filename)
|
||||
|
||||
if (!this.isDryRun) {
|
||||
if (!existsSync(dstPath)) {
|
||||
console.warn(`[${chalk.yellowBright(dstPath)}] doesn't exist`)
|
||||
continue
|
||||
}
|
||||
await spawn(`${npmClient} publish`, {
|
||||
cwd: pkgDir,
|
||||
env: process.env,
|
||||
})
|
||||
if (!this.skipGHRelease && repo && owner) {
|
||||
debug(
|
||||
`Start upload [${chalk.greenBright(
|
||||
dstPath,
|
||||
)}] to Github release, [${chalk.greenBright(pkgInfo.tag)}]`,
|
||||
)
|
||||
try {
|
||||
const releaseId = this.existingReleaseId
|
||||
? Number(this.existingReleaseId)
|
||||
: (
|
||||
await octokit!.repos.getReleaseByTag({
|
||||
repo: repo,
|
||||
owner: owner,
|
||||
tag: pkgInfo.tag,
|
||||
})
|
||||
).data.id
|
||||
const dstFileStats = statSync(dstPath)
|
||||
const assetInfo = await octokit!.repos.uploadReleaseAsset({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
name: filename,
|
||||
release_id: releaseId,
|
||||
mediaType: { format: 'raw' },
|
||||
headers: {
|
||||
'content-length': dstFileStats.size,
|
||||
'content-type': 'application/octet-stream',
|
||||
},
|
||||
// @ts-expect-error
|
||||
data: await readFileAsync(dstPath),
|
||||
})
|
||||
console.info(`${chalk.green(dstPath)} upload success`)
|
||||
console.info(
|
||||
`Download url: ${chalk.blueBright(
|
||||
assetInfo.data.browser_download_url,
|
||||
)}`,
|
||||
)
|
||||
} catch (e) {
|
||||
debug(
|
||||
`Param: ${JSON.stringify(
|
||||
{ owner, repo, tag: pkgInfo.tag, filename: dstPath },
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
)
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createGhRelease(packageName: string, version: string) {
|
||||
if (this.skipGHRelease) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
const { repo, owner, pkgInfo, octokit } = await this.getRepoInfo(
|
||||
packageName,
|
||||
version,
|
||||
)
|
||||
|
||||
if (!repo || !owner) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isDryRun) {
|
||||
try {
|
||||
await octokit.repos.createRelease({
|
||||
owner,
|
||||
repo,
|
||||
tag_name: pkgInfo.tag,
|
||||
name: this.ghReleaseName,
|
||||
prerelease:
|
||||
version.includes('alpha') ||
|
||||
version.includes('beta') ||
|
||||
version.includes('rc'),
|
||||
})
|
||||
} catch (e) {
|
||||
debug(
|
||||
`Params: ${JSON.stringify(
|
||||
{ owner, repo, tag_name: pkgInfo.tag },
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
)
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
return { owner, repo, pkgInfo, octokit }
|
||||
}
|
||||
|
||||
private async getRepoInfo(packageName: string, version: string) {
|
||||
const headCommit = (await spawn('git log -1 --pretty=%B'))
|
||||
.toString('utf8')
|
||||
.trim()
|
||||
const { GITHUB_REPOSITORY } = process.env
|
||||
if (!GITHUB_REPOSITORY) {
|
||||
return {
|
||||
owner: null,
|
||||
repo: null,
|
||||
pkgInfo: { name: null, version: null, tag: null },
|
||||
}
|
||||
}
|
||||
debug(`Github repository: ${GITHUB_REPOSITORY}`)
|
||||
const [owner, repo] = GITHUB_REPOSITORY.split('/')
|
||||
const octokit = new Octokit({
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
})
|
||||
let pkgInfo: PackageInfo | undefined
|
||||
if (this.tagStyle === 'lerna') {
|
||||
const packagesToPublish = headCommit
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line, index) => line.length && index)
|
||||
.map((line) => line.substring(2))
|
||||
.map(this.parseTag)
|
||||
pkgInfo = packagesToPublish.find(
|
||||
(pkgInfo) => pkgInfo.name === packageName,
|
||||
)
|
||||
if (!pkgInfo) {
|
||||
throw new TypeError(
|
||||
`No release commit found with ${packageName}, original commit info: ${headCommit}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
pkgInfo = {
|
||||
tag: `v${version}`,
|
||||
version,
|
||||
name: packageName,
|
||||
}
|
||||
}
|
||||
return { owner, repo, pkgInfo, octokit }
|
||||
}
|
||||
|
||||
private parseTag(tag: string) {
|
||||
const segments = tag.split('@')
|
||||
const version = segments.pop()!
|
||||
const name = segments.join('@')
|
||||
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
tag,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
import { join } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
import inquirer from 'inquirer'
|
||||
import { load, dump } from 'js-yaml'
|
||||
|
||||
import { debugFactory } from './debug'
|
||||
import { spawn } from './spawn'
|
||||
import { readFileAsync, writeFileAsync } from './utils'
|
||||
|
||||
const debug = debugFactory('rename')
|
||||
|
||||
export class RenameCommand extends Command {
|
||||
static paths = [['rename']]
|
||||
|
||||
name = Option.String('-n', {
|
||||
required: false,
|
||||
description: 'The new name of the project',
|
||||
})
|
||||
|
||||
napiName = Option.String('--napi-name', {
|
||||
required: false,
|
||||
description: 'The new napi addon name',
|
||||
})
|
||||
|
||||
repository = Option.String('--repository', {
|
||||
required: false,
|
||||
description: 'The repository of the package',
|
||||
})
|
||||
|
||||
description = Option.String('-d,--description', {
|
||||
required: false,
|
||||
description: 'The description of the package',
|
||||
})
|
||||
|
||||
cwd = Option.String({
|
||||
required: false,
|
||||
description: 'The working directory, default is [process.cwd()]',
|
||||
})
|
||||
|
||||
async execute() {
|
||||
const cwd = this.cwd ?? process.cwd()
|
||||
const packageJson = await readFileAsync(join(cwd, 'package.json'), 'utf8')
|
||||
const packageJsonData = JSON.parse(packageJson)
|
||||
const name =
|
||||
this.name ??
|
||||
(
|
||||
await inquirer.prompt({
|
||||
name: 'name',
|
||||
type: 'input',
|
||||
suffix: chalk.dim(' name field in package.json'),
|
||||
})
|
||||
).name
|
||||
const napiName =
|
||||
this.napiName ??
|
||||
(
|
||||
await inquirer.prompt({
|
||||
name: 'napi name',
|
||||
type: 'input',
|
||||
default: name.split('/')[1],
|
||||
})
|
||||
)['napi name']
|
||||
debug('name: %s, napi name: %s', name, napiName)
|
||||
packageJsonData.name = name
|
||||
packageJsonData.napi.name = napiName
|
||||
const repository =
|
||||
this.repository ??
|
||||
(
|
||||
await inquirer.prompt({
|
||||
name: 'repository',
|
||||
type: 'input',
|
||||
suffix: chalk.dim(' Leave empty to skip'),
|
||||
})
|
||||
).repository
|
||||
if (repository) {
|
||||
packageJsonData.repository = repository
|
||||
}
|
||||
const description =
|
||||
this.description ??
|
||||
(
|
||||
await inquirer.prompt({
|
||||
name: 'description',
|
||||
type: 'input',
|
||||
suffix: chalk.dim(' Leave empty to skip'),
|
||||
})
|
||||
).description
|
||||
|
||||
if (description) {
|
||||
packageJsonData.description = description
|
||||
}
|
||||
|
||||
await writeFileAsync(
|
||||
join(cwd, 'package.json'),
|
||||
JSON.stringify(packageJsonData, null, 2),
|
||||
)
|
||||
|
||||
const CI = await readFileAsync(
|
||||
join(cwd, '.github', 'workflows', 'CI.yml'),
|
||||
'utf8',
|
||||
)
|
||||
const CIObject = load(CI) as any
|
||||
CIObject.env.APP_NAME = napiName
|
||||
|
||||
await writeFileAsync(
|
||||
join(cwd, '.github', 'workflows', 'CI.yml'),
|
||||
dump(CIObject, {
|
||||
lineWidth: 1000,
|
||||
}),
|
||||
)
|
||||
|
||||
let tomlContent = await readFileAsync(join(cwd, 'Cargo.toml'), 'utf8')
|
||||
tomlContent = tomlContent.replace(
|
||||
'name = "napi-package-template"',
|
||||
`name = "${napiName}"`,
|
||||
)
|
||||
await writeFileAsync(join(cwd, 'Cargo.toml'), tomlContent)
|
||||
|
||||
await spawn('napi create-npm-dir -t .')
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { spawn as _spawn, SpawnOptionsWithoutStdio } from 'child_process'
|
||||
|
||||
import { debugFactory } from './debug'
|
||||
|
||||
const debug = debugFactory('spawn')
|
||||
|
||||
export function spawn(
|
||||
command: string,
|
||||
options: SpawnOptionsWithoutStdio = {},
|
||||
): Promise<Buffer> {
|
||||
const [cmd, ...args] = command.split(' ').map((s) => s.trim())
|
||||
debug(`execute ${cmd} ${args.join(' ')}`)
|
||||
return new Promise((resolve, reject) => {
|
||||
const spawnStream = _spawn(cmd, args, { ...options, shell: true })
|
||||
const chunks: Buffer[] = []
|
||||
process.stdin.pipe(spawnStream.stdin)
|
||||
spawnStream.stdout?.on('data', (chunk) => {
|
||||
chunks.push(chunk)
|
||||
})
|
||||
spawnStream.stdout.pipe(process.stdout)
|
||||
spawnStream.stderr.pipe(process.stderr)
|
||||
spawnStream.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject()
|
||||
} else {
|
||||
resolve(Buffer.concat(chunks))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import { spawnSync } from 'child_process'
|
||||
import { join } from 'path'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as chalk from 'colorette'
|
||||
|
||||
import { getNapiConfig } from './consts'
|
||||
import { debugFactory } from './debug'
|
||||
import { UniArchsByPlatform } from './parse-triple'
|
||||
import { fileExists } from './utils'
|
||||
|
||||
const debug = debugFactory('universal')
|
||||
|
||||
export class UniversalCommand extends Command {
|
||||
static usage = Command.Usage({
|
||||
description: 'Combine built binaries to universal binaries',
|
||||
})
|
||||
|
||||
static paths = [['universal']]
|
||||
|
||||
sourceDir = Option.String('-d,--dir', 'artifacts')
|
||||
|
||||
distDir = Option.String('--dist', '.')
|
||||
|
||||
configFileName?: string = Option.String('-c,--config')
|
||||
|
||||
buildUniversal: Record<
|
||||
keyof typeof UniArchsByPlatform,
|
||||
(binName: string, srcFiles: string[]) => string
|
||||
> = {
|
||||
darwin: (binName, srcFiles) => {
|
||||
const outPath = join(
|
||||
this.distDir,
|
||||
`${binName}.${process.platform}-universal.node`,
|
||||
)
|
||||
const srcPaths = srcFiles.map((f) => join(this.sourceDir, f))
|
||||
spawnSync('lipo', ['-create', '-output', outPath, ...srcPaths])
|
||||
return outPath
|
||||
},
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const { platforms, binaryName } = getNapiConfig(this.configFileName)
|
||||
|
||||
const targetPlatform = platforms.find(
|
||||
(p) => p.platform === process.platform && p.arch === 'universal',
|
||||
)
|
||||
if (!targetPlatform) {
|
||||
throw new TypeError(
|
||||
`'universal' arch for platform '${process.platform}' not found in config!`,
|
||||
)
|
||||
}
|
||||
|
||||
const srcFiles = UniArchsByPlatform[process.platform]?.map(
|
||||
(a) => `${binaryName}.${process.platform}-${a}.node`,
|
||||
)
|
||||
if (!srcFiles) {
|
||||
throw new TypeError(
|
||||
`'universal' arch for platform '${process.platform}' not supported.`,
|
||||
)
|
||||
}
|
||||
|
||||
debug(
|
||||
`Looking up source binaries to combine: ${chalk.yellowBright(
|
||||
srcFiles.join(', '),
|
||||
)}`,
|
||||
)
|
||||
const srcFileLookup = await Promise.all(
|
||||
srcFiles.map((f) => fileExists(join(this.sourceDir, f))),
|
||||
)
|
||||
const notFoundFiles = srcFiles.filter((_f, i) => !srcFileLookup[i])
|
||||
if (notFoundFiles.length > 0) {
|
||||
throw new TypeError(
|
||||
`Some binary files were not found: ${JSON.stringify(notFoundFiles)}`,
|
||||
)
|
||||
}
|
||||
|
||||
const outPath = this.buildUniversal[process.platform](binaryName, srcFiles)
|
||||
debug(`Produced universal binary: ${outPath}`)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import { debugFactory } from './debug'
|
||||
import { writeFileAsync, fileExists } from './utils'
|
||||
|
||||
const debug = debugFactory('update-package')
|
||||
|
||||
export async function updatePackageJson(
|
||||
path: string,
|
||||
partial: Record<string, any>,
|
||||
) {
|
||||
const exists = await fileExists(path)
|
||||
if (!exists) {
|
||||
debug(`File not exists ${path}`)
|
||||
return
|
||||
}
|
||||
const old = require(path)
|
||||
await writeFileAsync(path, JSON.stringify({ ...old, ...partial }, null, 2))
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { readFile, writeFile, copyFile, mkdir, unlink, stat } from 'fs'
|
||||
import { promisify } from 'util'
|
||||
|
||||
export const readFileAsync = promisify(readFile)
|
||||
export const writeFileAsync = promisify(writeFile)
|
||||
export const unlinkAsync = promisify(unlink)
|
||||
export const copyFileAsync = promisify(copyFile)
|
||||
export const mkdirAsync = promisify(mkdir)
|
||||
export const statAsync = promisify(stat)
|
||||
|
||||
export async function fileExists(path: string) {
|
||||
const exists = await statAsync(path)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
return exists
|
||||
}
|
||||
|
||||
export function pick<O, K extends keyof O>(o: O, ...keys: K[]): Pick<O, K> {
|
||||
return keys.reduce((acc, key) => {
|
||||
acc[key] = o[key]
|
||||
return acc
|
||||
}, {} as O)
|
||||
}
|
201
cli/src/utils/__tests__/__fixtures__/napi_type_def
Normal file
201
cli/src/utils/__tests__/__fixtures__/napi_type_def
Normal file
|
@ -0,0 +1,201 @@
|
|||
{"kind": "const", "name": "DEFAULT_COST", "js_doc": "/** This is a const */\n", "def": "export const DEFAULT_COST: number", "original_name": "DEFAULT_COST"}
|
||||
{"kind": "fn", "name": "getWords", "js_doc": "", "def": "export function getWords(): Array<string>"}
|
||||
{"kind": "fn", "name": "getNums", "js_doc": "/** Gets some numbers */\n", "def": "export function getNums(): Array<number>"}
|
||||
{"kind": "fn", "name": "sumNums", "js_doc": "", "def": "export function sumNums(nums: Array<number>): number"}
|
||||
{"kind": "fn", "name": "toJsObj", "js_doc": "", "def": "export function toJsObj(): object"}
|
||||
{"kind": "fn", "name": "getNumArr", "js_doc": "", "def": "export function getNumArr(): number[]"}
|
||||
{"kind": "fn", "name": "getNestedNumArr", "js_doc": "", "def": "export function getNestedNumArr(): number[][][]"}
|
||||
{"kind": "fn", "name": "readFileAsync", "js_doc": "", "def": "export function readFileAsync(path: string): Promise<Buffer>"}
|
||||
{"kind": "fn", "name": "asyncMultiTwo", "js_doc": "", "def": "export function asyncMultiTwo(arg: number): Promise<number>"}
|
||||
{"kind": "fn", "name": "bigintAdd", "js_doc": "", "def": "export function bigintAdd(a: bigint, b: bigint): bigint"}
|
||||
{"kind": "fn", "name": "createBigInt", "js_doc": "", "def": "export function createBigInt(): bigint"}
|
||||
{"kind": "fn", "name": "createBigIntI64", "js_doc": "", "def": "export function createBigIntI64(): bigint"}
|
||||
{"kind": "fn", "name": "bigintGetU64AsString", "js_doc": "", "def": "export function bigintGetU64AsString(bi: bigint): string"}
|
||||
{"kind": "fn", "name": "bigintFromI64", "js_doc": "", "def": "export function bigintFromI64(): bigint"}
|
||||
{"kind": "fn", "name": "bigintFromI128", "js_doc": "", "def": "export function bigintFromI128(): bigint"}
|
||||
{"kind": "fn", "name": "getCwd", "js_doc": "", "def": "export function getCwd(callback: (arg0: string) => void): void"}
|
||||
{"kind": "fn", "name": "optionEnd", "js_doc": "", "def": "export function optionEnd(callback: (arg0: string, arg1?: string | undefined | null) => void): void"}
|
||||
{"kind": "fn", "name": "optionStart", "js_doc": "", "def": "export function optionStart(callback: (arg0: string | undefined | null, arg1: string) => void): void"}
|
||||
{"kind": "fn", "name": "optionStartEnd", "js_doc": "", "def": "export function optionStartEnd(callback: (arg0: string | undefined | null, arg1: string, arg2?: string | undefined | null) => void): void"}
|
||||
{"kind": "fn", "name": "optionOnly", "js_doc": "", "def": "export function optionOnly(callback: (arg0?: string | undefined | null) => void): void"}
|
||||
{"kind": "fn", "name": "readFile", "js_doc": "/** napi = { version = 2, features = [\"serde-json\"] } */\n", "def": "export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void"}
|
||||
{"kind": "fn", "name": "returnJsFunction", "js_doc": "", "def": "export function returnJsFunction(): (...args: any[]) => any"}
|
||||
{"kind": "fn", "name": "callbackReturnPromise", "js_doc": "", "def": "export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>"}
|
||||
{"kind": "fn", "name": "captureErrorInCallback", "js_doc": "", "def": "export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void"}
|
||||
{"kind": "struct", "name": "Animal", "js_doc": "/**\n * `constructor` option for `struct` requires all fields to be public,\n * otherwise tag impl fn as constructor\n * #[napi(constructor)]\n */\n", "def": "/** Kind of animal */\nreadonly kind: Kind", "original_name": "Animal"}
|
||||
{"kind": "impl", "name": "Animal", "js_doc": "", "def": "/** This is the constructor */\n constructor(kind: Kind, name: string)\n/** This is a factory method */\nstatic withKind(kind: Kind): Animal\nget name(): string\nset name(name: string)\nget type(): Kind\nset type(kind: Kind)\n/**\n * This is a\n * multi-line comment\n * with an emoji \uD83D\uDE80\n */\n whoami(): string\n/** This is static... */\nstatic getDogKind(): Kind\n/**\n * Here are some characters and character sequences\n * that should be escaped correctly:\n * \\[]{}/\\:\"\"{\n * }\n */\n returnOtherClass(): Dog\n returnOtherClassWithCustomConstructor(): Bird\n overrideIndividualArgOnMethod(normalTy: string, overriddenTy: {n: string}): Bird"}
|
||||
{"kind": "struct", "name": "Dog", "js_doc": "", "def": "name: string\nconstructor(name: string)", "original_name": "Dog"}
|
||||
{"kind": "struct", "name": "Bird", "js_doc": "", "def": "name: string", "original_name": "Bird"}
|
||||
{"kind": "impl", "name": "Bird", "js_doc": "", "def": " constructor(name: string)\n getCount(): number\n getNameAsync(): Promise<string>"}
|
||||
{"kind": "struct", "name": "Blake2BHasher", "js_doc": "/** Smoking test for type generation */\n", "def": "", "original_name": "Blake2bHasher"}
|
||||
{"kind": "impl", "name": "Blake2BHasher", "js_doc": "", "def": "static withKey(key: Blake2bKey): Blake2BHasher"}
|
||||
{"kind": "impl", "name": "Blake2BHasher", "js_doc": "", "def": " update(data: Buffer): void"}
|
||||
{"kind": "struct", "name": "Blake2BKey", "js_doc": "", "def": "", "original_name": "Blake2bKey"}
|
||||
{"kind": "struct", "name": "Context", "js_doc": "", "def": "maybeNeed?: boolean\nbuffer: Uint8Array", "original_name": "Context"}
|
||||
{"kind": "impl", "name": "Context", "js_doc": "", "def": " constructor()\nstatic withData(data: string): Context\nstatic withBuffer(buf: Uint8Array): Context\n method(): string"}
|
||||
{"kind": "struct", "name": "AnimalWithDefaultConstructor", "js_doc": "", "def": "name: string\nkind: number\nconstructor(name: string, kind: number)", "original_name": "AnimalWithDefaultConstructor"}
|
||||
{"kind": "struct", "name": "NinjaTurtle", "js_doc": "", "def": "name: string", "original_name": "NinjaTurtle"}
|
||||
{"kind": "impl", "name": "NinjaTurtle", "js_doc": "", "def": "static isInstanceOf(value: unknown): boolean\n/** Create your ninja turtle! \uD83D\uDC22 */\nstatic newRaph(): NinjaTurtle\n getMaskColor(): string\n getName(): string\n returnThis(this: this): this"}
|
||||
{"kind": "struct", "name": "Assets", "js_doc": "", "def": "", "original_name": "JsAssets"}
|
||||
{"kind": "impl", "name": "Assets", "js_doc": "", "def": " constructor()\n get(id: number): JsAsset | null"}
|
||||
{"kind": "struct", "name": "Asset", "js_doc": "", "def": "", "original_name": "JsAsset"}
|
||||
{"kind": "impl", "name": "Asset", "js_doc": "", "def": " constructor()\nget filePath(): number"}
|
||||
{"kind": "struct", "name": "Optional", "js_doc": "", "def": "", "original_name": "Optional"}
|
||||
{"kind": "impl", "name": "Optional", "js_doc": "", "def": "static optionEnd(required: string, optional?: string | undefined | null): string\nstatic optionStart(optional: string | undefined | null, required: string): string\nstatic optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string\nstatic optionOnly(optional?: string | undefined | null): string"}
|
||||
{"kind": "interface", "name": "ObjectFieldClassInstance", "js_doc": "", "def": "bird: Bird", "original_name": "ObjectFieldClassInstance"}
|
||||
{"kind": "fn", "name": "createObjectWithClassField", "js_doc": "", "def": "export function createObjectWithClassField(): ObjectFieldClassInstance"}
|
||||
{"kind": "fn", "name": "receiveObjectWithClassField", "js_doc": "", "def": "export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird"}
|
||||
{"kind": "struct", "name": "NotWritableClass", "js_doc": "", "def": "name: string\nconstructor(name: string)", "original_name": "NotWritableClass"}
|
||||
{"kind": "impl", "name": "NotWritableClass", "js_doc": "", "def": " setName(name: string): void"}
|
||||
{"kind": "struct", "name": "CustomFinalize", "js_doc": "", "def": "", "original_name": "CustomFinalize"}
|
||||
{"kind": "impl", "name": "CustomFinalize", "js_doc": "", "def": " constructor(width: number, height: number)"}
|
||||
{"kind": "struct", "name": "Width", "js_doc": "", "def": "value: number\nconstructor(value: number)", "original_name": "Width"}
|
||||
{"kind": "fn", "name": "plusOne", "js_doc": "", "def": "export function plusOne(this: Width): number"}
|
||||
{"kind": "struct", "name": "ClassWithFactory", "js_doc": "", "def": "name: string", "original_name": "ClassWithFactory"}
|
||||
{"kind": "impl", "name": "ClassWithFactory", "js_doc": "", "def": "static withName(name: string): ClassWithFactory\n setName(name: string): this"}
|
||||
{"kind": "fn", "name": "dateToNumber", "js_doc": "", "def": "export function dateToNumber(input: Date): number"}
|
||||
{"kind": "fn", "name": "chronoDateToMillis", "js_doc": "", "def": "export function chronoDateToMillis(input: Date): number"}
|
||||
{"kind": "fn", "name": "chronoDateAdd1Minute", "js_doc": "", "def": "export function chronoDateAdd1Minute(input: Date): Date"}
|
||||
{"kind": "interface", "name": "Dates", "js_doc": "", "def": "start: Date\nend?: Date", "original_name": "Dates"}
|
||||
{"kind": "fn", "name": "eitherStringOrNumber", "js_doc": "", "def": "export function eitherStringOrNumber(input: string | number): number"}
|
||||
{"kind": "fn", "name": "returnEither", "js_doc": "", "def": "export function returnEither(input: number): string | number"}
|
||||
{"kind": "fn", "name": "either3", "js_doc": "", "def": "export function either3(input: string | number | boolean): number"}
|
||||
{"kind": "interface", "name": "Obj", "js_doc": "", "def": "v: string | number", "original_name": "Obj"}
|
||||
{"kind": "fn", "name": "either4", "js_doc": "", "def": "export function either4(input: string | number | boolean | Obj): number"}
|
||||
{"kind": "struct", "name": "JsClassForEither", "js_doc": "", "def": "", "original_name": "JsClassForEither"}
|
||||
{"kind": "impl", "name": "JsClassForEither", "js_doc": "", "def": " constructor()"}
|
||||
{"kind": "struct", "name": "AnotherClassForEither", "js_doc": "", "def": "", "original_name": "AnotherClassForEither"}
|
||||
{"kind": "impl", "name": "AnotherClassForEither", "js_doc": "", "def": " constructor()"}
|
||||
{"kind": "fn", "name": "receiveClassOrNumber", "js_doc": "", "def": "export function receiveClassOrNumber(either: number | JsClassForEither): number"}
|
||||
{"kind": "fn", "name": "receiveMutClassOrNumber", "js_doc": "", "def": "export function receiveMutClassOrNumber(either: number | JsClassForEither): number"}
|
||||
{"kind": "fn", "name": "receiveDifferentClass", "js_doc": "", "def": "export function receiveDifferentClass(either: JsClassForEither | AnotherClassForEither): number"}
|
||||
{"kind": "fn", "name": "returnEitherClass", "js_doc": "", "def": "export function returnEitherClass(input: number): number | JsClassForEither"}
|
||||
{"kind": "fn", "name": "eitherFromOption", "js_doc": "", "def": "export function eitherFromOption(): JsClassForEither | undefined"}
|
||||
{"kind": "interface", "name": "A", "js_doc": "", "def": "foo: number", "original_name": "A"}
|
||||
{"kind": "interface", "name": "B", "js_doc": "", "def": "bar: number", "original_name": "B"}
|
||||
{"kind": "interface", "name": "C", "js_doc": "", "def": "baz: number", "original_name": "C"}
|
||||
{"kind": "fn", "name": "eitherFromObjects", "js_doc": "", "def": "export function eitherFromObjects(input: A | B | C): string"}
|
||||
{"kind": "fn", "name": "eitherBoolOrFunction", "js_doc": "", "def": "export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void"}
|
||||
{"kind": "fn", "name": "promiseInEither", "js_doc": "", "def": "export function promiseInEither(input: number | Promise<number>): Promise<boolean>"}
|
||||
{"kind": "enum", "name": "Kind", "js_doc": "/** default enum values are continuos i32s start from 0 */\n", "def": "/** Barks */\nDog = 0,\n /** Kills birds */\nCat = 1,\n /** Tasty */\nDuck = 2", "original_name": "Kind"}
|
||||
{"kind": "enum", "name": "Empty", "js_doc": "", "def": "", "original_name": "Empty"}
|
||||
{"kind": "enum", "name": "CustomNumEnum", "js_doc": "/** You could break the step and for an new continuous value. */\n", "def": "One = 1,\n Two = 2,\n Three = 3,\n Four = 4,\n Six = 6,\n Eight = 8,\n Nine = 9,\n Ten = 10", "original_name": "CustomNumEnum"}
|
||||
{"kind": "fn", "name": "enumToI32", "js_doc": "", "def": "export function enumToI32(e: CustomNumEnum): number"}
|
||||
{"kind": "fn", "name": "throwError", "js_doc": "", "def": "export function throwError(): void"}
|
||||
{"kind": "fn", "name": "panic", "js_doc": "", "def": "export function panic(): void"}
|
||||
{"kind": "fn", "name": "receiveString", "js_doc": "", "def": "export function receiveString(s: string): string"}
|
||||
{"kind": "fn", "name": "customStatusCode", "js_doc": "", "def": "export function customStatusCode(): void"}
|
||||
{"kind": "fn", "name": "createExternal", "js_doc": "", "def": "export function createExternal(size: number): ExternalObject<number>"}
|
||||
{"kind": "fn", "name": "createExternalString", "js_doc": "", "def": "export function createExternalString(content: string): ExternalObject<string>"}
|
||||
{"kind": "fn", "name": "getExternal", "js_doc": "", "def": "export function getExternal(external: ExternalObject<number>): number"}
|
||||
{"kind": "fn", "name": "mutateExternal", "js_doc": "", "def": "export function mutateExternal(external: ExternalObject<number>, newVal: number): void"}
|
||||
{"kind": "fn", "name": "validateArray", "js_doc": "", "def": "export function validateArray(arr: Array<number>): number"}
|
||||
{"kind": "fn", "name": "validateBuffer", "js_doc": "", "def": "export function validateBuffer(b: Buffer): number"}
|
||||
{"kind": "fn", "name": "validateTypedArray", "js_doc": "", "def": "export function validateTypedArray(input: Uint8Array): number"}
|
||||
{"kind": "fn", "name": "validateBigint", "js_doc": "", "def": "export function validateBigint(input: bigint): bigint"}
|
||||
{"kind": "fn", "name": "validateBoolean", "js_doc": "", "def": "export function validateBoolean(i: boolean): boolean"}
|
||||
{"kind": "fn", "name": "validateDate", "js_doc": "", "def": "export function validateDate(d: Date): number"}
|
||||
{"kind": "fn", "name": "validateDateTime", "js_doc": "", "def": "export function validateDateTime(d: Date): number"}
|
||||
{"kind": "fn", "name": "validateExternal", "js_doc": "", "def": "export function validateExternal(e: ExternalObject<number>): number"}
|
||||
{"kind": "fn", "name": "validateFunction", "js_doc": "", "def": "export function validateFunction(cb: () => number): number"}
|
||||
{"kind": "fn", "name": "validateHashMap", "js_doc": "", "def": "export function validateHashMap(input: Record<string, number>): number"}
|
||||
{"kind": "fn", "name": "validateNull", "js_doc": "", "def": "export function validateNull(i: null): boolean"}
|
||||
{"kind": "fn", "name": "validateUndefined", "js_doc": "", "def": "export function validateUndefined(i: undefined): boolean"}
|
||||
{"kind": "fn", "name": "validateNumber", "js_doc": "", "def": "export function validateNumber(i: number): number"}
|
||||
{"kind": "fn", "name": "validatePromise", "js_doc": "", "def": "export function validatePromise(p: Promise<number>): Promise<number>"}
|
||||
{"kind": "fn", "name": "validateString", "js_doc": "", "def": "export function validateString(s: string): string"}
|
||||
{"kind": "fn", "name": "validateSymbol", "js_doc": "", "def": "export function validateSymbol(s: symbol): boolean"}
|
||||
{"kind": "fn", "name": "validateOptional", "js_doc": "", "def": "export function validateOptional(input1?: string | undefined | null, input2?: boolean | undefined | null): boolean"}
|
||||
{"kind": "fn", "name": "returnUndefinedIfInvalid", "js_doc": "", "def": "export function returnUndefinedIfInvalid(input: boolean): boolean"}
|
||||
{"kind": "fn", "name": "returnUndefinedIfInvalidPromise", "js_doc": "", "def": "export function returnUndefinedIfInvalidPromise(input: Promise<boolean>): Promise<boolean>"}
|
||||
{"kind": "fn", "name": "tsRename", "js_doc": "", "def": "export function tsRename(a: { foo: number }): string[]"}
|
||||
{"kind": "fn", "name": "overrideIndividualArgOnFunction", "js_doc": "", "def": "export function overrideIndividualArgOnFunction(notOverridden: string, f: () => string, notOverridden2: number): string"}
|
||||
{"kind": "fn", "name": "overrideIndividualArgOnFunctionWithCbArg", "js_doc": "", "def": "export function overrideIndividualArgOnFunctionWithCbArg(callback: (town: string, name?: string | undefined | null) => string, notOverridden: number): object"}
|
||||
{"kind": "struct", "name": "Fib", "js_doc": "", "def": "", "original_name": "Fib"}
|
||||
{"kind": "impl", "name": "Fib", "js_doc": "", "def": "[Symbol.iterator](): Iterator<number, void, number>"}
|
||||
{"kind": "impl", "name": "Fib", "js_doc": "", "def": " constructor()"}
|
||||
{"kind": "struct", "name": "Fib2", "js_doc": "", "def": "", "original_name": "Fib2"}
|
||||
{"kind": "impl", "name": "Fib2", "js_doc": "", "def": "[Symbol.iterator](): Iterator<number, void, number>"}
|
||||
{"kind": "impl", "name": "Fib2", "js_doc": "", "def": "static create(seed: number): Fib2"}
|
||||
{"kind": "struct", "name": "Fib3", "js_doc": "", "def": "current: number\nnext: number\nconstructor(current: number, next: number)", "original_name": "Fib3"}
|
||||
{"kind": "impl", "name": "Fib3", "js_doc": "", "def": "[Symbol.iterator](): Iterator<number, void, number>"}
|
||||
{"kind": "const", "name": "ALIGNMENT", "js_doc": "", "def": "export const ALIGNMENT: number", "original_name": "ALIGNMENT", "js_mod": "xxh3"}
|
||||
{"kind": "fn", "name": "xxh3_64", "js_doc": "", "def": "export function xxh3_64(input: Buffer): bigint", "js_mod": "xxh3"}
|
||||
{"kind": "fn", "name": "xxh128", "js_doc": "/** xxh128 function */\n", "def": "export function xxh128(input: Buffer): bigint", "js_mod": "xxh3"}
|
||||
{"kind": "struct", "name": "Xxh3", "js_doc": "/** Xxh3 class */\n", "def": "", "original_name": "Xxh3", "js_mod": "xxh3"}
|
||||
{"kind": "impl", "name": "Xxh3", "js_doc": "", "def": " constructor()\n/** update */\n update(input: Buffer): void\n digest(): bigint", "js_mod": "xxh3"}
|
||||
{"kind": "fn", "name": "xxh2Plus", "js_doc": "", "def": "export function xxh2Plus(a: number, b: number): number", "js_mod": "xxh2"}
|
||||
{"kind": "fn", "name": "xxh3Xxh64Alias", "js_doc": "", "def": "export function xxh3Xxh64Alias(input: Buffer): bigint", "js_mod": "xxh2"}
|
||||
{"kind": "fn", "name": "xxh64Alias", "js_doc": "", "def": "export function xxh64Alias(input: Buffer): bigint"}
|
||||
{"kind": "fn", "name": "getMapping", "js_doc": "", "def": "export function getMapping(): Record<string, number>"}
|
||||
{"kind": "fn", "name": "sumMapping", "js_doc": "", "def": "export function sumMapping(nums: Record<string, number>): number"}
|
||||
{"kind": "fn", "name": "mapOption", "js_doc": "", "def": "export function mapOption(val?: number | undefined | null): number | null"}
|
||||
{"kind": "fn", "name": "returnNull", "js_doc": "", "def": "export function returnNull(): null"}
|
||||
{"kind": "fn", "name": "returnUndefined", "js_doc": "", "def": "export function returnUndefined(): void"}
|
||||
{"kind": "fn", "name": "add", "js_doc": "", "def": "export function add(a: number, b: number): number"}
|
||||
{"kind": "fn", "name": "fibonacci", "js_doc": "", "def": "export function fibonacci(n: number): number"}
|
||||
{"kind": "fn", "name": "listObjKeys", "js_doc": "", "def": "export function listObjKeys(obj: object): Array<string>"}
|
||||
{"kind": "fn", "name": "createObj", "js_doc": "", "def": "export function createObj(): object"}
|
||||
{"kind": "fn", "name": "getGlobal", "js_doc": "", "def": "export function getGlobal(): typeof global"}
|
||||
{"kind": "fn", "name": "getUndefined", "js_doc": "", "def": "export function getUndefined(): void"}
|
||||
{"kind": "fn", "name": "getNull", "js_doc": "", "def": "export function getNull(): null"}
|
||||
{"kind": "interface", "name": "AllOptionalObject", "js_doc": "", "def": "name?: string\nage?: number", "original_name": "AllOptionalObject"}
|
||||
{"kind": "fn", "name": "receiveAllOptionalObject", "js_doc": "", "def": "export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void"}
|
||||
{"kind": "enum", "name": "ALIAS", "js_doc": "", "def": "A = 0,\n B = 1", "original_name": "AliasedEnum"}
|
||||
{"kind": "interface", "name": "AliasedStruct", "js_doc": "", "def": "a: ALIAS\nb: number", "original_name": "StructContainsAliasedEnum"}
|
||||
{"kind": "fn", "name": "fnReceivedAliased", "js_doc": "", "def": "export function fnReceivedAliased(s: AliasedStruct, e: ALIAS): void"}
|
||||
{"kind": "interface", "name": "StrictObject", "js_doc": "", "def": "name: string", "original_name": "StrictObject"}
|
||||
{"kind": "fn", "name": "receiveStrictObject", "js_doc": "", "def": "export function receiveStrictObject(strictObject: StrictObject): void"}
|
||||
{"kind": "fn", "name": "getStrFromObject", "js_doc": "", "def": "export function getStrFromObject(): void"}
|
||||
{"kind": "interface", "name": "TsTypeChanged", "js_doc": "", "def": "typeOverride: object\ntypeOverrideOptional?: object", "original_name": "TsTypeChanged"}
|
||||
{"kind": "fn", "name": "createObjWithProperty", "js_doc": "", "def": "export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }"}
|
||||
{"kind": "fn", "name": "getterFromObj", "js_doc": "", "def": "export function getterFromObj(): number"}
|
||||
{"kind": "interface", "name": "ObjectOnlyFromJs", "js_doc": "", "def": "count: number\ncallback: (err: Error | null, value: number) => any", "original_name": "ObjectOnlyFromJs"}
|
||||
{"kind": "fn", "name": "receiveObjectOnlyFromJs", "js_doc": "", "def": "export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void"}
|
||||
{"kind": "fn", "name": "asyncPlus100", "js_doc": "", "def": "export function asyncPlus100(p: Promise<number>): Promise<number>"}
|
||||
{"kind": "struct", "name": "JsRepo", "js_doc": "", "def": "", "original_name": "JsRepo"}
|
||||
{"kind": "impl", "name": "JsRepo", "js_doc": "", "def": " constructor(dir: string)\n remote(): JsRemote"}
|
||||
{"kind": "struct", "name": "JsRemote", "js_doc": "", "def": "", "original_name": "JsRemote"}
|
||||
{"kind": "impl", "name": "JsRemote", "js_doc": "", "def": " name(): string"}
|
||||
{"kind": "struct", "name": "CssRuleList", "js_doc": "", "def": "", "original_name": "CSSRuleList"}
|
||||
{"kind": "impl", "name": "CssRuleList", "js_doc": "", "def": " getRules(): Array<string>\nget parentStyleSheet(): CSSStyleSheet\nget name(): string | null"}
|
||||
{"kind": "struct", "name": "CssStyleSheet", "js_doc": "", "def": "", "original_name": "CSSStyleSheet"}
|
||||
{"kind": "struct", "name": "AnotherCssStyleSheet", "js_doc": "", "def": "", "original_name": "AnotherCSSStyleSheet"}
|
||||
{"kind": "impl", "name": "AnotherCssStyleSheet", "js_doc": "", "def": "get rules(): CssRuleList"}
|
||||
{"kind": "impl", "name": "CssStyleSheet", "js_doc": "", "def": " constructor(name: string, rules: Array<string>)\nget rules(): CssRuleList\n anotherCssStyleSheet(): AnotherCssStyleSheet"}
|
||||
{"kind": "interface", "name": "PackageJson", "js_doc": "/** This is an interface for package.json */\n", "def": "name: string\n/** The version of the package */\nversion: string\ndependencies?: Record<string, any>\ndevDependencies?: Record<string, any>", "original_name": "PackageJson"}
|
||||
{"kind": "fn", "name": "readPackageJson", "js_doc": "", "def": "export function readPackageJson(): PackageJson"}
|
||||
{"kind": "fn", "name": "getPackageJsonName", "js_doc": "", "def": "export function getPackageJsonName(packageJson: PackageJson): string"}
|
||||
{"kind": "fn", "name": "testSerdeRoundtrip", "js_doc": "", "def": "export function testSerdeRoundtrip(data: any): any"}
|
||||
{"kind": "fn", "name": "contains", "js_doc": "", "def": "export function contains(source: string, target: string): boolean"}
|
||||
{"kind": "fn", "name": "concatStr", "js_doc": "", "def": "export function concatStr(s: string): string"}
|
||||
{"kind": "fn", "name": "concatUtf16", "js_doc": "", "def": "export function concatUtf16(s: string): string"}
|
||||
{"kind": "fn", "name": "concatLatin1", "js_doc": "", "def": "export function concatLatin1(s: string): string"}
|
||||
{"kind": "fn", "name": "roundtripStr", "js_doc": "", "def": "export function roundtripStr(s: string): string"}
|
||||
{"kind": "fn", "name": "setSymbolInObj", "js_doc": "", "def": "export function setSymbolInObj(symbol: symbol): object"}
|
||||
{"kind": "fn", "name": "createSymbol", "js_doc": "", "def": "export function createSymbol(): symbol"}
|
||||
{"kind": "impl", "name": "DelaySum", "js_doc": "", "def": ""}
|
||||
{"kind": "fn", "name": "withoutAbortController", "js_doc": "", "def": "export function withoutAbortController(a: number, b: number): Promise<number>"}
|
||||
{"kind": "fn", "name": "withAbortController", "js_doc": "", "def": "export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>"}
|
||||
{"kind": "fn", "name": "callThreadsafeFunction", "js_doc": "", "def": "export function callThreadsafeFunction(callback: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "threadsafeFunctionThrowError", "js_doc": "", "def": "export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "threadsafeFunctionFatalMode", "js_doc": "", "def": "export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "threadsafeFunctionFatalModeError", "js_doc": "", "def": "export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "threadsafeFunctionClosureCapture", "js_doc": "", "def": "export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "tsfnCallWithCallback", "js_doc": "", "def": "export function tsfnCallWithCallback(func: (...args: any[]) => any): void"}
|
||||
{"kind": "fn", "name": "tsfnAsyncCall", "js_doc": "", "def": "export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>"}
|
||||
{"kind": "fn", "name": "acceptThreadsafeFunction", "js_doc": "", "def": "export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void"}
|
||||
{"kind": "fn", "name": "acceptThreadsafeFunctionFatal", "js_doc": "", "def": "export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void"}
|
||||
{"kind": "fn", "name": "acceptThreadsafeFunctionTupleArgs", "js_doc": "", "def": "export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void"}
|
||||
{"kind": "fn", "name": "getBuffer", "js_doc": "", "def": "export function getBuffer(): Buffer"}
|
||||
{"kind": "fn", "name": "appendBuffer", "js_doc": "", "def": "export function appendBuffer(buf: Buffer): Buffer"}
|
||||
{"kind": "fn", "name": "getEmptyBuffer", "js_doc": "", "def": "export function getEmptyBuffer(): Buffer"}
|
||||
{"kind": "fn", "name": "convertU32Array", "js_doc": "", "def": "export function convertU32Array(input: Uint32Array): Array<number>"}
|
||||
{"kind": "fn", "name": "createExternalTypedArray", "js_doc": "", "def": "export function createExternalTypedArray(): Uint32Array"}
|
||||
{"kind": "fn", "name": "mutateTypedArray", "js_doc": "", "def": "export function mutateTypedArray(input: Float32Array): void"}
|
||||
{"kind": "fn", "name": "derefUint8Array", "js_doc": "", "def": "export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number"}
|
||||
{"kind": "fn", "name": "bufferPassThrough", "js_doc": "", "def": "export function bufferPassThrough(buf: Buffer): Promise<Buffer>"}
|
||||
{"kind": "fn", "name": "arrayBufferPassThrough", "js_doc": "", "def": "export function arrayBufferPassThrough(buf: Uint8Array): Promise<Uint8Array>"}
|
||||
{"kind": "impl", "name": "AsyncBuffer", "js_doc": "", "def": ""}
|
||||
{"kind": "fn", "name": "asyncReduceBuffer", "js_doc": "", "def": "export function asyncReduceBuffer(buf: Buffer): Promise<number>"}
|
||||
{"kind": "fn", "name": "runScript", "js_doc": "", "def": "export function runScript(script: string): unknown"}
|
110
cli/src/utils/__tests__/__snapshots__/target.spec.ts.md
Normal file
110
cli/src/utils/__tests__/__snapshots__/target.spec.ts.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
# Snapshot report for `src/utils/__tests__/target.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `target.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should parse triple correctly
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
abi: null,
|
||||
arch: 'arm64',
|
||||
platform: 'darwin',
|
||||
platformArchABI: 'darwin-arm64',
|
||||
triple: 'aarch64-apple-darwin',
|
||||
},
|
||||
{
|
||||
abi: null,
|
||||
arch: 'arm64',
|
||||
platform: 'android',
|
||||
platformArchABI: 'android-arm64',
|
||||
triple: 'aarch64-linux-android',
|
||||
},
|
||||
{
|
||||
abi: 'gnu',
|
||||
arch: 'arm64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-arm64-gnu',
|
||||
triple: 'aarch64-unknown-linux-gnu',
|
||||
},
|
||||
{
|
||||
abi: 'musl',
|
||||
arch: 'arm64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-arm64-musl',
|
||||
triple: 'aarch64-unknown-linux-musl',
|
||||
},
|
||||
{
|
||||
abi: 'msvc',
|
||||
arch: 'arm64',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-arm64-msvc',
|
||||
triple: 'aarch64-pc-windows-msvc',
|
||||
},
|
||||
{
|
||||
abi: null,
|
||||
arch: 'x64',
|
||||
platform: 'darwin',
|
||||
platformArchABI: 'darwin-x64',
|
||||
triple: 'x86_64-apple-darwin',
|
||||
},
|
||||
{
|
||||
abi: 'msvc',
|
||||
arch: 'x64',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-x64-msvc',
|
||||
triple: 'x86_64-pc-windows-msvc',
|
||||
},
|
||||
{
|
||||
abi: 'gnu',
|
||||
arch: 'x64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-x64-gnu',
|
||||
triple: 'x86_64-unknown-linux-gnu',
|
||||
},
|
||||
{
|
||||
abi: 'musl',
|
||||
arch: 'x64',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-x64-musl',
|
||||
triple: 'x86_64-unknown-linux-musl',
|
||||
},
|
||||
{
|
||||
abi: null,
|
||||
arch: 'x64',
|
||||
platform: 'freebsd',
|
||||
platformArchABI: 'freebsd-x64',
|
||||
triple: 'x86_64-unknown-freebsd',
|
||||
},
|
||||
{
|
||||
abi: 'msvc',
|
||||
arch: 'ia32',
|
||||
platform: 'win32',
|
||||
platformArchABI: 'win32-ia32-msvc',
|
||||
triple: 'i686-pc-windows-msvc',
|
||||
},
|
||||
{
|
||||
abi: 'gnueabihf',
|
||||
arch: 'arm',
|
||||
platform: 'linux',
|
||||
platformArchABI: 'linux-arm-gnueabihf',
|
||||
triple: 'armv7-unknown-linux-gnueabihf',
|
||||
},
|
||||
{
|
||||
abi: 'eabi',
|
||||
arch: 'arm',
|
||||
platform: 'android',
|
||||
platformArchABI: 'android-arm-eabi',
|
||||
triple: 'armv7-linux-androideabi',
|
||||
},
|
||||
{
|
||||
abi: null,
|
||||
arch: 'universal',
|
||||
platform: 'darwin',
|
||||
platformArchABI: 'darwin-universal',
|
||||
triple: 'universal-apple-darwin',
|
||||
},
|
||||
]
|
BIN
cli/src/utils/__tests__/__snapshots__/target.spec.ts.snap
Normal file
BIN
cli/src/utils/__tests__/__snapshots__/target.spec.ts.snap
Normal file
Binary file not shown.
618
cli/src/utils/__tests__/__snapshots__/typegen.spec.ts.md
Normal file
618
cli/src/utils/__tests__/__snapshots__/typegen.spec.ts.md
Normal file
|
@ -0,0 +1,618 @@
|
|||
# Snapshot report for `src/utils/__tests__/typegen.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `typegen.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should ident string correctly
|
||||
|
||||
> original ident is 0
|
||||
|
||||
`␊
|
||||
/**␊
|
||||
* should keep␊
|
||||
* class A {␊
|
||||
* foo = () => {}␊
|
||||
* bar = () => {}␊
|
||||
* }␊
|
||||
*/␊
|
||||
class A {␊
|
||||
foo() {␊
|
||||
a = b␊
|
||||
}␊
|
||||
␊
|
||||
bar = () => {␊
|
||||
␊
|
||||
}␊
|
||||
boz = 1␊
|
||||
}␊
|
||||
␊
|
||||
namespace B {␊
|
||||
namespace C {␊
|
||||
type D = A␊
|
||||
}␊
|
||||
}␊
|
||||
`
|
||||
|
||||
> original ident is 2
|
||||
|
||||
`␊
|
||||
/**␊
|
||||
* should keep␊
|
||||
* class A {␊
|
||||
* foo = () => {}␊
|
||||
* bar = () => {}␊
|
||||
* }␊
|
||||
*/␊
|
||||
class A {␊
|
||||
foo() {␊
|
||||
a = b␊
|
||||
}␊
|
||||
␊
|
||||
bar = () => {␊
|
||||
␊
|
||||
}␊
|
||||
boz = 1␊
|
||||
}␊
|
||||
␊
|
||||
namespace B {␊
|
||||
namespace C {␊
|
||||
type D = A␊
|
||||
}␊
|
||||
}␊
|
||||
`
|
||||
|
||||
## should process type def correctly
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
`␊
|
||||
export class ExternalObject<T> {␊
|
||||
readonly '': {␊
|
||||
readonly '': unique symbol␊
|
||||
[K: symbol]: T␊
|
||||
}␊
|
||||
}␊
|
||||
/**␊
|
||||
* \`constructor\` option for \`struct\` requires all fields to be public,␊
|
||||
* otherwise tag impl fn as constructor␊
|
||||
* #[napi(constructor)]␊
|
||||
*/␊
|
||||
export class Animal {␊
|
||||
/** Kind of animal */␊
|
||||
readonly kind: Kind␊
|
||||
/** This is the constructor */␊
|
||||
constructor(kind: Kind, name: string)␊
|
||||
/** This is a factory method */␊
|
||||
static withKind(kind: Kind): Animal␊
|
||||
get name(): string␊
|
||||
set name(name: string)␊
|
||||
get type(): Kind␊
|
||||
set type(kind: Kind)␊
|
||||
/**␊
|
||||
* This is a␊
|
||||
* multi-line comment␊
|
||||
* with an emoji 🚀␊
|
||||
*/␊
|
||||
whoami(): string␊
|
||||
/** This is static... */␊
|
||||
static getDogKind(): Kind␊
|
||||
/**␊
|
||||
* Here are some characters and character sequences␊
|
||||
* that should be escaped correctly:␊
|
||||
* \\[]{}/\\:""{␊
|
||||
* }␊
|
||||
*/␊
|
||||
returnOtherClass(): Dog␊
|
||||
returnOtherClassWithCustomConstructor(): Bird␊
|
||||
overrideIndividualArgOnMethod(normalTy: string, overriddenTy: {n: string}): Bird␊
|
||||
}␊
|
||||
␊
|
||||
export class AnimalWithDefaultConstructor {␊
|
||||
name: string␊
|
||||
kind: number␊
|
||||
constructor(name: string, kind: number)␊
|
||||
}␊
|
||||
␊
|
||||
export class AnotherClassForEither {␊
|
||||
constructor()␊
|
||||
}␊
|
||||
␊
|
||||
export class AnotherCssStyleSheet {␊
|
||||
get rules(): CssRuleList␊
|
||||
}␊
|
||||
export type AnotherCSSStyleSheet = AnotherCssStyleSheet␊
|
||||
␊
|
||||
export class Asset {␊
|
||||
constructor()␊
|
||||
get filePath(): number␊
|
||||
}␊
|
||||
export type JsAsset = Asset␊
|
||||
␊
|
||||
export class Assets {␊
|
||||
constructor()␊
|
||||
get(id: number): JsAsset | null␊
|
||||
}␊
|
||||
export type JsAssets = Assets␊
|
||||
␊
|
||||
export class Bird {␊
|
||||
name: string␊
|
||||
constructor(name: string)␊
|
||||
getCount(): number␊
|
||||
getNameAsync(): Promise<string>␊
|
||||
}␊
|
||||
␊
|
||||
/** Smoking test for type generation */␊
|
||||
export class Blake2BHasher {␊
|
||||
static withKey(key: Blake2bKey): Blake2BHasher␊
|
||||
update(data: Buffer): void␊
|
||||
}␊
|
||||
export type Blake2bHasher = Blake2BHasher␊
|
||||
␊
|
||||
export class Blake2BKey {␊
|
||||
␊
|
||||
}␊
|
||||
export type Blake2bKey = Blake2BKey␊
|
||||
␊
|
||||
export class ClassWithFactory {␊
|
||||
name: string␊
|
||||
static withName(name: string): ClassWithFactory␊
|
||||
setName(name: string): this␊
|
||||
}␊
|
||||
␊
|
||||
export class Context {␊
|
||||
maybeNeed?: boolean␊
|
||||
buffer: Uint8Array␊
|
||||
constructor()␊
|
||||
static withData(data: string): Context␊
|
||||
static withBuffer(buf: Uint8Array): Context␊
|
||||
method(): string␊
|
||||
}␊
|
||||
␊
|
||||
export class CssRuleList {␊
|
||||
getRules(): Array<string>␊
|
||||
get parentStyleSheet(): CSSStyleSheet␊
|
||||
get name(): string | null␊
|
||||
}␊
|
||||
export type CSSRuleList = CssRuleList␊
|
||||
␊
|
||||
export class CssStyleSheet {␊
|
||||
constructor(name: string, rules: Array<string>)␊
|
||||
get rules(): CssRuleList␊
|
||||
anotherCssStyleSheet(): AnotherCssStyleSheet␊
|
||||
}␊
|
||||
export type CSSStyleSheet = CssStyleSheet␊
|
||||
␊
|
||||
export class CustomFinalize {␊
|
||||
constructor(width: number, height: number)␊
|
||||
}␊
|
||||
␊
|
||||
export class Dog {␊
|
||||
name: string␊
|
||||
constructor(name: string)␊
|
||||
}␊
|
||||
␊
|
||||
export class Fib {␊
|
||||
[Symbol.iterator](): Iterator<number, void, number>␊
|
||||
constructor()␊
|
||||
}␊
|
||||
␊
|
||||
export class Fib2 {␊
|
||||
[Symbol.iterator](): Iterator<number, void, number>␊
|
||||
static create(seed: number): Fib2␊
|
||||
}␊
|
||||
␊
|
||||
export class Fib3 {␊
|
||||
current: number␊
|
||||
next: number␊
|
||||
constructor(current: number, next: number)␊
|
||||
[Symbol.iterator](): Iterator<number, void, number>␊
|
||||
}␊
|
||||
␊
|
||||
export class JsClassForEither {␊
|
||||
constructor()␊
|
||||
}␊
|
||||
␊
|
||||
export class JsRemote {␊
|
||||
name(): string␊
|
||||
}␊
|
||||
␊
|
||||
export class JsRepo {␊
|
||||
constructor(dir: string)␊
|
||||
remote(): JsRemote␊
|
||||
}␊
|
||||
␊
|
||||
export class NinjaTurtle {␊
|
||||
name: string␊
|
||||
static isInstanceOf(value: unknown): boolean␊
|
||||
/** Create your ninja turtle! 🐢 */␊
|
||||
static newRaph(): NinjaTurtle␊
|
||||
getMaskColor(): string␊
|
||||
getName(): string␊
|
||||
returnThis(this: this): this␊
|
||||
}␊
|
||||
␊
|
||||
export class NotWritableClass {␊
|
||||
name: string␊
|
||||
constructor(name: string)␊
|
||||
setName(name: string): void␊
|
||||
}␊
|
||||
␊
|
||||
export class Optional {␊
|
||||
static optionEnd(required: string, optional?: string | undefined | null): string␊
|
||||
static optionStart(optional: string | undefined | null, required: string): string␊
|
||||
static optionStartEnd(optional1: string | undefined | null, required: string, optional2?: string | undefined | null): string␊
|
||||
static optionOnly(optional?: string | undefined | null): string␊
|
||||
}␊
|
||||
␊
|
||||
export class Width {␊
|
||||
value: number␊
|
||||
constructor(value: number)␊
|
||||
}␊
|
||||
␊
|
||||
export interface A {␊
|
||||
foo: number␊
|
||||
}␊
|
||||
␊
|
||||
export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void␊
|
||||
␊
|
||||
export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void␊
|
||||
␊
|
||||
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊
|
||||
␊
|
||||
export function add(a: number, b: number): number␊
|
||||
␊
|
||||
export const enum ALIAS {␊
|
||||
A = 0,␊
|
||||
B = 1␊
|
||||
}␊
|
||||
␊
|
||||
export interface AliasedStruct {␊
|
||||
a: ALIAS␊
|
||||
b: number␊
|
||||
}␊
|
||||
␊
|
||||
export interface AllOptionalObject {␊
|
||||
name?: string␊
|
||||
age?: number␊
|
||||
}␊
|
||||
␊
|
||||
export function appendBuffer(buf: Buffer): Buffer␊
|
||||
␊
|
||||
export function arrayBufferPassThrough(buf: Uint8Array): Promise<Uint8Array>␊
|
||||
␊
|
||||
export function asyncMultiTwo(arg: number): Promise<number>␊
|
||||
␊
|
||||
export function asyncPlus100(p: Promise<number>): Promise<number>␊
|
||||
␊
|
||||
export function asyncReduceBuffer(buf: Buffer): Promise<number>␊
|
||||
␊
|
||||
export interface B {␊
|
||||
bar: number␊
|
||||
}␊
|
||||
␊
|
||||
export function bigintAdd(a: bigint, b: bigint): bigint␊
|
||||
␊
|
||||
export function bigintFromI128(): bigint␊
|
||||
␊
|
||||
export function bigintFromI64(): bigint␊
|
||||
␊
|
||||
export function bigintGetU64AsString(bi: bigint): string␊
|
||||
␊
|
||||
export function bufferPassThrough(buf: Buffer): Promise<Buffer>␊
|
||||
␊
|
||||
export interface C {␊
|
||||
baz: number␊
|
||||
}␊
|
||||
␊
|
||||
export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>␊
|
||||
␊
|
||||
export function callThreadsafeFunction(callback: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊
|
||||
␊
|
||||
export function chronoDateAdd1Minute(input: Date): Date␊
|
||||
␊
|
||||
export function chronoDateToMillis(input: Date): number␊
|
||||
␊
|
||||
export function concatLatin1(s: string): string␊
|
||||
␊
|
||||
export function concatStr(s: string): string␊
|
||||
␊
|
||||
export function concatUtf16(s: string): string␊
|
||||
␊
|
||||
export function contains(source: string, target: string): boolean␊
|
||||
␊
|
||||
export function convertU32Array(input: Uint32Array): Array<number>␊
|
||||
␊
|
||||
export function createBigInt(): bigint␊
|
||||
␊
|
||||
export function createBigIntI64(): bigint␊
|
||||
␊
|
||||
export function createExternal(size: number): ExternalObject<number>␊
|
||||
␊
|
||||
export function createExternalString(content: string): ExternalObject<string>␊
|
||||
␊
|
||||
export function createExternalTypedArray(): Uint32Array␊
|
||||
␊
|
||||
export function createObj(): object␊
|
||||
␊
|
||||
export function createObjectWithClassField(): ObjectFieldClassInstance␊
|
||||
␊
|
||||
export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }␊
|
||||
␊
|
||||
export function createSymbol(): symbol␊
|
||||
␊
|
||||
/** You could break the step and for an new continuous value. */␊
|
||||
export const enum CustomNumEnum {␊
|
||||
One = 1,␊
|
||||
Two = 2,␊
|
||||
Three = 3,␊
|
||||
Four = 4,␊
|
||||
Six = 6,␊
|
||||
Eight = 8,␊
|
||||
Nine = 9,␊
|
||||
Ten = 10␊
|
||||
}␊
|
||||
␊
|
||||
export function customStatusCode(): void␊
|
||||
␊
|
||||
export interface Dates {␊
|
||||
start: Date␊
|
||||
end?: Date␊
|
||||
}␊
|
||||
␊
|
||||
export function dateToNumber(input: Date): number␊
|
||||
␊
|
||||
/** This is a const */␊
|
||||
export const DEFAULT_COST: number␊
|
||||
␊
|
||||
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊
|
||||
␊
|
||||
export function either3(input: string | number | boolean): number␊
|
||||
␊
|
||||
export function either4(input: string | number | boolean | Obj): number␊
|
||||
␊
|
||||
export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void␊
|
||||
␊
|
||||
export function eitherFromObjects(input: A | B | C): string␊
|
||||
␊
|
||||
export function eitherFromOption(): JsClassForEither | undefined␊
|
||||
␊
|
||||
export function eitherStringOrNumber(input: string | number): number␊
|
||||
␊
|
||||
export const enum Empty {␊
|
||||
␊
|
||||
}␊
|
||||
␊
|
||||
export function enumToI32(e: CustomNumEnum): number␊
|
||||
␊
|
||||
export function fibonacci(n: number): number␊
|
||||
␊
|
||||
export function fnReceivedAliased(s: AliasedStruct, e: ALIAS): void␊
|
||||
␊
|
||||
export function getBuffer(): Buffer␊
|
||||
␊
|
||||
export function getCwd(callback: (arg0: string) => void): void␊
|
||||
␊
|
||||
export function getEmptyBuffer(): Buffer␊
|
||||
␊
|
||||
export function getExternal(external: ExternalObject<number>): number␊
|
||||
␊
|
||||
export function getGlobal(): typeof global␊
|
||||
␊
|
||||
export function getMapping(): Record<string, number>␊
|
||||
␊
|
||||
export function getNestedNumArr(): number[][][]␊
|
||||
␊
|
||||
export function getNull(): null␊
|
||||
␊
|
||||
export function getNumArr(): number[]␊
|
||||
␊
|
||||
/** Gets some numbers */␊
|
||||
export function getNums(): Array<number>␊
|
||||
␊
|
||||
export function getPackageJsonName(packageJson: PackageJson): string␊
|
||||
␊
|
||||
export function getStrFromObject(): void␊
|
||||
␊
|
||||
export function getterFromObj(): number␊
|
||||
␊
|
||||
export function getUndefined(): void␊
|
||||
␊
|
||||
export function getWords(): Array<string>␊
|
||||
␊
|
||||
/** default enum values are continuos i32s start from 0 */␊
|
||||
export const enum Kind {␊
|
||||
/** Barks */␊
|
||||
Dog = 0,␊
|
||||
/** Kills birds */␊
|
||||
Cat = 1,␊
|
||||
/** Tasty */␊
|
||||
Duck = 2␊
|
||||
}␊
|
||||
␊
|
||||
export function listObjKeys(obj: object): Array<string>␊
|
||||
␊
|
||||
export function mapOption(val?: number | undefined | null): number | null␊
|
||||
␊
|
||||
export function mutateExternal(external: ExternalObject<number>, newVal: number): void␊
|
||||
␊
|
||||
export function mutateTypedArray(input: Float32Array): void␊
|
||||
␊
|
||||
export interface Obj {␊
|
||||
v: string | number␊
|
||||
}␊
|
||||
␊
|
||||
export interface ObjectFieldClassInstance {␊
|
||||
bird: Bird␊
|
||||
}␊
|
||||
␊
|
||||
export interface ObjectOnlyFromJs {␊
|
||||
count: number␊
|
||||
callback: (err: Error | null, value: number) => any␊
|
||||
}␊
|
||||
␊
|
||||
export function optionEnd(callback: (arg0: string, arg1?: string | undefined | null) => void): void␊
|
||||
␊
|
||||
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊
|
||||
␊
|
||||
export function optionStart(callback: (arg0: string | undefined | null, arg1: string) => void): void␊
|
||||
␊
|
||||
export function optionStartEnd(callback: (arg0: string | undefined | null, arg1: string, arg2?: string | undefined | null) => void): void␊
|
||||
␊
|
||||
export function overrideIndividualArgOnFunction(notOverridden: string, f: () => string, notOverridden2: number): string␊
|
||||
␊
|
||||
export function overrideIndividualArgOnFunctionWithCbArg(callback: (town: string, name?: string | undefined | null) => string, notOverridden: number): object␊
|
||||
␊
|
||||
/** This is an interface for package.json */␊
|
||||
export interface PackageJson {␊
|
||||
name: string␊
|
||||
/** The version of the package */␊
|
||||
version: string␊
|
||||
dependencies?: Record<string, any>␊
|
||||
devDependencies?: Record<string, any>␊
|
||||
}␊
|
||||
␊
|
||||
export function panic(): void␊
|
||||
␊
|
||||
export function plusOne(this: Width): number␊
|
||||
␊
|
||||
export function promiseInEither(input: number | Promise<number>): Promise<boolean>␊
|
||||
␊
|
||||
/** napi = { version = 2, features = ["serde-json"] } */␊
|
||||
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
|
||||
␊
|
||||
export function readFileAsync(path: string): Promise<Buffer>␊
|
||||
␊
|
||||
export function readPackageJson(): PackageJson␊
|
||||
␊
|
||||
export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void␊
|
||||
␊
|
||||
export function receiveClassOrNumber(either: number | JsClassForEither): number␊
|
||||
␊
|
||||
export function receiveDifferentClass(either: JsClassForEither | AnotherClassForEither): number␊
|
||||
␊
|
||||
export function receiveMutClassOrNumber(either: number | JsClassForEither): number␊
|
||||
␊
|
||||
export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void␊
|
||||
␊
|
||||
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊
|
||||
␊
|
||||
export function receiveStrictObject(strictObject: StrictObject): void␊
|
||||
␊
|
||||
export function receiveString(s: string): string␊
|
||||
␊
|
||||
export function returnEither(input: number): string | number␊
|
||||
␊
|
||||
export function returnEitherClass(input: number): number | JsClassForEither␊
|
||||
␊
|
||||
export function returnJsFunction(): (...args: any[]) => any␊
|
||||
␊
|
||||
export function returnNull(): null␊
|
||||
␊
|
||||
export function returnUndefined(): void␊
|
||||
␊
|
||||
export function returnUndefinedIfInvalid(input: boolean): boolean␊
|
||||
␊
|
||||
export function returnUndefinedIfInvalidPromise(input: Promise<boolean>): Promise<boolean>␊
|
||||
␊
|
||||
export function roundtripStr(s: string): string␊
|
||||
␊
|
||||
export function runScript(script: string): unknown␊
|
||||
␊
|
||||
export function setSymbolInObj(symbol: symbol): object␊
|
||||
␊
|
||||
export interface StrictObject {␊
|
||||
name: string␊
|
||||
}␊
|
||||
␊
|
||||
export function sumMapping(nums: Record<string, number>): number␊
|
||||
␊
|
||||
export function sumNums(nums: Array<number>): number␊
|
||||
␊
|
||||
export function testSerdeRoundtrip(data: any): any␊
|
||||
␊
|
||||
export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function throwError(): void␊
|
||||
␊
|
||||
export function toJsObj(): object␊
|
||||
␊
|
||||
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>␊
|
||||
␊
|
||||
export function tsfnCallWithCallback(func: (...args: any[]) => any): void␊
|
||||
␊
|
||||
export function tsRename(a: { foo: number }): string[]␊
|
||||
␊
|
||||
export interface TsTypeChanged {␊
|
||||
typeOverride: object␊
|
||||
typeOverrideOptional?: object␊
|
||||
}␊
|
||||
␊
|
||||
export function validateArray(arr: Array<number>): number␊
|
||||
␊
|
||||
export function validateBigint(input: bigint): bigint␊
|
||||
␊
|
||||
export function validateBoolean(i: boolean): boolean␊
|
||||
␊
|
||||
export function validateBuffer(b: Buffer): number␊
|
||||
␊
|
||||
export function validateDate(d: Date): number␊
|
||||
␊
|
||||
export function validateDateTime(d: Date): number␊
|
||||
␊
|
||||
export function validateExternal(e: ExternalObject<number>): number␊
|
||||
␊
|
||||
export function validateFunction(cb: () => number): number␊
|
||||
␊
|
||||
export function validateHashMap(input: Record<string, number>): number␊
|
||||
␊
|
||||
export function validateNull(i: null): boolean␊
|
||||
␊
|
||||
export function validateNumber(i: number): number␊
|
||||
␊
|
||||
export function validateOptional(input1?: string | undefined | null, input2?: boolean | undefined | null): boolean␊
|
||||
␊
|
||||
export function validatePromise(p: Promise<number>): Promise<number>␊
|
||||
␊
|
||||
export function validateString(s: string): string␊
|
||||
␊
|
||||
export function validateSymbol(s: symbol): boolean␊
|
||||
␊
|
||||
export function validateTypedArray(input: Uint8Array): number␊
|
||||
␊
|
||||
export function validateUndefined(i: undefined): boolean␊
|
||||
␊
|
||||
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>␊
|
||||
␊
|
||||
export function withoutAbortController(a: number, b: number): Promise<number>␊
|
||||
␊
|
||||
export function xxh64Alias(input: Buffer): bigint␊
|
||||
␊
|
||||
export namespace xxh2 {␊
|
||||
export function xxh2Plus(a: number, b: number): number␊
|
||||
export function xxh3Xxh64Alias(input: Buffer): bigint␊
|
||||
}␊
|
||||
␊
|
||||
export namespace xxh3 {␊
|
||||
/** Xxh3 class */␊
|
||||
export class Xxh3 {␊
|
||||
constructor()␊
|
||||
/** update */␊
|
||||
update(input: Buffer): void␊
|
||||
digest(): bigint␊
|
||||
}␊
|
||||
export const ALIGNMENT: number␊
|
||||
/** xxh128 function */␊
|
||||
export function xxh128(input: Buffer): bigint␊
|
||||
export function xxh3_64(input: Buffer): bigint␊
|
||||
}␊
|
||||
␊
|
||||
`
|
BIN
cli/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap
Normal file
BIN
cli/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap
Normal file
Binary file not shown.
20
cli/src/utils/__tests__/__snapshots__/version.spec.ts.md
Normal file
20
cli/src/utils/__tests__/__snapshots__/version.spec.ts.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Snapshot report for `src/utils/__tests__/version.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `version.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should generate correct napi engine requirement
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
'>= 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',
|
||||
]
|
BIN
cli/src/utils/__tests__/__snapshots__/version.spec.ts.snap
Normal file
BIN
cli/src/utils/__tests__/__snapshots__/version.spec.ts.snap
Normal file
Binary file not shown.
19
cli/src/utils/__tests__/target.spec.ts
Normal file
19
cli/src/utils/__tests__/target.spec.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import os from 'os'
|
||||
|
||||
import test from 'ava'
|
||||
|
||||
import {
|
||||
parseTriple,
|
||||
getSystemDefaultTarget,
|
||||
AVAILABLE_TARGETS,
|
||||
} from '../target.js'
|
||||
|
||||
test('should parse triple correctly', (t) => {
|
||||
t.snapshot(AVAILABLE_TARGETS.map(parseTriple))
|
||||
})
|
||||
|
||||
test('should get system default target correctly', (t) => {
|
||||
const target = getSystemDefaultTarget()
|
||||
|
||||
t.is(target.platform, os.platform())
|
||||
})
|
49
cli/src/utils/__tests__/typegen.spec.ts
Normal file
49
cli/src/utils/__tests__/typegen.spec.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import test from 'ava'
|
||||
|
||||
import { correctStringIdent, processTypeDef } from '../typegen.js'
|
||||
|
||||
test('should ident string correctly', (t) => {
|
||||
const input = `
|
||||
/**
|
||||
* should keep
|
||||
* class A {
|
||||
* foo = () => {}
|
||||
* bar = () => {}
|
||||
* }
|
||||
*/
|
||||
class A {
|
||||
foo() {
|
||||
a = b
|
||||
}
|
||||
|
||||
bar = () => {
|
||||
|
||||
}
|
||||
boz = 1
|
||||
}
|
||||
|
||||
namespace B {
|
||||
namespace C {
|
||||
type D = A
|
||||
}
|
||||
}
|
||||
`
|
||||
t.snapshot(correctStringIdent(input, 0), 'original ident is 0')
|
||||
t.snapshot(correctStringIdent(input, 2), 'original ident is 2')
|
||||
})
|
||||
|
||||
test('should process type def correctly', async (t) => {
|
||||
const dts = await processTypeDef(
|
||||
join(
|
||||
fileURLToPath(import.meta.url),
|
||||
'../',
|
||||
'__fixtures__',
|
||||
'napi_type_def',
|
||||
),
|
||||
)
|
||||
|
||||
t.snapshot(dts)
|
||||
})
|
13
cli/src/utils/__tests__/version.spec.ts
Normal file
13
cli/src/utils/__tests__/version.spec.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import test from 'ava'
|
||||
|
||||
import { napiEngineRequirement, NapiVersion } from '../version.js'
|
||||
|
||||
test('should generate correct napi engine requirement', (t) => {
|
||||
t.snapshot(
|
||||
(
|
||||
Object.values(NapiVersion).filter(
|
||||
(v) => typeof v === 'number',
|
||||
) as NapiVersion[]
|
||||
).map(napiEngineRequirement),
|
||||
)
|
||||
})
|
35
cli/src/utils/cargo.ts
Normal file
35
cli/src/utils/cargo.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { execSync } from 'child_process'
|
||||
|
||||
import { debug } from './log.js'
|
||||
|
||||
export function tryInstallCargoBinary(name: string, bin: string) {
|
||||
if (detectCargoBinary(bin)) {
|
||||
debug('Cargo binary already installed: %s', name)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
debug('Installing cargo binary: %s', name)
|
||||
execSync(`cargo install ${name}`, {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to install cargo binary: ${name}`, {
|
||||
cause: e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function detectCargoBinary(bin: string) {
|
||||
debug('Detecting cargo binary: %s', bin)
|
||||
try {
|
||||
execSync(`cargo help ${bin}`, {
|
||||
stdio: 'ignore',
|
||||
})
|
||||
debug('Cargo binary detected: %s', bin)
|
||||
return true
|
||||
} catch (e) {
|
||||
debug('Cargo binary not detected: %s', bin)
|
||||
return false
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue