大家好,我是一只不守妇道的花喵。

本文你能够学到:

  • 相熟cli开发的全流程,能够开发出相似@babel/core@babel/preset-env的npm包(是不是很装逼)
  • 相熟npm公布流程
  • 相熟lerna开发cli工具,疾速入门lerna
  • 理解架构师如何残缺设计、开发一个脚手架
  • 如何利用脚手架高效解决日常开发中的CI/CD流程

注:本文老衲在mac下测试,应用win的同学目录本人改下哈

1. 筹备

为什么要开发脚手架(像个架构师一样思考)

这就须要说到CI/CD

工厂里的装配线以疾速、自动化、可反复的形式从原材料生产出消费品。同样,软件交付管道以疾速、自动化和可反复的形式从源代码生成公布版本。如何实现这项工作的总体设计称为“继续交付”(CD)。启动装配线的过程称为“继续集成”(CI)。确保品质的过程称为“继续测试”,将最终产品提供给用户的过程称为“继续部署”。一些专家让这所有简略、顺畅、高效地运行,这些人被称为运维开发(DevOps)践行者。[1]

解决的问题:

传统(事实上大一点点的公司都会思考CI/CD自动化)手动操作低效、出错率高、依赖某些特定人员等。尤其在我的项目越来越大的时候,这些问题将更为严重,更不必谈“灰度公布”,这些点有一个就有足够的理由接着看本文。作为前端架构师就是要解决各种“效力”问题。

解决的场景:

  • 对立CI/CD标准
  • 进步CI/CD效率
  • 缩小CI/CD出错率
  • 不依赖任何人,按流程走
  • ……

前置常识

  • 相熟js,理解nodejs
  • 理解过一种或几种命令行工具,如:vue-clivitecreate-react-appgit
  • 理解npmjs注册、登录、公布等流程(上面有起码常识教程)

需要

  • 分包,一个脚手架如果较为简单就必然波及分包,分包能够无效升高工程复杂度、进步开发/保护效率、并且每个分包绝对独立,能够独自应用,babel就有将近150的包,点击查看,相互依赖,设想下你怎么治理这么多包?每次更新版本、装置依赖怎么操作?分包的依赖降级又要怎么整?
  • 命令+子命令,日常用的gitvue-cli都有子命令,脚手架如果足够简单也会波及子命令
  • 日志
  • 命令行交互
  • 网络申请
  • 本地文件解决
  • ……

注:咱们做一个我的项目的需要调研到技术选型,不会只思考当下须要什么,都会再需要下“多思考一步”,会思考其扩展性,如下面的分包、命令+子命令。当然如果的确是一个很小的脚手架,是能够不思考分包以升高设计的复杂度(当然相熟了lerna没啥复杂度),比如说只有一两个功能模块。然而如果像vue-clibabel,波及的模块/插件几十上百个,那就必须要思考。作为预留,咱们这边应用能分包(Multirepo)的扩大计划。

Monorepo vs Multirepo

选型

  • 分包,lerna,业界闻名的Multirepo计划解决方案
  • 命令+子命令,commanderyargs
  • 日志,npmlogcolors
  • 命令行交互
  • 网络申请
  • 本地文件解决,fs-extra

其余

  • 版本比拟:semver
  • 解析参数选项:minimist
  • ……
    <!--
  • .env文件中加载环境变量:dotenv
  • npm装置:npminstall
  • root权限查看:root-check
  • 获取用户主目录的门路:user-home
    -->

2. 开始

2.1.初始化

  • 创立mkdir ~/huamiao-cli_all文件夹,前面有后端服务
  • 创立mkdir ~/huamiao-cli_all/huamiao-cli
  • 切换目录:cd ~/huamiao-cli_all/huamiao-cli
  • 初始化为npm我的项目:npm init -y
  • 装置lerna:npm i -D lerna,为了使用方便(应用的时候不必应用npx,)全局也装置lerna:npm i -g lerna
  • lerna初始化:lerna init(如果不想全局装置lerna,能够应用npx lerna init),生成lerna.json并批改版本为1.0.0

    {"packages": [  "packages/*"],"version": "1.0.0"}
  • lerna init后会默认执行git init,创立.gitignore,疏忽以下文件:

    **/node_modules.vscode.DS_Storelerna-debug.log
  • git暂存:git add . && git status

    git add . && git status位于分支 master尚无提交要提交的变更:(应用 "git rm --cached <文件>..." 以勾销暂存)      新文件:   .gitignore      新文件:   lerna.json      新文件:   package-lock.json      新文件:   package.json
  • 提交到本地仓库:git commit -m 'init'

注:npm官网源如果太慢,能够切换为淘宝源或应用cnpm/yarn

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

2.2.创立包&测试公布

  • 创立core外围包,输出lerna create core,一路回车
    特地留神:package name: (core) @huamiao-cli/core,这边须要蕴含组织名称@huamiao-cli

    lerna create corepackage name: (core) @huamiao-cli/core{"name": "@huamiao-cli/core","version": "1.0.0","description": "> TODO: description","homepage": "","license": "ISC","main": "lib/core.js","directories": {  "lib": "lib",  "test": "__tests__"},"files": [  "lib"],"publishConfig": {  "registry": "https://registry.npm.taobao.org"},"scripts": {  "test": "echo \"Error: run tests from root\" && exit 1"}}Is this OK? (yes) lerna success create New package @huamiao-cli/core created at ./packages/core
  • 创立utils工具包,同上
  • npmjs创立组织:@huamiao-cli

点击Create

点击Skip

当初能够在npmjs->个人头像->packages查看到

  • 公布,lerna publish,公布组织的包须要配置publishConfig
    如果你用淘宝源,可能会呈现上面的状况:

    {"name": "@huamiao-cli/core","version": "1.0.6","description": "> TODO: description","homepage": "","license": "ISC","main": "lib/core.js","directories": {  "lib": "lib",  "test": "__tests__"},"files": [  "lib"],"publishConfig": {  "registry": "https://registry.npm.taobao.org"},"scripts": {  "test": "echo 'run utils test'"}}

    看这边

    "publishConfig": {  "registry": "https://registry.npm.taobao.org"},

    packages中两个包的package.json都改为:

    "publishConfig": {  "access": "public"}

    公布前要git提交,并且要绑定近程仓库,否则:

    lerna publish     info cli using local version of lernalerna notice cli v4.0.0lerna info current version 1.0.0lerna ERR! ENOREMOTEBRANCH Branch 'master' doesn't exist in remote 'origin'.lerna ERR! ENOREMOTEBRANCH If this is a new branch, please make sure you push it to the remote first.

    咱们在gitee创立一个公开库,这边不赘述了,有疑难留言。

配置近程仓库并推送:

git remote add origin https://gitee.com/xxx/huamiao-cli.gitgit push -u origin master

切换到npm官网源:

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

注:如果这边不慎没切换到官网源,再提交一个git,再公布,版本累加即可公布

公布:lerna publish选第一个先:

❯ Patch (1.0.1)   Minor (1.1.0)   Major (2.0.0)   Prepatch (1.0.1-alpha.0)   Preminor (1.1.0-alpha.0)   Premajor (2.0.0-alpha.0)   Custom Prerelease   Custom Version 

输出y

lerna publish                                                info cli using local version of lernalerna notice cli v4.0.0lerna info current version 1.0.0lerna info Assuming all packages changed? Select a new version (currently 1.0.0) Patch (1.0.1)Changes: - @huamiao-cli/core: 1.0.0 => 1.0.1 - @huamiao-cli/utils: 1.0.0 => 1.0.1? Are you sure you want to publish these packages? (ynH) y>> Yes

残缺日志:

