@lunora/cli 1.0.0-alpha.362 → 1.0.0-alpha.363

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.
@@ -1,189 +1,220 @@
1
1
  ---
2
2
  name: lunora-deploy
3
- description: Deploys a Lunora app to Cloudflare. Use for `lunora deploy`, wrangler.jsonc
4
- bindings (SHARD/SESSION DOs, D1, R2), provisioning databases/buckets, secrets
5
- (`wrangler secret` vs `.dev.vars`), the `lunora doctor` preflight, the
6
- schema-drift gate, and dev-vs-prod separation.
3
+ 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 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".
7
4
  ---
8
5
 
9
6
  # Lunora Deploy
10
7
 
11
- Ship a Lunora app to Cloudflare Workers + Durable Objects. Unlike a managed
12
- backend, deployment owns real Cloudflare resources — Durable Object bindings, a
13
- D1 database, R2 buckets, and secrets — so the work is mostly making
14
- `wrangler.jsonc` and the remote resources line up.
15
-
16
- ## When to Use
17
-
18
- - Deploying to production (or a Cloudflare environment) for the first time.
19
- - A deploy fails on a binding, a placeholder id, or the schema-drift gate.
20
- - Provisioning D1 / R2 / secrets for a Lunora app.
21
-
22
- ## When Not to Use
23
-
24
- - Local development — that is `lunora dev` (see `lunora-quickstart`).
25
- - A schema/data change that needs migrating — do that first with
26
- `lunora-migration-helper`, then deploy.
27
-
28
- ## What `lunora deploy` Does
29
-
30
- `lunora deploy` runs a fixed pipeline:
31
-
32
- 1. **Codegen** — regenerates `lunora/_generated/`. It does **not** run
33
- `tsc`: nothing in the deploy pipeline type-checks, and wrangler/esbuild
34
- strips types, so a project with a TS error deploys. Run `lunora verify`
35
- (which does run `tsc --noEmit`) as the CI type gate before this.
36
- 2. **Validate `wrangler.jsonc`** — a `compatibility_date` at or past the
37
- minimum the runtime needs, the `SHARD` Durable Object binding and its
38
- SQLite migration tag, and the shape of every declared binding. It does
39
- **not** check `compatibility_flags`: `nodejs_compat` is what every
40
- template ships and what dependencies reaching for `node:` builtins need,
41
- but no Lunora package requires it, so nothing fails when it is missing.
42
- 3. **Schema-drift gate** — blocks if the committed baseline
43
- (`lunora/.lunora-schema.json`) drifted with a breaking change and no
44
- accompanying migration. The baseline is re-blessed only after the deploy
45
- succeeds.
46
- 4. **`wrangler deploy`** — builds and pushes the worker (and any container
47
- images).
48
-
49
- Useful flags: `--env <name>` (Cloudflare environment), `--migrate` (run pending
50
- data migrations against the live worker after deploy), `--allow-schema-drift`
51
- (override the gate — use sparingly), and `--update-schema-baseline` (re-bless
52
- the baseline with the current shape).
53
-
54
- `--migrate` has three hard requirements, checked **before** `wrangler deploy`,
55
- so missing one aborts the whole deploy rather than skipping the migration:
56
-
57
- - `--migrate-yes` — confirms running production data migrations. Always
58
- required; there is no environment variable for it.
59
- - `--migrate-url <https://…>` — the deploy target URL is only known after
60
- wrangler runs, so it cannot be inferred. Omit it only when the checkout is
61
- linked (`lunora link`) for this same `--env`.
62
- - an admin token — `--migrate-token` or `LUNORA_ADMIN_TOKEN`.
8
+ Deploying a Lunora app means making `wrangler.jsonc`, the remote Cloudflare
9
+ resources (D1, R2, secrets) and the worker's exports agree. `lunora deploy`
10
+ automates most of that and refuses to ship when they don't.
11
+
12
+ Local development is `lunora dev` (see `lunora-quickstart`). If the deploy is
13
+ blocked by a breaking schema change, stage it with `lunora-migration-helper`
14
+ first.
15
+
16
+ ## What `lunora deploy` does
17
+
18
+ In order, stopping at the first failure:
19
+
20
+ 1. **Codegen** — regenerates `lunora/_generated/`. ERROR-level codegen
21
+ advisories fail the deploy in CI (`--strict-advisories` forces this locally,
22
+ `--no-strict-advisories` turns it off). Nothing here runs `tsc` and wrangler
23
+ strips types, so a project with a type error deploys — gate CI on
24
+ `lunora verify`, which does type-check.
25
+ 2. **Schema-drift gate** — compares the schema with the committed baseline
26
+ `lunora/.lunora-schema.json` (details below).
27
+ 3. **Architecture diff** — prints modules and edges (calls, table reads/writes,
28
+ enqueues) added or removed since the last successful deploy. Informational
29
+ only; the manifest is recorded in `.lunora/architecture[.<env>].json` after
30
+ wrangler succeeds.
31
+ 4. **Binding reconcile** — writes the bindings the code implies into
32
+ `wrangler.jsonc`: Durable Object bindings plus their `new_sqlite_classes`
33
+ migration, the `DB` D1 binding for `.global()` tables, crons, and the
34
+ compatibility date. R2 buckets are not auto-written (the bucket name is
35
+ yours to choose); you get a warning instead.
36
+ 5. **Read-only checks** — blocks on a D1 `database_id` still set to
37
+ `<replace-with-d1-create-id>`, on a loopback URL (`localhost`, `127.x`,
38
+ `::1`) in `vars`, on a missing container Dockerfile, or on no Docker engine
39
+ when a container builds locally.
40
+ 6. **Validate `wrangler.jsonc`** — the `SHARD` binding to `ShardDO` (or
41
+ `LunoraDO` for apps that merge their DOs), a minimum `compatibility_date`,
42
+ DO classes registered in `migrations`, and the shape of every binding.
43
+ `compatibility_flags` are not checked; templates ship `nodejs_compat` for
44
+ dependencies that import `node:` builtins.
45
+ 7. **Secrets gate** — lists the secrets the app's packages need and checks
46
+ which are set on the worker. Interactive runs offer to mint and push the
47
+ generatable ones (`BETTER_AUTH_SECRET`, `LUNORA_ADMIN_TOKEN`, …). In CI a
48
+ missing required secret aborts the deploy. Skipped for `--dry-run` /
49
+ `--preview`, and when the worker doesn't exist yet.
50
+ 8. **Services, then `wrangler deploy`** — services declared in
51
+ `lunora.config` deploy first (`--skip-services` to skip), then the app.
52
+ 9. **After a successful deploy** — records the URL as the project link
53
+ (`lunora link`), optionally runs `--migrate` and `--health-check`, and only
54
+ then re-blesses the schema baseline.
55
+
56
+ `lunora prepare` runs steps 1–6 without deploying (useful in CI), but it takes
57
+ no `--env`, so it validates the top-level config only. `lunora verify` runs
58
+ validation, a codegen dry run and `tsc --noEmit` without writing files, and does
59
+ take `--env`.
60
+
61
+ Flags worth knowing:
62
+
63
+ | Flag | Effect |
64
+ | -------------------------- | ------------------------------------------------------------------------------- |
65
+ | `--env <name>` | Deploy a wrangler environment; checks read that env's (non-inherited) bindings. |
66
+ | `--dry-run` | Run every gate and bundle without publishing; `wrangler.jsonc` edits roll back. |
67
+ | `--preview` | Upload a version (`wrangler versions upload`) with a preview URL; no traffic. |
68
+ | `--health-check` | Probe `/_lunora/health/ready` on the new version; fail if it never answers. |
69
+ | `--prebuilt` | Skip codegen and the drift gate when `lunora prepare` already ran in this job. |
70
+ | `--migrate` | Run every declared data migration (`up` is idempotent) against the live worker. |
71
+ | `--allow-schema-drift` | Deploy despite blocked drift; the baseline is not advanced. |
72
+ | `--update-schema-baseline` | Accept the current schema as the new baseline. |
73
+
74
+ `--migrate` is checked before wrangler runs, so a missing requirement aborts the
75
+ whole deploy:
76
+
77
+ - `--migrate-yes` — confirms running production data migrations.
78
+ - `--migrate-url <https://…>` — required unless this checkout is linked for the
79
+ same `--env` (a previous successful deploy links it automatically).
80
+ - An admin token — `--migrate-token` or `LUNORA_ADMIN_TOKEN`.
63
81
 
