refactor(cli): init yarn workspace, move napi-rs package => @napi-rs/cli
This commit is contained in:
parent
a8873525fc
commit
7767b83222
37 changed files with 6367 additions and 440 deletions
119
cli/src/__test__/parse-triple.spec.ts
Normal file
119
cli/src/__test__/parse-triple.spec.ts
Normal 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
69
cli/src/artifacts.ts
Normal 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
182
cli/src/build.ts
Normal 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
33
cli/src/consts.ts
Normal 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
84
cli/src/create-npm-dir.ts
Normal 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
3
cli/src/debug.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import debug from 'debug'
|
||||
|
||||
export const debugFactory = (namespace: string) => debug(`napi:${namespace}`)
|
32
cli/src/index.ts
Normal file
32
cli/src/index.ts
Normal 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
124
cli/src/parse-triple.ts
Normal 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
170
cli/src/pre-publish.ts
Normal 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
30
cli/src/spawn.ts
Normal 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))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
9
cli/src/update-package.ts
Normal file
9
cli/src/update-package.ts
Normal 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
6
cli/src/utils.ts
Normal 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
40
cli/src/version.ts
Normal 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 .')
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue