脚手架开发(1)-准备阶段
脚手架的基本原理
点击查看脚手架系列文章总览【正在更新】
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章
初始化项目
1. 创建项目文件
mkdir steamed-cli
cd steamed-cli
2. 使用lerna初始化项目
lerna init
项目默认结构如下:
C:.
| .git
│ lerna.json
│ package.json
│
└─packages
在此基础上需要进行一些改动:
删除
packages
目录,packages
是用来放置所有包的目录,包较多的情况下,最好多建几个文件夹将他们进行分类新建文件夹
commands
(命令) 、core
(核心包)、models
(类)、utils
(工具方法)修改
lerna.json
配置中的packages
属性,此属性指定包的位置{"packages": ["core/*","commands/*","models/*","utils/*"],"version": "1.0.0" }
新建
.gitignore
文件,添加git
忽略文件配置**/node_modules
新建
README.md
文件,对脚手架进行介绍及使用说明
整体流程
整个脚手架的开发分成三个阶段进行:准备阶段、注册阶段、执行阶段
- 准备阶段:在执行命令前需要做的一些事,如:检查node版本,检查脚手架更新,检查用户目录、权限等
- 注册阶段:解析用户输入的命令(command)和参数(options),这里的参数主要针对于全局参数。
- 执行阶段:安装/更新执行相应的package,也可直接执行本地文件。
准备阶段
创建入口 package
现在就正式开始脚手架的开发,首先需要一个入口。创建一个名叫 @steamed/cli
的包
lerna create @steamed/cli ./core
# 在最后可以加上一个路径,会将此包创建到指定的目录下
一个包的目录结构如下:
cli:.
│ package.json
│ README.md
│
├─lib
│ cli.js
│
└─__tests__cli.test.js
为了保持统一的规范,最好将 lib 下的 cli.js
改为 index.js
。
@steamed/cli
这个包是整个脚手架的入口,需要创建一个 /bin/index.js
文件作为入口,并在 packages.json
中配置 bin
属性。
{"name": "@steamed/cli","version": "1.0.0","bin": {"st": "./bin/index.js"}
}
测试流程
在 core/cli
下执行 npm link
,创建其软链接,使得我们可以在本地使用设置的命令进行调试。
在 core/cli/bin/index.js
下可以先写一句打印 console.log('hello steamed-cli')
,然后在命令行中执行 st
命令,如果正常输入,则说明到目前一切正常。
如何获取输入的命令参数?
需要根据用户输入不同的命令来做不同的事,使用 process.argv
可以获取到输入的所有内容,其值为一个数组。
st init procejectName['C:\\Program Files\\nodejs\\node.exe','C:\\Users\\DZY26\\AppData\\Roaming\\npm\\node_modules\\@steamed\\cli\\bin\\index.js','init','myname'
]
- 第一项是
node
的安装目录 - 第二项是所执行的文件路径
- 后面的内容就是所传入的参数
bin/index.js 只做一件事
对于输入命令后需要做的事情我们都放到 lib/index.js
中去做,在 bin/index.js
只做一件事。
- import-local:允许全局安装的包使用其自身的本地安装版本(如果可用)
如果存在本地版本,则执行本地脚手架。如果不存在,则执行 lib/index.js
中的内容
#! /usr/bin/env nodeconst importLocal = require('import-local');if (importLocal(__filename)) { console.log('执行本地脚手架');} else { require('../lib/index')(process.argv.slice(2))}
功能开发
本阶段的开发需要用到以下的 npm
包,
- semver:用户版本号规范校验,版本号比较等
- colors:对打印的日志设置颜色
- userHome:获取用户主目录
- npmlog:用户输入日志信息
- root-check:尝试降级具有root权限的进程的权限,如果失败,则阻止访问
- dotenv:可以将本地
.env
文件中的环境变量配置到全局环境变量中。
工具包开发
@steamed/log
功能:对
npmlog
进行封装,自定义打印级别和样式位置:/utils/log
** npmlog
打印级别**
在不同的场景下, 可以使用 npmlog
打印出不同类型的信息,包括:silly
,verbose
,info
,timing
,http
,notice
,warn
,error
,silent
默认打印级别为 info
,即大于等于 info
级别的信息才会被真正的打印。
主要作用
debug 信息一般使用 verbose
级别打印,因此默认情况下是不会展示debug信息的。为了更好的调试,定位问题,可以通过传入 --debug
参数使得可以看到debug信息,实现原理就是将 npmlog
的打印级别设置为 verbose
。
完整代码:
'use strict';const log = require('npmlog');log.level = process.env.STEAMED_CLI_LOG_LEVEL || 'info'; // 设置log 的打印级别,默认为info,可以在环境变量中拿到自定义级别设置log.heading = 'Steamed'; // 自定义log头部信息,一般为脚手架的名字log.headingStyle = { bg: 'white', fg: 'black' } // 自定义头部样式module.exports = log;
@steamed/get-npm-info
功能:获取
npm
信息、版本等功能位置:/utils/get-npm-info
完整代码:
'use strict';const axios = require('axios');const semver = require('semver');// 获取默认的npm源function getDefaultRegistry(origin = true) { return origin ? 'https://registry.npmjs.org/' : 'https://registry.npm.taobao.org/'}// 获取包的所有历史版本号async function getNpmVersions(npmName, registry) { const npmRegistry = registry || getDefaultRegistry(); return axios.get(`${npmRegistry}${npmName}`) .then((res) => { if (res.status === 200) { return Object.keys(res.data.versions); } return []; }) .catch(() => { return []; })}// 获取最新版本async function getLatestVersion(npmName, registry) { const versions = (await getNpmVersions(npmName, registry)) .sort((a, b) => semver.gte(a, b) ? -1 : 1); if (versions[0]) { return versions[0] } else { throw new Error('检查更新失败'); }}module.exports = { getDefaultRegistry, getNpmVersions, getLatestVersion};
准备阶段功能实现
在准备阶段需要按照下面的顺序依次完成每个功能。
检查当前版本
这里指的是脚手架当前版本,此版本号就是 package.json
中的 version
的值。
const pkg = require('../package.json'); // 检查当前版本function checkPkgVersion() { log.info('cli-version', pkg.version);}
检查node版本
需要检查用户当前使用的 node
版本是否满足脚手架的最低版本要求,以保证脚手架的所有功能可以正常使用。
新建一个 constant.js
文件,用户配置一些常量配置信息。
const LOWEST_NODE_VERSION = '12.0.0'; // 最低node版本const STEAMED_CLI_HOME_PATH = '.steamed'; // 脚手架主目录名称const STEAMED_CLI_LOG_LEVEL = 'info'; // log 的打印等级module.exports = { LOWEST_NODE_VERSION, STEAMED_CLI_HOME_PATH, STEAMED_CLI_LOG_LEVEL}
const semver = require('semver');const { LOWEST_NODE_VERSION } = require('./constant');// 检查node版本function checkNodeVersion() { const currentVersion = process.version; // 获取当前使用的版本 const lowestVersion = LOWEST_NODE_VERSION; if (!semver.gte(currentVersion, lowestVersion)) { throw new Error(`steamed-cli 需要node的最低版本为${lowestVersion},当前node.js版本为${currentVersion}`); }}
检查root权限
需要检查用户是否有root权限,如果没有,则会进行降级处理,此功能在 root-check
中已实现。
import rootCheck from 'root-check';// 检查root权限function checkRoot() { rootCheck();}
检查用户主目录
检查用户主目录是否存在
const userHome = require('user-home');// 检查用户主目录function checkUserHome() { if (!userHome || !fs.existsSync(userHome)) { throw new Error(colors.red('当前用户主目录不存在!')); } else { process.env.STEAMED_CLI_USER_HOME = userHome; }}
检查入参
- 用户需要执行命令,则至少需要输入一个命令或参数。
- 检查用户输入的参数是否包含
-d
或--debug
,如果存在,则表示开启debug
模式,需要将log
的打印等级设置为verbose
const log = require('@steamed/log');const { STEAMED_CLI_LOG_LEVEL } = require('./constant');// 检查用户主目录function checkArgv(argv) { if (argv.length < 0) { throw new Error('请输入命令') } if (argv.includes('-d') || argv.includes('--debug')) { log.level = 'verbose'; } else { log.level = STEAMED_CLI_LOG_LEVEL; } process.env.LOG_LEVEL = log.level; // 将log等级存到环境变量中,以便其他地方使用}
检查环境变量
将主目录下的 .env
文件中环境变量会被注入到 process.env 中
const { STEAMED_CLI_HOME_PATH } = require('./constant');const userHome = require('user-home');// 检查环境变量function checkEnv() { const dotnev = require('dotenv'); const envPath = path.resolve(userHome, '.env'); // 获取主目录下的 .env 文件 dotnev.config({ path: envPath }); // .env 中的环境变量会被注入到 process.env 中 process.env.STEAMED_CLI_HOME_PATH = path.resolve(userHome, STEAMED_CLI_HOME_PATH);}
检查版本更新
检查本地脚手架版本是否是最新版本,如果不是,则提示用户及时更新脚手架
const { getLatestVersion } = require('@steamed/get-npm-info');// 检查更新async function checkCliUpdate() { const lastVersion = await getLatestVersion(pkg.name); if (lastVersion && !semver.gte(pkg.version, lastVersion)) { log.warn('steamed更新', `发现新版本${lastVersion},当前版本${pkg.version},请及时更新!`); }}
打印报错信息
在上面的流程中,每一步都有可能会出错,可能是第三方组件内部报错,也可能是我们自己写的 throw new Error()
,这对这些错误信息,我们可以在最外层通过 try catch
处理。
async function index() { try { checkPkgVersion(); checkNodeVersion(); checkRoot(); checkUserHome(); checkEnv(); await checkCliUpdate(); } catch (e) { log.error(e.message); // 只打印关键信息 if (log.level === 'verbose') { // 如果是debug模式,则打印出完整的错误信息栈,以便于定位问题 console.log(e); } }}
完整代码地址 Github
:https://github.com/DengZhanyong/steamed
点击查看脚手架系列文章总览
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章
脚手架开发(1)-准备阶段相关推荐
- Python中str()与repr()函数的区别——repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用...
Python中str()与repr()函数的区别 from:https://www.jianshu.com/p/2a41315ca47e 在 Python 中要将某一类型的变量或者常量转换为字符串对象 ...
- 软件开发之计划阶段: ”声控打鼓”游戏的”用户/场景”分析
"用户/场景"分析(a.k.a user scenarios)对于软件开发的计划阶段是十分重要的.只有明确了软件的用户群,以及软件所应用的场合,才能真正了解到所要开发的软件是否有价 ...
- 脚手架(一)——脚手架开发入门
脚手架 1. 脚手架是什么 2. 为什么要开发脚手架 3. 脚手架实现原理 4. 脚手架开发 4.1 开发流程 4.2 安装脚手架 4.3 脚手架开发难点 1. 脚手架是什么 脚手架本质是一个操作系统 ...
- Vue教程03-Vue脚手架开发环境
Vue脚手架开发环境 1.Vue开发环境的安装 1.1安装Node JS 1.2全局安装Vue脚手架 1.3安装HBuilderX 1.4强烈推荐安装以下工具软件 2.HBuilderX创建Vue项目 ...
- 软件开发的六大阶段 (指针经典原创)
软件开发的六大阶段 第一阶段:调研阶段 本阶段我们将组成企业项目调研组到企业进行现场调研,企业也部分需组织相应人员进行配合.整个调研工作将历时三星期到一个月左右时间.调研内容按以下方面进行 ...
- 前端脚手架开发(1)
脚手架实现原理 当我们在终端输入vue create xx的时候 终端会去全局环境变量中,找到vue指令的方法 查看create-react-app的位置,所有通过npm -g安装的包都会放入该目录下 ...
- 脚手架开发(2)-注册阶段
点击查看脚手架系列文章总览[正在更新] 个人网站:www.dengzhanyong.com 关注公众号[前端筱园],不错过每一篇文章 在上篇文章,已经完成了第一个阶段:准备阶段,在准备阶段做了许多基础 ...
- 项目管理_软件开发的六大阶段
转载 2013年05月01日 19:47:08 http://blog.csdn.net/northplayboy/article/details/601443 第一阶段:调研阶段 本阶段我们将组成企 ...
- 利用Vue 脚手架 开发chrome 插件,太方便了
前言 一个 Chrome 插件,核心就是 manifest.json 文件,manifest.json 下的属性 content_scripts 指定inject的脚本列表 js,css [ inje ...
- 巨蟒python全栈开发-第11阶段 ansible_project2
一个NB的网站: https://www.toolfk.com/ CDN:将用户的需求送到最近的节点:内容分发网络 有些是专门做CDN的工具 常用的markdown是需要知道的,短信有字数限制. we ...
最新文章
- IDEA使用log4j
- css盒子子类继承父类哪些,css不继承父类的属性有哪些
- Django_ORM数据表查询总结
- 怎样定义网页里的关键字关键词
- qml mousearea 点击其他地方_Qml 快速使用
- android assets文件夹资源的访问
- 使用Visual Studio 创建新的Web Part项目
- java 过滤xss脚本_Java Web应用程序的反跨站点脚本(XSS)过滤器
- 莫比乌斯带catia建模_用sw2018制作莫比乌斯环图文教程
- arraylist线程安全吗_Java面试复习-IO和多线程
- ##CSP 201803-2 碰撞的小球(C语言)100分
- mysql的压缩包,mysql 压缩包安装
- 什么是面向对象对象,什么是面向过程,什么是面向对象思想。
- 什么是CSS网页切图
- pdfFactory2020最新7许可密钥-激活码版虚拟打印软件 中文特别授权版下载
- oracle英文日期转换为中文,excel中文日期与英文日期如何转换
- 编程语言介绍以及特点
- 一名程序员的内心独白:我很忙,但我的代码还是很糟糕
- java 模板 word转pdf 可分页 带图片
- Unit2 附加问句