info cli using local version of lernalerna notice cli v4.0.0lerna info current version 1.0.4lerna info Looking for changed packages since v1.0.4? Select a new version (currently 1.0.4) Patch (1.0.5)Changes: - @huamiao-cli-dev/core: 1.0.4 => 1.0.5 - @huamiao-cli-dev/utils: 1.0.4 => 1.0.5? Are you sure you want to publish these packages? Yeslerna info execute Skipping releaseslerna info git Pushing tags...lerna info publish Publishing packages to npm...lerna notice Skipping all user and access validation due to third-party registrylerna notice Make sure you're authenticated properly ¯\_(ツ)_/¯lerna WARN ENOLICENSE Packages @huamiao-cli-dev/core and @huamiao-cli-dev/utils are missing a license.lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.lerna http fetch PUT 200 https://registry.npmjs.org/@huamiao-cli-dev%2futils 4431mslerna success published @huamiao-cli-dev/utils 1.0.5lerna notice lerna notice   @huamiao-cli-dev/utils@1.0.5lerna notice === Tarball Contents === lerna notice 73B  lib/utils.jslerna notice 487B package.jsonlerna notice 108B README.md   lerna notice === Tarball Details === lerna notice name:          @huamiao-cli-dev/utils                  lerna notice version:       1.0.5                                   lerna notice filename:      huamiao-cli-dev-utils-1.0.5.tgz         lerna notice package size:  565 B                                   lerna notice unpacked size: 668 B                                   lerna notice shasum:        9af0a5740f88b0a6829ea4a90c7107435d0f5489lerna notice integrity:     sha512-8kKZVoH1sF0WB[...]1aTA3UQiBl70g==lerna notice total files:   3                                       lerna notice lerna http fetch PUT 200 https://registry.npmjs.org/@huamiao-cli-dev%2fcore 7474mslerna success published @huamiao-cli-dev/core 1.0.5lerna notice lerna notice   @huamiao-cli-dev/core@1.0.5lerna notice === Tarball Contents === lerna notice 71B  lib/core.js lerna notice 485B package.jsonlerna notice 105B README.md   lerna notice === Tarball Details === lerna notice name:          @huamiao-cli-dev/core                   lerna notice version:       1.0.5                                   lerna notice filename:      huamiao-cli-dev-core-1.0.5.tgz          lerna notice package size:  566 B                                   lerna notice unpacked size: 661 B                                   lerna notice shasum:        a26f686a3951de2df1ef2196a1b82c9d87e299eclerna notice integrity:     sha512-rP/WQo7oROLD3[...]7LZKGyzZ9vHYQ==lerna notice total files:   3                                       lerna notice Successfully published: - @huamiao-cli-dev/core@1.0.5 - @huamiao-cli-dev/utils@1.0.5lerna success published 2 packages

期待胜利后,在npmjs packages中的huamiao-cli组织,查看是否公布胜利。

这边公布在huamiao-cli-dev这个组织外面


npmjs偶然会不稳固,并且对于国内网络互联互通问题常常拜访较慢,如果失败,判断不是咱们的问题,能够从新公布,就是learn公布每次都会要求加个版本,这点有点不合理

注:这边老手会遇到各种问题,如有问题,能够在留言,老衲帮掘友解决。

强烈建议老手测试公布胜利后再进行开发。

3. 起码必要常识

3.1.learn

3.1.1.初始化

  • lerna init,初始化我的项目,创立lerna.json存储version,并查看package.jsondevDependency有无lerna,没有则增加

3.1.2.创立package

  • lerna create,创立package

    以下<>为必须,[]为可选Usage:lerna create <name> [loc]name:惟一、蕴含作用域名(本文为@huamiao-cli,eg:@huamiao-cli/core)loc:包和包能够嵌套,抉择包的绝对地址,默认为第一个配置的包地位
  • lerna add,装置依赖,eg:lerna add module-1 --scope=module-2scope指定包,不指定会装置到每个包里,每次只能装置一个依赖