64
82
  ```bash
65
83
  lunora deploy --migrate --migrate-yes --migrate-url https://app.example.com
66
84
  ```
67
85
 
68
- ## Preflight: `lunora doctor`
69
-
70
- Run the read-only preflight before deploying. It checks:
71
-
72
- - `wrangler.jsonc` present with the `SHARD` durable-object binding.
73
- - D1 `database_id`s are real, not placeholders (`<replace>` / empty).
74
- - `send_email` destination addresses aren't placeholders.
75
- - `.dev.vars` secret-looking keys are filled.
76
- - Declared containers are exported by the worker entry.
86
+ ## Preflight
77
87
 
78
88
  ```bash
79
- lunora doctor # FAIL → exit 1; WARN/INFO don't block
89
+ lunora doctor # read-only; exits 1 on any FAIL, WARN/INFO don't block
90
+ lunora doctor --format json # each finding carries a stable `code` for CI/agents
91
+ lunora verify --env production
80
92
  ```
81
93
 
82
- `lunora verify` and `lunora prepare` run related checks (drift gate, wrangler
83
- validation) — wire `lunora verify` into CI to catch drift before a deploy.
94
+ `lunora doctor` fails on a missing or invalid `wrangler.jsonc`, a missing
95
+ `SHARD` binding, a placeholder D1 id (`d1-placeholder-id`), and a binding whose
96
+ class the worker doesn't export (`wrangler-class-unexported`), and an Artifacts
97
+ namespace in the wrong jurisdiction. It warns on unfilled `.dev.vars` secrets,
98
+ placeholder `send_email` destinations, an account near its Durable Object class
99
+ cap, and a `cloudflare.config.ts` (`cf-config-present`).
84
100
 
