Skip to content

核心命令

semo

请注意,Semo 提供了一种默认机制,可以运行任意符合 Semo 命令行文件语法规范的文件。

semo command.js

一般我们定义的命令都不会出现 .js 这样的扩展名,所以会按照命令的方式直接执行这个文件。如果需要执行 .ts 的命令文件风格,需要 ts 环境,请参考 FAQ 里的条目。

这种机制的意义何在,可以使用 Semo 来定义和执行脚本文件,也就是模糊了命令和脚本的概念,他们是相通的,先有脚本,如果觉得需要频繁使用,就起个好名字,然后封装成插件。早期的时候还封装了一个 semo-plugin-script 的插件,目的也是做这件事情,现在有了这个默认机制就可以内置对脚本文件的支持了。不过 semo-plugin-script 插件还有脚本样板代码生成器的功能,这个特性 Semo 核心并不打算提供。因为这里更倾向理解成是快速开发命令行工具的一个简化方式。

semo application

alias: app

TIP

这条命令已经转移到 semo-plugin-application 插件

默认这个命令没有任何功能,存在的意思是跟业务项目建立一个约定,建议业务项目添加的命令都写成这个命令的子命令。而业务项目之所以能为这个命令添加子命令是利用了 Semo 的命令扩展机制。

bash
npm install semo-plugin-application
semo generate command application/test --extend=application

这样就可以为项目添加一个 test 命令,而这个命令在执行的时候需要使用 semo application test 的方式来调用。

通过 semo application help 可以看到当前业务项目定义的所有顶级子命令,因为如果项目实现的命令过多,层次也多的话,一般我们很难记住所有命令和参数,所以帮助命令是我们经常要执行的。

semo cleanup

alias: clean

这个命令用于清理一些 Semo 内部产生的文件,常见的有 repl 命令的历史, shell 命令的历史, repl 里临时下载的包,run 命令临时下载的包,以及全局插件目录。

目前提供了有限的扩展,只允许应用目录定义清理目录,不支持插件来添加清理目录,主要是为了安全性考虑。

semo config

我们可以通过核心内置的这个命令来查看和修改配置文件,可以操作当前项目的配置文件,也可以操作全局配置文件。

semo config <op>

Manage rc config

命令:
  semo config delete <configKey>                                Delete configs by key                     [aliases: del]
  semo config get <configKey>                                   Get configs by key
  semo config list                                              List configs                   [默认值] [aliases: ls, l]
  semo config set <configKey> <configValue> [configComment]     Set config by key
  [configType]

Options:
  --global, -g  For reading/writing configs from/to global yml rc file, default is false
  --watch       Watch config change, maybe only work on Mac

注意,这里的 <configKey> 的格式是 a.b.c 的形式,代表多层级配置。另外,这里支持对设置的最后一个层级的配置添加注释。

semo hook

TIP

这条命令已经转移到 semo-plugin-hook 插件

这个命令的输出显示了当前环境下可用的所有的钩子,所有实现这些钩子的逻辑都可以被执行。在输出当中能够看到钩子的名称,描述,以及钩子在哪个模块声明的:

Hook                         :  Package :  Description                                     
  hook_beforeCommand           :  semo    :  Hook triggered before command execution.        
  hook_afterCommand            :  semo    :  Hook triggered after command execution.         
  hook_component               :  semo    :  Hook triggered when needing to fetch component 
  hook_hook                    :  semo    :  Hook triggered in hook command.                 
  hook_repl                    :  semo    :  Hook triggered in repl command.                 
  hook_status                  :  semo    :  Hook triggered in status command.               
  hook_create_project_template :  semo    :  Hook triggered in create command.

这里可以看到有一个特殊的钩子是 hook_hook 实现这个钩子就可以声明钩子,任何插件都可以声明自己的钩子,让其他命令来调用,从而影响自身的行为,一般业务项目是不需要声明自己的钩子的,除非业务项目深度使用了这个机制,来构成自己业务的插件系统。

另外需要注意的是,即使不声明,钩子也是可以被使用的,只要其被实现了,这里声明钩子只是为了透明。具体如何声明和实现钩子将在钩子相关小节说明。

WARNING

这里未来有可能改成不声明的钩子不让使用的逻辑

semo init

alias: i

这个命令用来做初始化,可以实现两种场景,对业务项目的初始化或者对插件的初始化,这两个场景的差别在于目录结构稍有差异。

业务项目中,我们默认将 Semo 的目录结构放到 bin 目录:

