@lunora/cli 0.0.0 → 1.0.0-alpha.2

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.
Files changed (72) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +109 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/bin.mjs +11 -0
  5. package/dist/index.d.mts +852 -0
  6. package/dist/index.d.ts +852 -0
  7. package/dist/index.mjs +19 -0
  8. package/dist/packem_chunks/handler.mjs +76 -0
  9. package/dist/packem_chunks/handler10.mjs +22 -0
  10. package/dist/packem_chunks/handler11.mjs +192 -0
  11. package/dist/packem_chunks/handler12.mjs +131 -0
  12. package/dist/packem_chunks/handler13.mjs +65 -0
  13. package/dist/packem_chunks/handler14.mjs +58 -0
  14. package/dist/packem_chunks/handler15.mjs +79 -0
  15. package/dist/packem_chunks/handler16.mjs +41 -0
  16. package/dist/packem_chunks/handler17.mjs +105 -0
  17. package/dist/packem_chunks/handler18.mjs +172 -0
  18. package/dist/packem_chunks/handler19.mjs +89 -0
  19. package/dist/packem_chunks/handler2.mjs +114 -0
  20. package/dist/packem_chunks/handler20.mjs +94 -0
  21. package/dist/packem_chunks/handler21.mjs +311 -0
  22. package/dist/packem_chunks/handler3.mjs +204 -0
  23. package/dist/packem_chunks/handler4.mjs +33 -0
  24. package/dist/packem_chunks/handler5.mjs +49 -0
  25. package/dist/packem_chunks/handler6.mjs +91 -0
  26. package/dist/packem_chunks/handler7.mjs +42 -0
  27. package/dist/packem_chunks/handler8.mjs +174 -0
  28. package/dist/packem_chunks/handler9.mjs +16 -0
  29. package/dist/packem_chunks/planDevCommand.mjs +543 -0
  30. package/dist/packem_chunks/runCodegenCommand.mjs +52 -0
  31. package/dist/packem_chunks/runDeployCommand.mjs +504 -0
  32. package/dist/packem_chunks/runInitCommand.mjs +652 -0
  33. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +397 -0
  34. package/dist/packem_chunks/runResetCommand.mjs +41 -0
  35. package/dist/packem_chunks/runRpcCommand.mjs +68 -0
  36. package/dist/packem_shared/COMMANDS-1V_KEx35.mjs +905 -0
  37. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +244 -0
  38. package/dist/packem_shared/admin-url-4UzT-CI4.mjs +19 -0
  39. package/dist/packem_shared/api-spec-CtA6ilu4.mjs +13 -0
  40. package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +38 -0
  41. package/dist/packem_shared/command-BDXcJCCJ.mjs +14 -0
  42. package/dist/packem_shared/createLogger-CHPNjFw2.mjs +73 -0
  43. package/dist/packem_shared/defaultSpawner-DxI3mebw.mjs +43 -0
  44. package/dist/packem_shared/diffSnapshots-RR2ZE8Ya.mjs +161 -0
  45. package/dist/packem_shared/docker-hMQ97KSQ.mjs +21 -0
  46. package/dist/packem_shared/features-ocSSpZtS.mjs +24 -0
  47. package/dist/packem_shared/insertSchemaExtension-BuzF6-t2.mjs +59 -0
  48. package/dist/packem_shared/open-url-Dfq6fAyT.mjs +41 -0
  49. package/dist/packem_shared/output-format-7gyGR3h8.mjs +17 -0
  50. package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +56 -0
  51. package/dist/packem_shared/parseManifest--vZf2FY1.mjs +94 -0
  52. package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +16 -0
  53. package/dist/packem_shared/runAddCommand-BZGkRnBs.mjs +693 -0
  54. package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +79 -0
  55. package/dist/packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs +43 -0
  56. package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +12 -0
  57. package/package.json +61 -18
  58. package/skills/README.md +29 -0
  59. package/skills/lunora/SKILL.md +83 -0
  60. package/skills/lunora-create-package/SKILL.md +129 -0
  61. package/skills/lunora-deploy/SKILL.md +150 -0
  62. package/skills/lunora-functions/SKILL.md +182 -0
  63. package/skills/lunora-migration-helper/SKILL.md +194 -0
  64. package/skills/lunora-performance-audit/SKILL.md +143 -0
  65. package/skills/lunora-quickstart/SKILL.md +240 -0
  66. package/skills/lunora-realtime/SKILL.md +177 -0
  67. package/skills/lunora-setup-auth/SKILL.md +170 -0
  68. package/skills/lunora-setup-hyperdrive/SKILL.md +154 -0
  69. package/skills/lunora-setup-hyperdrive-global/SKILL.md +171 -0
  70. package/skills/lunora-setup-mail/SKILL.md +151 -0
  71. package/skills/lunora-setup-scheduler/SKILL.md +157 -0
  72. package/skills/lunora-setup-storage/SKILL.md +154 -0
