// src/index.ts
import { existsSync as existsSync2 } from "node:fs";
import { dirname, resolve as resolve3 } from "node:path";
import { glob } from "tinyglobby";

// src/frameworks/react.ts
var config = {
  // Package name used to detect React usage via import statements
  package: "@inertiajs/react",
  // React components can use either .tsx (TypeScript) or .jsx
  // The plugin tries .tsx first, then falls back to .jsx
  extensions: [".tsx", ".jsx"],
  // React components are exported as `export default`, so we need to extract .default
  extractDefault: true,
  // SSR template that wraps the createInertiaApp call with server bootstrap code
  // Uses import.meta.env.PROD to skip the standalone server in dev mode
  ssr: (configureCall, options) => `
import createServer from '@inertiajs/react/server'
import { renderToString } from 'react-dom/server'

const render = await ${configureCall}

const renderPage = (page) => render(page, renderToString)

if (import.meta.env.PROD) {
  createServer(renderPage${options})
}

export default renderPage
`
};

// src/frameworks/svelte.ts
var config2 = {
  // Package name used to detect Svelte usage via import statements
  package: "@inertiajs/svelte",
  // Svelte components use .svelte extension
  extensions: [".svelte"],
  // Svelte components ARE the module - no .default extraction needed
  // This is different from Vue/React where components use `export default`
  extractDefault: false,
  // SSR template that wraps the createInertiaApp call with server bootstrap code
  // Uses import.meta.env.PROD to skip the standalone server in dev mode
  // Note: Svelte uses a different variable name (ssr) and render function import
  ssr: (configureCall, options) => `
import createServer from '@inertiajs/svelte/server'
import { render } from 'svelte/server'

const ssr = await ${configureCall}

const renderPage = (page) => ssr(page, render)

if (import.meta.env.PROD) {
  createServer(renderPage${options})
}

export default renderPage
`
};

// src/frameworks/vue.ts
var config3 = {
  // Package name used to detect Vue usage via import statements
  package: "@inertiajs/vue3",
  // Vue single-file components use .vue extension
  extensions: [".vue"],
  // Vue components are exported as `export default`, so we need to extract .default
  extractDefault: true,
  // SSR template that wraps the createInertiaApp call with server bootstrap code
  // Uses import.meta.env.PROD to skip the standalone server in dev mode
  ssr: (configureCall, options) => `
import createServer from '@inertiajs/vue3/server'
import { renderToString } from 'vue/server-renderer'

const render = await ${configureCall}

const renderPage = (page) => render(page, renderToString)

if (import.meta.env.PROD) {
  createServer(renderPage${options})
}

export default renderPage
`
};

// src/frameworks/index.ts
var frameworks = [config3, config, config2];
var defaultFrameworks = Object.fromEntries(
  frameworks.map((config4) => [config4.package, config4])
);

