Make writing a habit together! This is the fifth day of my participation in the “Gold Digging Day New Plan ยท April More text Challenge”. Click here for more details.

The scaffold command dynamically loads the functional architecture design

Whether to execute native code

Set the global targetPath

// Set the global targetPath
program.on('option:targetPath'.() = > {
    process.env.CLI_TARGET_PATH = params.targetPath;
});
Copy the code

Create an exec package to load commands dynamically

lerna create @hzw-cli-dev/exec
Copy the code

Create a new package and place it under the Models directory

lerna create @hzw-cli-dev/package
Copy the code

Create a new Command package and place it under the Models directory

lerna create @hzw-cli-dev/command
Copy the code

Npminstall usage

const npmInstall = require('npminstall');
const path = require('path');
const userHome = require('user-home');

// Call the install method directly
npmInstall({
  root: path.resolve(userHome, '.hzw-cli-dev'), // Module path
  storeDir: path.resolve(userHome, ' '.'node_modules'), // Module installation position
  register: 'https://registry.npmjs.org'.// Set the NPM source
  pkgs: [ // Package information to install
    {
      name: 'warbler-js'.version: ' ',}]});Copy the code

The implementation of the Package class

'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 management module
class Package {
  / * * *@descriptionConstructor *@param {*} Options Configuration information passed in by the user *@return {*}* /
  constructor(options) {
    if(! options) {throw new Error(Arguments to the 'Package class cannot be empty! ');
    }
    if(! isObject(options)) {throw new Error(The arguments to the 'Package class must be object types! ');
    }
    // Get targetPath, if not a local package
    this.targetPath = options.targetPath;
    // Module installation location Cache path
    this.storeDir = options.storeDir;
    / / the name of the package
    this.packageName = options.packageName;
    / / package Version
    this.packageVersion = options.packageVersion;
    // Cache path prefix
    this.cacheFilePathPrefix = this.packageName.replace('/'.'_');
  }

  / * * *@description: Preparation *@param {*}
   * @return {*}* /
  async prepare() {}

  / * * *@description: Gets the current module cache path *@param {*}
   * @return {*}* /
  get cacheFilePath() {}

  / * * *@description: Gets the latest version module cache path *@param {*}
   * @return {*}* /
  getSpecificFilePath(packageVersion) {}

  / * * *@description: Checks whether the current package exists *@param {*}
   * @return {*}* /
  async exists() {}

  / * * *@description: Install package *@param {*}
   * @return {*}* /
  async install() {}

  / * * *@description: Update package *@param {*}
   * @return {*}* /
  async update() {}

  / * * *@description1. Obtain the package.json directory pkg-dir * 2. Read package.json * 3. Find the main or lib property to form path * 4. Path compatibility (macOs/ Windows) *@param {*}
   * @return {*}* /
  getRootFilePath() {}
module.exports = Package;
Copy the code

The preparatory work

  / * * *@description: Preparation *@param {*}
   * @return {*}* /
  async prepare() {
    // When the cache directory does not exist
    if (this.storeDir && ! pathExists(this.storeDir)) {
      // Create a cache directory
      fse.mkdirpSync(this.storeDir);
    }
    // Get the latest version
    const latestVersion = await getLatestVersion(this.packageName);
    // If the version number is the latest, the value is assigned
    if (this.packageVersion === 'latest') {
      this.packageVersion = latestVersion; }}Copy the code

Gets the current module cache path

  / * * *@description: Gets the current module cache path *@param {*}
   * @return {*}* /
  get cacheFilePath() {
    return path.resolve(
      this.storeDir,
      ` _The ${this.cacheFilePathPrefix}@The ${this.packageVersion}@The ${this.packageName}`,); }Copy the code

Obtain the module cache path of the latest version

  / * * *@description: Gets the latest version module cache path *@param {*}
   * @return {*}* /
  getSpecificFilePath(packageVersion) {
    return path.resolve(
      this.storeDir,
      ` _The ${this.cacheFilePathPrefix}@${packageVersion}@The ${this.packageName}`,); }Copy the code

Check whether the current package exists

  / * * *@description: Checks whether the current package exists *@param {*}
   * @return {*}* /
  async exists() {
    // If this.storeDir exists, it must be downloaded and installed, otherwise it must be installed locally
    if (this.storeDir) {
      // Get the specific version number
      await this.prepare();
      return pathExists(this.cacheFilePath);
    } else {
      // Check whether the local path exists
      return pathExists(this.targetPath); }}Copy the code

The installation package

  / * * *@description: Install package *@param {*}
   * @return {*}* /
  async install() {
    await this.prepare();
    return npmInstall({
      root: this.targetPath, // Module path
      storeDir: this.storeDir, // Module installation position
      register: getRegister('npm'), // Set the NPM source
      pkgs: [
        // Package information to install
        {
          name: this.packageName,
          version: this.packageVersion,
        },
      ],
    });
  }
Copy the code

Update package