├── .semorc.yml
├── bin
│   └── semo
│       ├── commands
│       ├── extends
│       ├── hooks
│       ├── plugins
│       └── scripts
└── package.json

而在插件项目中,我们是把所有代码放到 src 目录:

├── .semorc.yml
├── src
│    ├── commands
│    ├── extends
│    ├── hooks
└── package.json

这个命令存在的意义也仅仅是为了节省工程师若干秒的时间,也就是说如果不用这个命令,手动去创建这些目录和文件夹也是 OK 的。

TIP

关于 .semorc.yml 的结构和用途将在配置管理小节说明

另外,如果我们真的要创建一个插件,通过初始化的方式进行还是太慢了,这里推荐使用插件项目模板进行,具体的命令如下:

semo create semo-plugin-xxx --template=plugin

很明显这里还可以使用其他项目模板,关于 create 命令,参见下放关于 create 命令的介绍。

semo create <name> [repo] [branch]

alias: n

这个命令和 generate 以及 init 都不一样,是用来初始化一个新的项目目录的,这个项目可以是业务项目,也可以是一个插件。这个命令有很多参数,也有一些约定:

$ semo create help

semo create <name> [repo] [branch]

Create a create 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
  --template, -T   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-semo, -i     init new project
  -h, --help     显示帮助信息                                                                                     [布尔]

单个的说明上面已经有了,下面我们用具体的使用场景说明一下

从任意代码仓库初始化

semo create PROJECT_NAME PROJECT_REPO_URL master -f

这里可以看出,我们用 create 命令可以从任意 git 仓库地址下载代码,任何代码仓库都可以是我们的项目模板。其中 master 是分支名,默认就是 master 所以可以省略,-f 的意思是如果目录已经存在,会先删除原来的,再重新创建。

create 命令除了把代码下载下来,还帮着把原来的 .git 目录删除了,并且重新初始化了一个空的 .git 目录,然后把项目的依赖都自动下载下来了。

创建一个空项目,不基于任何项目模板

semo create PROJECT_NAME -yfie

这里可以看到一个 yargs 的特性,可以把短参数连起来用,这里相当于 -y -f -i -e,也就是,-y 帮我们在创建了 package.json时自动回答 yes-f 是强制删除已存在的目录,-i 是自动执行 semo init 初始化项目目录, -e 是告诉命令,即不基于代码仓库,也不基于内置模板,而是要声明一个空项目。

项目的目录结构如下:

├── .semorc.yml
├── bin
│   └── semo
│       ├── commands
│       ├── extends
│       ├── hooks
│       ├── plugins
│       └── scripts
└── package.json

创建一个 Semo 插件目录

如果不基于插件模板,我们可以手动创建一个基本的插件结构:

semo create semo-plugin-[PLUGIN_NAME] -yfie

可以看到,和上面很类似,除了项目名,这里存在一个项目名称的约定,如果项目名称以 semo-plugin- 开头,则认为是在初始化一个 Semo 插件,初始化时会执行 semo init --plugin

项目的目录结构如下:

├── .semorc.yml
├── package.json
└── src
    ├── commands
    ├── extends
    └── hooks

基于内置模板创建项目

如果我们创建项目执行下面的命令:

semo create PROJECT_NAME --template

则会看到下面的输出:

? Please choose a pre-defined repo to continue: (Use arrow keys)
❯ semo_plugin_starter [semo-plugin-starter, plugin]
❯ ...

这里可以选择一个想要选择的内置模板,也就是不用主动输入仓库地址了,这里默认只有一个插件模板,但是可以使用 hook_create_project_template 注入其他模板地址进去:

钩子实现示例,更多关于钩子的用法,请参见钩子相关说明

js
export const hook_create_project_template = {
  demo_repo: {
    repo: 'demo_repo.git',
    branch: 'master',
    alias: ['demo']
  },
}

如果在初始化的时候已经知道要使用的模板和标识,可以直接指定:

semo create PROJECT_NAME --template=demo
semo create PROJECT_NAME --template=demo_repo

TIP

在创建业务项目或者插件时,不推荐从空项目开始,因为还要考虑很多工程化的问题,技术选型的问题,推荐归纳总结自己公司常用的脚手架项目,然后通过统一的方式进行初始化。比如内置的插件模板,初始化后,可以直接编写逻辑,然后代码上传到 Github 再执行 npm version patch && npm publish 即可发布到 npm 仓库了。关于如何开发一个插件并且发布到 npm 仓库,会单独写文档说明。另外,需要注意,这里的脚手架项目可以是任意语言实现的。

