feat(cli): add back new command

This commit is contained in:
LongYinan 2021-09-01 23:21:11 +08:00
parent a983babd63
commit 2c23f444b0
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
12 changed files with 1046 additions and 5 deletions

View file

@ -33,13 +33,18 @@
}, },
"devDependencies": { "devDependencies": {
"@octokit/rest": "^18.10.0", "@octokit/rest": "^18.10.0",
"@types/inquirer": "^7.3.3",
"@types/js-yaml": "^4.0.3",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"clipanion": "^3.0.1", "clipanion": "^3.0.1",
"debug": "^4.3.2", "debug": "^4.3.2",
"fdir": "^5.1.0", "fdir": "^5.1.0",
"inquirer": "^8.1.2",
"js-yaml": "^4.1.0",
"putasset": "^5.0.3", "putasset": "^5.0.3",
"toml": "^3.0.0", "toml": "^3.0.0",
"tslib": "^2.3.1" "tslib": "^2.3.1",
"typanion": "^3.3.2"
}, },
"funding": { "funding": {
"type": "github", "type": "github",

View file

@ -5,6 +5,7 @@ import { Cli } from 'clipanion'
import { ArtifactsCommand } from './artifacts' import { ArtifactsCommand } from './artifacts'
import { BuildCommand } from './build' import { BuildCommand } from './build'
import { CreateNpmDirCommand } from './create-npm-dir' import { CreateNpmDirCommand } from './create-npm-dir'
import { NewProjectCommand } from './new'
import { PrePublishCommand } from './pre-publish' import { PrePublishCommand } from './pre-publish'
import { VersionCommand } from './version' import { VersionCommand } from './version'
@ -18,6 +19,7 @@ cli.register(BuildCommand)
cli.register(CreateNpmDirCommand) cli.register(CreateNpmDirCommand)
cli.register(PrePublishCommand) cli.register(PrePublishCommand)
cli.register(VersionCommand) cli.register(VersionCommand)
cli.register(NewProjectCommand)
cli cli
.run(process.argv.slice(2), { .run(process.argv.slice(2), {

View file

@ -0,0 +1,24 @@
export const createCargoConfig = (
enableLinuxArm7: boolean,
enableLinuxArm8Gnu: boolean,
enableLinuxArm8Musl: boolean,
) => {
let result = ''
if (enableLinuxArm8Gnu) {
result = `[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"`
}
if (enableLinuxArm8Musl) {
result = `[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]`
}
if (enableLinuxArm7) {
result = `${result}
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
`
}
return result
}

18
cli/src/new/cargo.ts Normal file
View file

@ -0,0 +1,18 @@
export const createCargoContent = (name: string) => `[package]
edition = "2018"
name = "${name.replace('@', '').replace('/', '_').toLowerCase()}"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
napi = "1"
napi-derive = "1"
[build-dependencies]
napi-build = "1"
[profile.release]
lto = true
`

503
cli/src/new/ci-template.ts Normal file
View file

@ -0,0 +1,503 @@
export const YAML = `
name: CI
env:
DEBUG: 'napi:*'
APP_NAME: 'napi'
MACOSX_DEPLOYMENT_TARGET: '10.13'
on:
push:
branches:
- main
tags-ignore:
- '**'
pull_request:
jobs:
build:
if: "!contains(github.event.head_commit.message, 'skip ci')"
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: 'x86_64-apple-darwin'
build: yarn build
- host: windows-latest
build: yarn build
target: 'x86_64-pc-windows-msvc'
- host: windows-latest
build: |
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=32;
export CARGO_PROFILE_RELEASE_LTO=false
yarn build --target i686-pc-windows-msvc
yarn test
target: 'i686-pc-windows-msvc'
setup: |
choco install nodejs-lts --x86 -y --force
echo "C:\\Program Files (x86)\\nodejs" >> $GITHUB_PATH
- host: ubuntu-latest
target: 'x86_64-unknown-linux-gnu'
docker: |
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL
docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian
docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian builder
build: |
docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder yarn build && strip \${{ env.APP_NAME }}.linux-x64-gnu.node
- host: ubuntu-latest
target: 'x86_64-unknown-linux-musl'
docker: |
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL
docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine
docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine builder
build: docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder yarn build && strip \${{ env.APP_NAME }}.linux-x64-musl.node
- host: macos-latest
target: 'aarch64-apple-darwin'
build: yarn build --target=aarch64-apple-darwin
- host: ubuntu-latest
target: 'aarch64-unknown-linux-gnu'
setup: |
sudo apt-get install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu -y
build: |
yarn build --target=aarch64-unknown-linux-gnu
aarch64-linux-gnu-strip \${{ env.APP_NAME }}.linux-arm64-gnu.node
- host: ubuntu-latest
target: 'armv7-unknown-linux-gnueabihf'
setup: |
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
build: |
yarn build --target=armv7-unknown-linux-gnueabihf
arm-linux-gnueabihf-strip \${{ env.APP_NAME }}.linux-arm-gnueabihf.node
- host: ubuntu-latest
target: 'aarch64-linux-android'
build: |
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="\${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
yarn build --target aarch64-linux-android
- host: ubuntu-latest
target: 'aarch64-unknown-linux-musl'
downloadTarget: 'aarch64-unknown-linux-musl'
docker: |
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL
docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder
build: |
docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/\${{ env.APP_NAME }} -w /\${{ env.APP_NAME }} builder sh -c "yarn build -- --target=aarch64-unknown-linux-musl && /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip \${{ env.APP_NAME }}.linux-arm64-musl.node"
- host: windows-latest
target: 'aarch64-pc-windows-msvc'
build: yarn build --target aarch64-pc-windows-msvc
name: stable - \${{ matrix.settings.target }} - node@14
runs-on: \${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Install
uses: actions-rs/toolchain@v1
with:
profile: minimal
override: true
toolchain: stable
target: \${{ matrix.settings.target }}
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: \${{ matrix.settings.target }}-node@14-cargo-registry-trimmed-\${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v2
with:
path: ~/.cargo/git
key: \${{ matrix.settings.target }}-node@14-cargo-index-trimmed-\${{ hashFiles('**/Cargo.lock') }}
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-\${{ matrix.settings.target }}-node@14-\${{ hashFiles('yarn.lock') }}
- name: Pull latest image
run: \${{ matrix.settings.docker }}
env:
DOCKER_REGISTRY_URL: ghcr.io
DOCKER_USERNAME: \${{ github.actor }}
DOCKER_PASSWORD: \${{ secrets.GITHUB_TOKEN }}
if: \${{ matrix.settings.docker }}
- name: Setup toolchain
run: \${{ matrix.settings.setup }}
if: \${{ matrix.settings.setup }}
shell: bash
- name: 'Install dependencies'
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: 'Build'
run: \${{ matrix.settings.build }}
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: bindings-\${{ matrix.settings.target }}
path: \${{ env.APP_NAME }}.*.node
build-freebsd:
runs-on: macos-latest
name: Build FreeBSD
steps:
- uses: actions/checkout@v2
- name: Build
id: build
uses: vmactions/freebsd-vm@v0.1.5
env:
DEBUG: 'napi:*'
RUSTUP_HOME: /usr/local/rustup
CARGO_HOME: /usr/local/cargo
RUSTUP_IO_THREADS: 1
with:
envs: 'DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS'
usesh: true
mem: 3000
prepare: |
pkg install -y curl node yarn npm python2
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain stable
export PATH="/usr/local/cargo/bin:$PATH"
echo "~~~~ rustc --version ~~~~"
rustc --version
echo "~~~~ node -v ~~~~"
node -v
echo "~~~~ yarn --version ~~~~"
yarn --version
run: |
export PATH="/usr/local/cargo/bin:$PATH"
pwd
ls -lah
whoami
env
freebsd-version
yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
yarn build
yarn test
rm -rf node_modules
rm -rf target
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: bindings-freebsd
path: \${{ env.APP_NAME }}.*.node
test-macOS-windows-binding:
name: Test bindings on \${{ matrix.settings.target }} - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: 'x86_64-apple-darwin'
- host: windows-latest
target: 'x86_64-pc-windows-msvc'
node: ['12', '14', '16']
runs-on: \${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: \${{ matrix.node }}
check-latest: true
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-\${{ matrix.settings.target }}-\${{ matrix.node }}-\${{ hashFiles('yarn.lock') }}
- name: 'Install dependencies'
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-\${{ matrix.settings.target }}
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: yarn test
test-linux-x64-gnu-binding:
name: Test bindings on Linux-x64-gnu - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node: ['12', '14', '16']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: \${{ matrix.node }}
check-latest: true
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-linux-x64-gnu-\${{ matrix.node }}-\${{ hashFiles('yarn.lock') }}
- name: 'Install dependencies'
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-x86_64-unknown-linux-gnu
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: docker run --rm -v $(pwd):/\${{ env.APP_NAME }} -w /\${{ env.APP_NAME }} node:\${{ matrix.node }}-slim yarn test
test-linux-x64-musl-binding:
name: Test bindings on x86_64-unknown-linux-musl - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node: ['12', '14', '16']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: \${{ matrix.node }}
check-latest: true
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-x86_64-unknown-linux-musl-\${{ matrix.node }}-\${{ hashFiles('yarn.lock') }}
- name: 'Install dependencies'
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-x86_64-unknown-linux-musl
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: docker run --rm -v $(pwd):/\${{ env.APP_NAME }} -w /\${{ env.APP_NAME }} node:\${{ matrix.node }}-alpine yarn test
test-linux-aarch64-gnu-binding:
name: Test bindings on aarch64-unknown-linux-gnu - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node: ['12', '14', '16']
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-aarch64-unknown-linux-gnu
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Setup and run tests
uses: docker://multiarch/ubuntu-core:arm64-focal
with:
args: >
sh -c "
apt-get update && \
apt-get install -y ca-certificates gnupg2 curl apt-transport-https && \
curl -sL https://deb.nodesource.com/setup_\${{ matrix.node }}.x | bash - && \
apt-get install -y nodejs && \
npm install -g yarn && \
yarn install --ignore-scripts --registry https://registry.npmjs.org --network-timeout 300000 && \
yarn test && \
ls -la
"
test-linux-aarch64-musl-binding:
name: Test bindings on aarch64-unknown-linux-musl - node@\${{ matrix.node }}
needs:
- build
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-aarch64-unknown-linux-musl
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Setup and run tests
uses: docker://multiarch/alpine:aarch64-latest-stable
with:
args: >
sh -c "
apk add nodejs npm && \
npm install -g yarn && \
yarn install --ignore-scripts --registry https://registry.npmjs.org --network-timeout 300000 && \
npm test
"
test-linux-arm-gnueabihf-binding:
name: Test bindings on armv7-unknown-linux-gnueabihf - node@\${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node: ['12', '14', '16']
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-armv7-unknown-linux-gnueabihf
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Setup and run tests
uses: docker://multiarch/ubuntu-core:armhf-focal
with:
args: >
sh -c "
apt-get update && \
apt-get install -y ca-certificates gnupg2 curl apt-transport-https && \
curl -sL https://deb.nodesource.com/setup_\${{ matrix.node }}.x | bash - && \
apt-get install -y nodejs && \
npm install -g yarn && \
yarn install --ignore-scripts --registry https://registry.npmjs.org --network-timeout 300000 && \
yarn test && \
ls -la
"
publish:
name: Publish
runs-on: ubuntu-latest
needs:
- test-linux-x64-gnu-binding
- test-linux-x64-musl-binding
- test-linux-aarch64-gnu-binding
- test-linux-arm-gnueabihf-binding
- test-macOS-windows-binding
- test-linux-aarch64-musl-binding
- build-freebsd
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-ubuntu-latest-\${{ hashFiles('yarn.lock') }}
restore-keys: |
npm-cache-
- name: 'Install dependencies'
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download all artifacts
uses: actions/download-artifact@v2
with:
path: artifacts
- name: Move artifacts
run: yarn artifacts
- name: List packages
run: ls -R ./npm
shell: bash
- name: Publish
run: |
if git log -1 --pretty=%B | grep "^[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$";
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --access public
elif git log -1 --pretty=%B | grep "^[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+";
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --tag next --access public
else
echo "Not a release, skipping publish"
fi
env:
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: \${{ secrets.NPM_TOKEN }}
`

87
cli/src/new/ci-yml.ts Normal file
View file

@ -0,0 +1,87 @@
import { load, dump } from 'js-yaml'
import { YAML } from './ci-template'
const BUILD_FREEBSD = 'build-freebsd'
const TEST_MACOS_WINDOWS = 'test-macOS-windows-binding'
const TEST_LINUX_X64_GNU = 'test-linux-x64-gnu-binding'
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'
export const createGithubActionsCIYml = (
binaryName: string,
targets: string[],
) => {
const fullTemplate = load(YAML) as any
const requiredSteps = []
const enableWindowsX86 = targets.includes('x86_64-pc-windows-msvc')
const enableMacOSX86 = targets.includes('x86_64-apple-darwin')
const enableLinuxX86Gnu = targets.includes('x86_64-unknown-linux-gnu')
const enableLinuxX86Musl = targets.includes('x86_64-unknown-linux-musl')
const enableLinuxArm8Gnu = targets.includes('aarch64-unknown-linux-gnu')
const enableLinuxArm8Musl = targets.includes('aarch64-unknown-linux-musl')
const enableLinuxArm7 = targets.includes('armv7-unknown-linux-gnueabihf')
const enableFreeBSD = targets.includes('x86_64-unknown-freebsd')
fullTemplate.env.APP_NAME = binaryName
fullTemplate.jobs.build.strategy.matrix.settings =
fullTemplate.jobs.build.strategy.matrix.settings.filter(
({ target }: { target: string }) => targets.includes(target),
)
if (!fullTemplate.jobs.build.strategy.matrix.settings.length) {
delete fullTemplate.jobs.build.strategy.matrix
}
if (!enableFreeBSD) {
delete fullTemplate.jobs[BUILD_FREEBSD]
} else {
requiredSteps.push(BUILD_FREEBSD)
}
if (!enableWindowsX86 && !enableMacOSX86) {
delete fullTemplate.jobs[TEST_MACOS_WINDOWS]
} else {
const filterTarget = enableWindowsX86 ? 'macos-latest' : 'windows-latest'
fullTemplate.jobs[TEST_MACOS_WINDOWS].strategy.matrix.settings =
fullTemplate.jobs[TEST_MACOS_WINDOWS].strategy.matrix.settings.filter(
({ host }: { host: string; target: string }) => host !== filterTarget,
)
requiredSteps.push(TEST_MACOS_WINDOWS)
}
if (!enableLinuxX86Gnu) {
delete fullTemplate.jobs[TEST_LINUX_X64_GNU]
} else {
requiredSteps.push(TEST_LINUX_X64_GNU)
}
if (!enableLinuxX86Musl) {
delete fullTemplate.jobs[TEST_LINUX_X64_MUSL]
} else {
requiredSteps.push(TEST_LINUX_X64_MUSL)
}
if (!enableLinuxArm8Gnu) {
delete fullTemplate.jobs[TEST_LINUX_AARCH64_GNU]
} else {
requiredSteps.push(TEST_LINUX_AARCH64_GNU)
}
if (!enableLinuxArm8Musl) {
delete fullTemplate.jobs[TEST_LINUX_AARCH64_MUSL]
} else {
requiredSteps.push(TEST_LINUX_AARCH64_MUSL)
}
if (!enableLinuxArm7) {
delete fullTemplate.jobs[TEST_LINUX_ARM_GNUEABIHF]
} else {
requiredSteps.push(TEST_LINUX_ARM_GNUEABIHF)
}
return dump(fullTemplate, {
lineWidth: 1000,
})
}

204
cli/src/new/index.ts Normal file
View file

@ -0,0 +1,204 @@
import { writeFileSync, mkdirSync } from 'fs'
import { join } from 'path'
import chalk from 'chalk'
import { Command, Option } from 'clipanion'
import inquirer, { prompt } from 'inquirer'
import { CreateNpmDirCommand } from '../create-npm-dir'
import { debugFactory } from '../debug'
import { DefaultPlatforms } from '../parse-triple'
import { createCargoContent } from './cargo'
import { createCargoConfig } from './cargo-config'
import { createGithubActionsCIYml } from './ci-yml'
import { createIndexJs } from './indexjs'
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-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',
]
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 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!))
mkdirSync(join(process.cwd(), this.dirname!, 'src'))
}
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('index.js', createIndexJs(this.name!, binaryName))
this.writeFile(
'package.json',
JSON.stringify(
createPackageJson(this.name!, binaryName, this.targets!),
null,
2,
),
)
this.writeFile('src/lib.rs', LibRs)
if (this.enableGithubActions) {
const githubDir = join(process.cwd(), this.dirname!, '.github')
const workflowsDir = join(githubDir, 'workflows')
if (!this.dryRun) {
mkdirSync(githubDir)
mkdirSync(workflowsDir)
}
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 enableLinuxArm8Gnu = this.targets!.includes(
'aarch64-unknown-linux-gnu',
)
const enableLinuxArm8Musl = this.targets!.includes(
'aarch64-unknown-linux-musl',
)
const enableLinuxArm7 = this.targets!.includes(
'armv7-unknown-linux-gnueabihf',
)
const cargoConfig = createCargoConfig(
enableLinuxArm7,
enableLinuxArm8Gnu,
enableLinuxArm8Musl,
)
if (cargoConfig.length) {
const configDir = join(process.cwd(), this.dirname!, '.config')
if (!this.dryRun) {
mkdirSync(configDir)
this.writeFile(join('.config', 'config.toml'), cargoConfig)
}
}
}
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 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
}
}
}
}

