Skip to content

Commit fb4f0c0

Browse files
authored
Merge pull request #9 from namecheap/feat/upgrade
Update with upstream and add Typescript version 6 support
2 parents d99f028 + 7c816d6 commit fb4f0c0

177 files changed

Lines changed: 16669 additions & 9827 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,6 @@ module.exports = {
1818
default: 'array-simple',
1919
},
2020
],
21-
'@typescript-eslint/ban-types': 'off',
22-
'@typescript-eslint/explicit-module-boundary-types': 'off',
23-
'@typescript-eslint/member-delimiter-style': [
24-
'off',
25-
{
26-
multiline: {
27-
delimiter: 'none',
28-
requireLast: true,
29-
},
30-
singleline: {
31-
delimiter: 'semi',
32-
requireLast: false,
33-
},
34-
},
35-
],
3621
'@typescript-eslint/naming-convention': [
3722
'error',
3823
{
@@ -45,9 +30,7 @@ module.exports = {
4530
},
4631
],
4732
'@typescript-eslint/no-explicit-any': 'off',
48-
'@typescript-eslint/no-non-null-assertion': 'off',
4933
'@typescript-eslint/no-unsafe-assignment': 'off',
50-
'@typescript-eslint/no-unsafe-call': 'off',
5134
'@typescript-eslint/no-unsafe-argument': 'off',
5235
'@typescript-eslint/no-unsafe-member-access': 'off',
5336
'@typescript-eslint/no-unsafe-return': 'off',
@@ -57,7 +40,6 @@ module.exports = {
5740
argsIgnorePattern: '^_',
5841
},
5942
],
60-
'@typescript-eslint/no-var-requires': 'off',
6143
'@typescript-eslint/triple-slash-reference': [
6244
'error',
6345
{
@@ -66,13 +48,8 @@ module.exports = {
6648
lib: 'always',
6749
},
6850
],
51+
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
6952
eqeqeq: ['error', 'smart'],
70-
'no-shadow': [
71-
'off',
72-
{
73-
hoist: 'all',
74-
},
75-
],
7653
},
7754
overrides: [
7855
{
@@ -87,12 +64,8 @@ module.exports = {
8764
'@typescript-eslint/require-await': 'off',
8865
'@typescript-eslint/no-unused-vars': 'off',
8966
'@typescript-eslint/no-floating-promises': 'off',
90-
'@typescript-eslint/restrict-template-expressions': 'warn',
91-
// for expectations
92-
'@typescript-eslint/no-unused-expressions': 'off',
9367
// Crashes also fail the test
9468
'no-unsafe-optional-chaining': 'off',
95-
'@typescript-eslint/no-non-null-assertion': 'off',
9669
},
9770
},
9871
],

.github/workflows/closeStaleIssuesAndPRs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
repo-token: ${{ secrets.GITHUB_TOKEN }}
1313
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
1414
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
15-
days-before-stale: 30
16-
days-before-close: 5
15+
days-before-stale: 150
16+
days-before-close: 30
1717
exempt-issue-labels: 'help wanted,Pending feedback'
1818
exempt-pr-labels: 'help wanted,breaking change'

.github/workflows/runTestsOnPush.yml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,48 @@
1-
on:
2-
push:
3-
branches:
4-
- master
5-
tags-ignore:
6-
- '**'
7-
pull_request:
8-
branches:
9-
- '**'
10-
1+
on: [push, pull_request]
112
name: Build and Test
123
jobs:
134
build:
145
name: Build
15-
runs-on: ubuntu-latest
6+
runs-on: ${{ matrix.os }}
7+
strategy:
8+
matrix:
9+
node-version: [22]
10+
os: [ubuntu-latest]
11+
typescript-version: ["^6.0.0"]
1612

1713
steps:
1814
- uses: actions/checkout@master
1915

2016
- name: Setup Node
2117
uses: actions/setup-node@v4
2218
with:
23-
node-version: 17
19+
node-version: ${{ matrix.node-version }}
2420
cache: yarn
25-
cache-dependency-path: '**/yarn.lock'
2621

2722
- name: Install Yarn
2823
run: npm install -g yarn
2924

