deepspace 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.36.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `createDeepSpaceAuth` takes an `errorURL` for OAuth failures that can't be tied back to their flow, so they land on the app's own page instead of Better Auth's error page.
8
+
9
+ ### Patch Changes
10
+
11
+ - Recover interrupted JobRoom jobs promptly after a room restart.
12
+
13
+ ## 0.35.0
14
+
15
+ ### Minor Changes
16
+
17
+ - `deepspace/expo`: closing the sign-in sheet now throws `DeepSpaceSignInCancelledError` (`code: 'sign_in_cancelled'`), so apps can tell it apart from a failed sign-in and skip the error message.
18
+ - Add the `medical` plane: `DEEPSPACE_ENV=medical` selects DeepSpace Medical's services on deepspacemedical.com, pages on deepspacemedical.app and deepspacemedical.com resolve to the medical plane instead of production, and the sign-in overlay links to the plane's own privacy policy. Every plane's URLs now come from one table, exported from `deepspace/worker` as `PLANES` along with `isRestrictedPlane`. The CLI omits the dashboard line on a plane without a dashboard.
19
+
20
+ ### Patch Changes
21
+
22
+ - Use neutral example values (`myapp://auth/callback`, `example.app.space`) in the Expo client's docs and the app template's native-callback comments.
23
+
3
24
  ## 0.34.0
4
25
 
