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 CHANGED
@@ -1,14 +1,14 @@
1
1
  # vite-plugin-openapi-codegen
2
2
 
3
- Generate typed API clients and path builders from an OpenAPI document during Vite builds.
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 the input spec in dev mode and regenerates the files when the spec changes.
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
- When you run `vp dev` or `vp build`, the plugin generates:
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
- ## Real Example Project
64
+ ## Example Projects
65
+
66
+ This repository includes two minimal Vite demos under `example/`:
49
67
 
50
- This repository includes a real minimal Vite project under `example/` that demonstrates
51
- automatic generation from `vite.config.ts`.
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
- Key files:
72
+ Run the local demo in dev mode:
54
73
 
55
- - `example/vite.config.ts` wires the plugin into Vite
56
- - `example/openapi.json` is the input spec
57
- - `example/src/http.ts` provides the runtime symbols used by generated clients
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 example build:
78
+ Run the online demo in dev mode:
61
79
 
62
80
  ```bash
63
- vp build example --config ./example/vite.config.ts
81
+ vp dev example/online --config ./example/online/vite.config.ts
64
82
  ```
65
83
 
66
- Run the example dev server:
84
+ Start both demos together with the shared mock OpenAPI server:
67
85
 
68
86
  ```bash
69
- vp example --config ./example/vite.config.ts
87
+ vp run dev:examples
70
88
  ```
71
89
 
72
- After either command, generated files are written to:
90
+ Build either demo shell without running code generation:
73
91
 
74
- ```text
75
- example/src/generated/
76
- api-types.d.ts
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 file, relative to the Vite project root.
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 to openapi.json (relative to project root) */
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(inputPath, outputDir) {
412
+ async function generateApiTypes(source, outputDir) {
411
413
  const { default: openapiTS, astToString } = await import("openapi-typescript");
412
- const contents = astToString(await openapiTS(new URL(`file://${inputPath}`)));
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 = JSON.parse(readFileSync(inputPath, "utf-8"));
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(inputPath, outputDir);
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
- await generate(root, options);
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
- await generate(root, options);
486
- console.log("[openapi-codegen] regeneration complete.");
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.1",
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",