even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、项目的初始化

npm init -y

要求:需要安装node.js

注意:在初始化的时候,package.json文件里的name相当于命令行的句柄比如name为test,那么相当执行的时候句柄是 test ***, 当然可以设置别外

{
  "name": "yf",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

2、创建可执行文件

  在项目中新建bin目录,并且在bin目录下新建yf文件,当然这个名字可以自己起

  在window创建文件夹用mkdir  文件夹名, 创建文件用  type nul>yf这样就会建立无后缀名的的yf文件,yf文件的开头如下

#!/usr/bin/env node
...正常的逻辑代码

注意第一行的 #!/usr/bin/env node#! 表示要指定脚本文件的解析程序,/usr/bin/env 表示要去哪里找解析程序,node 是解析程序的名字(表示这个要文件由 node.js 来运行)。

3、配置其他配置项

在package.json中配置bin的内容项如下:

如果命令行的命名与项目的名称一致,那么可以使用如下方式

{
  "name": "yf",
  "version": "1.0.0",
  "description": "",
  "bin": "./bin/index.js",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {}
}

以上运行命令用的是  yf 

如果需要使用别名,那么可以使用json的形式进行别名定义

{
  "name": "yf",
  "version": "1.0.0",
  "description": "",
  "bin": {
    "yf-cli": "./bin/index.js"
  },
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {}
}

注意:如果有提示操作权限问题可做如下配置

为 bin/index 添加可执行权限,在package.json同级目录下执行

chmod +x bin/index    //用git bash来执行这一命令

4、连接至本地环境

把对应的命令行放置到本地环境在项目的根目录下运行

npm link  //如果有重复,那么可以使用npm link --force 进行覆盖

如果不需要该命令的时候运行

npm unlink //则会删除指定的命令

查询命令指定的位置

which [命令]

在开发脚手架的过程中,如果一个脚手架依赖当前正在开发中的库文件,那么可以使用以下方法进行建立关联

// 链接本地库文件
cd you-lib-dir
npm link
cd you-cli-dir
npm link you-lib


// 取消链接本地库文件
cd you-lib-dir
npm unlink
cd you-cli-dir
npm unlink you-lib
// 如果发布上线后在库文件与当前冲突,可以移除全局的node_modules, 再重新npm install进行安装即可

5、配置可执行的命令 --使用插件commander依赖

安装commander依赖

npm i commander --save

commander快速入门

#! /usr/bin/env node

const commander = require('commander')
const pkg = require('../package.json')

//使用commander提供的单例的模式
// const { program } = commander

// 自己创建类进行初始化
const program = new commander.Command()

program.version(pkg.version).parse(process.argv)

commander中的配置选项

#! /usr/bin/env node

const commander = require('commander')
const pkg = require('../package.json')

const { program } = commander

// 在commander中 <command>表示必填项  [options]表示可选项
program
  .name(Object.keys(pkg.bin)[0])
  .usage('<command> [options]')
  .version(pkg.version)
  .option('-d, --debug [options]', '是否开启调试模式', false) // 第三个参数表示默认参数
  .option('-e, --envName <envName>', '获取环境变量')
  .parse(process.argv)

// 获取option所对应的值的集合
console.log(program.opts())

// 当用户输入even-cli时就自动的输了help信息
program.outputHelp()

commander中的命令注册

在commander中命令注册有两种方式: a、command注册命令; b、addCommand 注册命令

a方式

#! /usr/bin/env node

const commander = require('commander');
const pkg = require('../package.json');

const { program } = commander;

// 在commander中 <command>表示必填项  [options]表示可选项
program
  .name(Object.keys(pkg.bin)[0])
  .usage('<command> [options]')
  .version(pkg.version)
  .option('-d, --debug [options]', '是否开启调试模式', false) // 第三个参数表示默认参数
  .option('-e, --envName <envName>', '获取环境变量');

// 注册命令
const init = program.command('init <packageName> [destination]');
init
  .description('初始化项目的命令')
  .option('-f, --force [options]', '强制初始化', false)
  .action((pkgName, dest, opts) => {
    // 第一个参数与第二个参数分别对应的是init的参数,opts对应的option集合
    console.log('this is init');
    console.log(process.cwd());  // 获取当前cwd地址
require('./lib/create')(pkgName, dest, opts) // 通过require的方式进行分包处理
}); program.parse(process.argv); // 当用户输入even-cli时, 并且什么参数都没有输入,那么就弹出帮助信息 if (!process.argv.slice(2).length) program.outputHelp();

注意:如果在-f, --force后面添加options, 那么在even-cli init --force param1 param2 那么param1会默认为是--force的参数,如果没有【options】 那么param1与2都是init的参数

b方式

通过addCommand 注册子命令

// 相当于在program中添加子命令
const service = new Command('service');

service.usage('<command> [options]'); // 自定义子命令的使用方式,相当于在even-cli service [options]
service
  .command('start [port]')
  .option('-f, --force', '忽略端口占用情况,强制启动')
  .description('启动一个服务')
  .action((port, opts) => {
    console.log(port, opts);
  });
service
  .command('stop [serviceName]')
  .description('停止指定服务')
  .action(() => {
    console.log('这个是停止服务');
  });

// 注册子命令
program.addCommand(service);

command中的其它重要知识点

// 当所有命令匹配不上时,就会进行该程序中
program.arguments('<cmd> [options]').action((cmd, opts) => {
  console.log(cmd, opts);
});

// 监听help选项
program.on('--help', () => {
    console.log()
    console.log(`  Run ${chalk.cyan(`vue <command> --help`)} for detailed usage of given command.`)
    console.log()
})

command的示例

import { program, Command } from 'commander';
import colors from 'colors';
import log from '../utils/log';
import leven from 'leven';
import { init } from '../command/init';

const pkg = require('../../package.json');

// 进行建议的命令枚举
const suggestCommands = (unknownCmd: string, cmdList: Array<string>) => {
  let suggestion: string | undefined = undefined;

  cmdList.forEach((cmd: string) => {
    const isBestMatch =
      leven(cmd, unknownCmd) < leven(suggestion || '', unknownCmd);
    if (leven(cmd, unknownCmd) < 3 && isBestMatch) suggestion = cmd;
  });
  if (suggestion) {
    console.log();
    log.error('', colors.red(`Did you mean ${colors.yellow(suggestion)}?`));
  }
};

// 自定义报错信息
const enhanceErrorMessages = (
  methodName: string,
  callBack: (opt: any, flag?: string) => string,
) => {
  //@ts-ignore
  Command.prototype[methodName] = function (opt: any, flag: string) {
    //@ts-ignore
    if (methodName === 'unknownOption' && this._allowUnknownOption) return;
    this.outputHelp();
    console.log();
    log.error('', colors.red(callBack(opt, flag)));
    process.exit(1);
  };
};

// 注册命令
export const registerCommand = (): void => {
  program.version(`${pkg.name} ${pkg.version}`).usage('<command> [options]');

  // 开启debug模式,在prepare已做,需要配置该项才不会报错
  program.option('-d, --debug', 'enabling the Debug mode');

  // 初始化项目的命令
  program
    .command('init [projectName]')
    .description('initialize the project')
    .option('-f, --force', 'whether to force initialization of the project')
    .action((projectName: string, opt: Record<string, boolean>) => {
      init(projectName, opt);
    });

  enhanceErrorMessages('missingArgument', (argName) => {
    return `Missing required argument ${colors.yellow(`<${argName}>`)}.`;
  });

  // 无法匹配到指定的选项报错
  enhanceErrorMessages('unknownOption', (optionName) => {
    return `Unknown option ${colors.yellow(optionName)}.`;
  });

  enhanceErrorMessages('optionMissingArgument', (option, flag) => {
    return (
      `Missing required argument for option ${colors.yellow(option.flags)}` +
      (flag ? `, got ${colors.yellow(flag)}` : ``)
    );
  });

  // 进行无效指令匹配
  program.on('command:*', (list: Array<string>) => {
    const cmd = list[0]; // 获取到没有匹配的命令
    console.log();
    program.outputHelp();
    console.log();
    log.error('', colors.red(`Unknown command ${cmd}.`));
    suggestCommands(
      cmd,
      program.commands.map((cmd) => cmd.name()),
    );
  });

  // 输出帮助信息时的附带信息
  program.on('--help', () => {
    console.log();
    log.info(
      '',
      `Run ${colors.green(
        'even-pro <command> --help',
      )} for detailed usage of given command.`,
    );
  });

  program.parse(process.argv); //解析参数

  // 如果什么参数都没有输入,那么就弹出帮助信息
  if (!process.argv.slice(2).length) program.outputHelp();
};

6、命令行的ui显示

chalk颜色渲染

import chalk, { Chalk } from 'chalk';

// 通过颜色的api进行调用
console.log(chalk.red('hello world'));
// 能过颜色的api进行颜色拼接
console.log(chalk.red('hello') + ' ~', chalk.blue('world'));
// 进行链式调用
console.log(chalk.red.bgGreen.bold('hello world'));
// 进行嵌套调用
console.log(
  chalk.red('hello', chalk.underline.bold.italic.yellow.bgRed('world')),
);

// 使用rgb实现颜色自定义
console.log(chalk.green('hello', chalk.rgb(255, 215, 0)('world')));
// 使用16进制进行颜色表色
console.log(chalk.hex('#ffd700').bold('hello', chalk.red('world')));

// 使用new Chalk()来实现, 里面的等级与颜色支持等级有关, 当等级为0时则全部会转成无彩色
const customChalk = new Chalk({ level: 3 });

console.log(customChalk.red('hello', customChalk.underline('world~~~')));

注意:在使用chalk的时候,因为该库使用ems来编码的,所以需要用webpack进行编译,如果是测试可以暂用mjs的文件后缀来测试

 7、命令行的loading效果

npm i ora --save

初步使用案例

import ora from 'ora';

const spinner = ora('prepare').start();
// 指定loading的色彩
spinner.color = 'red';
// loading的前置文本
spinner.prefixText = 'loading';

// 自定义加载的图标
spinner.spinner = {
  interval: 80,
frames: ['——', '/', '|', '\']
}
let process
= 0; const handle = setInterval(() => { process += 10; // 更改loading的文本为loading spinner.text = `process ${process}%`; if (process >= 100) { // 下载成功文本; spinner.succeed('下载成功'); // 下载失败文本 // spinner.fail('下载失败'); // 停止loading // spinner.stop(); clearInterval(handle); } }, 1000);

 如果定制度比较低,那么可以使用oraPromise方式

import ora, { oraPromise } from 'ora';

const callBack = async () => {
  return new Promise((resolve, reject) => {
    let count = 0;

    const handle = setInterval(() => {
      count += 10;

      if (count >= 100) {
        clearInterval(handle);
        resolve();
      }
    }, 1000);
  });
};

// 可以配置常规的配置 oraPromise(callBack, { text:
'loading...', prefixText: 'status', }).then(() => { console.log('ok'); });

ora配合自制进度条

import ora from 'ora';

const spinner = ora('prepare').start();

// loading的前置文本
spinner.prefixText = 'loading';

let process = 0;

const processBar = (current, total, length = 25) => {
  // const cell = '█';
  // const empty = '░';
  const cell = '=';
  const empty = '-';

  const percent = current / total;
  const cellTotal = Math.floor(percent * length);
  const emptyTotal = length - cellTotal;

  return cell.repeat(cellTotal) + empty.repeat(emptyTotal);
};

const handle = setInterval(() => {
  process += Math.floor(Math.random() * 10 + 1);

  // 更改loading的文本为loading
  spinner.text = processBar(process, 100);

  if (process >= 100) {
    // 下载成功文本;
    spinner.succeed('下载成功');
    // 下载失败文本
    // spinner.fail('下载失败');
    // 停止loading
    // spinner.stop();
    clearInterval(handle);
  }
}, 1000);

8、进度条的显示

npm i progress --save

初步使用案例

let bar = new ProgressBar(':bar :current/:total', { total: 10 });
let timer = setInterval(function () {
    bar.tick();
    if (bar.complete) {
        console.log('\ncomplete\n');
        clearInterval(timer);
    }
}, 100);

具体可见npm 搜索 progress的使用

 

9、插件inquirer插件的使用

inquirer的使用主要针对在命令行中的交互组件,相当于页面中的input comfirm等功能

安装inquirer插件

npm i inquirer --save

 结果使用:

inquirer.prompt(promptList).then(res => {
     console.log(res) //返回用户所交互的对象,返回的是一个对象,里面的key是上面定义的name字段,value是用户选择的对象
})

常用类型以及配置

  • type:   String    类型  默认值:input      类型有:input,number,confirm,list,rawlist,expand,checkbox,password,editor
  • name:   String    名字    将答案存储在答案哈希中时使用的名称。设置的name字段在结果返回时会变成key
  • message: String | Function  提示信息   表示提示信息,如果是函数,那么接收的参数为之前项的选择答案,需要返回一个string类型,如果没有对应的值,默认为name字段
  • default: String|Number|Boolean|Array|Function  默认值  即如果没有输入,那么就返回的默认值,如果为函数则接收的是之前选的答案,需要返回一个值
  • prefix:  String    前置项  相当于在message前置一个信息项,可以用chalk进行颜色修饰
  • validate: Function  验证   对用户输入的内容进行验证,如果返回true则往下走,否则返回的字符串将成为提示信息,接收参数为用户输入的内容或选项
  • when: (params) => Boolean  验证是否展示  相当于该选项并非必须,会根据之前回答而定,接收参数为之前的输入回答,返回boolean类型,如果为true则展示,否则不展示
  • filter: (params) => String   过滤   相当对用户输入的内容进行过滤或者调整
  • transformer: (功能)接收用户输入,回答哈希和选项标志,并返回转换后的值以显示给用户。转换仅影响编辑时显示的内容。它不会修改答案哈希
  • choices: Array<String> 选项 当为list或者checkbox的时候,那么该选项的内容就是选项的内容

  每个type类型需要配置的项或多或少会有些不同,比如当type为checkbox时,需要配置choices这一项,而当type为number时就不需要。

input类型

let promptList = [
    {
        type: 'input',                  //类型
        message: '请设置一个用户名:',      //label字段
        name: 'name',                   //设置的name字段在结果返回时会变成key
        default: 'admin',               //默认值,如果用户没有输入时会自动填充默认值
        validate(str) {                 //输入后进行验证,如果返回的是字符串,那么就会提示在下方,除非返回true
            if(str.length < 3) return '用户名不能少于3个字符'
            return true
        }
    },
    {
        type: 'input',
        message: '请输入你的手机号码:',
        name: 'phone',
        validate(phone) {
            if(phone.length !== 11) return '手机的位数不正确'
            return true;
        }
    }
]

inquirer.prompt(promptList).then(res => {
    console.log(res) //返回用户所交互的对象,返回的是一个对象,里面的key是上面定义的name字段,value是用户选择的对象
})

 confirm类型

{
    type: 'confirm',
    name: 'confirm',
    message: '你今天是否需要加班',
    default: false
 }

 checkbox多选框

{
    type: 'checkbox',
    name: 'checkbox',
    message: '请问今天晚上加班到几点?',
    choices: ['8:00', '9:00', '10:00', '11:00'], // 也可以使用 choices: [{name: '8点',value: '8:00'}, ...]
    default: ['11:00']
}

list单选框

{
    type: 'list',
    name: 'list',
    message: '请问今天晚上加班到几点?',
    choices: ['8:00', '9:00', '10:00', '11:00'],
    default: ['11:00']
}

rawlist类型

该类型有些类似list只是这边是由用户自己输入编号

{
    type: 'rawlist',
    name: 'rawlist',
    message: '你喜欢吃哪种水果',
    choices: ['apple', 'pear', 'banana', 'grade'],
    default: 'pear'
}

 password类型

{
    type: 'password',
    name: 'psw',
    message: '请输入密码',
    validate(str) {
        if(str.length < 6) return '请输入至少6位数的密码'
        return true
    }
}

Expend类型 

该类型相当input由用户自己输入

{
    type: 'Expand',
    name: 'Expand',
    message: '你喜欢吃哪种水果',
    default: 'pear',
    validate(str) {
        if(str.length < 3) return '长度不能少于3个字符'
        return true
    }
}

editor类型

{
    type: "editor",
    message: "请输入备注:",
    name: "editor"
}

 10、在命令行中执行shell脚本

安装依赖

npm i shelljs --save

用法一:

#!/usr/bin/env node
var name = process.argv[2];
var shell = require("shelljs");

shell.exec("echo hello " + name);

用法二:

require('shelljs/global');

if (!which('git')) {
  echo('Sorry, this script requires git');
  exit(1);
}

mkdir('-p', 'out/Release');
cp('-R', 'stuff/*', 'out/Release');

cd('lib');
ls('*.js').forEach(function(file) {
  sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
  sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file);
  sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file);
});
cd('..');

