litestar-vite-plugin 0.16.3 → 0.17.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/dist/js/index.js CHANGED
@@ -26,6 +26,7 @@ function litestar(config) {
26
26
  })
27
27
  );
28
28
  }
29
+ plugins.push(createStaticPropsPlugin());
29
30
  return plugins;
30
31
  }
31
32
  async function findIndexHtmlPath(server, pluginConfig) {
@@ -489,26 +490,37 @@ function resolvePluginConfig(config) {
489
490
  if (typeof resolvedConfig.input === "undefined") {
490
491
  throw new Error('litestar-vite-plugin: missing configuration for "input".');
491
492
  }
493
+ const normalizeDir = (dir) => {
494
+ const trimmed = dir.trim().replace(/\/+$/, "");
495
+ if (trimmed.startsWith("/")) {
496
+ const withoutSlash = trimmed.slice(1);
497
+ const segments = withoutSlash.split("/").filter(Boolean);
498
+ if (segments.length <= 2) {
499
+ return withoutSlash;
500
+ }
501
+ }
502
+ return trimmed;
503
+ };
492
504
  if (typeof resolvedConfig.resourceDir === "string") {
493
- resolvedConfig.resourceDir = resolvedConfig.resourceDir.trim().replace(/^\/+/, "").replace(/\/+$/, "");
505
+ resolvedConfig.resourceDir = normalizeDir(resolvedConfig.resourceDir);
494
506
  if (resolvedConfig.resourceDir === "") {
495
507
  throw new Error("litestar-vite-plugin: resourceDir must be a subdirectory. E.g. 'resources'.");
496
508
  }
497
509
  }
498
510
  if (typeof resolvedConfig.bundleDir === "string") {
499
- resolvedConfig.bundleDir = resolvedConfig.bundleDir.trim().replace(/^\/+/, "").replace(/\/+$/, "");
511
+ resolvedConfig.bundleDir = normalizeDir(resolvedConfig.bundleDir);
500
512
  if (resolvedConfig.bundleDir === "") {
501
513
  throw new Error("litestar-vite-plugin: bundleDir must be a subdirectory. E.g. 'public'.");
502
514
  }
503
515
  }
504
516
  if (typeof resolvedConfig.staticDir === "string") {
505
- resolvedConfig.staticDir = resolvedConfig.staticDir.trim().replace(/^\/+/, "").replace(/\/+$/, "");
517
+ resolvedConfig.staticDir = normalizeDir(resolvedConfig.staticDir);
506
518
  if (resolvedConfig.staticDir === "") {
507
519
  throw new Error("litestar-vite-plugin: staticDir must be a subdirectory. E.g. 'src/public'.");
508
520
  }
509
521
  }
510
522
  if (typeof resolvedConfig.ssrOutDir === "string") {
511
- resolvedConfig.ssrOutDir = resolvedConfig.ssrOutDir.trim().replace(/^\/+/, "").replace(/\/+$/, "");
523
+ resolvedConfig.ssrOutDir = normalizeDir(resolvedConfig.ssrOutDir);
512
524
  }
513
525
  if (resolvedConfig.refresh === true) {
514
526
  resolvedConfig.refresh = [{ paths: refreshPaths }];
@@ -619,20 +631,26 @@ function validateAgainstPythonDefaults(resolved, pythonDefaults, userConfig) {
619
631
  if (!pythonDefaults) return;
620
632
  const warnings = [];
621
633
  const hasPythonValue = (value) => typeof value === "string" && value.length > 0;
634
+ const pathsAreSame = (a, b) => {
635
+ if (!a && !b) return true;
636
+ if (!a || !b) return false;
637
+ const normalize = (p) => path.resolve(process.cwd(), p).replace(/\\/g, "/").replace(/\/+$/, "");
638
+ return normalize(a) === normalize(b);
639
+ };
622
640
  if (userConfig.assetUrl !== void 0 && hasPythonValue(pythonDefaults.assetUrl) && resolved.assetUrl !== pythonDefaults.assetUrl) {
623
641
  warnings.push(`assetUrl: vite.config.ts="${resolved.assetUrl}" differs from Python="${pythonDefaults.assetUrl}"`);
624
642
  }
625
- if (userConfig.bundleDir !== void 0 && hasPythonValue(pythonDefaults.bundleDir) && resolved.bundleDir !== pythonDefaults.bundleDir) {
643
+ if (userConfig.bundleDir !== void 0 && hasPythonValue(pythonDefaults.bundleDir) && !pathsAreSame(resolved.bundleDir, pythonDefaults.bundleDir)) {
626
644
  warnings.push(`bundleDir: vite.config.ts="${resolved.bundleDir}" differs from Python="${pythonDefaults.bundleDir}"`);
627
645
  }
628
- if (userConfig.resourceDir !== void 0 && hasPythonValue(pythonDefaults.resourceDir) && resolved.resourceDir !== pythonDefaults.resourceDir) {
646
+ if (userConfig.resourceDir !== void 0 && hasPythonValue(pythonDefaults.resourceDir) && !pathsAreSame(resolved.resourceDir, pythonDefaults.resourceDir)) {
629
647
  warnings.push(`resourceDir: vite.config.ts="${resolved.resourceDir}" differs from Python="${pythonDefaults.resourceDir}"`);
630
648
  }
631
- if (userConfig.staticDir !== void 0 && hasPythonValue(pythonDefaults.staticDir) && resolved.staticDir !== pythonDefaults.staticDir) {
649
+ if (userConfig.staticDir !== void 0 && hasPythonValue(pythonDefaults.staticDir) && !pathsAreSame(resolved.staticDir, pythonDefaults.staticDir)) {
632
650
  warnings.push(`staticDir: vite.config.ts="${resolved.staticDir}" differs from Python="${pythonDefaults.staticDir}"`);
633
651
  }
634
652
  const frameworkMode = pythonDefaults.mode === "framework" || pythonDefaults.mode === "ssr" || pythonDefaults.mode === "ssg";
635
- if (frameworkMode && userConfig.ssrOutDir !== void 0 && hasPythonValue(pythonDefaults.ssrOutDir) && resolved.ssrOutDir !== pythonDefaults.ssrOutDir) {
653
+ if (frameworkMode && userConfig.ssrOutDir !== void 0 && hasPythonValue(pythonDefaults.ssrOutDir) && !pathsAreSame(resolved.ssrOutDir, pythonDefaults.ssrOutDir)) {
636
654
  warnings.push(`ssrOutDir: vite.config.ts="${resolved.ssrOutDir}" differs from Python="${pythonDefaults.ssrOutDir}"`);
637
655
  }
638
656
  if (warnings.length > 0) {
@@ -685,6 +703,35 @@ function resolveFullReloadConfig({ refresh: config }) {
685
703
  return plugin;
686
704
  });
687
705
  }
706
+ const STATIC_PROPS_VIRTUAL_MODULE_ID = "virtual:litestar-static-props";
707
+ const STATIC_PROPS_RESOLVED_ID = "\0virtual:litestar-static-props";
708
+ function createStaticPropsPlugin() {
709
+ const bridgeConfig = readBridgeConfig();
710
+ const staticProps = bridgeConfig?.staticProps ?? {};
711
+ return {
712
+ name: "litestar-vite-static-props",
713
+ enforce: "pre",
714
+ resolveId(id) {
715
+ if (id === STATIC_PROPS_VIRTUAL_MODULE_ID) {
716
+ return STATIC_PROPS_RESOLVED_ID;
717
+ }
718
+ return void 0;
719
+ },
720
+ load(id) {
721
+ if (id === STATIC_PROPS_RESOLVED_ID) {
722
+ const namedExports = [];
723
+ for (const key of Object.keys(staticProps)) {
724
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
725
+ namedExports.push(`export const ${key} = ${JSON.stringify(staticProps[key])};`);
726
+ }
727
+ }
728
+ const defaultExport = `export default ${JSON.stringify(staticProps)};`;
729
+ return [...namedExports, defaultExport].join("\n");
730
+ }
731
+ return void 0;
732
+ }
733
+ };
734
+ }
688
735
  function resolveDevServerUrl(address, config, userConfig) {
689
736
  const configHmrProtocol = typeof config.server.hmr === "object" ? config.server.hmr.protocol : null;
690
737
  const clientProtocol = configHmrProtocol ? configHmrProtocol === "wss" ? "https" : "http" : null;
@@ -54,6 +54,11 @@ export interface BridgeSchema {
54
54
  timestamps: boolean;
55
55
  } | null;
56
56
  litestarVersion: string;
57
+ /**
58
+ * Static props configured in Python ViteConfig.static_props.
59
+ * Available in JS via `virtual:litestar-static-props` module.
60
+ */
61
+ staticProps?: Record<string, unknown> | null;
57
62
  }
58
63
  export declare function parseBridgeSchema(value: unknown): BridgeSchema;
59
64
  export declare function readBridgeConfig(explicitPath?: string): BridgeSchema | null;
@@ -17,7 +17,8 @@ const allowedTopLevelKeys = /* @__PURE__ */ new Set([
17
17
  "spa",
18
18
  "executor",
19
19
  "logging",
20
- "litestarVersion"
20
+ "litestarVersion",
21
+ "staticProps"
21
22
  ]);
22
23
  const allowedModes = /* @__PURE__ */ new Set(["spa", "template", "htmx", "hybrid", "inertia", "framework", "ssr", "ssg", "external"]);
23
24
  const allowedProxyModes = /* @__PURE__ */ new Set(["vite", "direct", "proxy"]);
@@ -135,6 +136,10 @@ function parseSpaConfig(value) {
135
136
  const useScriptElement = assertBoolean(obj, "useScriptElement");
136
137
  return { useScriptElement };
137
138
  }
139
+ function parseStaticProps(value) {
140
+ if (value === null || value === void 0) return null;
141
+ return assertObject(value, "staticProps");
142
+ }
138
143
  function parseBridgeSchema(value) {
139
144
  const obj = assertObject(value, "root");
140
145
  for (const key of Object.keys(obj)) {
@@ -159,6 +164,7 @@ function parseBridgeSchema(value) {
159
164
  const executor = assertEnum(obj.executor, "executor", allowedExecutors);
160
165
  const logging = parseLogging(obj.logging);
161
166
  const litestarVersion = assertString(obj, "litestarVersion");
167
+ const staticProps = parseStaticProps(obj.staticProps);
162
168
  return {
163
169
  assetUrl,
164
170
  deployAssetUrl,
@@ -176,7 +182,8 @@ function parseBridgeSchema(value) {
176
182
  spa,
177
183
  executor,
178
184
  logging,
179
- litestarVersion
185
+ litestarVersion,
186
+ staticProps
180
187
  };
181
188
  }
182
189
  function readBridgeConfig(explicitPath) {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Generate `static-props.ts` from `.litestar.json` static props.
3
+ *
4
+ * This reads the staticProps from the bridge config and generates:
5
+ * - An interface with all properties typed
6
+ * - Named exports for each property
7
+ * - Default export with all properties
8
+ *
9
+ * @returns true if file was changed, false if unchanged
10
+ */
11
+ export declare function emitStaticPropsTypes(outputDir: string): Promise<boolean>;
@@ -0,0 +1,130 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { readBridgeConfig } from "./bridge-schema.js";
4
+ import { writeIfChanged } from "./write-if-changed.js";
5
+ function inferType(value, indent = 0) {
6
+ const spaces = " ".repeat(indent);
7
+ const innerSpaces = " ".repeat(indent + 1);
8
+ if (value === null) {
9
+ return "null";
10
+ }
11
+ if (value === void 0) {
12
+ return "undefined";
13
+ }
14
+ if (typeof value === "string") {
15
+ return "string";
16
+ }
17
+ if (typeof value === "number") {
18
+ return "number";
19
+ }
20
+ if (typeof value === "boolean") {
21
+ return "boolean";
22
+ }
23
+ if (Array.isArray(value)) {
24
+ if (value.length === 0) {
25
+ return "unknown[]";
26
+ }
27
+ const elementType = inferType(value[0], indent);
28
+ const allSameType = value.every((v) => inferType(v, indent) === elementType);
29
+ if (allSameType) {
30
+ return `${elementType}[]`;
31
+ }
32
+ const types = [...new Set(value.map((v) => inferType(v, indent)))];
33
+ return `(${types.join(" | ")})[]`;
34
+ }
35
+ if (typeof value === "object") {
36
+ const obj = value;
37
+ const keys = Object.keys(obj);
38
+ if (keys.length === 0) {
39
+ return "Record<string, unknown>";
40
+ }
41
+ const entries = keys.map((key) => {
42
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
43
+ const valueType = inferType(obj[key], indent + 1);
44
+ return `${innerSpaces}${safeKey}: ${valueType}`;
45
+ });
46
+ return `{
47
+ ${entries.join("\n")}
48
+ ${spaces}}`;
49
+ }
50
+ return "unknown";
51
+ }
52
+ function toInterfaceName(key) {
53
+ const pascal = key.replace(/[^a-zA-Z0-9_$]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").replace(/(^|_)([a-z])/g, (_, __, c) => c.toUpperCase());
54
+ return pascal || "StaticProp";
55
+ }
56
+ async function emitStaticPropsTypes(outputDir) {
57
+ const bridgeConfig = readBridgeConfig();
58
+ const staticProps = bridgeConfig?.staticProps ?? {};
59
+ const outDir = path.resolve(process.cwd(), outputDir);
60
+ await fs.promises.mkdir(outDir, { recursive: true });
61
+ const outFile = path.join(outDir, "static-props.ts");
62
+ if (Object.keys(staticProps).length === 0) {
63
+ const emptyBody = `// AUTO-GENERATED by litestar-vite. Do not edit.
64
+ /* eslint-disable */
65
+
66
+ /**
67
+ * Static props configured in Python ViteConfig.static_props.
68
+ * Currently empty - add static_props to your ViteConfig to generate types.
69
+ *
70
+ * @example
71
+ * config = ViteConfig(
72
+ * static_props={
73
+ * "appName": "My App",
74
+ * "version": "1.0.0",
75
+ * }
76
+ * )
77
+ */
78
+ export interface StaticProps {}
79
+
80
+ /** All static props */
81
+ export const staticProps: StaticProps = {}
82
+
83
+ export default staticProps
84
+ `;
85
+ const result2 = await writeIfChanged(outFile, emptyBody, { encoding: "utf-8" });
86
+ return result2.changed;
87
+ }
88
+ const interfaceProps = [];
89
+ const namedExports = [];
90
+ const nestedInterfaces = [];
91
+ for (const [key, value] of Object.entries(staticProps)) {
92
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
93
+ const inferredType = inferType(value, 1);
94
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0) {
95
+ const interfaceName = toInterfaceName(key);
96
+ nestedInterfaces.push(`export interface ${interfaceName} ${inferType(value, 0)}`);
97
+ interfaceProps.push(` ${safeKey}: ${interfaceName}`);
98
+ } else {
99
+ interfaceProps.push(` ${safeKey}: ${inferredType}`);
100
+ }
101
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
102
+ namedExports.push(`export const ${key} = staticProps.${key}`);
103
+ }
104
+ }
105
+ const body = `// AUTO-GENERATED by litestar-vite. Do not edit.
106
+ /* eslint-disable */
107
+
108
+ ${nestedInterfaces.length > 0 ? `${nestedInterfaces.join("\n\n")}
109
+
110
+ ` : ""}/**
111
+ * Static props configured in Python ViteConfig.static_props.
112
+ * These values are set at build time and do not change at runtime.
113
+ */
114
+ export interface StaticProps {
115
+ ${interfaceProps.join("\n")}
116
+ }
117
+
118
+ /** All static props with inferred types */
119
+ export const staticProps: StaticProps = ${JSON.stringify(staticProps, null, 2)} as const satisfies StaticProps
120
+
121
+ ${namedExports.join("\n")}
122
+
123
+ export default staticProps
124
+ `;
125
+ const result = await writeIfChanged(outFile, body, { encoding: "utf-8" });
126
+ return result.changed;
127
+ }
128
+ export {
129
+ emitStaticPropsTypes
130
+ };
@@ -4,6 +4,7 @@ import colors from "picocolors";
4
4
  import { debounce } from "./debounce.js";
5
5
  import { emitPagePropsTypes } from "./emit-page-props-types.js";
6
6
  import { emitSchemasTypes } from "./emit-schemas-types.js";
7
+ import { emitStaticPropsTypes } from "./emit-static-props-types.js";
7
8
  import { formatPath } from "./format-path.js";
8
9
  import { shouldRunOpenApiTs, updateOpenApiTsCache } from "./typegen-cache.js";
9
10
  import { buildHeyApiPlugins, findOpenApiTsConfig, runHeyApiGeneration } from "./typegen-core.js";
@@ -114,6 +115,17 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
114
115
  logger.error(`Schema types generation failed: ${message}`);
115
116
  }
116
117
  }
118
+ try {
119
+ const changed = await emitStaticPropsTypes(typesConfig.output);
120
+ if (changed) {
121
+ generated = true;
122
+ } else {
123
+ resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Static props types ${colors.dim("(unchanged)")}`);
124
+ }
125
+ } catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ logger.error(`Static props generation failed: ${message}`);
128
+ }
117
129
  if (generated && resolvedConfig) {
118
130
  const duration = Date.now() - startTime;
119
131
  resolvedConfig.logger.info(`${colors.green("\u2713")} TypeScript artifacts updated ${colors.dim(`(${duration}ms)`)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litestar-vite-plugin",
3
- "version": "0.16.3",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "Litestar plugin for Vite.",
6
6
  "keywords": [
@@ -45,6 +45,9 @@
45
45
  "./inertia": {
46
46
  "types": "./dist/js/inertia-types.d.ts",
47
47
  "import": "./dist/js/inertia-types.js"
48
+ },
49
+ "./virtual": {
50
+ "types": "./dist/js/virtual.d.ts"
48
51
  }
49
52
  },
50
53
  "types": "./dist/js/index.d.ts",
@@ -67,6 +70,9 @@
67
70
  ],
68
71
  "inertia": [
69
72
  "./dist/js/inertia-types.d.ts"
73
+ ],
74
+ "virtual": [
75
+ "./dist/js/virtual.d.ts"
70
76
  ]
71
77
  }
72
78
  },
@@ -84,7 +90,7 @@
84
90
  "build-plugin": "rm -rf dist/js && npm run build-plugin-types && npm run build-plugin-esm && npm run build-dev-server",
85
91
  "build-dev-server": "vite build --config src/js/src/dev-server/vite.config.ts && mv dist/js/index.html dist/js/dev-server-index.html",
86
92
  "build-plugin-types": "tsc --project src/js/tsconfig.json --emitDeclarationOnly",
87
- "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && esbuild src/js/src/typegen-cli.ts --platform=node --format=esm --outfile=dist/js/typegen-cli.js --banner:js='#!/usr/bin/env node' && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/constants.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/emit-schemas-types.ts src/js/src/shared/typegen-plugin.ts src/js/src/shared/typegen-core.ts src/js/src/shared/write-if-changed.ts src/js/src/shared/typegen-cache.ts src/js/src/shared/network.ts --platform=node --format=esm --outdir=dist/js/shared",
93
+ "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && esbuild src/js/src/typegen-cli.ts --platform=node --format=esm --outfile=dist/js/typegen-cli.js --banner:js='#!/usr/bin/env node' && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/constants.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/emit-schemas-types.ts src/js/src/shared/emit-static-props-types.ts src/js/src/shared/typegen-plugin.ts src/js/src/shared/typegen-core.ts src/js/src/shared/write-if-changed.ts src/js/src/shared/typegen-cache.ts src/js/src/shared/network.ts --platform=node --format=esm --outdir=dist/js/shared",
88
94
  "build-helpers": "rm -rf dist/js/helpers && tsc --project src/js/tsconfig.helpers.json",
89
95
  "build-inertia-helpers": "rm -rf dist/js/inertia-helpers && tsc --project src/js/tsconfig.inertia-helpers.json",
90
96
  "build-integrations": "esbuild src/js/src/astro.ts src/js/src/sveltekit.ts src/js/src/nuxt.ts src/js/src/inertia-types.ts --platform=node --format=esm --outdir=dist/js",