@@ -0,0 +1,79 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { evaluateSchemaDrift, parseSchemaSnapshot, SchemaSnapshotParseError, serializeSchemaSnapshot } from '@lunora/codegen';
3
+
4
+ const readBaseline = (snapshotPath) => {
5
+ if (!existsSync(snapshotPath)) {
6
+ return { status: "absent" };
7
+ }
8
+ let content;
9
+ try {
10
+ content = readFileSync(snapshotPath, "utf8");
11
+ } catch {
12
+ return { status: "corrupt" };
13
+ }
14
+ try {
15
+ const snapshot = parseSchemaSnapshot(content);
16
+ return snapshot === void 0 ? { status: "corrupt" } : { snapshot, status: "ok" };
17
+ } catch (error) {
18
+ if (error instanceof SchemaSnapshotParseError) {
19
+ return { status: "corrupt" };
20
+ }
21
+ throw error;
22
+ }
23
+ };
24
+ const writeBaseline = (snapshotPath, snapshot) => {
25
+ writeFileSync(snapshotPath, serializeSchemaSnapshot(snapshot), "utf8");
26
+ };
27
+ const corruptBaselineResult = (context) => {
28
+ const reason = `schema-drift gate: the committed baseline ${context.snapshotPath} is unreadable or malformed, so schema drift cannot be checked. Fix it (e.g. resolve a merge conflict in lunora/.lunora-schema.json), or pass --update-schema-baseline to regenerate it from the current schema.`;
29
+ if (context.updateBaseline && !context.readOnly) {
30
+ context.logger.warn(
31
+ `schema baseline was unreadable; regenerating from the current schema on success (--update-schema-baseline): ${context.snapshotPath}`
32
+ );
33
+ return { blocked: false, changes: [], reason, rebless: context.rebless };
34
+ }
35
+ if (!context.readOnly) {
36
+ context.logger.error(reason);
37
+ }
38
+ return { blocked: true, changes: [], reason };
39
+ };
40
+ const blockedDecisionResult = (decision, context) => {
41
+ if (!context.readOnly) {
42
+ context.logger.error(decision.reason);
43
+ }
44
+ if (context.updateBaseline && !context.readOnly) {
45
+ context.logger.warn(`schema baseline will be re-blessed despite breaking drift on success (--update-schema-baseline): ${context.snapshotPath}`);
46
+ return { blocked: false, changes: decision.changes, reason: decision.reason, rebless: context.rebless };
47
+ }
48
+ return { blocked: true, changes: decision.changes, reason: decision.reason };
49
+ };
50
+ const runSchemaDriftGate = (options) => {
51
+ const { allowDrift, codegen, logger, readOnly = false, updateBaseline = false } = options;
52
+ const snapshotPath = codegen.schemaSnapshotPath;
53
+ const baseline = readBaseline(snapshotPath);
54
+ const context = {
55
+ logger,
56
+ readOnly,
57
+ rebless: () => {
58
+ writeBaseline(snapshotPath, codegen.schemaSnapshot);
59
+ },
60
+ snapshotPath,
61
+ updateBaseline
62
+ };
63
+ if (baseline.status === "corrupt") {
64
+ return corruptBaselineResult(context);
65
+ }
66
+ const decision = evaluateSchemaDrift({ allowDrift, baseline: baseline.status === "ok" ? baseline.snapshot : void 0, current: codegen.schemaSnapshot });
67
+ if (decision.blocked) {
68
+ return blockedDecisionResult(decision, context);
69
+ }
70
+ if (decision.changes.length > 0) {
71
+ if (!readOnly) {
72
+ logger.info(decision.reason);
73
+ }
74
+ return { blocked: false, changes: decision.changes, reason: decision.reason, rebless: readOnly ? void 0 : context.rebless };
75
+ }
76
+ return { blocked: false, changes: decision.changes, reason: decision.reason };
77
+ };
78
+
79
+ export { runSchemaDriftGate as r };
@@ -0,0 +1,43 @@
1
+ import { validatorKindToSqlType } from './diffSnapshots-RR2ZE8Ya.mjs';
2
+
3
+ const validatorToColumn = (validator) => {
4
+ if (validator.kind === "optional" && validator.inner) {
5
+ return {
6
+ nullable: true,
7
+ sqlType: validatorKindToSqlType(validator.inner.kind)
8
+ };
9
+ }
10
+ return {
11
+ nullable: false,
12
+ sqlType: validatorKindToSqlType(validator.kind)
13
+ };
14
+ };
15
+ const isGlobal = (mode) => mode === "global";
16
+ const schemaIrToSnapshot = (ir) => {
17
+ const tables = {};
18
+ for (const table of ir.tables) {
19
+ if (!isGlobal(table.shardMode)) {
20
+ continue;
21
+ }
22
+ const columns = {};
23
+ for (const [columnName, validator] of Object.entries(table.shape)) {
24
+ columns[columnName] = validatorToColumn(validator);
25
+ }
26
+ const indexes = {};
27
+ for (const index of table.indexes) {
28
+ indexes[index.name] = {
29
+ fields: [...index.fields],
30
+ name: index.name,
31
+ unique: index.unique ?? false
32
+ };
33
+ }
34
+ tables[table.name] = {
35
+ columns,
36
+ indexes,
37
+ name: table.name
38
+ };
39
+ }
40
+ return { tables, version: 1 };
41
+ };
42
+
43
+ export { schemaIrToSnapshot as default };
@@ -0,0 +1,12 @@
1
+ import { findWranglerFile, readWranglerJsonc } from '@lunora/config';
2
+
3
+ const readWranglerName = (cwd) => {
4
+ const wranglerPath = findWranglerFile(cwd);
5
+ if (!wranglerPath) {
6
+ return void 0;
7
+ }
8
+ const { parsed } = readWranglerJsonc(wranglerPath);
9
+ return typeof parsed?.name === "string" && parsed.name.length > 0 ? parsed.name : void 0;
10
+ };
11
+
12
+ export { readWranglerName as r };
package/package.json CHANGED
@@ -1,33 +1,76 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "0.0.0",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
- "license": "FSL-1.1-Apache-2.0",
5
+ "keywords": [
6
+ "agent-skills",
7
+ "cli",
8
+ "cloudflare",
9
+ "codegen",
10
+ "deploy",
11
+ "durable-objects",
12
+ "lunora",
13
+ "tanstack-intent",
14
+ "vite",
15
+ "workers"
16
+ ],
6
17
  "homepage": "https://lunora.sh",