// src/astUtils.ts
import { parseAst } from "vite";
var INERTIA_APP_FUNCTION = "createInertiaApp";
var CREATE_SERVER_FUNCTION = "createServer";
var ParsedCode = class _ParsedCode {
  constructor(ast) {
    this.ast = ast;
  }
  ast;
  /** Returns null if parsing fails (e.g. non-JS file content). */
  static from(code) {
    try {
      return new _ParsedCode(parseAst(code));
    } catch {
      return null;
    }
  }
  get importSources() {
    const sources = [];
    for (const node of this.ast.body) {
      if (node.type === "ImportDeclaration") {
        sources.push(node.source.value);
      }
    }
    return sources;
  }
  detectFramework(frameworks2) {
    const name = this.importSources.find((source) => source in frameworks2);
    return name ? { name, config: frameworks2[name] } : null;
  }
  /**
   * Find a top-level `createInertiaApp()` expression statement, ignoring any
   * `await`, `void`, or `.then()`/`.catch()` wrappers around it.
   *
   * Does not match `export default createInertiaApp()`.
   */
  get inertiaStatement() {
    for (const node of this.ast.body) {
      if (node.type !== "ExpressionStatement") {
        continue;
      }
      const call = this.unwrapInertiaCall(node.expression);
      if (call) {
        const statement = node;
        return { statement, call };
      }
    }
    return null;
  }
  /**
   * Unwrap an expression to find a `createInertiaApp()` call inside it.
   * Strips away `await`, `void`, and `.then()`/`.catch()` chains.
   */
  unwrapInertiaCall(node) {
    if ((node.type === "UnaryExpression" || node.type === "AwaitExpression") && node.argument) {
      return this.unwrapInertiaCall(node.argument);
    }
    if (node.type === "CallExpression") {
      if (this.isInertiaCall(node)) {
        return node;
      }
      if (node.callee.type === "MemberExpression") {
        return this.unwrapInertiaCall(node.callee.object);
      }
    }
    return null;
  }
  /** Find a top-level `createServer()` call (not nested in exports). */
  get createServerStatement() {
    for (const node of this.ast.body) {
      if (node.type !== "ExpressionStatement") {
        continue;
      }
      const statement = node;
      const expr = statement.expression;
      if (expr.type === "CallExpression") {
        const call = expr;
        if (call.callee.type === "Identifier" && call.callee.name === CREATE_SERVER_FUNCTION) {
          return statement;
        }
      }
    }
    return null;
  }
  /**
   * Find all `createInertiaApp()` calls, including those nested inside exports.
   */
  get inertiaCalls() {
    const calls = [];
    this.walkAst(this.ast, (node) => {
      if (node.type === "CallExpression" && this.isInertiaCall(node)) {
        calls.push(node);
      }
    });
    return calls;
  }
  get pagesProperty() {
    for (const call of this.inertiaCalls) {
      if (call.arguments.length === 0 || call.arguments[0].type !== "ObjectExpression") {
        continue;
      }
      for (const prop of call.arguments[0].properties) {
        if (prop.type !== "Property" || prop.key.type !== "Identifier" || prop.key.name !== "pages") {
          continue;
        }
        return prop;
      }
    }
    return null;
  }
  /** Find a `createInertiaApp()` call that has no `pages` or `resolve` property yet. */
  get callWithoutResolver() {
    for (const call of this.inertiaCalls) {
      const callWithPos = call;
      if (call.arguments.length === 0) {
        return { callEnd: callWithPos.end };
      }
      if (call.arguments[0].type !== "ObjectExpression") {
        continue;
      }
      const obj = call.arguments[0];
      const hasResolver = obj.properties.some(
        (p) => p.type === "Property" && p.key.type === "Identifier" && (p.key.name === "pages" || p.key.name === "resolve")
      );
      if (hasResolver) {
        continue;
      }
      return {
        callEnd: callWithPos.end,
        options: { start: obj.start, end: obj.end, isEmpty: obj.properties.length === 0 }
      };
    }
    return null;
  }
  isInertiaCall(node) {
    return node.callee.type === "Identifier" && node.callee.name === INERTIA_APP_FUNCTION;
  }
  walkAst(node, callback) {
    if (!node || typeof node !== "object") {
      return;
    }
    if ("type" in node) {
      callback(node);
    }
    for (const value of Object.values(node)) {
      if (Array.isArray(value)) {
        for (const item of value) {
          this.walkAst(item, callback);
        }
      } else {
        this.walkAst(value, callback);
      }
    }
  }
};
function extractString(node) {
  if (node.type === "Literal" && typeof node.value === "string") {
    return node.value;
  }
  if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
    return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
  }
  return void 0;
}
function extractStringArray(node) {
  if (node.type !== "ArrayExpression") {
    return void 0;
  }
  const strings = node.elements.map((el) => el ? extractString(el) : void 0).filter((s) => s !== void 0);
  return strings.length > 0 ? strings : void 0;
}
function extractBoolean(node) {
  if (node.type === "Literal" && typeof node.value === "boolean") {
    return node.value;
  }
  return void 0;
}