5
26
  ### Minor Changes
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/build/app-id.ts","../src/server/utils/registry-client.ts","../src/cli/lib/wrangler-env.ts","../src/build/plugin.ts"],"sourcesContent":["/**\n * `deepspace/build` — build-time helpers an app's Vite config imports.\n *\n * Node-only. Nothing here may be reached from client or worker code; it runs\n * while `vite build` / `vite dev` is assembling the bundle, never inside it.\n *\n * The one job today is resolving the app's immutable id for the browser.\n * `wrangler.toml` is the single source of truth for app identity — the worker\n * reads `DEEPSPACE_APP_ID` off `env`, and every durable thing (data scope\n * `app:{appId}`, secrets, billing) keys to it. The browser has no `env`, so\n * the build stamps the same value in as `__DEEPSPACE_APP_ID__`, read from the\n * config THIS build is running against. A literal baked into the client once,\n * at scaffold time, is what let `deepspace deploy --env staging` ship a worker\n * writing staging's rooms beside a browser reading production's, with nothing\n * reporting the split.\n *\n * Living in the SDK rather than in a file copied into every app means one\n * definition, no per-app duplicate, and `smol-toml` stays the SDK's dependency\n * instead of every app's.\n */\n\nimport { resolve } from 'node:path'\n\n/** `app_` + 26-char Crockford ULID. */\n// Single source: the shared registry client owns the id shape.\nimport { APP_ID_RE } from '../server/utils/registry-client'\n// Single source: `cli/lib/wrangler-env` is the SDK's only wrangler.toml reader,\n// so the build sees the same parse, the same failure shape, and the same\n// whole-file guards the CLI does. Both are Node-only. Imported as that one\n// module, never through the CLI barrel — nothing else of the CLI belongs in an\n// app's build graph.\nimport { readAppIdVar, readWranglerConfigFile } from '../cli/lib/wrangler-env'\n\n/**\n * The build's environment, typed structurally. This module is imported from an\n * app's `vite.config.ts`, which sits outside the app's tsconfig `include` and\n * runs where the app carries no `@types/node`, so nothing here leans on the\n * ambient `NodeJS` namespace.\n */\nexport type BuildEnv = Record<string, string | undefined>\n\nexport interface ResolveAppIdOptions {\n /**\n * The app root — the directory holding `wrangler.toml`. The caller passes\n * its own location (`fileURLToPath(new URL('.', import.meta.url))` from\n * `vite.config.ts`), so the config is found by an authoritative anchor\n * rather than a guessed `process.cwd()`.\n */\n appDir: string\n /** Defaults to `process.env`; injectable for tests. */\n env?: BuildEnv\n}\n\n/**\n * Resolve the app id the browser bundle must carry.\n *\n * Mirrors the Cloudflare Vite plugin so dev, a bare `vite build`, and\n * `deepspace deploy --env <name>` all agree:\n *\n * config ← CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH, else <appDir>/wrangler.toml\n * section ← [env.<CLOUDFLARE_ENV>.vars] when that is set, else [vars]\n *\n * Env blocks do NOT inherit `[vars]` — each environment is its own app — so a\n * missing or malformed id throws and fails the build. It never falls back to\n * another environment's id: a fallback that silently picks the wrong one is\n * exactly the bug this exists to remove.\n */\nexport function resolveAppId({ appDir, env = process.env }: ResolveAppIdOptions): string {\n const configPath = env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH\n ? resolve(env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH)\n : resolve(appDir, 'wrangler.toml')\n const envName = env.CLOUDFLARE_ENV\n const section = envName ? `[env.${envName}.vars]` : '[vars]'\n\n const appId = readAppIdVar(readWranglerConfigFile(configPath), envName)\n if (typeof appId !== 'string' || !APP_ID_RE.test(appId)) {\n // A fresh-but-uninitialized scaffold (signed out at scaffold time) still\n // carries the __APP_ID__ placeholder — the remedy is registration, not\n // hand-writing an id (ids are server-minted at `deepspace app init`).\n const uninitialized = appId === undefined || appId === '__APP_ID__'\n throw new Error(\n `${configPath}: ${section} has no valid DEEPSPACE_APP_ID ` +\n `(found ${appId === undefined ? 'nothing' : JSON.stringify(appId)}). ` +\n `The browser keys its RecordRooms to this id, so the build stops rather than ship a wrong or empty one. ` +\n (uninitialized\n ? `Run \\`npx deepspace app init${envName ? ` --env ${envName}` : ''}\\` to register this app — ids are server-minted; never hand-write one.`\n : `Add the id your \\`deepspace app init\\` registered under ${section}.`) +\n (envName ? ` Environments do not inherit the top-level id; each one is its own app.` : ``),\n )\n }\n return appId\n}\n\n/**\n * Vite `define` entry consumed by an app's `vite.config.ts` and\n * `vitest.config.ts`, so unit tests see the same id the bundle is built with:\n *\n * ```ts\n * import { fileURLToPath } from 'node:url'\n * import { appIdDefine } from 'deepspace/build'\n * export default defineConfig({\n * define: appIdDefine({ appDir: fileURLToPath(new URL('.', import.meta.url)) }),\n * })\n * ```\n */\nexport function appIdDefine(options: ResolveAppIdOptions): Record<string, string> {\n return { __DEEPSPACE_APP_ID__: JSON.stringify(resolveAppId(options)) }\n}\n\n/**\n * How an app scaffolded before the build-injected id adopts it: one edit per\n * file, stated ONCE so the deploy refusals (`app_id_env_mismatch`,\n * `app_id_define_unsubstituted`) and `app update` guidance cannot disagree\n * about what it is. Keyed by the file each manual edit lands in.\n */\nexport const APP_ID_ADOPTION_EDITS = {\n 'vite.config.ts':\n `import { deepspaceBuild } from 'deepspace/build' and add\\n` +\n ` deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins`,\n 'vitest.config.ts': `import { appIdDefine } and set define: appIdDefine({ appDir })`,\n 'src/constants.ts':\n `declare const __DEEPSPACE_APP_ID__: string; export it as APP_ID\\n` +\n ` (drop the frozen literal)`,\n} as const\n\n/** The three edits are one change; this is the sentence that says so. */\nexport const APP_ID_ADOPTION_TOGETHER =\n `Apply all three together: src/constants.ts on its own builds clean and then throws ` +\n `in the browser. A newly scaffolded app already has this.`\n\n/** The full retrofit as the deploy refusals print it. */\nexport const APP_ID_ADOPTION_STEPS =\n `Point the client at the build-injected id — the SDK's \\`deepspace/build\\` supplies it:\\n` +\n Object.entries(APP_ID_ADOPTION_EDITS)\n .map(([file, edit]) => ` • ${file}: ${edit}\\n`)\n .join('') +\n APP_ID_ADOPTION_TOGETHER\n","/**\n * The ONE registry client. Every platform worker reaches the app-registry\n * Durable Object (hosted by the deploy worker, singleton `v1:global`) through\n * a cross-script REGISTRY binding — and before this module, each of the five\n * workers hand-rolled its own copy of the same mechanics: the instance name\n * appeared in 6 places, the `https://registry/` URL in 6, `APP_ID_RE` in 5,\n * and `RegistryClientError` + `registryErrorJson` existed byte-identical in\n * two workers. Copies that must agree and nothing enforcing it.\n *\n * This module owns the MECHANICS: instance name, stub lookup, the wire call,\n * the error type, and the standard route mapping. Each worker still declares\n * its own typed convenience wrapper listing only the actions it uses — that\n * subset is genuinely per-worker; the plumbing is not.\n */\n\n/** The singleton registry instance — one DO holds every app/route/collaborator. */\nexport const REGISTRY_INSTANCE = 'v1:global'\n\n/** Public app ids have one canonical ULID-minted shape. */\nexport const APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/\nexport const STRICT_APP_ID_RE = APP_ID_RE\n\n/** Internal physical locators survive identity migration and are not public ids. */\nexport const RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —\n * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */\nexport function mintUlid(now = Date.now()): string {\n let ts = ''\n let t = now\n for (let i = 0; i < 10; i++) {\n ts = CROCKFORD[t % 32] + ts\n t = Math.floor(t / 32)\n }\n const rand = new Uint8Array(10)\n crypto.getRandomValues(rand)\n let rs = ''\n let acc = 0\n let bits = 0\n for (const byte of rand) {\n acc = (acc << 8) | byte\n bits += 8\n while (bits >= 5) {\n bits -= 5\n rs += CROCKFORD[(acc >> bits) & 31]\n }\n }\n return `${ts}${rs}`.slice(0, 26)\n}\n\n/** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the\n * deploy worker's authenticated `POST /api/apps/mint`, which registers the id\n * to its caller in the same step — an id with no owner never exists. */\nexport function mintAppId(now = Date.now()): string {\n return `app_${mintUlid(now)}`\n}\n\nexport class RegistryClientError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n ) {\n super(message)\n this.name = 'RegistryClientError'\n }\n}\n\nexport function registryStub(ns: DurableObjectNamespace): DurableObjectStub {\n return ns.get(ns.idFromName(REGISTRY_INSTANCE))\n}\n\n/**\n * One registry action over the wire. Non-2xx answers throw\n * {@link RegistryClientError} carrying the DO's structured `code` (or\n * `'error'` for a transport-shaped body) — callers distinguish a registry-\n * REPORTED miss (`code === 'not_found'`) from an outage by that code, never\n * by treating every throw as \"not found\".\n */\nexport async function callRegistry<T>(\n stub: DurableObjectStub,\n body: { action: string } & Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch('https://registry/', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n const json = (await res.json().catch(() => ({}))) as { error?: string; code?: string }\n if (!res.ok) {\n throw new RegistryClientError(\n json.error ?? `Registry error (${res.status})`,\n json.code ?? 'error',\n res.status,\n )\n }\n return json as T\n}\n\n/** Standard mapping of a registry failure onto a route response. */\nexport function registryErrorJson(\n c: { json: (body: unknown, status?: number) => Response },\n err: unknown,\n): Response {\n if (err instanceof RegistryClientError) {\n return c.json({ error: err.message, code: err.code }, err.status as 400)\n }\n throw err\n}\n","/**\n * Wrangler env resolution for DeepSpace's multi-environment deploys.\n *\n * Lets a single repo deploy to `<app>.app.space` and `<app>-staging.app.space`\n * (or any other named environment) from one `wrangler.toml`:\n *\n * name = \"hopkins\"\n * [vars]\n * APP_NAME = \"hopkins\"\n *\n * [env.staging]\n * name = \"hopkins-staging\"\n * [env.staging.vars]\n * APP_NAME = \"hopkins-staging\"\n *\n * Then `npx deepspace deploy --env staging` reads the override and ships to\n * `hopkins-staging.app.space`. The platform treats it as a fully separate\n * app — its own DO state, OAuth tokens, Stripe products, cron schedule.\n * No platform-side change needed beyond letting the CLI register a second\n * app under the same owner.\n *\n * DeepSpace keeps generated dev/test/deploy credentials in one `.dev.vars`\n * file. For `--env`, it flattens the selected Wrangler env into a generated\n * config for the Cloudflare Vite plugin instead of passing `CLOUDFLARE_ENV`;\n * otherwise Wrangler would look for a separate `.dev.vars.<env>` file.\n *\n * This module is also the ONE reader of `wrangler.toml` — `cli/lib/app-identity`\n * (ids) and `deepspace/build` (the browser's id define) both come through\n * {@link readWranglerConfigFile}, so there is one parse, one error shape, and\n * one place for a whole-file guard. It stays free of CLI-only imports for that\n * reason: the `deepspace/build` bundle pulls this in, and must not drag citty\n * or the prompt stack along with it.\n */\n\nimport { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { parse as parseToml, stringify as stringifyToml } from 'smol-toml'\nimport { resolveAppName } from '../../server/rooms/app-name'\nimport { APP_ID_RE } from '../../server/utils/registry-client'\n\n/** Shape of the bits of wrangler.toml that DeepSpace CLI commands read.\n * Add an optional field here whenever a new command starts consulting\n * the config — avoids one-off `WranglerConfig & { … }` casts at\n * call sites and keeps the canonical type in one place. */\nexport interface WranglerConfig {\n name?: string\n env?: Record<\n string,\n ({ name?: string; vars?: Record<string, unknown> } & Record<string, unknown>) | undefined\n >\n /** `[assets]` block (`directory`, `binding`, `not_found_handling`,\n * `run_worker_first`). Consumed by deploy.ts to merge extra\n * worker-first routes into the deployed metadata. */\n assets?: {\n directory?: string\n binding?: string\n not_found_handling?: string\n run_worker_first?: unknown\n }\n /** `[vars]` block — string-valued environment variables baked into\n * the worker at deploy time. */\n vars?: Record<string, unknown>\n}\n\nexport interface PreparedWranglerEnvConfig {\n configPath?: string\n cleanup(): void\n}\n\ntype TomlRecord = Record<string, unknown>\n\nconst NON_INHERITABLE_ENV_KEYS = new Set([\n 'define',\n 'vars',\n 'durable_objects',\n 'kv_namespaces',\n 'r2_buckets',\n 'ai_search_namespaces',\n 'ai_search',\n 'ratelimits',\n 'vectorize',\n 'services',\n 'queues',\n 'workflows',\n 'tail_consumers',\n 'secrets',\n 'secrets_store_secrets',\n])\n\nlet generatedConfigSeq = 0\n\n/**\n * Resolve the appName that this CLI invocation should target.\n *\n * Without `envName`: returns the top-level `name`.\n * With `envName`: returns `[env.<envName>].name` and requires that the\n * env block exists and declares a name. We refuse to\n * fall back to the top-level name because that would\n * silently deploy staging code to production.\n *\n * Returns a discriminated union so callers can surface a precise error\n * (matches the existing `resolveAppName` contract). A failure may carry a\n * `code`: the stable machine contract callers emit as `{ ok: false, code }`.\n * Absent, the failure is about the name itself (`invalid_app_name`) — a\n * missing env block is not, so it names itself.\n */\nexport function resolveAppNameForEnv(\n config: WranglerConfig,\n envName: string | undefined,\n): { ok: true; name: string; warning?: string } | { ok: false; reason: string; code?: string } {\n if (!envName) {\n return resolveAppName(config.name)\n }\n const envBlock = config.env?.[envName]\n if (!envBlock) {\n return {\n ok: false,\n code: 'missing_env_block',\n reason:\n `wrangler.toml: no [env.${envName}] block found. ` +\n `Add one with a distinct \\`name\\` to deploy this environment.`,\n }\n }\n if (typeof envBlock.name !== 'string' || envBlock.name.trim() === '') {\n return {\n ok: false,\n reason:\n `wrangler.toml: [env.${envName}].name is missing. ` +\n `Set a distinct app name (e.g. \"${typeof config.name === 'string' ? config.name : 'myapp'}-${envName}\") so this environment doesn't overwrite the default.`,\n }\n }\n if (envBlock.name === config.name) {\n return {\n ok: false,\n reason:\n `wrangler.toml: [env.${envName}].name \"${envBlock.name}\" is the same as the top-level name. ` +\n `Pick a distinct name (e.g. \"${config.name}-${envName}\") so deploys don't collide.`,\n }\n }\n return resolveAppName(envBlock.name)\n}\n\n/** DeepSpace owns one generated local env file for every Wrangler environment. */\nexport function devVarsPathFor(appDir: string): string {\n return join(appDir, '.dev.vars')\n}\n\nexport function prepareWranglerEnvConfig(\n appDir: string,\n envName: string | undefined,\n): PreparedWranglerEnvConfig {\n if (!envName) return { cleanup() {} }\n\n const config = readWranglerConfig(appDir)\n const resolved = resolveAppNameForEnv(config, envName)\n if (!resolved.ok) throw new Error(resolved.reason)\n const flattened = flattenWranglerEnvConfig(config as TomlRecord, envName)\n const configPath = join(\n appDir,\n `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`,\n )\n writeFileSync(\n configPath,\n [\n '# Generated by DeepSpace for this CLI run.',\n '# Do not edit; wrangler.toml remains the source of truth.',\n stringifyToml(flattened),\n ]\n .join('\\n')\n .replace(/\\s*$/u, '\\n'),\n )\n return {\n configPath,\n cleanup() {\n rmSync(configPath, { force: true })\n },\n }\n}\n\nexport function wranglerViteEnv(\n baseEnv: Record<string, string | undefined>,\n prepared: PreparedWranglerEnvConfig,\n extraEnv: Record<string, string | undefined> = {},\n): Record<string, string | undefined> {\n const env = { ...baseEnv, ...extraEnv }\n if (prepared.configPath) {\n delete env.CLOUDFLARE_ENV\n env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath\n }\n return env\n}\n\nfunction flattenWranglerEnvConfig(config: TomlRecord, envName: string): TomlRecord {\n const envRoot = asTomlRecord(config.env)\n const envBlock = asTomlRecord(envRoot?.[envName])\n if (!envBlock) {\n throw new Error(`wrangler.toml: no [env.${envName}] block found.`)\n }\n\n const flattened: TomlRecord = {}\n for (const [key, value] of Object.entries(config)) {\n if (key === 'env' || NON_INHERITABLE_ENV_KEYS.has(key)) continue\n flattened[key] = value\n }\n for (const [key, value] of Object.entries(envBlock)) {\n flattened[key] = value\n }\n // Durable Object declarations contain class bindings, not shared resource ids.\n // A named worker gets its own namespaces, so preserving the declarations is\n // both safe and required for SDK features to work outside the default env.\n if (!('durable_objects' in envBlock) && config.durable_objects) {\n flattened.durable_objects = config.durable_objects\n }\n const envVars = asTomlRecord(envBlock.vars) ?? {}\n if (!('APP_NAME' in envVars) && typeof envBlock.name === 'string') {\n flattened.vars = { ...envVars, APP_NAME: envBlock.name }\n }\n return flattened\n}\n\nfunction asTomlRecord(value: unknown): TomlRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as TomlRecord)\n : undefined\n}\n\nfunction safeGeneratedConfigSegment(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]/gu, '_')\n}\n\n/**\n * The one failure shape for reading wrangler.toml — unreadable, malformed, or\n * self-contradictory. Carries the absolute path so the user is not left\n * staring at a raw smol-toml stack trace guessing which config in a multi-app\n * repo is broken, and a machine `code` so `--json` callers keep getting one\n * (`cli-errors.errorCode` recognizes this class by name, since this module\n * must not import the CLI error stack — see the header).\n */\nexport class WranglerConfigError extends Error {\n constructor(\n public readonly path: string,\n message: string,\n public readonly code: string = 'invalid_config',\n public readonly cause?: unknown,\n ) {\n super(message)\n this.name = 'WranglerConfigError'\n }\n}\n\nfunction causeDetail(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * Read + parse one wrangler.toml by absolute path. The single reader: every\n * consumer of this file in the SDK (deploy/status/dev, `readAppId`, and the\n * build's `__DEEPSPACE_APP_ID__` define) lands here, so a config that is wrong\n * is refused once, the same way, wherever it is read.\n */\nexport function readWranglerConfigFile(path: string): WranglerConfig {\n let raw: string\n try {\n raw = readFileSync(path, 'utf-8')\n } catch (err) {\n const missing = (err as NodeJS.ErrnoException | null)?.code === 'ENOENT'\n throw new WranglerConfigError(\n path,\n missing ? noWranglerConfigMessage(path) : `wrangler.toml: could not read ${path}: ${causeDetail(err)}`,\n missing ? 'not_in_app_repo' : 'invalid_config',\n err,\n )\n }\n let config: WranglerConfig\n try {\n config = parseToml(raw) as WranglerConfig\n } catch (err) {\n throw new WranglerConfigError(\n path,\n `wrangler.toml: malformed TOML at ${path}: ${causeDetail(err)}`,\n 'invalid_config',\n err,\n )\n }\n assertOneSectionPerAppId(config, path)\n return config\n}\n\n/** Read + parse `<appDir>/wrangler.toml`. Caller may check existence first via\n * `hasWranglerConfig` to distinguish \"no app here\" from \"broken app here\". */\nexport function readWranglerConfig(appDir: string): WranglerConfig {\n return readWranglerConfigFile(join(appDir, 'wrangler.toml'))\n}\n\n/**\n * The raw `DEEPSPACE_APP_ID` of one section — `[env.<envName>.vars]` when an\n * env is named, `[vars]` otherwise. Env blocks do NOT inherit the top-level\n * id: each environment is its own app, and silently inheriting is what once\n * shipped a staging worker beside a production browser.\n */\nexport function readAppIdVar(config: WranglerConfig, envName?: string): unknown {\n return (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID\n}\n\n/**\n * Refuse a config that gives one app id to more than one section.\n *\n * Copying the `[vars]` block into `[env.staging.vars]` leaves both halves of a\n * build agreeing on the same app while the deploy targets another — the client\n * id-mismatch guard cannot fire, because there is no mismatch to see. Only\n * real (`app_…`-shaped) ids count, so a fresh scaffold's repeated `__APP_ID__`\n * placeholder is still initializable.\n */\nfunction assertOneSectionPerAppId(config: WranglerConfig, path: string): void {\n const sections = new Map<string, string[]>()\n const record = (section: string, value: unknown) => {\n if (typeof value !== 'string' || !APP_ID_RE.test(value)) return\n sections.set(value, [...(sections.get(value) ?? []), section])\n }\n record('[vars]', config.vars?.DEEPSPACE_APP_ID)\n for (const envName of Object.keys(config.env ?? {})) {\n record(`[env.${envName}.vars]`, config.env?.[envName]?.vars?.DEEPSPACE_APP_ID)\n }\n for (const [appId, where] of sections) {\n if (where.length < 2) continue\n throw new WranglerConfigError(\n path,\n `wrangler.toml: ${where.join(' and ')} ${where.length === 2 ? 'both' : 'all'} set DEEPSPACE_APP_ID = \"${appId}\" in ${path}. ` +\n `One id under several sections points every environment's data, secrets, and billing at the ` +\n `same app while the deploys go to different places — each environment is its own app — ` +\n `run \\`deepspace app init --env <name>\\` to mint one for it.`,\n 'duplicate_app_id',\n )\n }\n}\n\n/**\n * The one \"not in an app directory\" sentence — the reader's own, and what\n * every command that pre-checks for wrangler.toml says (`not_in_app_repo`),\n * so an agent meets one text with one remedy wherever it hits this.\n */\nexport function noWranglerConfigMessage(where: string): string {\n return `No wrangler.toml at ${where}. Are you in a DeepSpace app directory? (Scaffold one with \\`npm create deepspace\\`, or cd into an existing app.)`\n}\n\n/** Convenience: true when wrangler.toml exists at the standard path. */\nexport function hasWranglerConfig(appDir: string): boolean {\n return existsSync(join(appDir, 'wrangler.toml'))\n}\n","/**\n * `deepspaceBuild()` — the one Vite plugin an app's build config needs.\n *\n * It carries the build-time wiring that is identical in every DeepSpace app\n * and belongs to the SDK's contract rather than to app code:\n *\n * 1. the app id `define` (see ./app-id) — so the browser is keyed to the\n * environment THIS build targets, never a literal frozen at scaffold time;\n * 2. removing the preview `.dev.vars` the Cloudflare plugin drops beside the\n * built worker — DeepSpace has one local runtime, so that second plaintext\n * copy is not kept (`removeBuildDevVars` below; `deepspace deploy` calls\n * the same function on the artifact it ships);\n * 3. the client `dedupe` hint — two copies of React (or better-auth) break\n * the SDK's hooks, and which packages must be single-instance is the SDK's\n * knowledge, not the app's.\n *\n * Housing these here means a future fix to any of them ships in an SDK version\n * bump, with no edit to — and no migration of — a single app.\n *\n * The return is a structural Vite plugin: typing it against `vite` would make\n * the SDK carry a Vite dependency it otherwise does not need, and Vite's\n * plugin hooks are optional and method-shaped, so a compatible object is\n * assignable to `PluginOption` where an app composes it.\n */\n\nimport { existsSync, lstatSync, readdirSync, unlinkSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { appIdDefine } from './app-id'\nimport type { ResolveAppIdOptions } from './app-id'\n\n/**\n * Delete Cloudflare's preview-only `.dev.vars` copy from ONE built worker dir.\n * Returns whether a file was removed; throws rather than following a symlink\n * or unlinking a directory, so a booby-trapped build output cannot make this\n * delete something else.\n *\n * TWO call sites, deliberately, and this is the single implementation they\n * share (they used to have one each, with only the CLI's carrying the symlink\n * guard):\n *\n * 1. `closeBundle` below — every `vite build` of an app that uses\n * `deepspaceBuild()`, including plain `npm run build` in CI, so a generic\n * archive step cannot pick the plaintext file up.\n * 2. `cli/commands/deploy/build.ts` — the AUTHORITATIVE one: it runs against\n * the exact worker dir the deploy is about to read, covers apps that do\n * not use this plugin, and fails the deploy (`build_output_unsafe`) when\n * the path is unsafe.\n *\n * Neither subsumes the other — they have different lifetimes (any build vs.\n * the artifact that ships). See `docs/migrations/build-preview-secrets.md`.\n */\nexport function removeBuildDevVars(workerDir: string): boolean {\n const path = join(workerDir, '.dev.vars')\n if (!existsSync(path)) return false\n // lstat, not stat: a symlink reports as a link, not as its target's file.\n if (!lstatSync(path).isFile()) {\n throw new Error(`Refusing unsafe build secret path: ${path}`)\n }\n unlinkSync(path)\n return true\n}\n\n/**\n * Packages that must resolve to a single instance in the client graph. Two\n * Reacts throw \"Cannot read properties of null (reading 'useState')\"; two\n * better-auth clients split the session. The SDK owns this list because it is\n * the SDK's hooks and auth client that break when it is wrong.\n */\nexport const CLIENT_DEDUPE: readonly string[] = ['react', 'react-dom', 'better-auth']\n\n/** The subset of Vite's `Plugin` this helper implements — declared locally so\n * `deepspace/build` needs no `vite` dependency. Assignable to `PluginOption`. */\ninterface VitePluginLike {\n name: string\n enforce?: 'pre' | 'post'\n config(): { define: Record<string, string>; resolve: { dedupe: string[] } }\n closeBundle(): void\n}\n\nexport type DeepspaceBuildOptions = ResolveAppIdOptions\n\nexport function deepspaceBuild(options: DeepspaceBuildOptions): VitePluginLike {\n const distDir = resolve(options.appDir, 'dist')\n return {\n name: 'deepspace-build',\n enforce: 'post',\n config() {\n return {\n define: appIdDefine(options),\n resolve: { dedupe: [...CLIENT_DEDUPE] },\n }\n },\n closeBundle() {\n // Cloudflare emits a preview-only `.dev.vars` beside each built worker.\n // Sweep every worker dir before reporting an unsafe one: an early throw\n // would leave the plaintext copies after it in place (fail-open).\n if (!existsSync(distDir)) return\n const unsafe: string[] = []\n for (const entry of readdirSync(distDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n try {\n removeBuildDevVars(join(distDir, entry.name))\n } catch (err) {\n unsafe.push(err instanceof Error ? err.message : String(err))\n }\n }\n if (unsafe.length > 0) throw new Error(unsafe.join('\\n'))\n },\n }\n}\n"],"mappings":"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","names":["existsSync","join","resolve","join","existsSync","resolve"]}
1
+ {"version":3,"sources":["../src/build/app-id.ts","../src/server/utils/registry-client.ts","../src/cli/lib/wrangler-env.ts","../src/build/plugin.ts"],"sourcesContent":["/**\n * `deepspace/build` — build-time helpers an app's Vite config imports.\n *\n * Node-only. Nothing here may be reached from client or worker code; it runs\n * while `vite build` / `vite dev` is assembling the bundle, never inside it.\n *\n * The one job today is resolving the app's immutable id for the browser.\n * `wrangler.toml` is the single source of truth for app identity — the worker\n * reads `DEEPSPACE_APP_ID` off `env`, and every durable thing (data scope\n * `app:{appId}`, secrets, billing) keys to it. The browser has no `env`, so\n * the build stamps the same value in as `__DEEPSPACE_APP_ID__`, read from the\n * config THIS build is running against. A literal baked into the client once,\n * at scaffold time, is what let `deepspace deploy --env staging` ship a worker\n * writing staging's rooms beside a browser reading production's, with nothing\n * reporting the split.\n *\n * Living in the SDK rather than in a file copied into every app means one\n * definition, no per-app duplicate, and `smol-toml` stays the SDK's dependency\n * instead of every app's.\n */\n\nimport { resolve } from 'node:path'\n\n/** `app_` + 26-char Crockford ULID. */\n// Single source: the shared registry client owns the id shape.\nimport { APP_ID_RE } from '../server/utils/registry-client'\n// Single source: `cli/lib/wrangler-env` is the SDK's only wrangler.toml reader,\n// so the build sees the same parse, the same failure shape, and the same\n// whole-file guards the CLI does. Both are Node-only. Imported as that one\n// module, never through the CLI barrel — nothing else of the CLI belongs in an\n// app's build graph.\nimport { readAppIdVar, readWranglerConfigFile } from '../cli/lib/wrangler-env'\n\n/**\n * The build's environment, typed structurally. This module is imported from an\n * app's `vite.config.ts`, which sits outside the app's tsconfig `include` and\n * runs where the app carries no `@types/node`, so nothing here leans on the\n * ambient `NodeJS` namespace.\n */\nexport type BuildEnv = Record<string, string | undefined>\n\nexport interface ResolveAppIdOptions {\n /**\n * The app root — the directory holding `wrangler.toml`. The caller passes\n * its own location (`fileURLToPath(new URL('.', import.meta.url))` from\n * `vite.config.ts`), so the config is found by an authoritative anchor\n * rather than a guessed `process.cwd()`.\n */\n appDir: string\n /** Defaults to `process.env`; injectable for tests. */\n env?: BuildEnv\n}\n\n/**\n * Resolve the app id the browser bundle must carry.\n *\n * Mirrors the Cloudflare Vite plugin so dev, a bare `vite build`, and\n * `deepspace deploy --env <name>` all agree:\n *\n * config ← CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH, else <appDir>/wrangler.toml\n * section ← [env.<CLOUDFLARE_ENV>.vars] when that is set, else [vars]\n *\n * Env blocks do NOT inherit `[vars]` — each environment is its own app — so a\n * missing or malformed id throws and fails the build. It never falls back to\n * another environment's id: a fallback that silently picks the wrong one is\n * exactly the bug this exists to remove.\n */\nexport function resolveAppId({ appDir, env = process.env }: ResolveAppIdOptions): string {\n const configPath = env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH\n ? resolve(env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH)\n : resolve(appDir, 'wrangler.toml')\n const envName = env.CLOUDFLARE_ENV\n const section = envName ? `[env.${envName}.vars]` : '[vars]'\n\n const appId = readAppIdVar(readWranglerConfigFile(configPath), envName)\n if (typeof appId !== 'string' || !APP_ID_RE.test(appId)) {\n // A fresh-but-uninitialized scaffold (signed out at scaffold time) still\n // carries the __APP_ID__ placeholder — the remedy is registration, not\n // hand-writing an id (ids are server-minted at `deepspace app init`).\n const uninitialized = appId === undefined || appId === '__APP_ID__'\n throw new Error(\n `${configPath}: ${section} has no valid DEEPSPACE_APP_ID ` +\n `(found ${appId === undefined ? 'nothing' : JSON.stringify(appId)}). ` +\n `The browser keys its RecordRooms to this id, so the build stops rather than ship a wrong or empty one. ` +\n (uninitialized\n ? `Run \\`npx deepspace app init${envName ? ` --env ${envName}` : ''}\\` to register this app — ids are server-minted; never hand-write one.`\n : `Add the id your \\`deepspace app init\\` registered under ${section}.`) +\n (envName ? ` Environments do not inherit the top-level id; each one is its own app.` : ``),\n )\n }\n return appId\n}\n\n/**\n * Vite `define` entry consumed by an app's `vite.config.ts` and\n * `vitest.config.ts`, so unit tests see the same id the bundle is built with:\n *\n * ```ts\n * import { fileURLToPath } from 'node:url'\n * import { appIdDefine } from 'deepspace/build'\n * export default defineConfig({\n * define: appIdDefine({ appDir: fileURLToPath(new URL('.', import.meta.url)) }),\n * })\n * ```\n */\nexport function appIdDefine(options: ResolveAppIdOptions): Record<string, string> {\n return { __DEEPSPACE_APP_ID__: JSON.stringify(resolveAppId(options)) }\n}\n\n/**\n * How an app scaffolded before the build-injected id adopts it: one edit per\n * file, stated ONCE so the deploy refusals (`app_id_env_mismatch`,\n * `app_id_define_unsubstituted`) and `app update` guidance cannot disagree\n * about what it is. Keyed by the file each manual edit lands in.\n */\nexport const APP_ID_ADOPTION_EDITS = {\n 'vite.config.ts':\n `import { deepspaceBuild } from 'deepspace/build' and add\\n` +\n ` deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins`,\n 'vitest.config.ts': `import { appIdDefine } and set define: appIdDefine({ appDir })`,\n 'src/constants.ts':\n `declare const __DEEPSPACE_APP_ID__: string; export it as APP_ID\\n` +\n ` (drop the frozen literal)`,\n} as const\n\n/** The three edits are one change; this is the sentence that says so. */\nexport const APP_ID_ADOPTION_TOGETHER =\n `Apply all three together: src/constants.ts on its own builds clean and then throws ` +\n `in the browser. A newly scaffolded app already has this.`\n\n/** The full retrofit as the deploy refusals print it. */\nexport const APP_ID_ADOPTION_STEPS =\n `Point the client at the build-injected id — the SDK's \\`deepspace/build\\` supplies it:\\n` +\n Object.entries(APP_ID_ADOPTION_EDITS)\n .map(([file, edit]) => ` • ${file}: ${edit}\\n`)\n .join('') +\n APP_ID_ADOPTION_TOGETHER\n","/**\n * The ONE registry client. Every platform worker reaches the app-registry\n * Durable Object (hosted by the deploy worker, singleton `v1:global`) through\n * a cross-script REGISTRY binding — and before this module, each of the five\n * workers hand-rolled its own copy of the same mechanics: the instance name\n * appeared in 6 places, the `https://registry/` URL in 6, `APP_ID_RE` in 5,\n * and `RegistryClientError` + `registryErrorJson` existed byte-identical in\n * two workers. Copies that must agree and nothing enforcing it.\n *\n * This module owns the MECHANICS: instance name, stub lookup, the wire call,\n * the error type, and the standard route mapping. Each worker still declares\n * its own typed convenience wrapper listing only the actions it uses — that\n * subset is genuinely per-worker; the plumbing is not.\n */\n\n/** The singleton registry instance — one DO holds every app/route/collaborator. */\nexport const REGISTRY_INSTANCE = 'v1:global'\n\n/** Public app ids have one canonical ULID-minted shape. */\nexport const APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/\nexport const STRICT_APP_ID_RE = APP_ID_RE\n\n/** Internal physical locators survive identity migration and are not public ids. */\nexport const RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —\n * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */\nexport function mintUlid(now = Date.now()): string {\n let ts = ''\n let t = now\n for (let i = 0; i < 10; i++) {\n ts = CROCKFORD[t % 32] + ts\n t = Math.floor(t / 32)\n }\n const rand = new Uint8Array(10)\n crypto.getRandomValues(rand)\n let rs = ''\n let acc = 0\n let bits = 0\n for (const byte of rand) {\n acc = (acc << 8) | byte\n bits += 8\n while (bits >= 5) {\n bits -= 5\n rs += CROCKFORD[(acc >> bits) & 31]\n }\n }\n return `${ts}${rs}`.slice(0, 26)\n}\n\n/** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the\n * deploy worker's authenticated `POST /api/apps/mint`, which registers the id\n * to its caller in the same step — an id with no owner never exists. */\nexport function mintAppId(now = Date.now()): string {\n return `app_${mintUlid(now)}`\n}\n\nexport class RegistryClientError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n ) {\n super(message)\n this.name = 'RegistryClientError'\n }\n}\n\nexport function registryStub(ns: DurableObjectNamespace): DurableObjectStub {\n return ns.get(ns.idFromName(REGISTRY_INSTANCE))\n}\n\n/**\n * One registry action over the wire. Non-2xx answers throw\n * {@link RegistryClientError} carrying the DO's structured `code` (or\n * `'error'` for a transport-shaped body) — callers distinguish a registry-\n * REPORTED miss (`code === 'not_found'`) from an outage by that code, never\n * by treating every throw as \"not found\".\n */\nexport async function callRegistry<T>(\n stub: DurableObjectStub,\n body: { action: string } & Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch('https://registry/', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n const json = (await res.json().catch(() => ({}))) as { error?: string; code?: string }\n if (!res.ok) {\n throw new RegistryClientError(\n json.error ?? `Registry error (${res.status})`,\n json.code ?? 'error',\n res.status,\n )\n }\n return json as T\n}\n\n/** Standard mapping of a registry failure onto a route response. */\nexport function registryErrorJson(\n c: { json: (body: unknown, status?: number) => Response },\n err: unknown,\n): Response {\n if (err instanceof RegistryClientError) {\n return c.json({ error: err.message, code: err.code }, err.status as 400)\n }\n throw err\n}\n","/**\n * Wrangler env resolution for DeepSpace's multi-environment deploys.\n *\n * Lets a single repo deploy to `<app>.app.space` and `<app>-staging.app.space`\n * (or any other named environment) from one `wrangler.toml`:\n *\n * name = \"hopkins\"\n * [vars]\n * APP_NAME = \"hopkins\"\n *\n * [env.staging]\n * name = \"hopkins-staging\"\n * [env.staging.vars]\n * APP_NAME = \"hopkins-staging\"\n *\n * Then `npx deepspace deploy --env staging` reads the override and ships to\n * `hopkins-staging.app.space`. The platform treats it as a fully separate\n * app — its own DO state, OAuth tokens, Stripe products, cron schedule.\n * No platform-side change needed beyond letting the CLI register a second\n * app under the same owner.\n *\n * DeepSpace keeps generated dev/test/deploy credentials in one `.dev.vars`\n * file. For `--env`, it flattens the selected Wrangler env into a generated\n * config for the Cloudflare Vite plugin instead of passing `CLOUDFLARE_ENV`;\n * otherwise Wrangler would look for a separate `.dev.vars.<env>` file.\n *\n * This module is also the ONE reader of `wrangler.toml` — `cli/lib/app-identity`\n * (ids) and `deepspace/build` (the browser's id define) both come through\n * {@link readWranglerConfigFile}, so there is one parse, one error shape, and\n * one place for a whole-file guard. It stays free of CLI-only imports for that\n * reason: the `deepspace/build` bundle pulls this in, and must not drag citty\n * or the prompt stack along with it.\n */\n\nimport { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { parse as parseToml, stringify as stringifyToml } from 'smol-toml'\nimport { resolveAppName } from '../../server/rooms/app-name'\nimport { APP_ID_RE } from '../../server/utils/registry-client'\n\n/** Shape of the bits of wrangler.toml that DeepSpace CLI commands read.\n * Add an optional field here whenever a new command starts consulting\n * the config — avoids one-off `WranglerConfig & { … }` casts at\n * call sites and keeps the canonical type in one place. */\nexport interface WranglerConfig {\n name?: string\n env?: Record<\n string,\n ({ name?: string; vars?: Record<string, unknown> } & Record<string, unknown>) | undefined\n >\n /** `[assets]` block (`directory`, `binding`, `not_found_handling`,\n * `run_worker_first`). Consumed by deploy.ts to merge extra\n * worker-first routes into the deployed metadata. */\n assets?: {\n directory?: string\n binding?: string\n not_found_handling?: string\n run_worker_first?: unknown\n }\n /** `[vars]` block — string-valued environment variables baked into\n * the worker at deploy time. */\n vars?: Record<string, unknown>\n}\n\nexport interface PreparedWranglerEnvConfig {\n configPath?: string\n cleanup(): void\n}\n\ntype TomlRecord = Record<string, unknown>\n\nconst NON_INHERITABLE_ENV_KEYS = new Set([\n 'define',\n 'vars',\n 'durable_objects',\n 'kv_namespaces',\n 'r2_buckets',\n 'ai_search_namespaces',\n 'ai_search',\n 'ratelimits',\n 'vectorize',\n 'services',\n 'queues',\n 'workflows',\n 'tail_consumers',\n 'secrets',\n 'secrets_store_secrets',\n])\n\nlet generatedConfigSeq = 0\n\n/**\n * Resolve the appName that this CLI invocation should target.\n *\n * Without `envName`: returns the top-level `name`.\n * With `envName`: returns `[env.<envName>].name` and requires that the\n * env block exists and declares a name. We refuse to\n * fall back to the top-level name because that would\n * silently deploy staging code to production.\n *\n * Returns a discriminated union so callers can surface a precise error\n * (matches the existing `resolveAppName` contract). A failure may carry a\n * `code`: the stable machine contract callers emit as `{ ok: false, code }`.\n * Absent, the failure is about the name itself (`invalid_app_name`) — a\n * missing env block is not, so it names itself.\n */\nexport function resolveAppNameForEnv(\n config: WranglerConfig,\n envName: string | undefined,\n): { ok: true; name: string; warning?: string } | { ok: false; reason: string; code?: string } {\n if (!envName) {\n return resolveAppName(config.name)\n }\n const envBlock = config.env?.[envName]\n if (!envBlock) {\n return {\n ok: false,\n code: 'missing_env_block',\n reason:\n `wrangler.toml: no [env.${envName}] block found. ` +\n `Add one with a distinct \\`name\\` to deploy this environment.`,\n }\n }\n if (typeof envBlock.name !== 'string' || envBlock.name.trim() === '') {\n return {\n ok: false,\n reason:\n `wrangler.toml: [env.${envName}].name is missing. ` +\n `Set a distinct app name (e.g. \"${typeof config.name === 'string' ? config.name : 'myapp'}-${envName}\") so this environment doesn't overwrite the default.`,\n }\n }\n if (envBlock.name === config.name) {\n return {\n ok: false,\n reason:\n `wrangler.toml: [env.${envName}].name \"${envBlock.name}\" is the same as the top-level name. ` +\n `Pick a distinct name (e.g. \"${config.name}-${envName}\") so deploys don't collide.`,\n }\n }\n return resolveAppName(envBlock.name)\n}\n\n/** DeepSpace owns one generated local env file for every Wrangler environment. */\nexport function devVarsPathFor(appDir: string): string {\n return join(appDir, '.dev.vars')\n}\n\nexport function prepareWranglerEnvConfig(\n appDir: string,\n envName: string | undefined,\n): PreparedWranglerEnvConfig {\n if (!envName) return { cleanup() {} }\n\n const config = readWranglerConfig(appDir)\n const resolved = resolveAppNameForEnv(config, envName)\n if (!resolved.ok) throw new Error(resolved.reason)\n const flattened = flattenWranglerEnvConfig(config as TomlRecord, envName)\n const configPath = join(\n appDir,\n `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`,\n )\n writeFileSync(\n configPath,\n [\n '# Generated by DeepSpace for this CLI run.',\n '# Do not edit; wrangler.toml remains the source of truth.',\n stringifyToml(flattened),\n ]\n .join('\\n')\n .replace(/\\s*$/u, '\\n'),\n )\n return {\n configPath,\n cleanup() {\n rmSync(configPath, { force: true })\n },\n }\n}\n\nexport function wranglerViteEnv(\n baseEnv: NodeJS.ProcessEnv,\n prepared: PreparedWranglerEnvConfig,\n extraEnv: NodeJS.ProcessEnv = {},\n): NodeJS.ProcessEnv {\n const env = { ...baseEnv, ...extraEnv }\n if (prepared.configPath) {\n delete env.CLOUDFLARE_ENV\n env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = prepared.configPath\n }\n return env\n}\n\nfunction flattenWranglerEnvConfig(config: TomlRecord, envName: string): TomlRecord {\n const envRoot = asTomlRecord(config.env)\n const envBlock = asTomlRecord(envRoot?.[envName])\n if (!envBlock) {\n throw new Error(`wrangler.toml: no [env.${envName}] block found.`)\n }\n\n const flattened: TomlRecord = {}\n for (const [key, value] of Object.entries(config)) {\n if (key === 'env' || NON_INHERITABLE_ENV_KEYS.has(key)) continue\n flattened[key] = value\n }\n for (const [key, value] of Object.entries(envBlock)) {\n flattened[key] = value\n }\n // Durable Object declarations contain class bindings, not shared resource ids.\n // A named worker gets its own namespaces, so preserving the declarations is\n // both safe and required for SDK features to work outside the default env.\n if (!('durable_objects' in envBlock) && config.durable_objects) {\n flattened.durable_objects = config.durable_objects\n }\n const envVars = asTomlRecord(envBlock.vars) ?? {}\n if (!('APP_NAME' in envVars) && typeof envBlock.name === 'string') {\n flattened.vars = { ...envVars, APP_NAME: envBlock.name }\n }\n return flattened\n}\n\nfunction asTomlRecord(value: unknown): TomlRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as TomlRecord)\n : undefined\n}\n\nfunction safeGeneratedConfigSegment(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]/gu, '_')\n}\n\n/**\n * The one failure shape for reading wrangler.toml — unreadable, malformed, or\n * self-contradictory. Carries the absolute path so the user is not left\n * staring at a raw smol-toml stack trace guessing which config in a multi-app\n * repo is broken, and a machine `code` so `--json` callers keep getting one\n * (`cli-errors.errorCode` recognizes this class by name, since this module\n * must not import the CLI error stack — see the header).\n */\nexport class WranglerConfigError extends Error {\n constructor(\n public readonly path: string,\n message: string,\n public readonly code: string = 'invalid_config',\n public readonly cause?: unknown,\n ) {\n super(message)\n this.name = 'WranglerConfigError'\n }\n}\n\nfunction causeDetail(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * Read + parse one wrangler.toml by absolute path. The single reader: every\n * consumer of this file in the SDK (deploy/status/dev, `readAppId`, and the\n * build's `__DEEPSPACE_APP_ID__` define) lands here, so a config that is wrong\n * is refused once, the same way, wherever it is read.\n */\nexport function readWranglerConfigFile(path: string): WranglerConfig {\n let raw: string\n try {\n raw = readFileSync(path, 'utf-8')\n } catch (err) {\n const missing = (err as NodeJS.ErrnoException | null)?.code === 'ENOENT'\n throw new WranglerConfigError(\n path,\n missing ? noWranglerConfigMessage(path) : `wrangler.toml: could not read ${path}: ${causeDetail(err)}`,\n missing ? 'not_in_app_repo' : 'invalid_config',\n err,\n )\n }\n let config: WranglerConfig\n try {\n config = parseToml(raw) as WranglerConfig\n } catch (err) {\n throw new WranglerConfigError(\n path,\n `wrangler.toml: malformed TOML at ${path}: ${causeDetail(err)}`,\n 'invalid_config',\n err,\n )\n }\n assertOneSectionPerAppId(config, path)\n return config\n}\n\n/** Read + parse `<appDir>/wrangler.toml`. Caller may check existence first via\n * `hasWranglerConfig` to distinguish \"no app here\" from \"broken app here\". */\nexport function readWranglerConfig(appDir: string): WranglerConfig {\n return readWranglerConfigFile(join(appDir, 'wrangler.toml'))\n}\n\n/**\n * The raw `DEEPSPACE_APP_ID` of one section — `[env.<envName>.vars]` when an\n * env is named, `[vars]` otherwise. Env blocks do NOT inherit the top-level\n * id: each environment is its own app, and silently inheriting is what once\n * shipped a staging worker beside a production browser.\n */\nexport function readAppIdVar(config: WranglerConfig, envName?: string): unknown {\n return (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID\n}\n\n/**\n * Refuse a config that gives one app id to more than one section.\n *\n * Copying the `[vars]` block into `[env.staging.vars]` leaves both halves of a\n * build agreeing on the same app while the deploy targets another — the client\n * id-mismatch guard cannot fire, because there is no mismatch to see. Only\n * real (`app_…`-shaped) ids count, so a fresh scaffold's repeated `__APP_ID__`\n * placeholder is still initializable.\n */\nfunction assertOneSectionPerAppId(config: WranglerConfig, path: string): void {\n const sections = new Map<string, string[]>()\n const record = (section: string, value: unknown) => {\n if (typeof value !== 'string' || !APP_ID_RE.test(value)) return\n sections.set(value, [...(sections.get(value) ?? []), section])\n }\n record('[vars]', config.vars?.DEEPSPACE_APP_ID)\n for (const envName of Object.keys(config.env ?? {})) {\n record(`[env.${envName}.vars]`, config.env?.[envName]?.vars?.DEEPSPACE_APP_ID)\n }\n for (const [appId, where] of sections) {\n if (where.length < 2) continue\n throw new WranglerConfigError(\n path,\n `wrangler.toml: ${where.join(' and ')} ${where.length === 2 ? 'both' : 'all'} set DEEPSPACE_APP_ID = \"${appId}\" in ${path}. ` +\n `One id under several sections points every environment's data, secrets, and billing at the ` +\n `same app while the deploys go to different places — each environment is its own app — ` +\n `run \\`deepspace app init --env <name>\\` to mint one for it.`,\n 'duplicate_app_id',\n )\n }\n}\n\n/**\n * The one \"not in an app directory\" sentence — the reader's own, and what\n * every command that pre-checks for wrangler.toml says (`not_in_app_repo`),\n * so an agent meets one text with one remedy wherever it hits this.\n */\nexport function noWranglerConfigMessage(where: string): string {\n return `No wrangler.toml at ${where}. Are you in a DeepSpace app directory? (Scaffold one with \\`npm create deepspace\\`, or cd into an existing app.)`\n}\n\n/** Convenience: true when wrangler.toml exists at the standard path. */\nexport function hasWranglerConfig(appDir: string): boolean {\n return existsSync(join(appDir, 'wrangler.toml'))\n}\n","/**\n * `deepspaceBuild()` — the one Vite plugin an app's build config needs.\n *\n * It carries the build-time wiring that is identical in every DeepSpace app\n * and belongs to the SDK's contract rather than to app code:\n *\n * 1. the app id `define` (see ./app-id) — so the browser is keyed to the\n * environment THIS build targets, never a literal frozen at scaffold time;\n * 2. removing the preview `.dev.vars` the Cloudflare plugin drops beside the\n * built worker — DeepSpace has one local runtime, so that second plaintext\n * copy is not kept (`removeBuildDevVars` below; `deepspace deploy` calls\n * the same function on the artifact it ships);\n * 3. the client `dedupe` hint — two copies of React (or better-auth) break\n * the SDK's hooks, and which packages must be single-instance is the SDK's\n * knowledge, not the app's.\n *\n * Housing these here means a future fix to any of them ships in an SDK version\n * bump, with no edit to — and no migration of — a single app.\n *\n * The return is a structural Vite plugin: typing it against `vite` would make\n * the SDK carry a Vite dependency it otherwise does not need, and Vite's\n * plugin hooks are optional and method-shaped, so a compatible object is\n * assignable to `PluginOption` where an app composes it.\n */\n\nimport { existsSync, lstatSync, readdirSync, unlinkSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { appIdDefine } from './app-id'\nimport type { ResolveAppIdOptions } from './app-id'\n\n/**\n * Delete Cloudflare's preview-only `.dev.vars` copy from ONE built worker dir.\n * Returns whether a file was removed; throws rather than following a symlink\n * or unlinking a directory, so a booby-trapped build output cannot make this\n * delete something else.\n *\n * TWO call sites, deliberately, and this is the single implementation they\n * share (they used to have one each, with only the CLI's carrying the symlink\n * guard):\n *\n * 1. `closeBundle` below — every `vite build` of an app that uses\n * `deepspaceBuild()`, including plain `npm run build` in CI, so a generic\n * archive step cannot pick the plaintext file up.\n * 2. `cli/commands/deploy/build.ts` — the AUTHORITATIVE one: it runs against\n * the exact worker dir the deploy is about to read, covers apps that do\n * not use this plugin, and fails the deploy (`build_output_unsafe`) when\n * the path is unsafe.\n *\n * Neither subsumes the other — they have different lifetimes (any build vs.\n * the artifact that ships). See `docs/migrations/build-preview-secrets.md`.\n */\nexport function removeBuildDevVars(workerDir: string): boolean {\n const path = join(workerDir, '.dev.vars')\n if (!existsSync(path)) return false\n // lstat, not stat: a symlink reports as a link, not as its target's file.\n if (!lstatSync(path).isFile()) {\n throw new Error(`Refusing unsafe build secret path: ${path}`)\n }\n unlinkSync(path)\n return true\n}\n\n/**\n * Packages that must resolve to a single instance in the client graph. Two\n * Reacts throw \"Cannot read properties of null (reading 'useState')\"; two\n * better-auth clients split the session. The SDK owns this list because it is\n * the SDK's hooks and auth client that break when it is wrong.\n */\nexport const CLIENT_DEDUPE: readonly string[] = ['react', 'react-dom', 'better-auth']\n\n/** The subset of Vite's `Plugin` this helper implements — declared locally so\n * `deepspace/build` needs no `vite` dependency. Assignable to `PluginOption`. */\ninterface VitePluginLike {\n name: string\n enforce?: 'pre' | 'post'\n config(): { define: Record<string, string>; resolve: { dedupe: string[] } }\n closeBundle(): void\n}\n\nexport type DeepspaceBuildOptions = ResolveAppIdOptions\n\nexport function deepspaceBuild(options: DeepspaceBuildOptions): VitePluginLike {\n const distDir = resolve(options.appDir, 'dist')\n return {\n name: 'deepspace-build',\n enforce: 'post',\n config() {\n return {\n define: appIdDefine(options),\n resolve: { dedupe: [...CLIENT_DEDUPE] },\n }\n },\n closeBundle() {\n // Cloudflare emits a preview-only `.dev.vars` beside each built worker.\n // Sweep every worker dir before reporting an unsafe one: an early throw\n // would leave the plaintext copies after it in place (fail-open).\n if (!existsSync(distDir)) return\n const unsafe: string[] = []\n for (const entry of readdirSync(distDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n try {\n removeBuildDevVars(join(distDir, entry.name))\n } catch (err) {\n unsafe.push(err instanceof Error ? err.message : String(err))\n }\n }\n if (unsafe.length > 0) throw new Error(unsafe.join('\\n'))\n },\n }\n}\n"],"mappings":"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","names":["existsSync","join","resolve","join","existsSync","resolve"]}
@@ -1274,7 +1274,7 @@ function renderInlineMarkdown(value) {
1274
1274
  import { useEffect as useEffect6, useRef as useRef5, useState as useState5 } from "react";
1275
1275
 
1276
1276
  // package.json
1277
- var version = "0.34.0";
1277
+ var version = "0.36.0";
1278
1278
 
1279
1279
  // src/documentation/runtime/navigation.tsx
1280
1280
  import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -1954,4 +1954,4 @@ export {
1954
1954
  DefaultDocumentation,
1955
1955
  DocumentationContent
1956
1956
  };
1957
- //# sourceMappingURL=chunk-KELQ7OD5.js.map
1957
+ //# sourceMappingURL=chunk-C6TBE6C4.js.map