if (exec('git commit -am "Auto-commit"').code !== 0) {
  echo('Error: Git commit failed');
  exit(1);
}

用法三

 

const command = `
  cp -rf ./abc ./test
  touch test.log
  rm -rf ./test
`;

_spawn('bash', ['-c', command], {
  cwd: 'C:\\Users\\even\\Desktop\\bf',
});

process.exit(0);

 

其他相关的库:

  npmlog: 处理npm日志的库 

  semver: 版本比对

11、关于路径问题

#!/usr/bin/env node
console.log(process.cwd());  //输出当前根目录
console.log(__dirname);     //输出bin目录

 注意:如果想从git上拉取代码,可以使用download-git-repo的依赖包

12、包的发布

  首先注册npmjs.org账号 => npm adduser/npm login (登录) => npm publish(发布)   

  注意需要在有package.json的目录下进行发布,并且在淘宝镜像下进行操作有可能会失败

切换成npm原始源

npm config set registry https://registry.npmjs.org/

切换成淘宝源

npm config set registry https://registry.npm.taobao.org

 12、使用node执行远程服务器的登录以及操作shell可以使用ssh2  ssh2-promise

 13、在node中利用child-process在window下执行shell命令报错的问题处理

  在window下不支持shell命令,但是可以使用git工具,但是在执行require('child-process').exec('ls -a')的时候报错,这是因为需要配置shell的使用环境, 按      以下步骤进行配置

       a、打开bash here  => 输入 which ls  => 输出 /usr/bin/ls

       b、cd /usr/bin => 再输入explorer .  会弹出对应的文件夹,复制文件夹地址 如  C:\Program Files\Git\usr\bin

       c、打开环境变量配置,系统环境变量配置,把复制来的地址添加到path下的选项中

 重启电脑后,这样就可以愉快的写代码了

posted on 2020-10-16 00:30  even_blogs  阅读(317)  评论(0编辑  收藏  举报