Skip to content

Commit 36de735

Browse files
committed
🔧 fix(type generator): friendly error message, and better error handling
1 parent 593c78d commit 36de735

File tree

4 files changed

+97
-32
lines changed

4 files changed

+97
-32
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 1.3.8 - 4 Sep 2025
2+
Bug fix:
3+
- type generator: if failed, do not generate empty JSON
4+
- type generator: friendly error message, and better error handling
5+
16
# 1.3.7 - 3 Sep 2025
27
Improvement:
38
- type generator: clean up temp files after generation

example/gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { fromTypes } from '../src/gen'
55
export const app = new Elysia()
66
.use(
77
openapi({
8-
references: fromTypes('example/gen.ts')
8+
references: fromTypes('example/gen3.ts')
99
})
1010
)
1111
.get(

src/gen/index.ts

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
rmSync,
77
existsSync,
88
cpSync,
9-
exists
9+
exists,
10+
readdirSync
1011
} from 'fs'
1112
import { TypeBox } from '@sinclair/typemap'
1213

@@ -15,18 +16,12 @@ import { join } from 'path'
1516
import { spawnSync } from 'child_process'
1617
import { AdditionalReference, AdditionalReferences } from '../types'
1718
import { Kind, TObject } from '@sinclair/typebox/type'
19+
import { readdir } from 'fs/promises'
1820

1921
const matchRoute = /: Elysia<(.*)>/gs
2022
const matchStatus = /(\d{3}):/g
2123
const wrapStatusInQuote = (value: string) => value.replace(matchStatus, '"$1":')
2224

23-
const exec = (command: string, cwd: string) =>
24-
spawnSync(command, {
25-
shell: true,
26-
cwd,
27-
stdio: 'inherit'
28-
})
29-
3025
interface OpenAPIGeneratorOptions {
3126
/**
3227
* Path to tsconfig.json
@@ -55,7 +50,14 @@ interface OpenAPIGeneratorOptions {
5550
* Under any circumstance, that Elysia failed to find a correct schema,
5651
* Put your own schema in this path
5752
*/
58-
overrideOutputPath?(tempDir: string): string
53+
overrideOutputPath?: string | ((tempDir: string) => string)
54+
55+
/**
56+
* don't remove temporary files
57+
* for debugging purpose
58+
* @default false
59+
*/
60+
debug?: boolean
5961
}
6062

