refactor(cli): init yarn workspace, move napi-rs package => @napi-rs/cli

This commit is contained in:
LongYinan 2020-10-15 20:16:52 +08:00 committed by LongYinan
parent a8873525fc
commit 7767b83222
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
37 changed files with 6367 additions and 440 deletions

View file

@ -0,0 +1,119 @@
import test from 'ava'
import { parseTriple, getDefaultTargetTriple } from '../parse-triple'
const triples = [
{
name: 'x86_64-unknown-linux-musl',
expected: {
abi: 'musl',
arch: 'x64',
platform: 'linux',
platformArchABI: 'linux-x64-musl',
raw: 'x86_64-unknown-linux-musl',
} as const,
},
{
name: 'x86_64-unknown-linux-gnu',
expected: {
abi: 'gnu',
arch: 'x64',
platform: 'linux',
platformArchABI: 'linux-x64-gnu',
raw: 'x86_64-unknown-linux-gnu',
} as const,
},
{
name: 'x86_64-pc-windows-msvc',
expected: {
abi: 'msvc',
arch: 'x64',
platform: 'win32',
platformArchABI: 'win32-x64-msvc',
raw: 'x86_64-pc-windows-msvc',
} as const,
},
{
name: 'x86_64-apple-darwin',
expected: {
abi: null,
arch: 'x64',
platform: 'darwin',
platformArchABI: 'darwin-x64',
raw: 'x86_64-apple-darwin',
} as const,
},
{
name: 'i686-pc-windows-msvc',
expected: {
abi: 'msvc',
arch: 'ia32',
platform: 'win32',
platformArchABI: 'win32-ia32-msvc',
raw: 'i686-pc-windows-msvc',
} as const,
},
{
name: 'x86_64-unknown-freebsd',
expected: {
abi: null,
arch: 'x64',
platform: 'freebsd',
platformArchABI: 'freebsd-x64',
raw: 'x86_64-unknown-freebsd',
} as const,
},
{
name: 'aarch64-unknown-linux-gnu',
expected: {
abi: 'gnu',
arch: 'arm64',
platform: 'linux',
platformArchABI: 'linux-arm64-gnu',
raw: 'aarch64-unknown-linux-gnu',
} as const,
},
{
name: 'aarch64-pc-windows-msvc',
expected: {
abi: 'msvc',
arch: 'arm64',
platform: 'win32',
platformArchABI: 'win32-arm64-msvc',
raw: 'aarch64-pc-windows-msvc',
} as const,
},
{
name: 'armv7-unknown-linux-gnueabihf',
expected: {
abi: 'gnueabihf',
arch: 'arm',
platform: 'linux',
platformArchABI: 'linux-arm-gnueabihf',
raw: 'armv7-unknown-linux-gnueabihf',
} as const,
},
]
for (const triple of triples) {
test(`should parse ${triple.name}`, (t) => {
t.deepEqual(parseTriple(triple.name), triple.expected)
})
}
test('should parse default triple from rustup show active', (t) => {
t.deepEqual(
getDefaultTargetTriple(
`x86_64-unknown-linux-gnu (directory override for '/home/runner/work/fast-escape/fast-escape')`,
),
parseTriple('x86_64-unknown-linux-gnu'),
)
t.deepEqual(
getDefaultTargetTriple(`stable-x86_64-apple-darwin (default)`),
parseTriple(`x86_64-apple-darwin`),
)
t.deepEqual(
getDefaultTargetTriple(`nightly-2020-08-29-x86_64-apple-darwin (default)`),
parseTriple('x86_64-apple-darwin'),
)
})

69
cli/src/artifacts.ts Normal file
View file

