@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.
- package/LICENSE.md +105 -0
- package/README.md +109 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/bin.mjs +11 -0
- package/dist/index.d.mts +852 -0
- package/dist/index.d.ts +852 -0
- package/dist/index.mjs +19 -0
- package/dist/packem_chunks/handler.mjs +76 -0
- package/dist/packem_chunks/handler10.mjs +22 -0
- package/dist/packem_chunks/handler11.mjs +192 -0
- package/dist/packem_chunks/handler12.mjs +131 -0
- package/dist/packem_chunks/handler13.mjs +65 -0
- package/dist/packem_chunks/handler14.mjs +58 -0
- package/dist/packem_chunks/handler15.mjs +79 -0
- package/dist/packem_chunks/handler16.mjs +41 -0
- package/dist/packem_chunks/handler17.mjs +105 -0
- package/dist/packem_chunks/handler18.mjs +172 -0
- package/dist/packem_chunks/handler19.mjs +89 -0
- package/dist/packem_chunks/handler2.mjs +114 -0
- package/dist/packem_chunks/handler20.mjs +94 -0
- package/dist/packem_chunks/handler21.mjs +311 -0
- package/dist/packem_chunks/handler3.mjs +204 -0
- package/dist/packem_chunks/handler4.mjs +33 -0
- package/dist/packem_chunks/handler5.mjs +49 -0
- package/dist/packem_chunks/handler6.mjs +91 -0
- package/dist/packem_chunks/handler7.mjs +42 -0
- package/dist/packem_chunks/handler8.mjs +174 -0
- package/dist/packem_chunks/handler9.mjs +16 -0
- package/dist/packem_chunks/planDevCommand.mjs +543 -0
- package/dist/packem_chunks/runCodegenCommand.mjs +52 -0
- package/dist/packem_chunks/runDeployCommand.mjs +504 -0
- package/dist/packem_chunks/runInitCommand.mjs +652 -0
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +397 -0
- package/dist/packem_chunks/runResetCommand.mjs +41 -0
- package/dist/packem_chunks/runRpcCommand.mjs +68 -0
- package/dist/packem_shared/COMMANDS-1V_KEx35.mjs +905 -0
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +244 -0
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +19 -0
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +13 -0
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +38 -0
- package/dist/packem_shared/command-BDXcJCCJ.mjs +14 -0
- package/dist/packem_shared/createLogger-CHPNjFw2.mjs +73 -0
- package/dist/packem_shared/defaultSpawner-DxI3mebw.mjs +43 -0
- package/dist/packem_shared/diffSnapshots-RR2ZE8Ya.mjs +161 -0
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +21 -0
- package/dist/packem_shared/features-ocSSpZtS.mjs +24 -0
- package/dist/packem_shared/insertSchemaExtension-BuzF6-t2.mjs +59 -0
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +41 -0
- package/dist/packem_shared/output-format-7gyGR3h8.mjs +17 -0
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +56 -0
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +94 -0
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +16 -0
- package/dist/packem_shared/runAddCommand-BZGkRnBs.mjs +693 -0
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +79 -0
- package/dist/packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs +43 -0
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +12 -0
- package/package.json +61 -18
- package/skills/README.md +29 -0
- package/skills/lunora/SKILL.md +83 -0
- package/skills/lunora-create-package/SKILL.md +129 -0
- package/skills/lunora-deploy/SKILL.md +150 -0
- package/skills/lunora-functions/SKILL.md +182 -0
- package/skills/lunora-migration-helper/SKILL.md +194 -0
- package/skills/lunora-performance-audit/SKILL.md +143 -0
- package/skills/lunora-quickstart/SKILL.md +240 -0
- package/skills/lunora-realtime/SKILL.md +177 -0
- package/skills/lunora-setup-auth/SKILL.md +170 -0
- package/skills/lunora-setup-hyperdrive/SKILL.md +154 -0
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +171 -0
- package/skills/lunora-setup-mail/SKILL.md +151 -0
- package/skills/lunora-setup-scheduler/SKILL.md +157 -0
- package/skills/lunora-setup-storage/SKILL.md +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lunora-setup-storage
|
|
3
|
+
description: Adds R2-backed file storage to a Lunora app. Use for uploads/downloads via `lunora registry add storage`, signed PUT/GET URLs, the `UPLOADS` R2 bucket binding, `STORAGE_SIGNING_SECRET`, per-tenant key scoping, and verifying downloads in the Worker.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lunora Setup Storage
|
|
7
|
+
|
|
8
|
+
Wire R2-backed file storage into a Lunora app using the `storage` registry item,
|
|
9
|
+
which is built on `@lunora/storage` (an R2 adapter plus HMAC signed-URL helpers)
|
|
10
|
+
and exposes idiomatic Lunora functions for direct browser uploads, gated
|
|
11
|
+
downloads, delete, and list — so the bytes never proxy through your Worker.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Uploading user files (avatars, attachments) straight to R2.
|
|
16
|
+
- Serving private/gated downloads via short-lived signed URLs.
|
|
17
|
+
- Listing or deleting a caller's stored objects.
|
|
18
|
+
|
|
19
|
+
## When Not to Use
|
|
20
|
+
|
|
21
|
+
- The project has no Lunora backend yet — use `lunora-quickstart` first.
|
|
22
|
+
- Storage is already installed and you just want to upload — call
|
|
23
|
+
`client.action("storage/generateUploadUrl", …)` and `PUT` to the returned URL.
|
|
24
|
+
|
|
25
|
+
## Workflow
|
|
26
|
+
|
|
27
|
+
1. Add the `storage` item.
|
|
28
|
+
2. Configure the `UPLOADS` R2 bucket binding and the signing secret.
|
|
29
|
+
3. Regenerate types with `lunora codegen`.
|
|
30
|
+
4. Verify signed downloads in the Worker's `GET /storage/:key` route.
|
|
31
|
+
5. Upload/download from the client.
|
|
32
|
+
|
|
33
|
+
## Step 1: Add the item
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
lunora registry add storage
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This:
|
|
40
|
+
|
|
41
|
+
1. Adds `@lunora/storage` and `@lunora/server` to `package.json` (run
|
|
42
|
+
`pnpm install` afterwards).
|
|
43
|
+
2. Adds an R2 bucket binding to `wrangler.jsonc` (`r2_buckets`, binding
|
|
44
|
+
**`UPLOADS`**, `bucket_name: "REPLACE_ME-uploads"` — rename it to a real
|
|
45
|
+
bucket). It **merges** into any existing `r2_buckets`.
|
|
46
|
+
3. Scaffolds `STORAGE_SIGNING_SECRET` (a secret) and `STORAGE_PUBLIC_BASE_URL`
|
|
47
|
+
into `.dev.vars`.
|
|
48
|
+
4. Copies `lunora/storage/index.ts` (the `generateUploadUrl` /
|
|
49
|
+
`getDownloadUrl` / `deleteObject` / `listObjects` functions) into your
|
|
50
|
+
project — it is **yours** to edit.
|
|
51
|
+
|
|
52
|
+
## Step 2: Configure the binding + secrets
|
|
53
|
+
|
|
54
|
+
| Name | Where | Notes |
|
|
55
|
+
| ------------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
|
|
56
|
+
| `UPLOADS` | `wrangler.jsonc` → `r2_buckets[]` | The R2 bucket binding. Point `bucket_name` at a real bucket. |
|
|
57
|
+
| `STORAGE_SIGNING_SECRET` | secret (`.dev.vars` / `secret put`) | HMAC secret for signed URLs. Min 32 chars; never share across buckets. |
|
|
58
|
+
| `STORAGE_PUBLIC_BASE_URL` | var (`.dev.vars` / `wrangler.jsonc`) | Public host/route that fronts the bucket and serves `GET /storage/:key`. |
|
|
59
|
+
|
|
60
|
+
Generate a real signing secret with `openssl rand -base64 32` and write it with
|
|
61
|
+
`wrangler secret put STORAGE_SIGNING_SECRET` for production.
|
|
62
|
+
|
|
63
|
+
## Step 3: Regenerate types
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
lunora codegen
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The functions surface in the generated `api` as `api.storage.generateUploadUrl`,
|
|
70
|
+
`api.storage.getDownloadUrl`, `api.storage.deleteObject`, and
|
|
71
|
+
`api.storage.listObjects`.
|
|
72
|
+
|
|
73
|
+
## Step 4: Verify downloads in the Worker
|
|
74
|
+
|
|
75
|
+
Signed URLs are only as safe as the route that checks them. Gate
|
|
76
|
+
`GET /storage/:key` with `verifySignedUrl` before streaming the R2 body
|
|
77
|
+
(`@lunora/server` also ships `serveStorageObject` to do this):
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { verifySignedUrl } from "@lunora/storage";
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
84
|
+
const url = new URL(request.url);
|
|
85
|
+
|
|
86
|
+
if (url.pathname.startsWith("/storage/")) {
|
|
87
|
+
const result = await verifySignedUrl(url, env.STORAGE_SIGNING_SECRET);
|
|
88
|
+
|
|
89
|
+
if (!result.valid || result.key === undefined) {
|
|
90
|
+
// Expose only `valid` — a precise reason is a signing oracle.
|
|
91
|
+
return new Response("forbidden", { status: 403 });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const object = await env.UPLOADS.get(result.key);
|
|
95
|
+
|
|
96
|
+
if (!object) {
|
|
97
|
+
return new Response("not found", { status: 404 });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Response(object.body, {
|
|
101
|
+
headers: { "content-type": object.httpMetadata?.contentType ?? "application/octet-stream" },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ... your Lunora handler
|
|
106
|
+
return new Response("not found", { status: 404 });
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`verifySignedUrl` checks expiry, then the HMAC. On a host-rewrite / CDN topology
|
|
112
|
+
pass `{ expectedHost }` (the `STORAGE_PUBLIC_BASE_URL` host) so the signature
|
|
113
|
+
canonicalizes against the host it was minted for.
|
|
114
|
+
|
|
115
|
+
## Step 5: Upload / download from the client
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// 1. ask the server for a signed PUT URL
|
|
119
|
+
const { key, url } = await client.action("storage/generateUploadUrl", {
|
|
120
|
+
key: "avatar.png",
|
|
121
|
+
contentType: file.type,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// 2. upload straight to R2 (no Worker proxy)
|
|
125
|
+
await fetch(url, { method: "PUT", headers: { "content-type": file.type }, body: file });
|
|
126
|
+
|
|
127
|
+
// 3. later, get a signed GET URL to display it
|
|
128
|
+
const { url: downloadUrl } = await client.action("storage/getDownloadUrl", { key: "avatar.png" });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Every key is scoped per-tenant with `scopeKey(tenantPrefix(ctx.auth.userId),
|
|
132
|
+
key)`, so a client-supplied key can never address another user's data. The
|
|
133
|
+
functions return the **scoped** key (`<userId>/avatar.png`) alongside the URL;
|
|
134
|
+
persist that, and pass the bare key back in — the component re-scopes it.
|
|
135
|
+
|
|
136
|
+
## Common Pitfalls
|
|
137
|
+
|
|
138
|
+
1. **Skipping `verifySignedUrl` on the download route.** Without it, anyone can
|
|
139
|
+
read any key. Always verify before streaming.
|
|
140
|
+
2. **Placeholder bucket name.** `bucket_name: "REPLACE_ME-uploads"` ships as a
|
|
141
|
+
placeholder — rename it to a real R2 bucket.
|
|
142
|
+
3. **Short / shared signing secret.** Use ≥32 chars and a distinct secret per
|
|
143
|
+
bucket; reusing it lets one bucket's URLs sign for another.
|
|
144
|
+
4. **Proxying bytes through the Worker.** The design uploads/downloads directly
|
|
145
|
+
to R2 via signed URLs — don't re-route the file body through a function.
|
|
146
|
+
|
|
147
|
+
## Checklist
|
|
148
|
+
|
|
149
|
+
- [ ] `lunora registry add storage` run, `pnpm install` done.
|
|
150
|
+
- [ ] `UPLOADS` bucket bound to a real bucket; `STORAGE_SIGNING_SECRET` (≥32
|
|
151
|
+
chars) and `STORAGE_PUBLIC_BASE_URL` set.
|
|
152
|
+
- [ ] `lunora codegen` run so `api.storage.*` is generated.
|
|
153
|
+
- [ ] `GET /storage/:key` route verifies signed URLs before streaming.
|
|
154
|
+
- [ ] Verified a client upload → signed download round-trip.
|