@guren/server 1.0.0 → 1.2.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/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  logDevServerBanner,
3
3
  parseImportMap,
4
4
  startViteDevServer
5
- } from "./chunk-VXXZIXAP.js";
5
+ } from "./chunk-HLXL2AYJ.js";
6
6
  import {
7
7
  PendingSchedule,
8
8
  Schedule,
@@ -1706,644 +1706,730 @@ var ErrorServiceProvider = class extends ServiceProvider {
1706
1706
  }
1707
1707
  };
1708
1708
 
1709
- // src/http/Application.ts
1710
- var MANAGED_VITE_ENV_FLAG = "GUREN_MANAGED_VITE_DEV_SERVER";
1711
- var DEFAULT_DEV_ENTRY_PATH = "/resources/js/dev-entry.ts";
1712
- function clearManagedViteEnv() {
1713
- if (typeof process === "undefined") {
1714
- return;
1709
+ // src/mvc/inertia/InertiaEngine.ts
1710
+ import { isAbsolute, resolve as resolvePath } from "path";
1711
+ import { pathToFileURL } from "url";
1712
+ ensureErrorStackTracePolyfill();
1713
+ var DEFAULT_TITLE = "Guren";
1714
+ var DEV_FALLBACK_IMPORT_MAP = {
1715
+ react: "https://esm.sh/react@19.0.0?dev",
1716
+ "react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime?dev",
1717
+ "react/jsx-dev-runtime": "https://esm.sh/react@19.0.0/jsx-dev-runtime?dev",
1718
+ "react-dom/client": "https://esm.sh/react-dom@19.0.0/client?dev",
1719
+ "@guren/inertia-client": "/vendor/inertia-client.tsx",
1720
+ "@inertiajs/react": "https://esm.sh/@inertiajs/react@2.2.15?dev&external=react,react-dom/client"
1721
+ };
1722
+ async function inertia(component, props, options = {}) {
1723
+ const resolvedVersion = options.version ?? process.env.GUREN_INERTIA_VERSION ?? void 0;
1724
+ const page = {
1725
+ component,
1726
+ props,
1727
+ url: options.url ?? "",
1728
+ version: resolvedVersion
1729
+ };
1730
+ const request = options.request;
1731
+ const isInertiaVisit = Boolean(request?.headers.get("X-Inertia"));
1732
+ const prefersJson = request ? acceptsJson(request) : false;
1733
+ const versionHeader = resolvedVersion ? { "X-Inertia-Version": resolvedVersion } : {};
1734
+ if (request && resolvedVersion && isInertiaVisit && request.method === "GET") {
1735
+ const clientVersion = request.headers.get("X-Inertia-Version");
1736
+ if (clientVersion !== resolvedVersion) {
1737
+ return new Response(null, {
1738
+ status: 409,
1739
+ headers: {
1740
+ "X-Inertia-Location": options.url ?? request.url,
1741
+ Vary: "Accept"
1742
+ }
1743
+ });
1744
+ }
1715
1745
  }
1716
- if (process.env[MANAGED_VITE_ENV_FLAG] === "1") {
1717
- delete process.env.VITE_DEV_SERVER_URL;
1746
+ if (isInertiaVisit || prefersJson) {
1747
+ return new Response(serializePage(page), {
1748
+ status: options.status ?? 200,
1749
+ headers: {
1750
+ "Content-Type": "application/json; charset=utf-8",
1751
+ "X-Inertia": "true",
1752
+ Vary: "Accept",
1753
+ ...versionHeader,
1754
+ ...options.headers
1755
+ }
1756
+ });
1718
1757
  }
1719
- delete process.env[MANAGED_VITE_ENV_FLAG];
1758
+ const html = await renderDocument(page, options);
1759
+ return new Response(html, {
1760
+ status: options.status ?? 200,
1761
+ headers: {
1762
+ "Content-Type": "text/html; charset=utf-8",
1763
+ "X-Inertia": "true",
1764
+ Vary: "Accept",
1765
+ ...versionHeader,
1766
+ ...options.headers
1767
+ }
1768
+ });
1720
1769
  }
1721
- function normalizeDevEntryUrl(baseUrl) {
1722
- return `${baseUrl.replace(/\/$/u, "")}${DEFAULT_DEV_ENTRY_PATH}`;
1770
+ async function renderDocument(page, options) {
1771
+ const defaultEntry = process.env.GUREN_INERTIA_ENTRY ?? "/resources/js/app.tsx";
1772
+ const entry = options.entry ?? defaultEntry;
1773
+ const title = escapeHtml(options.title ?? DEFAULT_TITLE);
1774
+ const styles = options.styles ?? parseStylesEnv(process.env.GUREN_INERTIA_STYLES);
1775
+ const envImportMap = parseImportMap(process.env.GUREN_INERTIA_IMPORT_MAP, {
1776
+ context: "GUREN_INERTIA_IMPORT_MAP"
1777
+ });
1778
+ const isProduction = process.env.NODE_ENV === "production";
1779
+ const importMapEntries = {
1780
+ ...isProduction ? {} : DEV_FALLBACK_IMPORT_MAP,
1781
+ ...envImportMap,
1782
+ ...options.importMap ?? {}
1783
+ };
1784
+ const importMap = JSON.stringify({ imports: importMapEntries }, null, 2);
1785
+ const serializedPage = serializePage(page);
1786
+ const stylesheetLinks = renderStyles(styles);
1787
+ const docsHeadBootstrap = renderDocsHeadBootstrap(page.component);
1788
+ const ssrResult = await tryRenderSsr(page, options);
1789
+ const headElements = (ssrResult?.head ?? []).map(normalizeHeadElement);
1790
+ const hasCustomTitle = headElements.some(
1791
+ (element) => /<title\b[^>]*>/iu.test(element)
1792
+ );
1793
+ const headSegments = [
1794
+ '<meta charset="utf-8" />',
1795
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
1796
+ hasCustomTitle ? "" : `<title>${title}</title>`,
1797
+ docsHeadBootstrap,
1798
+ stylesheetLinks,
1799
+ ...headElements,
1800
+ Object.keys(importMapEntries).length > 0 ? `<script type="importmap">${importMap}</script>` : "",
1801
+ `<script>window.__INERTIA_PAGE__ = ${serializedPage};</script>`
1802
+ ].filter((segment) => segment && segment.length > 0);
1803
+ const appMarkup = ssrResult?.body ?? `<script data-page="app" type="application/json">${serializedPage}</script><div id="app"></div>`;
1804
+ const bodyClass = resolveBodyClass(page.component);
1805
+ const bodyAttributes = bodyClass ? ` class="${escapeAttribute(bodyClass)}"` : "";
1806
+ const lang = escapeAttribute(options.lang ?? "en");
1807
+ return `<!DOCTYPE html>
1808
+ <html lang="${lang}">
1809
+ <head>
1810
+ ${headSegments.join("\n ")}
1811
+ </head>
1812
+ <body${bodyAttributes}>
1813
+ ${appMarkup}
1814
+ <script type="module" src="${entry}"></script>
1815
+ </body>
1816
+ </html>`;
1723
1817
  }
1724
- function syncManagedInertiaDevEntry(devServerUrl) {
1725
- if (typeof process === "undefined") {
1726
- return;
1727
- }
1728
- const nextEntry = normalizeDevEntryUrl(devServerUrl);
1729
- const currentEntry = process.env.GUREN_INERTIA_ENTRY;
1730
- if (!currentEntry || currentEntry.endsWith(DEFAULT_DEV_ENTRY_PATH)) {
1731
- process.env.GUREN_INERTIA_ENTRY = nextEntry;
1818
+ async function tryRenderSsr(page, options) {
1819
+ const ssrOptions = options.ssr;
1820
+ if (ssrOptions?.enabled === false) {
1821
+ return void 0;
1732
1822
  }
1733
- }
1734
- function getGlobalState() {
1735
- return globalThis;
1736
- }
1737
- async function stopActiveBunServer() {
1738
- const state = getGlobalState();
1739
- const previous = state.__gurenActiveServer;
1740
- if (!previous?.stop) {
1741
- state.__gurenActiveServer = void 0;
1742
- return;
1823
+ const manifest = (ssrOptions?.manifest ?? process.env.GUREN_INERTIA_SSR_MANIFEST)?.trim() || void 0;
1824
+ const renderer = ssrOptions?.render ?? await loadSsrRenderer(
1825
+ ssrOptions?.entry ?? process.env.GUREN_INERTIA_SSR_ENTRY
1826
+ );
1827
+ if (!renderer) {
1828
+ return void 0;
1743
1829
  }
1744
1830
  try {
1745
- await Promise.resolve(previous.stop());
1831
+ const result = await renderer({
1832
+ page,
1833
+ request: options.request,
1834
+ manifest
1835
+ });
1836
+ if (!result || typeof result.body !== "string") {
1837
+ return void 0;
1838
+ }
1839
+ return {
1840
+ body: result.body,
1841
+ head: Array.isArray(result.head) ? result.head : []
1842
+ };
1746
1843
  } catch (error) {
1747
- console.warn("Failed to stop previous Bun server:", error);
1748
- } finally {
1749
- state.__gurenActiveServer = void 0;
1844
+ console.error(
1845
+ "Inertia SSR renderer failed; falling back to client rendering.",
1846
+ error
1847
+ );
1848
+ return void 0;
1750
1849
  }
1751
1850
  }
1752
- function setActiveBunServer(server) {
1753
- getGlobalState().__gurenActiveServer = server;
1754
- }
1755
- async function stopActiveViteDevServer() {
1756
- const state = getGlobalState();
1757
- const previous = state.__gurenActiveViteDevServer;
1758
- if (!previous) {
1759
- state.__gurenActiveViteDevServer = void 0;
1760
- clearManagedViteEnv();
1761
- return;
1851
+ async function loadSsrRenderer(entry) {
1852
+ const specifier = entry?.trim();
1853
+ if (!specifier) {
1854
+ return void 0;
1762
1855
  }
1856
+ const normalized = normalizeSsrSpecifier(specifier);
1763
1857
  try {
1764
- await previous.close();
1858
+ const module = await import(normalized);
1859
+ const renderCandidate = extractSsrRenderer(module);
1860
+ if (!renderCandidate) {
1861
+ console.warn(
1862
+ `Inertia SSR entry "${specifier}" does not export a renderer. Expected a default export or a named "render" function.`
1863
+ );
1864
+ return void 0;
1865
+ }
1866
+ return renderCandidate;
1765
1867
  } catch (error) {
1766
- console.warn("Failed to stop previous Vite dev server:", error);
1767
- } finally {
1768
- state.__gurenActiveViteDevServer = void 0;
1769
- clearManagedViteEnv();
1868
+ console.error(`Failed to import Inertia SSR entry "${specifier}".`, error);
1869
+ return void 0;
1770
1870
  }
1771
1871
  }
1772
- function setActiveViteDevServer(server) {
1773
- getGlobalState().__gurenActiveViteDevServer = server;
1774
- }
1775
- var Application = class {
1776
- constructor(options = {}) {
1777
- this.options = options;
1778
- this.hono = new Hono();
1779
- this.container = new Container();
1780
- this.router = new Router();
1781
- this.authManager = new AuthManager();
1782
- this.providerManager = new ProviderManager(this.container);
1783
- this.container.instance("app", this);
1784
- this.container.instance("hono", this.hono);
1785
- this.container.instance("auth", this.authManager);
1786
- this.container.instance("router", this.router);
1787
- if (!this.authManager.guardNames().length) {
1788
- this.authManager.registerGuard("web", ({ ctx, session, manager }) => {
1789
- let provider;
1790
- try {
1791
- provider = manager.getProvider("users");
1792
- } catch {
1793
- provider = { retrieveById: async () => null, retrieveByCredentials: async () => null, validateCredentials: async () => false };
1794
- }
1795
- return new SessionGuard({ provider, session });
1796
- });
1797
- this.authManager.setDefaultGuard("web");
1798
- }
1799
- if (this.options.auth) {
1800
- this.providerManager.register(AuthServiceProvider);
1801
- } else {
1802
- this.hono.use("*", attachAuthContext((ctx) => this.authManager.createAuthContext(ctx)));
1803
- }
1804
- this.providerManager.register(AuthorizationServiceProvider);
1805
- const userProviders = Array.isArray(this.options.providers) ? this.options.providers : [];
1806
- const hasUserErrorProvider = userProviders.some(
1807
- (provider) => provider === ErrorServiceProvider || provider.prototype instanceof ErrorServiceProvider
1808
- );
1809
- if (!hasUserErrorProvider) {
1810
- this.providerManager.register(ErrorServiceProvider);
1811
- }
1812
- if (userProviders.length > 0) {
1813
- this.providerManager.registerMany(userProviders);
1814
- }
1872
+ function extractSsrRenderer(module) {
1873
+ if (!module || typeof module !== "object") {
1874
+ return void 0;
1815
1875
  }
1816
- hono;
1817
- container;
1818
- router;
1819
- providerManager;
1820
- authManager;
1821
- viteDevServer;
1822
- bunServer;
1823
- viteTeardownRegistered = false;
1824
- bunTeardownRegistered = false;
1825
- autoSessionAttached = false;
1826
- routesRegistered = false;
1827
- get auth() {
1828
- return this.authManager;
1876
+ const candidate = typeof module.render === "function" ? module.render : typeof module.default === "function" ? module.default : void 0;
1877
+ if (!candidate) {
1878
+ return void 0;
1829
1879
  }
1830
- get authOptions() {
1831
- return this.options.auth;
1880
+ return (context) => Promise.resolve(candidate(context));
1881
+ }
1882
+ function normalizeSsrSpecifier(specifier) {
1883
+ if (specifier.startsWith("file://") || isUrlLike(specifier)) {
1884
+ return specifier;
1832
1885
  }
1833
- markAutoSessionAttached() {
1834
- this.autoSessionAttached = true;
1886
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1887
+ const absolute = resolvePath(process.cwd(), specifier);
1888
+ return pathToFileURL(absolute).href;
1835
1889
  }
1836
- hasAutoSessionAttached() {
1837
- return this.autoSessionAttached;
1890
+ if (isAbsolute(specifier)) {
1891
+ return pathToFileURL(specifier).href;
1838
1892
  }
1839
- /**
1840
- * Mounts the application router onto the Hono instance.
1841
- */
1842
- async mountRoutes() {
1843
- if (this.options.routes && !this.routesRegistered) {
1844
- await this.options.routes(this.router);
1845
- this.routesRegistered = true;
1893
+ return specifier;
1894
+ }
1895
+ function isUrlLike(specifier) {
1896
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(specifier);
1897
+ }
1898
+ function renderStyles(styles) {
1899
+ if (!styles.length) {
1900
+ return "";
1901
+ }
1902
+ return styles.map((href) => `<link rel="stylesheet" href="${escapeAttribute(href)}" />`).join("\n ");
1903
+ }
1904
+ function normalizeHeadElement(element) {
1905
+ const markup = typeof element === "string" ? element : String(element ?? "");
1906
+ const pattern = /href="\/(?!public\/)([^"?]+\.(?:js|css))(\?[^"']*)?"/g;
1907
+ return markup.replace(
1908
+ pattern,
1909
+ (_match, file2, query = "") => `href="/public/assets/${file2}${query}"`
1910
+ );
1911
+ }
1912
+ function resolveBodyClass(componentName) {
1913
+ return componentName.startsWith("Docs/") ? "docs-theme" : "";
1914
+ }
1915
+ function renderDocsHeadBootstrap(componentName) {
1916
+ if (!componentName.startsWith("Docs/")) {
1917
+ return "";
1918
+ }
1919
+ const criticalStyles = [
1920
+ "html,body{background:#ffffff;color:#1f2937;}",
1921
+ "body.docs-theme{background:#ffffff;color:#1f2937;}",
1922
+ "html.dark,html.dark body,html.dark body.docs-theme{background:#1a1a2e;color:#e0def4;}",
1923
+ ".size-4{width:1rem;height:1rem;}",
1924
+ ".size-5{width:1.25rem;height:1.25rem;}",
1925
+ ".size-6{width:1.5rem;height:1.5rem;}",
1926
+ ".size-8{width:2rem;height:2rem;}",
1927
+ ".size-9{width:2.25rem;height:2.25rem;}"
1928
+ ].join("");
1929
+ const prepaintScript = [
1930
+ "(function(){",
1931
+ "try{",
1932
+ "var mode=localStorage.getItem('guren-color-mode')||'system';",
1933
+ "var dark=mode==='dark'||(mode!=='light'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);",
1934
+ "var root=document.documentElement;",
1935
+ "root.classList.toggle('dark',dark);",
1936
+ "root.style.colorScheme=dark?'dark':'light';",
1937
+ "}catch(_){",
1938
+ "}",
1939
+ "})();"
1940
+ ].join("");
1941
+ return `<style id="guren-docs-critical">${criticalStyles}</style><script>${prepaintScript}</script>`;
1942
+ }
1943
+ function serializePage(page) {
1944
+ return JSON.stringify(page).replace(/[<\u2028\u2029]/gu, (char) => {
1945
+ switch (char) {
1946
+ case "<":
1947
+ return "\\u003c";
1948
+ case "\u2028":
1949
+ return "\\u2028";
1950
+ case "\u2029":
1951
+ return "\\u2029";
1952
+ default:
1953
+ return char;
1846
1954
  }
1847
- this.router.mount(this.hono, { container: this.container });
1955
+ });
1956
+ }
1957
+ function acceptsJson(request) {
1958
+ const accept = request.headers.get("accept")?.toLowerCase() ?? "";
1959
+ if (!accept || accept === "*/*") {
1960
+ return false;
1848
1961
  }
1849
- /**
1850
- * Allows registering global middlewares directly on the underlying Hono app.
1851
- */
1852
- use(path, ...middleware) {
1853
- this.hono.use(path, ...middleware);
1962
+ if (accept.includes("text/html")) {
1963
+ return false;
1854
1964
  }
1855
- /**
1856
- * Executes provider registration, boot callback, mounts routes, and boots providers.
1857
- */
1858
- async boot() {
1859
- this.mountSecurityDefaults();
1860
- await this.providerManager.registerAll();
1861
- await this.options.boot?.(this.hono);
1862
- await this.mountRoutes();
1863
- await this.mountMcpEndpoint();
1864
- await this.providerManager.bootAll();
1965
+ return accept.includes("application/json") || accept.includes("json");
1966
+ }
1967
+ function escapeHtml(value) {
1968
+ return value.replace(/[&<>"']/gu, (char) => {
1969
+ switch (char) {
1970
+ case "&":
1971
+ return "&amp;";
1972
+ case "<":
1973
+ return "&lt;";
1974
+ case ">":
1975
+ return "&gt;";
1976
+ case '"':
1977
+ return "&quot;";
1978
+ case "'":
1979
+ return "&#39;";
1980
+ default:
1981
+ return char;
1982
+ }
1983
+ });
1984
+ }
1985
+ function escapeAttribute(value) {
1986
+ return value.replace(/[&"]/gu, (char) => {
1987
+ if (char === "&") {
1988
+ return "&amp;";
1989
+ }
1990
+ return "&quot;";
1991
+ });
1992
+ }
1993
+ function parseStylesEnv(value) {
1994
+ if (!value) {
1995
+ return [];
1865
1996
  }
1866
- /**
1867
- * Registers default security middleware (headers + host authorization).
1868
- */
1869
- mountSecurityDefaults() {
1870
- const { securityHeaders } = this.options;
1871
- if (securityHeaders !== false) {
1872
- this.hono.use("*", createSecurityHeaders(securityHeaders ?? {}));
1997
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
1998
+ }
1999
+
2000
+ // src/mvc/ViewEngine.ts
2001
+ var ViewEngine = class {
2002
+ static engines = /* @__PURE__ */ new Map();
2003
+ static register(name, renderer) {
2004
+ this.engines.set(name, renderer);
2005
+ }
2006
+ static has(name) {
2007
+ return this.engines.has(name);
2008
+ }
2009
+ static render(name, template, props) {
2010
+ const engine = this.engines.get(name);
2011
+ if (!engine) {
2012
+ throw new Error(`View engine "${name}" has not been registered.`);
1873
2013
  }
1874
- this.mountHostAuthorization();
2014
+ return engine(template, props);
1875
2015
  }
1876
- mountHostAuthorization() {
1877
- const { hostAuthorization } = this.options;
1878
- if (hostAuthorization === false || !hostAuthorization) return;
1879
- this.hono.use("*", createHostAuthorizationMiddleware(hostAuthorization));
2016
+ };
2017
+
2018
+ // src/mvc/inertia/shared.ts
2019
+ var resolver = null;
2020
+ function setInertiaSharedProps(resolverFn) {
2021
+ resolver = resolverFn;
2022
+ }
2023
+ function getInertiaSharedPropsResolver() {
2024
+ return resolver;
2025
+ }
2026
+ function shareInertiaProps(resolverFn) {
2027
+ const previous = resolver;
2028
+ resolver = async (ctx) => {
2029
+ const prev = previous ? await previous(ctx) : {};
2030
+ const next = await resolverFn(ctx);
2031
+ return { ...prev, ...next };
2032
+ };
2033
+ }
2034
+ async function resolveSharedInertiaProps(ctx) {
2035
+ if (!resolver) return {};
2036
+ const shared = await resolver(ctx);
2037
+ if (shared && typeof shared === "object") {
2038
+ return shared;
2039
+ }
2040
+ return {};
2041
+ }
2042
+
2043
+ // src/providers/InertiaServiceProvider.ts
2044
+ var InertiaServiceProvider = class extends ServiceProvider {
2045
+ register() {
2046
+ if (!ViewEngine.has("inertia")) {
2047
+ ViewEngine.register("inertia", inertia);
2048
+ }
2049
+ }
2050
+ boot() {
2051
+ this.registerValidationRenderer();
2052
+ this.registerSharedErrors();
1880
2053
  }
1881
2054
  /**
1882
- * Mounts the MCP endpoint at /_guren/mcp when explicitly enabled.
1883
- * Requires GUREN_MCP=1 environment variable. Never active in production.
1884
- * This allows AI coding agents to introspect the project.
2055
+ * Register a custom renderer for ValidationException on Inertia requests.
2056
+ * Non-Inertia requests fall through to the default JSON 422 response.
1885
2057
  */
1886
- async mountMcpEndpoint() {
1887
- if (typeof process === "undefined" || process.env?.NODE_ENV === "production" || process.env?.GUREN_MCP !== "1") {
1888
- return;
1889
- }
2058
+ registerValidationRenderer() {
2059
+ let handler;
1890
2060
  try {
1891
- const { McpServiceProvider } = await import("./McpServiceProvider-JW6PDVMD.js");
1892
- const provider = new McpServiceProvider(this.container);
1893
- provider.register();
1894
- await provider.boot();
2061
+ handler = this.container.make("exception.handler");
1895
2062
  } catch {
2063
+ return;
1896
2064
  }
2065
+ handler.render(ValidationException, (error, ctx) => {
2066
+ const isInertia = ctx.req.header("X-Inertia");
2067
+ if (!isInertia) {
2068
+ return ctx.json(
2069
+ {
2070
+ message: error.message,
2071
+ errors: error.errors
2072
+ },
2073
+ 422
2074
+ );
2075
+ }
2076
+ const session = getSessionFromContext(ctx);
2077
+ if (session) {
2078
+ const flattened = {};
2079
+ for (const [key, messages] of Object.entries(error.errors ?? {})) {
2080
+ flattened[key] = messages[0] ?? "";
2081
+ }
2082
+ session.flash("errors", flattened);
2083
+ }
2084
+ const referer = ctx.req.header("Referer") ?? "/";
2085
+ return new Response(null, {
2086
+ status: 303,
2087
+ headers: { Location: referer }
2088
+ });
2089
+ });
1897
2090
  }
1898
2091
  /**
1899
- * Fetch handler to integrate with Bun.serve or any standard Fetch runtime.
2092
+ * Wrap existing shared props resolver to auto-inject flashed `errors`.
1900
2093
  */
1901
- async fetch(request, env, executionCtx) {
1902
- return this.hono.fetch(request, env, executionCtx);
2094
+ registerSharedErrors() {
2095
+ const previous = getInertiaSharedPropsResolver();
2096
+ setInertiaSharedProps(async (ctx) => {
2097
+ const prev = previous ? await previous(ctx) : {};
2098
+ const session = getSessionFromContext(ctx);
2099
+ const errors = session?.getFlash("errors");
2100
+ return {
2101
+ ...prev,
2102
+ ...errors && Object.keys(errors).length > 0 ? { errors } : {}
2103
+ };
2104
+ });
1903
2105
  }
1904
- /**
1905
- * Convenience helper to start a Bun server when available.
1906
- */
1907
- async listen(options = {}) {
1908
- if (!Bun) {
1909
- throw new Error("Bun runtime is required to call Application.listen");
1910
- }
1911
- await stopActiveBunServer();
1912
- await stopActiveViteDevServer();
1913
- const { port = 3e3, hostname = "0.0.0.0", assetsUrl, vite } = options;
1914
- const externalAssetsUrl = typeof process !== "undefined" && process.env?.[MANAGED_VITE_ENV_FLAG] !== "1" ? process.env?.VITE_DEV_SERVER_URL : void 0;
1915
- let resolvedAssetsUrl = assetsUrl ?? externalAssetsUrl;
1916
- const shouldStartVite = vite !== false && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !resolvedAssetsUrl && process.env?.GUREN_DEV_VITE !== "0";
1917
- if (shouldStartVite) {
1918
- const viteOptions = typeof vite === "object" ? vite : void 0;
1919
- try {
1920
- await this.closeViteDevServer();
1921
- const { server: server2, localUrl } = await startViteDevServer({
1922
- root: viteOptions?.root ?? process.cwd(),
1923
- config: viteOptions?.config,
1924
- host: viteOptions?.host ?? true,
1925
- port: viteOptions?.port
1926
- });
1927
- this.viteDevServer = server2;
1928
- setActiveViteDevServer(server2);
1929
- resolvedAssetsUrl = localUrl;
1930
- if (typeof process !== "undefined") {
1931
- process.env.VITE_DEV_SERVER_URL = resolvedAssetsUrl;
1932
- process.env[MANAGED_VITE_ENV_FLAG] = "1";
2106
+ };
2107
+
2108
+ // src/http/Application.ts
2109
+ var MANAGED_VITE_ENV_FLAG = "GUREN_MANAGED_VITE_DEV_SERVER";
2110
+ var DEFAULT_DEV_ENTRY_PATH = "/resources/js/dev-entry.ts";
2111
+ function clearManagedViteEnv() {
2112
+ if (typeof process === "undefined") {
2113
+ return;
2114
+ }
2115
+ if (process.env[MANAGED_VITE_ENV_FLAG] === "1") {
2116
+ delete process.env.VITE_DEV_SERVER_URL;
2117
+ }
2118
+ delete process.env[MANAGED_VITE_ENV_FLAG];
2119
+ }
2120
+ function normalizeDevEntryUrl(baseUrl) {
2121
+ return `${baseUrl.replace(/\/$/u, "")}${DEFAULT_DEV_ENTRY_PATH}`;
2122
+ }
2123
+ function syncManagedInertiaDevEntry(devServerUrl) {
2124
+ if (typeof process === "undefined") {
2125
+ return;
2126
+ }
2127
+ const nextEntry = normalizeDevEntryUrl(devServerUrl);
2128
+ const currentEntry = process.env.GUREN_INERTIA_ENTRY;
2129
+ if (!currentEntry || currentEntry.endsWith(DEFAULT_DEV_ENTRY_PATH)) {
2130
+ process.env.GUREN_INERTIA_ENTRY = nextEntry;
2131
+ }
2132
+ }
2133
+ function getGlobalState() {
2134
+ return globalThis;
2135
+ }
2136
+ async function stopActiveBunServer() {
2137
+ const state = getGlobalState();
2138
+ const previous = state.__gurenActiveServer;
2139
+ if (!previous?.stop) {
2140
+ state.__gurenActiveServer = void 0;
2141
+ return;
2142
+ }
2143
+ try {
2144
+ await Promise.resolve(previous.stop());
2145
+ } catch (error) {
2146
+ console.warn("Failed to stop previous Bun server:", error);
2147
+ } finally {
2148
+ state.__gurenActiveServer = void 0;
2149
+ }
2150
+ }
2151
+ function setActiveBunServer(server) {
2152
+ getGlobalState().__gurenActiveServer = server;
2153
+ }
2154
+ async function stopActiveViteDevServer() {
2155
+ const state = getGlobalState();
2156
+ const previous = state.__gurenActiveViteDevServer;
2157
+ if (!previous) {
2158
+ state.__gurenActiveViteDevServer = void 0;
2159
+ clearManagedViteEnv();
2160
+ return;
2161
+ }
2162
+ try {
2163
+ await previous.close();
2164
+ } catch (error) {
2165
+ console.warn("Failed to stop previous Vite dev server:", error);
2166
+ } finally {
2167
+ state.__gurenActiveViteDevServer = void 0;
2168
+ clearManagedViteEnv();
2169
+ }
2170
+ }
2171
+ function setActiveViteDevServer(server) {
2172
+ getGlobalState().__gurenActiveViteDevServer = server;
2173
+ }
2174
+ var Application = class {
2175
+ constructor(options = {}) {
2176
+ this.options = options;
2177
+ this.hono = new Hono();
2178
+ this.container = new Container();
2179
+ this.router = new Router();
2180
+ this.authManager = new AuthManager();
2181
+ this.providerManager = new ProviderManager(this.container);
2182
+ this.container.instance("app", this);
2183
+ this.container.instance("hono", this.hono);
2184
+ this.container.instance("auth", this.authManager);
2185
+ this.container.instance("router", this.router);
2186
+ if (!this.authManager.guardNames().length) {
2187
+ this.authManager.registerGuard("web", ({ ctx, session, manager }) => {
2188
+ let provider;
2189
+ try {
2190
+ provider = manager.getProvider("users");
2191
+ } catch {
2192
+ provider = { retrieveById: async () => null, retrieveByCredentials: async () => null, validateCredentials: async () => false };
1933
2193
  }
1934
- syncManagedInertiaDevEntry(resolvedAssetsUrl);
1935
- this.registerViteTeardown();
1936
- } catch (error) {
1937
- console.error("Failed to start Vite dev server:", error);
1938
- process.exit(1);
1939
- }
1940
- }
1941
- const server = Bun.serve({
1942
- port,
1943
- hostname,
1944
- fetch: (request) => this.fetch(request)
1945
- });
1946
- this.bunServer = server;
1947
- setActiveBunServer(server);
1948
- this.registerBunTeardown();
1949
- const shouldLogBanner = typeof process === "undefined" || process.env?.NODE_ENV !== "production" && process.env?.GUREN_DEV_BANNER !== "0";
1950
- if (shouldLogBanner) {
1951
- this.logDevServerBanner({
1952
- hostname,
1953
- port,
1954
- assetsUrl: resolvedAssetsUrl ?? "http://localhost:5173"
2194
+ return new SessionGuard({ provider, session });
1955
2195
  });
2196
+ this.authManager.setDefaultGuard("web");
2197
+ }
2198
+ if (this.options.auth) {
2199
+ this.providerManager.register(AuthServiceProvider);
2200
+ } else {
2201
+ this.hono.use("*", attachAuthContext((ctx) => this.authManager.createAuthContext(ctx)));
2202
+ }
2203
+ this.providerManager.register(AuthorizationServiceProvider);
2204
+ const userProviders = Array.isArray(this.options.providers) ? this.options.providers : [];
2205
+ const hasUserErrorProvider = userProviders.some(
2206
+ (provider) => provider === ErrorServiceProvider || provider.prototype instanceof ErrorServiceProvider
2207
+ );
2208
+ if (!hasUserErrorProvider) {
2209
+ this.providerManager.register(ErrorServiceProvider);
2210
+ }
2211
+ if (userProviders.length > 0) {
2212
+ this.providerManager.registerMany(userProviders);
2213
+ }
2214
+ const hasUserInertiaProvider = userProviders.some(
2215
+ (provider) => provider === InertiaServiceProvider || provider.prototype instanceof InertiaServiceProvider
2216
+ );
2217
+ if (!hasUserInertiaProvider) {
2218
+ this.providerManager.register(InertiaServiceProvider);
1956
2219
  }
1957
2220
  }
2221
+ hono;
2222
+ container;
2223
+ router;
2224
+ providerManager;
2225
+ authManager;
2226
+ viteDevServer;
2227
+ bunServer;
2228
+ viteTeardownRegistered = false;
2229
+ bunTeardownRegistered = false;
2230
+ autoSessionAttached = false;
2231
+ routesRegistered = false;
2232
+ get auth() {
2233
+ return this.authManager;
2234
+ }
2235
+ get authOptions() {
2236
+ return this.options.auth;
2237
+ }
2238
+ markAutoSessionAttached() {
2239
+ this.autoSessionAttached = true;
2240
+ }
2241
+ hasAutoSessionAttached() {
2242
+ return this.autoSessionAttached;
2243
+ }
1958
2244
  /**
1959
- * Register a service provider.
2245
+ * Mounts the application router onto the Hono instance.
1960
2246
  */
1961
- register(provider) {
1962
- this.providerManager.register(provider);
1963
- return this;
2247
+ async mountRoutes() {
2248
+ if (this.options.routes && !this.routesRegistered) {
2249
+ await this.options.routes(this.router);
2250
+ this.routesRegistered = true;
2251
+ }
2252
+ this.router.mount(this.hono, { container: this.container });
1964
2253
  }
1965
2254
  /**
1966
- * Register multiple service providers.
2255
+ * Allows registering global middlewares directly on the underlying Hono app.
1967
2256
  */
1968
- registerMany(providers) {
1969
- this.providerManager.registerMany(providers);
1970
- return this;
2257
+ use(path, ...middleware) {
2258
+ this.hono.use(path, ...middleware);
1971
2259
  }
1972
2260
  /**
1973
- * Logs the rich development server banner to the console.
2261
+ * Executes provider registration, boot callback, mounts routes, and boots providers.
1974
2262
  */
1975
- logDevServerBanner(options) {
1976
- logDevServerBanner(options);
2263
+ async boot() {
2264
+ this.mountSecurityDefaults();
2265
+ await this.providerManager.registerAll();
2266
+ await this.options.boot?.(this.hono);
2267
+ await this.mountRoutes();
2268
+ await this.mountMcpEndpoint();
2269
+ await this.providerManager.bootAll();
1977
2270
  }
1978
- async closeViteDevServer() {
1979
- if (!this.viteDevServer) {
2271
+ /**
2272
+ * Registers default security middleware (headers + host authorization).
2273
+ */
2274
+ mountSecurityDefaults() {
2275
+ const { securityHeaders } = this.options;
2276
+ if (securityHeaders !== false) {
2277
+ this.hono.use("*", createSecurityHeaders(securityHeaders ?? {}));
2278
+ }
2279
+ this.mountHostAuthorization();
2280
+ }
2281
+ mountHostAuthorization() {
2282
+ const { hostAuthorization } = this.options;
2283
+ if (hostAuthorization === false || !hostAuthorization) return;
2284
+ this.hono.use("*", createHostAuthorizationMiddleware(hostAuthorization));
2285
+ }
2286
+ /**
2287
+ * Mounts the MCP endpoint at /_guren/mcp when explicitly enabled.
2288
+ * Requires GUREN_MCP=1 environment variable. Never active in production.
2289
+ * This allows AI coding agents to introspect the project.
2290
+ */
2291
+ async mountMcpEndpoint() {
2292
+ if (typeof process === "undefined" || process.env?.NODE_ENV === "production" || process.env?.GUREN_MCP !== "1") {
1980
2293
  return;
1981
2294
  }
1982
2295
  try {
1983
- await this.viteDevServer.close();
1984
- } catch (error) {
1985
- console.error("Error while shutting down Vite dev server:", error);
1986
- } finally {
1987
- if (getGlobalState().__gurenActiveViteDevServer === this.viteDevServer) {
1988
- setActiveViteDevServer();
1989
- }
1990
- this.viteDevServer = void 0;
1991
- this.viteTeardownRegistered = false;
1992
- clearManagedViteEnv();
2296
+ const { McpServiceProvider } = await import("./McpServiceProvider-JW6PDVMD.js");
2297
+ const provider = new McpServiceProvider(this.container);
2298
+ provider.register();
2299
+ await provider.boot();
2300
+ } catch {
1993
2301
  }
1994
2302
  }
1995
- registerViteTeardown() {
1996
- if (this.viteTeardownRegistered || !this.viteDevServer || typeof process === "undefined") {
1997
- return;
1998
- }
1999
- this.viteTeardownRegistered = true;
2000
- const exitHandler = () => {
2001
- this.closeViteDevServer().then(() => process.exit(0)).catch(() => process.exit(1));
2002
- };
2003
- process.once("SIGINT", exitHandler);
2004
- process.once("SIGTERM", exitHandler);
2005
- process.on("exit", () => {
2006
- if (this.viteDevServer) {
2007
- void this.viteDevServer.close();
2008
- }
2009
- });
2010
- }
2011
- registerBunTeardown() {
2012
- if (this.bunTeardownRegistered || typeof process === "undefined") {
2013
- return;
2014
- }
2015
- this.bunTeardownRegistered = true;
2016
- const exitHandler = () => {
2017
- stopActiveBunServer().then(() => process.exit(0)).catch(() => process.exit(1));
2018
- };
2019
- process.once("SIGINT", exitHandler);
2020
- process.once("SIGTERM", exitHandler);
2021
- process.on("exit", () => {
2022
- void stopActiveBunServer();
2023
- });
2024
- }
2025
- };
2026
- function createApp(options = {}) {
2027
- return new Application(options);
2028
- }
2029
-
2030
- // src/mvc/inertia/InertiaEngine.ts
2031
- import { isAbsolute, resolve as resolvePath } from "path";
2032
- import { pathToFileURL } from "url";
2033
- ensureErrorStackTracePolyfill();
2034
- var DEFAULT_TITLE = "Guren";
2035
- var DEFAULT_IMPORT_MAP = {
2036
- react: "https://esm.sh/react@19.0.0?dev",
2037
- "react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime?dev",
2038
- "react/jsx-dev-runtime": "https://esm.sh/react@19.0.0/jsx-dev-runtime?dev",
2039
- "react-dom/client": "https://esm.sh/react-dom@19.0.0/client?dev",
2040
- "@guren/inertia-client": "/vendor/inertia-client.tsx",
2041
- "@inertiajs/react": "https://esm.sh/@inertiajs/react@2.2.15?dev&external=react,react-dom/client"
2042
- };
2043
- async function inertia(component, props, options = {}) {
2044
- const resolvedVersion = options.version ?? process.env.GUREN_INERTIA_VERSION ?? void 0;
2045
- const page = {
2046
- component,
2047
- props,
2048
- url: options.url ?? "",
2049
- version: resolvedVersion
2050
- };
2051
- const request = options.request;
2052
- const isInertiaVisit = Boolean(request?.headers.get("X-Inertia"));
2053
- const prefersJson = request ? acceptsJson(request) : false;
2054
- const versionHeader = resolvedVersion ? { "X-Inertia-Version": resolvedVersion } : {};
2055
- if (request && resolvedVersion && isInertiaVisit && request.method === "GET") {
2056
- const clientVersion = request.headers.get("X-Inertia-Version");
2057
- if (clientVersion !== resolvedVersion) {
2058
- return new Response(null, {
2059
- status: 409,
2060
- headers: {
2061
- "X-Inertia-Location": options.url ?? request.url,
2062
- Vary: "Accept"
2063
- }
2064
- });
2065
- }
2066
- }
2067
- if (isInertiaVisit || prefersJson) {
2068
- return new Response(serializePage(page), {
2069
- status: options.status ?? 200,
2070
- headers: {
2071
- "Content-Type": "application/json; charset=utf-8",
2072
- "X-Inertia": "true",
2073
- Vary: "Accept",
2074
- ...versionHeader,
2075
- ...options.headers
2076
- }
2077
- });
2078
- }
2079
- const html = await renderDocument(page, options);
2080
- return new Response(html, {
2081
- status: options.status ?? 200,
2082
- headers: {
2083
- "Content-Type": "text/html; charset=utf-8",
2084
- "X-Inertia": "true",
2085
- Vary: "Accept",
2086
- ...versionHeader,
2087
- ...options.headers
2088
- }
2089
- });
2090
- }
2091
- async function renderDocument(page, options) {
2092
- const defaultEntry = process.env.GUREN_INERTIA_ENTRY ?? "/resources/js/app.tsx";
2093
- const entry = options.entry ?? defaultEntry;
2094
- const title = escapeHtml(options.title ?? DEFAULT_TITLE);
2095
- const styles = options.styles ?? parseStylesEnv(process.env.GUREN_INERTIA_STYLES);
2096
- const envImportMap = parseImportMap(process.env.GUREN_INERTIA_IMPORT_MAP, {
2097
- context: "GUREN_INERTIA_IMPORT_MAP"
2098
- });
2099
- const importMap = JSON.stringify(
2100
- {
2101
- imports: {
2102
- ...DEFAULT_IMPORT_MAP,
2103
- ...envImportMap,
2104
- ...options.importMap ?? {}
2105
- }
2106
- },
2107
- null,
2108
- 2
2109
- );
2110
- const serializedPage = serializePage(page);
2111
- const stylesheetLinks = renderStyles(styles);
2112
- const docsHeadBootstrap = renderDocsHeadBootstrap(page.component);
2113
- const ssrResult = await tryRenderSsr(page, options);
2114
- const headElements = (ssrResult?.head ?? []).map(normalizeHeadElement);
2115
- const hasCustomTitle = headElements.some(
2116
- (element) => /<title\b[^>]*>/iu.test(element)
2117
- );
2118
- const headSegments = [
2119
- '<meta charset="utf-8" />',
2120
- '<meta name="viewport" content="width=device-width, initial-scale=1" />',
2121
- hasCustomTitle ? "" : `<title>${title}</title>`,
2122
- docsHeadBootstrap,
2123
- stylesheetLinks,
2124
- ...headElements,
2125
- `<script type="importmap">${importMap}</script>`,
2126
- `<script>window.__INERTIA_PAGE__ = ${serializedPage};</script>`
2127
- ].filter((segment) => segment && segment.length > 0);
2128
- const appMarkup = ssrResult?.body ?? `<script data-page="app" type="application/json">${serializedPage}</script><div id="app"></div>`;
2129
- const bodyClass = resolveBodyClass(page.component);
2130
- const bodyAttributes = bodyClass ? ` class="${escapeAttribute(bodyClass)}"` : "";
2131
- return `<!DOCTYPE html>
2132
- <html lang="en">
2133
- <head>
2134
- ${headSegments.join("\n ")}
2135
- </head>
2136
- <body${bodyAttributes}>
2137
- ${appMarkup}
2138
- <script type="module" src="${entry}"></script>
2139
- </body>
2140
- </html>`;
2141
- }
2142
- async function tryRenderSsr(page, options) {
2143
- const ssrOptions = options.ssr;
2144
- if (ssrOptions?.enabled === false) {
2145
- return void 0;
2146
- }
2147
- const manifest = (ssrOptions?.manifest ?? process.env.GUREN_INERTIA_SSR_MANIFEST)?.trim() || void 0;
2148
- const renderer = ssrOptions?.render ?? await loadSsrRenderer(
2149
- ssrOptions?.entry ?? process.env.GUREN_INERTIA_SSR_ENTRY
2150
- );
2151
- if (!renderer) {
2152
- return void 0;
2153
- }
2154
- try {
2155
- const result = await renderer({
2156
- page,
2157
- request: options.request,
2158
- manifest
2159
- });
2160
- if (!result || typeof result.body !== "string") {
2161
- return void 0;
2162
- }
2163
- return {
2164
- body: result.body,
2165
- head: Array.isArray(result.head) ? result.head : []
2166
- };
2167
- } catch (error) {
2168
- console.error(
2169
- "Inertia SSR renderer failed; falling back to client rendering.",
2170
- error
2171
- );
2172
- return void 0;
2173
- }
2174
- }
2175
- async function loadSsrRenderer(entry) {
2176
- const specifier = entry?.trim();
2177
- if (!specifier) {
2178
- return void 0;
2303
+ /**
2304
+ * Fetch handler to integrate with Bun.serve or any standard Fetch runtime.
2305
+ */
2306
+ async fetch(request, env, executionCtx) {
2307
+ return this.hono.fetch(request, env, executionCtx);
2179
2308
  }
2180
- const normalized = normalizeSsrSpecifier(specifier);
2181
- try {
2182
- const module = await import(normalized);
2183
- const renderCandidate = extractSsrRenderer(module);
2184
- if (!renderCandidate) {
2185
- console.warn(
2186
- `Inertia SSR entry "${specifier}" does not export a renderer. Expected a default export or a named "render" function.`
2187
- );
2188
- return void 0;
2309
+ /**
2310
+ * Convenience helper to start a Bun server when available.
2311
+ */
2312
+ async listen(options = {}) {
2313
+ if (!Bun) {
2314
+ throw new Error("Bun runtime is required to call Application.listen");
2189
2315
  }
2190
- return renderCandidate;
2191
- } catch (error) {
2192
- console.error(`Failed to import Inertia SSR entry "${specifier}".`, error);
2193
- return void 0;
2194
- }
2195
- }
2196
- function extractSsrRenderer(module) {
2197
- if (!module || typeof module !== "object") {
2198
- return void 0;
2199
- }
2200
- const candidate = typeof module.render === "function" ? module.render : typeof module.default === "function" ? module.default : void 0;
2201
- if (!candidate) {
2202
- return void 0;
2203
- }
2204
- return (context) => Promise.resolve(candidate(context));
2205
- }
2206
- function normalizeSsrSpecifier(specifier) {
2207
- if (specifier.startsWith("file://") || isUrlLike(specifier)) {
2208
- return specifier;
2209
- }
2210
- if (specifier.startsWith("./") || specifier.startsWith("../")) {
2211
- const absolute = resolvePath(process.cwd(), specifier);
2212
- return pathToFileURL(absolute).href;
2213
- }
2214
- if (isAbsolute(specifier)) {
2215
- return pathToFileURL(specifier).href;
2216
- }
2217
- return specifier;
2218
- }
2219
- function isUrlLike(specifier) {
2220
- return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(specifier);
2221
- }
2222
- function renderStyles(styles) {
2223
- if (!styles.length) {
2224
- return "";
2225
- }
2226
- return styles.map((href) => `<link rel="stylesheet" href="${escapeAttribute(href)}" />`).join("\n ");
2227
- }
2228
- function normalizeHeadElement(element) {
2229
- const markup = typeof element === "string" ? element : String(element ?? "");
2230
- const pattern = /href="\/(?!public\/)([^"?]+\.(?:js|css))(\?[^"']*)?"/g;
2231
- return markup.replace(
2232
- pattern,
2233
- (_match, file2, query = "") => `href="/public/assets/${file2}${query}"`
2234
- );
2235
- }
2236
- function resolveBodyClass(componentName) {
2237
- return componentName.startsWith("Docs/") ? "docs-theme" : "";
2238
- }
2239
- function renderDocsHeadBootstrap(componentName) {
2240
- if (!componentName.startsWith("Docs/")) {
2241
- return "";
2242
- }
2243
- const criticalStyles = [
2244
- "html,body{background:#ffffff;color:#1f2937;}",
2245
- "body.docs-theme{background:#ffffff;color:#1f2937;}",
2246
- "html.dark,html.dark body,html.dark body.docs-theme{background:#1a1a2e;color:#e0def4;}",
2247
- ".size-4{width:1rem;height:1rem;}",
2248
- ".size-5{width:1.25rem;height:1.25rem;}",
2249
- ".size-6{width:1.5rem;height:1.5rem;}",
2250
- ".size-8{width:2rem;height:2rem;}",
2251
- ".size-9{width:2.25rem;height:2.25rem;}"
2252
- ].join("");
2253
- const prepaintScript = [
2254
- "(function(){",
2255
- "try{",
2256
- "var mode=localStorage.getItem('guren-color-mode')||'system';",
2257
- "var dark=mode==='dark'||(mode!=='light'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);",
2258
- "var root=document.documentElement;",
2259
- "root.classList.toggle('dark',dark);",
2260
- "root.style.colorScheme=dark?'dark':'light';",
2261
- "}catch(_){",
2262
- "}",
2263
- "})();"
2264
- ].join("");
2265
- return `<style id="guren-docs-critical">${criticalStyles}</style><script>${prepaintScript}</script>`;
2266
- }
2267
- function serializePage(page) {
2268
- return JSON.stringify(page).replace(/[<\u2028\u2029]/gu, (char) => {
2269
- switch (char) {
2270
- case "<":
2271
- return "\\u003c";
2272
- case "\u2028":
2273
- return "\\u2028";
2274
- case "\u2029":
2275
- return "\\u2029";
2276
- default:
2277
- return char;
2316
+ await stopActiveBunServer();
2317
+ await stopActiveViteDevServer();
2318
+ const { port = 3e3, hostname = "0.0.0.0", assetsUrl, vite } = options;
2319
+ const externalAssetsUrl = typeof process !== "undefined" && process.env?.[MANAGED_VITE_ENV_FLAG] !== "1" ? process.env?.VITE_DEV_SERVER_URL : void 0;
2320
+ let resolvedAssetsUrl = assetsUrl ?? externalAssetsUrl;
2321
+ const shouldStartVite = vite !== false && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !resolvedAssetsUrl && process.env?.GUREN_DEV_VITE !== "0";
2322
+ if (shouldStartVite) {
2323
+ const viteOptions = typeof vite === "object" ? vite : void 0;
2324
+ try {
2325
+ await this.closeViteDevServer();
2326
+ const { server: server2, localUrl } = await startViteDevServer({
2327
+ root: viteOptions?.root ?? process.cwd(),
2328
+ config: viteOptions?.config,
2329
+ host: viteOptions?.host ?? true,
2330
+ port: viteOptions?.port
2331
+ });
2332
+ this.viteDevServer = server2;
2333
+ setActiveViteDevServer(server2);
2334
+ resolvedAssetsUrl = localUrl;
2335
+ if (typeof process !== "undefined") {
2336
+ process.env.VITE_DEV_SERVER_URL = resolvedAssetsUrl;
2337
+ process.env[MANAGED_VITE_ENV_FLAG] = "1";
2338
+ }
2339
+ syncManagedInertiaDevEntry(resolvedAssetsUrl);
2340
+ this.registerViteTeardown();
2341
+ } catch (error) {
2342
+ console.error("Failed to start Vite dev server:", error);
2343
+ process.exit(1);
2344
+ }
2345
+ }
2346
+ const server = Bun.serve({
2347
+ port,
2348
+ hostname,
2349
+ fetch: (request) => this.fetch(request)
2350
+ });
2351
+ this.bunServer = server;
2352
+ setActiveBunServer(server);
2353
+ this.registerBunTeardown();
2354
+ const shouldLogBanner = typeof process === "undefined" || process.env?.NODE_ENV !== "production" && process.env?.GUREN_DEV_BANNER !== "0";
2355
+ if (shouldLogBanner) {
2356
+ this.logDevServerBanner({
2357
+ hostname,
2358
+ port,
2359
+ assetsUrl: resolvedAssetsUrl ?? "http://localhost:5173"
2360
+ });
2278
2361
  }
2279
- });
2280
- }
2281
- function acceptsJson(request) {
2282
- const accept = request.headers.get("accept")?.toLowerCase() ?? "";
2283
- if (!accept || accept === "*/*") {
2284
- return false;
2285
2362
  }
2286
- if (accept.includes("text/html")) {
2287
- return false;
2363
+ /**
2364
+ * Register a service provider.
2365
+ */
2366
+ register(provider) {
2367
+ this.providerManager.register(provider);
2368
+ return this;
2288
2369
  }
2289
- return accept.includes("application/json") || accept.includes("json");
2290
- }
2291
- function escapeHtml(value) {
2292
- return value.replace(/[&<>"']/gu, (char) => {
2293
- switch (char) {
2294
- case "&":
2295
- return "&amp;";
2296
- case "<":
2297
- return "&lt;";
2298
- case ">":
2299
- return "&gt;";
2300
- case '"':
2301
- return "&quot;";
2302
- case "'":
2303
- return "&#39;";
2304
- default:
2305
- return char;
2370
+ /**
2371
+ * Register multiple service providers.
2372
+ */
2373
+ registerMany(providers) {
2374
+ this.providerManager.registerMany(providers);
2375
+ return this;
2376
+ }
2377
+ /**
2378
+ * Logs the rich development server banner to the console.
2379
+ */
2380
+ logDevServerBanner(options) {
2381
+ logDevServerBanner(options);
2382
+ }
2383
+ async closeViteDevServer() {
2384
+ if (!this.viteDevServer) {
2385
+ return;
2306
2386
  }
2307
- });
2308
- }
2309
- function escapeAttribute(value) {
2310
- return value.replace(/[&"]/gu, (char) => {
2311
- if (char === "&") {
2312
- return "&amp;";
2387
+ try {
2388
+ await this.viteDevServer.close();
2389
+ } catch (error) {
2390
+ console.error("Error while shutting down Vite dev server:", error);
2391
+ } finally {
2392
+ if (getGlobalState().__gurenActiveViteDevServer === this.viteDevServer) {
2393
+ setActiveViteDevServer();
2394
+ }
2395
+ this.viteDevServer = void 0;
2396
+ this.viteTeardownRegistered = false;
2397
+ clearManagedViteEnv();
2313
2398
  }
2314
- return "&quot;";
2315
- });
2316
- }
2317
- function parseStylesEnv(value) {
2318
- if (!value) {
2319
- return [];
2320
2399
  }
2321
- return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2322
- }
2323
-
2324
- // src/mvc/inertia/shared.ts
2325
- var resolver = null;
2326
- function setInertiaSharedProps(resolverFn) {
2327
- resolver = resolverFn;
2328
- }
2329
- function getInertiaSharedPropsResolver() {
2330
- return resolver;
2331
- }
2332
- function shareInertiaProps(resolverFn) {
2333
- const previous = resolver;
2334
- resolver = async (ctx) => {
2335
- const prev = previous ? await previous(ctx) : {};
2336
- const next = await resolverFn(ctx);
2337
- return { ...prev, ...next };
2338
- };
2339
- }
2340
- async function resolveSharedInertiaProps(ctx) {
2341
- if (!resolver) return {};
2342
- const shared = await resolver(ctx);
2343
- if (shared && typeof shared === "object") {
2344
- return shared;
2400
+ registerViteTeardown() {
2401
+ if (this.viteTeardownRegistered || !this.viteDevServer || typeof process === "undefined") {
2402
+ return;
2403
+ }
2404
+ this.viteTeardownRegistered = true;
2405
+ const exitHandler = () => {
2406
+ this.closeViteDevServer().then(() => process.exit(0)).catch(() => process.exit(1));
2407
+ };
2408
+ process.once("SIGINT", exitHandler);
2409
+ process.once("SIGTERM", exitHandler);
2410
+ process.on("exit", () => {
2411
+ if (this.viteDevServer) {
2412
+ void this.viteDevServer.close();
2413
+ }
2414
+ });
2345
2415
  }
2346
- return {};
2416
+ registerBunTeardown() {
2417
+ if (this.bunTeardownRegistered || typeof process === "undefined") {
2418
+ return;
2419
+ }
2420
+ this.bunTeardownRegistered = true;
2421
+ const exitHandler = () => {
2422
+ stopActiveBunServer().then(() => process.exit(0)).catch(() => process.exit(1));
2423
+ };
2424
+ process.once("SIGINT", exitHandler);
2425
+ process.once("SIGTERM", exitHandler);
2426
+ process.on("exit", () => {
2427
+ void stopActiveBunServer();
2428
+ });
2429
+ }
2430
+ };
2431
+ function createApp(options = {}) {
2432
+ return new Application(options);
2347
2433
  }
2348
2434
 
2349
2435
  // src/mvc/Controller.ts
@@ -2518,6 +2604,7 @@ var Controller = class {
2518
2604
  const propsWithShared = { ...sharedProps, ...props };
2519
2605
  const response = await inertia(component, propsWithShared, {
2520
2606
  ...rest,
2607
+ lang: rest.lang ?? this.#defaultInertiaLang(),
2521
2608
  url: url2,
2522
2609
  request: ctx.req.raw
2523
2610
  });
@@ -2527,6 +2614,27 @@ var Controller = class {
2527
2614
  };
2528
2615
  return response;
2529
2616
  }
2617
+ /**
2618
+ * Default `<html lang>` for Inertia responses when `options.lang` is not
2619
+ * provided: the request-scoped `locale` context variable (set by locale
2620
+ * middleware) wins over the app-wide i18n locale.
2621
+ */
2622
+ #defaultInertiaLang() {
2623
+ const vars = this.ctx.var;
2624
+ const requestLocale = vars?.locale;
2625
+ if (typeof requestLocale === "string" && requestLocale.length > 0) {
2626
+ return requestLocale;
2627
+ }
2628
+ const container = vars?.container;
2629
+ if (container?.has?.("i18n")) {
2630
+ const i18n = container.make?.("i18n");
2631
+ const locale = i18n?.getLocale?.();
2632
+ if (typeof locale === "string" && locale.length > 0) {
2633
+ return locale;
2634
+ }
2635
+ }
2636
+ return void 0;
2637
+ }
2530
2638
  json(data, init = {}) {
2531
2639
  return new Response(JSON.stringify(data), {
2532
2640
  ...init,
@@ -2755,89 +2863,6 @@ var Controller = class {
2755
2863
  }
2756
2864
  };
2757
2865
 
2758
- // src/mvc/ViewEngine.ts
2759
- var ViewEngine = class {
2760
- static engines = /* @__PURE__ */ new Map();
2761
- static register(name, renderer) {
2762
- this.engines.set(name, renderer);
2763
- }
2764
- static has(name) {
2765
- return this.engines.has(name);
2766
- }
2767
- static render(name, template, props) {
2768
- const engine = this.engines.get(name);
2769
- if (!engine) {
2770
- throw new Error(`View engine "${name}" has not been registered.`);
2771
- }
2772
- return engine(template, props);
2773
- }
2774
- };
2775
-
2776
- // src/providers/InertiaServiceProvider.ts
2777
- var InertiaServiceProvider = class extends ServiceProvider {
2778
- register() {
2779
- if (!ViewEngine.has("inertia")) {
2780
- ViewEngine.register("inertia", inertia);
2781
- }
2782
- }
2783
- boot() {
2784
- this.registerValidationRenderer();
2785
- this.registerSharedErrors();
2786
- }
2787
- /**
2788
- * Register a custom renderer for ValidationException on Inertia requests.
2789
- * Non-Inertia requests fall through to the default JSON 422 response.
2790
- */
2791
- registerValidationRenderer() {
2792
- let handler;
2793
- try {
2794
- handler = this.container.make("exception.handler");
2795
- } catch {
2796
- return;
2797
- }
2798
- handler.render(ValidationException, (error, ctx) => {
2799
- const isInertia = ctx.req.header("X-Inertia");
2800
- if (!isInertia) {
2801
- return ctx.json(
2802
- {
2803
- message: error.message,
2804
- errors: error.errors
2805
- },
2806
- 422
2807
- );
2808
- }
2809
- const session = getSessionFromContext(ctx);
2810
- if (session) {
2811
- const flattened = {};
2812
- for (const [key, messages] of Object.entries(error.errors ?? {})) {
2813
- flattened[key] = messages[0] ?? "";
2814
- }
2815
- session.flash("errors", flattened);
2816
- }
2817
- const referer = ctx.req.header("Referer") ?? "/";
2818
- return new Response(null, {
2819
- status: 303,
2820
- headers: { Location: referer }
2821
- });
2822
- });
2823
- }
2824
- /**
2825
- * Wrap existing shared props resolver to auto-inject flashed `errors`.
2826
- */
2827
- registerSharedErrors() {
2828
- const previous = getInertiaSharedPropsResolver();
2829
- setInertiaSharedProps(async (ctx) => {
2830
- const prev = previous ? await previous(ctx) : {};
2831
- const session = getSessionFromContext(ctx);
2832
- const errors = session?.getFlash("errors");
2833
- return {
2834
- ...prev,
2835
- ...errors && Object.keys(errors).length > 0 ? { errors } : {}
2836
- };
2837
- });
2838
- }
2839
- };
2840
-
2841
2866
  // src/providers/OAuthServiceProvider.ts
2842
2867
  var OAuthServiceProvider = class extends ServiceProvider {
2843
2868
  register() {