@ -0,0 +1,69 @@
import { join, parse } from 'path'
import chalk from 'chalk'
import { Command } from 'clipanion'
import { fdir } from 'fdir'
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { readFileAsync, writeFileAsync } from './utils'
const debug = debugFactory('artifacts')
export class ArtifactsCommand extends Command {
@Command.String('-d,--dir')
sourceDir = 'artifacts'
@Command.String('--dist')
distDir = 'npm'
@Command.String('-c,--config')
configFileName?: string
@Command.Path('artifacts')
async execute() {
const { platforms, binaryName, packageJsonPath } = getNapiConfig(
this.configFileName,
)
const packageJsonDir = parse(packageJsonPath).dir
const sourceApi = new fdir()
.withFullPaths()
.crawl(join(process.cwd(), this.sourceDir))
const distDirs = platforms.map((platform) =>
join(process.cwd(), this.distDir, platform.platformArchABI),
)
await sourceApi.withPromise().then((output) =>
Promise.all(
(output as string[]).map(async (filePath) => {
debug(`Read [${chalk.yellowBright(filePath)}]`)
const sourceContent = await readFileAsync(filePath)
const parsedName = parse(filePath)
const [_binaryName, platformArchABI] = parsedName.name.split('.')
if (_binaryName !== binaryName) {
debug(
`[${chalk.yellowBright(
_binaryName,
)}] is not matched with [${chalk.greenBright(binaryName)}], skip`,
)
}
const dir = distDirs.find((dir) => dir.includes(platformArchABI))
if (!dir) {
throw new TypeError(`No dist dir found for ${filePath}`)
}
const distFilePath = join(dir, parsedName.base)
debug(`Write file content to [${chalk.yellowBright(distFilePath)}]`)
await writeFileAsync(distFilePath, sourceContent)
const distFilePathLocal = join(packageJsonDir, parsedName.base)
debug(
`Write file content to [${chalk.yellowBright(distFilePathLocal)}]`,
)
await writeFileAsync(distFilePathLocal, sourceContent)
}),
),
)
}
}

182
cli/src/build.ts Normal file
View file