6163
/**
@@ -77,29 +79,48 @@ export const fromTypes =
7779
tsconfigPath = 'tsconfig.json',
7880
instanceName,
7981
projectRoot = process.cwd(),
80-
overrideOutputPath
82+
overrideOutputPath,
83+
debug = false
8184
}: OpenAPIGeneratorOptions = {}
8285
) =>
8386
() => {
84-
if (!targetFilePath.endsWith('.ts') && !targetFilePath.endsWith('.tsx'))
85-
throw new Error('Only .ts files are supported')
86-
8787
const tmpRoot = join(tmpdir(), '.ElysiaAutoOpenAPI')
8888

89-
if (existsSync(tmpRoot))
90-
rmSync(tmpRoot, { recursive: true, force: true })
91-
mkdirSync(tmpRoot, { recursive: true })
89+
try {
90+
if (
91+
!targetFilePath.endsWith('.ts') &&
92+
!targetFilePath.endsWith('.tsx')
93+
)
94+
throw new Error('Only .ts files are supported')
95+
96+
if (targetFilePath.startsWith('./'))
97+
targetFilePath = targetFilePath.slice(2)
98+
99+
const src = targetFilePath.startsWith('/')
100+
? targetFilePath
101+
: join(projectRoot, targetFilePath)
102+
103+
if (!existsSync(src))
104+
throw new Error(
105+
`Couldn't find "${targetFilePath}" from ${projectRoot}`
106+
)
107+
108+
if (existsSync(tmpRoot))
109+
rmSync(tmpRoot, { recursive: true, force: true })
110+
111+
mkdirSync(tmpRoot, { recursive: true })
92112

93-
const extendsRef = existsSync(join(projectRoot, 'tsconfig.json'))
94-
? `"extends": "${join(projectRoot, 'tsconfig.json')}",`
95-
: ''
113+
const tsconfig = tsconfigPath.startsWith('/')
114+
? tsconfigPath
115+
: join(projectRoot, tsconfigPath)
96116

97-
if (!join(projectRoot, targetFilePath))
98-
throw new Error('Target file does not exist')
117+
const extendsRef = existsSync(tsconfig)
118+
? `"extends": "${join(projectRoot, 'tsconfig.json')}",`
119+
: ''
99120

100-
writeFileSync(
101-
join(tmpRoot, tsconfigPath),
102-
`{
121+
writeFileSync(
122+
join(tmpRoot, 'tsconfig.json'),
123+
`{
103124
${extendsRef}
104125
"compilerOptions": {
105126
"lib": ["ESNext"],
@@ -112,19 +133,28 @@ export const fromTypes =
112133
"skipDefaultLibCheck": true,
113134
"outDir": "./dist"
114135
},
115-
"include": ["${join(projectRoot, targetFilePath)}"]
136+
"include": ["${src}"]
116137
}`
117-
)
138+
)
118139

119-
exec(`tsc`, tmpRoot)
140+
spawnSync(`tsc`, {
141+
shell: true,
142+
cwd: tmpRoot,
143+
stdio: debug ? 'inherit' : undefined
144+
})
120145

121-
try {
122146
const fileName = targetFilePath
123147
.replace(/.tsx$/, '.ts')
124148
.replace(/.ts$/, '.d.ts')
125149

126150
let targetFile =
127-
overrideOutputPath?.(tmpRoot) ?? join(tmpRoot, 'dist', fileName)
151+
(overrideOutputPath
152+
? typeof overrideOutputPath === 'string'
153+
? overrideOutputPath.startsWith('/')
154+
? overrideOutputPath
155+
: join(tmpRoot, 'dist', overrideOutputPath)
156+
: overrideOutputPath(tmpRoot)
157+
: undefined) ?? join(tmpRoot, 'dist', fileName)
128158

129159
{
130160
const _targetFile = join(
@@ -133,7 +163,34 @@ export const fromTypes =
133163
fileName.slice(fileName.indexOf('/') + 1)
134164
)
135165

136-
if (existsSync(_targetFile)) targetFile = _targetFile
166+
if (!existsSync(_targetFile)) {
167+
rmSync(join(tmpRoot, 'tsconfig.json'))
168+
169+
console.warn(
170+
'[@elysiajs/openapi/gen] Failed to generate OpenAPI schema'
171+
)
172+
console.warn("Couldn't find generated declaration file")
173+
174+
if (existsSync(join(tmpRoot, 'dist'))) {
175+
const tempFiles = readdirSync(join(tmpRoot, 'dist'), {
176+
recursive: true
177+
})
178+
.filter((x) => x.toString().endsWith('.d.ts'))
179+
.map((x) => `- ${x}`)
180+
.join('\n')
181+
182+
if (tempFiles) {
183+
console.warn(
184+
'You can override with `overrideOutputPath` with one of the following:'
185+
)
186+
console.warn(tempFiles)
187+
}
188+
}
189+
190+
return
191+
}
192+
193+
targetFile = _targetFile
137194
}
138195

139196
const declaration = readFileSync(targetFile, 'utf8')
@@ -219,6 +276,7 @@ export const fromTypes =
219276

220277
return
221278
} finally {
222-
rmSync(tmpRoot, { recursive: true, force: true })
279+
if (!debug && existsSync(tmpRoot))
280+
rmSync(tmpRoot, { recursive: true, force: true })
223281
}
224282
}

src/openapi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export function toOpenAPISchema(
122122

123123
if (references)
124124
for (const reference of references as AdditionalReference[]) {
125+
if(!reference) continue
126+
125127
const refer =
126128
reference[route.path]?.[method] ??
127129
reference[getLoosePath(route.path)]?.[method]

0 commit comments

Comments
 (0)