脚手架命令注册和执行过程开发

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 源码

待更新...

Copyright © imooc-lego (2020 - present) all right reserved,powered by GitbookFile Modify: 2021-06-27 08:04:57

results matching ""

    No results matching ""