import { type DeclaredStorageSources, type NativeDependency, type RebaseBundleManifest, type RebaseBackendAppConfig } from "@rebasepro/types"; export declare const DEFAULT_BUNDLE_DIR = "dist-bundle"; export interface BuildBundleOptions { projectRoot: string; appName: string; app: RebaseBackendAppConfig; /** Output directory, absolute or relative to the project root. */ outDir?: string; /** Runtime range from the manifest, recorded for compatibility checks. */ runtimeRange: string; /** * The `storage` block of `rebase.json` — which buckets this project uses. * * Passed in rather than re-read here so `rebase.json` is parsed and validated * once, by the command that owns it. */ storage?: DeclaredStorageSources; /** * Install the declared dependencies into the bundle at build time. * * Omitted means "when it is safe to" — which is every bundle whose closure * has no native code. `false` is the escape hatch for a build that must not * shell out to npm at all (an air-gapped CI, a offline reproducibility * check); the bundle still works, it simply installs at boot as before. */ vendor?: boolean; /** Skip type checking. Faster, and strictly worse — for iteration only. */ skipTypeCheck?: boolean; /** Skip regenerating the Drizzle schema from the collections. */ skipSchema?: boolean; /** Emit progress. */ log?: (message: string) => void; } export interface BuildBundleResult { outDir: string; manifest: RebaseBundleManifest; collectionCount: number; /** * Whether the dependency tree was installed into the bundle, and why not * when it was not. Reported rather than silent: "your pods will take a * minute to start" is a consequence a developer should hear at build time, * not discover during an incident. */ vendor: VendorResult; } /** * Whether the compiled config package exports a `storageAuthorize` hook. * * Recorded in the manifest so a host can refuse a deploy that would enable file * storage with no access model, rather than let the runtime's boot guard turn it * into a crash loop the developer cannot read. * * Read from the *compiled* index, deliberately: that is the exact module the * runtime imports and reads the export off, so this cannot disagree with what * actually happens at boot. It is a textual check rather than an import because * a freshly built bundle cannot resolve its own dependencies until it is * deployed — the same reason schema hashing reads source. * * Errs toward `false`: a missed detection costs a deploy rejection whose message * says exactly how to proceed, while a false positive would hand back the crash * loop this exists to prevent. */ export declare function detectStorageAuthorize(compiledConfigDir: string, depth?: number): boolean; /** * Detect native code in the dependency closure. * * Walks declared runtime dependencies breadth-first through `node_modules`, * flagging anything with a `binding.gyp`, a prebuilt `.node` binary, or an * install script that builds one. The managed runtime cannot run these: a * binary compiled for one image will not load in another, and finding that out * at deploy time is far better than in a crash loop. * * The walk is bounded. A dependency graph can be enormous, and this is a * heuristic gate whose false negatives are caught at deploy time anyway. */ export declare function detectNativeDependencies(projectRoot: string, declared: Record, limit?: number): NativeDependency[]; /** * Collect the runtime dependencies a bundle needs installed beside it. * * Packages the runtime image already provides are excluded — reinstalling a * second copy of the server next to the one running the process is at best * wasted space and at worst a version conflict. Workspace packages are excluded * too: they are not on the registry the runtime installs from, and the project's * own config package already travels inside the bundle. */ export declare function collectDeclaredDependencies(projectRoot: string): Record; /** One `@rebasepro/*` dependency as some package.json in the project declares it. */ export interface DeclaredFrameworkDep { name: string; range: string; /** Project-relative package.json it was declared in. */ file: string; } export interface FrameworkDepDrift { /** Declared at a version that can never reach the CLI's own. */ behind: DeclaredFrameworkDep[]; /** * The distinct lower bounds found across all declared `@rebasepro/*`, when * there is more than one — the project is pinning mixed-era framework * packages against each other. */ disagreeing: string[]; } /** * Find `@rebasepro/*` dependencies pinned to a version older than this CLI. * * This is the only place a developer can be told. In development, every * `@rebasepro/*` resolves through pnpm's `link:`/`workspace:` overrides to the * checkout, so the version STRINGS in package.json are never exercised — the * project runs fine locally on whatever is on disk, and the declared numbers are * first honoured when the runtime npm-installs them from a bundle in the cloud. * A project scaffolded at 0.10.0 therefore keeps working on a developer's * machine indefinitely while being, in the cloud, a 0.10.0 driver. * * That matters because the image supplies only `@rebasepro/server`; the database * driver comes from these declarations and a newer runtime never updates it. * Every package.json is scanned, `dependencies` and `devDependencies` both, * because they have to be bumped together and the one that gets forgotten is the * one nobody looks at. */ export declare function detectFrameworkDepDrift(projectRoot: string, cliVersion: string): FrameworkDepDrift; /** * Rewrite relative import specifiers in emitted JavaScript so Node can resolve them. * * TypeScript deliberately does not touch specifiers: `moduleResolution: "bundler"` * lets a project write `from "./posts"` or `from "./collections"`, and TypeScript * emits them unchanged on the assumption that a bundler will finish the job. * Nothing bundles a Rebase bundle — the runtime imports these files directly with * Node's ESM loader, which requires a full path with an extension and refuses * directory imports outright. * * Without this, adopting the bundle would mean asking every project written in * the (extremely common) extensionless style to rewrite all of its imports. The * rewrite is mechanical and verifiable: only relative specifiers are touched, and * only when the target file actually exists on disk. */ export declare function normalizeEsmSpecifiers(outDir: string): { rewritten: number; unresolved: string[]; }; /** * A hand-written server entrypoint that a bundle does not use. * * `rebase dev` runs `backend/src/index.ts` whenever a project has one, so for * the whole of local development that file *is* the server and every route * written in it works. A bundle has no entrypoint of its own: the runtime boots * the bundle and mounts what the manifest points at — the config package, * functions, crons and the schema. The file is not compiled, not shipped, and * never imported. * * Nothing said so. A project with custom routes in its entrypoint built clean, * deployed green, and answered 404 on every one of them, with the file still * sitting in the repository looking exactly like the server. * * A project that means to own its server process runs `rebase eject`, which * writes an entrypoint, a Dockerfile and a compose file together and flips the * backend to `runtime: "custom"`. The warning names that route rather than * implying the file is a mistake — but eject writes *its* entrypoint, so the * warning must not read as "eject will keep what you wrote here". */ export declare function findUnusedServerEntry(projectRoot: string, functionsDir: string): string | undefined; /** * Compile and assemble a bundle. */ export declare function buildBundle(options: BuildBundleOptions): Promise; /** Default install target: what the published runtime image runs. */ export declare const VENDOR_TARGET_OS = "linux"; export declare const VENDOR_TARGET_CPU = "x64"; export interface VendorResult { vendored: boolean; target?: { os: string; cpu: string; node: string; }; /** Why nothing was installed. Present exactly when `vendored` is false. */ skipped?: string; /** Size of the installed tree on disk, when one was installed. */ bytes?: number; } /** * Where a vendored bundle starts being too big to upload. * * The control plane refuses a bundle over 100 MB, and that ceiling is not * arbitrary or easily raised: its pod has a 512Mi memory limit and the upload * route holds the body while it writes it, so the cap protects the process that * also serves the console, deploys and billing. Vendoring is the one change that * can push a bundle near it. * * So the warning is here, at build time, where the remedy is one flag away — * rather than at deploy time as a 413 nobody can act on without rebuilding. The * threshold sits below the real cap because this measures the tree on disk and * the upload is compressed: crossing it means "getting close", not "will fail". */ export declare const VENDOR_SIZE_WARN_BYTES: number; /** * Install the bundle's declared dependencies into the bundle itself. * * ## What this buys * * A managed pod's bundle lives on an `emptyDir`, so it is re-fetched and * re-installed on **every** start — an eviction, a node failure, an OOM, a * runtime rollout. The install is 35–55 seconds of a 40–60 second cold start, * which makes it the price of every unplanned restart a tenant suffers. Doing it * once at build time instead of every time at boot takes that to roughly the * cost of untarring. * * The pod side needs no change to benefit: the init container already skips * installing when `node_modules` is present, a guard that existed for * pre-baked images and turns out to be exactly the hook this needs. * * ## Why it refuses to vendor native code * * A compiled binary is valid only for the platform it was built for, and a * developer's machine is rarely the deployment's. The managed runtime already * refuses bundles containing native modules for the same reason, so this refusal * costs nothing there — but a self-hosted project may legitimately use them, and * for those the honest answer is to install in the container, where the platform * is known. * * ## Why `--os` and `--cpu` are not optional * * The dangerous case is not native code, which is detectable. It is a pure-JS * package whose real work lives in a **platform-specific optional dependency** — * `esbuild` being the one everybody meets. Installing on an Apple Silicon Mac * resolves `@esbuild/darwin-arm64`, produces a tree that looks complete, and * fails at import inside a linux/amd64 pod. npm resolves optional dependencies * for the declared target rather than the host when told to, so it is told to. */ export declare function vendorDependencies(options: { outDir: string; declared: Record; nativeModules: NativeDependency[]; /** * Packages the runtime resolves *from the bundle* and cannot boot without — * in practice the database driver, which the image deliberately does not * supply. A vendored tree missing one of these is refused; see below. */ required?: string[]; /** `false` disables; `undefined` means "when it is safe to". */ requested?: boolean; /** Injected in tests. */ run?: (cmd: string, args: string[], cwd: string) => void; }): VendorResult; /** * Package a built static app (a `static` or bundled-`admin` app) into a bundle. * * A static bundle is the counterpart to a backend bundle: the same shape, the * same runtime image runs it, but its manifest says `mode: "static"` and it * carries only the built assets under `static/`. That is what lets a frontend or * admin app be its own deployable, scalable unit rather than something baked into * the backend container. * * `assetsDir` is the app's built output (e.g. `frontend/dist`), already produced * by its own build command. This copies it into the bundle and writes the * manifest — no compilation, no dependency closure (a static bundle installs * nothing at boot). */ /** * Fold a built static app into a backend bundle, so one runtime serves both. * * ## Why this exists * * A managed tenant runs one pod, and `bootFromBundle` on the backend path already * knows how to serve a SPA — it looks for `entry.static` and mounts `serveSPA` * last, behind `REBASE_SERVE_STATIC`. What was missing was anything putting the * assets there. * * The consequence was not subtle. A project whose custom image served its website * at `/` and its API at `/api` — the shape the scaffolded template produces — lost * the website the moment it moved to the managed runtime: the API answered * perfectly and every page 404'd. Managed could not be a drop-in replacement for * custom while the frontend simply vanished. * * Folding restores parity with the container it replaces, which is the only * honest baseline. It is deliberately the FIRST implementation and not the last: * a static app on its own bucket behind a CDN is better for cache behaviour and * lets the frontend deploy independently. But that needs infrastructure that does * not exist yet, and "your site is gone" is not an acceptable state to leave a * project in while it gets built. * * The trade it makes, stated plainly: frontend and backend now deploy together * and the bundle carries the built assets. For a project that was shipping both * in one image already, that is exactly what it had. */ export declare function foldStaticIntoBundle(options: { /** The backend bundle directory, already written. */ bundleDir: string; /** Directory of built frontend assets (the static app's `output`). */ assetsDir: string; /** The app's name in `rebase.json`. Names its directory inside the bundle. */ appName: string; /** Public base path this app is served under. */ path: string; /** Serve `index.html` for unmatched paths under `path`. */ spa: boolean; }): { fileCount: number; dir: string; }; export declare function buildStaticBundle(options: { projectRoot: string; appName: string; assetsDir: string; outDir: string; runtimeRange: string; /** Public base path. Default `/` — a standalone bundle owns its origin. */ path?: string; /** Serve `index.html` for unmatched paths. Default `true`. */ spa?: boolean; }): { outDir: string; manifest: RebaseBundleManifest; fileCount: number; }; export declare function resolveCliVersion(): string;