@ -0,0 +1,182 @@
import { execSync } from 'child_process'
import os from 'os'
import { join, parse, sep } from 'path'
import chalk from 'chalk'
import { Command } from 'clipanion'
import toml from 'toml'
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { getDefaultTargetTriple, parseTriple } from './parse-triple'
import { existsAsync, readFileAsync, writeFileAsync } from './utils'
const debug = debugFactory('build')
export class BuildCommand extends Command {
static usage = Command.Usage({
description: 'Copy native module into specified dir',
})
@Command.Boolean(`--platform`)
appendPlatformToFilename = false
@Command.Boolean(`--release`)
isRelease = false
@Command.String('--config,-c')
configFileName?: string
@Command.String('--cargo-name')
cargoName?: string
@Command.String('--target')
targetTripleDir = ''
@Command.String('--features')
features?: string
@Command.String('--cargo-flags')
cargoFlags = ''
@Command.String({
required: false,
})
target = '.'
@Command.Path('build')
async execute() {
const releaseFlag = this.isRelease ? `--release` : ''
const targetFLag = this.targetTripleDir
? `--target ${this.targetTripleDir}`
: ''
const featuresFlag = this.features ? `--features ${this.features}` : ''
const triple = this.targetTripleDir
? parseTriple(this.targetTripleDir)
: getDefaultTargetTriple(
execSync('rustup show active-toolchain', {
env: process.env,
}).toString('utf8'),
)
debug(`Current triple is: ${chalk.green(triple.raw)}`)
const externalFlags = [
releaseFlag,
targetFLag,
featuresFlag,
this.cargoFlags,
]
.filter((flag) => Boolean(flag))
.join(' ')
const cargoCommand = `cargo build ${externalFlags}`
debug(`Run ${chalk.green(cargoCommand)}`)
execSync(cargoCommand, {
env: process.env,
stdio: 'inherit',
})
const { binaryName } = getNapiConfig(this.configFileName)
let dylibName = this.cargoName
if (!dylibName) {
let tomlContentString: string
let tomlContent: any
try {
debug('Start read toml')
tomlContentString = await readFileAsync(
join(process.cwd(), 'Cargo.toml'),
'utf-8',
)
} catch {
throw new TypeError(`Could not find Cargo.toml in ${process.cwd()}`)
}
try {
debug('Start parse toml')
tomlContent = toml.parse(tomlContentString)
} catch {
throw new TypeError('Could not parse the Cargo.toml')
}
if (tomlContent.package?.name) {
dylibName = tomlContent.package.name.replace(/-/g, '_')
} else {
throw new TypeError('No package.name field in Cargo.toml')
}
}
debug(`Dylib name: ${chalk.greenBright(dylibName)}`)
const platform = os.platform()
let libExt
debug(`Platform: ${chalk.greenBright(platform)}`)
// Platform based massaging for build commands
switch (platform) {
case 'darwin':
libExt = '.dylib'
dylibName = `lib${dylibName}`
break
case 'win32':
libExt = '.dll'
break
case 'linux':
case 'freebsd':
case 'openbsd':
case 'sunos':
dylibName = `lib${dylibName}`
libExt = '.so'
break
default:
throw new TypeError(
'Operating system not currently supported or recognized by the build script',
)
}
const targetDir = join(
this.targetTripleDir,
this.isRelease ? 'release' : 'debug',
)
const platformName = this.appendPlatformToFilename
? `.${triple.platformArchABI}`
: ''
debug(`Platform name: ${platformName || chalk.green('[Empty]')}`)
let distModulePath = this.target
? join(this.target, `${binaryName}${platformName}.node`)
: join('target', targetDir, `${binaryName}${platformName}.node`)
const parsedDist = parse(distModulePath)
if (!parsedDist.ext) {
distModulePath = `${distModulePath}${platformName}.node`
}
const dir = await findUp()
if (!dir) {
throw new TypeError('No target dir found')
}
const sourcePath = join(dir, 'target', targetDir, `${dylibName}${libExt}`)
debug(`Read [${chalk.yellowBright(sourcePath)}] content`)
const dylibContent = await readFileAsync(sourcePath)
debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`)
await writeFileAsync(distModulePath, dylibContent)
}
}
async function findUp(dir = process.cwd()): Promise<string | null> {
const dist = join(dir, 'target')
if (await existsAsync(dist)) {
return dir
}
const dirs = dir.split(sep)
if (dirs.length < 2) {
return null
}
dirs.pop()
return findUp(dirs.join(sep))
}

33
cli/src/consts.ts Normal file
View file

@ -0,0 +1,33 @@
import { join } from 'path'
import { DefaultPlatforms, PlatformDetail, parseTriple } from './parse-triple'
export function getNapiConfig(packageJson = 'package.json') {
const packageJsonPath = join(process.cwd(), packageJson)
const pkgJson = require(packageJsonPath)
const { version: packageVersion, napi, name } = pkgJson
const additionPlatforms: PlatformDetail[] = (
napi?.triples?.additional ?? []
).map(parseTriple)
const defaultPlatforms =
napi?.triples?.defaults === false ? [] : [...DefaultPlatforms]
const platforms = [...defaultPlatforms, ...additionPlatforms]
const releaseVersion = process.env.RELEASE_VERSION
const releaseVersionWithoutPrefix = releaseVersion?.startsWith('v')
? releaseVersion.substr(1)
: releaseVersion
const version = releaseVersionWithoutPrefix ?? packageVersion
const packageName = name
const binaryName: string = napi?.name ?? 'index'
return {
platforms,
version,
packageName,
binaryName,
packageJsonPath,
content: pkgJson,
}
}

84
cli/src/create-npm-dir.ts Normal file
View file

@ -0,0 +1,84 @@
import { join } from 'path'
import chalk from 'chalk'
import { Command } from 'clipanion'
import { pick } from 'lodash'
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { PlatformDetail } from './parse-triple'
import { spawn } from './spawn'
import { writeFileAsync } from './utils'
const debug = debugFactory('create-npm-dir')
export class CreateNpmDirCommand extends Command {
@Command.String('-t,--target')
targetDir!: string
@Command.String('-c,--config')
config = 'package.json'
@Command.Path('create-npm-dir')
async execute() {
const pkgJsonDir = this.config
debug(`Read content from [${chalk.yellowBright(pkgJsonDir)}]`)
const {
platforms,
packageName,
version,
binaryName,
content,
} = getNapiConfig(pkgJsonDir)
for (const platformDetail of platforms) {
const targetDir = join(
process.cwd(),
this.targetDir,
'npm',
`${platformDetail.platformArchABI}`,
)
await spawn(`mkdir -p ${targetDir}`)
const binaryFileName = `${binaryName}.${platformDetail.platformArchABI}.node`
const targetPackageJson = join(targetDir, 'package.json')
debug(`Write file [${chalk.yellowBright(targetPackageJson)}]`)
await writeFileAsync(
targetPackageJson,
JSON.stringify(
{
name: `${packageName}-${platformDetail.platformArchABI}`,
version,
os: [platformDetail.platform],
cpu: [platformDetail.arch],
main: binaryFileName,
files: [binaryFileName],
...pick(
content,
'description',
'keywords',
'author',
'homepage',
'license',
'engines',
'publishConfig',
'repository',
'bugs',
),
},
null,
2,
),
)
const targetReadme = join(targetDir, 'README.md')
debug(`Write target README.md [${chalk.yellowBright(targetReadme)}]`)
await writeFileAsync(targetReadme, readme(packageName, platformDetail))
}
}
}
function readme(packageName: string, platformDetail: PlatformDetail) {
return `# \`${packageName}-${platformDetail.platformArchABI}\`
This is the **${platformDetail.raw}** binary for \`${packageName}\`
`
}

3
cli/src/debug.ts Normal file
View file

@ -0,0 +1,3 @@
import debug from 'debug'
export const debugFactory = (namespace: string) => debug(`napi:${namespace}`)

32
cli/src/index.ts Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env node
import { Cli } from 'clipanion'
import { ArtifactsCommand } from './artifacts'
import { BuildCommand } from './build'
import { CreateNpmDirCommand } from './create-npm-dir'
import { PrePublishCommand } from './pre-publish'
import { VersionCommand } from './version'
const cli = new Cli({
binaryName: 'bin',
binaryVersion: require('../package.json').version,
})
cli.register(ArtifactsCommand)
cli.register(BuildCommand)
cli.register(CreateNpmDirCommand)
cli.register(PrePublishCommand)
cli.register(VersionCommand)
cli
.run(process.argv.slice(2), {
...Cli.defaultContext,
})
.then((status) => {
process.exit(status)
})
.catch((e) => {
console.error(e)
process.exit(1)
})

124
cli/src/parse-triple.ts Normal file
View file

@ -0,0 +1,124 @@
import { execSync } from 'child_process'
// https://nodejs.org/api/process.html#process_process_arch
type NodeJSArch =
| 'arm'
| 'arm64'
| 'ia32'
| 'mips'
| 'mipsel'
| 'ppc'
| 'ppc64'
| 's390'
| 's390x'
| 'x32'
| 'x64'
const CpuToNodeArch: { [index: string]: NodeJSArch } = {
x86_64: 'x64',
aarch64: 'arm64',
i686: 'ia32',
armv7: 'arm',
}
const SysToNodePlatform: { [index: string]: NodeJS.Platform } = {
linux: 'linux',
freebsd: 'freebsd',
darwin: 'darwin',
windows: 'win32',
}
export interface PlatformDetail {
platform: NodeJS.Platform
platformArchABI: string
arch: NodeJSArch
raw: string
abi: string | null
}
export const DefaultPlatforms: PlatformDetail[] = [
{
platform: 'win32',
arch: 'x64',
abi: 'msvc',
platformArchABI: 'win32-x64-msvc',
raw: 'x86_64-pc-windows-msvc',
},
{
platform: 'darwin',
arch: 'x64',
abi: null,
platformArchABI: 'darwin-x64',
raw: 'x86_64-apple-darwin',
},
{
platform: 'linux',
arch: 'x64',
abi: 'gnu',
platformArchABI: 'linux-x64-gnu',
raw: 'x86_64-unknown-linux-gnu',
},
]
/**
* A triple is a specific format for specifying a target architecture.
* Triples may be referred to as a target triple which is the architecture for the artifact produced, and the host triple which is the architecture that the compiler is running on.
* The general format of the triple is `<arch><sub>-<vendor>-<sys>-<abi>` where:
* - `arch` = The base CPU architecture, for example `x86_64`, `i686`, `arm`, `thumb`, `mips`, etc.
* - `sub` = The CPU sub-architecture, for example `arm` has `v7`, `v7s`, `v5te`, etc.
* - `vendor` = The vendor, for example `unknown`, `apple`, `pc`, `nvidia`, etc.
* - `sys` = The system name, for example `linux`, `windows`, `darwin`, etc. none is typically used for bare-metal without an OS.
* - `abi` = The ABI, for example `gnu`, `android`, `eabi`, etc.
*/
export function parseTriple(triple: string): PlatformDetail {
const triples = triple.split('-')
let cpu: string
let sys: string
let abi: string | null = null
if (triples.length === 4) {
;[cpu, , sys, abi = null] = triples
} else if (triples.length === 3) {
;[cpu, , sys] = triples
} else {
;[cpu, sys] = triples
}
const platformName = SysToNodePlatform[sys] ?? sys
const arch = CpuToNodeArch[cpu] ?? cpu
return {
platform: platformName,
arch,
abi,
platformArchABI: abi
? `${platformName}-${arch}-${abi}`
: `${platformName}-${arch}`,
raw: triple,
}
}
// x86_64-unknown-linux-gnu (directory override for '/home/runner/work/fast-escape/fast-escape')
// stable-x86_64-apple-darwin (default)
// nightly-2020-08-29-x86_64-apple-darwin (default)
export function getDefaultTargetTriple(rustcfg: string): PlatformDetail {
const currentTriple = rustcfg
.trim()
.replace(/\(.*?\)/, '')
.trim()
const allTriples = execSync(`rustup target list`, {
env: process.env,
})
.toString('utf8')
.split('\n')
.map((line) =>
line
.trim()
// remove (installed) from x86_64-apple-darwin (installed)
.replace(/\(.*?\)/, '')
.trim(),
)
.filter((line) => line.length)
const triple = allTriples.find((triple) => currentTriple.indexOf(triple) > -1)
if (!triple) {
throw new TypeError(`Can not parse target triple from ${currentTriple}`)
}
return parseTriple(triple)
}

170
cli/src/pre-publish.ts Normal file
View file

@ -0,0 +1,170 @@
import { join } from 'path'
import { Octokit } from '@octokit/rest'
import chalk from 'chalk'
import { Command } from 'clipanion'
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { spawn } from './spawn'
import { updatePackageJson } from './update-package'
import { existsAsync } from './utils'
import { VersionCommand } from './version'
const debug = debugFactory('prepublish')
interface PackageInfo {
name: string
version: string
tag: string
}
export class PrePublishCommand extends Command {
static usage = Command.Usage({
description:
'Update package.json and copy addons into per platform packages',
})
@Command.String(`-p,--prefix`)
prefix = 'npm'
@Command.String('--tagstyle,-t')
tagStyle: 'npm' | 'lerna' = 'lerna'
@Command.String('-c,--config')
configFileName?: string
@Command.Boolean('--dry-run')
isDryRun = false
@Command.Path('prepublish')
async execute() {
const {
packageJsonPath,
platforms,
version,
packageName,
binaryName,
} = getNapiConfig(this.configFileName)
debug(`Update optionalDependencies in [${packageJsonPath}]`)
if (!this.isDryRun) {
await VersionCommand.updatePackageJson(this.prefix, this.configFileName)
await updatePackageJson(packageJsonPath, {
optionalDependencies: platforms.reduce(
(acc: Record<string, string>, cur) => {
acc[`${packageName}-${cur.platformArchABI}`] = `^${version}`
return acc
},
{},
),
})
}
const { owner, repo, pkgInfo } = await this.createGhRelease(
packageName,
version,
)
for (const platformDetail of platforms) {
const pkgDir = join(
process.cwd(),
this.prefix,
`${platformDetail.platformArchABI}`,
)
const filename = `${binaryName}.${platformDetail.platformArchABI}.node`
const dstPath = join(pkgDir, filename)
debug(
`Start upload [${chalk.greenBright(
dstPath,
)}] to Github release, [${chalk.greenBright(pkgInfo.tag)}]`,
)
if (!this.isDryRun) {
if (!(await existsAsync(dstPath))) {
console.warn(`[${chalk.yellowBright(dstPath)}] is not existed`)
continue
}
await spawn('npm publish', {
cwd: pkgDir,
env: process.env,
})
const putasset = require('putasset')
try {
const downloadUrl = await putasset(process.env.GITHUB_TOKEN, {
owner,
repo,
tag: pkgInfo.tag,
filename: dstPath,
})
console.info(`${chalk.green(dstPath)} upload success`)
console.info(`Download url: ${chalk.blueBright(downloadUrl)}`)
} catch (e) {
debug(
`Param: ${{ owner, repo, tag: pkgInfo.tag, filename: dstPath }}`,
)
console.error(e)
}
}
}
}
private async createGhRelease(packageName: string, version: string) {
const headCommit = (await spawn('git log -1 --pretty=%B'))
.toString('utf8')
.trim()
debug(`Github repository: ${process.env.GITHUB_REPOSITORY}`)
const [owner, repo] = process.env.GITHUB_REPOSITORY!.split('/')
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
})
let pkgInfo: PackageInfo | undefined
if (this.tagStyle === 'lerna') {
const packagesToPublish = headCommit
.split('\n')
.map((line) => line.trim())
.filter((line, index) => line.length && index)
.map((line) => line.substr(2))
.map(this.parseTag)
pkgInfo = packagesToPublish.find(
(pkgInfo) => pkgInfo.name === packageName,
)
if (!pkgInfo) {
throw new TypeError(
`No release commit found with ${packageName}, original commit info: ${headCommit}`,
)
}
} else {
pkgInfo = {
tag: `v${version}`,
version,
name: packageName,
}
}
if (!this.isDryRun) {
try {
await octokit.repos.createRelease({
owner,
repo,
tag_name: pkgInfo.tag,
})
} catch (e) {
debug(`Params: ${{ owner, repo, tag_name: pkgInfo.tag }}`)
console.error(e)
}
}
return { owner, repo, pkgInfo }
}
private parseTag(tag: string) {
const segments = tag.split('@')
const version = segments.pop()!
const name = segments.join('@')
return {
name,
version,
tag,
}
}
}