15
cli/src/new/indexjs.ts Normal file
View file

@ -0,0 +1,15 @@
export const createIndexJs = (
pkgName: string,
name: string,
) => `const { loadBinding } = require('@node-rs/helper')
/**
* __dirname means load native addon from current dir
* '${name}' is the name of native addon
* the second arguments was decided by \`napi.name\` field in \`package.json\`
* the third arguments was decided by \`name\` field in \`package.json\`
* \`loadBinding\` helper will load \`${name}.[PLATFORM].node\` from \`__dirname\` first
* If failed to load addon, it will fallback to load from \`${pkgName}-[PLATFORM]\`
*/
module.exports = loadBinding(__dirname, '${name}', '${pkgName}')
`

50
cli/src/new/lib-rs.ts Normal file
View file

@ -0,0 +1,50 @@
export const LibRs = `#![deny(clippy::all)]
#[macro_use]
extern crate napi_derive;
use std::convert::TryInto;
use napi::{CallContext, Env, JsNumber, JsObject, Result, Task};
struct AsyncTask(u32);
impl Task for AsyncTask {
type Output = u32;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
use std::thread::sleep;
use std::time::Duration;
sleep(Duration::from_millis(self.0 as u64));
Ok(self.0 * 2)
}
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
env.create_uint32(output)
}
}
#[module_exports]
fn init(mut exports: JsObject) -> Result<()> {
exports.create_named_method("sync", sync_fn)?;
exports.create_named_method("sleep", sleep)?;
Ok(())
}
#[js_function(1)]
fn sync_fn(ctx: CallContext) -> Result<JsNumber> {
let argument: u32 = ctx.get::<JsNumber>(0)?.try_into()?;
ctx.env.create_uint32(argument + 100)
}
#[js_function(1)]
fn sleep(ctx: CallContext) -> Result<JsObject> {
let argument: u32 = ctx.get::<JsNumber>(0)?.try_into()?;
let task = AsyncTask(argument);
let async_task = ctx.env.spawn(task)?;
Ok(async_task.promise_object())
}
`

