{"version":3,"file":"interpolation-plugin-BUHP0lpT.cjs","names":[],"sources":["../../src/island/generate-entry.ts","../../src/middleware/index.ts","../../src/vite/interpolation-plugin.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, relative, sep } from \"node:path\";\nimport type { IslandModule } from \"./scan.js\";\n\n// --- Client entry generator ---\n//\n// Turns a list of scanned islands into a client entry module that imports each\n// island and registers it with `hydrateIslands`. This removes the need to hand-\n// maintain `entry-client.ts` as islands are added or removed.\n//\n// The generated file imports island default exports and passes them to\n// `hydrateIslands` keyed by their registry name.\n\n/** Options for generating the client entry module. */\nexport interface GenerateEntryOptions {\n  /** Islands to register, from `scanIslands`. */\n  islands: IslandModule[];\n  /** Absolute path of the entry file to write (e.g. \".nix-js/entry-client.ts\"). */\n  outFile: string;\n  /**\n   * Import specifier for the kit's client island helpers.\n   * Defaults to the published subpath `@deijose/nix-js-kit/island`.\n   */\n  hydrateImport?: string;\n  /**\n   * Import specifier for the kit's client router.\n   * Defaults to the published subpath `@deijose/nix-js-kit/router`.\n   */\n  routerImport?: string;\n}\n\n/** Turns a registry name into a safe JS identifier for the import binding. */\nfunction toIdentifier(name: string, index: number): string {\n  const cleaned = name.replace(/[^a-zA-Z0-9_$]/g, \"_\");\n  return /^[a-zA-Z_$]/.test(cleaned) ? `${cleaned}_${index}` : `_${cleaned}_${index}`;\n}\n\n/** Builds the source code of the client entry module. */\nexport function buildEntrySource(\n  islands: IslandModule[],\n  outFile: string,\n  hydrateImport = \"@deijose/nix-js-kit/island\",\n  routerImport = \"@deijose/nix-js-kit/router\",\n): string {\n  const bindings = islands.map((island, i) => ({\n    ident: toIdentifier(island.name, i),\n    name: island.name,\n    // Relative import specifier from the entry file to the island module.\n    spec: toImportSpecifier(outFile, island.filePath),\n  }));\n\n  // Lazy registry: each island is loaded on-demand via dynamic import().\n  // This enables code-splitting — islands not on the current page (or not yet\n  // triggered by their directive) stay out of the initial bundle.\n  //\n  // The registry maps island name → discriminated lazy loader `{ load }`.\n  // hydrateIslands() awaits `entry.load()` before hydrating, so the first\n  // paint only needs the small entry chunk + the islands on the page. The\n  // discriminated form lets the hydrator tell eager components from lazy\n  // loaders without executing a probe.\n  const registryLines = bindings\n    .map((b) => `  ${JSON.stringify(b.name)}: { load: () => import(${JSON.stringify(b.spec)}).then(m => m.default) },`)\n    .join(\"\\n\");\n\n  const islandHydration = registryLines\n    ? `const registry = {\n${registryLines}\n};\nhydrateIslands(registry);\ndocument.addEventListener(\"nix-js:rendered\", () => {\n  cleanupHydratedIslands();\n  hydrateIslands(registry);\n});\n\n// Vite HMR: when an island module (or the entry itself) updates, dispose the\n// current islands and re-hydrate from the updated modules — the registry's\n// dynamic import() resolves to the fresh modules, so no full page reload is\n// needed (progressive enhancement, audit §10.2 / §12.2).\nif (import.meta.hot) {\n  import.meta.hot.accept((newModule) => {\n    cleanupHydratedIslands();\n    hydrateIslands(registry);\n    if (newModule) {\n      // Re-run the module so its side effects (router, listeners) apply.\n    }\n  });\n}`\n    : \"\";\n\n  return `// AUTO-GENERATED by @deijose/nix-js-kit. Do not edit.\nimport { startClientRouter } from ${JSON.stringify(routerImport)};\nimport { hydrateIslands, cleanupHydratedIslands } from ${JSON.stringify(hydrateImport)};\n\nstartClientRouter();\n${islandHydration}\n`;\n}\n\n/** Computes a POSIX-style relative import specifier between two files. */\nfunction toImportSpecifier(fromFile: string, toFile: string): string {\n  let spec = relative(dirname(fromFile), toFile).split(sep).join(\"/\");\n  if (!spec.startsWith(\".\")) spec = `./${spec}`;\n  return spec;\n}\n\n/**\n * Generates and writes the client entry module for the given islands.\n *\n * @param options Generation options.\n * @returns The absolute path of the written entry file.\n */\nexport async function generateClientEntry(\n  options: GenerateEntryOptions,\n): Promise<string> {\n  const source = buildEntrySource(\n    options.islands,\n    options.outFile,\n    options.hydrateImport,\n    options.routerImport,\n  );\n  await mkdir(dirname(options.outFile), { recursive: true });\n  await writeFile(options.outFile, source, \"utf8\");\n  return options.outFile;\n}\n","// --- Middleware ---\n//\n// Convention: `src/middleware.ts` in the project root exports a default\n// function and an optional `config` with a `matcher` array.\n//\n//   import type { Middleware } from \"@deijose/nix-js-kit\";\n//\n//   export default function middleware(request: Request) {\n//     if (!request.headers.get(\"Cookie\")?.includes(\"session=\")) {\n//       return Response.redirect(new URL(\"/login\", request.url), 307);\n//     }\n//   }\n//\n//   export const config = { matcher: [\"/dashboard/:path*\", \"/admin/:path*\"] };\n//\n// The middleware runs before routing. Return a `Response` to short-circuit\n// (redirect, rewrite, 401, etc.). Return `undefined` or nothing to continue.\n// Use `next()` to pass headers to the loader.\n\nimport { matchRoute } from \"../ssr/match.js\";\nimport type { PageRoute } from \"../router/route-scanner.js\";\n\n/** The middleware function signature. */\nexport type Middleware = (request: Request, context: MiddlewareContext) =>\n  | Response\n  | void\n  | Promise<Response | void>;\n\n/** Context passed to the middleware function. */\nexport interface MiddlewareContext {\n  /** Helper to continue to the next handler. Can attach headers, params, and locals. */\n  next(options?: {\n    headers?: Record<string, string>;\n    params?: Record<string, string | string[]>;\n    locals?: Record<string, unknown>;\n  }): void;\n  /** Matched route params (only available if the path matches a page route). */\n  params?: Record<string, string | string[]>;\n  /** Per-request locals (populated by middleware, available to loaders/actions). */\n  locals?: Record<string, unknown>;\n}\n\n/** Configuration for the middleware module. */\nexport interface MiddlewareConfig {\n  /** Path patterns that trigger the middleware. Supports `:param` and `:param*`. */\n  matcher?: string[];\n}\n\nexport interface LoadedMiddleware {\n  handler: Middleware;\n  config: MiddlewareConfig;\n}\n\n/** Result of running middleware: either a response to short-circuit with, or continue. */\nexport type MiddlewareResult =\n  | { kind: \"response\"; response: Response }\n  | {\n    kind: \"continue\";\n    headers?: Record<string, string>;\n    params?: Record<string, string | string[]>;\n    locals?: Record<string, unknown>;\n  };\n\n/**\n * Loads the user's `src/middleware.ts` module. Returns `null` if no middleware\n * file exists. Distinguishes \"file not found\" from \"file has errors\" (§6):\n * an import error is not silently treated as \"no middleware\".\n */\nexport async function loadMiddleware(root: string): Promise<LoadedMiddleware | null> {\n  const candidates = [\n    `${root}/src/middleware.ts`,\n    `${root}/middleware.ts`,\n  ];\n\n  for (const path of candidates) {\n    try {\n      const mod = await import(path);\n      const handler = (mod.default ?? mod.middleware) as Middleware | undefined;\n      if (typeof handler !== \"function\") continue;\n      const config = (mod.config ?? {}) as MiddlewareConfig;\n      return { handler, config };\n    } catch (err) {\n      // Distinguish \"module not found\" from actual errors.\n      // If the error is a module resolution error for this specific file,\n      // it means the file doesn't exist — try the next candidate.\n      // If it's a syntax/runtime error, rethrow so the user sees it.\n      // Note: Bun's ResolveMessage is not `instanceof Error`, so match on the\n      // message property instead of relying on the class hierarchy.\n      const msg =\n        typeof err === \"object\" && err !== null && \"message\" in err\n          ? String((err as { message: unknown }).message)\n          : String(err);\n      if (\n        msg.includes(\"Cannot find module\") ||\n        msg.includes(\"Cannot find package\") ||\n        msg.includes(\"ENOENT\") ||\n        msg.includes(\"Module not found\")\n      ) {\n        // File doesn't exist — try next candidate.\n        continue;\n      }\n      // Actual error in the middleware file — rethrow (§6).\n      throw new Error(`[nix-js-kit] Error loading middleware: ${msg}`, { cause: err });\n    }\n  }\n\n  return null;\n}\n\n/**\n * Checks if a pathname matches any of the middleware's matcher patterns.\n * If no matcher is configured, the middleware runs for every request.\n *\n * Catch-all patterns (`:param*`) match both the base path and any sub-paths,\n * e.g. `/dashboard/:path*` matches `/dashboard` and `/dashboard/settings/users`.\n */\nexport function matchesMiddleware(pathname: string, config: MiddlewareConfig): boolean {\n  if (!config.matcher || config.matcher.length === 0) return true;\n\n  const cleanPath = pathname.split(\"?\")[0];\n\n  for (const pattern of config.matcher) {\n    // Exact match.\n    if (pattern === cleanPath) return true;\n\n    // Check for catch-all: `/foo/:bar*` should also match `/foo`.\n    const catchAllMatch = pattern.match(/^(.*)\\/:[\\w]+\\*$/);\n    if (catchAllMatch) {\n      const base = catchAllMatch[1];\n      if (cleanPath === base) return true;\n    }\n\n    // Use matchRoute for param matching.\n    const pseudoRoutes: PageRoute[] = [{\n      path: pattern,\n      pagePath: \"\",\n      params: [],\n      layouts: [],\n    }];\n    if (matchRoute(cleanPath, pseudoRoutes)) return true;\n  }\n\n  return false;\n}\n\n/**\n * Runs the middleware for a request. Returns the result indicating whether to\n * short-circuit with a response or continue with propagated headers/params/locals.\n *\n * Per §6: cleanup runs in `finally`, response short-circuits the pipeline,\n * headers/params/locals are propagated to downstream handlers.\n */\nexport async function runMiddleware(\n  middleware: LoadedMiddleware,\n  request: Request,\n  params?: Record<string, string | string[]>,\n): Promise<MiddlewareResult> {\n  let nextHeaders: Record<string, string> | undefined;\n  let nextParams: Record<string, string | string[]> | undefined;\n  let nextLocals: Record<string, unknown> | undefined;\n  const cleanups: Array<() => void | Promise<void>> = [];\n\n  const context: MiddlewareContext = {\n    next(options) {\n      if (options?.headers) nextHeaders = options.headers;\n      if (options?.params) nextParams = options.params;\n      if (options?.locals) nextLocals = options.locals;\n    },\n    params,\n    locals: {},\n  };\n\n  try {\n    const result = await middleware.handler(request, context);\n\n    if (result instanceof Response) {\n      return { kind: \"response\", response: result };\n    }\n\n    return {\n      kind: \"continue\",\n      headers: nextHeaders,\n      params: nextParams ?? params,\n      locals: nextLocals,\n    };\n  } finally {\n    // Run any cleanup functions (§6). Errors in cleanup are logged but\n    // do not propagate to the caller.\n    for (const cleanup of cleanups) {\n      try {\n        await cleanup();\n      } catch (err) {\n        console.error(\"[nix-js-kit] middleware cleanup error:\", err);\n      }\n    }\n  }\n}\n","import { createRequire } from \"node:module\";\nimport type { Plugin } from \"vite\";\n\n/**\n * How the legacy interpolation transform is handled relative to the installed\n * Nix.js core and Vite plugin:\n *\n * - `\"auto\"` (default): the kit's legacy transform is only applied when the\n *   Vite plugin (`@deijose/vite-plugin-nix-js` >= 1.1.0) is NOT installed.\n *   The plugin has a more powerful state-machine lexer and takes precedence.\n * - `\"legacy\"`: always apply the kit's transform (for migrations), with a\n *   one-time deprecation warning.\n * - `\"off\"`: never apply the kit's transform. Recommended when the Vite\n *   plugin is installed.\n */\nexport type InterpolationMode = \"auto\" | \"legacy\" | \"off\";\n\nconst require = createRequire(import.meta.url);\n\nlet _warnedLegacy = false;\n\nfunction warnLegacyOnce(): void {\n  if (_warnedLegacy) return;\n  _warnedLegacy = true;\n  console.warn(\n    \"[nix-js-kit] The legacy interpolation transform is deprecated. \" +\n    \"Install @deijose/vite-plugin-nix-js >= 1.1.0 for compile-time \" +\n    \"partial attribute interpolation. Remove `interpolation: \\\"legacy\\\"` \" +\n    \"once migration is complete.\",\n  );\n}\n\n/**\n * Detects whether the Vite plugin (`@deijose/vite-plugin-nix-js`) is\n * installed and provides compile-time partial attribute interpolation.\n */\nexport function pluginSupportsPartialInterpolation(): boolean {\n  try {\n    const pkg = require(\"@deijose/vite-plugin-nix-js/package.json\") as {\n      version?: string;\n    };\n    // >= 1.1.0 has the interpolation lexer\n    const [major, minor] = (pkg.version ?? \"0.0.0\").split(\".\").map(Number);\n    return major > 1 || (major === 1 && minor >= 1);\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Detects whether the installed Nix.js core supports partial attribute\n * interpolation natively (via the public `templateFeatures` capability).\n * Note: as of core v3.4.0, this is always false — the lexer moved to the\n * Vite plugin.\n */\nexport function coreSupportsPartialInterpolation(): boolean {\n  try {\n    const core = require(\"@deijose/nix-js\") as {\n      templateFeatures?: { partialAttributeInterpolation?: boolean };\n    };\n    return core?.templateFeatures?.partialAttributeInterpolation === true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Resolves whether the kit's legacy transform should be applied.\n *\n * In `\"auto\"` mode, the kit's transform runs only when neither the Vite\n * plugin nor the core provides partial interpolation. When the Vite plugin\n * is installed (>= 1.1.0), it takes precedence and the kit's transform is\n * skipped to avoid double-processing.\n */\nexport function shouldUseLegacyInterpolation(mode: InterpolationMode): boolean {\n  if (mode === \"off\") return false;\n  if (mode === \"legacy\") {\n    warnLegacyOnce();\n    return true;\n  }\n  // auto: skip if the Vite plugin handles it\n  if (pluginSupportsPartialInterpolation()) return false;\n  // fallback: use legacy if core doesn't support it natively\n  return !coreSupportsPartialInterpolation();\n}\n\n/**\n * Transforms Nix.js `html\\`\\`` templates so that attributes with partial\n * interpolation become a single interpolation expression.\n *\n * Nix.js requires every dynamic attribute to be a single interpolation covering\n * the whole value. This plugin rewrites patterns such as:\n *\n *   html\\`<a href=\"/blog/${slug}\">...</a>\\`\n *\n * into:\n *\n *   html\\`<a href=${\"/blog/\" + slug}>...</a>\\`\n *\n * Only files inside the app and islands directories are processed.\n *\n * @deprecated Nix.js core supports partial attribute interpolation natively.\n *   Keep this transform only for migrations against older cores\n *   (`interpolation: \"legacy\"`).\n */\nexport interface InterpolationPluginOptions {\n  appDir?: string;\n  islandsDir?: string;\n}\n\nconst HTML_TAG = \"html\";\nconst TEMPLATE_START = \"`\";\n\n/**\n * Scans a `${...}` interpolation starting at `start` (where content[start] is\n * `$` and content[start + 1] is `{`), honoring nested braces, strings and\n * escape sequences. Returns the index just past the closing `}`.\n */\nfunction scanInterpolation(content: string, start: number): number {\n  let depth = 1;\n  let i = start + 2;\n  while (i < content.length && depth > 0) {\n    const c = content[i];\n    if (c === \"\\\\\") {\n      i += 2;\n      continue;\n    }\n    if (c === '\"' || c === \"'\" || c === \"`\") {\n      const q = c;\n      i++;\n      while (i < content.length) {\n        if (content[i] === \"\\\\\") {\n          i += 2;\n          continue;\n        }\n        if (content[i] === q) break;\n        i++;\n      }\n      i++;\n      continue;\n    }\n    if (c === \"{\") depth++;\n    else if (c === \"}\") depth--;\n    i++;\n  }\n  return i;\n}\n\n/**\n * Scans a quoted attribute value starting at `start` (where content[start] is\n * the quote character). Handles escapes, `${...}` interpolations with nested\n * braces, and nested quotes. Returns the index just past the closing quote,\n * the raw inner text (escapes preserved as in the source) and whether the\n * value contains at least one interpolation.\n */\nfunction scanQuotedValue(\n  content: string,\n  start: number,\n  quote: string,\n): { end: number; inside: string; hasInterp: boolean } {\n  let i = start + 1;\n  let inside = \"\";\n  let hasInterp = false;\n  while (i < content.length) {\n    const c = content[i];\n    if (c === \"\\\\\") {\n      inside += c + (content[i + 1] ?? \"\");\n      i += 2;\n      continue;\n    }\n    if (c === quote) {\n      i++;\n      break;\n    }\n    if (c === \"$\" && content[i + 1] === \"{\") {\n      const end = scanInterpolation(content, i);\n      inside += content.slice(i, end);\n      i = end;\n      hasInterp = true;\n      continue;\n    }\n    inside += c;\n    i++;\n  }\n  return { end: i, inside, hasInterp };\n}\n\n/**\n * Converts the inner text of a quoted attribute value (which may contain\n * `${...}` interpolations) into a JS expression. Literal parts are JSON\n * encoded; interpolations keep their raw expression text.\n *\n * Examples:\n *   /blog/${slug}     -> \"/blog/\" + (slug)\n *   ${slug}           -> (slug)\n *   tag ${cls({a:1})} -> \"tag \" + (cls({a:1}))\n */\nfunction valueToExpression(value: string): string {\n  const parts: string[] = [];\n  let i = 0;\n  let literal = \"\";\n  const flush = () => {\n    if (literal) {\n      parts.push(JSON.stringify(unescapeAttributeLiteral(literal)));\n      literal = \"\";\n    }\n  };\n\n  while (i < value.length) {\n    if (value[i] === \"\\\\\") {\n      literal += value[i] + (value[i + 1] ?? \"\");\n      i += 2;\n      continue;\n    }\n    if (value[i] === \"$\" && value[i + 1] === \"{\") {\n      flush();\n      const end = scanInterpolation(value, i);\n      const expr = value.slice(i + 2, end - 1).trim();\n      if (expr) parts.push(`(${expr})`);\n      i = end;\n      continue;\n    }\n    literal += value[i];\n    i++;\n  }\n  flush();\n\n  if (parts.length === 0) return '\"\"';\n  if (parts.length === 1) return parts[0] as string;\n  return parts.join(\" + \");\n}\n\n/**\n * Unescapes escape sequences that appear inside a JS template literal so the\n * JSON.stringify output matches the runtime string value.\n */\nfunction unescapeAttributeLiteral(literal: string): string {\n  const escapes: Record<string, string> = {\n    n: \"\\n\",\n    t: \"\\t\",\n    r: \"\\r\",\n  };\n  let out = \"\";\n  let i = 0;\n  while (i < literal.length) {\n    const c = literal[i];\n    if (c === \"\\\\\" && i + 1 < literal.length) {\n      const next = literal[i + 1];\n      if (next in escapes) {\n        out += escapes[next];\n        i += 2;\n        continue;\n      }\n      out += next;\n      i += 2;\n      continue;\n    }\n    out += c;\n    i++;\n  }\n  return out;\n}\n\n/**\n * Rewrites quoted attribute values that contain interpolations inside html``\n * templates, leaving everything else untouched.\n */\nfunction transformTemplateContent(content: string): string {\n  let out = \"\";\n  let i = 0;\n  const n = content.length;\n\n  while (i < n) {\n    const lt = content.indexOf(\"<\", i);\n    if (lt === -1) {\n      out += content.slice(i);\n      break;\n    }\n    out += content.slice(i, lt);\n    i = lt;\n\n    // HTML comments: copy verbatim.\n    if (content.startsWith(\"<!--\", i)) {\n      const end = content.indexOf(\"-->\", i + 4);\n      if (end === -1) {\n        out += content.slice(i);\n        break;\n      }\n      out += content.slice(i, end + 3);\n      i = end + 3;\n      continue;\n    }\n\n    // Closing tags, doctype, CDATA, processing instructions: copy verbatim.\n    if (content[i + 1] === \"/\" || content[i + 1] === \"!\" || content[i + 1] === \"?\") {\n      const gt = content.indexOf(\">\", i + 1);\n      if (gt === -1) {\n        out += content.slice(i);\n        break;\n      }\n      out += content.slice(i, gt + 1);\n      i = gt + 1;\n      continue;\n    }\n\n    // Opening tag. Copy the tag name, then walk its attributes.\n    let j = i + 1;\n    while (j < n && /[a-zA-Z0-9-]/.test(content[j])) j++;\n    out += content.slice(i, j);\n    i = j;\n\n    while (i < n) {\n      let ws = \"\";\n      while (i < n && /\\s/.test(content[i])) {\n        ws += content[i];\n        i++;\n      }\n      if (i >= n) {\n        out += ws;\n        break;\n      }\n      if (content[i] === \">\") {\n        out += ws + \">\";\n        i++;\n        break;\n      }\n      if (content[i] === \"/\" && content[i + 1] === \">\") {\n        out += ws + \"/>\";\n        i += 2;\n        break;\n      }\n      // Interpolation in the tag body (dynamic attrs/spread): copy verbatim.\n      if (content[i] === \"$\" && content[i + 1] === \"{\") {\n        const end = scanInterpolation(content, i);\n        out += ws + content.slice(i, end);\n        i = end;\n        continue;\n      }\n\n      // Attribute name.\n      let nameStart = i;\n      while (i < n && !/[\\s=/>\"'$]/.test(content[i])) i++;\n      const name = content.slice(nameStart, i);\n      if (!name) {\n        out += ws + content[i];\n        i++;\n        continue;\n      }\n\n      let eqWs = \"\";\n      while (i < n && /\\s/.test(content[i])) {\n        eqWs += content[i];\n        i++;\n      }\n\n      if (content[i] !== \"=\") {\n        out += ws + name + eqWs;\n        continue;\n      }\n\n      i++; // consume \"=\"\n      let valWs = \"\";\n      while (i < n && /\\s/.test(content[i])) {\n        valWs += content[i];\n        i++;\n      }\n\n      const quote = content[i];\n      if (quote === '\"' || quote === \"'\") {\n        const { end, inside, hasInterp } = scanQuotedValue(content, i, quote);\n        if (hasInterp) {\n          // Skip values that are a single full interpolation: Nix.js handles\n          // `attr=\"${expr}\"` natively, so only partial interpolations need the\n          // rewrite.\n          const first = scanInterpolation(inside, 0);\n          const fullValue =\n            inside.startsWith(\"${\") &&\n            first === inside.length &&\n            !inside.slice(2, first - 1).includes(\"${\");\n          if (!fullValue) {\n            // Nix.js needs the interpolation to start right after \"=\" (no space),\n            // so the whitespace before the original value is dropped.\n            out += ws + name + eqWs + \"=\" + \"${\" + valueToExpression(inside) + \"}\";\n            i = end;\n            continue;\n          }\n          out += ws + name + eqWs + \"=\" + valWs + content.slice(i, end);\n        } else {\n          out += ws + name + eqWs + \"=\" + valWs + content.slice(i, end);\n        }\n        i = end;\n        continue;\n      }\n\n      // Unquoted value: copy up to whitespace, \">\" or \"/>\".\n      let v = \"\";\n      while (\n        i < n &&\n        !/\\s/.test(content[i]) &&\n        content[i] !== \">\" &&\n        !(content[i] === \"/\" && content[i + 1] === \">\")\n      ) {\n        v += content[i];\n        i++;\n      }\n      out += ws + name + eqWs + \"=\" + valWs + v;\n    }\n  }\n\n  return out;\n}\n\n/**\n * @deprecated Use the native partial attribute interpolation of Nix.js core\n *   (core >= 3.3). Kept for legacy migrations and direct consumers.\n */\nexport function transformPartialInterpolations(source: string): string {\n  let result = \"\";\n  let i = 0;\n  while (i < source.length) {\n    // Find the next html` sequence.\n    const htmlIndex = source.indexOf(HTML_TAG, i);\n    if (htmlIndex === -1) {\n      result += source.slice(i);\n      break;\n    }\n    result += source.slice(i, htmlIndex + HTML_TAG.length);\n    i = htmlIndex + HTML_TAG.length;\n\n    // Skip whitespace before the backtick.\n    while (i < source.length && /\\s/.test(source[i])) {\n      result += source[i];\n      i++;\n    }\n    if (i >= source.length || source[i] !== TEMPLATE_START) {\n      continue;\n    }\n    result += source[i];\n    i++;\n\n    // Parse the template literal until the matching backtick.\n    let depth = 1;\n    let templateContent = \"\";\n    while (i < source.length && depth > 0) {\n      const char = source[i];\n      if (char === \"\\\\\") {\n        templateContent += char + source[i + 1];\n        i += 2;\n        continue;\n      }\n      if (char === TEMPLATE_START) {\n        depth--;\n        if (depth === 0) {\n          i++;\n          break;\n        }\n      }\n      if (char === \"$\") {\n        // Look ahead for ${...}\n        if (source[i + 1] === \"{\") {\n          const end = scanInterpolation(source, i);\n          templateContent += source.slice(i, end);\n          i = end;\n          continue;\n        }\n      }\n      templateContent += char;\n      i++;\n    }\n\n    const transformed = transformTemplateContent(templateContent);\n    result += transformed;\n    result += TEMPLATE_START;\n  }\n  return result;\n}\n\nexport function nixJsInterpolationPlugin(options: InterpolationPluginOptions = {}): Plugin {\n  const appDir = options.appDir ?? \"src/app\";\n  const islandsDir = options.islandsDir ?? \"src/islands\";\n  return {\n    name: \"nix-js-kit-interpolation\",\n    enforce: \"pre\",\n    transform(code, id) {\n      if (!id.endsWith(\".ts\") && !id.endsWith(\".js\")) return;\n      if (!id.includes(appDir) && !id.includes(islandsDir)) return;\n      if (!code.includes(\"html`\")) return;\n      const transformed = transformPartialInterpolations(code);\n      if (transformed === code) return;\n      return { code: transformed, map: null };\n    },\n  };\n}\n"],"mappings":"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"}