  / * * *@description: Update package *@param {*}
   * @return {*}* /
  async update() {
    await this.prepare();
    // Get the latest version
    const latestVersion = await getLatestVersion(this.packageName);
    // Check whether the local version is up to date
    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 not, install the latest version
    if(! isLocalLatestVersion) {await npmInstall({
        root: this.targetPath, // Module path
        storeDir: this.storeDir, // Module installation position
        register: getRegister('npm'), // Set the NPM source
        pkgs: [
          // Package information to install
          {
            name: this.packageName,
            version: latestVersion,
          },
        ],
      });
      this.packageVersion = latestVersion; }}Copy the code

Gets the path to the entry file

  / * * *@description1. Obtain the package.json directory pkg-dir * 2. Read package.json * 3. Find the main or lib property to form path * 4. Path compatibility (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));
          returnrootPath; }}return null;
    }
    // If this.storeDir exists, it must be downloaded and installed, otherwise it must be installed locally
    if (this.storeDir) {
      return _getRootFIle(this.cacheFilePath);
    }
    return _getRootFIle(this.targetPath);
  }
Copy the code

Scaffolding optimizes the execution process

Implementation of the Command class

'use strict';

// Introduce the version comparison library semver
const semver = require('semver');
const LOWEST_NODE_VERSION = '12.0.0';
// Introduce the color library colors
const colors = require('colors/safe');
const log = require('@hzw-cli-dev/log');
const { isArray } = require('@hzw-cli-dev/utils');
class Command {
  / * * *@description: Command preparation and execution stages *@param {*} Argv: projectName projectName, options of the command, commander instance *@return {*}* /
  constructor(argv) {
    if(! argv) {throw new Error('Parameters must not be empty! ');
    }
    if(! isArray(argv)) {throw new Error('Parameter must be array type! ');
    }
    if (argv.length < 1) {
      throw new Error('Parameter list cannot be empty! ');
    }
    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: Check the current node version to prevent errors using the latest API *@param {*}
   * @return {*}* /
  checkNodeVersion() {
    // Get the current Node version
    const currentVersion = process.version;
    log.test('Environment check the current Node version is :', process.version);
    // Get the lowest node version
    const lowestVersion = LOWEST_NODE_VERSION;
    // Compare the lowest node version
    if(! semver.gte(currentVersion, lowestVersion)) {throw new Error(colors.red('Error: Node version too old')); }}/ * * *@description: Initialization parameter *@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 must have an init method');
  }

  exec() {
    throw new Error('Command must have an exec method'); }}module.exports = Command;

Copy the code

Note: I didn’t understand all of chapter 5 and Chapter 7 in The fourth week, so I need to review it later, but it won’t affect the main course

The realization of the exec

'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');

// Mapping table of package
const SETTINGS = {
  // init: '@hzw-cli-dev/init',

  init: '@imooc-cli/init'};// Cache directory
const CACHE_DIR = 'dependencies';

/ * * *@description1. Get targetPath * 2. ModulePath * 3. Generate a package * 4. Provide a getRootFIle method to get the entry file * 5. Provide the update method and install method *@return {*}* /
async function exec(. argv) {
  / / get targetPath
  let targetPath = process.env.CLI_TARGET_PATH;
  / / get homePath
  const homePath = process.env.CLI_HOME_PATH;
  log.verbose('targetPath', targetPath);
  log.verbose('homePath', homePath);
  // Get the commander instance
  const cmdObj = argv[argv.length - 1];
  Exp :init
  const cmdName = cmdObj.name();
  Exp: map to @hzw-cli-dev/init
  const packageName = SETTINGS[cmdName];
  // Get the version of the package
  const packageVersion = 'latest';
  // Module installation path
  let storeDir = ' ';
  / / package
  let pkg = ' ';

  // If targetPath does not exist
  if(! targetPath) { targetPath = path.resolve(homePath, CACHE_DIR);// Generate the cache path
    storeDir = path.resolve(targetPath, 'node_modules');
    // Create Package object
    pkg = new Package({
      storeDir,
      targetPath,
      packageName,
      packageVersion,
    });
    // If the current package exists
    if (await pkg.exists()) {
      / / update the package
      pkg.update();
    } else {
      / / install package
      awaitpkg.install(); }}else {
    // If targetPath exists
    pkg = new Package({
      targetPath,
      packageName,
      packageVersion,
    });
  }

  // Get the module entry
  const rootFile = pkg.getRootFilePath();
  if(! rootFile) {throw new Error('Module does not have entry file! ');
  }
  // Execute the module
  try {
    // called in the current process
    // rootFile && require(rootFile)(argv);
    // called in the node child process
    const cmd = argv[argv.length - 1];
    const newCmd = Object.create(null);
    // Trim the parameters
    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}'),The ${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('Command executed successfully');
      process.exit(e);
    });
  } catch (error) {
    console.log(error.message); }}/ * * *@description: Encapsulates a spawn method compatible with MAC and 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)
 */

/** all possible values 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;

Copy the code