18
+ "bugs": "https://github.com/anolilab/lunora/issues",
19
+ "license": "FSL-1.1-Apache-2.0",
20
+ "author": {
21
+ "name": "Daniel Bannert",
22
+ "email": "d.bannert@anolilab.de"
23
+ },
7
24
  "repository": {
8
25
  "type": "git",
9
26
  "url": "git+https://github.com/anolilab/lunora.git",
10
27
  "directory": "packages/cli"
11
28
  },
12
- "bugs": {
13
- "url": "https://github.com/anolilab/lunora/issues"
29
+ "bin": {
30
+ "lunora": "./dist/bin.mjs"
14
31
  },
15
- "keywords": [
16
- "lunora",
17
- "cloudflare",
18
- "workers",
19
- "durable-objects",
20
- "cli",
21
- "codegen",
22
- "deploy",
23
- "vite",
24
- "tanstack-intent",
25
- "agent-skills"
32
+ "files": [
33
+ "dist",
34
+ "__assets__",
35
+ "skills",
36
+ "README.md",
37
+ "LICENSE.md"
26
38
  ],
39
+ "type": "module",
40
+ "sideEffects": false,
41
+ "module": "./dist/index.mjs",
42
+ "types": "./dist/index.d.ts",
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist/index.d.ts",
46
+ "import": "./dist/index.mjs"
47
+ },
48
+ "./package.json": "./package.json"
49
+ },
27
50
  "publishConfig": {
28
51
  "access": "public"
29
52
  },