Usage:lerna add <package>[@version] [--dev] [--exact] [--peer]
  • lerna link,链接依赖,本地调试包的相互依赖,多个包一起开发

3.1.3.开发和测试

  • lerna exec,对packages中每个包执行命令,eg:lerna exec -- rm -rf ./node_modules
  • lerna run,对packages中每个包执行npm script,eg:lerna run test
  • lerna clean,删除所有packages中的node_module,约等于lerna exec -- rm -rf ./node_modules
  • lerna bootstrap,对packages中每个包从新按装置依赖

3.2. npm公布繁难流程

3.2.1. npmjs注册账号

3.2.2. 切换为官网源

关上终端,切换为官网源(如果之前没切换过源,默认是官网的,不须要切换)

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

3.2.3. npm登录

npm login

Username: <username>Password: Email: (this IS public) <username>@qq.comLogged in as <username> on https://registry.npmjs.org/.

3.2.4. npm公布

npm publish

Successfully published: - @huamiao-cli/core@1.0.0 - @huamiao-cli/utils@1.0.0lerna success published 2 packages

4. cli筹备工作

4.1. 配置命令

packages/core将作为cli入口

切换到packages/core目录

新增文件bin/index.js

#!/usr/bin/env noderequire('../lib/index')();

package.json增加:

"bin": {  "miao": "bin/index.js"},

lib/index.js

console.log('hello world!');

npm link装置到全局,全局就能够调用miao命令了

关上终端测试下:miao,测试胜利,失常打印出:

hello world!

4.2. 版本号、欢送语

接着开发,获取版本号

const packageJson = require('../package');

下面的欢送语,比拟个别,咱们来个有逼格的:

const packageJson = require('../package');console.log('欢送应用');console.log(` _   _             __  __ _                    ____ _ _ | | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | ||  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | ||_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}`);

4.3. 日志,分包开发

下面的console.log('欢送应用');改色彩下,晋升逼格:

const { log } = require('@huamiao-cli/utils');

这边log咱们还没开发

core/package.json增加

"dependencies": {    "@huamiao-cli/utils": "^1.0.0"},

lerna link

当初就能够用packages/utils包的办法了

日志咱们用的是npmlog,切换到packages/utils须要装置依赖:npm i npmlog

packages/utils/package.json批改:

  "main": "lib/index.js",

lib/index.js文件:

'use strict';const log = require('./log');// 对立导出,前面还有很多工具module.exports = {  log};

lib/log.js文件:

const log = require('npmlog')log.level = 'info'log.heading = 'huamiao-cli' // 自定义头部log.addLevel('success', 2000, { fg: 'green', bold: true }) // 自定义success日志log.addLevel('notice', 2000, { fg: 'blue', bg: 'black' }) // 自定义notice日志module.exports = log

回到packages/core/lib/index.js

"use strict";module.exports = core;const packageJson = require("../package");const { log } = require("@huamiao-cli/utils");function core() {  console.log(` _   _             __  __ _                    ____ _ _ | | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | ||  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | ||_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}`);  log.info("欢送应用");}

这样版本号、欢送语曾经ok了

4.4. nodejs最低版本判断

咱们这个cli应用到的一些库是须要nodejs version 12+,所以这边咱们须要查看下以后nodejs的版本,这边须要semver包,装置下:npm i semver

packages/core/lib/index.js减少:

  const semver = require("semver");  const MINIMUM_NODEJS_VERSION = "14.0.0";  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {    console.log(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);    process.exit();  }

这个提醒咱们想让他变成红色,能够用log:

  const semver = require("semver");  const MINIMUM_NODEJS_VERSION = "14.0.0";  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {    log.error(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);    process.exit();  }

最低nodejs的版本号咱们抽离常量进去,不便前面批改

packages/core/lib/index.js当初咱们残缺的代码如下:

