litestar-vite-plugin 0.25.0 → 0.26.1

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.
@@ -1,3 +1,9 @@
1
+ interface OpenApiTsCacheOptions {
2
+ generateSdk: boolean;
3
+ generateZod: boolean;
4
+ plugins: string[];
5
+ outputPaths?: string[];
6
+ }
1
7
  /**
2
8
  * Check if openapi-ts should run based on input cache.
3
9
  *
@@ -6,11 +12,7 @@
6
12
  * @param options - Generator options
7
13
  * @returns true if generation should run, false if cached
8
14
  */
9
- export declare function shouldRunOpenApiTs(openapiPath: string, configPath: string | null, options: {
10
- generateSdk: boolean;
11
- generateZod: boolean;
12
- plugins: string[];
13
- }): Promise<boolean>;
15
+ export declare function shouldRunOpenApiTs(openapiPath: string, configPath: string | null, options: OpenApiTsCacheOptions): Promise<boolean>;
14
16
  /**
15
17
  * Update cache after successful openapi-ts run.
16
18
  *
@@ -18,11 +20,7 @@ export declare function shouldRunOpenApiTs(openapiPath: string, configPath: stri
18
20
  * @param configPath - Path to config file (or null)
19
21
  * @param options - Generator options
20
22
  */
21
- export declare function updateOpenApiTsCache(openapiPath: string, configPath: string | null, options: {
22
- generateSdk: boolean;
23
- generateZod: boolean;
24
- plugins: string[];
25
- }): Promise<void>;
23
+ export declare function updateOpenApiTsCache(openapiPath: string, configPath: string | null, options: OpenApiTsCacheOptions): Promise<void>;
26
24
  /**
27
25
  * Compute input hash for page props generation.
28
26
  *
@@ -36,10 +34,11 @@ export declare function computePagePropsHash(pagePropsPath: string): Promise<str
36
34
  * @param pagePropsPath - Path to inertia-pages.json
37
35
  * @returns true if generation should run, false if cached
38
36
  */
39
- export declare function shouldRegeneratePageProps(pagePropsPath: string): Promise<boolean>;
37
+ export declare function shouldRegeneratePageProps(pagePropsPath: string, outputPath?: string): Promise<boolean>;
40
38
  /**
41
39
  * Update cache after successful page props generation.
42
40
  *
43
41
  * @param pagePropsPath - Path to inertia-pages.json
44
42
  */
45
43
  export declare function updatePagePropsCache(pagePropsPath: string): Promise<void>;
44
+ export {};
@@ -45,11 +45,17 @@ function isOptionalMetadataMatch(a, b) {
45
45
  }
46
46
  return isMetadataMatch(a, b);
47
47
  }
48
+ function areExpectedOutputsPresent(outputPaths) {
49
+ return !outputPaths?.some((outputPath) => !fs.existsSync(outputPath));
50
+ }
48
51
  function hashObject(obj) {
49
52
  const sorted = JSON.stringify(obj, Object.keys(obj).toSorted());
50
53
  return crypto.createHash("sha256").update(sorted).digest("hex");
51
54
  }