11
cli/src/new/npmignore.ts Normal file
View file

@ -0,0 +1,11 @@
export const NPMIgnoreFiles = `target
Cargo.lock
.cargo
.github
npm
.eslintrc
.prettierignore
rustfmt.toml
yarn.lock
*.node
`

58
cli/src/new/package.ts Normal file
View file

@ -0,0 +1,58 @@
import { DefaultPlatforms } from '../parse-triple'
export const createPackageJson = (
name: string,
binaryName: string,
targets: string[],
) => {
const pkgContent = {
name,
version: '0.0.0',
napi: {
name: binaryName,
},
license: 'MIT',
dependencies: {
'@node-rs/helper': '^1.2.1',
},
devDependencies: {
'@napi-rs/cli': '^1.2.1',
},
engines: {
node: '>= 10',
},
scripts: {
artifacts: 'napi artifacts',
build: 'napi build --platform --release',
'build:debug': 'napi build --platform',
prepublishOnly: 'napi prepublish -t npm',
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
}

View file

@ -1127,11 +1127,24 @@
"@types/docker-modem" "*" "@types/docker-modem" "*"
"@types/node" "*" "@types/node" "*"
"@types/inquirer@^7.3.3":
version "7.3.3"
resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac"
integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==
dependencies:
"@types/through" "*"
rxjs "^6.4.0"
"@types/istanbul-lib-coverage@^2.0.1": "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3" version "2.0.3"
resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
"@types/js-yaml@^4.0.3":
version "4.0.3"
resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.3.tgz#9f33cd6fbf0d5ec575dc8c8fc69c7fec1b4eb200"
integrity sha512-5t9BhoORasuF5uCPr+d5/hdB++zRFUTMIZOzbNkr+jZh3yQht4HYbRDyj9fY8n2TZT30iW9huzav73x4NikqWg==
"@types/json-schema@^7.0.7": "@types/json-schema@^7.0.7":
version "7.0.9" version "7.0.9"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
@ -1206,6 +1219,13 @@
"@types/node" "*" "@types/node" "*"
"@types/ssh2-streams" "*" "@types/ssh2-streams" "*"
"@types/through@*":
version "0.0.30"
resolved "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^4.30.0": "@typescript-eslint/eslint-plugin@^4.30.0":
version "4.30.0" version "4.30.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.30.0.tgz#4a0c1ae96b953f4e67435e20248d812bfa55e4fb" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.30.0.tgz#4a0c1ae96b953f4e67435e20248d812bfa55e4fb"
@ -1453,6 +1473,11 @@ argparse@^1.0.7:
dependencies: dependencies:
sprintf-js "~1.0.2" sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
array-differ@^3.0.0: array-differ@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b"
@ -3509,6 +3534,26 @@ inquirer@^7.3.3:
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
through "^2.3.6" through "^2.3.6"
inquirer@^8.1.2:
version "8.1.2"
resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.1.2.tgz#65b204d2cd7fb63400edd925dfe428bafd422e3d"
integrity sha512-DHLKJwLPNgkfwNmsuEUKSejJFbkv0FMO9SMiQbjI3n5NQuCrSIBqP66ggqyz2a6t2qEolKrMjhQ3+W/xXgUQ+Q==
dependencies:
ansi-escapes "^4.2.1"
chalk "^4.1.1"
cli-cursor "^3.1.0"
cli-width "^3.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.21"
mute-stream "0.0.8"
ora "^5.3.0"
run-async "^2.4.0"
rxjs "^7.2.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
through "^2.3.6"
internal-slot@^1.0.3: internal-slot@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
@ -3843,6 +3888,13 @@ js-yaml@^3.13.1, js-yaml@^3.14.0:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
jsbn@~0.1.0: jsbn@~0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -4154,7 +4206,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.7.0: lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21" version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -4875,7 +4927,7 @@ optionator@^0.9.1:
type-check "^0.4.0" type-check "^0.4.0"
word-wrap "^1.2.3" word-wrap "^1.2.3"
ora@^5.2.0: ora@^5.2.0, ora@^5.3.0:
version "5.4.1" version "5.4.1"
resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
@ -5705,13 +5757,20 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
rxjs@^6.6.0, rxjs@^6.6.7: rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.7:
version "6.6.7" version "6.6.7"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
rxjs@^7.2.0:
version "7.3.0"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.3.0.tgz#39fe4f3461dc1e50be1475b2b85a0a88c1e938c6"
integrity sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw==
dependencies:
tslib "~2.1.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@ -6460,6 +6519,11 @@ tslib@^2.3.1:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tslib@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tsutils@^3.21.0: tsutils@^3.21.0:
version "3.21.0" version "3.21.0"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@ -6479,7 +6543,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
typanion@^3.3.1: typanion@^3.3.1, typanion@^3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.npmjs.org/typanion/-/typanion-3.3.2.tgz#c31f3b2afb6e8ae74dbd3f96d5b1d8f9745e483e" resolved "https://registry.npmjs.org/typanion/-/typanion-3.3.2.tgz#c31f3b2afb6e8ae74dbd3f96d5b1d8f9745e483e"
integrity sha512-m3v3wtFc6R0wtl0RpEn11bKXIOjS1zch5gmx0zg2G5qfGQ3A9TVZRMSL43O5eFuGXsrgzyvDcGRmSXGP5UqpDQ== integrity sha512-m3v3wtFc6R0wtl0RpEn11bKXIOjS1zch5gmx0zg2G5qfGQ3A9TVZRMSL43O5eFuGXsrgzyvDcGRmSXGP5UqpDQ==