// src/pagesTransform.ts
function transformPageResolution(code, frameworks2) {
  if (!code.includes("InertiaApp")) {
    return null;
  }
  const parsed = ParsedCode.from(code);
  if (!parsed) {
    return null;
  }
  const framework = parsed.detectFramework(frameworks2);
  if (!framework) {
    return null;
  }
  const extensions = framework.config.extensions;
  const extractDefault = framework.config.extractDefault ?? true;
  if (parsed.pagesProperty) {
    const result = replacePages(code, parsed.pagesProperty, extensions, extractDefault);
    return result ? { code: result.code, pageGlobs: result.globs } : null;
  }
  if (parsed.callWithoutResolver) {
    const defaultGlobs = buildDefaultGlobs(extensions);
    return {
      code: injectResolver(code, parsed.callWithoutResolver, extensions, extractDefault),
      pageGlobs: defaultGlobs
    };
  }
  return null;
}
function replacePages(code, property, defaultExtensions, extractDefault) {
  const config4 = extractPagesConfig(property.value, code);
  if (!config4) {
    return null;
  }
  const extensions = config4.extensions ? Array.isArray(config4.extensions) ? config4.extensions : [config4.extensions] : defaultExtensions;
  const eager = !(config4.lazy ?? true);
  const directories = config4.directory ? [config4.directory.replace(/\/$/, "")] : ["./pages", "./Pages"];
  const resolver = buildResolver(directories, extensions, extractDefault, eager, config4.transform);
  const globs = directories.map((d) => buildGlob(d, extensions));
  return {
    code: code.slice(0, property.start) + resolver + code.slice(property.end),
    globs
  };
}
function injectResolver(code, call, extensions, extractDefault) {
  const resolver = buildDefaultResolver(extensions, extractDefault);
  if (!call.options) {
    return code.slice(0, call.callEnd - 1) + `{ ${resolver} })` + code.slice(call.callEnd);
  }
  if (call.options.isEmpty) {
    return code.slice(0, call.options.start) + `{ ${resolver} }` + code.slice(call.options.end);
  }
  return code.slice(0, call.options.start + 1) + ` ${resolver},` + code.slice(call.options.start + 1);
}
function extractPagesConfig(node, code) {
  const str = extractString(node);
  if (str) {
    return { directory: str };
  }
  if (node.type !== "ObjectExpression") {
    return null;
  }
  let directory;
  let extensions;
  let transform;
  let lazy;
  for (const prop of node.properties) {
    if (prop.type !== "Property" || prop.key.type !== "Identifier") {
      continue;
    }
    const key = prop.key.name;
    const value = prop.value;
    if (key === "path") {
      directory = extractString(value);
    } else if (key === "extension") {
      extensions = extractString(value) ?? extractStringArray(value);
    } else if (key === "transform") {
      transform = code.slice(value.start, value.end);
    } else if (key === "lazy") {
      lazy = extractBoolean(value);
    }
  }
  return { directory, extensions, transform, lazy };
}
function buildResolver(directories, extensions, extractDefault, eager, transform) {
  const dirs = Array.isArray(directories) ? directories : [directories];
  const globs = dirs.map((d) => buildGlob(d, extensions));
  const glob2 = globs.length === 1 ? `'${globs[0]}'` : `['${globs.join("', '")}']`;
  const nameVar = transform ? "resolvedName" : "name";
  const lookup = dirs.flatMap((d) => extensions.map((ext) => `pages[\`${d}/\${${nameVar}}${ext}\`]`)).join(" || ");
  const transformLine = transform ? `const resolvedName = (${transform})(name, page)
    ` : "";
  const returnValue = extractDefault ? "module.default ?? module" : "module";
  const globOptions = `, { eager: ${eager} }`;
  const moduleLookup = eager ? lookup : `await (${lookup})?.()`;
  return `resolve: async (name, page) => {
    ${transformLine}const pages = import.meta.glob(${glob2}${globOptions})
    const module = ${moduleLookup}
    if (!module) throw new Error(\`Page not found: \${name}\`)
    return ${returnValue}
  }`;
}
var DEFAULT_PAGE_DIRECTORIES = ["./pages", "./Pages"];
function buildDefaultResolver(extensions, extractDefault, eager = false) {
  return buildResolver(DEFAULT_PAGE_DIRECTORIES, extensions, extractDefault, eager);
}
function buildDefaultGlobs(extensions) {
  return DEFAULT_PAGE_DIRECTORIES.map((d) => buildGlob(d, extensions));
}
function buildGlob(directory, extensions) {
  const ext = extensions.length === 1 ? extensions[0] : `{${extensions.join(",")}}`;
  return `${directory}/**/*${ext}`;
}

