脚手架命令注册和执行过程开发
weilai-cli 脚手架动态命令执行代码
'use strict';
const cp = require('child_process')
const path = require('path')
const log = require('@weilai-cli/log')
const Package = require('@weilai-cli/package')
const SETTINGS = {
init: '@weilai-cli/init'
}
const CHCHE_DIR = 'dependencies'
async function exec(...argm) {
let storePath, pkg
let targetPath = process.env.CLI_TARGET_PATH
const homePath = process.env.CLI_HOME_PATH
log.verbose('targetPath', targetPath)
log.verbose('homePath', homePath)
const cmdObj = argm[argm.length - 1]
const cmdName = cmdObj.name()
const packageName = SETTINGS[cmdName]
const packageVersion = 'latest'
if(!targetPath) {
targetPath = path.resolve(homePath, CHCHE_DIR)
storePath = path.resolve(homePath, 'node_modules')
log.verbose('targetPath', targetPath)
log.verbose('storePath', storePath)
pkg = new Package({
targetPath,
storePath,
packageName,
packageVersion
})
if(await pkg.exists()) {
// 更新
log.verbose('package', '更新')
await pkg.update()
} else {
// 安装
log.verbose('package', '安装')
await pkg.install()
}
} else {
pkg = new Package({
targetPath,
packageName,
packageVersion
})
}
const rootFile = pkg.getRootFile()
if(rootFile) {
try {
// 当前进程
// rootFile && require(rootFile)(argm)
// 子进程
const o = Object.create(null)
Object.keys(cmdObj).forEach(key => {
if(
cmdObj.hasOwnProperty(key) &&
!key.startsWith('_') &&
key !== 'parent'
) {
o[key] = cmdObj[key]
}
})
argm[argm.length - 1] = o
const code = `require('${rootFile}')(${JSON.stringify(argm)})`
const child = spawn('node', [ '-e', code ], {
cwd: process.cwd(),
stdio: 'inherit' // 这个属性是把子进程的输出流直接挂载到父进程
})
child.on('error', e => {
log.error(e.message)
process.exit(1)
})
child.on('exit', e => {
log.verbose('命令执行成功:', e)
process.exit(e)
})
} catch(err) {
log.error(err.message)
}
}
}
function spawn(command, args, options) {
const win32 = process.platform === 'win32'
const cmd = win32 ? 'cmd' : command
const cmdArgs = win32 ? ['/c'].concat(command, args) : args
return cp.spawn(cmd, cmdArgs, options || {})
}
module.exports = exec;
// package
'use strict';
const path = require('path')
const pkgDir = require('pkg-dir')
const fsExtra = require('fs-extra')
const npminstall = require('npminstall')
const pathExists = require('path-exists')
const { isObject } = require('@weilai-cli/utils')
const formatPath = require('@weilai-cli/format-path')
const {
getDefaultRegistry,
getNpmLatestVersion
} = require('@weilai-cli/get-npm-info')
class Package {
constructor(options) {
if(!options) throw new Error('Package 类的 options 参数不能为空')
if(!isObject(options)) throw new Error('Package 类的 options 参数必须是对象')
// 路径
this.targetPath = options.targetPath
// 存储路径
this.storePath = options.storePath
// 名称
this.packageName = options.packageName
// 版本号
this.packageVersion = options.packageVersion
// 缓存目录的前缀
this.cacheFilePathPrefix = this.packageName.replace('/', '_')
}
async prepare() {
if(this.storePath && !pathExists.sync(this.storePath)) {
fsExtra.mkdirpSync(this.storePath)
}
if(this.packageVersion === 'latest') {
this.packageVersion = await getNpmLatestVersion(this.packageVersion)
}
}
// 获取缓存文件的路径
get cacheFilePath() {
return path.resolve(
this.storePath,
`_${this.cacheFilePathPrefix}@${this.packageVersion}@${this.packageName}`
)
}
getSpecificCacheFilePath(packageVersion) {
return path.resolve(
this.storePath,
`_${this.cacheFilePathPrefix}@${packageVersion}@${this.packageName}`
)
}
// 判断当前 package 是否存在
async exists() {
if(this.storePath) {
await this.prepare()
console.log('cacheFilePath', this.cacheFilePath)
return pathExists.sync(this.cacheFilePath)
} else {
return pathExists.sync(this.targetPath)
}
}
// 安装 package
install() {
npminstall({
root: this.targetPath,
storeDir: this.storePath,
registry: getDefaultRegistry(),
pkgs: [{
name: this.packageName,
version: this.packageVersion
}]
})
}
// 更新 package
async update() {
await this.prepare()
// 1. 获取最新的 npm 模块的版本号
const latestPackageVersion = await getNpmLatestVersion(this.packageName)
// 2. 查询最新版本号对应的路径是否存在
const latestFilePath = this.getSpecificCacheFilePath(latestPackageVersion)
// 3. 如果不存在,则直接安装最新版本
if(!pathExists.sync(latestFilePath)) {
npminstall({
root: this.targetPath,
storeDir: this.storePath,
registry: getDefaultRegistry(),
pkgs: [{
name: this.packageName,
version: latestPackageVersion
}]
})
this.packageVersion = latestPackageVersion
}
}
// 获取入口文件
getRootFile() {
function _getRootFile(targetPath) {
// 1. 获取 package.json 所在的目录
const dir = pkgDir.sync(targetPath)
if(dir) {
// 2. 读取 package.json
const pkgFile = require(path.resolve(dir, 'package.json'))
// 3. 寻找 main / bin
if(pkgFile && pkgFile.main) {
// 4. 路径的兼容
return formatPath(path.resolve(dir, pkgFile.main))
}
}
return null
}
return this.storePath
? _getRootFile(this.storePath)
: _getRootFile(this.targetPath)
}
}
module.exports = Package;
// init
'use strict';
const log = require("@weilai-cli/log")
const Command = require('@weilai-cli/command')
class initCommand extends Command {
init() {
this.projectName = this._argv[0] || ''
this.force = !!this._cmd.force
log.verbose('projectName', this.projectName)
log.verbose('force',this.force)
}
exec() {
// init 的业务逻辑
console.log('init 的业务逻辑')
}
}
function init(argv) {
return new initCommand(argv)
}
module.exports = init
module.exports.initCommand = initCommand
分析 Node 多进程 execSync/execFileSync/SpawnSync 源码
待更新...