From 2c23f444b09fbecef21e36a22a35e472cecb9cd2 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 1 Sep 2021 23:21:11 +0800 Subject: [PATCH] feat(cli): add back new command --- cli/package.json | 7 +- cli/src/index.ts | 2 + cli/src/new/cargo-config.ts | 24 ++ cli/src/new/cargo.ts | 18 ++ cli/src/new/ci-template.ts | 503 ++++++++++++++++++++++++++++++++++++ cli/src/new/ci-yml.ts | 87 +++++++ cli/src/new/index.ts | 204 +++++++++++++++ cli/src/new/indexjs.ts | 15 ++ cli/src/new/lib-rs.ts | 50 ++++ cli/src/new/npmignore.ts | 11 + cli/src/new/package.ts | 58 +++++ yarn.lock | 72 +++++- 12 files changed, 1046 insertions(+), 5 deletions(-) create mode 100644 cli/src/new/cargo-config.ts create mode 100644 cli/src/new/cargo.ts create mode 100644 cli/src/new/ci-template.ts create mode 100644 cli/src/new/ci-yml.ts create mode 100644 cli/src/new/index.ts create mode 100644 cli/src/new/indexjs.ts create mode 100644 cli/src/new/lib-rs.ts create mode 100644 cli/src/new/npmignore.ts create mode 100644 cli/src/new/package.ts diff --git a/cli/package.json b/cli/package.json index 56501772..e52b6669 100644 --- a/cli/package.json +++ b/cli/package.json @@ -33,13 +33,18 @@ }, "devDependencies": { "@octokit/rest": "^18.10.0", + "@types/inquirer": "^7.3.3", + "@types/js-yaml": "^4.0.3", "chalk": "^4.1.2", "clipanion": "^3.0.1", "debug": "^4.3.2", "fdir": "^5.1.0", + "inquirer": "^8.1.2", + "js-yaml": "^4.1.0", "putasset": "^5.0.3", "toml": "^3.0.0", - "tslib": "^2.3.1" + "tslib": "^2.3.1", + "typanion": "^3.3.2" }, "funding": { "type": "github", diff --git a/cli/src/index.ts b/cli/src/index.ts index b54662e7..38184606 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -5,6 +5,7 @@ import { Cli } from 'clipanion' import { ArtifactsCommand } from './artifacts' import { BuildCommand } from './build' import { CreateNpmDirCommand } from './create-npm-dir' +import { NewProjectCommand } from './new' import { PrePublishCommand } from './pre-publish' import { VersionCommand } from './version' @@ -18,6 +19,7 @@ cli.register(BuildCommand) cli.register(CreateNpmDirCommand) cli.register(PrePublishCommand) cli.register(VersionCommand) +cli.register(NewProjectCommand) cli .run(process.argv.slice(2), { diff --git a/cli/src/new/cargo-config.ts b/cli/src/new/cargo-config.ts new file mode 100644 index 00000000..6a1e05b5 --- /dev/null +++ b/cli/src/new/cargo-config.ts @@ -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 +} diff --git a/cli/src/new/cargo.ts b/cli/src/new/cargo.ts new file mode 100644 index 00000000..5e2e55f2 --- /dev/null +++ b/cli/src/new/cargo.ts @@ -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 +` diff --git a/cli/src/new/ci-template.ts b/cli/src/new/ci-template.ts new file mode 100644 index 00000000..b59ab28e --- /dev/null +++ b/cli/src/new/ci-template.ts @@ -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 }} +` diff --git a/cli/src/new/ci-yml.ts b/cli/src/new/ci-yml.ts new file mode 100644 index 00000000..6159423c --- /dev/null +++ b/cli/src/new/ci-yml.ts @@ -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, + }) +} diff --git a/cli/src/new/index.ts b/cli/src/new/index.ts new file mode 100644 index 00000000..619d0f69 --- /dev/null +++ b/cli/src/new/index.ts @@ -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 + } + } + } +} diff --git a/cli/src/new/indexjs.ts b/cli/src/new/indexjs.ts new file mode 100644 index 00000000..c5b2d8ff --- /dev/null +++ b/cli/src/new/indexjs.ts @@ -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}') +` diff --git a/cli/src/new/lib-rs.ts b/cli/src/new/lib-rs.ts new file mode 100644 index 00000000..b003704d --- /dev/null +++ b/cli/src/new/lib-rs.ts @@ -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 { + 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 { + 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 { + let argument: u32 = ctx.get::(0)?.try_into()?; + + ctx.env.create_uint32(argument + 100) +} + +#[js_function(1)] +fn sleep(ctx: CallContext) -> Result { + let argument: u32 = ctx.get::(0)?.try_into()?; + let task = AsyncTask(argument); + let async_task = ctx.env.spawn(task)?; + Ok(async_task.promise_object()) +} +` diff --git a/cli/src/new/npmignore.ts b/cli/src/new/npmignore.ts new file mode 100644 index 00000000..3c79319f --- /dev/null +++ b/cli/src/new/npmignore.ts @@ -0,0 +1,11 @@ +export const NPMIgnoreFiles = `target +Cargo.lock +.cargo +.github +npm +.eslintrc +.prettierignore +rustfmt.toml +yarn.lock +*.node +` diff --git a/cli/src/new/package.ts b/cli/src/new/package.ts new file mode 100644 index 00000000..9401eafd --- /dev/null +++ b/cli/src/new/package.ts @@ -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 +} diff --git a/yarn.lock b/yarn.lock index 16752716..22886c50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1127,11 +1127,24 @@ "@types/docker-modem" "*" "@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": version "2.0.3" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" 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": version "7.0.9" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -1206,6 +1219,13 @@ "@types/node" "*" "@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": version "4.30.0" 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: 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: version "3.0.0" 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" 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: version "1.0.3" 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" 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: version "0.1.1" 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" 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" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4875,7 +4927,7 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -ora@^5.2.0: +ora@^5.2.0, ora@^5.3.0: version "5.4.1" resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== @@ -5705,13 +5757,20 @@ run-parallel@^1.1.9: dependencies: 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" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: 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: version "5.2.1" 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" 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: version "3.21.0" 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" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typanion@^3.3.1: +typanion@^3.3.1, typanion@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/typanion/-/typanion-3.3.2.tgz#c31f3b2afb6e8ae74dbd3f96d5b1d8f9745e483e" integrity sha512-m3v3wtFc6R0wtl0RpEn11bKXIOjS1zch5gmx0zg2G5qfGQ3A9TVZRMSL43O5eFuGXsrgzyvDcGRmSXGP5UqpDQ==