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.
@@ -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
- const execAsync = promisify(exec);
12
- const nodeRequire = createRequire(import.meta.url);
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
- let _chosenConfigPath = null;
25
- async function runTypeGeneration() {
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 openapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
34
- const pagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
35
+ const absoluteOpenapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
36
+ const absolutePagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
35
37
  let generated = false;
36
- const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
37
- const configPath = candidates.find((p) => fs.existsSync(p)) || null;
38
- _chosenConfigPath = configPath;
39
- const shouldRunOpenApiTs = configPath || typesConfig.generateSdk;
40
- if (fs.existsSync(openapiPath) && shouldRunOpenApiTs) {
41
- resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Generating TypeScript types...`);
42
- if (resolvedConfig && configPath) {
43
- const relConfigPath = formatPath(configPath, resolvedConfig.root);
44
- resolvedConfig.logger.info(`${colors.cyan("\u2022")} openapi-ts config: ${colors.yellow(relConfigPath)}`);
45
- }
46
- const sdkOutput = path.join(typesConfig.output, "api");
47
- let args;
48
- if (configPath) {
49
- args = ["@hey-api/openapi-ts", "--file", configPath];
50
- } else {
51
- args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", sdkOutput];
52
- const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
53
- if (typesConfig.generateSdk) {
54
- plugins.push("@hey-api/sdk", sdkClientPlugin);
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
- if (typesConfig.generateZod) {
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
- nodeRequire.resolve("zod", { paths: [projectRoot] });
66
- } catch {
67
- resolvedConfig?.logger.warn(`${colors.yellow("!")} zod not installed - run: ${resolveInstallHint()} zod`);
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(pagePropsPath)) {
74
- await emitPagePropsTypes(pagePropsPath, typesConfig.output);
75
- generated = true;
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
- if (resolvedConfig) {
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(runTypeGeneration, typesConfig.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 runTypeGeneration();
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
+ });