diff --git a/.github/workflows/build.adtion.yml b/.github/workflows/build.adtion.yml index e7e7094..8f1dc0c 100644 --- a/.github/workflows/build.adtion.yml +++ b/.github/workflows/build.adtion.yml @@ -1,39 +1,56 @@ -# name: Build - -# on: -# push: -# tags: -# - "v*" -# branches: -# - main - -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - name: 安装 Node.js -# uses: actions/setup-node@v3 -# with: -# node-version: 18 -# registry-url: https://registry.npmjs.org/ - -# - name: 安装依赖 -# run: npm i - -# - name: 安装 vsce -# run: npm install -g vsce - -# # - name: 编译 -# # run: vsce package -o dist.vsix - -# # - name: 上传 -# # uses: actions/upload-artifact@v3 -# # with: -# # name: code-coverage-report -# # path: dist.vsix - -# - name: 编译上传 -# run: vsce publish -p ${{ secrets.VSCODE_PUBLISHER_TOKEN }} \ No newline at end of file +name: Build + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: main + + - name: 安装 Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + + - name: 安装依赖 + run: npm i + + - name: 安装 vsce + run: npm install -g vsce + + - name: 获取版本号 + id: get_tag + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + VERSION=${TAG_NAME#v} + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: 更新 npm 版本号 + run: npm version $VERSION --no-git-tag-version + env: + VERSION: ${{ env.VERSION }} + + - name: Configure git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Commit changes + run: | + git add package.json + git commit -m "CI Update version to $VERSION" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} + + - name: 编译上传 + run: vsce publish -p ${{ secrets.VSCODE_PUBLISHER_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci-test.action.yml b/.github/workflows/ci-test.action.yml new file mode 100644 index 0000000..3b94258 --- /dev/null +++ b/.github/workflows/ci-test.action.yml @@ -0,0 +1,50 @@ +name: CI test + +on: + push: + tags: + - test-* + # branches: + # - ci-test + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ci-test + + # - name: 安装 Node.js + # uses: actions/setup-node@v3 + # with: + # node-version: 18 + # registry-url: https://registry.npmjs.org/ + + # - name: 获取版本号 + # id: get_tag + # run: | + # TAG_NAME=${GITHUB_REF#refs/tags/} + # VERSION=${TAG_NAME#v} + # echo "VERSION=$VERSION" >> $GITHUB_ENV + + # - name: 更新 NPM 版本号 + # run: npm version $VERSION --no-git-tag-version + # env: + # VERSION: ${{ env.VERSION }} + + - name: Configure git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Commit changes + run: | + git checkout ci-test + # git add package.json + # git commit -m "Update version to $VERSION" + # git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} \ No newline at end of file diff --git a/src/project/app/nest.ts b/src/project/app/nest.ts index 22dc8b9..1fa051c 100644 --- a/src/project/app/nest.ts +++ b/src/project/app/nest.ts @@ -10,6 +10,15 @@ import { joinPath } from '../../utils'; type FilePath = string type ModuleName = string +/** + * 默认导出 + */ +const DEFAULT_KEY = '__DEFAULT__' +/** + * 匿名变量 + */ +const ANONYMOUS_KEY = '__ANONYMOUS__' + export namespace Nest { export type Controller = Omit & { version?: string[] @@ -224,6 +233,8 @@ export namespace Nest { }) } + const routerModules = [] + const traverseRouters = (routeNode: ts.Node, __prefix: string) => { if (routeNode.kind === ts.SyntaxKind.ArrayLiteralExpression) { const arr = routeNode as ts.ArrayLiteralExpression @@ -238,6 +249,7 @@ export namespace Nest { if (_module) { const _filePath = this.project.pathResolver(_moduleInfo.path, _module.path) module.importModules.push(this.findModule(Object.assign({}, _module, { path: _filePath }), prefix)) + routerModules.push(_module) } } else if (obj.children) { traverseRouters(obj.children, _path ? joinPath(__prefix, _path) : __prefix) @@ -248,6 +260,7 @@ export namespace Nest { if (_module) { const _filePath = this.project.pathResolver(_moduleInfo.path, _module.path) module.importModules.push(this.findModule(Object.assign({}, _module, { path: _filePath }), __prefix)) + routerModules.push(_module) } } }) @@ -276,7 +289,9 @@ export namespace Nest { } }) - _importModules.forEach(v => module.importModules.push(this.findModule(v))) + _importModules // 过滤使用路由模块进行注册的模块 + .filter(v => routerModules.every(_v => (v.name ?? '') !== (_v.name ?? ''))) + .forEach(v => module.importModules.push(this.findModule(v))) _controllers.forEach(v => module.controllers.push(this.findController(v))) if (!this.moduleMap.has(_moduleInfo.path)) this.moduleMap.set(_moduleInfo.path, {}) @@ -294,22 +309,39 @@ export namespace Nest { const ast = this.getAST(_moduleInfo.path) const controller: Controller = { mappings: [], filePath: _moduleInfo.path } + const extendsClassNameMap: Map = new Map() + const importVarMap = this.importVarMap.get(_moduleInfo.path)! + const classMap = new Map() + + const handleClassDeclaration = (node: ts.Node) => { + const classNode = node as ts.ClassDeclaration + // 可能是匿名类 + const name = classNode.name ? AST.getIdentifierName(classNode.name as ts.Identifier) : ANONYMOUS_KEY + const { isDefault, isExport, decorators } = AST.filterDecorator(classNode.modifiers!) + classMap.set(isDefault ? DEFAULT_KEY : name, node) + // 仅目标类需要继续读取信息 + if (!isExport || _moduleInfo.isDefault !== isDefault || (!_moduleInfo.isDefault && _moduleInfo.name !== name)) return + // 有 Controller 装饰器 + if (decorators.hasOwnProperty('Controller')) { + const options = NestDecorator.Controller.getArgs(decorators.Controller) + Object.assign(controller, options) + controller.mappings.push(...NestDecorator.RequsetMapping.getMapping(classNode, ast, _moduleInfo.path)) + } + + // 继承的类,子类不管有没有 Controller 装饰器都需要加上继承的 Mapping + classNode.heritageClauses?.forEach(v => { + // TODO: 类只有单继承,可以链式继承(之后再实现链式继承) + extendsClassNameMap.set(name, AST.getIdentifierName(v.types[0].expression as ts.Identifier)) + }) + } + AST.traverse(ast, (node, next) => { switch (node.kind) { case ts.SyntaxKind.ImportDeclaration: this.saveImportVar(node as ts.ImportDeclaration) break - case ts.SyntaxKind.ClassDeclaration: { - const classNode = node as ts.ClassDeclaration - const name = AST.getIdentifierName(classNode.name as ts.Identifier) - const { isDefault, isExport, decorators } = AST.filterDecorator(classNode.modifiers!) - if (!isExport || _moduleInfo.isDefault !== isDefault || (!_moduleInfo.isDefault && _moduleInfo.name !== name)) return - // 找到指定 Controller - if (decorators.hasOwnProperty('Controller')) { - const options = NestDecorator.Controller.getArgs(decorators.Controller) - Object.assign(controller, options) - controller.mappings = NestDecorator.RequsetMapping.getMapping(classNode, ast) - } + case ts.SyntaxKind.ClassDeclaration: { // 类声明 + handleClassDeclaration(node) } break default: next() @@ -317,6 +349,35 @@ export namespace Nest { } }) + if (extendsClassNameMap.size) { // 处理继承 + const className = _moduleInfo.isDefault ? DEFAULT_KEY : _moduleInfo.name! + // 从入参的目标类上开始找父类 + const parentName = extendsClassNameMap.get(className)! + if (classMap.has(parentName)) { // 在当前文件中 + const parentClassNode = classMap.get(parentName) as ts.ClassDeclaration + const { decorators } = AST.filterDecorator(parentClassNode.modifiers!) + if (decorators.hasOwnProperty('Controller')) { // 父类是 Controller + const options = NestDecorator.Controller.getArgs(decorators.Controller) + if (!controller.path && options.path) { + controller.path = Array.isArray(options.path) ? options.path : [options.path] + } + controller.mappings.push(...NestDecorator.RequsetMapping.getMapping(parentClassNode, ast, _moduleInfo.path)) + controller.mappings = NestDecorator.RequsetMapping.mergeMapping( + controller.mappings, + NestDecorator.RequsetMapping.getMapping( + classMap.get(className)! as ts.ClassDeclaration, + ast, + _moduleInfo.path + ) + ) + } else { // TODO 父类不是 Controller,需要看整个继承链,先不管 + + } + } else if (importVarMap[parentName]) { // TODO 从其他地方导入的 + + } + } + if (!this.controllerMap.has(_moduleInfo.path)) this.controllerMap.set(_moduleInfo.path, {}) this.controllerMap.get(_moduleInfo.path)![_moduleInfo.name || 'DefaultExportModule'] = controller @@ -359,7 +420,7 @@ export namespace Nest { mapping.path.forEach(path => { let _path = joinPath(prefix, path) if (!_path.endsWith('/')) _path += '/' - const { version, ...rest } = mapping + const { version, filePath, ...rest } = mapping const _versions = [...new Set([...version, ...(_controller.version ?? [])])] if (!_versions.length) _versions.push('') _versions.forEach(_version => { @@ -367,7 +428,7 @@ export namespace Nest { ...rest, version: _version, path: _path, - filePath: _controller.filePath + filePath: filePath || _controller.filePath, }) }) }) diff --git a/src/project/index.ts b/src/project/index.ts index c95103a..ad6f151 100644 --- a/src/project/index.ts +++ b/src/project/index.ts @@ -232,7 +232,7 @@ export class Project { createApp( 'main', path.resolve(configPath, '../', config.sourceRoot || 'src'), - config.entryFile ||'main' + config.entryFile || 'main' ) } } diff --git a/src/utils/ast.ts b/src/utils/ast.ts index b21b451..43ff9e4 100644 --- a/src/utils/ast.ts +++ b/src/utils/ast.ts @@ -179,19 +179,20 @@ export namespace NestDecorator { export const ReqMethodSet = new Set(['Get', 'Post', 'Put', 'Delete', 'Patch', 'All', 'Options', 'Head', 'Search']) export type Mapping = { - method: Methods, - path: string[], - version: string[], + method: Methods + path: string[] + version: string[] fn: ts.MethodDeclaration fnName: string + filePath?: string className: string line: { - start: ts.LineAndCharacter, + start: ts.LineAndCharacter end: ts.LineAndCharacter } } - export const getMapping = (classNode: ts.ClassDeclaration, ast: ts.SourceFile) => { + export const getMapping = (classNode: ts.ClassDeclaration, ast: ts.SourceFile, filePath: string) => { const mappings: Mapping[] = [] const className = AST.getIdentifierName(classNode.name!) || '' classNode.members.forEach(_member => { @@ -214,6 +215,7 @@ export namespace NestDecorator { method: key as Methods, path: args.length ? AST.getStringList(args[0]) : [''], version, + filePath, fn: member, className, fnName: AST.getIdentifierName(member.name as ts.Identifier), @@ -226,5 +228,23 @@ export namespace NestDecorator { return mappings } + + /** + * 处理继承的合并 mapping , 子类覆盖父类的同名方法 mapping + */ + export const mergeMapping = (parentMappings: Mapping[], childMappings: Mapping[]) => { + const mappings: Mapping[] = [...parentMappings] + + childMappings.forEach(v => { + const _index = mappings.findIndex(_v => _v.fnName === v.fnName) + if (_index === -1) { + mappings.push(v) + } else { + mappings[_index] = v + } + }) + + return mappings + } } } \ No newline at end of file