背景
这段时间因项目需求,需要写一个node的工具,用于扫描一些结构文件,并输出扫描的结果。 本质上其实是一个node工具,类似于vue cli这样的node工具。
这里,我们不弄这么麻烦,我们做一个简单的从远端git库中下载文件,并记录下载的文件数量即可。
本文所涉及的代码,均已记录在 示例仓库-node-tool中,如有需要可自取!
任务拆解
node工具需要做以下事情:
- 命令行交互,用于接收以下信息
- 项目名称,可以输入中文
- git库地址,可能需要校验下地址是不是绝对地址
- 用户名和密码,密码输入时不可见
- 下载远端git库中的文件,并存储至本地
- 【进阶】发布包
开始实现
项目初始化
在gitee.com中创建一个仓库,例如: node-tool
创建好仓库后,将代码clone下来。
进入到项目目录下,然后,在项目录下打开命令行,并输入:
yarn init
或 npm init
根据提示,填写相应的信息,例如:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ yarn init
yarn init v1.17.3
question name (node-tool):
question version (1.0.0):
question description: A simple front-end node tool, which supports global installation and use, and supports command-line interaction
question entry point (index.js):
question repository url (git@gitee.com:hellojameszhang/node-tool.git): https://gitee.com/hellojameszhang/node-tool.git
question author (jameszhang <james.zxj@foxmail.com>):
question license (MIT):
question private:
success Saved package.json
Done in 111.13s.
添加脚本和命令行工具入口
"bin": {
"scan": "./bin/index.js"
},
"type": "commonjs",
"scripts": {
"start": "node ./bin/index.js"
},
"engines": {
"node": ">=12.16"
}
关于package.json,大家可以参考npm package.json,官方文档是正解!
这里简单介绍下用到的几个属性:
- scripts, 是一个字典,它包含有package生命周期的不同时间运行的脚本命令。键是生命周期事件,值是要在该点运行的命令。例如:
- prepublish,在打包和发布包之前运行,也可以在本地运行
- prestart,在
npm start
命令运行执行前运行,也可以在本地运行 - 关于scripts相关的更多细节,可以参阅:npm package.json scripts
- engines, 指定包需要在哪个node版本中运行,如果不指定,默认所有node版本中均可运行。
- type: commonjs, 不特别指定的话,默认此项值为: commonjs, 也可指定值为:module, 即支持ESM规范(import / export)
- bin, 许多软件包都有一个或多个可执行文件,在全局安装后,会安装到 PATH 中包提供的可执行文件,安装后时,npm 会将该文件符号链接到
- 本地安装:
./node_modules/.bin/
- 全局安装:
prefix/bin
,与npm全局命令的路径相同。
- 本地安装:
通过以上配置后,我们在项目目录下添加新的文件目录和文件:
#!/usr/bin/env node
console.log('>>> hello node tool')
内容很简单,添加一个打印信息,以表明功能可正常运行!其中 ./bin/index.js
文件的第一行:
#!/usr/bin/env node
表明,需要指定使用node来执行此文件:
- windows环境中,会从环境变量中查找node安装路径下的bin目录查找node来执行脚本。
- linux环境下,会查找用户的
/usr/bin
下的env环境变量中查找node来执行脚本。
若不添加此行代码,在全局安装后,可能会报如下报错:
$ node-tool
C:\Users\James\AppData\Roaming\npm/node_modules/node-tool/bin/index.js: line 1: syntax error near unexpected token `'>>> hello node tool''
C:\Users\James\AppData\Roaming\npm/node_modules/node-tool/bin/index.js: line 1: `console.log('>>> hello node tool')'
本地安装&验证
在当前项目目录下(与package.json同级)执行以下命令,将通过本地目录,全局安装node-tool包
npm install -g .
安装好后,我们在命令行执行一下,试试看有没有效果:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ node-tool
>>> hello node tool
可以看到,我们的命令生效了,说明可以通过全局安装包执行命令了。
命令行交互
完成上面的步骤,基本上我们一个简单的框架算是搭建完成了,接下来我们丰富下工具包,添加命令行交互相关的功能。 接下来,我们将会使用到以下2个包:
- Inquirer, 交互式命令行提示工具
- commander, 一个轻巧的nodejs模块,提供了用户命令行输入和参数解析强大功能,有提供中文文档 commander-中文文档
关于这2个包的详细使用方法,可自行查阅文档,这里我们将只会演示使用到的场景,就不“搬砖”了。
首先,我们先安装这2个包:
yarn add inquirer@^8.0.0 commander
注意: inquirer在9.x版本后,原生支持ESM语法,若仍想使用commonjs语法,需安装8.x版本.详细可参考官方文档。
添加交互
接下来,我们将实现一个简单的交互功能,其主要提示用户输入以下信息,且每一项都必须填写:
- 应用名称
- 仓库地址
- 用户名
- 密码,不可明文展示
添加如下代码:
#!/usr/bin/env node
const inquire = require('inquirer')
async function main(){
const answers = await inquire.prompt([
{
type: 'input',
name: 'application',
message: 'please input your application name: ',
validate: (str) => required(str, 'please input your application name')
},
{
type: 'input',
name: 'url',
message: 'please input your git repo url: ',
validate: (str) => required(str, 'please input your git repo url')
},
{
type: 'input',
name: 'username',
message: "please input gitee.com's username: ",
validate: (str) => required(str, "please input gitee.com's username")
},{
type: 'password',
name: 'password',
message: "please input gitee.com's password: ",
validate: (str) => required(str, "please input gitee.com's password")
}
])
console.log(answers)
}
main()
function required(str, message){
if(str) return true
return message
}
试着运行一下吧,正常应该可以得到类似如下的信息:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ yarn start
yarn run v1.17.3
$ node ./bin/index.js
? please input your application name: node-tool
? please input your git repo url: https://gitee.com/hellojameszhang/node-tool.git
? please input gitee.com's username: username
? please input gitee.com's password: [hidden]
{
application: 'node-tool',
url: 'https://gitee.com/hellojameszhang/node-tool.git',
username: 'username',
password: 'password'
}
Done in 27.73s.
至此,基本的交互功能,我们基本实现了。
优化命令行
接下来,我们优化下我们的命令,即:把commander
用起来。通过它,我们将实现以下命令:
- scan help
- scan --version
- scan version
- scan repo
最终效果可能如下:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan --help
Usage: index <command> [options]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
version
repo 扫描单个仓库文件
help [command] display help for command
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan version
node-tool 1.0.0
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan repo --help
Usage: index repo [options]
扫描单个仓库文件
Options:
-h, --help display help for command
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$
要想实现上述功能,我们就需要使用到commander
.
基于我们前面实现的交互功能,再新增如下代码(完整的代码,可通过node-tool仓库查看下载):
const {program} = require('commander')
program
.version(`node-tool ${require('../package').version}`)
.usage('<command> [options]')
.command('version')
.action(() => {
console.log(`node-tool ${require('../package').version}`)
})
program
.command('repo')
.description('扫描单个仓库文件')
.action(async (str, options) => {
console.log('==>', str, options)
await main()
})
// other code ...
program.parse()
到此,基本功能已实现,这里我们稍做些解释
- prgram.command, 创建子命令
- prgram.description, 子命令的描述,在帮助信息中可以看到
- program.action, 子命令对应的动作
- program.parse, 解析命令
有关commander更详细的介绍,可以参阅:commander-中文文档
下载远端git库中的文件
基于前面我们已实现的功能,接下来,开始做我们真正需要做的事情:下载远端git库中的文件。
要实现这个功能,我们安装一个npm包,再稍微包装下即可完成功能:
yarn add download-git-repo
download-git-repo 是一个用来从代码仓库中下载代码用的包
安装好包之后,我们在根目录下仓库一个utils
目录,用于存储工具函数,并在此目录下添加index.js
文件,其内容如下:
const download = require('download-git-repo')
function downloadRepo(url, distDir, options = {clone: true}){
return new Promise((resolve, reject) => {
download(url, distDir, options, function(err){
if(err){
console.error(`download file from ${url} failed!`)
reject(err)
} else {
resolve(distDir)
}
})
})
}
/**
* @description: 是否为绝对地址
* @param {String} url
* @returns {Boolean}
*/
function isAbsoluteUrl(url) {
// const reg = new RegExp("^(https):\/\/[\\w\\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?")
const reg = new RegExp("^(https):\/\/[\\w-_]+(\\.)+[\\w\\-_]+")
return reg.test(url)
}
function getRepoUrlByAuth(username, password, url){
if(isAbsoluteUrl(url)) {
const prefix = 'https://'
const repoUrl = url.substring(prefix.length)
let downloadUrl = `direct:${prefix}${encodeURIComponent(username)}:${encodeURIComponent(password)}@${repoUrl}`
if(repoUrl.indexOf('#') <= -1){
downloadUrl += '#master'
}
return [true, downloadUrl]
}
return [false, '请输入绝对地址,如:https://gitee.com/demoRepo/']
}
module.exports = {
downloadRepo,
getRepoUrlByAuth,
isAbsoluteUrl
}
接下来,修改下 /bin/index.js
文件,接收命令行传入的地址,或提示用户输入仓库地址,修改部分的代码为:
async function main(url){
const answers = await inquire.prompt(PROMPT)
const rawUrl = url ? url : answers.url
const [flag, downloadUrl] = getRepoUrlByAuth(answers.username, answers.password, rawUrl)
if(!flag){
const message = downloadUrl
console.error(message)
} else {
const storageDir = './repos/' + answers.application
await downloadRepo(downloadUrl, storageDir)
console.log('download repo success!')
}
}
完整的代码修改部分,可以参考如下变更集: gitee.com/hellojamesz…
接下来,我们全局安装下,简单验证下功能。
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm install -g .
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ scan repo https://gitee.com/hellojameszhang/node-tool.git
? please input your application name: node-tool
? please input gitee.com's username: hellojames
? please input gitee.com's password: [hidden]
download repo success!
到此,工具包的基本功能已实现!
进阶:发布包
发包前,我们需要在npmjs中注册一个帐号,注册过程这里就不细述了。 发布包是比较简单的,我们尝试下。 基本分为2个步骤:
- npm login, 登录
- npm publish, 发布包
登录npm
输入命令npm login
,然后按提示输入相应信息即可:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm login
npm notice Log in on https://registry.npmjs.org/
Username: jameszhang
Password:
Email: (this IS public) james.zxj@foxmail.com
npm notice Please check your email for a one-time password (OTP)
Enter one-time password: 82719745
Logged in as jameszhang on https://registry.npmjs.org/.
发布包
正常输入命令npm publish
即可:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm publish
npm notice
npm notice 📦 node-tool@1.0.0
npm notice === Tarball Contents ===
npm notice 92B .gitee/ISSUE_TEMPLATE.zh-CN.md
npm notice 190B .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
npm notice 924B README.en.md
npm notice 1.0kB README.md
npm notice 2.1kB bin/index.js
npm notice 692B package.json
npm notice 1.3kB utils/index.js
npm notice === Tarball Details ===
npm notice name: node-tool
npm notice version: 1.0.0
npm notice filename: node-tool-1.0.0.tgz
npm notice package size: 2.9 kB
npm notice unpacked size: 6.3 kB
npm notice shasum: 6e6669f2c38fae550b22b69b1036c6727334369b
npm notice integrity: sha512-gyK5eQe9mR0R+[...]yuCTN/f0d1Xeg==
npm notice total files: 7
npm notice
npm notice Publishing to https://registry.npmjs.org/
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/node-tool - Package name too similar to existing package nodetool; try renaming your package to '@jameszhang/node-tool' and publishing with 'npm publish --access=public' instead
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\James\AppData\Local\npm-cache\_logs\2022-07-26T14_21_57_550Z-debug-0.log
可以发现,已经存在有一个名为node-tool
的包名了,我们需要更换一个包名,这里我们修改为了james-node-tool
,然后,我们再尝试发包一次:
James@LAPTOP MINGW64 /d/Projects/node-tool (master)
$ npm publish
npm notice
npm notice 📦 james-node-tool@1.0.0
npm notice === Tarball Contents ===
npm notice 92B .gitee/ISSUE_TEMPLATE.zh-CN.md
npm notice 190B .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
npm notice 924B README.en.md
npm notice 1.0kB README.md
npm notice 2.1kB bin/index.js
npm notice 698B package.json
npm notice 1.3kB utils/index.js
npm notice === Tarball Details ===
npm notice name: james-node-tool
npm notice version: 1.0.0
npm notice filename: james-node-tool-1.0.0.tgz
npm notice package size: 2.9 kB
npm notice unpacked size: 6.3 kB
npm notice shasum: 308eea0c327ea9cd97dfa34be41dbc70b16dd907
npm notice integrity: sha512-QGoJ873jdX74j[...]Lhf/X/4vKV3EA==
npm notice total files: 7
npm notice
npm notice Publishing to https://registry.npmjs.org/
+ james-node-tool@1.0.0
到此,包已经发布出去,此时,我们就可以通过npm install -g james-node-tool
进行全局安装和使用了。
总结
通过这次实践,对于前端制作node工具库不再那么陌生了,可以造更多的轮子了。 希望整理的这些资料,对各位看官有所帮助,欢迎各位在评论区讨论交流!
参考链接
- npm package.json
- npm package.json scripts
- Inquirer
- commander-中文文档
- node-tool仓库
- download-git-repo
- 示例仓库-node-tool
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。