30
- "files": [
31
- "README.md"
32
- ]
53
+ "dependencies": {
54
+ "@bomb.sh/tab": "0.0.17",
55
+ "@lunora/codegen": "1.0.0-alpha.2",
56
+ "@lunora/config": "1.0.0-alpha.2",
57
+ "@lunora/container": "1.0.0-alpha.1",
58
+ "@lunora/d1": "1.0.0-alpha.2",
59
+ "@lunora/seed": "1.0.0-alpha.1",
60
+ "@lunora/server": "1.0.0-alpha.1",
61
+ "@lunora/studio": "1.0.0-alpha.1",
62
+ "@lunora/values": "1.0.0-alpha.1",
63
+ "@visulima/cerebro": "3.0.0-alpha.32",
64
+ "@visulima/fs": "5.0.0-alpha.32",
65
+ "@visulima/pail": "4.0.0-alpha.22",
66
+ "@visulima/path": "3.0.0-alpha.13",
67
+ "@visulima/spinner": "1.0.0-alpha.4",
68
+ "giget": "3.3.0",
69
+ "jsonc-parser": "^3.3.1",
70
+ "magic-string": "^0.30.21",
71
+ "ts-morph": "^28.0.0"
72
+ },
73
+ "engines": {
74
+ "node": "^22.15.0 || >=24.11.0"
75
+ }
33
76
  }
