设置全局的 targetPath
// 设置全局的 targetPath
program.on('option:targetPath', () => {
process.env.CLI_TARGET_PATH = params.targetPath;
});
新建一个 exec
包,动态加载命令
lerna create @hzw-cli-dev/exec
新建一个 package
包,放在 models
目录下面
lerna create @hzw-cli-dev/package
新建一个 command
包,放在 models
目录下面
lerna create @hzw-cli-dev/command
const npmInstall = require('npminstall');
const path = require('path');
const userHome = require('user-home');
// 直接调用安装方法
npmInstall({
root: path.resolve(userHome, '.hzw-cli-dev'), // 模块路径
storeDir: path.resolve(userHome, '', 'node_modules'), // 模块安装位置
register: 'https://registry.npmjs.org', // 设置 npm 源
pkgs: [ // 要安装的包信息
{
name: 'warbler-js',
version: '',
},
],
});
'use strict';
const { isObject } = require('@hzw-cli-dev/utils');
const { getRegister, getLatestVersion } = require('@hzw-cli-dev/get-npm-info');
const formatPath = require('@hzw-cli-dev/format-path');
const npmInstall = require('npminstall');
const fse = require('fs-extra');
const pathExists = require('path-exists').sync;
const pkgDir = require('pkg-dir').sync;
const path = require('path');
// Package 类 管理模块
class Package {
/**
* @description: 构造函数
* @param {*} options 用户传入的配置信息
* @return {*}
*/
constructor(options) {
if (!options) {
throw new Error('Package 类的参数不能为空!');
}
if (!isObject(options)) {
throw new Error('Package 类的参数必须是对象类型!');
}
// 获取 targetPath ,如果没有 则说明不是一个本地的package
this.targetPath = options.targetPath;
// 模块安装位置 缓存路径
this.storeDir = options.storeDir;
// package 的 name
this.packageName = options.packageName;
// package 的 Version
this.packageVersion = options.packageVersion;
// 缓存路径的前缀
this.cacheFilePathPrefix = this.packageName.replace('/', '_');
}
/**
* @description: 准备工作
* @param {*}
* @return {*}
*/
async prepare() {}
/**
* @description: 获取当前模块缓存路径
* @param {*}
* @return {*}
*/
get cacheFilePath() {}
/**
* @description: 获取最新版本模块缓存路径
* @param {*}
* @return {*}
*/
getSpecificFilePath(packageVersion) {}
/**
* @description: 判断当前 package 是否存在
* @param {*}
* @return {*}
*/
async exists() {}
/**
* @description: 安装 package
* @param {*}
* @return {*}
*/
async install() {}
/**
* @description: 更新 package
* @param {*}
* @return {*}
*/
async update() {}
/**
* @description:获取入口文件的路径
* 1.获取package.json所在的目录 pkg-dir
* 2.读取package.json
* 3.找到main或者lib属性 形成路径
* 4.路径的兼容(macOs/windows)
* @param {*}
* @return {*}
*/
getRootFilePath() {}
module.exports = Package;
/**
* @description: 准备工作
* @param {*}
* @return {*}
*/
async prepare() {
// 当缓存目录不存在的时候
if (this.storeDir && !pathExists(this.storeDir)) {
// 创建缓存目录
fse.mkdirpSync(this.storeDir);
}
// 获取最新版本
const latestVersion = await getLatestVersion(this.packageName);
// 如果设定的版本号是最新的话,就赋值
if (this.packageVersion === 'latest') {
this.packageVersion = latestVersion;
}
}
/**
* @description: 获取当前模块缓存路径
* @param {*}
* @return {*}
*/
get cacheFilePath() {
return path.resolve(
this.storeDir,
`_${this.cacheFilePathPrefix}@${this.packageVersion}@${this.packageName}`,
);
}
/**
* @description: 获取最新版本模块缓存路径
* @param {*}
* @return {*}
*/
getSpecificFilePath(packageVersion) {
return path.resolve(
this.storeDir,
`_${this.cacheFilePathPrefix}@${packageVersion}@${this.packageName}`,
);
}
/**
* @description: 判断当前 package 是否存在
* @param {*}
* @return {*}
*/
async exists() {
// 如果 this.storeDir 存在 ,就是需要下载安装,否则就是本地安装
if (this.storeDir) {
// 获取具体版本号
await this.prepare();
return pathExists(this.cacheFilePath);
} else {
// 查看本地路径是否存在
return pathExists(this.targetPath);
}
}
/**
* @description: 安装 package
* @param {*}
* @return {*}
*/
async install() {
await this.prepare();
return npmInstall({
root: this.targetPath, // 模块路径
storeDir: this.storeDir, // 模块安装位置
register: getRegister('npm'), // 设置 npm 源
pkgs: [
// 要安装的包信息
{
name: this.packageName,
version: this.packageVersion,
},
],
});
}
/**
* @description: 更新 package
* @param {*}
* @return {*}
*/
async update() {
await this.prepare();
// 获取最新版本号
const latestVersion = await getLatestVersion(this.packageName);
// 查询本地是否已经是最新版本
const localPath = this.getSpecificFilePath(latestVersion);
const isLocalLatestVersion = pathExists(localPath);
console.log('🚀🚀 ~ Package ~ latestVersion', latestVersion);
console.log('🚀🚀 ~ Package ~ localPath', localPath);
console.log('🚀🚀 ~ Package ~ isLocalLatestVersion', isLocalLatestVersion);
// 如果不是最新版本 安装最新版本
if (!isLocalLatestVersion) {
await npmInstall({
root: this.targetPath, // 模块路径
storeDir: this.storeDir, // 模块安装位置
register: getRegister('npm'), // 设置 npm 源
pkgs: [
// 要安装的包信息
{
name: this.packageName,
version: latestVersion,
},
],
});
this.packageVersion = latestVersion;
}
}
/**
* @description:获取入口文件的路径
* 1.获取package.json所在的目录 pkg-dir
* 2.读取package.json
* 3.找到main或者lib属性 形成路径
* 4.路径的兼容(macOs/windows)
* @param {*}
* @return {*}
*/
getRootFilePath() {
function _getRootFIle(_path) {
const dir = pkgDir(_path);
if (dir) {
const pkgFile = require(path.resolve(dir, 'package.json'));
if (pkgFile && (pkgFile.main || pkgFile.lib)) {
const rootPath =
formatPath(path.resolve(dir, pkgFile.main)) ||
formatPath(path.resolve(dir, pkgFile.lib));
return rootPath;
}
}
return null;
}
// 如果 this.storeDir 存在 ,就是需要下载安装,否则就是本地安装
if (this.storeDir) {
return _getRootFIle(this.cacheFilePath);
}
return _getRootFIle(this.targetPath);
}
'use strict';
// 引入版本比对第三方库 semver
const semver = require('semver');
const LOWEST_NODE_VERSION = '12.0.0';
// 引入颜色库 colors
const colors = require('colors/safe');
const log = require('@hzw-cli-dev/log');
const { isArray } = require('@hzw-cli-dev/utils');
class Command {
/**
* @description: 命令的准备和执行阶段
* @param {*} argv : projectName 项目名称 , 命令的 options , commander实例
* @return {*}
*/
constructor(argv) {
if (!argv) {
throw new Error('参数不可以为空!');
}
if (!isArray(argv)) {
throw new Error('参数必须是数组类型!');
}
if (argv.length < 1) {
throw new Error('参数列表不可以为空!');
}
this._argv = argv;
let runner = new Promise((resolve, reject) => {
let chain = Promise.resolve();
chain = chain.then(() => this.checkNodeVersion());
chain = chain.then(() => this.initArgs());
chain = chain.then(() => this.init());
chain = chain.then(() => this.exec());
chain.catch((error) => log.error(error.message));
});
}
/**
* @description: 检查当前的 node 版本,防止 node 版本过低调用最新 api 出错
* @param {*}
* @return {*}
*/
checkNodeVersion() {
// 获取当前 node 版本号
const currentVersion = process.version;
log.test('环境检查 当前的node版本是:', process.version);
// 获取最低 node 版本号
const lowestVersion = LOWEST_NODE_VERSION;
// 对比最低 node 版本号
if (!semver.gte(currentVersion, lowestVersion)) {
throw new Error(colors.red('错误:node版本过低'));
}
}
/**
* @description:初始化参数
* @param {*}
* @return {*}
*/
initArgs() {
this._cmd = this._argv[this._argv.length - 1];
this._argv = this._argv.slice(0, this._argv.length - 1);
}
init() {
throw new Error('command 必须拥有一个 init 方法');
}
exec() {
throw new Error('command 必须拥有一个 exec 方法');
}
}
module.exports = Command;
注:第四周
第五章全部以及第七章全部
没看懂,后面需要再复习一下,但是不影响主课程的进行
'use strict';
const Package = require('@hzw-cli-dev/package');
const log = require('@hzw-cli-dev/log');
const path = require('path');
const cp = require('child_process');
// package 的映射表
const SETTINGS = {
// init: '@hzw-cli-dev/init',
init: '@imooc-cli/init',
};
// 缓存目录
const CACHE_DIR = 'dependencies';
/**
* @description: 动态加载命令
* 1.获取 targetPath
* 2.获取 modulePath
* 3.生成一个 package
* 4.提供一个 getRootFIle 方法获取入口文件
* 5.提供 update 更新方法以及 install 安装方法
* @return {*}
*/
async function exec(...argv) {
// 获取 targetPath
let targetPath = process.env.CLI_TARGET_PATH;
// 获取 homePath
const homePath = process.env.CLI_HOME_PATH;
log.verbose('targetPath', targetPath);
log.verbose('homePath', homePath);
// 获取 commander 实例
const cmdObj = argv[argv.length - 1];
// 获取命令名称 exp:init
const cmdName = cmdObj.name();
// 获取 package 的 name exp:根据 init 命令 映射到 @hzw-cli-dev/init 包
const packageName = SETTINGS[cmdName];
// 获取 package 的 version
const packageVersion = 'latest';
// 模块安装路径
let storeDir = '';
// package 类
let pkg = '';
// 如果 targetPath 不存在
if (!targetPath) {
targetPath = path.resolve(homePath, CACHE_DIR); // 生成缓存路径
storeDir = path.resolve(targetPath, 'node_modules');
// 创建 Package 对象
pkg = new Package({
storeDir,
targetPath,
packageName,
packageVersion,
});
// 如果当前 package 存在
if (await pkg.exists()) {
// 更新 package
pkg.update();
} else {
// 安装 package
await pkg.install();
}
} else {
// 如果 targetPath 存在
pkg = new Package({
targetPath,
packageName,
packageVersion,
});
}
// 获取模块入口
const rootFile = pkg.getRootFilePath();
if (!rootFile) {
throw new Error('模块不存在入口文件!');
}
// 执行模块
try {
// 在当前进程中调用
// rootFile && require(rootFile)(argv);
// 在 node 子进程中调用
const cmd = argv[argv.length - 1];
const newCmd = Object.create(null);
// 给参数瘦身
Object.keys(cmd).forEach((key) => {
if (cmd.hasOwnProperty(key) && !key.startsWith('_') && key !== 'parent') {
newCmd[key] = cmd[key];
}
});
argv[argv.length - 1] = newCmd;
// 组合code
const code = `require('${rootFile}')(${JSON.stringify(argv)})`;
const childProcess = spawn('node', ['-e', code], {
cwd: process.cwd(),
stdio: 'inherit',
});
childProcess.on('error', (error) => {
log.error(error.message);
process.exit(1);
});
childProcess.on('exit', (e) => {
log.verbose('命令执行成功');
process.exit(e);
});
} catch (error) {
console.log(error.message);
}
}
/**
* @description: 封装一个 spawn 方法,兼容 mac 和 windows
* windows : cp.spawn('cmd',['/c','node','-e',code],{})
* mac : cp.spawn('node', ['-e', code],{})
* @param {*} command 'cmd'
* @param {*} args ['/c','node','-e',code]
* @param {*} options {}
* @return {*} cp.spawn(cmd, cmdArgs, options)
*/
/** 所有可能的值
const process = require('process');
var platform = process.platform;
switch (platform) {
case 'aix':
console.log('IBM AIX platform');
break;
case 'darwin':
console.log('Darwin platform(MacOS, IOS etc)');
break;
case 'freebsd':
console.log('FreeBSD Platform');
break;
case 'linux':
console.log('Linux Platform');
break;
case 'openbsd':
console.log('OpenBSD platform');
break;
case 'sunos':
console.log('SunOS platform');
break;
case 'win32':
console.log('windows platform');
break;
default:
console.log('unknown platform');
}
*/
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;