import path from 'path' import fs from 'fs-extra' import execa from 'execa' import log, { logBlankLine } from './log' import { isMac, isCloud } from './env' import { logError } from './error' export function binExistsSync({ cwd = process.cwd(), bin, }: { cwd?: string bin: string }) { return fs.existsSync(path.join(cwd, 'node_modules', bin)) } export function resolveBin({ cwd = process.cwd(), bin, }: { cwd?: string bin: string }): string { if (path.isAbsolute(bin)) { return bin } const absBinPath = path.join(cwd, 'node_modules', bin) if (!fs.existsSync(absBinPath)) { log.error('bin', 'resolveBin err, could not find ' + absBinPath) process.exit(1) } return absBinPath } export interface execBinOptions { global?: boolean binPath?: string // alias to binPath bin?: string args?: string[] // default to bin binName?: string // alias to binName name?: string cwd?: string exitWhenErr?: boolean } export async function execBin({ global, binPath, bin = binPath, args, binName = bin || binPath, name = binName, cwd, exitWhenErr = true, }: execBinOptions) { try { if (!bin) { throw new Error('bin is needed') } args = args || [] let absBinPath = global ? bin : resolveBin({ cwd, bin }) // global args if (global && !args.length) { args = process.argv.slice(3) } // 使用项目自带的 tnpm node 来统一执行环境, 只统一 mac 即可 // win 太麻烦了, 别管了 // 云环境执行的是 tnpm run build, 会使用自带的 node, 所以不用管 const binNode = 'node/bin/node' if (isMac && !isCloud && !global && binExistsSync({ cwd, bin: binNode })) { // !!! order maters args.unshift(absBinPath) absBinPath = resolveBin({ cwd, bin: binNode }) } logBlankLine() log.info('bin', absBinPath + ' ' + args.join(' ')) await execa(absBinPath, args, { cwd, stdio: 'inherit' }) } catch (err: unknown) { log.error('bin', `execBin err, ${name || ''}`) console.error(err) // eslint-disable-line no-console if (exitWhenErr !== false) { log.error('bin', 'process exit 1') // eslint-disable-line no-console // 调用 process.exit 不生效, process 还是正常退出了, 额外指定一下 process.exitCode 有效, WTF process.exitCode = 42 process.exit(42) } } } export async function execGlobalBin(options: execBinOptions) { return execBin({ ...options, global: true }) }