@@ -0,0 +1,29 @@
1
+ # Lunora Agent Skills
2
+
3
+ First-party [Agent Skills](https://tanstack.com/intent/latest/docs/registry) for
4
+ Lunora — portable instructions that teach AI coding agents how to use the
5
+ framework correctly. They ship inside `@lunora/cli` and are discovered by the
6
+ [TanStack Intent registry](https://tanstack.com/intent/registry) via the
7
+ `tanstack-intent` package keyword.
8
+
9
+ Each skill is a `SKILL.md` with YAML frontmatter (`name`, `description`) in its
10
+ own directory:
11
+
12
+ | Skill | Use for |
13
+ | -------------------------- | --------------------------------------------------------------------------- |
14
+ | `lunora` | Router — start here, then switch to the matching skill below. |
15
+ | `lunora-quickstart` | `lunora init` / adding Lunora to an app + first round-trip. |
16
+ | `lunora-functions` | Core authoring rules — schema, validators, query/mutation/action, `ctx.db`. |
17
+ | `lunora-realtime` | Client reactivity — live hooks, optimistic updates, `@lunora/db`. |
18
+ | `lunora-setup-auth` | Authentication via `lunora registry add auth` (+ providers). |
19
+ | `lunora-setup-mail` | Transactional email via `lunora registry add mail` — `sendEmail` actions. |
20
+ | `lunora-setup-storage` | R2 file storage via `lunora registry add storage` — signed upload/download. |
21
+ | `lunora-setup-scheduler` | Deferred (`ctx.scheduler`) + cron jobs (`lunora registry add crons`). |
22
+ | `lunora-create-package` | Building a reusable registry item or `@lunora/*` package. |
23
+ | `lunora-migration-helper` | Schema/data migrations, `.global()` D1 flow, the drift gate. |
24
+ | `lunora-deploy` | Deploying to Cloudflare — wrangler bindings, secrets, the gate. |
25
+ | `lunora-performance-audit` | Scans, indexes, OCC write conflicts, sharding/`.global()`. |
26
+
27
+ These are mirrored into `.agents/skills/` and `.claude/skills/` (via symlinks)
28
+ so agents working inside this repo pick them up directly. The source of truth
29
+ lives here so the published `@lunora/cli` tarball carries them.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: lunora
3
+ description: Routes general Lunora requests to the right project skill. Use when the user
4
+ asks which Lunora skill to use, or gives an underspecified task for a Lunora
5
+ app (a type-safe, real-time backend on Cloudflare Workers + Durable Objects
6
+ with a Vite-first DX).
7
+ ---
8
+
9
+ # Lunora
10
+
11
+ Use this as the routing skill for Lunora work in this repo.
12
+
13
+ Lunora exposes a Convex-style functional API (`defineSchema`, `query`,
14
+ `mutation`, `action`) on top of Cloudflare Workers and Durable Objects. State
15
+ lives in a per-app `ShardDO` (SQLite, OCC, hibernated WebSocket subscriptions)
16
+ by default; `.shardBy(key)` partitions it across many DOs and `.global()`
17
+ replicates a table to D1 for low-latency cross-region reads. A Vite plugin
18
+ drives codegen and end-to-end type sync.
19
+
20
+ If a more specific Lunora skill clearly matches the request, use that instead.
21
+
22
+ ## Start Here
23
+
24
+ Before writing or changing any `lunora/` code, make sure the generated types are
25
+ current — they are the contract the client and server share.
26
+
27
+ ```bash
28
+ lunora codegen
29
+ ```
30
+
31
+ This regenerates `lunora/_generated/` (`api.ts`, `server.ts`, `dataModel.ts`,
32
+ `shard.ts`, `openapi.ts`, …) from `lunora/schema.ts` and your function files. The
33
+ output typechecks your schema and functions, so it doubles as the agent's main
34
+ feedback loop after each edit. Commit `lunora/_generated/` — it is part of the
35
+ source tree, not a build artifact to gitignore.
36
+
37
+ If a project-level `AGENTS.md` / `CLAUDE.md` exists, read it first — it overrides
38
+ these defaults.
39
+
40
+ ## Route to the Right Skill
41
+
42
+ After codegen is green, use the most specific Lunora skill for the task:
43
+
44
+ - New project, or adding Lunora to an existing app: `lunora-quickstart`
45
+ - Writing or reviewing schema + functions (the core authoring rules):
46
+ `lunora-functions`
47
+ - Wiring live data into a client (hooks, optimistic updates): `lunora-realtime`
48
+ - Authentication setup (email/password, OAuth, magic link, OTP):
49
+ `lunora-setup-auth`
50
+ - Wiring a prebuilt capability (mail, file storage, scheduled jobs, rate
51
+ limiting, vectors, AI, containers, payments, MCP): install it with
52
+ `lunora registry add <item>` (see `lunora registry list`). Capabilities with a
53
+ dedicated skill: `lunora-setup-mail` (mail), `lunora-setup-storage` (R2 file
54
+ storage), `lunora-setup-scheduler` (deferred `ctx.scheduler` + cron jobs). For
55
+ the rest, read the item's README after installing.
56
+ - Building a reusable capability — a registry item or an `@lunora/*` package:
57
+ `lunora-create-package`
58
+ - Planning or running a schema/data migration: `lunora-migration-helper`
59
+ - Deploying to Cloudflare (wrangler, bindings, secrets, the drift gate):
60
+ `lunora-deploy`
61
+ - Investigating performance, scan, or write-conflict issues:
62
+ `lunora-performance-audit`
63
+
64
+ If one of those clearly matches the user's goal, switch to it instead of staying
65
+ in this skill.
66
+
67
+ ## Core Mental Model
68
+
69
+ - **Functions** live in `lunora/*.ts` and are one of `query` (reactive read),
70
+ `mutation` (transactional write), or `action` (side effects / `fetch` / no
71
+ direct db). `internalQuery` / `internalMutation` / `internalAction` are the
72
+ non-public variants.
73
+ - **Schema** lives in `lunora/schema.ts` via `defineSchema` + `defineTable`,
74
+ with validators from `v.*` (re-exported by `@lunora/server`).
75
+ - **Reads go through indexes.** Prefer `ctx.db.query("t").withIndex(...)` over
76
+ `.filter(...)`; declare the index with `.index("by_x", ["x"])`.
77
+ - **Clients** subscribe over WebSocket. `useQuery`/`useMutation` (React, Vue,
78
+ Solid, Svelte) re-render the moment a mutation changes the queried rows.
79
+
80
+ ## When Not to Use
81
+
82
+ - The user has already named a more specific Lunora workflow.
83
+ - Another Lunora skill obviously fits the request better.
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: lunora-create-package
3
+ description: Builds a reusable Lunora capability — either a registry item installed with
4
+ `lunora registry add`, or a publishable `@lunora/*` workspace package. Use for
5
+ packaging schema + functions + bindings others can drop into their app.
6
+ ---
7
+
8
+ # Lunora Create Package
9
+
10
+ Package a reusable Lunora capability. There are two distribution shapes; pick
11
+ based on whether the capability is **copied into the user's `lunora/`** or
12
+ **imported as a dependency**.
13
+
14
+ | Shape | Distribution | Use for |
15
+ | ----------------- | -------------------------------- | -------------------------------------------------------- |
16
+ | **Registry item** | `lunora registry add <name>` | App-owned code (schema/functions) the user edits + wires |
17
+ | **Workspace pkg** | `import … from "@lunora/<name>"` | Reusable library code imported as a dependency |
18
+
19
+ Many capabilities use **both**: a thin `@lunora/<name>` package holding the
20
+ reusable runtime, plus a registry item that scaffolds the glue (`lunora/<name>/`
21
+ files, bindings, env vars) into the user's project. `auth`, `mail`, `ratelimit`,
22
+ and `storage` all follow this pattern.
23
+
24
+ ## When to Use
25
+
26
+ - Extracting schema + functions you have written into something reusable.
27
+ - Authoring a new capability (presence, search, payments, …) for other apps.
28
+ - Adding a new item to this repo's `registry/`.
29
+
30
+ ## When Not to Use
31
+
32
+ - A one-off feature for a single app — just write it in `lunora/`.
33
+ - The capability already exists as a registry item or `@lunora/*` package — use
34
+ it (`lunora registry list` to browse).
35
+
36
+ ## Path A: Registry Item
37
+
38
+ A registry item is a directory under `registry/<name>/` with three files:
39
+
40
+ - `registry.json` — the manifest (deps, bindings, env vars, files, requires).
41
+ - `index.ts` (and any siblings) — the code copied into the user's project.
42
+ - `README.md` — install + configuration docs.
43
+
44
+ ### Manifest shape
45
+
46
+ ```jsonc
47
+ // registry/<name>/registry.json
48
+ {
49
+ "$schema": "../schema/registry-item.schema.json",
50
+ "name": "<name>",
51
+ "title": "Human Title",
52
+ "description": "One-paragraph summary shown in `lunora registry list`.",
53
+ "docs": "Post-install steps surfaced to the user after `lunora registry add`.",
54
+ "requires": [], // other item names this one depends on
55
+ "deps": { "@lunora/server": "workspace:*" },
56
+ "bindings": [
57
+ // reconciled into wrangler.jsonc
58
+ { "path": ["d1_databases"], "value": [{ "binding": "DB", "database_name": "REPLACE_ME-db", "database_id": "<replace-with-d1-create-id>" }] },
59
+ ],
60
+ "envVars": [{ "name": "MY_SECRET", "description": "What it is and how to generate it.", "secret": true }],
61
+ "files": [{ "from": "index.ts", "to": "lunora/<name>/index.ts", "merge": "create-or-skip" }],
62
+ }
63
+ ```
64
+
65
+ - `files[].merge` is typically `create-or-skip` (never clobber edited user code);
66
+ `bindings` are reconciled into `wrangler.jsonc`; `envVars` are scaffolded into
67
+ `.dev.vars` (secret-looking ones get generated values).
68
+ - `requires` lets a provider item (e.g. `auth-clerk`) build on a base item
69
+ (`auth`). The resolver installs the chain.
70
+
71
+ ### Register and validate
72
+
73
+ Add an entry to `registry/index.json`, then rebuild and check the index:
74
+
75
+ ```bash
76
+ lunora registry build # regenerate registry/index.json
77
+ lunora registry build --check # verify the index is up to date (CI)
78
+ lunora registry view <name> # preview what `add` would do
79
+ lunora registry add <name> # install into the current project
80
+ ```
81
+
82
+ ## Path B: Workspace Package
83
+
84
+ Scaffold a fresh `@lunora/<name>` package with the generator (always use the
85
+ `--name=value` form):
86
+
87
+ ```bash
88
+ vis generate lunora-package --name=search --description='Typed full-text search over Lunora tables'
89
+ ```
90
+
91
+ This creates `packages/search/` following the repo's package shape: `src/index.ts`,
92
+ `__tests__/`, `vitest.config.ts`, `tsconfig.json` (extends `../../tsconfig.base.json`),
93
+ `project.json` (vis tags `type:package` + `category:<slug>`), `package.json` (ESM,
94
+ `"sideEffects": false`, conditional exports), and `.releaserc.json`.
95
+
96
+ ### Repo conventions to honor
97
+
98
+ - **No `.js` extensions** in relative imports (`moduleResolution: "bundler"`).
99
+ The lone exception is `@lunora/codegen`'s emitted output.
100
+ - **No mixed default + named exports** in one file — named-only when there is
101
+ more than one export.
102
+ - **Use the dependency catalogs** in `pnpm-workspace.yaml` (`catalog:test`,
103
+ `catalog:lint`, …) — never hard-code a version that lives in a catalog.
104
+ - Tag `project.json` with `type:package` and a `category:<slug>`.
105
+
106
+ Build and test the new package in isolation:
107
+
108
+ ```bash
109
+ pnpm --filter "@lunora/search" run lint:types
110
+ pnpm --filter "@lunora/search" run test
111
+ ```
112
+
113
+ ## Codegen-Wired Capabilities
114
+
115
+ If your capability surfaces functions on a context (e.g. `ctx.ai`, `ctx.containers`)
116
+ or new generated tables, it must be discoverable by `@lunora/codegen` — codegen
117
+ parses `lunora/schema.ts` and the function files. Document any `lunora/*.ts`
118
+ declaration the user must add so codegen wires the typed surface.
119
+
120
+ ## Checklist
121
+
122
+ - [ ] Chose the right shape (registry item, workspace package, or both).
123
+ - [ ] Registry item: `registry.json` + `index.ts` + `README.md` authored;
124
+ `bindings`/`envVars`/`requires`/`files` correct.
125
+ - [ ] `lunora registry build` run; `lunora registry build --check` passes.
126
+ - [ ] Workspace package: scaffolded via `vis generate lunora-package`; no `.js`
127
+ extensions, no mixed default+named exports, catalog versions used.
128
+ - [ ] `lint:types` and `test` pass for the new package.
129
+ - [ ] README documents install, bindings, env vars, and any `lunora/` glue.
@@ -0,0 +1,150 @@
1
+ ---
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.
7
+ ---
8
+
9
+ # Lunora Deploy
10
+
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/` and typechecks.
33
+ 2. **Validate `wrangler.jsonc`** — required `compatibility_date`, the
34
+ `nodejs_compat` flag, and the `SHARD` Durable Object binding.
35
+ 3. **Schema-drift gate** — blocks if the committed baseline
36
+ (`lunora/.lunora-schema.json`) drifted with a breaking change and no
37
+ accompanying migration. The baseline is re-blessed only after the deploy
38
+ succeeds.
39
+ 4. **`wrangler deploy`** — builds and pushes the worker (and any container
40
+ images).
41
+
42
+ Useful flags: `--env <name>` (Cloudflare environment), `--migrate` (run pending
43
+ data migrations against the live worker after deploy, with `--migrate-token` /
44
+ `--migrate-url`), `--allow-schema-drift` (override the gate — use sparingly), and
45
+ `--update-schema-baseline` (re-bless the baseline with the current shape).
46
+
47
+ ## Preflight: `lunora doctor`
48
+
49
+ Run the read-only preflight before deploying. It checks:
50
+
51
+ - `wrangler.jsonc` present with the `SHARD` durable-object binding.
52
+ - D1 `database_id`s are real, not placeholders (`<replace>` / empty).
53
+ - `send_email` destination addresses aren't placeholders.
54
+ - `.dev.vars` secret-looking keys are filled.
55
+ - Declared containers are exported by the worker entry.
56
+
57
+ ```bash
58
+ lunora doctor # FAIL → exit 1; WARN/INFO don't block
59
+ ```
60
+
61
+ `lunora verify` and `lunora prepare` run related checks (drift gate, wrangler
62
+ validation) — wire `lunora verify` into CI to catch drift before a deploy.
63
+
64
+ ## `wrangler.jsonc` — the binding contract
65
+
66
+ A Lunora worker needs the ShardDO (and SessionDO when auth is wired), the
67
+ SQLite-DO migration tag, and whatever D1/R2 the app uses:
68
+
69
+ ```jsonc
70
+ {
71
+ "name": "my-app",
72
+ "main": "src/index.ts",
73
+ "compatibility_date": "2026-04-07",
74
+ "compatibility_flags": ["nodejs_compat"],
75
+ "durable_objects": {
76
+ "bindings": [
77
+ { "name": "SHARD", "class_name": "ShardDO" },
78
+ { "name": "SESSION", "class_name": "SessionDO" }, // only with @lunora/auth
79
+ ],
80
+ },
81
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ShardDO", "SessionDO"] }],
82
+ "d1_databases": [{ "binding": "DB", "database_name": "my-app-global", "database_id": "<replace>" }],
83
+ "r2_buckets": [{ "binding": "FILES", "bucket_name": "my-app-files" }],
84
+ }
85
+ ```
86
+
87
+ `lunora dev` auto-reconciles most of this (and `lunora registry add` adds the
88
+ bindings an item needs), but the **resources themselves** must exist and their
89
+ ids must be filled in:
90
+
91
+ ```bash
92
+ wrangler d1 create my-app-global # paste the returned database_id into wrangler.jsonc
93
+ wrangler r2 bucket create my-app-files
94
+ ```
95
+
96
+ The DO `class_name`s must be exported by your worker entry (the
97
+ `createShardDO()` / generated container exports) — wrangler rejects a binding
98
+ whose class the worker doesn't export. `lunora doctor` surfaces a missing
99
+ container export proactively.
100
+
101
+ ## Secrets: `wrangler secret`, not `.dev.vars`
102
+
103
+ `.dev.vars` is **dev only** — it is git-ignored and never deployed. Production
104
+ secrets (`BETTER_AUTH_SECRET`, `RESEND_API_KEY`, provider client secrets, …) go
105
+ into Cloudflare:
106
+
107
+ ```bash
108
+ wrangler secret put BETTER_AUTH_SECRET # prompts for the value, stored encrypted
109
+ wrangler secret list
110
+ ```
111
+
112
+ Set every secret your app reads (mirror the secret-looking keys in `.dev.vars`)
113
+ before the first request hits production.
114
+
115
+ ## Dev vs Production
116
+
117
+ - **Development:** `lunora dev` (Vite + workerd + Studio + codegen-on-save). The
118
+ schema baseline and `.dev.vars` belong to dev.
119
+ - **Production:** `lunora deploy`. Separate D1 database / R2 buckets / secrets
120
+ from dev. Never point a dev worker at prod resources.
121
+
122
+ For `.global()` table DDL, generate and commit SQL migrations with `lunora
123
+ migrate generate` before deploying; `@lunora/d1`'s runner applies them. For data
124
+ backfills, deploy first, then `lunora deploy --migrate` (or `lunora migrate up
125
+ --prod`). See `lunora-migration-helper`.
126
+
127
+ ## Common Pitfalls
128
+
129
+ 1. **Placeholder `database_id`.** The D1 binding ships with `<replace>`; run
130
+ `wrangler d1 create` and paste the id. `lunora doctor` catches this.
131
+ 2. **DO class not exported.** `wrangler deploy` fails if a `class_name` isn't
132
+ exported by the worker entry — export `ShardDO`/`SessionDO`/generated
133
+ containers.
134
+ 3. **Secrets only in `.dev.vars`.** They never reach production; use `wrangler
135
+ secret put` for every prod secret.
136
+ 4. **Bypassing the drift gate.** `--allow-schema-drift` ships a breaking schema
137
+ with no migration — stage the change (`lunora-migration-helper`) instead.
138
+ 5. **Deploying with uncommitted codegen.** Commit `lunora/_generated/` and
139
+ `lunora/.lunora-schema.json` so CI and the gate see the same baseline.
140
+
141
+ ## Checklist
142
+
143
+ - [ ] `lunora doctor` passes (no FAIL).
144
+ - [ ] `wrangler.jsonc` has `compatibility_date`, `nodejs_compat`, the `SHARD` DO
145
+ binding, and the SQLite migration tag.
146
+ - [ ] D1 / R2 resources created; real ids pasted into `wrangler.jsonc`.
147
+ - [ ] DO + container `class_name`s exported by the worker entry.
148
+ - [ ] Production secrets set via `wrangler secret put`.
149
+ - [ ] Schema changes migrated; the drift gate is green (no `--allow-schema-drift`).
150
+ - [ ] `lunora deploy` succeeded; `--migrate` run if data backfills were pending.