vite-plugin-openapi-codegen 1.0.1 → 1.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/README.md +45 -24
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +57 -8
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# vite-plugin-openapi-codegen
|
|
2
2
|
|
|
3
|
-
Generate typed API clients and path builders from
|
|
3
|
+
Generate typed API clients and path builders from a local or online OpenAPI document during Vite dev runs.
|
|
4
4
|
|
|
5
|
-
The plugin reads your OpenAPI spec, runs `openapi-typescript`, and emits three files into your target directory:
|
|
5
|
+
The plugin reads your OpenAPI JSON or YAML spec, runs `openapi-typescript`, and emits three files into your target directory:
|
|
6
6
|
|
|
7
7
|
- `api-types.d.ts` for raw OpenAPI-derived types
|
|
8
8
|
- `api.ts` for path builder functions
|
|
9
9
|
- `client.ts` for typed request helpers
|
|
10
10
|
|
|
11
|
-
It also watches
|
|
11
|
+
It also watches local input specs in dev mode and regenerates the files when the spec changes. In dev mode, generation runs in the background so Vite can finish starting even if the remote spec is temporarily unavailable. Online `http://` and `https://` inputs are fetched once when Vite starts. Build mode skips the plugin entirely.
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -36,7 +36,23 @@ export default defineConfig({
|
|
|
36
36
|
});
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Online YAML inputs use the same option:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { defineConfig } from "vite-plus";
|
|
43
|
+
import { openapiCodegen } from "vite-plugin-openapi-codegen";
|
|
44
|
+
|
|
45
|
+
export default defineConfig({
|
|
46
|
+
plugins: [
|
|
47
|
+
openapiCodegen({
|
|
48
|
+
input: "https://example.com/openapi.yaml",
|
|
49
|
+
output: "src/generated",
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
When you run `vp dev`, the plugin generates:
|
|
40
56
|
|
|
41
57
|
```text
|
|
42
58
|
src/generated/
|
|
@@ -45,39 +61,41 @@ src/generated/
|
|
|
45
61
|
client.ts
|
|
46
62
|
```
|
|
47
63
|
|
|
48
|
-
##
|
|
64
|
+
## Example Projects
|
|
65
|
+
|
|
66
|
+
This repository includes two minimal Vite demos under `example/`:
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
- `example/local` demonstrates generation from the shared local file `example/openapi.json`
|
|
69
|
+
- `example/online` demonstrates generation from `http://localhost:8080/openapi.yaml`
|
|
70
|
+
- Both demos reuse the shared runtime helper in `example/src/http.ts`
|
|
52
71
|
|
|
53
|
-
|
|
72
|
+
Run the local demo in dev mode:
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- `example/src/generated/*` is generated during build/dev and is gitignored
|
|
74
|
+
```bash
|
|
75
|
+
vp dev example/local --config ./example/local/vite.config.ts
|
|
76
|
+
```
|
|
59
77
|
|
|
60
|
-
Run the
|
|
78
|
+
Run the online demo in dev mode:
|
|
61
79
|
|
|
62
80
|
```bash
|
|
63
|
-
vp
|
|
81
|
+
vp dev example/online --config ./example/online/vite.config.ts
|
|
64
82
|
```
|
|
65
83
|
|
|
66
|
-
|
|
84
|
+
Start both demos together with the shared mock OpenAPI server:
|
|
67
85
|
|
|
68
86
|
```bash
|
|
69
|
-
vp
|
|
87
|
+
vp run dev:examples
|
|
70
88
|
```
|
|
71
89
|
|
|
72
|
-
|
|
90
|
+
Build either demo shell without running code generation:
|
|
73
91
|
|
|
74
|
-
```
|
|
75
|
-
example/
|
|
76
|
-
|
|
77
|
-
api.ts
|
|
78
|
-
client.ts
|
|
92
|
+
```bash
|
|
93
|
+
vp build example/local --config ./example/local/vite.config.ts
|
|
94
|
+
vp build example/online --config ./example/online/vite.config.ts
|
|
79
95
|
```
|
|
80
96
|
|
|
97
|
+
When you run the dev server, generated files are written to `example/<demo>/src/generated/` and are ignored by git.
|
|
98
|
+
|
|
81
99
|
## Runtime Contract
|
|
82
100
|
|
|
83
101
|
By default, generated clients import the following symbols from `#/integrations/http`:
|
|
@@ -195,7 +213,9 @@ interface Options {
|
|
|
195
213
|
|
|
196
214
|
### `input`
|
|
197
215
|
|
|
198
|
-
Path to the OpenAPI JSON
|
|
216
|
+
Path or URL to the OpenAPI JSON/YAML document. Local paths are resolved relative to the Vite project root and watched in dev mode. Online `http://` and `https://` URLs are fetched once at startup and are not watched.
|
|
217
|
+
|
|
218
|
+
In dev mode, generation errors are logged and do not stop Vite from starting. In build mode, the plugin is skipped entirely.
|
|
199
219
|
|
|
200
220
|
### `output`
|
|
201
221
|
|
|
@@ -244,5 +264,6 @@ vp pack
|
|
|
244
264
|
To validate the real example project as part of local development:
|
|
245
265
|
|
|
246
266
|
```bash
|
|
247
|
-
vp build example --config ./example/vite.config.ts
|
|
267
|
+
vp build example/local --config ./example/local/vite.config.ts
|
|
268
|
+
vp build example/online --config ./example/online/vite.config.ts
|
|
248
269
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -65,7 +65,7 @@ interface HttpClientConfig {
|
|
|
65
65
|
omitKeys?: string[];
|
|
66
66
|
}
|
|
67
67
|
interface Options {
|
|
68
|
-
/** Path
|
|
68
|
+
/** Path or HTTP(S) URL to an OpenAPI JSON/YAML document */
|
|
69
69
|
input: string;
|
|
70
70
|
/** Output directory for generated files (relative to project root) */
|
|
71
71
|
output: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { parse } from "yaml";
|
|
3
5
|
import * as ts from "typescript";
|
|
4
6
|
//#region src/ast.ts
|
|
5
7
|
const AST_PRINTER = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
@@ -407,9 +409,9 @@ function resolveHttpClientConfig(config) {
|
|
|
407
409
|
};
|
|
408
410
|
}
|
|
409
411
|
const GENERATED_HEADER = ["// This file is auto-generated by vite-plugin-openapi-codegen.", "// Do not edit manually. Changes will be overwritten on next build."];
|
|
410
|
-
async function generateApiTypes(
|
|
412
|
+
async function generateApiTypes(source, outputDir) {
|
|
411
413
|
const { default: openapiTS, astToString } = await import("openapi-typescript");
|
|
412
|
-
const contents = astToString(await openapiTS(
|
|
414
|
+
const contents = astToString(await openapiTS(source));
|
|
413
415
|
writeFileSync(resolve(outputDir, "api-types.d.ts"), `${GENERATED_HEADER.join("\n")}\n\n${contents}`);
|
|
414
416
|
}
|
|
415
417
|
function createApiEntries(normalizedOps) {
|
|
@@ -453,41 +455,88 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
453
455
|
client: renderClientSource(createClientRenderModel(clientModel), GENERATED_HEADER, httpClient)
|
|
454
456
|
};
|
|
455
457
|
}
|
|
458
|
+
async function loadOpenAPIInput(root, input) {
|
|
459
|
+
if (isHttpUrl(input)) {
|
|
460
|
+
const sourceText = await fetchRemoteOpenAPIInput(input);
|
|
461
|
+
return {
|
|
462
|
+
apiTypesSource: sourceText,
|
|
463
|
+
spec: parseOpenAPISpec(sourceText, input)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const inputPath = resolve(root, input);
|
|
467
|
+
const sourceText = readFileSync(inputPath, "utf-8");
|
|
468
|
+
return {
|
|
469
|
+
apiTypesSource: pathToFileURL(inputPath),
|
|
470
|
+
spec: parseOpenAPISpec(sourceText, inputPath)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function isHttpUrl(value) {
|
|
474
|
+
try {
|
|
475
|
+
const url = new URL(value);
|
|
476
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
477
|
+
} catch {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function fetchRemoteOpenAPIInput(input) {
|
|
482
|
+
const response = await fetch(input);
|
|
483
|
+
if (!response.ok) throw new Error(`[openapi-codegen] Failed to fetch OpenAPI document from ${input}: ${response.status} ${response.statusText}`);
|
|
484
|
+
return response.text();
|
|
485
|
+
}
|
|
486
|
+
function parseOpenAPISpec(sourceText, inputLabel) {
|
|
487
|
+
const parsed = parse(sourceText);
|
|
488
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`[openapi-codegen] Expected OpenAPI document object from ${inputLabel}`);
|
|
489
|
+
return parsed;
|
|
490
|
+
}
|
|
456
491
|
async function generate(root, options) {
|
|
457
|
-
const inputPath = resolve(root, options.input);
|
|
458
492
|
const outputDir = resolve(root, options.output);
|
|
459
493
|
mkdirSync(outputDir, { recursive: true });
|
|
460
|
-
const spec =
|
|
494
|
+
const { apiTypesSource, spec } = await loadOpenAPIInput(root, options.input);
|
|
461
495
|
const operations = collectOperations(spec, options.pathPrefix ?? "/api/", options.stripPrefix ?? true);
|
|
462
496
|
const artifacts = renderGeneratedArtifacts(spec, options, operations);
|
|
463
497
|
warnOnParameterLocationMismatch(operations);
|
|
464
|
-
await generateApiTypes(
|
|
498
|
+
await generateApiTypes(apiTypesSource, outputDir);
|
|
465
499
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
466
500
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
467
501
|
}
|
|
468
502
|
function openapiCodegen(options) {
|
|
469
503
|
let root = process.cwd();
|
|
504
|
+
let command = "build";
|
|
470
505
|
return {
|
|
471
506
|
name: "openapi-codegen",
|
|
472
507
|
enforce: "pre",
|
|
473
508
|
configResolved(config) {
|
|
474
509
|
root = config.root;
|
|
510
|
+
command = config.command;
|
|
475
511
|
},
|
|
476
512
|
async buildStart() {
|
|
477
|
-
|
|
513
|
+
if (command === "serve") {
|
|
514
|
+
runDevelopmentGeneration(root, options);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
478
517
|
},
|
|
479
518
|
configureServer(server) {
|
|
519
|
+
if (isHttpUrl(options.input)) return;
|
|
480
520
|
const inputPath = resolve(root, options.input);
|
|
481
521
|
server.watcher.add(inputPath);
|
|
482
522
|
server.watcher.on("change", async (path) => {
|
|
483
523
|
if (path === inputPath) {
|
|
484
524
|
console.log("[openapi-codegen] openapi.json changed, regenerating...");
|
|
485
|
-
|
|
486
|
-
|
|
525
|
+
runDevelopmentGeneration(root, options, { onSuccess: () => {
|
|
526
|
+
console.log("[openapi-codegen] regeneration complete.");
|
|
527
|
+
} });
|
|
487
528
|
}
|
|
488
529
|
});
|
|
489
530
|
}
|
|
490
531
|
};
|
|
491
532
|
}
|
|
533
|
+
async function runDevelopmentGeneration(root, options, handlers) {
|
|
534
|
+
try {
|
|
535
|
+
await generate(root, options);
|
|
536
|
+
handlers?.onSuccess?.();
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error("[openapi-codegen] generation failed during dev mode.", error);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
492
541
|
//#endregion
|
|
493
542
|
export { openapiCodegen, renderGeneratedArtifacts };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-openapi-codegen",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Vite plugin that generates typed API clients and route builders from OpenAPI specs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "vp pack",
|
|
40
40
|
"dev": "vp pack --watch",
|
|
41
|
+
"dev:examples": "node scripts/dev-examples.mjs",
|
|
41
42
|
"test": "vp test",
|
|
42
43
|
"check": "vp check",
|
|
43
44
|
"release": "bumpp --no-verify",
|
|
@@ -45,7 +46,8 @@
|
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"openapi-typescript": "^7.13.0",
|
|
48
|
-
"typescript": ">=5.0.0"
|
|
49
|
+
"typescript": ">=5.0.0",
|
|
50
|
+
"yaml": "^2.9.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
53
|
"@types/node": "^25.5.0",
|