Read the source code:
- Download the source code
- Install dependencies
- Open the IDE (webstorm)
Source code ready-to-complete standards
- Find the entry file
- Package. The json bin configuration
"bin": {
"lerna": "core/lerna/cli.js"
}
Copy the code
- Ability to debug locally
- Use vscode debugging methods
- Root Directory Creation
.vscode
directory - Create a.vscode/launch.json file
{ "configurations": [{"type": "node"."request": "launch"."name": "nodemon"."runtimeExecutable": "nodemon"."program": "${workspaceFolder}/core/lerna/cli.js"."restart": true."console": "integratedTerminal"."internalConsoleOptions": "neverOpen".// Parameter is a group of name and value, multiple parameters, array can be added, debugging automatically attached "args": ["list"]]}}Copy the code
- Root Directory Creation
- Debug using WebStorm
- Debug method
- Use vscode debugging methods
Parsing entry files
#! /usr/bin/env node
"use strict";
/* eslint-disable impo
rt/no-dynamic-require, global-require */
const importLocal = require("import-local");
if (importLocal(__filename)) {
require("npmlog").info("cli"."using local version of lerna");
} else {
require(".")(process.argv.slice(2)); // Perform this step
}
Copy the code
- Require (‘.’) is the relative path of the current directory in index.js, equivalent to require(‘./index.js’).
- Check index. Js
"use strict";
const cli = require("@lerna/cli"); // points to core/cli/index.js, which is almost a yargs scaffolding command. It should be @lerna/cli under node_modules, but the dependency name of core's package.json file is lerna. So I went straight to core/cli/index.js
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 = { // Inject the version number
lernaVersion: pkg.version,
};
return cli()
// register yargs command
.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)
.parse(argv, context ); // Inject parameters and version numbers
}
Copy the code
- To view
const cli = require("@lerna/cli");
What does this reference do- The globalOptions(CLI) method is returned, which should be yargs registered with globalOptions
- Check the globalOptions (cli)
"use strict";
const dedent = require("dedent"); // The library that handles the indentation
const log = require("npmlog");
const yargs = require("yargs/yargs"); // Yargs scaffolding is used
const globalOptions = require("@lerna/global-options"); // Register globalOptions
module.exports = lernaCLI;
function lernaCLI(argv, cwd) {
const cli = yargs(argv, cwd);
return globalOptions(cli)
.usage("Usage: $0 <command> [options]")
.demandCommand(1."A command is required. Pass --help to see all available commands and options.")
.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.code > 0 ? actual.code : 1, actual);
})
.alias("h"."help")
.alias("v"."version")
.wrap(cli.terminalWidth()).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 `);
}
Copy the code
- Check the globaOptions
- Receive the YARgs parameter and return yargs after registering globalOptions
"use strict";
const os = require("os");
module.exports = globalOptions;
function globalOptions(yargs) {
// the global options applicable to _every_ command
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,}};// group options under "Global Options:" header
const globalKeys = Object.keys(opts).concat(["help"."version"]);
return yargs
.options(opts)
.group(globalKeys, "Global Options:")
.option("ci", {
hidden: true./ / hide option
type: "boolean"}); }Copy the code
Entry file initial resolution analysis summary
-
Use YARgs to register commands and parameters
-
Registered globalOptions
module.exports = globalOptions; function globalOptions(yargs) { // the global options applicable to _every_ command 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,}};// group options under "Global Options:" header const globalKeys = Object.keys(opts).concat(["help"."version"]); return yargs .options(opts) .group(globalKeys, "Global Options:") .option("ci", { hidden: true.type: "boolean"}); }Copy the code
-
define
- Define the use method:
.usage("Usage: $0 <command> [options]")
- Define the need to receive at least one argument:
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
- Defines the command that most closely resembles if a command or argument is entered incorrectly
recommendCommands()
-
Defines unrecognized arguments that are prompted if the entered command is not:.strict()
-
What is the error message if a command is incorrectly typed
.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.code > 0 ? actual.code : 1, actual); }) Copy the code
rainbow@MacBook-Pro-673lerna % lerna D ERR! Lerna refers to ls?Copy the code
-
Defining parameter aliases
You can use lerna -h lerna –help
.alias("h"."help") .alias("v"."version") Copy the code
-
Defines the width of the command content as the entire width of the terminal
.wrap(cli.terminalWidth()) Copy the code
-
Defines what to display at the end of command input
.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 `); Copy the code
-
Various commands are defined
.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) .parse(argv, context); Copy the code
What does the Lerna ls command do
Const listCmd = require(“@lerna/list/command”); Look at this reference
"use strict";
/ * * *@see https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module
*/
exports.command = "link";
exports.describe = "Symlink together all packages that are dependencies of each other";
//builer: Registers parameters before executing the command
exports.builder = yargs= > {
yargs.options({
"force-local": {
group: "Command Options:".describe: "Force local sibling links regardless of version range match".type: "boolean",},contents: {
group: "Command Options:".describe: "Subdirectory to use as the source of the symlink. Must apply to ALL packages.".type: "string".defaultDescription: ".",}});// Actually execute the command
return yargs.command(
"convert"."Replace local sibling version ranges with relative file: specifiers".() = > {},
handler
);
};
// Execute the real command
exports.handler = handler;
function handler(argv) {
return require(".")(argv);
}
Copy the code
Look at this piece of code, which is the index.js of the current directory
function handler(argv) {
return require(".")(argv);
}
Copy the code
"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) {
return new ListCommand(argv);
}
class ListCommand extends Command {
get requiresGit() {
return false;
}
initialize() {
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;
Copy the code
Check the inherited Command parent class that defines constructor and execute the constructor method core/ Command /index.js
class Command {
constructor(_argv) {
log.pause();
log.heading = "lerna";
const argv = cloneDeep(_argv); // Deep copy parameters
log.silly("argv", argv);
// "FooCommand" => "foo"
this.name = this.constructor.name.replace(/Command$/."").toLowerCase(); // Class name ListCommand
// composed commands are called from other commands, like publish -> version
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}`);
}
// launch the command
let runner = new Promise((resolve, reject) = > {
// run everything inside a Promise chain
let chain = Promise.resolve();
chain = chain.then(() = > {
this.project = new Project(argv.cwd);
});
chain = chain.then(() = > this.configureEnvironment()); // Configure environment variables
chain = chain.then(() = > this.configureOptions()); // Configure parameters
chain = chain.then(() = > this.configureProperties()); // Configure the properties
chain = chain.then(() = > this.configureLogging()); // Configure logs
chain = chain.then(() = > this.runValidations()); // Configure variables
chain = chain.then(() = > this.runPreparations()); // Configure the precheck content
chain = chain.then(() = > this.runCommand()); // Core execution code
chain.then(
result= > {
warnIfHanging();
resolve(result);
},
err= > {
if (err.pkg) {
// Cleanly log specific package error details
logPackageError(err, this.options.stream);
} else if(err.name ! = ="ValidationError") {
// npmlog does some funny stuff to the stack by default,
// so pass it directly to avoid duplication.
log.error("", cleanStack(err, this.constructor.name));
}
// ValidationError does not trigger a log dump, nor do external package errors
if(err.name ! = ="ValidationError" && !err.pkg) {
writeLogFile(this.project.rootPath);
}
warnIfHanging();
// error code is handled by cli.fail()reject(err); }); });// passed via yargs context in tests, never actual CLI
/* istanbul ignore else */
if (argv.onResolved || argv.onRejected) {
runner = runner.then(argv.onResolved, argv.onRejected);
// when nested, never resolve inner with outer callbacks
delete argv.onResolved; // eslint-disable-line no-param-reassign
delete argv.onRejected; // eslint-disable-line no-param-reassign
}
// "hide" irrelevant argv keys from options
for (const key of ["cwd"."$0"]) { Inject "CWD" and "$0" into argv
Object.defineProperty(argv, key, { enumerable: false });
}
Object.defineProperty(this."argv", { // As a general operation, register this.argv
value: Object.freeze(argv),
});
Object.defineProperty(this."runner", { / / register this. Runner
value: runner, }); }}Copy the code
Read the Command core code
- If there is no definition in a subclass
initialize
andexecute
The method is just reporting an error
runCommand() {
return Promise.resolve()
.then(() = > this.initialize())
.then(proceed= > {
if(proceed ! = =false) {
return this.execute();
}
// early exits set their own exitCode (if non-zero)
});
}
initialize() {
throw new ValidationError(this.name, "initialize() needs to be implemented.");
}
execute() {
throw new ValidationError(this.name, "execute() needs to be implemented.");
}
Copy the code
initialize() { // Define the content of the output
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); // The output is formatted as a string
});
return chain;
}
execute() {
// piping to `wc -l` should not yield 1 when no packages matched
if (this.result.text.length) {
output(this.result.text); // The output of the terminal
}
this.logger.success(
"found"."%d %s".this.result.count,
this.result.count === 1 ? "package" : "packages"
);
}
Copy the code
The Lerna project takes another approach to local debugging
Normally we use NPM link for local debugging, if there are many packages, it will be confusing, and after each release, we need NPM unlink;
Lerna uses the publish parameter to parse local links to online links.
! [image-20211101142340701](/Users/rainbow/Library/Application Support/typora-user-images/image-20211101142340701.png)
Use the file method for local debugging
In the previous LERna-REPo project;
packages/core
packages/utils
Reference the ‘@rainbow-cli-dev/utils’ dependency in core
"dependencies": {
"@rainbow-cli-dev/utils": "file:.. /utils",},Copy the code
“@rainbow-cli-dev/utils” soft link created in node_modules of core directory NPM I. Dependencies can also be used normally.
Yargs scaffolding usage
npm i yargs
#! /usr/bin/env node
const yargs = require("yargs/yargs");
const dedent = require("dedent");
const { hideBin } = require("yargs/helpers");
const cli = yargs(hideBin(process.argv))
// dedent
cli
.strict()
.usage("Usage: $0 <command> [options]")
.demandCommand(
1."A command is required. Pass --help to see all available commands and options."
)
.recommendCommands()
.alias("h"."help")
.alias("v"."version")
// Define multiple option parameters
.options({
debugger: {
defaultDescription: "".describe: "bootstrap debugger mode".type: "boolean".alias: "d",}})// Registers a single option argument
.option("register", {
describe: "Define Global Option".type: "string".alias: "r",
})
.option("ci", {
type: "boolean".hidden: true,})// Define command groups
.group(["debugger"]."Dev Options")
.group(["register"]."Extra Options")
.wrap(cli.terminalWidth()).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 `).argv;
Copy the code
Enter the rainbow – test – h
To get the output
rainbow@MacBook-Pro-673 rainbow-test % rainbow-test -h
Usage: rainbow-test <command> [options]
Dev Command
-d, --debugger bootstrap debuggerMode [Boolean] option: -r, --register Define Global Option [string] -h, --help displays help information [Boolean] -v, --version Displays the version number [Boolean] When a command fails, all logs are written to lerna-debug.login the current working directory.
For more information, find our manual at https://github.com/lerna/lerna
rainbow@MacBook-Pro-673 rainbow-test % rainbow-test -h
Usage: rainbow-test <command> [options]
Dev Options
-d, --debugger bootstrap debuggerMode [Boolean] Extra Options -r, --register Define Global Option -h, --help Displays help information [Boolean] -v, --version Displays the version number [Boolean] When a command fails, all logs are written to lerna-debug.login the current working directory.
For more information, find our manual at https://github.com/lerna/lerna
Copy the code