1、lerna入口是lerna/core/lerna/cli.js这个文件
#!/usr/bin/env node //执行node命令
"use strict";
/* eslint-disable import/no-dynamic-require, global-require */
//当我们本地node_modules存在一个脚手架命令,同时全局在node_modules中也存在这个脚手架命令的时候,优先选用本地node_modules中的版本
const importLocal = require("import-local");
if (importLocal(__filename)) {
require("npmlog").info("cli", "using local version of lerna");
} else {
//.相当于./index.js
require(".")(process.argv.slice(2));
}
2、进入lerna/core/lerna/index.js
"use strict";
//依次加载相应命令
const cli = require("@lerna/cli");
const addCmd = require("@lerna/add/command");
const bootstrapCmd = require("@lerna/bootstrap/command");
const changedCmd = require("@lerna/changed/command");
const cleanCmd = require("@lerna/clean/command");
const createCmd = require("@lerna/create/command");
const diffCmd = require("@lerna/diff/command");
const execCmd = require("@lerna/exec/command");
const importCmd = require("@lerna/import/command");
const infoCmd = require("@lerna/info/command");
const initCmd = require("@lerna/init/command");
const linkCmd = require("@lerna/link/command");
const listCmd = require("@lerna/list/command");
const publishCmd = require("@lerna/publish/command");
const runCmd = require("@lerna/run/command");
const versionCmd = require("@lerna/version/command");
const pkg = require("./package.json");
module.exports = main;
function main(argv) {
const context = {
lernaVersion: pkg.version,
};
return cli()
//添加命令
.command(addCmd)
.command(bootstrapCmd)
.command(changedCmd)
.command(cleanCmd)
.command(createCmd)
.command(diffCmd)
.command(execCmd)
.command(importCmd)
.command(infoCmd)
.command(initCmd)
.command(linkCmd)
.command(listCmd)
.command(publishCmd)
.command(runCmd)
.command(versionCmd)
// 合并参数 , 将 argv 和自定义的 context 中的属性合并到 argv 中
.parse(argv, context);
}
3、进入@lerna/cli/index.js
"use strict";
const dedent = require("dedent");
const log = require("npmlog");
const yargs = require("yargs/yargs");
const { globalOptions } = require("@lerna/global-options");
module.exports = lernaCLI;
/**
* A factory that returns a yargs() instance configured with everything except commands.
* Chain .parse() from this method to invoke.
*
* @param {Array = []} argv
* @param {String = process.cwd()} cwd
*/
function lernaCLI(argv, cwd) {
// 对 yargs 进行初始化
const cli = yargs(argv, cwd);
//globalOptions 也是一个方法 把 yargs 作为参数传入 返回的还是这个 yargs 对象
//运用构造者模式,对一个对象调用方法,然后返回这个对象本身
return globalOptions(cli)
/// 配置 cli 的开始内容 $0 表示再 argv 中寻找 $0 这个值进行替换
.usage("Usage: $0 <command> [options]")
// 配置输入的最小命令,为1,否则弹出提示
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
// 配置 command 的与错误最相近的 command 提示
.recommendCommands()
// 开启严格模式
.strict()
//命令不存在时,弹出警告
.fail((msg, err) => {
// certain yargs validations throw strings :P
const actual = err || new Error(msg);
// ValidationErrors are already logged, as are package errors
if (actual.name !== "ValidationError" && !actual.pkg) {
// the recommendCommands() message is too terse
if (/Did you mean/.test(actual.message)) {
log.error("lerna", `Unknown command "${cli.parsed.argv._[0]}"`);
}
//日记打印
log.error("lerna", actual.message);
}
// exit non-zero so the CLI can be usefully chained
cli.exit(actual.exitCode > 0 ? actual.exitCode : 1, actual);
})
//命令别名
.alias("h", "help")
.alias("v", "version")
// 配置cli的宽度和命令行一样
.wrap(cli.terminalWidth())
// 配置 cli 结尾的内容
.epilogue(dedent`
When a command fails, all logs are written to lerna-debug.log in the current working directory.
For more information, find our manual at https://github.com/lerna/lerna
`);
}
4、进入@lerna/global-options/index.js
"use strict";
const os = require("os");
module.exports.globalOptions = globalOptions;
function globalOptions(yargs) {
// 适用于 每一条命令的全局选项
const opts = {
loglevel: {
defaultDescription: "info",
describe: "What level of logs to report.",
type: "string",
},
concurrency: {
defaultDescription: os.cpus().length,
describe: "How many processes to use when lerna parallelizes tasks.",
type: "number",
requiresArg: true,
},
"reject-cycles": {
describe: "Fail if a cycle is detected among dependencies.",
type: "boolean",
},
"no-progress": {
describe: "Disable progress bars. (Always off in CI)",
type: "boolean",
},
progress: {
// proxy for --no-progress
hidden: true,
type: "boolean",
},
"no-sort": {
describe: "Do not sort packages topologically (dependencies before dependents).",
type: "boolean",
},
sort: {
// proxy for --no-sort
hidden: true,
type: "boolean",
},
"max-buffer": {
describe: "Set max-buffer (in bytes) for subcommand execution",
type: "number",
requiresArg: true,
},
};
// 得到这个option的名称
const globalKeys = Object.keys(opts).concat(["help", "version"]);
// 给 yargs 添加 全局options
return yargs.options(opts)
// 对 options 进行分组
.group(globalKeys, "Global Options:")
// 添加了一个隐藏的 option
.option("ci", {
hidden: true,
type: "boolean",
});
}
5、从2,进入@lerna/list/command中
"use strict";
//需要过滤的lerna子命令的选项
const { filterOptions } = require("@lerna/filter-options");
const listable = require("@lerna/listable");
/**
* @see https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module
*/
//command 的格式
exports.command = "list";
exports.aliases = ["ls", "la", "ll"];
//对 command 的描述
exports.describe = "List local packages";
//builder 函数,在执行命令之前做的一些事情
exports.builder = (yargs) => {
listable.options(yargs);
return filterOptions(yargs);
};
//handler 函数,执行 command 的行为
exports.handler = function handler(argv) {
//调index.js函数,并把argv传入
return require(".")(argv);
};
//返回一个对象,包含command、aliases、describe等方法
7、从handler进入index.js,执行factory方法
"use strict";
const { Command } = require("@lerna/command");
const listable = require("@lerna/listable");
const { output } = require("@lerna/output");
const { getFilteredPackages } = require("@lerna/filter-options");
module.exports = factory;
function factory(argv) {
//实例化一个 ListCommand
return new ListCommand(argv);
}
//继承Command
class ListCommand extends Command {
get requiresGit() {
return false;
}
initialize() {
// 也是通过 chain 微任务队列的方式
let chain = Promise.resolve();
chain = chain.then(() => getFilteredPackages(this.packageGraph, this.execOpts, this.options));
chain = chain.then((filteredPackages) => {
this.result = listable.format(filteredPackages, this.options);
});
return chain;
}
execute() {
// piping to `wc -l` should not yield 1 when no packages matched
if (this.result.text.length) {
output(this.result.text);
}
this.logger.success(
"found",
"%d %s",
this.result.count,
this.result.count === 1 ? "package" : "packages"
);
}
}
module.exports.ListCommand = ListCommand;
8、继承Command
"use strict";
class Command {
constructor(_argv) {
//深拷贝argv
const argv = cloneDeep(_argv);
// 对constructor的name进行处理,ListCommand变成list "FooCommand" => "foo"
this.name = this.constructor.name.replace(/Command$/, "").toLowerCase();
// 添加 composed 属性, 是否使用复合指令
this.composed = typeof argv.composed === "string" && argv.composed !== this.name;
//如果不是复合指令
if (!this.composed) {
// composed commands have already logged the lerna version
log.notice("cli", `v${argv.lernaVersion}`);
}
// 执行命令
let runner = new Promise((resolve, reject) => {
// 定义一个微任务 chain.then 会被加入到微任务队列,不会立即执行
let chain = Promise.resolve();
s // 会行程队列,一个接一个执行
chain = chain.then(() => {
this.project = new Project(argv.cwd);
});
chain = chain.then(() => this.configureEnvironment());
chain = chain.then(() => this.configureOptions());
chain = chain.then(() => this.configureProperties());
chain = chain.then(() => this.configureLogging());
chain = chain.then(() => this.runValidations());
chain = chain.then(() => this.runPreparations());
//执行命令
chain = chain.then(() => this.runCommand());
...
// argv中顶一个cwd和$0的参数
for (const key of ["cwd", "$0"]) {
Object.defineProperty(argv, key, { enumerable: false });
}
// 对 argv 属性做一些处理
Object.defineProperty(this, "argv", {
value: Object.freeze(argv),
});
// 对 runner 属性做一些处理
Object.defineProperty(this, "runner", {
value: runner,
});
}
...
//核心内容
runCommand() {
return Promise.resolve()
// 调用 initialize 方法
.then(() => this.initialize())
.then((proceed) => {
if (proceed !== false) {
// 调用 execute方法
return this.execute();
}
// early exits set their own exitCode (if non-zero)
});
}
// initialize 和 execute 强制用户实现,否则会报错
initialize() {
throw new ValidationError(this.name, "initialize() needs to be implemented.");
}
execute() {
throw new ValidationError(this.name, "execute() needs to be implemented.");
}
}
module.exports.Command = Command;
6、注意:npm项目本地依赖引用方法
"dependencies": {
//依赖本地的文件相对于npm link,不用将文件导入到全局,占内存,更推荐
"@lerna/global-options": "file:../global-options",
"dedent": "^0.7.0",
"npmlog": "^4.1.2",
"yargs": "^16.2.0"
}