NPM Engineering & source code analysis of Inquirer

Time:2021-10-20

fromnpm run-scriptApplication start

View the of some NPM packagesnpm_package_scripts, you can often see itrun-scriptExample:

...
  "scripts": {
    "prerelease": "npm test && npm run integration",
    "release": "env-cmd lerna version",
    "postversion": "lerna publish from-git",
    "fix": "npm run lint -- --fix",
    "lint": "eslint . -c .eslintrc.yaml --no-eslintrc --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint",
    "integration": "jest --config jest.integration.js --maxWorkers=2",
    "pretest": "npm run lint",
    "test": "jest"
  },
...

Explain one by one:

customnpm run-script

stayNPMFriendly environment(npm init -y)Next, you cannode index.jsDefined innpm_package_scripts_*Directly as an alias in.

{
  "name": "cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "d1": "node ./demo1/bin/operation.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

On the command line, enternpm run d1It’s executionnode ./demo1/bin/operation.js

npm_packagevariable

npm run-scriptCustom commands, you canpackage.jsonOther configuration items are used as variables

{
  "name": "cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "d1": "node ./demo1/bin/operation.js",
    "d1:var": "%npm_package_scripts_d1%",
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

In daily application, you can useconfigField definition constants:

{
  "name": "cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "config": {
    "port": 8081
  },
  "scripts": {
    "d1": "node ./demo1/bin/operation.js",
    "d1:var": "%npm_package_scripts_d1%",
    "test": "echo %npm_package_config_port%"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Platform differences

  • Linux/Mac:$npm_package_*
  • Windows:$npm_package_*
  • Cross platform:cross_varThird party NPM package

shebang

Only inUnixAvailable in the system, specified on the first line#!usr/bin/env node, when the file is executed, the specified execution environment will be run under the execution path of the user

Can passtype envConfirm the environment variable path.

#!/usr/bin/env node
console.log('-------------')

The above file can be executed directly with the file name withoutnode index.jsTo execute

E:\demos\node\cli> ./index.js
--------

process.envenvironment variable

havePlatform differences

  • Unix: run-cli
mode=development npm run build

It can be obtained in the logic codeprocess.env.mode === "develop"

  • Windows: run-cli

Environment variables are not allowed to be defined in this way

  • Cross platform

With the help ofcross-envDefine environment variables

Multi command serial

Examples are as follows:

{
  "name": "cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "d2:o1": "node ./demo2/bin/ope1.js",
    "d2:o2": "node ./demo2/bin/ope2.js",
    "d2:err": "node ./demo2/bin/op_error.js",
    "d2": "npm run d2:o1 && npm run d2:o2"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

&&Multiple commands can be connected and executed serially.

If there is an asynchronous method in the previous command, the subsequent command will not be executed until the asynchronous execution is completed and the process is completely completed.

// ./demo2/bin/ope1.js
console.log(1)
setTimeout(() => {
  console.log(2)
}, 4000)
console.log(3)
// ./demo2/bin/ope2.js
console.log(4)

Execution results:

1
3
2
4

Multi command parallel

havePlatform differences

  • Unix: &Multiple commands can be connected and executed in parallel.
  • Windows&Multiple commands are still serial.
  • Cross platform: withnpm-run-allThird party NPM package

Serial example inMacOutput results:

1
3
4
2

Conditional execution

In a multi command orchestration process, it may be necessary to end the process under certain conditions.

End nowprocess.exit(1)

// demo2/bin/op_error.js
console.log(1)
process.exit(1)
setTimeout(() => {
  console.log(2)
}, 4000)
console.log(3)
// demo2/bin/ope2.js
console.log(4)

Execute command"d2:error": "npm run d2:err && npm run d2:o2", output result:

1
Error

amongprocess.exit(1)Subsequent code and tasks are no longer executed.

The current process has finished executingprocess.exitCode = 1

// demo2/bin/op_error.js
console.log(1)
process.exitCode = 1
setTimeout(() => {
  console.log(2)
}, 4000)
console.log(3)

reformop_error.js, executenpm run d2:error, output result:

1
3
2
Error

amongprocess.exitCode = 1Subsequent code continues to be executed, and subsequent tasks are no longer executed.

npm run-scriptTransmission parameter

npm run-scriptparameter

Custom command"d4": "node ./demo4/bin/operation.js"

console.log(process.argv)

implementnpm run d4 -f, output result:

E:\demos\node\cli>npm run d4 -f
npm WARN using --force I sure hope you know what you are doing.

> [email protected] d4 E:\demos\node\cli
> node ./demo4/bin/operation.js

[
  'D:\\nodejs\\node.exe',
  'E:\\demos\\node\\cli\\demo4\\bin\\operation.js'
]

Among them,-fNot bybin/operation.jsUndertake, but asnpm run-scriptParameters are digested (even ifnpm run-scriptThe parameter is not recognized).

  • -s

    • Silent executionnpm run-script: ignore log output
  • -d

    • Debug mode executionnpm run-scriptLog full level output:

definitionnpm run-scriptend

implementnpm run d4 -- -f, output result:

E:\demos\node\cli>npm run d4 -- -f

> [email protected] d4 E:\demos\node\cli
> node ./demo4/bin/operation.js "-f"

[
  'D:\\nodejs\\node.exe',
  'E:\\demos\\node\\cli\\demo4\\bin\\operation.js',
  '-f'
]

Among them,-fcoverbin/operation.jsUndertake.

Visible, innpm run-script <command>Post use--definitionnpmEnd of parameter,npmWill--All subsequent parameters are passed directly to the custom script.

NPMhook

npm_package_scripts_*definition

{
  "name": "cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "pred5": "node ./demo5/bin/pre.js",
    "d5": "node ./demo5/bin/operation.js",
    "postd5": "node ./demo5/bin/post.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

implementnpm run d5, in executionnode ./demo5/bin/operation.jsIt will be executed automatically before"pred5": "node ./demo5/bin/pre.js", in executionnode ./demo5/bin/operation.jsThen it will be executed automatically"postd5": "node ./demo5/bin/post.js"

node_modules/.hooks/definition

Unixavailable

  1. establishnode_modules/.hooks/catalogue
  2. establishpred5file

    console.log('---------pre--------')
  3. Modify file permissions to executablechmod 777 node_modules/.hooks/pred5
  4. Execute commandnpm run d5that will do

Scenario:

  "postinstall": "husky install"

NPM package local debuggingnpm link

//Switch to NPM package directory
npm link

npm linkLocal packages can be registered to the global in the form of a soft chainnode_modules/binNext, tonpm_package_nameIs the package name.

//Switch to project directory
npm link package_name

In the project directory, clicknpm link package_nameYou can link the local NPM package to the project for local debugging and development.

//Under project directory
npm unlink package_name

unlinkUnbind project from local NPM package

//Under NPM package directory
npm unlink

Unregister local NPM packages globally

Command line based on node module

Example 1:process.stdin & process.stdoutInteractive command line

function cli () {
  process.stdout.write("Hello");
  process.stdout.write("World");
  process.stdout.write("!!!");
  process.stdout.write('\n')
  console.log("Hello");
  console.log("World");
  console.log("!!!");
  process.on('exit', function () {
    console.log('----exit')
  })
  process.stdin.setEncoding('utf8')

  process.stdin.on('data', (input) => {
    console.dir(input)
    input = input.toString().trim()
    if (['Y', 'y', 'YES', 'yes'].indexOf(input) > -1) {
      console.log('success')
    }
    if (['N', 'n', 'No', 'no'].indexOf(input) > -1) {
      console.log('reject')
    }
  })
  process.stdout.write('......\n')
  console.log('----------------00000000000------------')
  Process. Stdout. Write ('confirm execution (Y / N)? ')
  process.stdout.write('......\n')
}
cli()

stdin

  • Standard input monitor console input
  • End with enter
  • The input obtained contains a carriage return character

stdout

process.stdout vs. console.log

amongconsole.logThe output of the underlying call isprocess.stdout, it is processed before output, such as callingutil.formatmethod

difference process.stdout console.log
parameter Only strings can be accepted as parameters All data types of ECMA are supported
Number of parameters Only one string Can receive multiple
Line feed Inline continuous output Auto append wrap
format I won’t support it Formatting of ‘% s’,’% C ‘is supported
Output itself Writestream object character string

Example 2:process.stdinWorking mode

process.stdin.setEncoding('utf8');

function readlineSync() {
  return new Promise((resolve, reject) => {
    console.log(`--status----${process.stdin.readableFlowing}`);
    process.stdin.resume();
    process.stdin.on('data', function (data) {
      console.log(`--status----${process.stdin.readableFlowing}`);
      process.stdin.pause(); //  Stops after one line reads // pauses the input stream, allowing it to be resumed later if necessary.
      console.log(`--status----${process.stdin.readableFlowing}`);
      resolve(data);
    });
  });
}

async function main() {
  let input = await readlineSync();
  console.log('inputLine1 = ', input);
  console.log('bye');
}

main();

If n callsreadlineSync(), will bedataThe event listener binds the processing function for multiple times, and the callback function will be executed n times.

stdin

Standard input is an instance of a readable stream

Working mode

Working mode conforming to readable stream:

  • Flow mode

    In the flow mode, the data is automatically read from the underlying system and passed through theEventEmitteInterface events are provided to the application as quickly as possible

  • Paused mode

    In pause mode, you must explicitly callstream.read()Read data block

working condition

  • null
  • false
  • true
    Can passreadable.readableFlowingView the corresponding operating mode

State switching

  • add to'data'Event handle.
  • callstream.resume()method.
  • callstream.pipe()Method to send data to a writable stream.

Process end

  • If there is no additional work to be processed in the event loop, thenNode.jsThe process exits itself.
  • callprocess.exit()It will force the process to exit as soon as possible, even if there are still asynchronous operations waiting to be completed, includingprocess.stdoutandprocess.stderrI / O operations.

Example 3:readlinemodular

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  Prompt: 'please enter >'
});

rl.prompt();

rl.on('line', (line) => {
  console.dir(line)
  switch (line.trim()) {
    case 'hello':
      console.log('world!');
      break;
    default:
      Console. Log (` what you entered is' ${line. Trim()} '`);
      break;
  }
  rl.prompt();
}).on('close', () => {
  Console.log ('goodbye! ');
  process.exit(0);
});

ReadLine module

Create UI interface

const rl = readline.createInterface({
  Input: process.stdin, // define the input UI
  Output: process.stdout, // define the output UI
  Historysize: 0, // disable history scrolling - default: 30
  Removehistoryduplicates: true, // enter history de duplication - default: false
  Complete: function (line) {// tabs automatically fill in matching text
    const completions = '.help .error .exit .quit .q'.split(' ');
    const hits = completions.filter((c) => c.startsWith(line));
    return [hits.length ? hits : completions, line];  //  Output array: 0 -- matching result; 1 - input
  },
  Prompt: 'please enter >' // command line prefix
});

method

rl.prompt()

Opens a new input line with a prefix

rl.close()

closereadline.InterfaceInstance and discard theinputandoutputFlow control

event

lineevent
rl.on('line', (line) => {
  //Compared with process. Stdin. On ('data ', function (chunk) {}), the input line does not contain line breaks
  switch (line.trim()) {
    case 'hello':
      console.log('world!');
      break;
    default:
      Console. Log (` what you entered is' ${line. Trim()} '`);
      break;
  }
  rl.prompt();
});

Inquirer source code analysis

Core:

  • Command line UI

    • readline.createInterface
  • Render output

    • rl.output.write
  • event listeners

    • rxjs

Enhance interactive experience:

  • Mute stream: control output stream output
  • Chalk: colorful log printing
  • Figures: command line small icon
  • CLI cursor: control of hiding and displaying cursor

Take type = “list” as an example

Create command line

this.rl = readline.createInterface({
  terminal: true,
  input: process.stdin,
  output: process.stdout
})

Render output

var obs = from(questions)
this.process = obs.pipe(
  concatMap(this.processQuestion.bind(this)),
  publish()
)

Convert the incoming parameters into data stream form and render each item of dataprocessQuestion

  render(error) {
    var message = this.getQuestion();
    if (this.firstRender) {
      message += chalk.dim('(Use arrow keys)');
    }
    if (this.status === 'answered') {
      message += chalk.cyan(this.opt.choices.getChoice(this.selected).short);
    } else {
      var choicesStr = listRender(this.opt.choices, this.selected);
      var indexPosition = this.opt.choices.indexOf(
        this.opt.choices.getChoice(this.selected)
      );
      message +=
        '\n' + choicesStr;
    }
    this.firstRender = false;
    this.rl.output.unmute();
    this.rl.output.write(message);
    this.rl.output.mute();
  }

Of which:

  • With the help ofchalkThe output color is diversified;
  • listRenderPut eachchoiceSpliced as string;
  • usethis.selectedIdentifies the currently selected item. The default value is 0;
  • usethis.rl.output.writeOutput string;
  • With the help ofmute-streamInvalid output of control command line;

event listeners

function observe(rl) {
  var keypress = fromEvent(rl.input, 'keypress', normalizeKeypressEvents)
    .pipe(takeUntil(fromEvent(rl, 'close')))
    // Ignore `enter` key. On the readline, we only care about the `line` event.
    .pipe(filter(({ key }) => key !== 'enter' && key.name !== 'return'));
  return {
    line: fromEvent(rl, 'line'),
    keypress: keypress,
    normalizedUpKey: keypress.pipe(
      filter(
        ({ key }) =>
          key.name === 'up' || key.name === 'k' || (key.name === 'p' && key.ctrl)
      ),
      share()
    ),
    normalizedDownKey: keypress.pipe(
      filter(
        ({ key }) =>
          key.name === 'down' || key.name === 'j' || (key.name === 'n' && key.ctrl)
      ),
      share()
    ),
    numberKey: keypress.pipe(
      filter((e) => e.value && '123456789'.indexOf(e.value) >= 0),
      map((e) => Number(e.value)),
      share()
    ),
  };
};

With the help ofRx.fromEventListening for command linekeypresslineevent.

var events = observe(this.rl);
events.normalizedUpKey
  .pipe(takeUntil(events.line))
  .forEach(this.onUpKey.bind(this));
events.normalizedDownKey
  .pipe(takeUntil(events.line))
  .forEach(this.onDownKey.bind(this));
events.line
  .pipe(
    take(1)
  )
  .forEach(this.onSubmit.bind(this));

Subscribe to events and process corresponding events

  onUpKey () {
    console.log('--------up')
    this.selected = incrementListIndex(this.selected, 'up', this.opt);
    this.render();
  }
  onDownKey () {
    console.log('--------down')
    this.selected = incrementListIndex(this.selected, 'down', this.opt);
    this.render();
  }
  onSubmit () {
    console.log('------------submit')
  }

modifythis.selectedValue, bythis.renderUpdate the command line interface.
monitorlineEvent, willthis.selectedOutput the corresponding results.

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]