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);}//继承Commandclass 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" }