// src/ssr.ts
import { existsSync } from "node:fs";
import { resolve as resolve2 } from "node:path";
import { classifySSRError, formatConsoleError } from "@inertiajs/core/ssrErrors";

// src/css.ts
import { resolve } from "node:path";
function collectCSSFromModuleGraph(server, entry) {
  const entryModule = resolveEntryModule(server, entry);
  if (!entryModule) {
    return [];
  }
  const cssModules = collectCSSModules(entryModule);
  if (cssModules.length === 0) {
    return [];
  }
  const origin = resolveDevServerOrigin(server);
  const base = server.config.base || "/";
  const basePrefix = base === "/" ? "" : base.replace(/\/$/, "");
  return cssModules.map(({ url, id }) => {
    const href = `${origin}${basePrefix}${url}`;
    const devId = id ? ` data-vite-dev-id="${id}"` : "";
    return `<link rel="stylesheet" href="${href}"${devId}>`;
  });
}
function resolveEntryModule(server, entry) {
  const resolvedEntry = resolve(server.config.root, entry);
  return server.environments.ssr.moduleGraph.getModuleById(resolvedEntry);
}
function collectCSSModules(entryModule) {
  const cssModules = [];
  const visited = /* @__PURE__ */ new Set();
  function traverse(mod) {
    if (visited.has(mod)) {
      return;
    }
    visited.add(mod);
    if (isCSSRequest(mod.url)) {
      cssModules.push({ url: mod.url, id: mod.id });
      return;
    }
    for (const imported of mod.importedModules) {
      traverse(imported);
    }
  }
  traverse(entryModule);
  return cssModules;
}
var CSS_EXTENSIONS = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
function isCSSRequest(url) {
  return CSS_EXTENSIONS.test(url);
}
function resolveDevServerOrigin(server) {
  const url = server.resolvedUrls?.local[0] ?? server.resolvedUrls?.network[0];
  if (url) {
    return new URL(url).origin;
  }
  const protocol = server.config.server.https ? "https" : "http";
  const port = server.config.server.port ?? 5173;
  return `${protocol}://localhost:${port}`;
}