25+
- name: Set resolutions to correct typescript version
26+
uses: jossef/action-set-json-field@v2.1
27+
with:
28+
file: package.json
29+
field: resolutions
30+
value: '{ "typescript": "${{ matrix.typescript-version }}"}'
31+
parse_json: true
32+
33+
# for yarn3
34+
# - name: Set Typescript version
35+
# run: yarn set resolution typescript ${{ matrix.typescript-version }}
36+
3037
- name: Install
3138
run: yarn install --ignore-scripts
3239

40+
- name: Output ts version
41+
run: yarn tsc --version
42+
43+
- name: Output packages version
44+
run: yarn list
45+
3346
- name: Build
3447
run: yarn run build
3548

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ distForNoAdditional
33
node_modules
44
routes.ts
55
customRoutes.ts
6+
**/custom-route-generator/routes/*
67
.idea/
78
*.swp
89
yarn-error.log
910
tsconfig.tsbuildinfo
10-
.history/
11-
.vscode/
11+
.typedoc

.vscode/launch.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"type": "node",
6+
"request": "launch",
7+
"name": "Mocha TS Tests",
8+
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
9+
"cwd": "${workspaceRoot}/tests",
10+
"env": {
11+
"NODE_ENV": "tsoa_test"
12+
},
13+
"args": ["**/*.spec.ts"],
14+
"runtimeArgs": ["--inspect", "--inspect-brk"],
15+
"preLaunchTask": "prepareFiles",
16+
"console": "integratedTerminal",
17+
"internalConsoleOptions": "neverOpen"
18+
},
19+
{
20+
"type": "node",
21+
"request": "launch",
22+
"name": "Mocha TS Tests: Current File",
23+
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
24+
"cwd": "${workspaceRoot}/tests",
25+
"env": {
26+
"NODE_ENV": "tsoa_test"
27+
},
28+
"args": ["${file}", "--timeout", "0"],
29+
"runtimeArgs": ["--inspect", "--inspect-brk"],
30+
"preLaunchTask": "prepareFiles",
31+
"console": "integratedTerminal",
32+
"internalConsoleOptions": "neverOpen"
33+
},
34+
{
35+
"name": "Generate",
36+
"type": "node",
37+
"request": "launch",
38+
"args": ["${workspaceRoot}/packages/tsoa/src/cli.ts", "swagger"],
39+
"cwd": "${workspaceRoot}/tests",
40+
"preLaunchTask": "build",
41+
"runtimeArgs": ["--require", "ts-node/register", "--require", "tsconfig-paths/register"],
42+
"env": {
43+
"NODE_ENV": "development"
44+
}
45+
},
46+
{
47+
"name": "Pretest",
48+
"type": "node",
49+
"request": "launch",
50+
"args": ["${workspaceRoot}/tests/prepare.ts"],
51+
"cwd": "${workspaceRoot}/tests",
52+
"runtimeArgs": ["--require", "ts-node/register", "--require", "tsconfig-paths/register"],
53+
"env": {
54+
"NODE_ENV": "development"
55+
}
56+
}
57+
]
58+
}

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"eslint.autoFixOnSave": "explicit"
4+
},
5+
"typescript.tsdk": "node_modules/typescript/lib"
6+
}

.vscode/tasks.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"version": "2.0.0",
3+
"command": "yarn",
4+
"type": "shell",
5+
"tasks": [
6+
{
7+
"label": "build",
8+
"group": "build",
9+
"args": ["run", "build"]
10+
},
11+
{
12+
"label": "test",
13+
"group": "test",
14+
"args": ["test"]
15+
},
16+
{
17+
"label": "prepareFiles",
18+
"group": "none",
19+
"args": ["run", "pretest"],
20+
"options": { "cwd": "${workspaceFolder}/tests" }
21+
},
22+
{
23+
"label": "watch",
24+
"group": "build",
25+
"args": ["run", "watch"]
26+
}
27+
]
28+
}

