napi-rs/cli/src/new/index.ts
2021-02-06 14:10:17 +08:00

196 lines
5.2 KiB
TypeScript

import { writeFileSync, mkdirSync } from 'fs'
import { join } from 'path'
import chalk from 'chalk'
import { Command } 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',
'armv7-unknown-linux-gnueabihf',
'x86_64-apple-darwin',
'x86_64-pc-windows-msvc',
'x86_64-unknown-linux-gnu',
'x86_64-unknown-linux-musl',
]
export class NewProjectCommand extends Command {
static usage = Command.Usage({
description: 'Create a new project from scratch',
})
@Command.String({
name: '-n,--name',
required: false,
})
name?: string
@Command.String({
name: '-d,--dirname',
required: false,
})
dirname?: string
@Command.Array('--targets,-t')
targets?: string[]
@Command.Boolean(`--dry-run`)
dryRun = false
@Command.Boolean(`--enable-github-actions`)
enableGithubActions!: boolean
@Command.Path('new')
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 enableLinuxArm8 = this.targets!.includes('aarch64-unknown-linux-gnu')
const enableLinuxArm7 = this.targets!.includes(
'armv7-unknown-linux-gnueabihf',
)
const cargoConfig = createCargoConfig(enableLinuxArm7, enableLinuxArm8)
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
}
}
}
}