85
- ## `wrangler.jsonc` — the binding contract
101
+ ## `wrangler.jsonc`
86
102
 
87
- A Lunora worker needs the ShardDO (and SessionDO when auth is wired), the
88
- SQLite-DO migration tag, and whatever D1/R2 the app uses:
103
+ Templates ship this shape. The `d1_databases` and `r2_buckets` entries are
104
+ only needed when the app uses `.global()` tables or auth (D1) and storage (R2):
89
105
 
90
106
  ```jsonc
91
107
  {
92
108
  "name": "my-app",
93
- "main": "src/index.ts",
94
- "compatibility_date": "2026-04-07",
109
+ "main": "src/server.ts",
110
+ "compatibility_date": "2026-10-01",
95
111
  "compatibility_flags": ["nodejs_compat"],
96
112
  "durable_objects": {
97
113
  "bindings": [
98
114
  { "name": "SHARD", "class_name": "ShardDO" },
99
- { "name": "SESSION", "class_name": "SessionDO" }, // only with @lunora/auth
115
+ { "name": "SHARD_REGISTRY", "class_name": "ShardRegistryDO" },
100
116
  ],
101
117
  },
102
- "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ShardDO", "SessionDO"] }],
103
- "d1_databases": [{ "binding": "DB", "database_name": "my-app-global", "database_id": "<replace>" }],
104
- "r2_buckets": [{ "binding": "FILES", "bucket_name": "my-app-files" }],
118
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ShardDO", "ShardRegistryDO"] }],
119
+ "limits": { "cpu_ms": 30000 },
120
+ "d1_databases": [{ "binding": "DB", "database_name": "my-app", "database_id": "<replace-with-d1-create-id>" }],
121
+ "r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "my-app-uploads" }],
105
122
  }
106
123
  ```
107
124
 
108
- `lunora dev` auto-reconciles most of this (and `lunora registry add` adds the
109
- bindings an item needs), but the **resources themselves** must exist and their
110
- ids must be filled in:
125
+ Reconcile adds `SCHEDULER` → `SchedulerDO` when the app schedules work, and
126
+ bindings for generated containers, workflows and agents. Every `class_name`
127
+ must be exported by the worker entry or wrangler rejects the deploy.
128
+
129
+ Lunora writes the bindings but cannot create the remote resources:
111
130
 
112
131
  ```bash
113
- wrangler d1 create my-app-global # paste the returned database_id into wrangler.jsonc
114
- wrangler r2 bucket create my-app-files
132
+ wrangler d1 create my-app # paste the returned database_id into wrangler.jsonc
133
+ wrangler r2 bucket create my-app-uploads
115
134
  ```
116
135
 
117
- The DO `class_name`s must be exported by your worker entry (the
118
- `createShardDO()` / generated container exports) — wrangler rejects a binding
119
- whose class the worker doesn't export. `lunora doctor` surfaces a missing
120
- container export proactively.
136
+ When using `--env`, remember that `vars`, `d1_databases`, `durable_objects` and
137
+ `containers` do not inherit in wrangler. Each environment needs its own entries,
138
+ including its own D1 database.
121
139
 
