alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
168 lines (158 loc) • 6.1 kB
text/typescript
import { $atom, type Static, z } from "alepha";
/**
* Platform deployment configuration atom.
*
* Filled from the `platform` section of `alepha.config.ts`.
* Read by `PlatformCommand` to resolve environments and adapters.
*/
export const platformOptions = $atom({
name: "alepha.cli.platform.options",
description: "Platform deployment configuration",
schema: z
.object({
/**
* Project name override. Defaults to root package.json "name".
*/
name: z.text().optional(),
/**
* Default environment when --env is omitted.
*
* @default "production"
*/
default: z.text().optional(),
/**
* Multi-tenancy mode — controls whether `--tenant <slug>` is
* accepted/required and how it shapes resource names + the domain.
*
* - `none` (default): single instance. `--tenant` is rejected.
* - `required`: every deploy needs `--tenant`; resources are named
* `<tenant>-<project>-<env>` and the host becomes
* `<tenant>.<domain>`. Omitting it errors.
* - `optional`: a base instance (no `--tenant`) plus per-tenant
* instances coexist.
*
* @default "none"
*/
tenancy: z.enum(["none", "optional", "required"]).optional(),
/**
* Secret store configuration for syncing .env secrets
* to external providers (e.g. GitHub Actions environments).
*/
secrets: z
.object({
/**
* Explicit override of the worker secret-key allowlist.
*
* By default the deploy `secrets` step uses the build manifest's
* `env` list (every key the app declares via `$env`, captured at
* build time) as the allowlist, resolving each value from
* `.env.<env>[.local]` first, then `process.env`. This lets CI
* deliver secrets via the job environment (no `.env` file on the
* runner) while only ever pushing declared keys — ambient runner
* vars (PATH, GITHUB_*, …) can never leak.
*
* Set `keys` to override that auto-detected list (e.g. to narrow it,
* or to add a key read via `process.env` rather than `$env`). When
* neither this nor a manifest is present, the `.env.<env>` file is
* itself the allowlist (legacy fallback).
*/
keys: z.array(z.text()).optional(),
/**
* Secret store backend.
*/
store: z.enum(["github"]).optional(),
/**
* Pattern for resolving environment names in the store.
* Placeholders: {project}, {env}.
*
* @default "{project}-{env}"
*/
environmentPattern: z.text().optional(),
})
.optional(),
/**
* Named environments with their adapter and configuration.
*/
environments: z.record(
z.text({
description:
"Environment name (e.g. 'production', 'staging', 'preview'). Used in resource naming and selected via --env.",
}),
z.object({
adapter: z.enum(["cloudflare", "vercel"]),
/**
* Custom domain for the deployed worker (e.g. "api.example.com").
*
* On Cloudflare this is attached as a custom-domain route.
* Omit to use the adapter's default `*.workers.dev` / preview URL.
*
* Wildcards are supported for multi-tenant SaaS apps:
* `"*.club.alepha.dev"` routes every subdomain to the worker.
* Wildcard patterns require `zone` to be set, and the wildcard DNS
* record must already exist (proxied) in the Cloudflare zone.
*/
domain: z.text().optional(),
/**
* Cloudflare zone name (e.g. "alepha.dev") that owns `domain`.
*
* Required when `domain` contains a wildcard (`*`). For a plain
* host, setting `zone` switches the binding from a Custom Domain to
* a zone *Route* (`domain/*`) — needed when another Worker holds a
* wildcard route covering the host (routes win by specificity,
* while a Custom Domain would lose to the wildcard route).
*/
zone: z.text().optional(),
/**
* Worker-to-worker service bindings, e.g.
* `[{ binding: "CLUB", service: "club-staging" }]`.
*
* Exposed on the runtime `env` (Alepha store key `cloudflare.env`).
* Use a binding to fetch() a sibling Worker on the same zone —
* plain subrequests to a host served by a same-zone Worker route
* bypass the route and 522.
*/
services: z
.array(
z.object({
binding: z.text(),
service: z.text(),
}),
)
.optional(),
/**
* Cloudflare data jurisdiction for R2 buckets and D1 databases.
* - "eu": data stays within the EU
* - "fedramp": FedRAMP-authorized regions
*
* Omit for the default (global) jurisdiction.
*/
jurisdiction: z.enum(["eu", "fedramp"]).optional(),
/**
* Cloudflare account ID to deploy into.
*
* Falls back to `CLOUDFLARE_ACCOUNT_ID` env var, then to the
* token's account when the token is scoped to exactly one.
* Required when the token has access to multiple accounts.
*/
accountId: z.text().optional(),
}),
),
})
.optional(),
});
/**
* Type for platform options.
*/
export type PlatformOptions = Static<typeof platformOptions.schema>;
/**
* Configuration for a single named environment.
*/
export interface EnvironmentConfig {
adapter: "cloudflare" | "vercel";
domain?: string;
zone?: string;
vars?: Record<string, string>;
jurisdiction?: "eu" | "fedramp";
accountId?: string;
services?: Array<{ binding: string; service: string }>;
}