litestar-vite-plugin 0.25.0 → 0.26.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.
@@ -170,6 +170,12 @@ export interface AstroTypesConfig {
170
170
  * @default false
171
171
  */
172
172
  globalRoute?: boolean;
173
+ /**
174
+ * Fail Vite when type generation fails.
175
+ *
176
+ * Defaults to true during build and false during dev.
177
+ */
178
+ failOnError?: boolean;
173
179
  /**
174
180
  * Debounce time in milliseconds for type regeneration.
175
181
  *
package/dist/js/astro.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readBridgeConfig } from "./shared/bridge-schema.js";
4
- import { DEBOUNCE_MS } from "./shared/constants.js";
5
4
  import { normalizeHost, resolveHotFilePath, resolveLitestarPort } from "./shared/network.js";
6
- import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
5
+ import { createLitestarTypeGenPlugin, resolveTypesConfig } from "./shared/typegen-plugin.js";
7
6
  import { hmrServerConfig } from "./shared/vite-compat.js";
8
7
  function resolveConfig(config = {}) {
9
8
  let hotFile;
@@ -38,74 +37,13 @@ function resolveConfig(config = {}) {
38
37
  if (resolvedLitestarPort !== null) {
39
38
  litestarPort = resolvedLitestarPort;
40
39
  }
41
- let typesConfig = false;
42
- const defaultTypesOutput = "src/generated";
43
- const buildTypeDefaults = (output) => ({
44
- openapiPath: path.join(output, "openapi.json"),
45
- routesPath: path.join(output, "routes.json"),
46
- pagePropsPath: path.join(output, "inertia-pages.json"),
47
- schemasTsPath: path.join(output, "schemas.ts")
40
+ const typesConfig = resolveTypesConfig({
41
+ requested: config.types,
42
+ pythonConfig: pythonTypesConfig ?? void 0,
43
+ defaultOutput: "src/generated",
44
+ mergePythonWhenTrue: true,
45
+ mergePythonForObject: true
48
46
  });
49
- if (config.types === true) {
50
- const output = pythonTypesConfig?.output ?? defaultTypesOutput;
51
- const defaults = buildTypeDefaults(output);
52
- typesConfig = {
53
- enabled: true,
54
- output,
55
- openapiPath: pythonTypesConfig?.openapiPath ?? defaults.openapiPath,
56
- routesPath: pythonTypesConfig?.routesPath ?? defaults.routesPath,
57
- pagePropsPath: pythonTypesConfig?.pagePropsPath ?? defaults.pagePropsPath,
58
- schemasTsPath: pythonTypesConfig?.schemasTsPath ?? defaults.schemasTsPath,
59
- generateZod: pythonTypesConfig?.generateZod ?? false,
60
- generateSdk: pythonTypesConfig?.generateSdk ?? true,
61
- generateRoutes: pythonTypesConfig?.generateRoutes ?? true,
62
- generatePageProps: pythonTypesConfig?.generatePageProps ?? true,
63
- generateSchemas: pythonTypesConfig?.generateSchemas ?? true,
64
- globalRoute: pythonTypesConfig?.globalRoute ?? false,
65
- debounce: DEBOUNCE_MS
66
- };
67
- } else if (typeof config.types === "object" && config.types !== null) {
68
- const userProvidedOutput = Object.hasOwn(config.types, "output");
69
- const output = config.types.output ?? pythonTypesConfig?.output ?? defaultTypesOutput;
70
- const defaults = buildTypeDefaults(output);
71
- const openapiFallback = userProvidedOutput ? defaults.openapiPath : pythonTypesConfig?.openapiPath ?? defaults.openapiPath;
72
- const routesFallback = userProvidedOutput ? defaults.routesPath : pythonTypesConfig?.routesPath ?? defaults.routesPath;
73
- const pagePropsFallback = userProvidedOutput ? defaults.pagePropsPath : pythonTypesConfig?.pagePropsPath ?? defaults.pagePropsPath;
74
- const schemasFallback = userProvidedOutput ? defaults.schemasTsPath : pythonTypesConfig?.schemasTsPath ?? defaults.schemasTsPath;
75
- typesConfig = {
76
- enabled: config.types.enabled ?? true,
77
- output,
78
- openapiPath: config.types.openapiPath ?? openapiFallback,
79
- routesPath: config.types.routesPath ?? routesFallback,
80
- pagePropsPath: config.types.pagePropsPath ?? pagePropsFallback,
81
- schemasTsPath: config.types.schemasTsPath ?? schemasFallback,
82
- generateZod: config.types.generateZod ?? pythonTypesConfig?.generateZod ?? false,
83
- generateSdk: config.types.generateSdk ?? pythonTypesConfig?.generateSdk ?? true,
84
- generateRoutes: config.types.generateRoutes ?? pythonTypesConfig?.generateRoutes ?? true,
85
- generatePageProps: config.types.generatePageProps ?? pythonTypesConfig?.generatePageProps ?? true,
86
- generateSchemas: config.types.generateSchemas ?? pythonTypesConfig?.generateSchemas ?? true,
87
- globalRoute: config.types.globalRoute ?? pythonTypesConfig?.globalRoute ?? false,
88
- debounce: config.types.debounce ?? DEBOUNCE_MS
89
- };
90
- } else if (config.types !== false && pythonTypesConfig?.enabled) {
91
- const output = pythonTypesConfig.output ?? defaultTypesOutput;
92
- const defaults = buildTypeDefaults(output);
93
- typesConfig = {
94
- enabled: true,
95
- output,
96
- openapiPath: pythonTypesConfig.openapiPath ?? defaults.openapiPath,
97
- routesPath: pythonTypesConfig.routesPath ?? defaults.routesPath,
98
- pagePropsPath: pythonTypesConfig.pagePropsPath ?? defaults.pagePropsPath,
99
- schemasTsPath: pythonTypesConfig.schemasTsPath ?? defaults.schemasTsPath,
100
- generateZod: pythonTypesConfig.generateZod ?? false,
101
- generateSdk: pythonTypesConfig.generateSdk ?? true,
102
- generateRoutes: pythonTypesConfig.generateRoutes ?? true,
103
- generatePageProps: pythonTypesConfig.generatePageProps ?? true,
104
- generateSchemas: pythonTypesConfig.generateSchemas ?? true,
105
- globalRoute: pythonTypesConfig.globalRoute ?? false,
106
- debounce: DEBOUNCE_MS
107
- };
108
- }
109
47
  return {
110
48
  apiProxy: config.apiProxy ?? "http://localhost:8000",
111
49
  apiPrefix: config.apiPrefix ?? "/api",
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" />
10
- <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
10
+ <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
11
11
  @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--spacing:.25rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--tracking-widest:.1em;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-navy:#202235;--color-navy-surface:#2a2d40;--color-navy-surface-variant:#1a1c2e;--color-gold:#edb641;--color-gold-light:#ffd480;--color-on-surface:#f8fafc;--color-on-surface-muted:#cbd5e1;--color-on-surface-subtle:#94a3b8;--color-outline:#334155;--color-success:#4ade80;--radius-brand:8px;--radius-brand-lg:12px;--shadow-text-glow-dark:0 0 24px #edb64138}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.inline-flex{display:inline-flex}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-auto{width:auto}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.min-w-0{min-width:0}.flex-1{flex:1}.flex-none{flex:none}.animate-pulse-dot{animation:2s ease-in-out infinite pulse-dot}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.rounded-brand{border-radius:var(--radius-brand)}.rounded-brand-lg{border-radius:var(--radius-brand-lg)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-\[3px\]{border-left-style:var(--tw-border-style);border-left-width:3px}.border-outline{border-color:var(--color-outline)}.border-l-gold{border-left-color:var(--color-gold)}.bg-navy-surface{background-color:var(--color-navy-surface)}.bg-navy-surface-variant{background-color:var(--color-navy-surface-variant)}.bg-success{background-color:var(--color-success)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-gold{color:var(--color-gold)}.text-on-surface{color:var(--color-on-surface)}.text-on-surface-muted{color:var(--color-on-surface-muted)}.text-on-surface-subtle{color:var(--color-on-surface-subtle)}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-all{-webkit-user-select:all;user-select:all}.text-brand-glow{text-shadow:var(--shadow-text-glow-dark)}@media (hover:hover){.group-hover\:translate-x-0\.5:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-navy:hover{background-color:var(--color-navy)}.hover\:text-gold:hover{color:var(--color-gold)}.hover\:text-gold-light:hover{color:var(--color-gold-light)}.hover\:opacity-80:hover{opacity:.8}}@media (width>=40rem){.sm\:px-10{padding-inline:calc(var(--spacing) * 10)}.sm\:py-10{padding-block:calc(var(--spacing) * 10)}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}}html{font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--color-navy);color:var(--color-on-surface-muted);background-image:radial-gradient(#f8fafc0a 1px,#0000 1px);background-size:24px 24px}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.92)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}
12
12
  /*$vite$:1*/</style>
13
13
  </head>
@@ -5,6 +5,7 @@
5
5
  * 1. window.__LITESTAR_CSRF__ (SPA mode)
6
6
  * 2. <meta name="csrf-token" content="..."> (template mode)
7
7
  * 3. Inertia shared props (Inertia mode)
8
+ * 4. csrftoken / XSRF-TOKEN cookie (SPA fallback)
8
9
  *
9
10
  * @module
10
11
  */
@@ -21,6 +22,7 @@ export declare const DEFAULT_CSRF_HEADER_NAME = "X-CSRFToken";
21
22
  * 1. window.__LITESTAR_CSRF__ (injected by SPA handler)
22
23
  * 2. <meta name="csrf-token"> element
23
24
  * 3. Inertia page props (if Inertia is present)
25
+ * 4. csrftoken / XSRF-TOKEN cookie
24
26
  *
25
27
  * @returns The CSRF token or empty string if not found
26
28
  *
@@ -5,6 +5,7 @@
5
5
  * 1. window.__LITESTAR_CSRF__ (SPA mode)
6
6
  * 2. <meta name="csrf-token" content="..."> (template mode)
7
7
  * 3. Inertia shared props (Inertia mode)
8
+ * 4. csrftoken / XSRF-TOKEN cookie (SPA fallback)
8
9
  *
9
10
  * @module
10
11
  */
@@ -42,6 +43,28 @@ function getInertiaToken() {
42
43
  }
43
44
  return undefined;
44
45
  }