剩余的其他几个选项也很好理解,--yarn 声明项目使用 yarn 来初始化和安装依赖,--add--add-dev 用来在初始化时指定新的依赖包。--merge 是说不删除原来的项目,而是进入项目目录,然后应用 --init, --add, --add-dev

semo generate <component>

alias: generate, g

这个命令是一个组件代码生成命令,这里组件的意思是对开发目标进行抽象的后的分层分类概念,比如 Semo 核心就定义了插件,命令和脚本3个概念,所以这三个概念有对应的代码生成子命令,同样的,semo 插件或者集成的项目都可以创建自己的抽象概念,并提供配套的代码生成器,比如业务项目后端会有路由,控制器,模型,数据库迁移文件,单元测试等概念,这些概念由于项目的不同可能是不通用的,但是一个项目内部最好风格保持一致,通过自动生成样板代码可以更好的保持风格一致。

$ semo generate help

semo generate <component>

Generate component sample code

命令:
  semo generate command <name> [description]               Generate a command template
  semo generate plugin <name>                              Generate a plugin structure
  semo generate script <name>                              Generate a script file

选项:
  --version   显示版本号                                                                                          [布尔]
  -h, --help  显示帮助信息                                                                                        [布尔]

扩展 generate 命令添加子命令

和上面扩展 application 命令的方法是一样的:

bash
semo generate command generate/test --extend=semo

具体怎么实现这些代码生成命令,这里是没有做约束的,因为首先 es6 内置的模板字符串机制可以解决大多数问题,然后 Semo 还内置了 lodash,其 _.template 方法也比较灵活,最后只要把组装好的样板代码放到想放的位置即可。

因为这部分都是基于 Semo 的,所以相关的配置建议放到 .semorc.yml 文件,例如自动生成的配置里就有的:

yml
commandDir: src/commands
extendDir: src/extends
hookDir: src/hooks

可以看到,create 命令生成默认配置也仅仅是约定了一些代码自动生成的目录,同时也给出一种定义目录的配置风格,如果想保持配置的一致性,可以用同样的风格定义其他目录。

semo plugin

alias: p

TIP

这条命令已经转移到 semo-plugin-plugin 插件

这个命令用于安装在家目录的全局插件,也可以用于优化当前项目的 semo 执行效率。

$ semo plugin help
semo plugin

Plugin management tool

命令:
  semo p install <plugin>    Install plugin                                                                 [aliases: i]
  semo p list                List all plugins                                                           [aliases: l, ls]
  semo p uninstall <plugin>  Uninstall plugin                                                              [aliases: un]

semo repl [replFile]

alias: r

REPL(read-eval-print-loop):交互式解析器,每一个现代的编程语言大概都有这类交互环境,在里面我们可以写一些简单的代码,做为一个快速了解和学习语言特性的工具。但是当 REPL 可以和框架或者业务项目结合以后,可以发挥出更大的作用。

REPL 的一些扩展

在开发Semo 和这个脚手架时,Node 的 REPL 还不支持 await,这里是模拟实现了这个机制,目的是可以触发执行项目中的一些 promise 或 generator 方法。通过这个能力,再加上我们可以把一些业务代码注入到 REPL 我们就可以在接口控制器,脚本,单元测试之外多了一种执行方式,而这种执行方式还是交互式的。

REPL 注入新的对象

这里需要实现内置的 hook_repl 钩子,并且在业务项目的声明的钩子目录配置: hookDir,下面代码仅供参考。

js
// src/hooks/index.ts
export const hook_repl = () => {
  return {
    add: async (a, b) => {
      return a + b
    },
    multiple: async (a, b) => {
      return a * b
    }
  }
}

然后在 REPL 环境,就可以使用了:

TIP

hook_repl 返回的信息都注入到了 REPL 里的 Semo 对象。

>>> add
[Function: add]
>>> await Semo.add(1, 2)
3
>>> multiple
[Function: multiple]
>>> await Semo.multiple(3, 4)
12

在实际的业务项目中,会把项目中的公共方法,工具函数等等都注入进去,这对开发以及后面的排查问题都是很有帮助的。默认 Semo 把自己的 Utils 工具对象注入进去了,里面有一些是 Semo 自定义的工具函数,更多的是把 Semo 引入的依赖包暴露出来,比如 lodash

TIP

在具体的实践中,我们把数据库,缓存,OSS,Consul, ElasticSearch 等等多种公司的基础设施注入了进来,写成插件,使得我们更容易的直接访问基础设施。

重新载入一遍钩子文件