// src/ssr.ts
var SSR_ENDPOINT = "/__inertia_ssr";
var SSR_ENTRY_CANDIDATES = [
  "resources/js/ssr.ts",
  "resources/js/ssr.tsx",
  "resources/js/ssr.js",
  "resources/js/ssr.jsx",
  "src/ssr.ts",
  "src/ssr.tsx",
  "src/ssr.js",
  "src/ssr.jsx",
  "resources/js/app.ts",
  "resources/js/app.tsx",
  "resources/js/app.js",
  "resources/js/app.jsx",
  "src/app.ts",
  "src/app.tsx",
  "src/app.js",
  "src/app.jsx"
];
function resolveSSREntry(options, config4) {
  if (options.entry) {
    if (existsSync(resolve2(config4.root, options.entry))) {
      return options.entry;
    }
    config4.logger.warn(`Inertia SSR entry not found: ${options.entry}`);
    return null;
  }
  return SSR_ENTRY_CANDIDATES.find((path) => existsSync(resolve2(config4.root, path))) ?? null;
}
async function handleSSRRequest(server, entry, req, res, formatErrors = true) {
  let component;
  let url;
  const originalWarn = console.warn;
  const suppressedWarnings = [];
  console.warn = (...args) => {
    const message = args[0]?.toString() ?? "";
    if (message.includes("[Vue warn]") || message.includes("at <")) {
      if (formatErrors) {
        suppressedWarnings.push(args.map(String).join(" "));
      }
      return;
    }
    originalWarn.apply(console, args);
  };
  try {
    const page = await readRequestBody(req);
    component = page.component;
    url = page.url;
    const start = performance.now();
    const render = await loadRenderFunction(server, entry);
    const result = await render(page);
    if (!result || typeof result.body !== "string") {
      throw new Error(`SSR render must return { head: string[], body: string }`);
    }
    const cssLinks = collectCSSFromModuleGraph(server, entry);
    result.head = [...cssLinks, ...result.head];
    logSSRRequest(server, page.component, start);
    res.setHeader("Content-Type", "application/json");
    res.end(JSON.stringify(result));
  } catch (error) {
    handleSSRError(server, res, error, component, url, formatErrors, suppressedWarnings);
  } finally {
    console.warn = originalWarn;
  }
}
async function loadRenderFunction(server, entry) {
  const module = await server.ssrLoadModule(entry);
  const render = await Promise.resolve(module.default);
  if (typeof render !== "function") {
    throw new Error(`SSR entry "${entry}" must export a render function`);
  }
  return render;
}
function readRequestBody(req) {
  return new Promise((resolve4, reject) => {
    let data = "";
    req.on("data", (chunk) => data += chunk);
    req.on("end", () => {
      if (!data.trim()) {
        reject(new Error("Request body is empty"));
        return;
      }
      try {
        resolve4(JSON.parse(data));
      } catch (error) {
        reject(new Error(`Invalid JSON in request body: ${error instanceof Error ? error.message : "Unknown error"}`));
      }
    });
    req.on("error", reject);
  });
}
function logSSRRequest(server, component, start) {
  const duration = (performance.now() - start).toFixed(2);
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
  server.config.logger.info(`\x1B[32m${timestamp}\x1B[0m SSR ${component} \x1B[90m${duration}ms\x1B[0m`);
}
function handleSSRError(server, res, error, component, url, formatErrors = true, suppressedWarnings = []) {
  server.ssrFixStacktrace(error);
  if (!formatErrors) {
    throw error;
  }
  const classified = classifySSRError(error, component, url);
  server.config.logger.error(formatConsoleError(classified, server.config.root, formatErrors, suppressedWarnings));
  res.setHeader("Content-Type", "application/json");
  res.statusCode = 500;
  res.end(JSON.stringify(classified));
}

// src/ssrTransform.ts
function formatSSROptions(options) {
  const entries = Object.entries(options).filter(([, v]) => v !== void 0);
  return entries.length > 0 ? `, ${JSON.stringify(Object.fromEntries(entries))}` : "";
}
function findInertiaAppExport(code) {
  const parsed = ParsedCode.from(code);
  return !!(parsed?.inertiaStatement || parsed?.createServerStatement);
}
function wrapWithServerBootstrap(code, options, frameworks2) {
  const parsed = ParsedCode.from(code);
  if (!parsed) {
    return null;
  }
  if (parsed.inertiaStatement) {
    const framework = parsed.detectFramework(frameworks2);
    if (!framework?.config.ssr) {
      return null;
    }
    const { statement, call } = parsed.inertiaStatement;
    const configureCall = code.slice(call.start, call.end);
    const ssrCode = framework.config.ssr(configureCall, formatSSROptions(options)).trim();
    return code.slice(0, statement.start) + ssrCode + code.slice(statement.end);
  }
  if (parsed.createServerStatement) {
    const statement = parsed.createServerStatement;
    const args = statement.expression.arguments;
    const callback = code.slice(args[0].start, args[0].end);
    const trailingArgs = code.slice(args[0].end, args[args.length - 1].end);
    const replacement = `const renderPage = ${callback}

if (import.meta.env.PROD) {
  createServer(renderPage${trailingArgs})
}

export default renderPage`;
    return code.slice(0, statement.start) + replacement + code.slice(statement.end);
  }
  return null;
}