46
+ function getCookie(name) {
47
+ if (typeof document === "undefined" || typeof document.cookie !== "string") {
48
+ return undefined;
49
+ }
50
+ const value = `; ${document.cookie}`;
51
+ const parts = value.split(`; ${name}=`);
52
+ if (parts.length === 2) {
53
+ const raw = parts.pop()?.split(";").shift();
54
+ if (raw !== undefined && raw.length > 0) {
55
+ try {
56
+ return decodeURIComponent(raw);
57
+ }
58
+ catch {
59
+ return raw;
60
+ }
61
+ }
62
+ }
63
+ return undefined;
64
+ }
65
+ function getCookieToken() {
66
+ return getCookie("csrftoken") ?? getCookie("XSRF-TOKEN");
67
+ }
45
68
  /**
46
69
  * Get the CSRF token from the page.
47
70
  *
@@ -49,6 +72,7 @@ function getInertiaToken() {
49
72
  * 1. window.__LITESTAR_CSRF__ (injected by SPA handler)
50
73
  * 2. <meta name="csrf-token"> element
51
74
  * 3. Inertia page props (if Inertia is present)
75
+ * 4. csrftoken / XSRF-TOKEN cookie
52
76
  *
53
77
  * @returns The CSRF token or empty string if not found
54
78
  *
@@ -79,11 +103,13 @@ export function getCsrfToken() {
79
103
  }
80
104
  const metaToken = getMetaToken();
81
105
  const inertiaToken = getInertiaToken();
82
- const token = metaToken ?? inertiaToken ?? "";
106
+ const cookieToken = getCookieToken();
107
+ const token = metaToken ?? inertiaToken ?? cookieToken ?? "";
83
108
  if (csrfTokenCache &&
84
109
  csrfTokenCache.windowToken === undefined &&
85
110
  csrfTokenCache.metaToken === metaToken &&
86
111
  csrfTokenCache.inertiaToken === inertiaToken &&
112
+ csrfTokenCache.cookieToken === cookieToken &&
87
113
  csrfTokenCache.token === token) {
88
114
  return csrfTokenCache.token;
89
115
  }
@@ -92,6 +118,7 @@ export function getCsrfToken() {
92
118
  windowToken: undefined,
93
119
  metaToken,
94
120
  inertiaToken,
121
+ cookieToken,
95
122
  };
96
123
  return token;
97
124
  }
@@ -213,7 +213,12 @@ const directives = [
213
213
  el.removeAttribute(name);
214
214
  }
215
215
  else {
216
- el.setAttribute(name, v === true ? "" : String(v));
216
+ const value = v === true ? "" : String(v);
217
+ if (isDangerousUrlAttribute(name, value)) {
218
+ el.removeAttribute(name);
219
+ return;
220
+ }
221
+ el.setAttribute(name, value);
217
222
  }
218
223
  };
219
224
  },
@@ -480,7 +485,101 @@ function stripStrings(s) {
480
485
  .replace(/"(?:[^"\\]|\\.)*"/g, "") // double-quoted strings
481
486
  .replace(/'(?:[^'\\]|\\.)*'/g, ""); // single-quoted strings
482
487
  }
488
+ function readQuotedLiteral(s, start) {
489
+ const quote = s[start];
490
+ if (quote !== "'" && quote !== '"' && quote !== "`")
491
+ return null;
492
+ let value = "";
493
+ for (let i = start + 1; i < s.length; i++) {
494
+ const ch = s[i];
495
+ if (ch === "\\") {
496
+ if (i + 1 >= s.length)
497
+ return null;
498
+ const escaped = readEscapedCharacter(s, i + 1);
499
+ if (!escaped)
500
+ return null;
501
+ value += escaped.value;
502
+ i = escaped.end - 1;
503
+ continue;
504
+ }
505
+ if (quote === "`" && ch === "$" && s[i + 1] === "{") {
506
+ return null;
507
+ }
508
+ if (ch === quote) {
509
+ return { end: i + 1, value };
510
+ }
511
+ value += ch;
512
+ }
513
+ return null;
514
+ }
515
+ function readEscapedCharacter(s, start) {
516
+ const ch = s[start];
517
+ if (ch === "u") {
518
+ if (s[start + 1] === "{") {
519
+ const close = s.indexOf("}", start + 2);
520
+ if (close === -1)
521
+ return null;
522
+ const codePoint = Number.parseInt(s.slice(start + 2, close), 16);
523
+ return Number.isFinite(codePoint) ? { end: close + 1, value: String.fromCodePoint(codePoint) } : null;
524
+ }
525
+ const codePoint = Number.parseInt(s.slice(start + 1, start + 5), 16);
526
+ return Number.isFinite(codePoint) ? { end: start + 5, value: String.fromCharCode(codePoint) } : null;
527
+ }
528
+ if (ch === "x") {
529
+ const codePoint = Number.parseInt(s.slice(start + 1, start + 3), 16);
530
+ return Number.isFinite(codePoint) ? { end: start + 3, value: String.fromCharCode(codePoint) } : null;
531
+ }
532
+ const escapes = { b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", v: "\v" };
533
+ return { end: start + 1, value: escapes[ch] ?? ch };
534
+ }
535
+ function skipTrivia(s, start) {
536
+ let i = start;
537
+ while (i < s.length) {
538
+ while (/\s/.test(s[i] ?? ""))
539
+ i += 1;
540
+ if (s[i] === "/" && s[i + 1] === "/") {
541
+ i = s.indexOf("\n", i + 2);
542
+ if (i === -1)
543
+ return s.length;
544
+ continue;
545
+ }
546
+ if (s[i] === "/" && s[i + 1] === "*") {
547
+ const close = s.indexOf("*/", i + 2);
548
+ if (close === -1)
549
+ return s.length;
550
+ i = close + 2;
551
+ continue;
552
+ }
553
+ break;
554
+ }
555
+ return i;
556
+ }
557
+ function hasBlockedStringConcatenation(s) {
558
+ for (let i = 0; i < s.length; i++) {
559
+ const first = readQuotedLiteral(s, i);
560
+ if (!first)
561
+ continue;
562
+ const parts = [first.value];
563
+ let cursor = skipTrivia(s, first.end);
564
+ while (s[cursor] === "+") {
565
+ const nextStart = skipTrivia(s, cursor + 1);
566
+ const next = readQuotedLiteral(s, nextStart);
567
+ if (!next)
568
+ break;
569
+ parts.push(next.value);
570
+ cursor = skipTrivia(s, next.end);
571
+ }
572
+ if (parts.length > 1 && BLOCKED_GLOBALS.includes(parts.join(""))) {
573
+ return true;
574
+ }
575
+ i = first.end - 1;
576
+ }
577
+ return false;
578
+ }
483
579
  function isExpressionSafe(s) {
580
+ if (hasBlockedStringConcatenation(s)) {
581
+ return false;
582
+ }
484
583
  const stripped = stripStrings(s);
485
584
  return !BLOCKED_RE.test(stripped);
486
585
  }
