{"version":3,"file":"preview-server-DEgaVBRz.mjs","names":[],"sources":["../../src/preview-server.ts"],"sourcesContent":["import type { BindingHooks, Module } from \"@distilled.cloud/cloudflare-runtime\";\nimport * as Runtime from \"@distilled.cloud/cloudflare-runtime/Runtime\";\nimport * as RuntimeServices from \"@distilled.cloud/cloudflare-runtime/RuntimeServices\";\nimport * as Credentials from \"@distilled.cloud/cloudflare/Credentials\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport * as Layer from \"effect/Layer\";\nimport * as Scope from \"effect/Scope\";\nimport * as FetchHttpClient from \"effect/unstable/http/FetchHttpClient\";\nimport * as NodeFs from \"node:fs/promises\";\nimport * as NodePath from \"node:path\";\nimport type { CloudflareVitePluginOptions } from \"./plugin.js\";\n\n/**\n * The freshly built worker a preview server hosts: which directory holds the\n * server modules, which of them is the entry, and where the client assets\n * live. Derived by the preview plugin from the resolved Vite config.\n */\nexport interface PreviewWorkerBuild {\n  /**\n   * Absolute directory containing the built server modules (the entry\n   * environment's `build.outDir`). Every file under it (recursively) is\n   * loaded into workerd as a module, so relative imports between chunks —\n   * including nested child-environment output like waku's `server/ssr` —\n   * resolve exactly as they would on the deployed worker.\n   */\n  readonly directory: string;\n  /** The entry module's name, relative to `directory` (POSIX separators). */\n  readonly entryModule: string;\n  /** Absolute directory of the built client assets, if any. */\n  readonly assetsDirectory?: string | undefined;\n}\n\nexport interface PreviewServerHandle {\n  readonly address: URL;\n  readonly close: () => Promise<void>;\n}\n\n/**\n * Boot workerd (via `cloudflare-runtime`'s `Runtime.start`) over the built\n * worker output on disk. Unlike the dev server there is no module runner and\n * no module fallback: the build is self-contained, so every module is read\n * from the output directory upfront (entry first — workerd treats the first\n * module as the main module) and handed to workerd directly. Assets are\n * served by the runtime's disk-backed assets plugin from the client build\n * directory.\n *\n * When the plugin options carry no `context`, a runtime context is built\n * into the preview's own scope and torn down by `close()` — unlike the dev\n * server's process-lifetime cached context. This matters for build-time SSG\n * (waku boots a preview server mid-`buildApp`): a lingering context's open\n * handles would keep the build process alive after the build completes.\n */\nexport const startPreviewServer = async <B extends BindingHooks = BindingHooks>(\n  options: CloudflareVitePluginOptions<B>,\n  build: PreviewWorkerBuild,\n): Promise<PreviewServerHandle> => {\n  const scope = Scope.makeUnsafe();\n  // Only sweep handles for a context we build (and tear down) ourselves; a\n  // caller-provided context is process-lifetime by design (dev semantics).\n  const sweep = options.context === undefined ? makeHandleSweep() : undefined;\n  try {\n    const context =\n      options.context ??\n      (await makePreviewContext().pipe(Layer.buildWithScope(scope), Effect.runPromise));\n    const address = await serve(options, build).pipe(\n      Effect.provide(context),\n      Scope.provide(scope),\n      Effect.runPromise,\n    );\n    return {\n      address,\n      close: async () => {\n        await closeScope(scope);\n        sweep?.();\n      },\n    };\n  } catch (error) {\n    await closeScope(scope);\n    sweep?.();\n    throw error;\n  }\n};\n\n/**\n * Workaround for a `cloudflare-runtime` teardown gap: building the runtime\n * context spawns a detached, uninterruptible fiber (`DockerLive`) that binds\n * a Docker proxy server (`server.listen(0)`) and probes the local Docker\n * daemon with no finalizer — so a build process that booted a preview server\n * for SSG can never exit on its own, even after the runtime scope closes.\n *\n * Until the runtime tears that fiber down (or starts it lazily), snapshot\n * the process's libuv handles before the context is built and, on close,\n * `unref()` whatever appeared since and survived scope teardown. `unref` is\n * strictly about process exit: leaked handles keep working for as long as\n * the process lives, they just stop pinning the event loop. Handles we own\n * are already closed by the scope; the caller's preview http server is\n * closed by the caller immediately after, so unref-ing it is harmless.\n */\nconst makeHandleSweep = (): (() => void) => {\n  const getHandles = (): Array<{ unref?: () => void }> => {\n    const process_ = process as unknown as {\n      _getActiveHandles?: () => Array<{ unref?: () => void }>;\n    };\n    try {\n      return process_._getActiveHandles?.() ?? [];\n    } catch {\n      return [];\n    }\n  };\n  const before = new Set(getHandles());\n  return () => {\n    for (const handle of getHandles()) {\n      if (before.has(handle)) continue;\n      try {\n        handle.unref?.();\n      } catch {\n        // best-effort: exiting cleanly matters more than the odd handle\n      }\n    }\n  };\n};\n\nconst importPlatformServices = Layer.unwrap(\n  Effect.promise(async () => {\n    try {\n      const BunServices = await import(\"@effect/platform-bun/BunServices\");\n      return BunServices.layer;\n    } catch {\n      // ignore and fall back to NodeServices\n    }\n    const NodeServices = await import(\"@effect/platform-node/NodeServices\");\n    return NodeServices.layer;\n  }),\n);\n\nconst makePreviewContext = () =>\n  RuntimeServices.layerRuntime({\n    api: {\n      accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,\n    },\n  }).pipe(\n    Layer.provideMerge(importPlatformServices),\n    Layer.provide(Layer.merge(Credentials.fromEnv(), FetchHttpClient.layer)),\n  );\n\nconst closeScope = async (scope: Scope.Scope) => {\n  await Effect.runPromiseExit(Scope.closeUnsafe(scope, Exit.void) ?? Effect.void);\n};\n\n// Deliberately non-generic: `CloudflareVitePluginOptions<BindingHooks>` is a\n// supertype of every instantiation, and `BindingRequirements<BindingHooks>`\n// collapses to `never`, so the effect's requirements are fully discharged by\n// the provided runtime context + scope. Keeping the caller's `B` generic here\n// would leave a deferred `BindingRequirements<B>` in the requirements that\n// `Effect.runPromise` cannot prove away.\nconst serve = Effect.fn(function* (\n  options: CloudflareVitePluginOptions,\n  build: PreviewWorkerBuild,\n) {\n  const runtime = yield* Runtime.Runtime;\n  const modules = yield* Effect.promise(() => readWorkerModules(build));\n  const assetsDirectory = options.worker?.assets?.directory ?? build.assetsDirectory;\n  return yield* runtime.start({\n    name: options.worker?.name ?? `vite-preview-${crypto.randomUUID()}`,\n    modules,\n    compatibilityDate: options.compatibilityDate ?? \"2026-05-12\",\n    compatibilityFlags: options.compatibilityFlags ?? [],\n    bindings: options.worker?.bindings ?? [],\n    durableObjectNamespaces: options.worker?.durableObjectNamespaces,\n    hyperdrives: options.worker?.hyperdrives,\n    queueConsumers: options.worker?.queueConsumers,\n    assets:\n      options.worker?.assets !== undefined || assetsDirectory !== undefined\n        ? { ...options.worker?.assets, directory: assetsDirectory }\n        : undefined,\n    logging: options.worker?.logging,\n    unsafe: options.worker?.unsafe,\n  });\n});\n\n/**\n * Read the built server output into workerd modules, entry first. Source maps\n * are skipped; everything else is typed by extension so wasm/text/data\n * modules emitted next to the chunks keep working.\n */\nexport const readWorkerModules = async (build: PreviewWorkerBuild): Promise<Array<Module>> => {\n  const entries = await NodeFs.readdir(build.directory, {\n    recursive: true,\n    withFileTypes: true,\n  });\n  const modules = await Promise.all(\n    entries\n      .filter((entry) => entry.isFile())\n      .map((entry) => {\n        const file = NodePath.join(entry.parentPath, entry.name);\n        const name = NodePath.relative(build.directory, file).replaceAll(\"\\\\\", \"/\");\n        return readWorkerModule(file, name);\n      }),\n  );\n  const found = modules.filter((module): module is Module => module !== undefined);\n  const entryIndex = found.findIndex((module) => module.name === build.entryModule);\n  if (entryIndex === -1) {\n    throw new Error(\n      `Cannot find the worker entry module \"${build.entryModule}\" in \"${build.directory}\". ` +\n        \"Run the build before starting the preview server.\",\n    );\n  }\n  const [entry] = found.splice(entryIndex, 1);\n  return [entry!, ...found];\n};\n\nconst readWorkerModule = async (file: string, name: string): Promise<Module | undefined> => {\n  switch (NodePath.extname(file)) {\n    case \".map\":\n      return undefined;\n    case \".js\":\n    case \".mjs\":\n      return { name, type: \"ESModule\", content: await NodeFs.readFile(file, \"utf8\") };\n    case \".cjs\":\n      return { name, type: \"CommonJsModule\", content: await NodeFs.readFile(file, \"utf8\") };\n    case \".json\":\n      return { name, type: \"Json\", content: await NodeFs.readFile(file, \"utf8\") };\n    case \".txt\":\n    case \".html\":\n    case \".css\":\n    case \".sql\":\n      return { name, type: \"Text\", content: await NodeFs.readFile(file, \"utf8\") };\n    case \".wasm\":\n      return { name, type: \"Wasm\", content: new Uint8Array(await NodeFs.readFile(file)) };\n    default:\n      return { name, type: \"Data\", content: new Uint8Array(await NodeFs.readFile(file)) };\n  }\n};\n"],"mappings":"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"}