# 核心命令
# zignis application
alias:
app
默认这个命令没有任何功能,存在的意思是跟业务项目建立一个约定,建议业务项目添加的命令都写成这个命令的子命令。而业务项目之所以能为这个命令添加子命令是利用了 Zignis
的命令扩展机制。
zignis make command application/test --extend=zignis
这样就可以为项目添加一个 test 命令,而这个命令在执行的时候需要使用 zignis application test
的方式来调用。
通过 zignis application help
可以看到当前业务项目定义的所有顶级子命令,因为如果项目实现的命令过多,层次也多的话,一般我们很难记住所有命令和参数,所以帮助命令是我们经常要执行的。
# zignis hook
这个命令的输出显示了当前环境下可用的所有的钩子,所有实现这些钩子的逻辑都可以被执行。在输出当中能够看到钩子的名称,描述,以及钩子在哪个模块声明的:
╔══════════════════════╤═════════════════════╤════════════════════════════════════════════════════╗
║ Hook │ Package │ Description ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_beforeCommand │ zignis │ Hook triggered before command execution. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_afterCommand │ zignis │ Hook triggered after command execution. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_components │ zignis │ Hook triggered when needing to fetch components ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_hook │ zignis │ Hook triggered in hook command. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_repl │ zignis │ Hook triggered in repl command. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_status │ zignis │ Hook triggered in status command. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_new_repo │ zignis │ Hook triggered in new command. ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_zhike_cron │ zignis-plugin-zhike │ Hook triggered in zignis zhike cron command ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_zhike_component │ zignis-plugin-zhike │ Hook triggered when zhike hook_components invoked ║
╟──────────────────────┼─────────────────────┼────────────────────────────────────────────────────╢
║ hook_zhike_repl │ zignis-plugin-zhike │ Hook triggered when zhike hook_repl invoked ║
╚══════════════════════╧═════════════════════╧════════════════════════════════════════════════════╝
这里可以看到有一个特殊的钩子是 hook_hook
实现这个钩子就可以声明钩子,任何插件都可以声明自己的钩子,让其他命令来调用,从而影响自身的行为,一般业务项目是不需要声明自己的钩子的,除非业务项目深度使用了这个机制,来构成自己业务的插件系统。
另外需要注意的是,即使不声明,钩子也是可以被使用的,只要其被实现了,这里声明钩子只是为了透明。具体如何声明和实现钩子将在钩子相关小节说明。
WARNING
这里未来有可能改成不声明的钩子不让使用的逻辑
# zignis init
alias:
i
这个命令用来做初始化,可以实现两种场景,对业务项目的初始化或者对插件的初始化,这两个场景的差别在于目录结构稍有差异。
业务项目中,我们默认将 Zignis
的目录结构放到 bin
目录:
├── .zignisrc.json
├── bin
│ └── zignis
│ ├── commands
│ ├── extends
│ ├── hooks
│ ├── plugins
│ └── scripts
└── package.json
而在插件项目中,我们是把所有代码放到 src
目录:
├── .zignisrc.json
├── src
│ ├── commands
│ ├── extends
│ ├── hooks
└── package.json
这个命令存在的意义也仅仅是为了节省工程师若干秒的时间,也就是说如果不用这个命令,手动去创建这些目录和文件夹也是 OK 的。
TIP
关于 .zignisrc.json
的结构和用途将在配置管理小节说明
另外,如果我们真的要创建一个插件,通过初始化的方式进行还是太慢了,这里推荐使用插件项目模板进行,具体的命令如下:
zignis new zignis-plugin-xxx --select=plugin
很明显这里还可以使用其他项目模板,关于 new
命令,参见下放关于 new
命令的介绍。
# zignis make <component>
alias:
generate
,g
这个命令是一个组件代码生成命令,这里组件的意思是对开发目标进行抽象的后的分层分类概念,比如 Zignis
核心就定义了插件,命令和脚本3个概念,所以这三个概念有对应的代码生成子命令,同样的,zignis
插件或者集成的项目都可以创建自己的抽象概念,并提供配套的代码生成器,比如业务项目后端会有路由,控制器,模型,数据库迁移文件,单元测试等概念,这些概念由于项目的不同可能是不通用的,但是一个项目内部最好风格保持一致,通过自动生成样板代码可以更好的保持风格一致。
$ zignis make help
zignis make <component>
Generate component sample code
命令:
zignis make command <name> [description] Generate a command template
zignis make plugin <name> Generate a plugin structure
zignis make script <name> Generate a script file
选项:
--version 显示版本号 [布尔]
-h, --help 显示帮助信息 [布尔]
# 扩展 make
命令添加子命令
和上面扩展 application
命令的方法是一样的:
zignis make command make/test --extend=zignis
具体怎么实现这些代码生成命令,这里是没有做约束的,因为首先 es6 内置的模板字符串机制可以解决大多数问题,然后 Zignis
还内置了 lodash
,其 _.template
方法也比较灵活,最后只要把组装好的样板代码放到想放的位置即可。
因为这部分都是基于 Zignis
的,所以相关的配置建议放到 .zignisrc.json
文件,例如自动生成的配置里就有的:
{
"commandDir": "src/commands",
"extendDir": "src/extends",
"hookDir": "src/hooks"
}
可以看到,new
命令生成默认配置也仅仅是约定了一些代码自动生成的目录,同时也给出一种定义目录的配置风格,如果想保持配置的一致性,可以用同样的风格定义其他目录。
# zignis new <name> [repo] [branch]
alias:
n
这个命令和 make
以及 init
都不一样,是用来初始化一个新的项目目录的,这个项目可以是业务项目,也可以是一个插件。这个命令有很多参数,也有一些约定:
$ zignis new help
zignis new <name> [repo] [branch]
Create a new project from specific repo
选项:
--version 显示版本号 [布尔]
--yarn use yarn command [默认值: false]
--yes, -y run npm/yarn init with --yes [默认值: true]
--force, -f force download, existed folder will be deleted!
--merge, -m merge config with exist project folder!
--empty, -e force empty project, ignore repo
--select, -s select from default repos
--add, -A add npm package to package.json dependencies [默认值: false]
--add-dev, -D add npm package to package.json devDependencies [默认值: false]
--init, -i init new project
-h, --help 显示帮助信息 [布尔]
单个的说明上面已经有了,下面我们用具体的使用场景说明一下
# 从任意代码仓库初始化
zignis new PROJECT_NAME PROJECT_REPO_URL master -f
这里可以看出,我们用 new 命令可以从任意 git 仓库地址下载代码,任何代码仓库都可以是我们的项目模板。其中 master
是分支名,默认就是 master
所以可以省略,-f
的意思是如果目录已经存在,会先删除原来的,再重新创建。
new 命令除了把代码下载下来,还帮着把原来的 .git
目录删除了,并且重新初始化了一个空的 .git
目录,然后把项目的依赖都自动下载下来了。
# 创建一个空项目,不基于任何项目模板
zignis new PROJECT_NAME -yfie
这里可以看到一个 yargs
的特性,可以把短参数连起来用,这里相当于 -y -f -i -e
,也就是,-y
帮我们在创建了 package.json
时自动回答 yes
,-f
是强制删除已存在的目录,-i
是自动执行 zignis init
初始化项目目录, -e
是告诉命令,即不基于代码仓库,也不基于内置模板,而是要声明一个空项目。
项目的目录结构如下:
├── .zignisrc.json
├── bin
│ └── zignis
│ ├── commands
│ ├── extends
│ ├── hooks
│ ├── plugins
│ └── scripts
└── package.json
# 创建一个 Zignis
插件目录
如果不基于插件模板,我们可以手动创建一个基本的插件结构:
zignis new zignis-plugin-[PLUGIN_NAME] -yfie
可以看到,和上面很类似,除了项目名,这里存在一个项目名称的约定,如果项目名称以 zignis-plugin-
开头,则认为是在初始化一个 Zignis
插件,初始化时会执行 zignis init --plugin
。
项目的目录结构如下:
├── .zignisrc.json
├── package.json
└── src
├── commands
├── extends
└── hooks
# 基于内置模板创建项目
如果我们创建项目执行下面的命令:
zignis new PROJECT_NAME --select
则会看到下面的输出:
? Please choose a pre-defined repo to continue: (Use arrow keys)
❯ zignis_plugin_starter [zignis-plugin-starter, plugin]
❯ ...
这里可以选择一个想要选择的内置模板,也就是不用主动输入仓库地址了,这里默认只有一个插件模板,但是可以使用 hook_new_repo
注入其他模板地址进去:
钩子实现示例,更多关于钩子的用法,请参见钩子相关说明
export const hook_new_repo = {
demo_repo: {
repo: 'demo_repo.git',
branch: 'master',
alias: ['demo']
},
}
如果在初始化的时候已经知道要使用的模板和标识,可以直接指定:
zignis new PROJECT_NAME --select=demo
zignis new PROJECT_NAME --select=demo_repo
TIP
在创建业务项目或者插件时,不推荐从空项目开始,因为还要考虑很多工程化的问题,技术选型的问题,推荐归纳总结自己公司常用的脚手架项目,然后通过统一的方式进行初始化。比如内置的插件模板,初始化后,可以直接编写逻辑,然后代码上传到 Github
再执行 npm version patch && npm publish
即可发布到 npm 仓库了。关于如何开发一个插件并且发布到 npm
仓库,会单独写文档说明。
剩余的其他几个选项也很好理解,--yarn
声明项目使用 yarn
来初始化和安装依赖,--add
和 --add-dev
用来在初始化时指定新的依赖包。--merge
是说不删除原来的项目,而是进入项目目录,然后应用 --init
, --add
, --add-dev
。
# zignis repl
alias:
r
REPL(read-eval-print-loop):交互式解析器,每一个现代的编程语言大概都有这类交互环境,在里面我们可以写一些简单的代码,做为一个快速了解和学习语言特性的工具。但是当 REPL 可以和框架或者业务项目结合以后,可以发挥出更大的作用。
# 对 REPL
的一些扩展
默认 REPL 的退出只能通过 ctrl+c
或者 ctrl+d
或者 .exit
来进行,这里我们加入了几个快捷的命令,quit
, q
, exit
。
在开发Zignis 和这个脚手架时,Node 的 REPL 还不支持 await
,这里是模拟实现了这个机制,目的是可以触发执行项目中的一些 promise 或 generator 方法。通过这个能力,再加上我们可以把一些业务代码注入到 REPL
我们就可以在接口控制器,脚本,单元测试之外多了一种执行方式,而这种执行方式还是交互式的。
# 为 REPL
注入新的对象
这里需要实现内置的 hook_repl
钩子,并且在业务项目的声明的钩子目录配置: hookDir
,下面代码仅供参考。
// src/hooks/index.ts
export const hook_repl = () => {
return {
add: async (a, b) => {
return a + b
},
multiple: async (a, b) => {
return a * b
}
}
}
然后在 REPL 环境,就可以使用了:
>>> add
[Function: add]
>>> await add(1, 2)
3
>>> multiple
[Function: multiple]
>>> await multiple(3, 4)
12
在实际的业务项目中,会把项目中的公共方法,工具函数等等都注入进去,这对开发以及后面的排查问题都是很有帮助的。默认 Zignis
把自己的 Utils
工具对象注入进去了,里面有一些是 Zignis
自定义的工具函数,更多的是把 Zignis
引入的依赖包暴露出来,比如 lodash
。
TIP
在具体的实践中,我们把数据库,缓存,OSS,Consul, ElasticSearch 等等多种公司的基础设施注入了进来,写成插件,使得我们更容易的直接访问基础设施。
# zignis script [file]
alias:
scr
很多时候我们都需要跑一些脚本,这些脚本是在项目服务之外的,需要我们主动触发,可能是做数据迁移,可能是数据导出,可能是数据批量修改,也可能是执行业务逻辑,比如发邮件,发短信,发通知等等。在遇到这样的需求的时候,我们都需要写脚本,但是我们会遇到几个问题:
- 放哪里
- 怎么写
- 脚本参数怎么解析
很多时候这些需求都是一次性的,或者有前提的,不是很适合写成命令,不然命令就太多了,在这种场景下,Zignis
通过这条命令给出了一个统一的方案。
# 放哪里
在配置中有一个 scriptDir
,默认是 src/scripts
,我们默认把脚本都放到这里,因为这些脚本不会被服务访问到,所以没必要和项目核心逻辑放的太近。
# 怎么写,怎么解析参数
当然可以手动建脚本,然后用这个命令来触发,但是因脚本还需要起名字,而且还有一定的格式要求,所以,推荐使用 zignis make script
命令来生成。
zignis make script test
自动生成的样板代码及文件名:
// src/bin/zignis/scripts/20191025130716346_test.ts
export const builder = function (yargs: any) {
// yargs.option('option', {default, describe, alias})
}
export const handler = async function (argv: any) {
console.log('Start to draw your dream code!')
process.exit(0)
}
可以看到,作为一个脚本,不是一上来就写业务逻辑,也不需要声明 shebang
标识,只需要定义两个方法,一个是 builder
,一个是 handler
。其中 builder
用于声明脚本的参数,格式可以参考 yargs
,如果脚本不需要参数,其实也可以不定义,由于是模板自动生成,放到那里即可,以备不时之需。handler
是具体的执行逻辑,传入的参数就是解析好的脚本参数,也包含了项目的 .zignisrc.json
里的配置。可以看到 handler
支持 async
所以这里可以执行一些异步操作。
所以,脚本和命令最大的区别其实就是使用的频率,以及业务的定位,我们经常做的分层是定义原子命令,然后在脚本中调度。
# zignis shell
alias:
sh
这个命令是个很简单的命令,目的是不用每次敲命令都输入前面的 zignis
,例如:
zignis shell
> status
> hook
> repl
这个命令平时的使用频率不是很高,但是也许有一些人会喜欢使用。退出和 repl
命令一样支持:q
, quit
, exit
。这里还有个额外的用法是,你也可以修改前缀,对其他多层级的命令行工具实现类似的效果,比如:
zignis shell --prefix=git
> log
> remote -v
# zignis status
alias:
st
这个命令的作用很简单,就是看 Zignis
当前所处的环境,例如:
$ zignis st
Core Information
version : 1.8.17
location : ~/.nvm/versions/node/[VERSION]/lib/node_modules/zignis
os : macOS 10.15
node : 8.16.2
npm : 6.4.1
yarn : 1.15.2
hostname : [MY_HOST]
home : [MY_HOME]
shell : [MY_SHELL]
如果加上 --plugin
参数,还可以看安装的插件信息,主要是插件目录和版本,插件也可以使用 hook_status
钩子添加更多的状态信息。注意插件扫描是动态的,不同的目录扫描的插件列表是不一样的,具体参看插件相关文档。
[zignis-plugin-x]
version : 0.0.13
location : ~/.nvm/versions/node/[VERSION]/lib/node_modules/zignis-plugin-x
[zignis-plugin-y]
version : 1.8.39
location : ~/.nvm/versions/node/[VERSION]/lib/node_modules/zignis-plugin-y
[zignis-plugin-z]
version : 0.0.2
location : ~/.nvm/versions/node/[VERSION]/lib/node_modules/zignis-plugin-z
# zignis completion
这个命令的作用是输出一段 Shell
脚本,放到 .bashrc
或者 .zshrc
里,就能够获得子命令的自动补全效果。
WARNING
由于 Zignis
的性能有些查,所以这个自动补全虽然能用,但是体验极差,不建议使用。