@@ -525,6 +624,10 @@ function compileTextExpr(t) {
525
624
  const DANGEROUS_TAGS = new Set(["script", "style", "iframe", "object", "embed", "form", "meta", "link", "base"]);
526
625
  const DANGEROUS_ATTR_RE = /^on/i;
527
626
  const DANGEROUS_PROTO_RE = /^\s*javascript\s*:/i;
627
+ const URL_ATTRS = new Set(["href", "src", "action"]);
628
+ function isDangerousUrlAttribute(name, value) {
629
+ return URL_ATTRS.has(name.toLowerCase()) && DANGEROUS_PROTO_RE.test(value);
630
+ }
528
631
  function sanitizeHtml(html) {
529
632
  const tpl = document.createElement("template");
530
633
  tpl.innerHTML = html;
@@ -545,7 +648,7 @@ function sanitizeNode(node) {
545
648
  if (DANGEROUS_ATTR_RE.test(attr.name)) {
546
649
  el.removeAttribute(attr.name);
547
650
  }
548
- else if ((attr.name === "href" || attr.name === "src" || attr.name === "action") && DANGEROUS_PROTO_RE.test(attr.value)) {
651
+ else if (isDangerousUrlAttribute(attr.name, attr.value)) {
549
652
  el.removeAttribute(attr.name);
550
653
  }
551
654
  }
@@ -94,6 +94,12 @@ export interface TypesConfig {
94
94
  * @default false
95
95
  */
96
96
  globalRoute?: boolean;
97
+ /**
98
+ * Fail Vite when type generation fails.
99
+ *
100
+ * Defaults to true during `vite build` and false during `vite serve`.
101
+ */
102
+ failOnError?: boolean;
97
103
  /**
98
104
  * Debounce time in milliseconds for type regeneration.
99
105
  * Prevents regeneration from running too frequently when
package/dist/js/index.js CHANGED
@@ -6,11 +6,10 @@ import { loadEnv } from "vite";
6
6
  import fullReload from "vite-plugin-full-reload";
7
7
  import { checkBackendAvailability, loadLitestarMeta } from "./litestar-meta.js";
8
8
  import { readBridgeConfig } from "./shared/bridge-schema.js";
9
- import { DEBOUNCE_MS } from "./shared/constants.js";
10
9
  import { createLogger } from "./shared/logger.js";
11
10
  import { resolveLitestarPort } from "./shared/network.js";
12
11
  import { resolveDefaultSdkClientPlugin } from "./shared/typegen-core.js";
13
- import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
12
+ import { createLitestarTypeGenPlugin, resolveTypesConfig } from "./shared/typegen-plugin.js";
14
13
  import { buildInputOptions, hmrServerConfig, resolveUserBuildInput } from "./shared/vite-compat.js";
15
14
  let exitHandlersBound = false;
16
15
  let warnedMissingRuntimeConfig = false;
@@ -94,32 +93,32 @@ function resolveLitestarPlugin(pluginConfig) {
94
93
  const proxyHmrClientPort = pythonDefaults?.proxyMode === "vite" ? resolveLitestarPort(pythonDefaults.litestarPort, effectiveAppUrl, process.env) : null;
95
94
  const withProxyErrorSilencer = (proxyConfig) => {
96
95
  if (!proxyConfig) return void 0;
97
- return Object.fromEntries(
98
- Object.entries(proxyConfig).map(([key, value]) => {
99
- if (typeof value !== "object" || value === null) {
100
- return [key, value];
101
- }
102
- const existingConfigure = value.configure;
103
- const valueWithWebsocket = Object.hasOwn(value, "ws") ? value : { ...value, ws: true };
104
- return [
105
- key,
106
- {
107
- ...valueWithWebsocket,
108
- configure(proxy, opts) {
109
- proxy.on("error", (err) => {
110
- const msg = String(err?.message ?? "");
111
- if (shuttingDown || msg.includes("ECONNREFUSED") || msg.includes("ECONNRESET") || msg.includes("socket hang up")) {
112
- return;
113
- }
114
- });
115
- if (typeof existingConfigure === "function") {
116
- existingConfigure(proxy, opts);
117
- }
118
- }
96
+ const entries = Object.entries(proxyConfig).map(([key, value]) => {
97
+ if (typeof value !== "object" || value === null) {
98
+ return [key, value];
99
+ }
100
+ const existingConfigure = value.configure;
101
+ const valueWithWebsocket = Object.hasOwn(value, "ws") ? value : { ...value, ws: true };
102
+ const configure = (proxy, opts) => {
103
+ proxy.on("error", (err) => {
104
+ const msg = err.message;
105
+ if (shuttingDown || msg.includes("ECONNREFUSED") || msg.includes("ECONNRESET") || msg.includes("socket hang up")) {
106
+ return;
119
107
  }
120
- ];
121
- })
122
- );
108
+ });
109
+ if (typeof existingConfigure === "function") {
110
+ existingConfigure(proxy, opts);
111
+ }
112
+ };
113
+ return [
114
+ key,
115
+ {
116
+ ...valueWithWebsocket,
117
+ configure
118
+ }
119
+ ];
120
+ });
121
+ return Object.fromEntries(entries);
123
122
  };
124
123
  const explicitServerOrigin = typeof userConfig.server?.origin === "string" && userConfig.server.origin.length > 0 ? userConfig.server.origin : void 0;
125
124
  const shouldForceDirectServerOrigin = explicitServerOrigin !== void 0 || pythonDefaults?.proxyMode === "direct";
@@ -587,80 +586,11 @@ function resolvePluginConfig(config) {
587
586
  if (resolvedConfig.refresh === true) {
588
587
  resolvedConfig.refresh = [{ paths: refreshPaths }];
589
588
  }
590
- let typesConfig = false;
591
- const defaultTypesOutput = "src/generated";
592
- const defaultOpenapiPath = path.join(defaultTypesOutput, "openapi.json");
593
- const defaultRoutesPath = path.join(defaultTypesOutput, "routes.json");
594
- const defaultSchemasTsPath = path.join(defaultTypesOutput, "schemas.ts");
595
- const defaultPagePropsPath = path.join(defaultTypesOutput, "inertia-pages.json");
596
- if (resolvedConfig.types === false) {
597
- } else if (resolvedConfig.types === true) {
598
- typesConfig = {
599
- enabled: true,
600
- output: defaultTypesOutput,
601
- openapiPath: defaultOpenapiPath,
602
- routesPath: defaultRoutesPath,
603
- pagePropsPath: defaultPagePropsPath,
604
- schemasTsPath: defaultSchemasTsPath,
605
- generateZod: false,
606
- generateSdk: true,
607
- generateRoutes: true,
608
- generatePageProps: true,
609
- generateSchemas: true,
610
- globalRoute: false,
611
- debounce: DEBOUNCE_MS
612
- };
613
- } else if (resolvedConfig.types === "auto" || typeof resolvedConfig.types === "undefined") {
614
- if (pythonDefaults?.types) {
615
- typesConfig = {
616
- enabled: pythonDefaults.types.enabled,
617
- output: pythonDefaults.types.output,
618
- openapiPath: pythonDefaults.types.openapiPath,
619
- routesPath: pythonDefaults.types.routesPath,
620
- pagePropsPath: pythonDefaults.types.pagePropsPath,
621
- schemasTsPath: pythonDefaults.types.schemasTsPath ?? path.join(pythonDefaults.types.output, "schemas.ts"),
622
- generateZod: pythonDefaults.types.generateZod,
623
- generateSdk: pythonDefaults.types.generateSdk,
624
- generateRoutes: pythonDefaults.types.generateRoutes,
625
- generatePageProps: pythonDefaults.types.generatePageProps,
626
- generateSchemas: pythonDefaults.types.generateSchemas ?? true,
627
- globalRoute: pythonDefaults.types.globalRoute,
628
- debounce: DEBOUNCE_MS
629
- };
630
- }
631
- } else if (typeof resolvedConfig.types === "object" && resolvedConfig.types !== null) {
632
- const userProvidedOpenapi = Object.hasOwn(resolvedConfig.types, "openapiPath");
633
- const userProvidedRoutes = Object.hasOwn(resolvedConfig.types, "routesPath");
634
- const userProvidedPageProps = Object.hasOwn(resolvedConfig.types, "pagePropsPath");
635
- const userProvidedSchemasTs = Object.hasOwn(resolvedConfig.types, "schemasTsPath");
636
- typesConfig = {
637
- enabled: resolvedConfig.types.enabled ?? true,
638
- output: resolvedConfig.types.output ?? defaultTypesOutput,
639
- openapiPath: resolvedConfig.types.openapiPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "openapi.json") : defaultOpenapiPath),
640
- routesPath: resolvedConfig.types.routesPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "routes.json") : defaultRoutesPath),
641
- pagePropsPath: resolvedConfig.types.pagePropsPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "inertia-pages.json") : defaultPagePropsPath),
642
- schemasTsPath: resolvedConfig.types.schemasTsPath ?? (resolvedConfig.types.output ? path.join(resolvedConfig.types.output, "schemas.ts") : defaultSchemasTsPath),
643
- generateZod: resolvedConfig.types.generateZod ?? false,
644
- generateSdk: resolvedConfig.types.generateSdk ?? true,
645
- generateRoutes: resolvedConfig.types.generateRoutes ?? true,
646
- generatePageProps: resolvedConfig.types.generatePageProps ?? true,
647
- generateSchemas: resolvedConfig.types.generateSchemas ?? true,
648
- globalRoute: resolvedConfig.types.globalRoute ?? false,
649
- debounce: resolvedConfig.types.debounce ?? DEBOUNCE_MS
650
- };
651
- if (!userProvidedOpenapi && resolvedConfig.types.output) {
652
- typesConfig.openapiPath = path.join(typesConfig.output, "openapi.json");
653
- }
654
- if (!userProvidedRoutes && resolvedConfig.types.output) {
655
- typesConfig.routesPath = path.join(typesConfig.output, "routes.json");
656
- }
657
- if (!userProvidedPageProps && resolvedConfig.types.output) {
658
- typesConfig.pagePropsPath = path.join(typesConfig.output, "inertia-pages.json");
659
- }
660
- if (!userProvidedSchemasTs && resolvedConfig.types.output) {
661
- typesConfig.schemasTsPath = path.join(typesConfig.output, "schemas.ts");
662
- }
663
- }
589
+ const typesConfig = resolveTypesConfig({
590
+ requested: resolvedConfig.types,
591
+ pythonConfig: pythonDefaults?.types ?? void 0,
592
+ defaultOutput: "src/generated"
593
+ });
664
594
  const bridgeInertiaEnabled = pythonDefaults?.spa !== null && pythonDefaults?.spa !== void 0;
665
595
  const inertiaMode = resolvedConfig.inertiaMode ?? bridgeInertiaEnabled;
666
596
  const effectiveResourceDir = resolvedConfig.resourceDir ?? pythonDefaults?.resourceDir ?? "src";
@@ -819,14 +749,12 @@ function resolveDevServerUrl(address, config, userConfig) {
819
749
  return `${protocol}://${host}:${port}`;
820
750
  }
821
751
  function isIpv6(address) {
822
- return address.family === "IPv6" || // In node >=18.0 <18.4 this was an integer value. This was changed in a minor version.
823
- // See: https://github.com/laravel/vite-plugin/issues/103
824
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
825
- // @ts-ignore-next-line
826
- address.family === 6;
752
+ const family = address.family;
753
+ return family === "IPv6" || family === 6;
827
754
  }
828
755
  function noExternalInertiaHelpers(config) {
829
- const userNoExternal = config.ssr?.noExternal;
756
+ const ssrConfig = typeof config.ssr === "object" && config.ssr !== null ? config.ssr : void 0;
757
+ const userNoExternal = ssrConfig?.noExternal;
830
758
  const pluginNoExternal = ["litestar-vite-plugin"];
831
759
  if (userNoExternal === true) {
832
760
  return true;
@@ -852,8 +780,8 @@ function resolveEnvironmentServerConfig(env) {
852
780
  return {
853
781
  host,
854
782
  https: {
855
- key: fs.readFileSync(env.VITE_DEV_SERVER_KEY),
856
- cert: fs.readFileSync(env.VITE_DEV_SERVER_CERT)
783
+ key: fs.readFileSync(env.VITE_SERVER_KEY),
784
+ cert: fs.readFileSync(env.VITE_SERVER_CERT)
857
785
  }
858
786
  };
859
787
  }
@@ -919,25 +847,40 @@ function dirname() {
919
847
  }
920
848
  return path.resolve(process.cwd(), "../dist/js");
921
849
  }
850
+ let cachedDevServerPlaceholderCandidates;
851
+ let cachedDevServerPlaceholderPath;
922
852
  function getDevServerPlaceholderCandidates() {
853
+ if (cachedDevServerPlaceholderCandidates) {
854
+ return cachedDevServerPlaceholderCandidates;
855
+ }
923
856
  const distJsRoot = "dist/js";
924
857
  const placeholderName = "dev-server-index.html";
925
858
  const moduleDir = dirname();
926
859
  const candidates = [
927
- path.join(dirname(), placeholderName),
860
+ path.join(moduleDir, placeholderName),
928
861
  path.join(process.cwd(), distJsRoot, placeholderName),
929
862
  path.join(process.cwd(), "src", "js", distJsRoot, placeholderName),
930
863
  path.resolve(process.cwd(), "..", distJsRoot, placeholderName),
931
864
  path.join(moduleDir, distJsRoot, placeholderName)
932
865
  ];
933
- return [...new Set(candidates)];
866
+ cachedDevServerPlaceholderCandidates = [...new Set(candidates)];
867
+ return cachedDevServerPlaceholderCandidates;
934
868
  }
935
869
  async function loadDevServerPlaceholder() {
870
+ if (cachedDevServerPlaceholderPath) {
871
+ try {
872
+ return await fs.promises.readFile(cachedDevServerPlaceholderPath, "utf-8");
873
+ } catch {
874
+ cachedDevServerPlaceholderPath = void 0;
875
+ }
876
+ }
936
877
  const candidates = getDevServerPlaceholderCandidates();
937
878
  let lastError = new Error("Failed to load dev server placeholder index.html");
938
879
  for (const candidatePath of candidates) {
939
880
  try {
940
- return await fs.promises.readFile(candidatePath, "utf-8");
881
+ const content = await fs.promises.readFile(candidatePath, "utf-8");
882
+ cachedDevServerPlaceholderPath = candidatePath;
883
+ return content;
941
884
  } catch (error) {
942
885
  lastError = error;
943
886
  }