52
55
  async function shouldRunOpenApiTs(openapiPath, configPath, options) {
56
+ if (!areExpectedOutputsPresent(options.outputPaths)) {
57
+ return true;
58
+ }
53
59
  const cache = await loadCache();
54
60
  const optionsHash = hashObject(options);
55
61
  const cacheKey = "openapi-ts";
@@ -81,7 +87,10 @@ async function updateOpenApiTsCache(openapiPath, configPath, options) {
81
87
  async function computePagePropsHash(pagePropsPath) {
82
88
  return hashFile(pagePropsPath);
83
89
  }
84
- async function shouldRegeneratePageProps(pagePropsPath) {
90
+ async function shouldRegeneratePageProps(pagePropsPath, outputPath) {
91
+ if (outputPath && !fs.existsSync(outputPath)) {
92
+ return true;
93
+ }
85
94
  const cache = await loadCache();
86
95
  const currentMetadata = await readFileMetadata(pagePropsPath);
87
96
  const cacheKey = "page-props";
@@ -53,12 +53,30 @@ export interface TypeGenLogger {
53
53
  warn(message: string): void;
54
54
  error(message: string): void;
55
55
  }
56
+ export interface TypeGenCacheHooks {
57
+ shouldRunOpenApiTs(openapiPath: string, configPath: string | null, options: {
58
+ generateSdk: boolean;
59
+ generateZod: boolean;
60
+ plugins: string[];
61
+ outputPaths?: string[];
62
+ }): Promise<boolean>;
63
+ updateOpenApiTsCache(openapiPath: string, configPath: string | null, options: {
64
+ generateSdk: boolean;
65
+ generateZod: boolean;
66
+ plugins: string[];
67
+ outputPaths?: string[];
68
+ }): Promise<void>;
69
+ shouldRegeneratePageProps(pagePropsPath: string, outputPath?: string): Promise<boolean>;
70
+ updatePagePropsCache(pagePropsPath: string): Promise<void>;
71
+ }
56
72
  /**
57
73
  * Options for running type generation.
58
74
  */
59
75
  export interface RunTypeGenOptions {
60
76
  /** Logger for output (optional - silent if not provided) */
61
77
  logger?: TypeGenLogger;
78
+ /** Optional cache hooks for Vite dev/build contexts */
79
+ cache?: TypeGenCacheHooks;
62
80
  }
63
81
  /**
64
82
  * Resolve the default hey-api client plugin for the current project mode.
@@ -79,6 +97,12 @@ export declare function buildHeyApiPlugins(config: {
79
97
  generateZod: boolean;
80
98
  sdkClientPlugin: string;
81
99
  }): string[];
100
+ /**
101
+ * Resolve the locally installed @hey-api/openapi-ts executable.
102
+ */
103
+ export declare function resolveHeyApiBin(projectRoot: string): {
104
+ binPath: string;
105
+ } | null;
82
106
  /**
83
107
  * Run @hey-api/openapi-ts to generate TypeScript types from OpenAPI spec.
84
108
  *
@@ -1,19 +1,23 @@
1
- import { exec } from "node:child_process";
1
+ import { execFile } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "node:path";
5
5
  import { promisify } from "node:util";
6
- import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
6
+ import { resolveInstallHint, resolvePackageExecutorArgv } from "../install-hint.js";
7
+ import { HEY_API_PINNED_SPEC } from "./constants.js";
7
8
  import { emitPagePropsTypes } from "./emit-page-props-types.js";
8
9
  import { emitSchemasTypes } from "./emit-schemas-types.js";
9
- const execAsync = promisify(exec);
10
+ import { emitStaticPropsTypes } from "./emit-static-props-types.js";
11
+ const execFileAsync = promisify(execFile);
10
12
  const nodeRequire = createRequire(import.meta.url);
11
13
  function resolveDefaultSdkClientPlugin(options) {
12
14
  void options;
13
15
  return "@hey-api/client-fetch";
14
16
  }
15
17
  function findOpenApiTsConfig(projectRoot) {
16
- const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
18
+ const bases = ["openapi-ts.config", "hey-api.config", ".hey-api.config"];
19
+ const extensions = [".ts", ".mjs", ".cjs", ".js"];
20
+ const candidates = bases.flatMap((base) => extensions.map((extension) => path.resolve(projectRoot, `${base}${extension}`)));
17
21
  return candidates.find((p) => fs.existsSync(p)) || null;
18
22
  }
19
23
  function buildHeyApiPlugins(config) {
@@ -26,22 +30,28 @@ function buildHeyApiPlugins(config) {
26
30
  }
27
31
  return plugins;
28
32
  }
33
+ function resolveHeyApiBin(projectRoot) {
34
+ try {
35
+ const packageJsonPath = nodeRequire.resolve("@hey-api/openapi-ts/package.json", { paths: [projectRoot] });
36
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
37
+ const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.["openapi-ts"];
38
+ if (!bin) {
39
+ return null;
40
+ }
41
+ return { binPath: path.resolve(path.dirname(packageJsonPath), bin) };
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
29
46
  async function runHeyApiGeneration(config, configPath, plugins, logger) {
30
47
  const { projectRoot, openapiPath, output, executor, generateZod } = config;
31
48
  const sdkOutput = path.join(output, "api");
32
- if (process.env.VITEST || process.env.VITE_TEST || process.env.NODE_ENV === "test" || process.env.CI) {
33
- try {
34
- nodeRequire.resolve("@hey-api/openapi-ts/package.json", { paths: [projectRoot] });
35
- } catch {
36
- throw new Error("@hey-api/openapi-ts not installed");
37
- }
38
- }
39
49
  let args;
40
50
  if (configPath) {
41
- args = ["@hey-api/openapi-ts", "--file", configPath];
51
+ args = ["--file", configPath];
42
52
  } else {
43
53
  const relativeOpenapiPath = path.relative(projectRoot, path.resolve(projectRoot, openapiPath));
44
- args = ["@hey-api/openapi-ts", "-i", relativeOpenapiPath, "-o", sdkOutput];
54
+ args = ["-i", relativeOpenapiPath, "-o", sdkOutput];
45
55
  if (plugins.length) {
46
56
  args.push("--plugins", ...plugins);
47
57
  }
@@ -53,12 +63,23 @@ async function runHeyApiGeneration(config, configPath, plugins, logger) {
53
63
  logger?.warn(`zod not installed - run: ${resolveInstallHint("zod")}`);
54
64
  }
55
65
  }
56
- const cmd = resolvePackageExecutor(args.join(" "), executor);
57
- await execAsync(cmd, { cwd: projectRoot });
66
+ const local = resolveHeyApiBin(projectRoot);
67
+ if (local) {
68
+ await execFileAsync(process.execPath, [local.binPath, ...args], { cwd: projectRoot });
69
+ return sdkOutput;
70
+ }
71
+ const fallback = resolvePackageExecutorArgv(args, executor, {
72
+ packageSpec: HEY_API_PINNED_SPEC,
73
+ binName: "openapi-ts"
74
+ });
75
+ if (!fallback.length) {
76
+ throw new Error("@hey-api/openapi-ts not installed");
77
+ }
78
+ await execFileAsync(fallback[0], fallback.slice(1), { cwd: projectRoot });
58
79
  return sdkOutput;
59
80
  }
60
81
  async function runTypeGeneration(config, options = {}) {
61
- const { logger } = options;
82
+ const { cache, logger } = options;
62
83
  const startTime = Date.now();
63
84
  const result = {
64
85
  generated: false,
@@ -76,40 +97,60 @@ async function runTypeGeneration(config, options = {}) {
76
97
  const shouldGenerateSdk = configPath || generateSdk;
77
98
  if (fs.existsSync(absoluteOpenapiPath) && shouldGenerateSdk) {
78
99
  const plugins = buildHeyApiPlugins(config);
79
- logger?.info("Generating TypeScript types...");
80
- if (configPath) {
81
- const relConfigPath = path.relative(projectRoot, configPath);
82
- logger?.info(`openapi-ts config: ${relConfigPath}`);
83
- }
84
- try {
85
- const outputPath = await runHeyApiGeneration(config, configPath, plugins, logger);
86
- result.generatedFiles.push(outputPath);
87
- result.generated = true;
88
- } catch (error) {
89
- const message = error instanceof Error ? error.message : String(error);
90
- const isPackageNotInstalled = message.includes("not installed");
91
- const isRuntimeEnoent = !isPackageNotInstalled && (message.includes("ENOENT") || error instanceof Error && "code" in error && error.code === "ENOENT");
92
- if (isPackageNotInstalled) {
93
- const zodHint = config.generateZod ? " zod" : "";
94
- const warning = `@hey-api/openapi-ts not installed - run: ${resolveInstallHint(`@hey-api/openapi-ts${zodHint}`)}`;
95
- result.warnings.push(warning);
96
- logger?.warn(warning);
97
- } else if (isRuntimeEnoent) {
98
- result.errors.push(`File not found during type generation: ${message}`);
99
- logger?.error(`Type generation failed (file not found): ${message}`);
100
- } else {
101
- result.errors.push(message);
102
- logger?.error(`Type generation failed: ${message}`);
100
+ const sdkOutput = path.join(output, "api");
101
+ const cacheOptions = {
102
+ generateSdk: config.generateSdk,
103
+ generateZod: config.generateZod,
104
+ plugins,
105
+ outputPaths: [path.join(projectRoot, sdkOutput, "types.gen.ts")]
106
+ };
107
+ const shouldRun = cache ? await cache.shouldRunOpenApiTs(absoluteOpenapiPath, configPath, cacheOptions) : true;
108
+ if (shouldRun) {
109
+ logger?.info("Generating TypeScript types...");
110
+ if (configPath) {
111
+ const relConfigPath = path.relative(projectRoot, configPath);
112
+ logger?.info(`openapi-ts config: ${relConfigPath}`);
103
113
  }
114
+ try {
115
+ const outputPath = await runHeyApiGeneration(config, configPath, plugins, logger);
116
+ await cache?.updateOpenApiTsCache(absoluteOpenapiPath, configPath, cacheOptions);
117
+ result.generatedFiles.push(outputPath);
118
+ result.generated = true;
119
+ } catch (error) {
120
+ const message = error instanceof Error ? error.message : String(error);
121
+ const isPackageNotInstalled = message.includes("not installed");
122
+ const isRuntimeEnoent = !isPackageNotInstalled && (message.includes("ENOENT") || error instanceof Error && "code" in error && error.code === "ENOENT");
123
+ if (isPackageNotInstalled) {
124
+ const zodHint = config.generateZod ? " zod" : "";
125
+ const errorMessage = `@hey-api/openapi-ts not installed - run: ${resolveInstallHint(`@hey-api/openapi-ts${zodHint}`)}`;
126
+ result.errors.push(errorMessage);
127
+ logger?.error(errorMessage);
128
+ } else if (isRuntimeEnoent) {
129
+ result.errors.push(`File not found during type generation: ${message}`);
130
+ logger?.error(`Type generation failed (file not found): ${message}`);
131
+ } else {
132
+ result.errors.push(message);
133
+ logger?.error(`Type generation failed: ${message}`);
134
+ }
135
+ }
136
+ } else {
137
+ result.skippedFiles.push(sdkOutput);
104
138
  }
105
139
  }
106
140
  if (generatePageProps && fs.existsSync(absolutePagePropsPath)) {
107
141
  try {
108
- const changed = await emitPagePropsTypes(absolutePagePropsPath, output);
109
142
  const pagePropsOutput = path.join(output, "page-props.ts");
110
- if (changed) {
111
- result.generatedFiles.push(pagePropsOutput);
112
- result.generated = true;
143
+ const absolutePagePropsOutput = path.resolve(projectRoot, pagePropsOutput);
144
+ const shouldRun = cache ? await cache.shouldRegeneratePageProps(absolutePagePropsPath, absolutePagePropsOutput) : true;
145
+ if (shouldRun) {
146
+ const changed = await emitPagePropsTypes(absolutePagePropsPath, output, projectRoot);
147
+ await cache?.updatePagePropsCache(absolutePagePropsPath);
148
+ if (changed) {
149
+ result.generatedFiles.push(pagePropsOutput);
150
+ result.generated = true;
151
+ } else {
152
+ result.skippedFiles.push(pagePropsOutput);
153
+ }
113
154
  } else {
114
155
  result.skippedFiles.push(pagePropsOutput);
115
156
  }
@@ -124,7 +165,7 @@ async function runTypeGeneration(config, options = {}) {
124
165
  const absoluteRoutesPath = path.resolve(projectRoot, routesPath);
125
166
  if (fs.existsSync(absoluteRoutesPath)) {
126
167
  try {
127
- const changed = await emitSchemasTypes(absoluteRoutesPath, output, schemasTsPath);
168
+ const changed = await emitSchemasTypes(absoluteRoutesPath, output, schemasTsPath, projectRoot);
128
169
  const schemasOutput = schemasTsPath ?? path.join(output, "schemas.ts");
129
170
  if (changed) {
130
171
  result.generatedFiles.push(schemasOutput);
@@ -139,6 +180,20 @@ async function runTypeGeneration(config, options = {}) {
139
180
  }
140
181
  }
141
182
  }
183
+ try {
184
+ const changed = await emitStaticPropsTypes(output, projectRoot);
185
+ const staticPropsOutput = path.join(output, "static-props.ts");
186
+ if (changed) {
187
+ result.generatedFiles.push(staticPropsOutput);
188
+ result.generated = true;
189
+ } else {
190
+ result.skippedFiles.push(staticPropsOutput);
191
+ }
192
+ } catch (error) {
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ result.errors.push(`Static props generation failed: ${message}`);
195
+ logger?.error(`Static props generation failed: ${message}`);
196
+ }
142
197
  } finally {
143
198
  result.durationMs = Date.now() - startTime;
144
199
  }
@@ -148,6 +203,7 @@ export {
148
203
  buildHeyApiPlugins,
149
204
  findOpenApiTsConfig,
150
205
  resolveDefaultSdkClientPlugin,
206
+ resolveHeyApiBin,
151
207
  runHeyApiGeneration,
152
208
  runTypeGeneration
153
209
  };
@@ -1,4 +1,5 @@
1
1
  import type { Plugin } from "vite";
2
+ import type { BridgeTypesConfig } from "./bridge-schema.js";
2
3
  export interface RequiredTypeGenConfig {
3
4
  enabled: boolean;
4
5
  output: string;
@@ -12,6 +13,7 @@ export interface RequiredTypeGenConfig {
12
13
  generatePageProps: boolean;
13
14
  generateSchemas: boolean;
14
15
  globalRoute: boolean;
16
+ failOnError?: boolean;
15
17
  debounce: number;
16
18
  }
17
19
  export interface TypeGenPluginOptions {
@@ -26,6 +28,30 @@ export interface TypeGenPluginOptions {
26
28
  /** Whether .litestar.json was present (used for buildStart warnings) */
27
29
  hasPythonConfig?: boolean;
28
30
  }
31
+ export interface TypesConfigShape {
32
+ enabled?: boolean;
33
+ output?: string;
34
+ openapiPath?: string;
35
+ routesPath?: string;
36
+ pagePropsPath?: string;
37
+ schemasTsPath?: string;
38
+ generateZod?: boolean;
39
+ generateSdk?: boolean;
40
+ generateRoutes?: boolean;
41
+ generatePageProps?: boolean;
42
+ generateSchemas?: boolean;
43
+ globalRoute?: boolean;
44
+ failOnError?: boolean;
45
+ debounce?: number;
46
+ }
47
+ export interface ResolveTypesConfigOptions {
48
+ requested: boolean | "auto" | TypesConfigShape | undefined;
49
+ pythonConfig?: BridgeTypesConfig;
50
+ defaultOutput: string;
51
+ mergePythonWhenTrue?: boolean;
52
+ mergePythonForObject?: boolean;
53
+ }
54
+ export declare function resolveTypesConfig(options: ResolveTypesConfigOptions): RequiredTypeGenConfig | false;
29
55
  /**
30
56
  * Unified Litestar type generation Vite plugin.
31
57
  *