/** * `deepspace/build` — build-time helpers an app's Vite config imports. * * Node-only. Nothing here may be reached from client or worker code; it runs * while `vite build` / `vite dev` is assembling the bundle, never inside it. * * The one job today is resolving the app's immutable id for the browser. * `wrangler.toml` is the single source of truth for app identity — the worker * reads `DEEPSPACE_APP_ID` off `env`, and every durable thing (data scope * `app:{appId}`, secrets, billing) keys to it. The browser has no `env`, so * the build stamps the same value in as `__DEEPSPACE_APP_ID__`, read from the * config THIS build is running against. A literal baked into the client once, * at scaffold time, is what let `deepspace deploy --env staging` ship a worker * writing staging's rooms beside a browser reading production's, with nothing * reporting the split. * * Living in the SDK rather than in a file copied into every app means one * definition, no per-app duplicate, and `smol-toml` stays the SDK's dependency * instead of every app's. */ /** * The build's environment, typed structurally. This module is imported from an * app's `vite.config.ts`, which sits outside the app's tsconfig `include` and * runs where the app carries no `@types/node`, so nothing here leans on the * ambient `NodeJS` namespace. */ type BuildEnv = Record; interface ResolveAppIdOptions { /** * The app root — the directory holding `wrangler.toml`. The caller passes * its own location (`fileURLToPath(new URL('.', import.meta.url))` from * `vite.config.ts`), so the config is found by an authoritative anchor * rather than a guessed `process.cwd()`. */ appDir: string; /** Defaults to `process.env`; injectable for tests. */ env?: BuildEnv; } /** * Resolve the app id the browser bundle must carry. * * Mirrors the Cloudflare Vite plugin so dev, a bare `vite build`, and * `deepspace deploy --env ` all agree: * * config ← CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH, else /wrangler.toml * section ← [env..vars] when that is set, else [vars] * * Env blocks do NOT inherit `[vars]` — each environment is its own app — so a * missing or malformed id throws and fails the build. It never falls back to * another environment's id: a fallback that silently picks the wrong one is * exactly the bug this exists to remove. */ declare function resolveAppId({ appDir, env }: ResolveAppIdOptions): string; /** * Vite `define` entry consumed by an app's `vite.config.ts` and * `vitest.config.ts`, so unit tests see the same id the bundle is built with: * * ```ts * import { fileURLToPath } from 'node:url' * import { appIdDefine } from 'deepspace/build' * export default defineConfig({ * define: appIdDefine({ appDir: fileURLToPath(new URL('.', import.meta.url)) }), * }) * ``` */ declare function appIdDefine(options: ResolveAppIdOptions): Record; /** * `deepspaceBuild()` — the one Vite plugin an app's build config needs. * * It carries the build-time wiring that is identical in every DeepSpace app * and belongs to the SDK's contract rather than to app code: * * 1. the app id `define` (see ./app-id) — so the browser is keyed to the * environment THIS build targets, never a literal frozen at scaffold time; * 2. removing the preview `.dev.vars` the Cloudflare plugin drops beside the * built worker — DeepSpace has one local runtime, so that second plaintext * copy is not kept (`removeBuildDevVars` below; `deepspace deploy` calls * the same function on the artifact it ships); * 3. the client `dedupe` hint — two copies of React (or better-auth) break * the SDK's hooks, and which packages must be single-instance is the SDK's * knowledge, not the app's. * * Housing these here means a future fix to any of them ships in an SDK version * bump, with no edit to — and no migration of — a single app. * * The return is a structural Vite plugin: typing it against `vite` would make * the SDK carry a Vite dependency it otherwise does not need, and Vite's * plugin hooks are optional and method-shaped, so a compatible object is * assignable to `PluginOption` where an app composes it. */ /** * Packages that must resolve to a single instance in the client graph. Two * Reacts throw "Cannot read properties of null (reading 'useState')"; two * better-auth clients split the session. The SDK owns this list because it is * the SDK's hooks and auth client that break when it is wrong. */ declare const CLIENT_DEDUPE: readonly string[]; /** The subset of Vite's `Plugin` this helper implements — declared locally so * `deepspace/build` needs no `vite` dependency. Assignable to `PluginOption`. */ interface VitePluginLike { name: string; enforce?: 'pre' | 'post'; config(): { define: Record; resolve: { dedupe: string[]; }; }; closeBundle(): void; } type DeepspaceBuildOptions = ResolveAppIdOptions; declare function deepspaceBuild(options: DeepspaceBuildOptions): VitePluginLike; /** * `expoWeb()` — serve an app's Expo client on the web. * * For apps whose only UI is the Expo app in `mobile/` (iPhone, iPad, Mac): * at `vite build`, which `deepspace deploy` runs, it exports that app for * the web and writes it over the client assets the Worker serves, so one * React Native tree is also the app's web page. `deepspace/native` resolves * to its browser build there. * * It also writes the page as `_spa.html`, the shell the Worker's client-route * fallback serves, so a refresh at any path loads the app. * * ```ts * // vite.config.ts * plugins: [deepspaceBuild(), expoWeb({ mobileDir: fileURLToPath(new URL('./mobile', import.meta.url)) })] * ``` * * Structural like `deepspaceBuild()`, so the SDK carries no Vite dependency. */ interface ExpoWebOptions { /** The Expo app's directory (its own package.json and node_modules). */ mobileDir: string; } interface ExpoWebPlugin { name: string; apply: 'build'; applyToEnvironment(environment: { name: string; }): boolean; writeBundle(options: { dir?: string; }): void; } declare function expoWeb({ mobileDir }: ExpoWebOptions): ExpoWebPlugin; /** * Where a local run keeps its data: `servePreview` (deepspace/testing) sets * DEEPSPACE_TEST_STATE_DIR to a fresh directory per test run, so tests never * read or write the developer's usual local data. * * ```ts * plugins: [cloudflare(testState()), deepspaceBuild({ appDir })] * ``` */ declare const TEST_STATE_DIR_ENV = "DEEPSPACE_TEST_STATE_DIR"; declare function testState(env?: NodeJS.ProcessEnv): { persistState: { path: string; }; } | undefined; export { type BuildEnv, CLIENT_DEDUPE, type DeepspaceBuildOptions, type ExpoWebOptions, type ExpoWebPlugin, type ResolveAppIdOptions, TEST_STATE_DIR_ENV, appIdDefine, deepspaceBuild, expoWeb, resolveAppId, testState };