30
cli/src/spawn.ts Normal file
View file

@ -0,0 +1,30 @@
import { spawn as _spawn, SpawnOptionsWithoutStdio } from 'child_process'
import { debugFactory } from './debug'
const debug = debugFactory('spawn')
export function spawn(
command: string,
options: SpawnOptionsWithoutStdio = {},
): Promise<Buffer> {
const [cmd, ...args] = command.split(' ').map((s) => s.trim())
debug(`execute ${cmd} ${args.join(' ')}`)
return new Promise((resolve, reject) => {
const spawnStream = _spawn(cmd, args, { ...options, shell: 'bash' })
const chunks: Buffer[] = []
process.stdin.pipe(spawnStream.stdin)
spawnStream.stdout?.on('data', (chunk) => {
chunks.push(chunk)
})
spawnStream.stdout.pipe(process.stdout)
spawnStream.stderr.pipe(process.stderr)
spawnStream.on('close', (code) => {
if (code !== 0) {
reject()
} else {
resolve(Buffer.concat(chunks))
}
})
})
}

View file

@ -0,0 +1,9 @@
import { writeFileAsync } from './utils'
export async function updatePackageJson(
path: string,
partial: Record<string, any>,
) {
const old = require(path)
await writeFileAsync(path, JSON.stringify({ ...old, ...partial }, null, 2))
}

