litestar-vite-plugin 0.15.0-rc.2 → 0.15.0-rc.3
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 +4 -4
- package/dist/js/helpers/routes.d.ts +1 -0
- package/dist/js/index.d.ts +10 -2
- package/dist/js/index.js +82 -22
- package/dist/js/inertia-types.d.ts +37 -0
- package/dist/js/inertia-types.js +0 -0
- package/dist/js/install-hint.d.ts +6 -1
- package/dist/js/install-hint.js +35 -2
- package/dist/js/shared/bridge-schema.d.ts +7 -2
- package/dist/js/shared/bridge-schema.js +13 -4
- package/dist/js/shared/emit-page-props-types.d.ts +3 -1
- package/dist/js/shared/emit-page-props-types.js +70 -48
- package/dist/js/shared/typegen-cache.d.ts +45 -0
- package/dist/js/shared/typegen-cache.js +83 -0
- package/dist/js/shared/typegen-core.d.ts +90 -0
- package/dist/js/shared/typegen-core.js +122 -0
- package/dist/js/shared/typegen-plugin.js +71 -57
- package/dist/js/shared/write-if-changed.d.ts +16 -0
- package/dist/js/shared/write-if-changed.js +19 -0
- package/dist/js/typegen-cli.js +66 -0
- package/package.json +33 -4
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if openapi-ts should run based on input cache.
|
|
3
|
+
*
|
|
4
|
+
* @param openapiPath - Path to openapi.json
|
|
5
|
+
* @param configPath - Path to config file (or null)
|
|
6
|
+
* @param options - Generator options
|
|
7
|
+
* @returns true if generation should run, false if cached
|
|
8
|
+
*/
|
|
9
|
+
export declare function shouldRunOpenApiTs(openapiPath: string, configPath: string | null, options: {
|
|
10
|
+
generateSdk: boolean;
|
|
11
|
+
generateZod: boolean;
|
|
12
|
+
plugins: string[];
|
|
13
|
+
}): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Update cache after successful openapi-ts run.
|
|
16
|
+
*
|
|
17
|
+
* @param openapiPath - Path to openapi.json
|
|
18
|
+
* @param configPath - Path to config file (or null)
|
|
19
|
+
* @param options - Generator options
|
|
20
|
+
*/
|
|
21
|
+
export declare function updateOpenApiTsCache(openapiPath: string, configPath: string | null, options: {
|
|
22
|
+
generateSdk: boolean;
|
|
23
|
+
generateZod: boolean;
|
|
24
|
+
plugins: string[];
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Compute input hash for page props generation.
|
|
28
|
+
*
|
|
29
|
+
* @param pagePropsPath - Path to inertia-pages.json
|
|
30
|
+
* @returns Hash of the input file
|
|
31
|
+
*/
|
|
32
|
+
export declare function computePagePropsHash(pagePropsPath: string): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Check if page props types should be regenerated.
|
|
35
|
+
*
|
|
36
|
+
* @param pagePropsPath - Path to inertia-pages.json
|
|
37
|
+
* @returns true if generation should run, false if cached
|
|
38
|
+
*/
|
|
39
|
+
export declare function shouldRegeneratePageProps(pagePropsPath: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Update cache after successful page props generation.
|
|
42
|
+
*
|
|
43
|
+
* @param pagePropsPath - Path to inertia-pages.json
|
|
44
|
+
*/
|
|
45
|
+
export declare function updatePagePropsCache(pagePropsPath: string): Promise<void>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const CACHE_DIR = "node_modules/.cache/litestar-vite";
|
|
5
|
+
const CACHE_FILE = "typegen-cache.json";
|
|
6
|
+
async function loadCache() {
|
|
7
|
+
const cacheFilePath = path.join(CACHE_DIR, CACHE_FILE);
|
|
8
|
+
try {
|
|
9
|
+
const content = await fs.promises.readFile(cacheFilePath, "utf-8");
|
|
10
|
+
return JSON.parse(content);
|
|
11
|
+
} catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function saveCache(cache) {
|
|
16
|
+
const cacheFilePath = path.join(CACHE_DIR, CACHE_FILE);
|
|
17
|
+
await fs.promises.mkdir(CACHE_DIR, { recursive: true });
|
|
18
|
+
await fs.promises.writeFile(cacheFilePath, JSON.stringify(cache, null, 2), "utf-8");
|
|
19
|
+
}
|
|
20
|
+
async function hashFile(filePath) {
|
|
21
|
+
try {
|
|
22
|
+
const content = await fs.promises.readFile(filePath);
|
|
23
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
24
|
+
} catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function hashObject(obj) {
|
|
29
|
+
const sorted = JSON.stringify(obj, Object.keys(obj).sort());
|
|
30
|
+
return crypto.createHash("sha256").update(sorted).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
async function shouldRunOpenApiTs(openapiPath, configPath, options) {
|
|
33
|
+
const cache = await loadCache();
|
|
34
|
+
const inputHash = await hashFile(openapiPath);
|
|
35
|
+
const configHash = configPath ? await hashFile(configPath) : "";
|
|
36
|
+
const optionsHash = hashObject(options);
|
|
37
|
+
const cacheKey = "openapi-ts";
|
|
38
|
+
const entry = cache[cacheKey];
|
|
39
|
+
if (entry && entry.inputHash === inputHash && entry.configHash === configHash && entry.optionsHash === optionsHash) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
async function updateOpenApiTsCache(openapiPath, configPath, options) {
|
|
45
|
+
const cache = await loadCache();
|
|
46
|
+
cache["openapi-ts"] = {
|
|
47
|
+
inputHash: await hashFile(openapiPath),
|
|
48
|
+
configHash: configPath ? await hashFile(configPath) : "",
|
|
49
|
+
optionsHash: hashObject(options),
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
};
|
|
52
|
+
await saveCache(cache);
|
|
53
|
+
}
|
|
54
|
+
async function computePagePropsHash(pagePropsPath) {
|
|
55
|
+
return hashFile(pagePropsPath);
|
|
56
|
+
}
|
|
57
|
+
async function shouldRegeneratePageProps(pagePropsPath) {
|
|
58
|
+
const cache = await loadCache();
|
|
59
|
+
const currentHash = await hashFile(pagePropsPath);
|
|
60
|
+
const cacheKey = "page-props";
|
|
61
|
+
const entry = cache[cacheKey];
|
|
62
|
+
if (entry && entry.inputHash === currentHash) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
async function updatePagePropsCache(pagePropsPath) {
|
|
68
|
+
const cache = await loadCache();
|
|
69
|
+
cache["page-props"] = {
|
|
70
|
+
inputHash: await hashFile(pagePropsPath),
|
|
71
|
+
configHash: "",
|
|
72
|
+
optionsHash: "",
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
};
|
|
75
|
+
await saveCache(cache);
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
computePagePropsHash,
|
|
79
|
+
shouldRegeneratePageProps,
|
|
80
|
+
shouldRunOpenApiTs,
|
|
81
|
+
updateOpenApiTsCache,
|
|
82
|
+
updatePagePropsCache
|
|
83
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for type generation.
|
|
3
|
+
*/
|
|
4
|
+
export interface TypeGenCoreConfig {
|
|
5
|
+
/** Project root directory */
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
/** Path to openapi.json (relative or absolute) */
|
|
8
|
+
openapiPath: string;
|
|
9
|
+
/** Output directory for generated files */
|
|
10
|
+
output: string;
|
|
11
|
+
/** Path to inertia-pages.json (relative or absolute) */
|
|
12
|
+
pagePropsPath: string;
|
|
13
|
+
/** Whether to generate SDK client */
|
|
14
|
+
generateSdk: boolean;
|
|
15
|
+
/** Whether to generate Zod schemas */
|
|
16
|
+
generateZod: boolean;
|
|
17
|
+
/** Whether to generate page props types */
|
|
18
|
+
generatePageProps: boolean;
|
|
19
|
+
/** SDK client plugin (e.g., "@hey-api/client-fetch") */
|
|
20
|
+
sdkClientPlugin: string;
|
|
21
|
+
/** JS runtime executor (e.g., "bun", "pnpm") */
|
|
22
|
+
executor?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result of type generation.
|
|
26
|
+
*/
|
|
27
|
+
export interface TypeGenResult {
|
|
28
|
+
/** Whether any files were generated */
|
|
29
|
+
generated: boolean;
|
|
30
|
+
/** Files that were generated/updated */
|
|
31
|
+
generatedFiles: string[];
|
|
32
|
+
/** Files that were skipped (unchanged) */
|
|
33
|
+
skippedFiles: string[];
|
|
34
|
+
/** Total duration in milliseconds */
|
|
35
|
+
durationMs: number;
|
|
36
|
+
/** Any warnings to report */
|
|
37
|
+
warnings: string[];
|
|
38
|
+
/** Any errors encountered */
|
|
39
|
+
errors: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Logger interface for type generation.
|
|
43
|
+
* Allows callers to inject their own logging behavior.
|
|
44
|
+
*/
|
|
45
|
+
export interface TypeGenLogger {
|
|
46
|
+
info(message: string): void;
|
|
47
|
+
warn(message: string): void;
|
|
48
|
+
error(message: string): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Options for running type generation.
|
|
52
|
+
*/
|
|
53
|
+
export interface RunTypeGenOptions {
|
|
54
|
+
/** Logger for output (optional - silent if not provided) */
|
|
55
|
+
logger?: TypeGenLogger;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Find user's openapi-ts config file.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findOpenApiTsConfig(projectRoot: string): string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Build the list of plugins for @hey-api/openapi-ts.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildHeyApiPlugins(config: {
|
|
65
|
+
generateSdk: boolean;
|
|
66
|
+
generateZod: boolean;
|
|
67
|
+
sdkClientPlugin: string;
|
|
68
|
+
}): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Run @hey-api/openapi-ts to generate TypeScript types from OpenAPI spec.
|
|
71
|
+
*
|
|
72
|
+
* @param config - Type generation configuration
|
|
73
|
+
* @param configPath - Path to user's openapi-ts config (or null for default)
|
|
74
|
+
* @param plugins - List of plugins to use
|
|
75
|
+
* @param logger - Optional logger for output
|
|
76
|
+
* @returns Path to output directory
|
|
77
|
+
*/
|
|
78
|
+
export declare function runHeyApiGeneration(config: TypeGenCoreConfig, configPath: string | null, plugins: string[], logger?: TypeGenLogger): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Run the complete type generation pipeline.
|
|
81
|
+
*
|
|
82
|
+
* This is the main entry point for type generation, handling:
|
|
83
|
+
* - OpenAPI type generation via @hey-api/openapi-ts
|
|
84
|
+
* - Page props type generation
|
|
85
|
+
*
|
|
86
|
+
* @param config - Type generation configuration
|
|
87
|
+
* @param options - Runtime options (logger, etc.)
|
|
88
|
+
* @returns Result with generated files and timing info
|
|
89
|
+
*/
|
|
90
|
+
export declare function runTypeGeneration(config: TypeGenCoreConfig, options?: RunTypeGenOptions): Promise<TypeGenResult>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
|
|
7
|
+
import { emitPagePropsTypes } from "./emit-page-props-types.js";
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
10
|
+
function findOpenApiTsConfig(projectRoot) {
|
|
11
|
+
const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
|
|
12
|
+
return candidates.find((p) => fs.existsSync(p)) || null;
|
|
13
|
+
}
|
|
14
|
+
function buildHeyApiPlugins(config) {
|
|
15
|
+
const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
|
|
16
|
+
if (config.generateSdk) {
|
|
17
|
+
plugins.push("@hey-api/sdk", config.sdkClientPlugin);
|
|
18
|
+
}
|
|
19
|
+
if (config.generateZod) {
|
|
20
|
+
plugins.push("zod");
|
|
21
|
+
}
|
|
22
|
+
return plugins;
|
|
23
|
+
}
|
|
24
|
+
async function runHeyApiGeneration(config, configPath, plugins, logger) {
|
|
25
|
+
const { projectRoot, openapiPath, output, executor, generateZod } = config;
|
|
26
|
+
const sdkOutput = path.join(output, "api");
|
|
27
|
+
if (process.env.VITEST || process.env.VITE_TEST || process.env.NODE_ENV === "test" || process.env.CI) {
|
|
28
|
+
try {
|
|
29
|
+
nodeRequire.resolve("@hey-api/openapi-ts/package.json", { paths: [projectRoot] });
|
|
30
|
+
} catch {
|
|
31
|
+
throw new Error("@hey-api/openapi-ts not installed");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let args;
|
|
35
|
+
if (configPath) {
|
|
36
|
+
args = ["@hey-api/openapi-ts", "--file", configPath];
|
|
37
|
+
} else {
|
|
38
|
+
const relativeOpenapiPath = path.relative(projectRoot, path.resolve(projectRoot, openapiPath));
|
|
39
|
+
args = ["@hey-api/openapi-ts", "-i", relativeOpenapiPath, "-o", sdkOutput];
|
|
40
|
+
if (plugins.length) {
|
|
41
|
+
args.push("--plugins", ...plugins);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (generateZod) {
|
|
45
|
+
try {
|
|
46
|
+
nodeRequire.resolve("zod", { paths: [projectRoot] });
|
|
47
|
+
} catch {
|
|
48
|
+
logger?.warn(`zod not installed - run: ${resolveInstallHint()} zod`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const cmd = resolvePackageExecutor(args.join(" "), executor);
|
|
52
|
+
await execAsync(cmd, { cwd: projectRoot });
|
|
53
|
+
return sdkOutput;
|
|
54
|
+
}
|
|
55
|
+
async function runTypeGeneration(config, options = {}) {
|
|
56
|
+
const { logger } = options;
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
const result = {
|
|
59
|
+
generated: false,
|
|
60
|
+
generatedFiles: [],
|
|
61
|
+
skippedFiles: [],
|
|
62
|
+
durationMs: 0,
|
|
63
|
+
warnings: [],
|
|
64
|
+
errors: []
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
const { projectRoot, openapiPath, pagePropsPath, output, generateSdk, generatePageProps } = config;
|
|
68
|
+
const absoluteOpenapiPath = path.resolve(projectRoot, openapiPath);
|
|
69
|
+
const absolutePagePropsPath = path.resolve(projectRoot, pagePropsPath);
|
|
70
|
+
const configPath = findOpenApiTsConfig(projectRoot);
|
|
71
|
+
const shouldGenerateSdk = configPath || generateSdk;
|
|
72
|
+
if (fs.existsSync(absoluteOpenapiPath) && shouldGenerateSdk) {
|
|
73
|
+
const plugins = buildHeyApiPlugins(config);
|
|
74
|
+
logger?.info("Generating TypeScript types...");
|
|
75
|
+
if (configPath) {
|
|
76
|
+
const relConfigPath = path.relative(projectRoot, configPath);
|
|
77
|
+
logger?.info(`openapi-ts config: ${relConfigPath}`);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const outputPath = await runHeyApiGeneration(config, configPath, plugins, logger);
|
|
81
|
+
result.generatedFiles.push(outputPath);
|
|
82
|
+
result.generated = true;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
if (message.includes("not found") || message.includes("ENOENT") || message.includes("not installed")) {
|
|
86
|
+
const zodHint = config.generateZod ? " zod" : "";
|
|
87
|
+
const warning = `@hey-api/openapi-ts not installed - run: ${resolveInstallHint()} -D @hey-api/openapi-ts${zodHint}`;
|
|
88
|
+
result.warnings.push(warning);
|
|
89
|
+
logger?.warn(warning);
|
|
90
|
+
} else {
|
|
91
|
+
result.errors.push(message);
|
|
92
|
+
logger?.error(`Type generation failed: ${message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (generatePageProps && fs.existsSync(absolutePagePropsPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const changed = await emitPagePropsTypes(absolutePagePropsPath, output);
|
|
99
|
+
const pagePropsOutput = path.join(output, "page-props.ts");
|
|
100
|
+
if (changed) {
|
|
101
|
+
result.generatedFiles.push(pagePropsOutput);
|
|
102
|
+
result.generated = true;
|
|
103
|
+
} else {
|
|
104
|
+
result.skippedFiles.push(pagePropsOutput);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
108
|
+
result.errors.push(`Page props generation failed: ${message}`);
|
|
109
|
+
logger?.error(`Page props generation failed: ${message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
result.durationMs = Date.now() - startTime;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
buildHeyApiPlugins,
|
|
119
|
+
findOpenApiTsConfig,
|
|
120
|
+
runHeyApiGeneration,
|
|
121
|
+
runTypeGeneration
|
|
122
|
+
};
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
1
|
import fs from "node:fs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
2
|
import path from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
3
|
import colors from "picocolors";
|
|
7
|
-
import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
|
|
8
4
|
import { debounce } from "./debounce.js";
|
|
9
5
|
import { emitPagePropsTypes } from "./emit-page-props-types.js";
|
|
10
6
|
import { formatPath } from "./format-path.js";
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
import { shouldRunOpenApiTs, updateOpenApiTsCache } from "./typegen-cache.js";
|
|
8
|
+
import { buildHeyApiPlugins, findOpenApiTsConfig, runHeyApiGeneration } from "./typegen-core.js";
|
|
13
9
|
async function getFileMtime(filePath) {
|
|
14
10
|
const stat = await fs.promises.stat(filePath);
|
|
15
11
|
return stat.mtimeMs.toString();
|
|
@@ -21,8 +17,14 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
|
|
|
21
17
|
let server = null;
|
|
22
18
|
let isGenerating = false;
|
|
23
19
|
let resolvedConfig = null;
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
function createViteLogger() {
|
|
21
|
+
return {
|
|
22
|
+
info: (message) => resolvedConfig?.logger.info(`${colors.cyan("\u2022")} ${message}`),
|
|
23
|
+
warn: (message) => resolvedConfig?.logger.warn(`${colors.yellow("!")} ${message}`),
|
|
24
|
+
error: (message) => resolvedConfig?.logger.error(`${colors.red("\u2717")} ${message}`)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function runTypeGenerationWithCache() {
|
|
26
28
|
if (isGenerating) {
|
|
27
29
|
return false;
|
|
28
30
|
}
|
|
@@ -30,49 +32,70 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
|
|
|
30
32
|
const startTime = Date.now();
|
|
31
33
|
try {
|
|
32
34
|
const projectRoot = resolvedConfig?.root ?? process.cwd();
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
+
const absoluteOpenapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
|
|
36
|
+
const absolutePagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
|
|
35
37
|
let generated = false;
|
|
36
|
-
const
|
|
37
|
-
const configPath =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (typesConfig.generateZod) {
|
|
57
|
-
plugins.push("zod");
|
|
58
|
-
}
|
|
59
|
-
if (plugins.length) {
|
|
60
|
-
args.push("--plugins", ...plugins);
|
|
38
|
+
const logger = createViteLogger();
|
|
39
|
+
const configPath = findOpenApiTsConfig(projectRoot);
|
|
40
|
+
const shouldGenerateSdk = configPath || typesConfig.generateSdk;
|
|
41
|
+
if (fs.existsSync(absoluteOpenapiPath) && shouldGenerateSdk) {
|
|
42
|
+
const plugins = buildHeyApiPlugins({
|
|
43
|
+
generateSdk: typesConfig.generateSdk,
|
|
44
|
+
generateZod: typesConfig.generateZod,
|
|
45
|
+
sdkClientPlugin
|
|
46
|
+
});
|
|
47
|
+
const cacheOptions = {
|
|
48
|
+
generateSdk: typesConfig.generateSdk,
|
|
49
|
+
generateZod: typesConfig.generateZod,
|
|
50
|
+
plugins
|
|
51
|
+
};
|
|
52
|
+
const shouldRun = await shouldRunOpenApiTs(absoluteOpenapiPath, configPath, cacheOptions);
|
|
53
|
+
if (shouldRun) {
|
|
54
|
+
logger.info("Generating TypeScript types...");
|
|
55
|
+
if (configPath && resolvedConfig) {
|
|
56
|
+
const relConfigPath = formatPath(configPath, resolvedConfig.root);
|
|
57
|
+
logger.info(`openapi-ts config: ${colors.yellow(relConfigPath)}`);
|
|
61
58
|
}
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const coreConfig = {
|
|
60
|
+
projectRoot,
|
|
61
|
+
openapiPath: typesConfig.openapiPath,
|
|
62
|
+
output: typesConfig.output,
|
|
63
|
+
pagePropsPath: typesConfig.pagePropsPath,
|
|
64
|
+
generateSdk: typesConfig.generateSdk,
|
|
65
|
+
generateZod: typesConfig.generateZod,
|
|
66
|
+
generatePageProps: false,
|
|
67
|
+
// Handle separately below
|
|
68
|
+
sdkClientPlugin,
|
|
69
|
+
executor
|
|
70
|
+
};
|
|
64
71
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
await runHeyApiGeneration(coreConfig, configPath, plugins, logger);
|
|
73
|
+
await updateOpenApiTsCache(absoluteOpenapiPath, configPath, cacheOptions);
|
|
74
|
+
generated = true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
78
|
+
logger.warn("@hey-api/openapi-ts not installed");
|
|
79
|
+
} else {
|
|
80
|
+
logger.error(`Type generation failed: ${message}`);
|
|
81
|
+
}
|
|
68
82
|
}
|
|
83
|
+
} else {
|
|
84
|
+
resolvedConfig?.logger.info(`${colors.cyan("\u2022")} TypeScript types ${colors.dim("(unchanged)")}`);
|
|
69
85
|
}
|
|
70
|
-
await execAsync(resolvePackageExecutor(args.join(" "), executor), { cwd: projectRoot });
|
|
71
|
-
generated = true;
|
|
72
86
|
}
|
|
73
|
-
if (typesConfig.generatePageProps && fs.existsSync(
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
if (typesConfig.generatePageProps && fs.existsSync(absolutePagePropsPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const changed = await emitPagePropsTypes(absolutePagePropsPath, typesConfig.output);
|
|
90
|
+
if (changed) {
|
|
91
|
+
generated = true;
|
|
92
|
+
} else {
|
|
93
|
+
resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Page props types ${colors.dim("(unchanged)")}`);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
+
logger.error(`Page props generation failed: ${message}`);
|
|
98
|
+
}
|
|
76
99
|
}
|
|
77
100
|
if (generated && resolvedConfig) {
|
|
78
101
|
const duration = Date.now() - startTime;
|
|
@@ -91,22 +114,13 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
|
|
|
91
114
|
return true;
|
|
92
115
|
} catch (error) {
|
|
93
116
|
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
-
|
|
95
|
-
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
96
|
-
const zodHint = typesConfig.generateZod ? " zod" : "";
|
|
97
|
-
resolvedConfig.logger.warn(
|
|
98
|
-
`${colors.cyan("litestar-vite")} ${colors.yellow("@hey-api/openapi-ts not installed")} - run: ${resolveInstallHint()} -D @hey-api/openapi-ts${zodHint}`
|
|
99
|
-
);
|
|
100
|
-
} else {
|
|
101
|
-
resolvedConfig.logger.error(`${colors.cyan("litestar-vite")} ${colors.red("type generation failed:")} ${message}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
117
|
+
resolvedConfig?.logger.error(`${colors.cyan("litestar-vite")} ${colors.red("type generation failed:")} ${message}`);
|
|
104
118
|
return false;
|
|
105
119
|
} finally {
|
|
106
120
|
isGenerating = false;
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
|
-
const debouncedRunTypeGeneration = debounce(
|
|
123
|
+
const debouncedRunTypeGeneration = debounce(runTypeGenerationWithCache, typesConfig.debounce);
|
|
110
124
|
return {
|
|
111
125
|
name: pluginName,
|
|
112
126
|
enforce: "pre",
|
|
@@ -144,7 +158,7 @@ Solutions:
|
|
|
144
158
|
const hasOpenapi = fs.existsSync(openapiPath);
|
|
145
159
|
const hasPageProps = typesConfig.generatePageProps && fs.existsSync(pagePropsPath);
|
|
146
160
|
if (hasOpenapi || hasPageProps) {
|
|
147
|
-
await
|
|
161
|
+
await runTypeGenerationWithCache();
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
164
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface WriteResult {
|
|
2
|
+
changed: boolean;
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Write file only if content differs from existing.
|
|
7
|
+
* Uses direct Buffer comparison (more efficient than hashing for small files).
|
|
8
|
+
*
|
|
9
|
+
* @param filePath - Absolute path to file
|
|
10
|
+
* @param content - Content to write
|
|
11
|
+
* @param options - Write options
|
|
12
|
+
* @returns WriteResult indicating whether file was changed
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeIfChanged(filePath: string, content: string, options?: {
|
|
15
|
+
encoding?: BufferEncoding;
|
|
16
|
+
}): Promise<WriteResult>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
async function writeIfChanged(filePath, content, options) {
|
|
4
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
5
|
+
const newBuffer = Buffer.from(content, encoding);
|
|
6
|
+
try {
|
|
7
|
+
const existing = await fs.promises.readFile(filePath);
|
|
8
|
+
if (existing.equals(newBuffer)) {
|
|
9
|
+
return { changed: false, path: filePath };
|
|
10
|
+
}
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
14
|
+
await fs.promises.writeFile(filePath, newBuffer);
|
|
15
|
+
return { changed: true, path: filePath };
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
writeIfChanged
|
|
19
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import colors from "picocolors";
|
|
3
|
+
import { readBridgeConfig } from "./shared/bridge-schema.js";
|
|
4
|
+
import { runTypeGeneration } from "./shared/typegen-core.js";
|
|
5
|
+
function parseArgs() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
return {
|
|
8
|
+
verbose: args.includes("--verbose")
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function log(message) {
|
|
12
|
+
console.log(message);
|
|
13
|
+
}
|
|
14
|
+
function logError(message) {
|
|
15
|
+
console.error(message);
|
|
16
|
+
}
|
|
17
|
+
function createConsoleLogger() {
|
|
18
|
+
return {
|
|
19
|
+
info: (message) => log(`${colors.cyan("\u2022")} ${message}`),
|
|
20
|
+
warn: (message) => log(`${colors.yellow("!")} ${message}`),
|
|
21
|
+
error: (message) => logError(`${colors.red("\u2717")} ${message}`)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function main() {
|
|
25
|
+
parseArgs();
|
|
26
|
+
const projectRoot = process.cwd();
|
|
27
|
+
const bridgeConfig = readBridgeConfig();
|
|
28
|
+
if (!bridgeConfig) {
|
|
29
|
+
logError(`${colors.red("\u2717")} .litestar.json not found`);
|
|
30
|
+
logError(" Run 'litestar run' or 'litestar assets generate-types' first to generate it.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const typesConfig = bridgeConfig.types;
|
|
34
|
+
if (!typesConfig || !typesConfig.enabled) {
|
|
35
|
+
log(`${colors.yellow("!")} Type generation is disabled in configuration`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
const coreConfig = {
|
|
39
|
+
projectRoot,
|
|
40
|
+
openapiPath: typesConfig.openapiPath,
|
|
41
|
+
output: typesConfig.output,
|
|
42
|
+
pagePropsPath: typesConfig.pagePropsPath,
|
|
43
|
+
generateSdk: typesConfig.generateSdk,
|
|
44
|
+
generateZod: typesConfig.generateZod,
|
|
45
|
+
generatePageProps: typesConfig.generatePageProps,
|
|
46
|
+
sdkClientPlugin: "@hey-api/client-fetch",
|
|
47
|
+
// Default for CLI
|
|
48
|
+
executor: bridgeConfig.executor
|
|
49
|
+
};
|
|
50
|
+
const logger = createConsoleLogger();
|
|
51
|
+
const result = await runTypeGeneration(coreConfig, { logger });
|
|
52
|
+
if (result.errors.length > 0) {
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
for (const file of result.skippedFiles) {
|
|
56
|
+
const name = file.includes("page-props") ? "Page props types" : file;
|
|
57
|
+
log(`${colors.cyan("\u2022")} ${name} ${colors.dim("(unchanged)")}`);
|
|
58
|
+
}
|
|
59
|
+
if (result.generated) {
|
|
60
|
+
log(`${colors.green("\u2713")} TypeScript artifacts updated ${colors.dim(`(${result.durationMs}ms)`)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main().catch((error) => {
|
|
64
|
+
logError(`${colors.red("\u2717")} Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|