122
- ## Secrets: `wrangler secret`, not `.dev.vars`
140
+ ## Secrets
123
141
 
124
- `.dev.vars` is **dev only** — it is git-ignored and never deployed. Production
125
- secrets (`BETTER_AUTH_SECRET`, `RESEND_API_KEY`, provider client secrets, …) go
126
- into Cloudflare:
142
+ `.dev.vars` is for development only: it is git-ignored and `wrangler deploy`
143
+ never uploads it. Production secrets live in Cloudflare:
127
144
 
128
145
  ```bash
129
- wrangler secret put BETTER_AUTH_SECRET # prompts for the value, stored encrypted
130
- wrangler secret list
146
+ lunora env diff --env production # keys local-only vs set remotely
147
+ lunora env push --env production --yes # uploads .dev.vars values; refuses placeholders
148
+ wrangler secret put RESEND_API_KEY --env production # set a prod-only value by hand
131
149
  ```
132
150
 
133
- Set every secret your app reads (mirror the secret-looking keys in `.dev.vars`)
134
- before the first request hits production.
135
-
136
- ## Dev vs Production
137
-
138
- - **Development:** `lunora dev` (Vite + workerd + Studio + codegen-on-save). The
139
- schema baseline and `.dev.vars` belong to dev.
140
- - **Production:** `lunora deploy`. Separate D1 database / R2 buckets / secrets
141
- from dev. Never point a dev worker at prod resources.
142
-
143
- For `.global()` table DDL, `lunora migrate generate` emits a reviewable SQL
144
- file — but **no command applies it**, `lunora deploy` included. What actually
145
- provisions a global table is the runtime, from the schema rather than from the
146
- file: on first use it runs `CREATE TABLE IF NOT EXISTS` plus additive
147
- `ADD COLUMN`, so additive changes land whether or not the file exists.
148
- Anything the generator cannot express — a backfilling `UPDATE`, a rename
149
- written as add/copy/drop, an index the schema does not declare — must be
150
- applied by hand:
151
- `wrangler d1 execute <DB> --remote --file=lunora/migrations/<file>.sql`. The
152
- file is multi-statement, so it is not a `@lunora/d1` `Migration` either.
153
- For data backfills, deploy first, then `lunora deploy --migrate --migrate-yes
154
- --migrate-url <url>` (or `lunora migrate up --prod`). See
151
+ `lunora env push` uploads the values from `.dev.vars`. If production needs
152
+ different values than dev (it usually does for provider keys), set those with
153
+ `wrangler secret put` instead.
154
+
155
+ ## Schema-drift gate
156
+
157
+ The gate blocks when the schema changed in a breaking way since
158
+ `lunora/.lunora-schema.json` and no new `defineMigration` on the affected table
159
+ covers it. The block message lists each change and a paste-ready
160
+ `lunora migrate create … --table …` command. Commit the baseline file so CI
161
+ and every machine gate against the same shape. It is advanced only after a
162
+ successful deploy, so a failed deploy never moves it past a change that didn't
163
+ ship.
164
+
165
+ Reach for `--allow-schema-drift` / `--update-schema-baseline` only when you know
166
+ existing data is compatible. Otherwise stage the change with
155
167
  `lunora-migration-helper`.
156
168
 