// src/index.ts
function toFrameworkRecord(input) {
  if (!input) {
    return {};
  }
  const configs = Array.isArray(input) ? input : [input];
  return Object.fromEntries(configs.map((config4) => [config4.package, config4]));
}
async function warmupPageFiles(server, sourceFile, pageGlobs) {
  const files = await glob(pageGlobs, { cwd: dirname(sourceFile), absolute: true });
  files.forEach((file) => server.warmupRequest(file));
}
function inertia(options = {}) {
  const ssrDisabled = options.ssr === false;
  const ssr = typeof options.ssr === "string" ? { entry: options.ssr } : options.ssr || {};
  const frameworks2 = { ...defaultFrameworks, ...toFrameworkRecord(options.frameworks) };
  let entry = null;
  let devServer = null;
  return {
    name: "@inertiajs/vite",
    config(config4, env) {
      if (ssrDisabled || !env.isSsrBuild) {
        return;
      }
      const root = config4.root ?? process.cwd();
      const ssrEntry = ssr.entry ?? SSR_ENTRY_CANDIDATES.find((candidate) => existsSync2(resolve3(root, candidate)));
      return {
        build: {
          sourcemap: ssr.sourcemap !== false ? config4.build?.sourcemap ?? true : void 0,
          rollupOptions: ssrEntry ? {
            input: config4.build?.rollupOptions?.input ?? config4.build?.rolldownOptions?.input ?? ssrEntry
          } : void 0
        }
      };
    },
    configResolved(config4) {
      if (ssrDisabled) {
        return;
      }
      entry = resolveSSREntry(ssr, config4);
      if (entry && config4.build?.ssr) {
        config4.logger.info(`Inertia SSR entry: ${entry}`);
      }
    },
    transform(code, id, options2) {
      if (!/\.[jt]sx?$/.test(id)) {
        return null;
      }
      let result = code;
      if (!ssrDisabled && options2?.ssr && findInertiaAppExport(result)) {
        result = wrapWithServerBootstrap(
          result,
          { port: ssr.port, host: ssr.host, cluster: ssr.cluster, formatErrors: ssr.formatErrors },
          frameworks2
        ) ?? result;
      }
      const pageTransform = transformPageResolution(result, frameworks2);
      if (pageTransform) {
        if (devServer && pageTransform.pageGlobs.length > 0) {
          warmupPageFiles(devServer, id, pageTransform.pageGlobs).catch(() => {
          });
        }
        return pageTransform.code;
      }
      return result !== code ? result : null;
    },
    configureServer(server) {
      devServer = server;
      if (!entry) {
        return;
      }
      let warmingUp = false;
      server.middlewares.use(SSR_ENDPOINT, async (req, res, next) => {
        if (req.method !== "POST") {
          return next();
        }
        if (warmingUp) {
          server.config.logger.info("SSR skipped, module graph is still warming up...");
          res.setHeader("Content-Type", "application/json");
          res.end(JSON.stringify(null));
          return;
        }
        await handleSSRRequest(server, entry, req, res, ssr.formatErrors ?? true);
      });
      server.config.logger.info(`Inertia SSR dev endpoint: ${SSR_ENDPOINT}`);
      server.httpServer?.once("listening", () => {
        warmingUp = true;
        server.config.logger.info("Warming up Inertia SSR module graph...");
        server.ssrLoadModule(entry).then(() => server.config.logger.info("Inertia SSR module graph warmed up")).catch(
          (error) => server.config.logger.warn(`Failed to warm up Inertia SSR module graph: ${error?.message ?? error}`)
        ).finally(() => {
          warmingUp = false;
        });
      });
    }
  };
}
export {
  inertia as default
};
//# sourceMappingURL=index.js.map