AGENTS.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Namecheap TSOA Fork
2+
3+
## Overview
4+
5+
This is a Namecheap fork of [tsoa](https://github.com/lukeautry/tsoa) — a framework that generates OpenAPI specs and Express/Koa/Hapi route boilerplate from TypeScript controller classes and decorators. Packages are published under `@namecheap/` scope instead of the upstream `tsoa` scope.
6+
7+
## Monorepo Structure
8+
9+
```
10+
packages/
11+
runtime/ @namecheap/tsoa-runtime — decorators, interfaces, runtime validation helpers
12+
cli/ @namecheap/tsoa-cli — metadata extraction, spec + route generation
13+
tsoa/ @namecheap/tsoa — re-exports both; the package end-users install
14+
tests/
15+
fixtures/ — controllers, services, models used by tests
16+
unit/ — unit tests (spec generation, metadata, templating)
17+
integration/ — full server integration tests (express, koa, hapi, inversify)
18+
```
19+
20+
`tests` is a Yarn workspace sibling. Its `tsconfig.json` maps `@namecheap/tsoa-cli/*` and `@namecheap/tsoa-runtime/*` to the package source directories so tests run against unbuilt TypeScript.
21+
22+
## Architecture
23+
24+
### Two-Phase Code Generation
25+
26+
**Phase 1 — Metadata extraction** (`packages/cli/src/metadataGeneration/`):
27+
`MetadataGenerator` creates a TypeScript `Program`, walks source files for classes decorated with `@Route`, and delegates to:
28+
- `ControllerGenerator` → per-class metadata
29+
- `MethodGenerator` → per-method metadata (params, responses, security, extensions)
30+
- `ParameterGenerator` → per-parameter metadata
31+
- `TypeResolver` → resolves TypeScript types to `Tsoa.Type` (handles generics, imports, aliases, enums, intersections)
32+
33+
The output is `Tsoa.Metadata` — a plain serializable object describing all controllers and a reference type map.
34+
35+
**Phase 2a — Spec generation** (`packages/cli/src/swagger/`):
36+
`SpecGenerator2` / `SpecGenerator3` consume `Tsoa.Metadata` and emit a Swagger 2.0 or OpenAPI 3.0 JSON/YAML document.
37+
38+
**Phase 2b — Route generation** (`packages/cli/src/routeGeneration/`):
39+
`RouteGenerator` consumes `Tsoa.Metadata` and renders one of three Handlebars templates (`express.hbs`, `koa.hbs`, `hapi.hbs`) to produce a routes file with runtime validation.
40+
41+
Both phases are triggered by `generateSpec` / `generateRoutes` / `generateSpecAndRoutes` in `packages/cli/src/module/`.
42+
43+
### Runtime Package
44+
45+
`packages/runtime/` contains only what ships to end-users at runtime:
46+
- TypeScript decorators (`@Route`, `@Get`, `@Security`, `@Body`, etc.)
47+
- Validation logic used in generated routes
48+
- Type definitions (`Tsoa.*`, `Swagger.*`, `Config`)
49+
50+
It has no dependency on the CLI or TypeScript compiler API.
51+
52+
---
53+
54+
## Namecheap-Specific Source Code Changes
55+
56+
### 1. Pluggable Security Hook
57+
58+
**Files:** `packages/cli/src/metadataGeneration/metadataGenerator.ts`, `controllerGenerator.ts`, `methodGenerator.ts`, `securityGenerator.ts`
59+
60+
Added `MetadataGeneratorOptions` with an optional `securityGenerator` callback:
61+
62+
```ts
63+
type SecurityGenerator = (node: ClassDeclaration | MethodDeclaration, typeChecker: TypeChecker, inheritedSecurities?: Tsoa.Security[]) => Tsoa.Security[]
64+
```
65+
66+
When provided, both `ControllerGenerator.getSecurity()` and `MethodGenerator.getSecurity()` short-circuit to call it instead of reading `@Security` / `@NoSecurity` decorators. Lets callers implement custom security extraction logic entirely from outside tsoa.
67+
68+
---
69+
70+
### 2. Custom Decorator Extension System (NodeDecoratorProcessor)
71+
72+
**Files:** `packages/cli/src/metadataGeneration/methodGenerator.ts`, `packages/cli/src/metadataGeneration/types/nodeDecoratorProcessor.ts`
73+
74+
Added `customDecoratorProcessors: Record<string, NodeDecoratorProcessor>` to `MetadataGeneratorOptions`. After building method metadata, `MethodGenerator` loops over registered processors, finds matching decorators on the AST node, and calls:
75+
76+
```ts
77+
interface DecoratorProcessorContext { methodObject: Tsoa.Method; decoratorArguments: any[] }
78+
type NodeDecoratorProcessor = (context: DecoratorProcessorContext) => void
79+
```
80+
81+
The processor mutates `methodObject` in-place (e.g. pushing to `extensions` for OpenAPI `x-*` fields). Errors include the AST node in `GenerateMetadataError` for better diagnostics. Decorator arguments are resolved via `getInitializerValue` in `initializer-value.ts`.
82+
83+
---
84+
85+
### 3. Decorator Argument Resolution in `initializer-value.ts`
86+
87+
**File:** `packages/cli/src/metadataGeneration/initializer-value.ts`
88+
89+
Extended `getInitializerValue` to handle more expression forms when reading decorator arguments at compile time:
90+
91+
- **Named import aliases** (`import { cfg } from './x'; @Dec(cfg)`) — follows the import alias chain via `getAliasedSymbol`
92+
- **`as const` / type assertions** — handles `AsExpression` (`x as const`), unwrapping it to evaluate the inner expression
93+
- **Arithmetic expressions** — handles `BinaryExpression` for `*` and `/` operators (e.g. `15 * 60` evaluates to `900`)
94+
- **`ImportSpecifier`** — refactored into a shared `getImportSpecifierValue` helper to avoid duplication
95+
96+
---
97+
98+
### 4. TypeScript v6 Compatibility
99+
100+
**Files:** `packages/cli/src/metadataGeneration/typeResolver.ts`, `packages/cli/src/swagger/specGenerator2.ts`, `packages/cli/src/metadataGeneration/metadataGenerator.ts`
101+
102+
#### `typeResolver.ts`
103+
104+
- **`getTypeOfSymbol` instead of `getTypeOfSymbolAtLocation`** — TS v6 removed `getTypeOfSymbolAtLocation`; replaced with `getTypeOfSymbol` when resolving object property types.
105+
- **Optional property `undefined` stripping** — TS v6's `getTypeOfSymbol` includes `undefined` in the returned union for optional properties. The code now strips it before calling `typeToTypeNode`, so optional enum properties don't fall through the union path.
106+
- **Synthetic union member matching** — When a union `TypeNode` has `pos === -1` (synthetic, produced by `typeToTypeNode`), member order may differ from the semantic union's `.types`. Members are now matched by `TypeFlags` (Undefined / Null / other) instead of position.
107+
- **`SymbolFlags.TypeParameter` fix** — `symbol.getFlags()` returns `SymbolFlags`, not `TypeFlags`; corrected the flag constant used for generic `keyof T` detection.
108+
- **`isMappedTypeNode` guard** — When resolving indexed access types, skip following `symbol.valueDeclaration.type` if the declaration is a mapped type node; TS v6 can produce mapped type declarations where the `.type` doesn't resolve correctly.
109+
- **Built-in declaration filter** — The filter that drops declarations inside `node_modules/typescript` (to exclude lib types) now only applies when it leaves at least one declaration. TS v6 ships built-ins like `Error` with all declarations inside its own lib files, so the old filter would drop everything.
110+
- **`this.referencer` fallback for synthetic type aliases** — When resolving a type alias on a synthetic `TypeNode` (`pos === -1`), the referencer is now `this.referencer` instead of `undefined`, preserving context through the synthetic node boundary.
111+
112+
#### `specGenerator2.ts`
113+
114+
- **`T | undefined` collapsing** — When a union reduces to a single non-undefined type (i.e. `T | undefined`), the underlying type is returned directly instead of generating a Swagger union. Optionality is expressed via the `required` array, not the type.
115+
116+
#### `metadataGenerator.ts`
117+
118+
- **Raw `compilerOptions` normalisation** — Added `resolveCompilerOptions()` which runs options through `convertCompilerOptionsFromJson` before every `createProgram` call. Callers (e.g. host apps) often pass string values (`target: 'ES2020'`) from JSON config; TypeScript's `createProgram` requires numeric enum values.

0 commit comments

Comments
 (0)