#!/usr/bin/env node /** * Post-build step for deploying agent-native apps to edge/serverless targets. * * When NITRO_PRESET is set, this script: * 1. Takes the React Router build output (build/client/ + build/server/) * 2. Generates a platform-specific server entry point * 3. Bundles everything with esbuild into the target format * * Supported presets: * - cloudflare_pages: Outputs dist/ with _worker.js for Cloudflare Pages * - cloudflare_module: Outputs a native Cloudflare Worker under .output/server * * Usage: node deploy/build.js (called automatically by `agent-native build`) */ import { type DiscoveredRoute, type DiscoveredAction } from "./route-discovery.js"; import { type WorkspaceCoreExports } from "./workspace-core.js"; export declare const CLOUDFLARE_MODULE_PRESETS: readonly ["cloudflare_module", "cloudflare-module"]; export declare function isCloudflareModulePreset(targetPreset: string): boolean; export declare const CLOUDFLARE_MODULE_WORKER_ENTRY = "worker.mjs"; export declare function generateCloudflareModuleWorkerEntry(): string; export declare function patchCloudflareModuleNitroEntry(code: string): string; export declare function configureCloudflareModuleWorkerOutput(serverDir: string): void; export declare const NITRO_RUNTIME_IGNORE_PATTERNS: string[]; export declare const CLOUDFLARE_WORKER_ESBUILD_EXTERNALS: string[]; export declare const CLOUDFLARE_WORKER_STUB_MODULES: Record; export declare const CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES: Record; export declare function cloudflareWorkerStubAliasArgs(stubDir: string): string[]; export declare function assertNoCloudflareWorkerStubDynamicImports(code: string, sourceName: string): void; export declare const CLOUDFLARE_WORKER_NODE_BUILTIN_STUB_MODULES: Record; export interface GenerateWorkerEntryOptions { includeReactRouterSsr?: boolean; } interface ReactRouterAssetManifest { entry: ReactRouterAssetManifestEntry; routes: Record; url: string; } interface ReactRouterAssetManifestEntry { module: string; imports?: string[]; css?: string[]; } interface ReactRouterAssetManifestRoute { id: string; module: string; imports?: string[]; css?: string[]; hasLoader?: boolean; clientActionModule?: string; clientLoaderModule?: string; clientMiddlewareModule?: string; hydrateFallbackModule?: string; } export declare function generateProvidedPluginsNitroPluginSource(pluginStems: string[]): string; type RouteRules = Record; }>; export declare function addImmutableAssetRouteRulesForClientBuild(routeRules: RouteRules, clientDir: string, appBasePath?: string): void; /** * Generate the worker entry source code that wires up H3 + React Router SSR. * * If a workspace core is present (monorepo with `agent-native.workspaceCore` * configured and the named package resolves), any plugin slot that the * workspace core exports is imported from there instead of from * `@agent-native/core/server/edge`. This is the middle layer of the three-layer * inheritance model: app local > workspace core > framework default. */ export declare function generateWorkerEntry(routes: DiscoveredRoute[], pluginPaths: string[], defaultPluginStems?: string[], actions?: DiscoveredAction[], workspaceCore?: WorkspaceCoreExports | null, immutableAssetPaths?: string[], builtAppBasePath?: string, options?: GenerateWorkerEntryOptions): string; export declare function generateCloudflarePagesStaticShellFromManifest(manifest: ReactRouterAssetManifest, basePath?: string): string; export declare function getNodeBuiltinNames(): string[]; export declare function copyDir(src: string, dest: string, ancestorRealPaths?: Set): void; type ServerlessFfmpegStaticArch = "arm64" | "x64"; export declare function shouldBundleFfmpegStaticForServerless(hostPlatform?: NodeJS.Platform, hostArch?: NodeJS.Architecture, targetArch?: ServerlessFfmpegStaticArch | null): boolean; export declare function findInstalledFfmpegStaticPackage(nodeModulesRoots: string[]): string | null; export declare function findInstalledResvgPackages(nodeModulesRoots: string[]): Array<{ packageName: string; packageDir: string; }>; /** * Deploy-time gate for emitting the second `-background` Netlify function. * Reads the same env flag the runtime gate uses * (`AGENT_CHAT_DURABLE_BACKGROUND`). * * DEFAULT-OFF (opt-in). It IS the runtime gate's flag parse * (`isDurableBackgroundFlagEnabled`), so the two can no longer drift: * unset/empty/unknown means DISABLED; an app opts IN * only with an explicit truthy value (`true`/`1`/`yes`/`on`). A premature * fleet-wide default-on caused real-user incidents (2026-06-24) before the * async worker path was proven, so durable is opt-in until verified live. This * gate is what emits the 15-min `-background` function so the `_process-run` * dispatch lands on it (async 202 → the worker runs with the real 15-min budget * → its ~13-min soft-timeout fits). The deploy gate and runtime gate MUST agree: * if the deploy emitted no `-background` function but the runtime still routed * the worker into the ~13-min timeout regime, the worker would overshoot the * ~60s synchronous wall and re-dispatch in a loop. (The runtime now also guards * the ~13-min budget on the real function name via `isInBackgroundFunctionRuntime`, * so a missing emit does not loop.) A missing emit is NOT benign, though: the * app then runs every turn on the ~60s synchronous wall for the life of the * deploy, so an opted-in build that cannot emit the function FAILS rather than * warning. */ export declare function isDurableBackgroundDeployEnabled(): boolean; export declare const NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME = "server-integration-recovery"; export declare function isIntegrationDurableDispatchDeployEnabled(): boolean; /** * Emit a site-local Netlify Scheduled Function that wakes the public server * function and its database every minute. GitHub Actions schedules can be * delayed by tens of minutes, which is longer than a scale-to-zero database's * autosuspend window and leaves the next visitor to pay the cold-start cost. */ export declare function emitSingleTemplateNetlifyKeepWarmFunction(projectCwd: string): void; /** * Single-template Netlify build: emit an async (background) function INSIDE the * scanned functions dir so the chat `_process-run` worker runs on Netlify's * 15-min async function instead of the synchronous `/*` catch-all. * Additive + flag-gated (see `isDurableBackgroundDeployEnabled`). * * GROUNDED IN THE REAL NETLIFY BUILD OUTPUT (verified from a local Nitro build) * AND THE NETLIFY DOCS DEFAULT-URL RULE: * - Nitro's `netlify` preset emits exactly ONE function source at * `.netlify/functions-internal/server/`. `server.mjs` re-exports `main.mjs` * and declares `export const config = { path: "/*", excludedPath: * ["/.netlify/*"], preferStatic: true, ... }`. The `/*` catch-all is an * IN-CODE Functions-API-v2 `config.path` and it ALREADY EXCLUDES * `/.netlify/*`. * - The generated `.netlify/netlify.toml` sets * `functionsDirectory = ".netlify/functions-internal"`. Netlify scans EXACTLY * that dir; functions placed anywhere else (e.g. `.netlify/functions/`, which * is the BUILD OUTPUT dir where `@netlify/build` later writes the zipped * functions + `manifest.json`) are NEVER deployed. * - Every scanned function is reachable at its DEFAULT url * `/.netlify/functions/` BY DEFAULT. A custom `config.path` REMOVES * that default url; declaring NO custom `config.path` KEEPS it. * * THEREFORE we: * 1. Emit the background function INTO the scanned dir * (`.netlify/functions-internal/server-agent-background/`), sharing the same * built `main.mjs` bundle, so Netlify discovers it and honors its config. * 2. Give its `export const config` `background: true` (→ async invoke, * immediate 202, 15-min budget) and NO custom `config.path`. With no custom * path the function keeps its DEFAULT url * `/.netlify/functions/server-agent-background`, and because the Nitro * `server` function's `/*` catch-all already excludes `/.netlify/*`, that * default-url namespace is NEVER shadowed by the synchronous function — no * catch-all patch is needed. * 3. The entry NORMALIZES/rewrites the incoming request pathname to * `AGENT_CHAT_PROCESS_RUN_PATH` before delegating to `./main.mjs`. The * function is reached at its default url * (`/.netlify/functions/server-agent-background`), so the Nitro router needs * the path rewritten to the framework `_process-run` route, preserving the * method, ALL headers (the HMAC `Authorization: Bearer` MUST survive), and * the body. * 4. Set `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start * (read back by `isInBackgroundFunctionRuntime()` so the worker takes the * ~13-min soft-timeout). A `globalThis` flag — NOT `process.env` — keeps the * no-env-mutation guard satisfied and carries no cross-request state. * * The foreground dispatches to this DEFAULT url on hosted Netlify * (`resolveAgentChatProcessRunDispatchPath` → `AGENT_BACKGROUND_FUNCTION_URL_PATH`). * * WHY THIS IS THE DOC-CORRECT FIX: a prior attempt gave the function a custom * `config.path` (= the framework route) plus a catch-all `excludedPath` patch. * The custom `config.path` was NOT honored as a route in prod — a probe of * `POST /_agent-native/agent-chat/_process-run` returned 404. The doc-correct * approach (confirmed against the Netlify docs) is to use the DEFAULT function * url with no custom path: the function stays reachable at * `/.netlify/functions/` and is never shadowed because `/.netlify/*` is * already excluded from the `server` catch-all. * * Safety net regardless of Netlify routing nuance: if the dispatch fast-fails * (e.g. the function was not emitted), the foreground handler degrades to an * inline 40s synchronous run (see production-agent.ts). */ export declare function emitSingleTemplateNetlifyBackgroundFunction(projectCwd: string): void; /** * Prove the artifact Netlify will scan actually landed. A partial copy (the * entry without its handler bundle) deploys as a function that 500s on every * invocation, which looks exactly like "no background function" at runtime. * Exported so the workspace deploy asserts the same shape. */ export declare function assertEmittedBackgroundFunctionOnDisk(destDir: string, functionName: string): void; export declare function emitSingleTemplateNetlifyIntegrationRecoveryFunction(projectCwd: string): void; /** * Nitro receives the React Router SSR build as prebuilt chunks, so its normal * dependency resolver cannot reliably fold the preserved bare `yjs` imports * into the same module instance used by core's server collaboration code. * Keep Yjs external through Nitro, bundle its complete public ESM surface once, * then point every emitted server chunk at that one portable runtime module. */ export declare function bundleYjsRuntimeForServerlessOutput(serverDir: string, projectCwd: string): string[]; export declare function assertSingleTemplateNetlifyBuildOutput(projectCwd: string): void; /** * Strip the harmful single-template catch-all rewrite that points at * `/.netlify/functions/server`. Nitro declares `config.path: "/*"`, which * removes the default function URL, so rewriting to that URL publishes * Netlify platform 404s. Preserve any real redirects from `public/_redirects`. */ export declare function writeSingleTemplateNetlifyRedirects(projectCwd: string): void; /** * Nitro's file tracer can over-include optional desktop/dev packages that are * present in a monorepo install but cannot run in serverless. Netlify installs * the generated per-function package.json before upload; if `electron` remains * there, the function can exceed Netlify's 250 MB unzipped size limit even * though the server bundle never imports Electron at runtime. */ export declare function sanitizeServerlessFunctionPackageManifest(functionDir: string | undefined): void; /** * Build for any non-Cloudflare preset using Nitro's programmatic build API. * Handles netlify, vercel, deno_deploy, aws-lambda, and all other targets. */ export interface NitroBuildHooks { prepare: (nitro: any) => Promise; copyPublicAssets: (nitro: any) => Promise; nitroBuild: (nitro: any) => Promise; } export interface NitroBuildPipelineOptions { nitro: any; hooks: NitroBuildHooks; clientDir: string; publicOutputDir: string | undefined; appBasePath: string; cwd: string; } /** * Run Nitro's lifecycle in the order required to ship a working React Router * framework-mode build. * * The critical ordering constraint is that the React Router client build must * be copied into `publicOutputDir` *before* `nitroBuild` runs. Nitro generates * the static-asset manifest baked into the server bundle by globbing * `publicDir` during the server build; files copied in after that point exist * on disk but are invisible to the runtime `serveStatic` handler. Every * /assets/* request then falls through to the SSR catch-all, which 404s * anything with a file extension. */ export declare function runNitroBuildPipeline(opts: NitroBuildPipelineOptions): Promise; /** * Packages that Nitro can discover through the shared server graph but that * cannot be evaluated in a Cloudflare Worker. Keep the network-capable SDKs * real; these are limited to Node/native/browser-runtime packages whose * server-side paths already fail closed when the capability is unavailable. */ export declare const CLOUDFLARE_MODULE_STUB_MODULES: readonly ["@napi-rs/canvas", "@resvg/resvg-js", "@sentry/node", "@sparticuz/chromium-min", "better-sqlite3", "chartjs-node-canvas", "chokidar", "fsevents", "node-pty", "playwright", "playwright-core"]; /** * Mirror the fail-closed package stubs used by the Pages bundler in Nitro's * Rolldown graph. Without this, the native module preset emits a valid module * graph that still crashes at Worker cold start when a Node-only optional * dependency is evaluated. */ export declare function createCloudflareModuleStubPlugin(): { name: string; resolveId(id: string): string; load(id: string): string; }; /** * Dependencies Nitro itself must bundle outside the controlled serverless * output pass. Netlify, Vercel, and Lambda keep Yjs external through Nitro; * `bundleYjsRuntimeForServerlessOutput` then creates their one portable copy. */ export declare const NITRO_SERVER_RUNTIME_BUNDLED_DEPS: readonly ["yjs"]; /** * Locate the core-owned ESM entry used by the controlled serverless bundling * pass. Resolving from this module keeps the build independent of whether a * template exposes core's transitive Yjs dependency at its own package root. */ export declare function resolveNitroBundledYjsEntry(): string; /** * Edge runtimes have no node_modules, while Node/serverless outputs only need * the small set above bundled to keep their package manifests traceable. */ export declare function nitroNoExternalsForPreset(targetPreset: string): true | readonly string[]; export {}; //# sourceMappingURL=build.d.ts.map