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
      1. Root Directory Creation.vscodedirectory
      2. 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
    • Debug using WebStorm
      • Debug method

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 viewconst 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
    1. 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

  1. Use YARgs to register commands and parameters

  2. 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
  3. 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 incorrectlyrecommendCommands()

  • 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 subclassinitializeandexecuteThe 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