如何开发可像NestJS那样生成完整文件的TypeScript CLI库?
NestJS 自定义CLI生成类库实现方案
Nest 自带的nest g xxx生成能力基于 Angular DevKit Schematics 实现,你要开发支持CLI调用、自动生成文件的自定义库,本质是开发符合Nest规范的Schematics扩展包,不需要修改Nest核心源码,按以下步骤实现即可:
1. 项目初始化与依赖安装
首先创建独立的npm包项目,安装开发依赖:
npm i -D @angular-devkit/schematics @angular-devkit/schematics-cli @nestjs/schematics typescript @types/node
项目按固定结构组织,Nest CLI会自动识别约定路径下的配置:
your-nest-generator/ ├── src/ │ ├── collection.json # 生成器集合入口配置 │ └── custom-resource/ # 单个生成器目录,可按需求新增多个 │ ├── index.ts # 生成逻辑主代码 │ ├── schema.json # 命令参数规则定义 │ └── files/ # 待生成的文件模板目录 │ └── __name@dasherize@if-flat__/ │ ├── __name@dasherize__.controller.ts.template │ ├── __name@dasherize__.service.ts.template │ └── __name@dasherize__.module.ts.template ├── package.json └── tsconfig.json
2. 核心配置编写
首先配置collection.json,声明你提供的生成命令、别名、对应逻辑入口:
{ "schematics": { "custom-resource": { "aliases": ["cr"], "factory": "./custom-resource/index#main", "schema": "./custom-resource/schema.json", "description": "生成业务自定义资源模板" } } }
然后配置schema.json,定义命令支持的参数、默认值、交互提示:
{ "cli": "nestjs", "type": "object", "properties": { "name": { "type": "string", "description": "生成资源的名称", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "请输入资源名称" }, "flat": { "type": "boolean", "default": false, "description": "是否跳过独立目录直接生成文件" } }, "required": ["name"] }
最后在package.json中添加schematics字段,让Nest CLI能识别到这是个可调用的生成包:
{ "name": "your-nest-generator", "version": "1.0.0", "main": "dist/index.js", "schematics": "./dist/collection.json", "scripts": { "build": "tsc -p tsconfig.json" } }
注意tsconfig编译配置要开启outDir: "dist",编译后所有配置和js文件要输出到dist目录,路径和package.json里的声明对齐。
3. 模板与生成逻辑编写
files目录下的模板文件遵循Schematics命名约定:
- 双下划线包裹的内容是动态变量,
@后面跟的是内置转换规则,比如__name@dasherize__会自动把传入的name转成短横线连接格式 @if-flat这类带判断的规则,会根据传入的参数决定是否生成对应目录- 模板内部用
<%= 变量名 %>语法引用参数,内置的strings工具集提供了常用格式转换:classify转大驼峰(适合类名)、camelize转小驼峰(适合实例名)、dasherize转短横线格式(适合文件名、路由路径)
举个controller模板示例:
// __name@dasherize__.controller.ts.template import { Controller, Get } from '@nestjs/common'; import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service'; @Controller('<%= dasherize(name) %>') export class <%= classify(name) %>Controller { constructor(private readonly <%= camelize(name) %>Service: <%= classify(name) %>Service) {} @Get() list() { return this.<%= camelize(name) %>Service.getList(); } }
然后在index.ts中写生成逻辑的工厂函数,核心是读取模板、替换变量、输出到目标路径:
import { Rule, Tree, apply, url, template, mergeWith, move } from '@angular-devkit/schematics'; import { strings } from '@angular-devkit/core'; import { parseName } from '@nestjs/schematics/dist/utils/parse-name.util'; export function main(options: { name: string; flat: boolean }): Rule { return (tree: Tree) => { // 复用Nest自带的路径解析逻辑,和官方nest g命令的路径行为对齐 const { path, name } = parseName(tree.root.path + '/src', options.name); // 加载模板、替换变量、移动到目标目录 const generatedFiles = apply(url('./files'), [ template({ ...strings, ...options, name, }), move(path), ]); return mergeWith(generatedFiles); }; }
如果需要更复杂的逻辑,比如自动修改app.module.ts引入新生成的模块,可以直接用@nestjs/schematics导出的AST工具函数,不需要自己手写文件解析:
addImportToModule:自动给模块添加imports数组项addDeclarationToModule:自动给模块添加declarations数组项addProviderToModule:自动给模块添加providers数组项
4. 本地调试与使用
- 执行
npm run build编译TS代码到dist目录 - 本地调试时,在生成包根目录执行
npm link把包挂载到全局,然后在你的Nest业务项目里执行npm link your-nest-generator关联依赖 - 之后就可以像调用官方生成器一样使用自定义命令:
nest g your-nest-generator:custom-resource user,配置别名的话直接执行nest g your-nest-generator:cr user即可 - 文件已存在时的覆盖提示、参数缺失时的交互输入这些能力,底层Schematics已经内置,不需要额外开发
常见踩坑点
- 不要自己写CLI入口、命令行解析逻辑,所有命令交互、文件操作的通用逻辑Nest CLI已经封装完成,你只需要关注模板和自定义规则即可
- 编译后要确保collection.json、模板文件都被输出到dist对应目录,否则执行命令时会报找不到模板的错误
- 如果要给生成器传自定义参数,只需要在schema.json的properties里新增对应字段,逻辑里直接从options里取值就能用,不需要修改其他配置
内容的提问来源于stack exchange,提问作者Thanabhas
相关产品推荐
相关产品推荐