"use strict";module.exports = core;const packageJson = require("../package");const { log } = require("@huamiao-cli/utils");function core() {  console.log();  console.log(` _   _             __  __ _                    ____ _ _ | | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | ||  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | ||_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}`);  log.info("欢送应用");  console.log();  const semver = require("semver");  const MINIMUM_NODEJS_VERSION = "14.0.0";  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {    log.error(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);    process.exit();  }}

执行下miao

4.5. 剖析参数是否无效,是否debug

接下来剖析下参数

为了调试不便packages/utils/lib/log.js能够批改日志等级为verbose,这个是调试用的等级:

log.level = 'verbose'

校验入参,这边须要,minimist包,装置下依赖:npm i minimist

  const minimist = require("minimist");  log.info("校验入参:");  let args = minimist(process.argv.slice(2));  log.info("args: ", args);    if (args["_"].length === 0) {    log.warn('请输出参数')  }

测试下
miao cmdChild cmdChildParams -a aParams -b bParams

huamiao-cli info 欢送应用 huamiao-cli verb 校验入参: huamiao-cli verb args:  { _: [ 'cmdChild', 'cmdChildParams' ], a: 'aParams', b: 'bParams' }

咱们常常须要调试,所以咱们加个debug参数,miao --debugminimist默认反对

  const minimist = require("minimist");  log.info("校验入参:");  let args = minimist(process.argv.slice(2));  log.info("args: ", args);  if (args["_"].length === 0 && !args.debug) {    log.warn("请输出参数");  } else {    // 能够在这配置debug相干设置,比方批改log的等级为verbose来打印调试日志    log.level = "verbose";    log.verbose("debug");  }

增加下环境变量process.env.LOG_LEVEL,并设置log的日志等级

  const minimist = require("minimist");  log.verbose("校验入参:");  let args = minimist(process.argv.slice(2));  log.verbose("args: ", args);  if (args["_"].length === 0 && !args.debug) {    log.warn("请输出参数");  } else {    // 能够在这配置debug相干设置,比方批改log的等级为verbose来打印调试日志    if (args.debug) process.env.DEBUGGING = "verbose";    else process.env.DEBUGGING = "info";    log.level = process.env.DEBUGGING;  }

筹备工作算完结了

4.6. 注册命令

应用commander包,装置依赖:npm i commander

const program = require("commander");// 设置版本号、自定义用法说明program.version(packageJson.version).usage("<command> [options] 其余阐明");// 增加命令能够在这里增加// ……// 注册命令program.parse(process.argv);

咱们测试下,miao -h

增加个简略的命令:

const program = require("commander");  // 设置版本号、自定义用法说明  program.version(packageJson.version).usage("<command> [options] 其余阐明");  // 增加命令能够在这里增加  program    .command("test")    .description("形容")    .option("-a, --all", "清空全副")    .action((cmd, options) => {      // cmd.all 主动由下面配置 .option("-a, --all", "清空全副") 创立      console.log("cmd: ", cmd.all);      log.success("测试", "一只花喵");    });  // 其余子命令  program.command("*").action(function (cmd, options) {    console.log("cmd: ", cmd);    console.log("options: ", options.args);    console.log("没有匹配到命令:miao", args['_']);  });  // 注册命令  program.option("--debug", "关上调试模式").parse(process.argv);

试试miao test -a aaaamiao test --all bbbbmiao diy吧!

内容有点多,第一节先这样吧!

FAQ

  • 依赖谬误或未知谬误,清理所有依赖lerna clean,重装依赖lerna bootstrap

    Loadmap

  • init【重磅】:抉择模板、自定义模板等
  • 公布【重磅】:公布、git操作、构建等
  • 更多实现细节

参考

  • [1] 什么是 CI/CD:https://linux.cn/article-9926...
  • lerna doc:https://github.com/lerna/lerna
  • CI/CD是什么:https://www.redhat.com/zh/top...

点赞、关注越多更新越快哦~