.reload 或者 Semo.reload() 可以重新执行一遍 hook_repl 钩子,然后把最新的结果注入 Semo。这个的用途是希望在不退出 REPL 环境的情况下能够调用最新的钩子结果,这里只能保证重新加载和执行钩子文件本身,如果钩子内部用了 require 还是会被缓存,这部分就需要用户自己来处理了,比如每次 require 之前先尝试删除 require.cache

临时试用 npm 包

在 REPL 下支持用 Semo.import 临时下载和调试一些包,这个调试包下载不会进入当前项目的 node_modules 目录。(还有一个等价方法是:Semo.require)

>>> let _ = Semo.import('lodash')
>>> _.VERSION

使用内部命令的方式

v1.5.14 新增 .require.import 是等价的,可以快速导入一些常用包用于调试,例如:

>>> .import lodash:_ dayjs:day

冒号后面的是别名,意思是导入后存成什么变量名。

释放对象的属性到 REPL 环境

>>> Semo.extract(Semo)

这个操作的潜在风险就是会覆盖 REPL 环境里内置的对象,但是这个 API 的目的和作用是释放一些明确的对象,比如从 ORM 里释放一个数据库中所有的表模型。

这个操作也支持在配置中进行,比如要将 Semo 对象里的 Utils 注入进去,可以在配置文件中配置:

$plugin:
  semo:
    extract: Semo

这种方式会把 Semo 下的所有属性都注入进去,如果只想注入 Utils,支持这么写。

$plugin:
  semo:
    extract:
      Semo: [Utils]

Semo 对象简介

最早的时候本来打算核心和插件可以自由的注入到 REPL 环境,后来觉得不可控,所以决定核心和插件都只能注入到 Semo 对象。下面说一下 Semo 对象的结构

  • Semo.hooks 为了对各个插件的信息进行隔离,所有插件注入的信息按照插件名称注入到这里,各个插件不会相互干扰
  • Semo.argv 这个是进入命令的 yargs argv参数,有时可以用于看看配置合并是否生效,以及实验 yargs 的参数解析。
  • Semo.repl 当前 REPL 环境的对象实例
  • Semo.Utils 核心工具包,里面除了自定义的若干函数之外,会暴露出一些常用的第三方包,比如 lodash, chalk
  • Semo.reload 重新执行 hook_repl 钩子,使用最新的钩子文件
  • Semo.import 用于临时实验一些 npm 包, 可以用 semo cleanup 清理缓存
  • Semo.extract 用于释放内部对象的键值到当前作用域,可以算作是把所有钩子的注入都放到 Semo 对象的一个补偿

默认注入到全局作用域的方法

如果觉得默认的对象层次比较深,可以通过配置或者参数使得默认注入到 REPL 的全局作用域。方式就是 --extract

semo repl --extract Semo.hooks

配置的方式: 修改 .semorc.yml

$plugin:
  semo:
    extract: Semo.hooks

CommandDefault:
  repl:
    extract: Semo.hooks

支持执行一个repl文件

用途也是执行逻辑后将一些结果注入到REPL,文件也是Node模块,需要符合指定格式。

js
exports.handler = async (argv, context) => {}

// 或者

module.exports = async (argv, context) => {}

需要注意,只有通过 context 才能注入到 REPL,如果你的代码不放到函数当中,也是可以执行的,但是不能获取到之前其他逻辑注入到context中的内容,也不同获得 argv 对象,另外,必须通过 global 对象注入到REPL当中。

这个机制有什么作用呢?主要用途是在我们开发调试时,有一些想在REPL里进行的调试是有许多前置逻辑的,如果都在REPL里一行一行的输入太麻烦,所以通过这种方式就可以把调试逻辑固化下来。

还有一个角度是: semo repl --require 这种方式,如果全局设置会比较固定,不宜太多,而通过命令行设置又需要每次都输入,不够方便,利用执行脚本的方式,我们可以灵活的组织逻辑,将我们想要注入的常用工具库注入,甚至在注入之前还可以做一番设置,部分可以取代之前的 --require 机制和 hook 机制。

semo run <PLUGIN> [COMMAND]

这个命令可以像 yarn create 一样,实现直接执行远程插件包里的命令的效果

例如:

semo run semo-plugin-serve serve

这里是调用了 semo-plugin-serve 插件实现简单的 HTTP 服务,也许我们会觉得这样写起来还是不是很方便,那么我们可以简化一下。

semo run serve

