UNPKG

@lunora/cli

Version:

The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands

224 lines (182 loc) • 12.3 kB
--- name: lunora-deploy description: Deploys a Lunora app to Cloudflare Workers + Durable Objects and gets wrangler.jsonc, remote resources and secrets to line up. Covers `lunora deploy` (and `--env`, `--dry-run`, `--preview`, `--migrate`), the `lunora doctor` / `lunora verify` / `lunora prepare` preflights, Durable Object bindings (SHARD, SHARD_REGISTRY, SCHEDULER), the `DB` D1 binding and its placeholder id, R2 buckets, production secrets (`lunora env push`, `wrangler secret put`) versus `.dev.vars`, the schema-drift gate, the architecture diff, and rollback with `lunora cloudflare deployments`. Use when the user asks to "deploy", "ship to production", "set up a staging environment", wire CI deploys, or when a deploy fails on a binding, a placeholder `database_id`, an unexported Durable Object class, a missing secret, or "schema drift gate blocked deploy". --- # Lunora Deploy Deploying a Lunora app means making `wrangler.jsonc`, the remote Cloudflare resources (D1, R2, secrets) and the worker's exports agree. `lunora deploy` automates most of that and refuses to ship when they don't. Local development is `lunora dev` (see `lunora-quickstart`). If the deploy is blocked by a breaking schema change, stage it with `lunora-migration-helper` first. ## What `lunora deploy` does In order, stopping at the first failure: 1. **Codegen** — regenerates `lunora/_generated/`. ERROR-level codegen advisories fail the deploy in CI (`--strict-advisories` forces this locally, `--no-strict-advisories` turns it off). Nothing here runs `tsc` and wrangler strips types, so a project with a type error deploys — gate CI on `lunora verify`, which does type-check. 2. **Schema-drift gate** — compares the schema with the committed baseline `lunora/.lunora-schema.json` (details below). 3. **Architecture diff** — prints modules and edges (calls, table reads/writes, enqueues) added or removed since the last successful deploy. Informational only; the manifest is recorded in `.lunora/architecture[.<env>].json` after wrangler succeeds. 4. **Binding reconcile** — writes the bindings the code implies into `wrangler.jsonc`: Durable Object bindings plus their `new_sqlite_classes` migration, the `DB` D1 binding for `.global()` tables, crons, and the compatibility date. R2 buckets are not auto-written (the bucket name is yours to choose); you get a warning instead. 5. **Read-only checks** — blocks on a D1 `database_id` still set to `<replace-with-d1-create-id>`, on a loopback URL (`localhost`, `127.x`, `::1`) in `vars`, on a missing container Dockerfile, or on no Docker engine when a container builds locally. 6. **Validate `wrangler.jsonc`** — the `SHARD` binding to `ShardDO` (or `LunoraDO` for apps that merge their DOs), a minimum `compatibility_date`, DO classes registered in `migrations`, and the shape of every binding. `compatibility_flags` are not checked; templates ship `nodejs_compat` for dependencies that import `node:` builtins. 7. **Secrets gate** — lists the secrets the app's packages need and checks which are set on the worker. Interactive runs offer to mint and push the generatable ones (`BETTER_AUTH_SECRET`, `LUNORA_ADMIN_TOKEN`, …). In CI a missing required secret aborts the deploy. Skipped for `--dry-run` / `--preview`, and when the worker doesn't exist yet. 8. **Services, then `wrangler deploy`** — services declared in `lunora.config` deploy first (`--skip-services` to skip), then the app. 9. **After a successful deploy** — records the URL as the project link (`lunora link`), optionally runs `--migrate` and `--health-check`, and only then re-blesses the schema baseline. `lunora prepare` runs steps 1–6 without deploying (useful in CI), but it takes no `--env`, so it validates the top-level config only. `lunora verify` runs validation, a codegen dry run and `tsc --noEmit` without writing files, and does take `--env`. Flags worth knowing: | Flag | Effect | | -------------------------- | ------------------------------------------------------------------------------- | | `--env <name>` | Deploy a wrangler environment; checks read that env's (non-inherited) bindings. | | `--dry-run` | Run every gate and bundle without publishing; `wrangler.jsonc` edits roll back. | | `--preview` | Upload a version (`wrangler versions upload`) with a preview URL; no traffic. | | `--health-check` | Probe `/_lunora/health/ready` on the new version; fail if it never answers. | | `--prebuilt` | Skip codegen and the drift gate when `lunora prepare` already ran in this job. | | `--migrate` | Run every declared data migration (`up` is idempotent) against the live worker. | | `--allow-schema-drift` | Deploy despite blocked drift; the baseline is not advanced. | | `--update-schema-baseline` | Accept the current schema as the new baseline. | `--migrate` is checked before wrangler runs, so a missing requirement aborts the whole deploy: - `--migrate-yes` — confirms running production data migrations. - `--migrate-url <https://…>` — required unless this checkout is linked for the same `--env` (a previous successful deploy links it automatically). - An admin token — `--migrate-token` or `LUNORA_ADMIN_TOKEN`. ```bash lunora deploy --migrate --migrate-yes --migrate-url https://app.example.com ``` ## Preflight ```bash lunora doctor # read-only; exits 1 on any FAIL, WARN/INFO don't block lunora doctor --format json # each finding carries a stable `code` for CI/agents lunora verify --env production ``` `lunora doctor` fails on a missing or invalid `wrangler.jsonc`, a missing `SHARD` binding, a placeholder D1 id (`d1-placeholder-id`), and a binding whose class the worker doesn't export (`wrangler-class-unexported`), and an Artifacts namespace in the wrong jurisdiction. It warns on unfilled `.dev.vars` secrets, placeholder `send_email` destinations, an account near its Durable Object class cap, and a `cloudflare.config.ts` (`cf-config-present`). ## `wrangler.jsonc` Templates ship this shape. The `d1_databases` and `r2_buckets` entries are only needed when the app uses `.global()` tables or auth (D1) and storage (R2): ```jsonc { "name": "my-app", "main": "src/server.ts", "compatibility_date": "2026-10-01", "compatibility_flags": ["nodejs_compat"], "durable_objects": { "bindings": [ { "name": "SHARD", "class_name": "ShardDO" }, { "name": "SHARD_REGISTRY", "class_name": "ShardRegistryDO" }, ], }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ShardDO", "ShardRegistryDO"] }], "limits": { "cpu_ms": 30000 }, "d1_databases": [{ "binding": "DB", "database_name": "my-app", "database_id": "<replace-with-d1-create-id>" }], "r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "my-app-uploads" }], } ``` Reconcile adds `SCHEDULER` → `SchedulerDO` when the app schedules work, and bindings for generated containers, workflows and agents. Every `class_name` must be exported by the worker entry or wrangler rejects the deploy. Lunora writes the bindings but cannot create the remote resources: ```bash wrangler d1 create my-app # paste the returned database_id into wrangler.jsonc wrangler r2 bucket create my-app-uploads ``` When using `--env`, remember that `vars`, `d1_databases`, `durable_objects` and `containers` do not inherit in wrangler. Each environment needs its own entries, including its own D1 database. ## Secrets `.dev.vars` is for development only: it is git-ignored and `wrangler deploy` never uploads it. Production secrets live in Cloudflare: ```bash lunora env diff --env production # keys local-only vs set remotely lunora env push --env production --yes # uploads .dev.vars values; refuses placeholders wrangler secret put RESEND_API_KEY --env production # set a prod-only value by hand ``` `lunora env push` uploads the values from `.dev.vars`. If production needs different values than dev (it usually does for provider keys), set those with `wrangler secret put` instead. ## Schema-drift gate The gate blocks when the schema changed in a breaking way since `lunora/.lunora-schema.json` and no new `defineMigration` on the affected table covers it. The block message lists each change and a paste-ready `lunora migrate create … --table …` command. Commit the baseline file so CI and every machine gate against the same shape. It is advanced only after a successful deploy, so a failed deploy never moves it past a change that didn't ship. Reach for `--allow-schema-drift` / `--update-schema-baseline` only when you know existing data is compatible. Otherwise stage the change with `lunora-migration-helper`. ## `.global()` table DDL The runtime provisions `.global()` tables itself on first use (`CREATE TABLE IF NOT EXISTS` plus additive `ADD COLUMN` / indexes), so additive changes need no step. `lunora migrate generate` writes a reviewable SQL file for D1-backed global tables, but no command applies it, `lunora deploy` included. Destructive or hand-written statements in it (drops, a backfilling `UPDATE`, a rename) must be applied by hand: ```bash wrangler d1 execute DB --remote --file=lunora/migrations/<file>.sql ``` See `lunora-migration-helper` for the full flow. ## Rollback ```bash lunora cloudflare deployments list lunora cloudflare deployments status # which versions carry traffic, and how much lunora cloudflare deployments rollback --yes # previous version lunora cloudflare deployments promote <version-id> --yes # send 100% of traffic to a version ``` A partial rollout (`--percent N --from <live-version-id> --confirm-shared-state`) runs two versions against one Durable Object database. Nothing in `promote` compares the two versions' schemas, so confirm them yourself before using it. Two facts bound the risk: `lunora deploy --preview` runs the schema-drift gate before it uploads, so a version uploaded that way passed it unless `--prebuilt` skipped the gate or `--allow-schema-drift` overrode it; and `wrangler versions upload` refuses a version with a pending Durable Object migration, so that migration reaches traffic only through `deploy`, at 100%. A split whose live version predates a migration the target relies on is not detected by either check. Rolling back code does not roll back data migrations or D1 DDL. ## Common pitfalls 1. **Placeholder `database_id`.** Reconcile writes `<replace-with-d1-create-id>`; deploy refuses it. Run `wrangler d1 create` and paste the id. 2. **DO class not exported.** Export `ShardDO` (and any generated container / workflow class) from the worker entry; `lunora doctor` names the gap. 3. **Secrets only in `.dev.vars`.** They never reach production. Push or `wrangler secret put` them before the first request. 4. **Dev and prod sharing resources.** Give each environment its own D1 database, R2 buckets and secrets; never point a dev worker at prod data. 5. **Uncommitted schema baseline.** Commit `lunora/.lunora-schema.json` (and `lunora/migrations/.snapshot.json` if you use `migrate generate`). `lunora/_generated/` is regenerated by deploy; templates git-ignore it. 6. **Using `cf dev` / `cf build` / `cf deploy`.** They read `cloudflare.config.ts`, which Lunora never updates ([#964](https://github.com/anolilab/lunora/issues/964)). Use `lunora dev` / `lunora deploy`; `cf` resource commands (zones, DNS, KV) are fine. ## Checklist - [ ] `lunora doctor` has no FAIL; `lunora verify --env <name>` passes in CI. - [ ] D1 / R2 resources created per environment; real ids in `wrangler.jsonc`. - [ ] Every DO / container / workflow `class_name` is exported by the worker entry. - [ ] Production secrets set (`lunora env diff --env <name>` shows nothing local-only that prod needs). - [ ] Breaking schema changes staged with a migration; drift gate green. - [ ] `lunora deploy --env <name> --health-check` succeeded; `--migrate --migrate-yes` run if backfills were pending.