feat(cli): support wasi target test & release workflow (#1867)

This commit is contained in:
LongYinan 2023-12-26 23:16:42 +08:00 committed by GitHub
parent bac8ea0e4d
commit c42f00ff43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 346 additions and 179 deletions

View file

@ -34,7 +34,7 @@ const NEW_OPTIONS: CommandSchema = {
name: 'path',
type: 'string',
description: 'The path where the NAPI-RS project will be created.',
required: true,
required: false,
},
],
options: [
@ -53,13 +53,12 @@ const NEW_OPTIONS: CommandSchema = {
short: ['v'],
long: 'min-node-api',
},
// will support it later
// {
// name: 'packageManager',
// type: 'string',
// description: 'The package manager to use',
// default: "'yarn'",
// },
{
name: 'packageManager',
type: 'string',
description: 'The package manager to use. Only support yarn 4.x for now.',
default: "'yarn'",
},
{
name: 'license',
type: 'string',
@ -99,6 +98,13 @@ const NEW_OPTIONS: CommandSchema = {
description: 'Whether generate preconfigured GitHub Actions workflow',
default: true,
},
{
name: 'testFramework',
type: 'string',
description:
'The JavaScript test framework to use, only support `ava` for now',
default: "'ava'",
},
{
name: 'dryRun',
type: 'boolean',

View file

@ -25,13 +25,15 @@ new NapiCli().new({
| 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. |
| path | <path> | false | 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 |
| packageManager | --package-manager | string | false | 'yarn' | The package manager to use. Only support yarn 4.x for now. |
| 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 |
| testFramework | --test-framework | string | false | 'ava' | The JavaScript test framework to use, only support `ava` for now |
| dryRun | --dry-run | boolean | false | false | Whether to run the command in dry-run mode |

View file

@ -31,9 +31,9 @@ import {
writeFileAsync,
} from '../utils/index.js'
import { createWasiBinding } from './load-wasi-template.js'
import { createCjsBinding } from './templates/index.js'
import { WASI_WORKER_TEMPLATE } from './wasi-worker-template.js'
import { createWasiBinding } from './templates/load-wasi-template.js'
import { WASI_WORKER_TEMPLATE } from './templates/wasi-worker-template.js'
const debug = debugFactory('build')
const require = createRequire(import.meta.url)
@ -704,7 +704,7 @@ class Builder {
destName += `.${this.target.platformArchABI}`
}
if (srcName.endsWith('.wasm')) {
destName += '.wasi-wasm32.wasm'
destName += '.wasm'
} else {
destName += '.node'
}

View file

@ -1,4 +1,4 @@
import { join, resolve } from 'path'
import { join, resolve } from 'node:path'
import {
applyDefaultCreateNpmDirsOptions,
@ -55,11 +55,13 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
const targetDir = join(npmPath, `${target.platformArchABI}`)
await mkdirAsync(targetDir)
const binaryFileName = `${binaryName}.${target.platformArchABI}.node`
const binaryFileName =
target.arch === 'wasm32'
? `${binaryName}.${target.platformArchABI}.wasm`
: `${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],
@ -77,9 +79,11 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
'bugs',
),
}
if (target.arch !== 'wasm32') {
// @ts-expect-error
scopedPackageJson.os = [target.platform]
}
// Only works with yarn 3.1+
// https://github.com/yarnpkg/berry/pull/3981
if (target.abi === 'gnu') {
// @ts-expect-error
scopedPackageJson.libc = ['glibc']

View file

@ -1,4 +1,4 @@
import path from 'path'
import path from 'node:path'
import {
applyDefaultNewOptions,
@ -12,7 +12,9 @@ import {
mkdirAsync,
readdirAsync,
statAsync,
type SupportedTestFramework,
writeFileAsync,
SupportedPackageManager,
} from '../utils/index.js'
import { napiEngineRequirement } from '../utils/version.js'
@ -37,6 +39,9 @@ type NewOptions = Required<RawNewOptions>
function processOptions(options: RawNewOptions) {
debug('Processing options...')
if (!options.path) {
throw new Error('Please provide the path as the argument')
}
options.path = path.resolve(process.cwd(), options.path)
debug(`Resolved target path to: ${options.path}`)
@ -69,7 +74,7 @@ export async function newProject(userOptions: RawNewOptions) {
debug('Targets to be enabled:')
debug(options.targets)
const outputs = generateFiles(options)
const outputs = await generateFiles(options)
await ensurePath(options.path, options.dryRun)
@ -110,30 +115,34 @@ async function ensurePath(path: string, dryRun = false) {
}
}
function generateFiles(options: NewOptions): Output[] {
async function generateFiles(options: NewOptions): Promise<Output[]> {
const packageJson = await generatePackageJson(options)
return [
generateCargoToml,
generateLibRs,
generateBuildRs,
generatePackageJson,
generateGithubWorkflow,
generateIgnoreFiles,
].flatMap((generator) => {
const output = generator(options)
]
.flatMap((generator) => {
const output = generator(options)
if (!output) {
return []
}
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) }]
}
})
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) }]
}
})
.concat([
{ ...packageJson, target: path.join(options.path, packageJson.target) },
])
}
function generateCargoToml(options: NewOptions): Output {
@ -162,16 +171,17 @@ function generateBuildRs(_options: NewOptions): Output {
}
}
function generatePackageJson(options: NewOptions): Output {
async function generatePackageJson(options: NewOptions): Promise<Output> {
return {
target: './package.json',
content: createPackageJson({
content: await createPackageJson({
name: options.name,
binaryName: getBinaryName(options.name),
targets: options.targets,
license: options.license,
engineRequirement: napiEngineRequirement(options.minNodeApiVersion),
cliVersion: CLI_VERSION,
testFramework: options.testFramework as SupportedTestFramework,
}),
}
}
@ -183,7 +193,10 @@ function generateGithubWorkflow(options: NewOptions): Output | null {
return {
target: './.github/workflows/ci.yml',
content: createGithubActionsCIYml(options.targets),
content: createGithubActionsCIYml(
options.targets,
options.packageManager as SupportedPackageManager,
),
}
}

View file

@ -1,6 +1,6 @@
import { execSync } from 'child_process'
import { existsSync, statSync } from 'fs'
import { join, resolve } from 'path'
import { execSync } from 'node:child_process'
import { existsSync, statSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { Octokit } from '@octokit/rest'
@ -153,7 +153,9 @@ export async function prePublish(userOptions: PrePublishOptions) {
options.npmDir,
`${target.platformArchABI}`,
)
const filename = `${binaryName}.${target.platformArchABI}.node`
const ext =
target.platform === 'wasi' || target.platform === 'wasm' ? 'wasm' : 'node'
const filename = `${binaryName}.${target.platformArchABI}.${ext}`
const dstPath = join(pkgDir, filename)
if (!options.dryRun) {

View file

@ -1,4 +1,4 @@
import { resolve } from 'path'
import { resolve } from 'node:path'
import { isNil, merge, omitBy, pick } from 'lodash-es'

View file

@ -1,4 +1,6 @@
export const YAML = () => `
import type { SupportedPackageManager } from '../../utils/config.js'
export const YAML = (packageManager: SupportedPackageManager) => `
name: CI
env:
@ -32,82 +34,53 @@ jobs:
settings:
- host: macos-latest
target: 'x86_64-apple-darwin'
build: |
yarn build --platform
strip -x *.node
build: ${packageManager} build --platform
- host: windows-latest
build: yarn build --platform
build: ${packageManager} build --platform
target: 'x86_64-pc-windows-msvc'
- host: windows-latest
build: |
yarn build --platform --target i686-pc-windows-msvc
yarn test
${packageManager} build --platform --target i686-pc-windows-msvc
${packageManager} test
target: 'i686-pc-windows-msvc'
- host: ubuntu-latest
target: 'x86_64-unknown-linux-gnu'
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
build: >-
set -e &&\n
yarn build --platform --target x86_64-unknown-linux-gnu &&\n
strip *.node
build: ${packageManager} build --platform --target x86_64-unknown-linux-gnu --use-napi-cross
- 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 --platform &&
strip *.node
build: ${packageManager} build --platform --target x86_64-unknown-linux-musl -x
- host: macos-latest
target: 'aarch64-apple-darwin'
build: |
yarn build --platform --target aarch64-apple-darwin
strip -x *.node
build: ${packageManager} build --platform --target aarch64-apple-darwin
- 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 --platform --target aarch64-unknown-linux-gnu &&\n
aarch64-unknown-linux-gnu-strip *.node
build: ${packageManager} build --platform --target aarch64-unknown-linux-gnu --use-napi-cross
- host: ubuntu-latest
target: 'armv7-unknown-linux-gnueabihf'
setup: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf -y
build: |
yarn build --platform --target armv7-unknown-linux-gnueabihf --cross-compile
arm-linux-gnueabihf-strip *.node
build: ${packageManager} build --platform --target armv7-unknown-linux-gnueabihf --use-napi-cross
- host: ubuntu-latest
target: 'aarch64-linux-android'
build: |
yarn build --platform --target aarch64-linux-android
\${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node
build: ${packageManager} build --platform --target aarch64-linux-android
- host: ubuntu-latest
target: 'armv7-linux-androideabi'
build: |
yarn build --platform --target armv7-linux-androideabi
\${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node
build: ${packageManager} build --platform --target armv7-linux-androideabi
- host: ubuntu-latest
target: 'aarch64-unknown-linux-musl'
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&\n
rustup target add aarch64-unknown-linux-musl &&\n
yarn build --platform --target aarch64-unknown-linux-musl &&\n
/aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node
build: ${packageManager} build --platform --target aarch64-unknown-linux-musl -x
- host: windows-latest
target: 'aarch64-pc-windows-msvc'
build: yarn build --platform --target aarch64-pc-windows-msvc
build: ${packageManager} build --platform --target aarch64-pc-windows-msvc
- host: ubuntu-latest
target: 'riscv64gc-unknown-linux-gnu'
setup: |
sudo apt-get update
sudo apt-get install gcc-riscv64-linux-gnu -y
build: |
yarn build --platform --target riscv64gc-unknown-linux-gnu
riscv64-linux-gnu-strip *.node
build: ${packageManager} build --platform --target riscv64gc-unknown-linux-gnu
- host: ubuntu-latest
target: 'wasm32-wasi-preview1-threads'
build: ${packageManager} build --platform --target wasm32-wasi-preview1-threads
name: stable - \${{ matrix.settings.target }} - node@18
name: stable - \${{ matrix.settings.target }} - node@20
runs-on: \${{ matrix.settings.host }}
steps:
@ -115,14 +88,12 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
if: \${{ !matrix.settings.docker }}
with:
node-version: 18
cache: yarn
node-version: 20
cache: ${packageManager}
- name: Install
uses: dtolnay/rust-toolchain@stable
if: \${{ !matrix.settings.docker }}
with:
toolchain: stable
targets: \${{ matrix.settings.target }}
@ -134,12 +105,13 @@ jobs:
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
~/.napi-rs
.cargo-cache
target/
key: \${{ matrix.settings.target }}-cargo-\${{ matrix.settings.host }}
- uses: goto-bus-stop/setup-zig@v2
if: \${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}
if: \${{ contains(matrix.settings.target, 'musl') }}
with:
version: 0.11.0
@ -154,36 +126,35 @@ jobs:
shell: bash
- name: 'Install dependencies'
run: yarn install
run: ${packageManager} install
- name: Setup node x86
uses: actions/setup-node@v4
if: matrix.settings.target == 'i686-pc-windows-msvc'
with:
node-version: 18
cache: yarn
node-version: 20
architecture: x86
- name: Build in docker
uses: addnab/docker-run-action@v3
if: \${{ matrix.settings.docker }}
with:
image: \${{ matrix.settings.docker }}
options: --user 0:0 -v \${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v \${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v \${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v \${{ github.workspace }}:/build -w /build
run: \${{ matrix.settings.build }}
- name: 'Build'
run: \${{ matrix.settings.build }}
if: \${{ !matrix.settings.docker }}
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: matrix.settings.target != 'wasm32-wasi-preview1-threads'
with:
name: bindings-\${{ matrix.settings.target }}
path: "*.node"
if-no-files-found: error
- name: Upload artifact
uses: actions/upload-artifact@v4
if: matrix.settings.target == 'wasm32-wasi-preview1-threads'
with:
name: bindings-\${{ matrix.settings.target }}
path: "*.wasm"
if-no-files-found: error
build-freebsd:
runs-on: macos-12
name: Build FreeBSD
@ -191,7 +162,8 @@ jobs:
- uses: actions/checkout@v4
- name: Build
id: build
uses: cross-platform-actions/action@v0.21.0
uses: cross-platform-actions/action@v0.21.1
timeout-minutes: 30
env:
DEBUG: 'napi:*'
RUSTUP_IO_THREADS: 1
@ -202,9 +174,9 @@ jobs:
cpu_count: 3
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
shell: bash
prepare: |
run: |
sudo pkg install -y -f curl node libnghttp2 npm
sudo npm install -g yarn --ignore-scripts
sudo npm install -g ${packageManager} --ignore-scripts
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain stable
source "$HOME/.cargo/env"
@ -219,15 +191,15 @@ jobs:
whoami
env
freebsd-version
yarn install
yarn build
${packageManager} install
${packageManager} build
strip -x *.node
yarn test
rm -rf node_modules
rm -rf target
rm -rf .yarn/cache
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: bindings-freebsd
path: "*.node"
@ -255,13 +227,13 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: \${{ matrix.node }}
cache: 'yarn'
cache: '${packageManager}'
- name: 'Install dependencies'
run: yarn install
run: ${packageManager} install
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-\${{ matrix.settings.target }}
path: .
@ -271,7 +243,7 @@ jobs:
shell: bash
- name: Test bindings
run: yarn test
run: ${packageManager} test
test-linux-x64-gnu-binding:
name: Test bindings on Linux-x64-gnu - node@\${{ matrix.node }}
@ -290,13 +262,13 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: \${{ matrix.node }}
cache: 'yarn'
cache: '${packageManager}'
- name: 'Install dependencies'
run: yarn install
run: ${packageManager} install
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-x86_64-unknown-linux-gnu
path: .
@ -330,10 +302,10 @@ jobs:
- name: 'Install dependencies'
run: |
yarn config set supportedArchitectures.libc "musl"
yarn install
${packageManager} install
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-x86_64-unknown-linux-musl
path: .
@ -352,14 +324,14 @@ jobs:
strategy:
fail-fast: false
matrix:
node: ['18', '20']
node: ['20']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-aarch64-unknown-linux-gnu
path: .
@ -372,7 +344,7 @@ jobs:
run: |
yarn config set supportedArchitectures.cpu "arm64"
yarn config set supportedArchitectures.libc "glibc"
yarn install
${packageManager} install
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -385,10 +357,7 @@ jobs:
with:
image: node:\${{ matrix.node }}-slim
options: --platform linux/arm64 -v \${{ github.workspace }}:/build -w /build
run: |
set -e
yarn test
ls -la
run: yarn test
test-linux-aarch64-musl-binding:
name: Test bindings on aarch64-unknown-linux-musl - node@\${{ matrix.node }}
@ -405,7 +374,7 @@ jobs:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-aarch64-unknown-linux-musl
path: .
@ -418,7 +387,7 @@ jobs:
run: |
yarn config set supportedArchitectures.cpu "arm64"
yarn config set supportedArchitectures.libc "musl"
yarn install
${packageManager} install
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -431,9 +400,7 @@ jobs:
with:
image: node:\${{ matrix.node }}-alpine
options: --platform linux/arm64 -v \${{ github.workspace }}:/build -w /build
run: |
set -e
yarn test
run: yarn test
test-linux-arm-gnueabihf-binding:
name: Test bindings on armv7-unknown-linux-gnueabihf - node@\${{ matrix.node }}
@ -449,7 +416,7 @@ jobs:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-armv7-unknown-linux-gnueabihf
path: .
@ -461,7 +428,7 @@ jobs:
- name: Install dependencies
run: |
yarn config set supportedArchitectures.cpu "arm"
yarn install
${packageManager} install
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -474,10 +441,7 @@ jobs:
with:
image: node:\${{ matrix.node }}-bullseye-slim
options: --platform linux/arm/v7 -v \${{ github.workspace }}:/build -w /build
run: |
set -e
yarn test
ls -la
run: ${packageManager} test
universal-macOS:
name: Build universal macOS binary
@ -491,33 +455,62 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: yarn
node-version: 20
cache: ${packageManager}
- name: 'Install dependencies'
run: yarn install
run: ${packageManager} install
- name: Download macOS x64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-x86_64-apple-darwin
path: .
- name: Download macOS arm64 artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-aarch64-apple-darwin
path: .
- name: Combine binaries
run: yarn napi universalize
run: ${packageManager} napi universalize
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: bindings-universal-apple-darwin
path: "*.node"
if-no-files-found: error
test-wasi-nodejs:
name: Test bindings on wasi - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node: ['18', '20']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: bindings-wasm32-wasi-preview1-threads
path: .
- name: List packages
run: ls -R .
- uses: actions/setup-node@v4
with:
node-version: \${{ matrix.node }}
cache: ${packageManager}
- name: 'Install dependencies'
run: ${packageManager} install
- name: Test
run: ${packageManager} test
env:
NAPI_RS_FORCE_WASI: true
publish:
name: Publish
runs-on: ubuntu-latest
@ -528,6 +521,7 @@ jobs:
- test-linux-arm-gnueabihf-binding
- test-macOS-windows-binding
- test-linux-aarch64-musl-binding
- test-wasi-nodejs
- build-freebsd
steps:
@ -536,15 +530,15 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
node-version: 20
cache: '${packageManager}'
registry-url: 'https://registry.npmjs.org'
- name: 'Install dependencies'
run: yarn install
run: ${packageManager} install
- name: Download all artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts

View file

@ -2,6 +2,7 @@ import { load, dump } from 'js-yaml'
import {
NodeArchToCpu,
type SupportedPackageManager,
UniArchsByPlatform,
parseTriple,
} from '../../utils/index.js'
@ -15,9 +16,13 @@ const TEST_LINUX_X64_MUSL = 'test-linux-x64-musl-binding'
const TEST_LINUX_AARCH64_GNU = 'test-linux-aarch64-gnu-binding'
const TEST_LINUX_AARCH64_MUSL = 'test-linux-aarch64-musl-binding'
const TEST_LINUX_ARM_GNUEABIHF = 'test-linux-arm-gnueabihf-binding'
const TEST_WASI = 'test-wasi-nodejs'
const UNIVERSAL_MACOS = 'universal-macOS'
export const createGithubActionsCIYml = (targets: string[]) => {
export const createGithubActionsCIYml = (
targets: string[],
packageManager: SupportedPackageManager,
) => {
const allTargets = new Set(
targets.flatMap((t) => {
const platform = parseTriple(t)
@ -31,7 +36,7 @@ export const createGithubActionsCIYml = (targets: string[]) => {
}),
)
const fullTemplate = load(YAML()) as any
const fullTemplate = load(YAML(packageManager)) as any
const requiredSteps = []
const enableWindowsX86 = allTargets.has('x86_64-pc-windows-msvc')
@ -43,6 +48,7 @@ export const createGithubActionsCIYml = (targets: string[]) => {
const enableLinuxArm7 = allTargets.has('armv7-unknown-linux-gnueabihf')
const enableFreeBSD = allTargets.has('x86_64-unknown-freebsd')
const enableMacOSUni = allTargets.has('universal-apple-darwin')
const enableWasi = allTargets.has('wasm32-wasi-preview1-threads')
fullTemplate.jobs.build.strategy.matrix.settings =
fullTemplate.jobs.build.strategy.matrix.settings.filter(
({ target }: { target: string }) => allTargets.has(target),
@ -111,6 +117,12 @@ export const createGithubActionsCIYml = (targets: string[]) => {
requiredSteps.push(UNIVERSAL_MACOS)
}
if (!enableWasi) {
delete fullTemplate.jobs[TEST_WASI]
} else {
requiredSteps.push(TEST_WASI)
}
fullTemplate.jobs.publish.needs = requiredSteps
return dump(fullTemplate, {

View file

@ -311,6 +311,21 @@ switch (platform) {
throw new Error(\`Unsupported OS: \${platform}, architecture: \${arch}\`)
}
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
try {
localFileExisted = existsSync(
join(__dirname, '${localName}.wasm32-wasi.wasm')
) && existsSync(join(__dirname, '${localName}.wasi.cjs'))
if (localFileExisted) {
nativeBinding = require('./${localName}.wasi.cjs')
} else {
nativeBinding = require('${pkgName}-wasm32-wasi')
}
} catch {
// ignore
}
}
if (!nativeBinding) {
if (loadError) {
throw loadError

View file

@ -1,12 +1,21 @@
import { CommonPackageJsonFields } from '../../utils/config.js'
import type {
CommonPackageJsonFields,
SupportedTestFramework,
} from '../../utils/config.js'
import { UNIVERSAL_TARGETS } from '../../utils/target.js'
export const createPackageJson = ({
interface PackageMeta {
'dist-tags': { [index: string]: string }
}
export const createPackageJson = async ({
name,
binaryName,
targets,
license,
engineRequirement,
cliVersion,
testFramework,
}: {
name: string
binaryName: string
@ -14,10 +23,24 @@ export const createPackageJson = ({
license: string
engineRequirement: string
cliVersion: string
testFramework: SupportedTestFramework
}) => {
const hasWasmTarget = targets.some((t) => t.includes('wasm'))
const universalTargets = targets.filter(
(t) => t in UNIVERSAL_TARGETS,
) as (keyof typeof UNIVERSAL_TARGETS)[]
const unifiedtargets = universalTargets.length
? targets.filter(
(target) =>
!universalTargets.some((t) => {
// @ts-expect-error
return UNIVERSAL_TARGETS[t].includes(target)
}),
)
: targets
const content: CommonPackageJsonFields = {
name,
version: '1.0.0',
version: '0.0.0',
license,
engines: {
node: engineRequirement,
@ -29,10 +52,10 @@ export const createPackageJson = ({
exports: undefined,
napi: {
binaryName,
targets,
targets: unifiedtargets,
},
scripts: {
test: 'node -e "assert(require(\'.\').sum(1, 2) === 3)"',
test: testFramework,
build: 'napi build --release --platform --strip',
'build:debug': 'napi build',
prepublishOnly: 'napi prepublish -t npm',
@ -44,5 +67,28 @@ export const createPackageJson = ({
},
}
if (testFramework === 'ava') {
const avaMeta = await fetch(`https://registry.npmjs.org/ava`).then(
(res) => res.json() as Promise<PackageMeta>,
)
content.devDependencies!['ava'] = `^${avaMeta['dist-tags'].latest}`
content.ava = {
timeout: '1m',
}
}
if (hasWasmTarget) {
const emnapiCoreMeta = await fetch(
`https://registry.npmjs.org/@emnapi/core`,
).then((res) => res.json() as Promise<PackageMeta>)
const latest = emnapiCoreMeta['dist-tags'].latest
content.devDependencies!['@emnapi/core'] = `^${latest}`
const emnapiRuntimeMeta = await fetch(
`https://registry.npmjs.org/@emnapi/runtime`,
).then((res) => res.json() as Promise<PackageMeta>)
const runtimeLatest = emnapiRuntimeMeta['dist-tags'].latest
content.devDependencies!['@emnapi/runtime'] = `^${runtimeLatest}`
}
return JSON.stringify(content, null, 2)
}

View file

@ -1,5 +1,5 @@
import { spawnSync } from 'child_process'
import { join, resolve } from 'path'
import { spawnSync } from 'node:child_process'
import { join, resolve } from 'node:path'
import {
applyDefaultUniversalizeOptions,

View file

@ -1,4 +1,4 @@
import { join, resolve } from 'path'
import { join, resolve } from 'node:path'
import { applyDefaultVersionOptions, VersionOptions } from '../def/version.js'
import {

View file

@ -1,4 +1,4 @@
import { execSync } from 'child_process'
import { execSync } from 'node:child_process'
import { Option } from 'clipanion'

View file

@ -1,4 +1,4 @@
import path from 'path'
import path from 'node:path'
import { Option } from 'clipanion'
import inquirer from 'inquirer'
@ -16,7 +16,7 @@ import { napiEngineRequirement } from '../utils/version.js'
const debug = debugFactory('new')
export class NewCommand extends BaseNewCommand {
interactive = Option.Boolean('--interactive,-i', false, {
interactive = Option.Boolean('--interactive,-i', true, {
description:
'Ask project basic information interactively without just using the default.',
})
@ -37,9 +37,19 @@ export class NewCommand extends BaseNewCommand {
const cmdOptions = super.getOptions()
if (this.interactive) {
const targetPath: string = cmdOptions.path
? cmdOptions.path
: await inquirer
.prompt({
type: 'input',
name: 'path',
message: 'Target path to create the project, relative to cwd',
})
.then(({ path }) => path)
cmdOptions.path = targetPath
return {
...cmdOptions,
name: await this.fetchName(path.parse(cmdOptions.path).base),
name: await this.fetchName(path.parse(targetPath).base),
minNodeApiVersion: await this.fetchNapiVersion(),
targets: await this.fetchTargets(),
license: await this.fetchLicense(),

View file

@ -10,7 +10,7 @@ export abstract class BaseNewCommand extends Command {
description: 'Create a new project with pre-configured boilerplate',
})
$$path = Option.String({ required: true })
$$path = Option.String({ required: false })
$$name?: string = Option.String('--name,-n', {
description:
@ -22,6 +22,10 @@ export abstract class BaseNewCommand extends Command {
description: 'The minimum Node-API version to support',
})
packageManager = Option.String('--package-manager', 'yarn', {
description: 'The package manager to use. Only support yarn 4.x for now.',
})
license = Option.String('--license,-l', 'MIT', {
description: 'License for open-sourced project',
})
@ -47,6 +51,11 @@ export abstract class BaseNewCommand extends Command {
description: 'Whether generate preconfigured GitHub Actions workflow',
})
testFramework = Option.String('--test-framework', 'ava', {
description:
'The JavaScript test framework to use, only support `ava` for now',
})
dryRun = Option.Boolean('--dry-run', false, {
description: 'Whether to run the command in dry-run mode',
})
@ -56,12 +65,14 @@ export abstract class BaseNewCommand extends Command {
path: this.$$path,
name: this.$$name,
minNodeApiVersion: this.minNodeApiVersion,
packageManager: this.packageManager,
license: this.license,
targets: this.targets,
enableDefaultTargets: this.enableDefaultTargets,
enableAllTargets: this.enableAllTargets,
enableTypeDef: this.enableTypeDef,
enableGithubActions: this.enableGithubActions,
testFramework: this.testFramework,
dryRun: this.dryRun,
}
}
@ -74,7 +85,7 @@ export interface NewOptions {
/**
* The path where the NAPI-RS project will be created.
*/
path: string
path?: string
/**
* The name of the project, default to the name of the directory if not provided
*/
@ -85,6 +96,12 @@ export interface NewOptions {
* @default 4
*/
minNodeApiVersion?: number
/**
* The package manager to use. Only support yarn 4.x for now.
*
* @default 'yarn'
*/
packageManager?: string
/**
* License for open-sourced project
*
@ -121,6 +138,12 @@ export interface NewOptions {
* @default true
*/
enableGithubActions?: boolean
/**
* The JavaScript test framework to use, only support `ava` for now
*
* @default 'ava'
*/
testFramework?: string
/**
* Whether to run the command in dry-run mode
*
@ -132,12 +155,14 @@ export interface NewOptions {
export function applyDefaultNewOptions(options: NewOptions) {
return {
minNodeApiVersion: 4,
packageManager: 'yarn',
license: 'MIT',
targets: [],
enableDefaultTargets: true,
enableAllTargets: false,
enableTypeDef: true,
enableGithubActions: true,
testFramework: 'ava',
dryRun: false,
...options,
}

View file

@ -114,4 +114,11 @@ Generated by [AVA](https://avajs.dev).
platformArchABI: 'linux-riscv64-gnu',
triple: 'riscv64gc-unknown-linux-gnu',
},
{
abi: 'wasi',
arch: 'wasm32',
platform: 'wasi',
platformArchABI: 'wasm32-wasi',
triple: 'wasm32-wasi-preview1-threads',
},
]

View file

@ -1,4 +1,4 @@
import { execSync } from 'child_process'
import { execSync } from 'node:child_process'
import { debug } from './log.js'

View file

@ -4,6 +4,18 @@ import { merge, omit } from 'lodash-es'
import { fileExists, readFileAsync } from './misc.js'
import { DEFAULT_TARGETS, parseTriple, Target } from './target.js'
export type ValueOfConstArray<T> = T[Exclude<keyof T, keyof Array<any>>]
export const SupportedPackageManagers = ['yarn'] as const
export const SupportedTestFrameworks = ['ava'] as const
export type SupportedPackageManager = ValueOfConstArray<
typeof SupportedPackageManagers
>
export type SupportedTestFramework = ValueOfConstArray<
typeof SupportedTestFrameworks
>
export interface UserNapiConfig {
/**
* Name of the binary to be generated, default to `index`
@ -81,6 +93,10 @@ export interface CommonPackageJsonFields {
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
ava?: {
timeout?: string
}
}
export type NapiConfig = Required<

View file

@ -1,5 +1,5 @@
import { execSync } from 'child_process'
import fs from 'fs'
import { execSync } from 'node:child_process'
import fs from 'node:fs'
export type CrateTargetKind =
| 'bin'

View file

@ -1,6 +1,14 @@
import { readFile, writeFile, copyFile, mkdir, unlink, stat, readdir } from 'fs'
import { createRequire } from 'module'
import { promisify } from 'util'
import {
readFile,
writeFile,
copyFile,
mkdir,
unlink,
stat,
readdir,
} from 'node:fs'
import { createRequire } from 'node:module'
import { promisify } from 'node:util'
import { debug } from './log.js'

View file

@ -2,6 +2,10 @@ import { execSync } from 'node:child_process'
export type Platform = NodeJS.Platform | 'wasm' | 'wasi'
export const UNIVERSAL_TARGETS = {
'universal-apple-darwin': ['aarch64-apple-darwin', 'x86_64-apple-darwin'],
} as const
export const AVAILABLE_TARGETS = [
'aarch64-apple-darwin',
'aarch64-linux-android',
@ -18,12 +22,14 @@ export const AVAILABLE_TARGETS = [
'armv7-linux-androideabi',
'universal-apple-darwin',
'riscv64gc-unknown-linux-gnu',
'wasm32-wasi-preview1-threads',
] as const
export type TargetTriple = (typeof AVAILABLE_TARGETS)[number]
export const DEFAULT_TARGETS = [
'x86_64-apple-darwin',
'aarch64-apple-darwin',
'x86_64-pc-windows-msvc',
'x86_64-unknown-linux-gnu',
] as const
@ -48,6 +54,7 @@ type NodeJSArch =
| 'x32'
| 'x64'
| 'universal'
| 'wasm32'
const CpuToNodeArch: Record<string, NodeJSArch> = {
x86_64: 'x64',
@ -80,7 +87,7 @@ export interface Target {
triple: string
platformArchABI: string
platform: Platform
arch: NodeJSArch | 'wasm32'
arch: NodeJSArch
abi: string | null
}
@ -98,7 +105,7 @@ export function parseTriple(rawTriple: string): Target {
if (rawTriple === 'wasm32-wasi-preview1-threads') {
return {
triple: rawTriple,
platformArchABI: rawTriple,
platformArchABI: 'wasm32-wasi',
platform: 'wasi',
arch: 'wasm32',
abi: 'wasi',

View file

@ -15,7 +15,7 @@ const __wasi = new __nodeWASI({
version: 'preview1',
env: process.env,
preopens: {
'/': __dirname,
'/': '/'
}
})
@ -27,7 +27,7 @@ const __sharedMemory = new WebAssembly.Memory({
shared: true,
})
const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__nodePath.join(__dirname, 'index.wasi-wasm32.wasm')), {
const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__nodePath.join(__dirname, 'index.wasm')), {
context: __emnapiContext,
asyncWorkPoolSize: 4,
wasi: __wasi,