6
cli/src/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { readFile, writeFile, exists } from 'fs'
import { promisify } from 'util'
export const readFileAsync = promisify(readFile)
export const writeFileAsync = promisify(writeFile)
export const existsAsync = promisify(exists)

40
cli/src/version.ts Normal file
View file

@ -0,0 +1,40 @@
import { join } from 'path'
import chalk from 'chalk'
import { Command } from 'clipanion'
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { spawn } from './spawn'
import { updatePackageJson } from './update-package'
const debug = debugFactory('version')
export class VersionCommand extends Command {
static async updatePackageJson(prefix: string, configFileName?: string) {
const { version, platforms } = getNapiConfig(configFileName)
for (const platformDetail of platforms) {
const pkgDir = join(process.cwd(), prefix, platformDetail.platformArchABI)
debug(
`Update version to ${chalk.greenBright(
version,
)} in [${chalk.yellowBright(pkgDir)}]`,
)
await updatePackageJson(join(pkgDir, 'package.json'), {
version,
})
}
}
@Command.String(`-p,--prefix`)
prefix = 'npm'
@Command.String('-c,--config')
configFileName?: string
@Command.Path('version')
async execute() {
await VersionCommand.updatePackageJson(this.prefix, this.configFileName)
await spawn('git add .')
}
}