157
- ## Common Pitfalls
158
-
159
- 1. **Placeholder `database_id`.** The D1 binding ships with `<replace>`; run
160
- `wrangler d1 create` and paste the id. `lunora doctor` catches this.
161
- 2. **DO class not exported.** `wrangler deploy` fails if a `class_name` isn't
162
- exported by the worker entry — export `ShardDO`/`SessionDO`/generated
163
- containers.
164
- 3. **Secrets only in `.dev.vars`.** They never reach production; use `wrangler
165
- secret put` for every prod secret.
166
- 4. **Bypassing the drift gate.** `--allow-schema-drift` ships a breaking schema
167
- with no migration — stage the change (`lunora-migration-helper`) instead.
168
- 5. **Deploying with uncommitted codegen.** Commit `lunora/_generated/` and
169
- `lunora/.lunora-schema.json` so CI and the gate see the same baseline.
170
- 6. **Using the Cloudflare CLI (`cf`) lifecycle commands.** Lunora manages
171
- `wrangler.jsonc` and doesn't support `cf` yet
172
- ([#964](https://github.com/anolilab/lunora/issues/964)). `cf` resource
173
- commands (zones, DNS, KV, …) are fine, but never `cf dev` / `cf build` /
174
- `cf deploy`: they read `cloudflare.config.ts`, which Lunora never updates.
175
- `lunora doctor` reports one as `cf-config-present` on every run; deploy,
176
- codegen, `lunora dev` and the Vite/Rspack plugins (dev and build) warn once
177
- per process tree.
169
+ ## `.global()` table DDL
170
+
171
+ The runtime provisions `.global()` tables itself on first use
172
+ (`CREATE TABLE IF NOT EXISTS` plus additive `ADD COLUMN` / indexes), so additive
173
+ changes need no step. `lunora migrate generate` writes a reviewable SQL file for
174
+ D1-backed global tables, but no command applies it, `lunora deploy` included.
175
+ Destructive or hand-written statements in it (drops, a backfilling `UPDATE`, a
176
+ rename) must be applied by hand:
177
+
178
+ ```bash
179
+ wrangler d1 execute DB --remote --file=lunora/migrations/<file>.sql
180
+ ```
181
+
182
+ See `lunora-migration-helper` for the full flow.
183
+
184
+ ## Rollback
185
+
186
+ ```bash
187
+ lunora deployments list
188
+ lunora deployments rollback --yes # previous version
189
+ lunora deployments promote <version-id> --yes # send 100% of traffic to a version
190
+ ```
191
+
192
+ Rolling back code does not roll back data migrations or D1 DDL.
193
+
194
+ ## Common pitfalls
195
+
196
+ 1. **Placeholder `database_id`.** Reconcile writes
197
+ `<replace-with-d1-create-id>`; deploy refuses it. Run `wrangler d1 create`
198
+ and paste the id.
199
+ 2. **DO class not exported.** Export `ShardDO` (and any generated container /
200
+ workflow class) from the worker entry; `lunora doctor` names the gap.
201
+ 3. **Secrets only in `.dev.vars`.** They never reach production. Push or
202
+ `wrangler secret put` them before the first request.
203
+ 4. **Dev and prod sharing resources.** Give each environment its own D1
204
+ database, R2 buckets and secrets; never point a dev worker at prod data.
205
+ 5. **Uncommitted schema baseline.** Commit `lunora/.lunora-schema.json` (and
206
+ `lunora/migrations/.snapshot.json` if you use `migrate generate`).
207
+ `lunora/_generated/` is regenerated by deploy; templates git-ignore it.
208
+ 6. **Using `cf dev` / `cf build` / `cf deploy`.** They read
209
+ `cloudflare.config.ts`, which Lunora never updates
210
+ ([#964](https://github.com/anolilab/lunora/issues/964)). Use `lunora dev` /
211
+ `lunora deploy`; `cf` resource commands (zones, DNS, KV) are fine.
178
212
 
179
213
  ## Checklist
180
214
 
181
- - [ ] `lunora doctor` passes (no FAIL).
182
- - [ ] `wrangler.jsonc` has `compatibility_date`, `nodejs_compat`, the `SHARD` DO
183
- binding, and the SQLite migration tag.
184
- - [ ] D1 / R2 resources created; real ids pasted into `wrangler.jsonc`.
185
- - [ ] DO + container `class_name`s exported by the worker entry.
186
- - [ ] Production secrets set via `wrangler secret put`.
187
- - [ ] Schema changes migrated; the drift gate is green (no `--allow-schema-drift`).
188
- - [ ] `lunora deploy` succeeded; `--migrate --migrate-yes --migrate-url <url>` run
189
- if data backfills were pending.
215
+ - [ ] `lunora doctor` has no FAIL; `lunora verify --env <name>` passes in CI.
216
+ - [ ] D1 / R2 resources created per environment; real ids in `wrangler.jsonc`.
217
+ - [ ] Every DO / container / workflow `class_name` is exported by the worker entry.
218
+ - [ ] Production secrets set (`lunora env diff --env <name>` shows nothing local-only that prod needs).
219
+ - [ ] Breaking schema changes staged with a migration; drift gate green.
220
+ - [ ] `lunora deploy --env <name> --health-check` succeeded; `--migrate --migrate-yes` run if backfills were pending.