这样看是不是简洁多了,这里能把 semo-plugin- 省略的原因是这里只支持 semo 系列插件,而不是所有的 npm 包,所以可以内部帮着加上,而后面的 serve 命令去掉是因为插件为此实现了一个约定,插件就是一个普通的 node 包,可以对外暴露方法,这里暴露了一个 handler 方法,而这个 handler 方法又去掉了包里的 serve 命令,因为这个命令文件也是一个 Node 模块。如果插件里面包含多个命令,可以用这个机制对外暴露最常用的,其他的还是应该明确传参。另外,需要注意的是一些命令需要传递参数,这里需要把所有的参数和选项都改造成选项。

之前是命令的时候:

semo serve [publicDir]

在用 run 命令调度时:注意,插件命令里的参数需要放到 --

semo run serve -- --public-dir=.

如果你在 npm 的 semo 插件包也是在 scope 下的,在用 run 时需要指定 scope

semo run xxx --SCOPE yyy

run 命令运行的插件肯定是缓存到本地了,只不过不在全局插件目录 .semo/node_modules, 而是在 .semo/run_plugin_cache/node_modules 目录,默认如果存在就会用缓存里的插件,如果想更新需要用参数 --upgrade

semo run serve --UPGRADE|--UP

有些插件可能依赖于另一些插件,如果有这种情况,就需要手动指定依赖插件,实现一起下载,为什么不能基于 npm 的依赖关系呢,可以看一下下面这个例子:

TIP

此特性 v0.8.2 引入

semo run read READ_URL --format=editor --DEP=read-extend-format-editor

editor 这个插件在开发时是依赖于 read 的,但是在运行时,read 指定的参数却是 editor 这个插件实现的,所以只能手动指定依赖了。

你可能已经发现这个命令的所有参数和选项都是大写的,这是为了减少与其他插件的冲突,我们最好约定所有的插件的参数和选项都用小写。

semo script [file]

alias: scr

TIP

这条命令已经转移到 semo-plugin-script 插件

很多时候我们都需要跑一些脚本,这些脚本是在项目服务之外的,需要我们主动触发,可能是做数据迁移,可能是数据导出,可能是数据批量修改,也可能是执行业务逻辑,比如发邮件,发短信,发通知等等。在遇到这样的需求的时候,我们都需要写脚本,但是我们会遇到几个问题:

  • 放哪里
  • 怎么写
  • 脚本参数怎么解析

很多时候这些需求都是一次性的,或者有前提的,不是很适合写成命令,不然命令就太多了,在这种场景下,Semo 通过这条命令给出了一个统一的方案。

放哪里

在配置中有一个 scriptDir,默认是 src/scripts,我们默认把脚本都放到这里,因为这些脚本不会被服务访问到,所以没必要和项目核心逻辑放的太近。

怎么写,怎么解析参数

当然可以手动建脚本,然后用这个命令来触发,但是因脚本还需要起名字,而且还有一定的格式要求,所以,推荐使用 semo generate script 命令来生成。

semo generate script test

自动生成的样板代码及文件名:

js
// src/bin/semo/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!')
}

可以看到,作为一个脚本,不是一上来就写业务逻辑,也不需要声明 shebang 标识,只需要定义两个方法,一个是 builder,一个是 handler。其中 builder 用于声明脚本的参数,格式可以参考 yargs,如果脚本不需要参数,其实也可以不定义,由于是模板自动生成,放到那里即可,以备不时之需。handler 是具体的执行逻辑,传入的参数就是解析好的脚本参数,也包含了项目的 .semorc.yml 里的配置。可以看到 handler 支持 async 所以这里可以执行一些异步操作。

所以,脚本和命令最大的区别其实就是使用的频率,以及业务的定位,我们经常做的分层是定义原子命令,然后在脚本中调度。

semo shell

alias: sh

TIP

这条命令已经转移到 semo-plugin-shell 插件

这个命令是个很简单的命令,目的是不用每次敲命令都输入前面的 semo,例如:

semo shell
> status
> hook
> repl
semo shell --prefix=git
> log
> remote -v
semo: prefix=git
git: log

semo status

alias: st

这个命令的作用很简单,就是看 Semo 当前所处的环境,例如:

$ semo st
  version  :  1.8.17
  location :  ~/.nvm/versions/node/[VERSION]/lib/node_modules/semo
  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]

这里实现了一个 hook, hook_status,实现了这个 hook 的插件,可以在这里展示插件的相关信息,如果是业务项目实现了这个钩子,也可以在这里显示项目信息。

semo completion

这个命令的作用是输出一段 Shell 脚本,放到 .bashrc 或者 .zshrc 里,就能够获得子命令的自动补全效果。

WARNING

由于 Semo 的性能有些差,所以这个自动补全虽然能用,但是体验极差,不建议使用。