@cognisivelabs/openapi-to-express 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +124 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/generate.d.ts +25 -0
- package/dist/generate.js +144 -0
- package/dist/generate.js.map +1 -0
- package/dist/generators/controller-generator.d.ts +8 -0
- package/dist/generators/controller-generator.js +33 -0
- package/dist/generators/controller-generator.js.map +1 -0
- package/dist/generators/routes-express-generator.d.ts +9 -0
- package/dist/generators/routes-express-generator.js +39 -0
- package/dist/generators/routes-express-generator.js.map +1 -0
- package/dist/generators/types-generator.d.ts +14 -0
- package/dist/generators/types-generator.js +240 -0
- package/dist/generators/types-generator.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/spec-parser.d.ts +51 -0
- package/dist/parser/spec-parser.js +216 -0
- package/dist/parser/spec-parser.js.map +1 -0
- package/dist/utils/paths.d.ts +8 -0
- package/dist/utils/paths.js +17 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/strings.d.ts +30 -0
- package/dist/utils/strings.js +68 -0
- package/dist/utils/strings.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sachin Gupta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# @cognisivelabs/openapi-to-express
|
|
2
|
+
|
|
3
|
+
Generate Express routes, controller interfaces, and TypeScript types from an OpenAPI spec. Design-first API development — define your contract first, the compiler enforces it.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D @cognisivelabs/openapi-to-express
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx openapi-to-express -i openapi.json -o src
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This reads your OpenAPI spec and generates files grouped by **tag**:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
├── types/
|
|
22
|
+
│ ├── users.types.ts # TypeScript interfaces and enums
|
|
23
|
+
│ └── index.ts # Barrel re-exports
|
|
24
|
+
├── controllers/
|
|
25
|
+
│ ├── users.controller.interface.ts # UsersController interface
|
|
26
|
+
│ └── index.ts
|
|
27
|
+
└── routes/
|
|
28
|
+
├── users.routes.ts # Complete Express router
|
|
29
|
+
└── index.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each tag in your spec gets its own types, controller interface, and routes file. Schemas shared across tags go to `common.types.ts` automatically.
|
|
33
|
+
|
|
34
|
+
## Step-by-Step Usage
|
|
35
|
+
|
|
36
|
+
### 1. Write your OpenAPI spec
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# openapi.yaml
|
|
40
|
+
openapi: "3.0.3"
|
|
41
|
+
info:
|
|
42
|
+
title: User API
|
|
43
|
+
version: "1.0.0"
|
|
44
|
+
paths:
|
|
45
|
+
/users/{id}:
|
|
46
|
+
get:
|
|
47
|
+
operationId: getUserById
|
|
48
|
+
tags: [Users]
|
|
49
|
+
parameters:
|
|
50
|
+
- name: id
|
|
51
|
+
in: path
|
|
52
|
+
required: true
|
|
53
|
+
schema:
|
|
54
|
+
type: string
|
|
55
|
+
responses:
|
|
56
|
+
"200":
|
|
57
|
+
description: User found
|
|
58
|
+
content:
|
|
59
|
+
application/json:
|
|
60
|
+
schema:
|
|
61
|
+
$ref: "#/components/schemas/User"
|
|
62
|
+
/users:
|
|
63
|
+
post:
|
|
64
|
+
operationId: createUser
|
|
65
|
+
tags: [Users]
|
|
66
|
+
requestBody:
|
|
67
|
+
required: true
|
|
68
|
+
content:
|
|
69
|
+
application/json:
|
|
70
|
+
schema:
|
|
71
|
+
$ref: "#/components/schemas/CreateUserRequest"
|
|
72
|
+
responses:
|
|
73
|
+
"201":
|
|
74
|
+
description: User created
|
|
75
|
+
content:
|
|
76
|
+
application/json:
|
|
77
|
+
schema:
|
|
78
|
+
$ref: "#/components/schemas/User"
|
|
79
|
+
components:
|
|
80
|
+
schemas:
|
|
81
|
+
User:
|
|
82
|
+
type: object
|
|
83
|
+
required: [id, name, email]
|
|
84
|
+
properties:
|
|
85
|
+
id:
|
|
86
|
+
type: string
|
|
87
|
+
format: uuid
|
|
88
|
+
name:
|
|
89
|
+
type: string
|
|
90
|
+
email:
|
|
91
|
+
type: string
|
|
92
|
+
format: email
|
|
93
|
+
CreateUserRequest:
|
|
94
|
+
type: object
|
|
95
|
+
required: [name, email]
|
|
96
|
+
properties:
|
|
97
|
+
name:
|
|
98
|
+
type: string
|
|
99
|
+
email:
|
|
100
|
+
type: string
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Generate code
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx openapi-to-express -i openapi.yaml -o src
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. See what was generated
|
|
110
|
+
|
|
111
|
+
**Types** (`src/types/users.types.ts`):
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export interface User {
|
|
115
|
+
/** @format uuid */
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
/** @format email */
|
|
119
|
+
email: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface CreateUserRequest {
|
|
123
|
+
name: string;
|
|
124
|
+
email: string;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Controller interface** (`src/controllers/users.controller.interface.ts`):
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import type { Request, Response } from "express";
|
|
132
|
+
|
|
133
|
+
export interface UsersController {
|
|
134
|
+
getUserById(req: Request, res: Response): Promise<void>;
|
|
135
|
+
createUser(req: Request, res: Response): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Routes** (`src/routes/users.routes.ts`) — lean, one line per endpoint:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Router } from "express";
|
|
143
|
+
import type { UsersController } from "../controllers/users.controller.interface";
|
|
144
|
+
|
|
145
|
+
export function createUsersRouter(controller: UsersController, middleware?: any[]): Router {
|
|
146
|
+
const router = Router();
|
|
147
|
+
|
|
148
|
+
if (middleware) {
|
|
149
|
+
middleware.forEach((mw) => router.use(mw));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
router.get("/users/:id", (req, res) => controller.getUserById(req, res));
|
|
153
|
+
router.post("/users", (req, res) => controller.createUser(req, res));
|
|
154
|
+
|
|
155
|
+
return router;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 4. Implement the controller
|
|
160
|
+
|
|
161
|
+
The controller owns the HTTP layer — param extraction, response formatting, error handling:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// src/controllers/users.controller.ts
|
|
165
|
+
import type { Request, Response } from "express";
|
|
166
|
+
import type { UsersController } from "./users.controller.interface";
|
|
167
|
+
import type { User } from "../types/users.types";
|
|
168
|
+
|
|
169
|
+
export class UsersControllerImpl implements UsersController {
|
|
170
|
+
constructor(private readonly userService: UserService) {}
|
|
171
|
+
|
|
172
|
+
async getUserById(req: Request, res: Response): Promise<void> {
|
|
173
|
+
try {
|
|
174
|
+
const id = req.params.id;
|
|
175
|
+
const user = await this.userService.findById(id);
|
|
176
|
+
res.json(user);
|
|
177
|
+
} catch (err: any) {
|
|
178
|
+
res.status(err.status ?? 500).json({ message: err.message });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async createUser(req: Request, res: Response): Promise<void> {
|
|
183
|
+
try {
|
|
184
|
+
const user = await this.userService.create(req.body);
|
|
185
|
+
res.status(201).json(user);
|
|
186
|
+
} catch (err: any) {
|
|
187
|
+
res.status(err.status ?? 500).json({ message: err.message });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async createUser(request: CreateUserRequest): Promise<User> {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 5. Wire it in your server
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// src/server.ts
|
|
200
|
+
import express from "express";
|
|
201
|
+
import { createUsersRouter } from "./routes/users.routes";
|
|
202
|
+
import { UsersControllerImpl } from "./controllers/users.controller";
|
|
203
|
+
import { UserServiceImpl } from "./services/user.service";
|
|
204
|
+
|
|
205
|
+
const app = express();
|
|
206
|
+
app.use(express.json());
|
|
207
|
+
|
|
208
|
+
const userService = new UserServiceImpl();
|
|
209
|
+
const controller = new UsersControllerImpl(userService);
|
|
210
|
+
app.use("/api/v1", createUsersRouter(controller));
|
|
211
|
+
|
|
212
|
+
app.listen(3000);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 6. When the spec changes
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Architect adds a new endpoint to openapi.yaml
|
|
219
|
+
npx openapi-to-express -i openapi.yaml -o src
|
|
220
|
+
|
|
221
|
+
# TypeScript now fails — UsersControllerImpl doesn't implement the new method
|
|
222
|
+
# Developer adds the implementation, tsc passes, contract enforced
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## CLI Options
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
Usage:
|
|
229
|
+
openapi-to-express --input <spec> --output <dir> [options]
|
|
230
|
+
openapi-to-express (reads from .openapi-to-expressrc.json)
|
|
231
|
+
|
|
232
|
+
Required (unless provided in config file):
|
|
233
|
+
-i, --input <value> OpenAPI spec — file path (.json/.yaml/.yml), URL, or string
|
|
234
|
+
-o, --output <value> Output directory (e.g. "src")
|
|
235
|
+
|
|
236
|
+
Optional:
|
|
237
|
+
--types-dir <name> Folder name for types (default: "types")
|
|
238
|
+
--controllers-dir <name> Folder name for controllers (default: "controllers")
|
|
239
|
+
--routes-dir <name> Folder name for routes (default: "routes")
|
|
240
|
+
--dry-run Show what would be generated without writing files
|
|
241
|
+
-w, --watch Watch spec file and regenerate on changes
|
|
242
|
+
-v, --version Output version number
|
|
243
|
+
-h, --help Show help
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
246
|
+
npx openapi-to-express -i openapi.json -o src
|
|
247
|
+
npx openapi-to-express -i openapi.yaml -o src
|
|
248
|
+
npx openapi-to-express -i https://petstore3.swagger.io/api/v3/openapi.json -o src
|
|
249
|
+
npx openapi-to-express -i openapi.json -o src --types-dir models --controllers-dir interfaces
|
|
250
|
+
npx openapi-to-express -i openapi.json -o src --dry-run
|
|
251
|
+
npx openapi-to-express -i openapi.json -o src --watch
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Config File
|
|
255
|
+
|
|
256
|
+
Create `.openapi-to-expressrc.json` in your project root to avoid repeating CLI flags:
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"input": "openapi.yaml",
|
|
261
|
+
"output": "src",
|
|
262
|
+
"types-dir": "types",
|
|
263
|
+
"controllers-dir": "controllers",
|
|
264
|
+
"routes-dir": "routes"
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Then just run:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
npx openapi-to-express
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
CLI flags override config file values.
|
|
275
|
+
|
|
276
|
+
## Programmatic API
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { generate } from "@cognisivelabs/openapi-to-express";
|
|
280
|
+
|
|
281
|
+
await generate({
|
|
282
|
+
input: "openapi.json",
|
|
283
|
+
output: "src",
|
|
284
|
+
dryRun: false,
|
|
285
|
+
dirs: {
|
|
286
|
+
types: "models",
|
|
287
|
+
controllers: "interfaces",
|
|
288
|
+
routes: "api",
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Supported OpenAPI Features
|
|
294
|
+
|
|
295
|
+
### Schemas
|
|
296
|
+
|
|
297
|
+
| Feature | Generated TypeScript |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `type: object` with `properties` | `export interface Name { ... }` |
|
|
300
|
+
| `type: string / number / integer / boolean` | `string`, `number`, `boolean` |
|
|
301
|
+
| `type: array` | `Type[]` |
|
|
302
|
+
| `$ref` | Resolved type name |
|
|
303
|
+
| `enum` (string) | Named `const` object + type alias |
|
|
304
|
+
| `enum` (integer) | Named `const` object with number values |
|
|
305
|
+
| `required` / optional | Required fields, `?` for optional |
|
|
306
|
+
| `nullable: true` | `Type \| null` |
|
|
307
|
+
| `allOf` with `$ref` + properties | `interface Child extends Base { ... }` |
|
|
308
|
+
| `allOf` (pure intersection) | `type AB = A & B` |
|
|
309
|
+
| `oneOf` / `anyOf` | `type Either = A \| B` |
|
|
310
|
+
| `discriminator` | `@discriminator` JSDoc tag |
|
|
311
|
+
| Inline nested objects | `{ field: type; ... }` |
|
|
312
|
+
| `additionalProperties` | `Record<string, Type>` |
|
|
313
|
+
| `format` (date-time, uuid, email, etc.) | `@format` JSDoc annotation |
|
|
314
|
+
| `default` values | `@default` JSDoc annotation |
|
|
315
|
+
| `readOnly` / `writeOnly` | `@readonly` / `@writeOnly` JSDoc |
|
|
316
|
+
| `deprecated` (on schema or property) | `@deprecated` JSDoc |
|
|
317
|
+
| `description` | JSDoc comment |
|
|
318
|
+
|
|
319
|
+
### Operations
|
|
320
|
+
|
|
321
|
+
| Feature | Generated code |
|
|
322
|
+
|---|---|
|
|
323
|
+
| Path parameters (`/users/{id}`) | Route uses Express `:id` format |
|
|
324
|
+
| Query parameters | Parsed from spec for types generation |
|
|
325
|
+
| Header parameters | Parsed from spec for types generation |
|
|
326
|
+
| `$ref` parameters (`#/components/parameters/...`) | Resolved from components |
|
|
327
|
+
| Request body (`application/json`) | Schema used for types generation |
|
|
328
|
+
| Inline request/response schemas | Auto-named `{OperationId}Request` / `{OperationId}Response` |
|
|
329
|
+
| Response `200`, `201`, `202` | Response type generated |
|
|
330
|
+
| Response `204` No Content | Parsed as `void` response |
|
|
331
|
+
| Error responses (`4xx`, `5xx`) | Error types generated in types file |
|
|
332
|
+
| `summary` / `description` | JSDoc on controller methods |
|
|
333
|
+
| `deprecated` operations | `@deprecated` JSDoc |
|
|
334
|
+
| Tags | One file set per tag |
|
|
335
|
+
|
|
336
|
+
### Code Organization
|
|
337
|
+
|
|
338
|
+
| Feature | How it works |
|
|
339
|
+
|---|---|
|
|
340
|
+
| Shared schemas | Types used by 2+ tags go to `common.types.ts` |
|
|
341
|
+
| Barrel files | `index.ts` per folder for clean imports |
|
|
342
|
+
| Overwrite protection | Won't overwrite files you've manually modified |
|
|
343
|
+
| Configurable directories | `--types-dir`, `--controllers-dir`, `--routes-dir` |
|
|
344
|
+
|
|
345
|
+
## What's NOT Generated
|
|
346
|
+
|
|
347
|
+
The generator only produces code that can be derived from the OpenAPI spec. The following are your responsibility:
|
|
348
|
+
|
|
349
|
+
- **Authentication / authorization** — middleware, token validation, user extraction
|
|
350
|
+
- **Service layer** — business logic, database access
|
|
351
|
+
- **Controller implementation** — the class that implements the generated interface, handles param extraction, response formatting, error handling
|
|
352
|
+
- **Validation** — consider [express-openapi-validator](https://www.npmjs.com/package/express-openapi-validator) for request validation from the same spec
|
|
353
|
+
|
|
354
|
+
## Requirements
|
|
355
|
+
|
|
356
|
+
- Node.js 20+
|
|
357
|
+
- Express 4.x or 5.x in your project
|
|
358
|
+
- TypeScript 5.x
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for openapi-to-express
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx openapi-to-express -i openapi.json -o src
|
|
7
|
+
* npx openapi-to-express (reads from .openapi-to-expressrc.json)
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { parseArgs } from "node:util";
|
|
13
|
+
import { generate } from "./generate.js";
|
|
14
|
+
import { loadConfig } from "./config.js";
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
17
|
+
const { values } = parseArgs({
|
|
18
|
+
options: {
|
|
19
|
+
input: { type: "string", short: "i" },
|
|
20
|
+
output: { type: "string", short: "o" },
|
|
21
|
+
"types-dir": { type: "string" },
|
|
22
|
+
"controllers-dir": { type: "string" },
|
|
23
|
+
"routes-dir": { type: "string" },
|
|
24
|
+
"dry-run": { type: "boolean" },
|
|
25
|
+
watch: { type: "boolean", short: "w" },
|
|
26
|
+
version: { type: "boolean", short: "v" },
|
|
27
|
+
help: { type: "boolean", short: "h" },
|
|
28
|
+
},
|
|
29
|
+
strict: true,
|
|
30
|
+
});
|
|
31
|
+
if (values.version) {
|
|
32
|
+
console.log(pkg.version);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
if (values.help) {
|
|
36
|
+
console.log(`
|
|
37
|
+
openapi-to-express v${pkg.version} — Generate server code from OpenAPI specs
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
openapi-to-express --input <spec> --output <dir> [options]
|
|
41
|
+
openapi-to-express (reads from .openapi-to-expressrc.json)
|
|
42
|
+
|
|
43
|
+
Required (unless provided in .openapi-to-expressrc.json):
|
|
44
|
+
-i, --input <value> OpenAPI specification — path (.json/.yaml/.yml), URL, or string content
|
|
45
|
+
-o, --output <value> Output directory (e.g. "src")
|
|
46
|
+
|
|
47
|
+
Optional:
|
|
48
|
+
--types-dir <name> Folder name for types (default: "types")
|
|
49
|
+
--controllers-dir <name> Folder name for controller interfaces (default: "controllers")
|
|
50
|
+
--routes-dir <name> Folder name for routes (default: "routes")
|
|
51
|
+
--dry-run Show what would be generated without writing files
|
|
52
|
+
-w, --watch Watch the spec file and regenerate on changes
|
|
53
|
+
-v, --version Output the version number
|
|
54
|
+
-h, --help Show this help message
|
|
55
|
+
|
|
56
|
+
Config file (.openapi-to-expressrc.json):
|
|
57
|
+
{
|
|
58
|
+
"input": "openapi.json",
|
|
59
|
+
"output": "src",
|
|
60
|
+
"types-dir": "types",
|
|
61
|
+
"controllers-dir": "controllers",
|
|
62
|
+
"routes-dir": "routes"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
CLI flags override config file values.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
npx openapi-to-express -i openapi.json -o src
|
|
69
|
+
npx openapi-to-express -i openapi.json -o src --types-dir models
|
|
70
|
+
npx openapi-to-express
|
|
71
|
+
`);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
// Load config file, merge with CLI flags (CLI wins)
|
|
75
|
+
const config = loadConfig() ?? {};
|
|
76
|
+
const input = values.input ?? config.input;
|
|
77
|
+
const output = values.output ?? config.output;
|
|
78
|
+
if (!input || !output) {
|
|
79
|
+
console.error("Error: --input and --output are required (via CLI flags or .openapi-to-expressrc.json).\n");
|
|
80
|
+
console.error("Usage: openapi-to-express --input openapi.json --output src");
|
|
81
|
+
console.error("Run with --help for more info.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const typesDir = values["types-dir"] ?? config["types-dir"];
|
|
85
|
+
const controllersDir = values["controllers-dir"] ?? config["controllers-dir"];
|
|
86
|
+
const routesDir = values["routes-dir"] ?? config["routes-dir"];
|
|
87
|
+
const genOptions = {
|
|
88
|
+
input,
|
|
89
|
+
output,
|
|
90
|
+
dryRun: values["dry-run"] ?? false,
|
|
91
|
+
dirs: {
|
|
92
|
+
...(typesDir && { types: typesDir }),
|
|
93
|
+
...(controllersDir && { controllers: controllersDir }),
|
|
94
|
+
...(routesDir && { routes: routesDir }),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
await generate(genOptions);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.error(`Error: ${err.message}`);
|
|
102
|
+
if (!values.watch)
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
if (values.watch) {
|
|
106
|
+
const { watch: fsWatch } = await import("node:fs");
|
|
107
|
+
const specPath = resolve(input);
|
|
108
|
+
console.log(`\nWatching ${specPath} for changes...\n`);
|
|
109
|
+
let debounce = null;
|
|
110
|
+
fsWatch(specPath, () => {
|
|
111
|
+
if (debounce)
|
|
112
|
+
clearTimeout(debounce);
|
|
113
|
+
debounce = setTimeout(async () => {
|
|
114
|
+
console.log(`\n--- ${new Date().toLocaleTimeString()} — spec changed, regenerating ---\n`);
|
|
115
|
+
try {
|
|
116
|
+
await generate(genOptions);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(`Error: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
}, 300);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads configuration from .openapi-to-expressrc.json in the current working directory.
|
|
3
|
+
* Returns null if no config file is found.
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigFile {
|
|
6
|
+
input?: string;
|
|
7
|
+
output?: string;
|
|
8
|
+
"types-dir"?: string;
|
|
9
|
+
"controllers-dir"?: string;
|
|
10
|
+
"routes-dir"?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadConfig(cwd?: string): ConfigFile | null;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads configuration from .openapi-to-expressrc.json in the current working directory.
|
|
3
|
+
* Returns null if no config file is found.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
const CONFIG_FILE = ".openapi-to-expressrc.json";
|
|
8
|
+
export function loadConfig(cwd = process.cwd()) {
|
|
9
|
+
const configPath = join(cwd, CONFIG_FILE);
|
|
10
|
+
if (!existsSync(configPath))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
throw new Error(`Failed to parse ${CONFIG_FILE}: ${err.message}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAUjD,MAAM,UAAU,UAAU,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAE1C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,WAAW,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main generation orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Reads an OpenAPI spec, classifies schemas, and writes all generated files
|
|
5
|
+
* into the target directory with configurable folder names.
|
|
6
|
+
*/
|
|
7
|
+
export interface DirectoryConfig {
|
|
8
|
+
/** Folder name for generated types (default: "types") */
|
|
9
|
+
types: string;
|
|
10
|
+
/** Folder name for generated controller interfaces (default: "controllers") */
|
|
11
|
+
controllers: string;
|
|
12
|
+
/** Folder name for generated routes (default: "routes") */
|
|
13
|
+
routes: string;
|
|
14
|
+
}
|
|
15
|
+
export interface GenerateOptions {
|
|
16
|
+
/** OpenAPI specification — can be a file path, URL, or JSON string content */
|
|
17
|
+
input: string;
|
|
18
|
+
/** Output directory (e.g. "src") */
|
|
19
|
+
output: string;
|
|
20
|
+
/** Custom folder names for generated output */
|
|
21
|
+
dirs?: Partial<DirectoryConfig>;
|
|
22
|
+
/** If true, only print what would be generated without writing files */
|
|
23
|
+
dryRun?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function generate(options: GenerateOptions): Promise<void>;
|