@mediamonks/canopy-cli 0.7.25 → 0.7.27
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/dist/{db-jmYaum8F.mjs → db-DVMwCLlU.mjs} +2 -2
- package/dist/{deploy-CYKiBnrg.mjs → deploy-CTMcwHhI.mjs} +2 -2
- package/dist/dev-BOvMCaML.mjs +2 -0
- package/dist/{dev-BanjQNiD.mjs → dev-BvjbJcse.mjs} +4 -4
- package/dist/docs/cli-dev.md +1 -1
- package/dist/docs/cli-feedback.md +9 -6
- package/dist/docs/cli-secrets.md +45 -0
- package/dist/docs/dev.md +3 -3
- package/dist/docs/index.json +5 -0
- package/dist/docs/platform-google.md +15 -8
- package/dist/docs/porting-nextjs.md +19 -19
- package/dist/{docs-xRbVshXp.mjs → docs-CuS9nIXm.mjs} +2 -2
- package/dist/{egress-BjN-7vil.mjs → egress-Cn-P46O3.mjs} +1 -1
- package/dist/{feedback-CGjOAHON.mjs → feedback-DormOsQa.mjs} +39 -19
- package/dist/index.mjs +26 -25
- package/dist/{local-DvzG4XUB.mjs → local-laAZEDVJ.mjs} +2 -2
- package/dist/{login-COZ7Xcm0.mjs → login-C4MDP5NM.mjs} +1 -1
- package/dist/{logs-B_YoflCq.mjs → logs-CfDINw7U.mjs} +1 -1
- package/dist/{migrations-Btzr9_q7.mjs → migrations-B9fXxMDj.mjs} +1 -1
- package/dist/{open-D2hH933K.mjs → open-rS1ARzOy.mjs} +1 -1
- package/dist/{package-vZ0xH49Y.mjs → package-CsmBH_fN.mjs} +1 -1
- package/dist/{projects-CLs6MEF7.mjs → projects-Dmm_976k.mjs} +1 -1
- package/dist/{schedule-B8zA8WlG.mjs → schedule-Buu71RWQ.mjs} +1 -1
- package/dist/secrets-BD3DaTih.mjs +121 -0
- package/dist/{skill-PoaZrCNy.mjs → skill-DX18WOap.mjs} +2 -2
- package/dist/{staging-pZpdZuj_.mjs → staging-CKEoDnac.mjs} +1 -1
- package/dist/templates/worker/.dev.vars.example +1 -1
- package/dist/templates/worker/AGENTS.md +1 -1
- package/dist/templates/worker/worker/index.ts +1 -1
- package/dist/{token-BKwGa10J.mjs → token-Dp2CoIYj.mjs} +1 -1
- package/dist/{ui-CUNkis89.mjs → ui-DR-siu2S.mjs} +52 -5
- package/dist/{upgrade-DjrNZ-B4.mjs → upgrade-PfdH0NPe.mjs} +2 -2
- package/dist/{usage-UmzU6sGp.mjs → usage-81JSiWIx.mjs} +1 -1
- package/package.json +1 -1
- package/dist/dev-BAcZi226.mjs +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, s as apiDataRequest } from "./ui-
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, s as apiDataRequest } from "./ui-DR-siu2S.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-B9fXxMDj.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, n as siteConfig, o as projectFlagArgs, s as projectUrl, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { a as unwrap, c as apiDownload, d as withContext, l as apiRequest, r as reportErrors, s as apiDataRequest, t as formatDateTime } from "./ui-
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { a as unwrap, c as apiDownload, d as withContext, l as apiRequest, r as reportErrors, s as apiDataRequest, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-B9fXxMDj.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { d as withContext, l as apiRequest, o as ApiError, r as reportErrors, t as formatDateTime } from "./ui-
|
|
3
|
-
import { r as readMigrationFiles } from "./migrations-
|
|
2
|
+
import { d as withContext, l as apiRequest, o as ApiError, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
|
+
import { r as readMigrationFiles } from "./migrations-B9fXxMDj.mjs";
|
|
4
4
|
import { n as stampWorkerHeaders } from "./src-CwSKZwNK.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
@@ -176,7 +176,7 @@ async function devRemote(flags, slug) {
|
|
|
176
176
|
async function devLocal(flags, slug) {
|
|
177
177
|
let dump;
|
|
178
178
|
if (flags.pull) {
|
|
179
|
-
const { fetchProjectDump } = await import("./local-
|
|
179
|
+
const { fetchProjectDump } = await import("./local-laAZEDVJ.mjs");
|
|
180
180
|
console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
|
|
181
181
|
dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
|
|
182
182
|
}
|
|
@@ -191,7 +191,7 @@ async function devLocal(flags, slug) {
|
|
|
191
191
|
const reason = err instanceof ApiError ? err.explain().why : String(err);
|
|
192
192
|
console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
|
|
193
193
|
}
|
|
194
|
-
const { createSandbox } = await import("./local-
|
|
194
|
+
const { createSandbox } = await import("./local-laAZEDVJ.mjs");
|
|
195
195
|
const sandbox = await createSandbox({
|
|
196
196
|
slug,
|
|
197
197
|
persistDir: join(flags.dir, ".canopy", "local"),
|
package/dist/docs/cli-dev.md
CHANGED
|
@@ -47,7 +47,7 @@ To tick a schedule by hand, post its name to the same path the platform uses:
|
|
|
47
47
|
curl -X POST localhost:5173/__canopy/scheduled -d '{"name":"nightly-digest"}'
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
**Worker secrets.** Secrets you set in the dashboard are never downloaded — the platform can't hand a stored value back, and your laptop is not where a live API key belongs. Put local values in a `.dev.vars` file next to `canopy.json` instead:
|
|
50
|
+
**Worker secrets.** Secrets you set in the dashboard or with [`canopy secrets set`](https://docs.canopy.website/cli/secrets) are never downloaded — the platform can't hand a stored value back, and your laptop is not where a live API key belongs. Put local values in a `.dev.vars` file next to `canopy.json` instead:
|
|
51
51
|
|
|
52
52
|
```
|
|
53
53
|
STRIPE_KEY=sk_test_local
|
|
@@ -24,12 +24,15 @@ canopy feedback attach <id> ./after-fix.png --comment "fixed — see the shot"
|
|
|
24
24
|
|
|
25
25
|
## Subcommands
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
Every report has a key made of its project and a number, like
|
|
28
|
+
`my-project-17`. Inbox commands resolve the project the usual way —
|
|
29
|
+
`canopy.json` in the target directory, or `--project <slug>` — and require
|
|
30
|
+
project membership; inside the project you can refer to a report by its
|
|
31
|
+
number alone (`canopy feedback show 17`). The item-scoped commands (`show`,
|
|
32
|
+
`comment`, `status`, `attach`) also take the full key from anywhere, with no
|
|
33
|
+
project context: the key names its project. Older ids from before keys
|
|
34
|
+
existed still work wherever a key does. `list` and `show` take `--json` for
|
|
35
|
+
machine-readable output.
|
|
33
36
|
|
|
34
37
|
- `list [--status <s>] [--kind <k>]` — the project's inbox. Statuses:
|
|
35
38
|
`investigate`, `investigated`, `accepted`, `in_progress`, `resolved`,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: canopy secrets
|
|
3
|
+
description: Set, list, and remove the project's worker secrets from the terminal.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`canopy secrets` manages the secrets your backend worker reads as `c.env.NAME`
|
|
7
|
+
— API keys, signing secrets, anything the browser must never see. It does the
|
|
8
|
+
same job as the dashboard's Secrets panel, so a first deploy can be scripted
|
|
9
|
+
end to end: create the project, allow its hosts, set its secrets, deploy.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
canopy secrets list
|
|
15
|
+
printf %s "$STRIPE_SECRET" | canopy secrets set STRIPE_SECRET
|
|
16
|
+
canopy secrets set STRIPE_SECRET --from-env STRIPE_SECRET
|
|
17
|
+
canopy secrets set STRIPE_SECRET # hidden prompt in a terminal
|
|
18
|
+
canopy secrets remove STRIPE_SECRET
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Arguments & flags
|
|
22
|
+
|
|
23
|
+
All subcommands resolve the project the usual way — `canopy.json` in the
|
|
24
|
+
target directory, or `--project <slug>` to override — and honor the global
|
|
25
|
+
`--profile <site>` / `CANOPY_PROFILE`.
|
|
26
|
+
|
|
27
|
+
- `list` — print the secret names and when each was last set. Values are
|
|
28
|
+
never shown.
|
|
29
|
+
- `set <name>` — store a value. Names are `UPPER_SNAKE_CASE`, up to 64
|
|
30
|
+
characters. The value comes from **stdin** when piped (one trailing newline
|
|
31
|
+
is trimmed, so `echo` works too), from `--from-env <VAR>` to read an
|
|
32
|
+
environment variable, or from a hidden prompt when you run it in a terminal.
|
|
33
|
+
There is deliberately no `--value` flag: a value on the command line would
|
|
34
|
+
end up in shell history and process lists.
|
|
35
|
+
- `remove <name>` — delete a secret. The worker stops seeing it right away.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
Secrets live on the platform, encrypted, per project. Setting or removing one
|
|
40
|
+
re-rolls the running worker immediately — no deploy needed. The live and
|
|
41
|
+
staging channels share one set of secrets, like the egress allowlist. Values
|
|
42
|
+
travel one way: once stored they can't be read back by anyone, including
|
|
43
|
+
`canopy dev`, which is why local development uses a `.dev.vars` file with
|
|
44
|
+
stand-in values (see [`canopy dev`](https://docs.canopy.website/cli/dev)). A project holds at most 64
|
|
45
|
+
secrets of up to 8 KiB each.
|
package/dist/docs/dev.md
CHANGED
|
@@ -53,9 +53,9 @@ Projects with a `worker` entry in `canopy.json` get their backend locally too:
|
|
|
53
53
|
production (`env.CANOPY`, `env.DB`, `env.BLOB`, `env.USER`). Edit and refresh —
|
|
54
54
|
there's no build step.
|
|
55
55
|
|
|
56
|
-
Secrets are the one thing that doesn't come from the
|
|
57
|
-
|
|
58
|
-
`canopy.json`:
|
|
56
|
+
Secrets are the one thing that doesn't come from the platform. Stored values
|
|
57
|
+
(set in the dashboard or with `canopy secrets set`) are never downloadable, so
|
|
58
|
+
put local ones in a `.dev.vars` file next to `canopy.json`:
|
|
59
59
|
|
|
60
60
|
```
|
|
61
61
|
STRIPE_KEY=sk_test_local
|
package/dist/docs/index.json
CHANGED
|
@@ -251,6 +251,11 @@
|
|
|
251
251
|
"title": "canopy egress",
|
|
252
252
|
"summary": "Manage which outbound hosts the project's backend worker may fetch."
|
|
253
253
|
},
|
|
254
|
+
{
|
|
255
|
+
"slug": "cli-secrets",
|
|
256
|
+
"title": "canopy secrets",
|
|
257
|
+
"summary": "Set, list, and remove the project's worker secrets from the terminal."
|
|
258
|
+
},
|
|
254
259
|
{
|
|
255
260
|
"slug": "cli-schedule",
|
|
256
261
|
"title": "canopy schedule",
|
|
@@ -97,6 +97,12 @@ so is a write using a reading-only permission.
|
|
|
97
97
|
|
|
98
98
|
Drive is deliberately limited to files your project created or that someone
|
|
99
99
|
explicitly opened with it — connecting Drive does not expose a whole Drive.
|
|
100
|
+
The one exception is **Copy files it is given**: hand your project the id of a
|
|
101
|
+
file the account can open (a template deck, say) and it may copy that one file.
|
|
102
|
+
The copy is a file your project made, so with _Files this project makes_ it can
|
|
103
|
+
open and change the copy from then on — the original is never touched. It cannot
|
|
104
|
+
list, search or read anything else. Because Google has no copy-only permission,
|
|
105
|
+
this one counts as wider access and needs a Canopy admin to unlock it.
|
|
100
106
|
|
|
101
107
|
### Wider permissions
|
|
102
108
|
|
|
@@ -145,14 +151,15 @@ reading-only permission is refused before it reaches Google.
|
|
|
145
151
|
|
|
146
152
|
**Drive**
|
|
147
153
|
|
|
148
|
-
| Permission | What it allows
|
|
149
|
-
| --------------------------------------------- |
|
|
150
|
-
| Files this project makes | Create files, and reopen the ones this project made or someone picked for it.
|
|
151
|
-
| Its own config folder | A hidden folder only this project can see. Invisible in the account's Drive.
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
| Read
|
|
155
|
-
|
|
|
154
|
+
| Permission | What it allows |
|
|
155
|
+
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
156
|
+
| Files this project makes | Create files, and reopen the ones this project made or someone picked for it. |
|
|
157
|
+
| Its own config folder | A hidden folder only this project can see. Invisible in the account's Drive. |
|
|
158
|
+
| Copy files it is given **(wider access)** | Copy a file the account can open, by id, into a file this project owns. Cannot list, read or change anything else. |
|
|
159
|
+
| Recordings and transcripts **(wider access)** | Read the recordings and transcripts Meet saved to Drive. |
|
|
160
|
+
| Read the whole Drive _(under consideration)_ | Read and download every file the account can see. |
|
|
161
|
+
| Read all file names _(under consideration)_ | Names and metadata of every file the account can see, without contents. |
|
|
162
|
+
| Full Drive access _(under consideration)_ | Read, change and delete every file the account can see. |
|
|
156
163
|
|
|
157
164
|
**Gmail**
|
|
158
165
|
|
|
@@ -62,24 +62,24 @@ together as a single version, with rollback.
|
|
|
62
62
|
List everything in the app that runs on the server. Each item lands in one of
|
|
63
63
|
three buckets:
|
|
64
64
|
|
|
65
|
-
| Next.js feature | Where it goes on Canopy
|
|
66
|
-
| ----------------------------------------------- |
|
|
67
|
-
| Pages, layouts, client components | Static build (unchanged in spirit)
|
|
68
|
-
| Server components fetching data at request time | Client-side fetching, or a worker endpoint
|
|
69
|
-
| Route handlers (`app/api/*/route.ts`) | Worker endpoints under `/api/*`
|
|
70
|
-
| Server actions | Explicit worker endpoints, called with `fetch` from the client
|
|
71
|
-
| Middleware | Usually deleted (auth is platform-provided); anything else moves into the worker
|
|
72
|
-
| NextAuth / auth middleware | Deleted — use `canopy.auth.user()` (client) or `env.USER` (worker)
|
|
73
|
-
| Database (Prisma, Postgres, …) | `canopy.db` — per-project database, document API + SQL escape hatch
|
|
74
|
-
| File uploads (S3, …) | `canopy.blob` — per-project file storage with signed downloads
|
|
75
|
-
| AI SDK / OpenAI client | `canopy.ai.chat()`, or point the Vercel AI SDK at `canopy.ai.endpoint()`
|
|
76
|
-
| `NEXT_PUBLIC_*` env vars | Build-time env vars in your CI — the platform doesn't inject build config
|
|
77
|
-
| Server-side env vars / secrets | Project secrets (dashboard Settings) — injected into the worker's `env`
|
|
78
|
-
| Cron (`vercel.json` crons) | [Schedules](https://docs.canopy.website/platform/schedules) — delivered to the worker's `scheduled()`
|
|
79
|
-
| ISR / `revalidate` / caching | Dropped — at internal-tool scale, fetch fresh or cache in the client
|
|
80
|
-
| `next/image` optimization | Plain `<img>`, or optimize at build time
|
|
81
|
-
| Custom domain | Not available — projects live at `<slug>.canopy.<domain>` behind sign-in
|
|
82
|
-
| Queues / background jobs | Not supported — restructure around request/response plus schedules
|
|
65
|
+
| Next.js feature | Where it goes on Canopy |
|
|
66
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
67
|
+
| Pages, layouts, client components | Static build (unchanged in spirit) |
|
|
68
|
+
| Server components fetching data at request time | Client-side fetching, or a worker endpoint |
|
|
69
|
+
| Route handlers (`app/api/*/route.ts`) | Worker endpoints under `/api/*` |
|
|
70
|
+
| Server actions | Explicit worker endpoints, called with `fetch` from the client |
|
|
71
|
+
| Middleware | Usually deleted (auth is platform-provided); anything else moves into the worker |
|
|
72
|
+
| NextAuth / auth middleware | Deleted — use `canopy.auth.user()` (client) or `env.USER` (worker) |
|
|
73
|
+
| Database (Prisma, Postgres, …) | `canopy.db` — per-project database, document API + SQL escape hatch |
|
|
74
|
+
| File uploads (S3, …) | `canopy.blob` — per-project file storage with signed downloads |
|
|
75
|
+
| AI SDK / OpenAI client | `canopy.ai.chat()`, or point the Vercel AI SDK at `canopy.ai.endpoint()` |
|
|
76
|
+
| `NEXT_PUBLIC_*` env vars | Build-time env vars in your CI — the platform doesn't inject build config |
|
|
77
|
+
| Server-side env vars / secrets | Project secrets (`canopy secrets set` or dashboard Settings) — injected into the worker's `env` |
|
|
78
|
+
| Cron (`vercel.json` crons) | [Schedules](https://docs.canopy.website/platform/schedules) — delivered to the worker's `scheduled()` |
|
|
79
|
+
| ISR / `revalidate` / caching | Dropped — at internal-tool scale, fetch fresh or cache in the client |
|
|
80
|
+
| `next/image` optimization | Plain `<img>`, or optimize at build time |
|
|
81
|
+
| Custom domain | Not available — projects live at `<slug>.canopy.<domain>` behind sign-in |
|
|
82
|
+
| Queues / background jobs | Not supported — restructure around request/response plus schedules |
|
|
83
83
|
|
|
84
84
|
If the last two rows are deal-breakers, stop here: see
|
|
85
85
|
[When not to port](#when-not-to-port).
|
|
@@ -253,6 +253,6 @@ past greenhouse size (see [Use cases](https://docs.canopy.website/overview/use-c
|
|
|
253
253
|
- [ ] Worker: route handlers + server actions → `/api/*` endpoints in `worker/index.ts`
|
|
254
254
|
- [ ] Data: ORM → `canopy.db`; uploads → `canopy.blob`; AI clients → `canopy.ai`
|
|
255
255
|
- [ ] Delete NextAuth/middleware; read identity from `canopy.auth.user()` / `env.USER`
|
|
256
|
-
- [ ] Secrets
|
|
256
|
+
- [ ] Secrets via `canopy secrets set` (or dashboard Settings); `NEXT_PUBLIC_*` into your CI build
|
|
257
257
|
- [ ] Crons into Settings → Schedules, handled in `scheduled()`
|
|
258
258
|
- [ ] `canopy.json` with `build`, `dist`, `worker` — then `canopy deploy`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CsmBH_fN.mjs";
|
|
2
2
|
import { n as siteConfig } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
|
-
import { r as reportErrors } from "./ui-
|
|
4
|
+
import { r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region src/egress.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-CsmBH_fN.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
3
|
-
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-
|
|
3
|
+
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-DR-siu2S.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { basename, extname, join } from "node:path";
|
|
6
6
|
import { readFile, writeFile } from "node:fs/promises";
|
|
@@ -72,19 +72,38 @@ const jsonArg = { json: {
|
|
|
72
72
|
type: "boolean",
|
|
73
73
|
description: "Machine-readable JSON output"
|
|
74
74
|
} };
|
|
75
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
76
|
+
const KEY_RE = /^([a-z][a-z0-9-]*)-(\d+)$/;
|
|
75
77
|
/**
|
|
76
|
-
* The project
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* an id from a notification or a Slack thread, in a directory that isn't the
|
|
80
|
-
* project.
|
|
78
|
+
* The project slug a full key names (`kitchen-sink-17` → `kitchen-sink`), or
|
|
79
|
+
* null for a UUID or a bare number. UUIDs are checked first: one whose last
|
|
80
|
+
* group is all digits would otherwise look like a key.
|
|
81
81
|
*/
|
|
82
|
-
|
|
82
|
+
function slugOfKey(ref) {
|
|
83
|
+
if (UUID_RE.test(ref)) return null;
|
|
84
|
+
return KEY_RE.exec(ref.toLowerCase())?.[1] ?? null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The project an item-scoped command targets. A full key names its own
|
|
88
|
+
* project and wins over the directory — `canopy feedback show other-3` from
|
|
89
|
+
* inside `sink` means other's report, not a miss on sink's. Otherwise
|
|
90
|
+
* `--project` or canopy.json; and failing those, the API's project-less
|
|
91
|
+
* lookup for a UUID from an old link. A bare number is only meaningful with a
|
|
92
|
+
* project in hand.
|
|
93
|
+
*/
|
|
94
|
+
async function resolveItemSlug(args, ref) {
|
|
95
|
+
const fromKey = slugOfKey(ref);
|
|
96
|
+
if (fromKey) return fromKey;
|
|
83
97
|
const { slug } = tryResolveProject(args);
|
|
84
98
|
if (slug) return slug;
|
|
85
|
-
|
|
99
|
+
if (/^\d+$/.test(ref)) throw new Error(`"${ref}" is a bare report number — run this inside the project, pass --project, or use the full key (e.g. my-project-${ref}).`);
|
|
100
|
+
const { project } = await apiRequest("GET", `/feedback/${encodeURIComponent(ref)}`);
|
|
86
101
|
return project.slug;
|
|
87
102
|
}
|
|
103
|
+
/** What a report is called in output: its key, or the UUID on pre-key rows. */
|
|
104
|
+
function refOf(item) {
|
|
105
|
+
return item.key ?? item.id;
|
|
106
|
+
}
|
|
88
107
|
function firstLine(body) {
|
|
89
108
|
const line = body.split("\n")[0] ?? "";
|
|
90
109
|
return line.length > 72 ? `${line.slice(0, 71)}…` : line;
|
|
@@ -101,7 +120,7 @@ function printItems(items) {
|
|
|
101
120
|
for (const item of items) {
|
|
102
121
|
const comments = item.comment_count ? ` 💬${item.comment_count}` : "";
|
|
103
122
|
const shots = item.attachment_count ? ` 📎${item.attachment_count}` : "";
|
|
104
|
-
console.log(`${item
|
|
123
|
+
console.log(`${refOf(item)} ${item.status} ${item.kind} ${itemLabel(item)}${comments}${shots}`);
|
|
105
124
|
}
|
|
106
125
|
}
|
|
107
126
|
const listCommand = defineCommand({
|
|
@@ -139,7 +158,7 @@ const showCommand = defineCommand({
|
|
|
139
158
|
args: {
|
|
140
159
|
id: {
|
|
141
160
|
type: "positional",
|
|
142
|
-
description: "
|
|
161
|
+
description: "Report key like my-project-17 (from `canopy feedback list`), or its number inside the project",
|
|
143
162
|
required: true
|
|
144
163
|
},
|
|
145
164
|
...projectDirArgs,
|
|
@@ -150,7 +169,8 @@ const showCommand = defineCommand({
|
|
|
150
169
|
}
|
|
151
170
|
},
|
|
152
171
|
run: reportErrors("feedback show", async ({ args }) => {
|
|
153
|
-
const
|
|
172
|
+
const slug = slugOfKey(args.id) ?? tryResolveProject(args).slug;
|
|
173
|
+
if (!slug && /^\d+$/.test(args.id)) throw new Error(`"${args.id}" is a bare report number — run this inside the project, pass --project, or use the full key (e.g. my-project-${args.id}).`);
|
|
154
174
|
const detail = await apiRequest("GET", slug ? `/projects/${slug}/feedback/${encodeURIComponent(args.id)}` : `/feedback/${encodeURIComponent(args.id)}`);
|
|
155
175
|
if (args.json) {
|
|
156
176
|
console.log(JSON.stringify(detail, null, 2));
|
|
@@ -158,7 +178,7 @@ const showCommand = defineCommand({
|
|
|
158
178
|
}
|
|
159
179
|
const { item, comments } = detail;
|
|
160
180
|
if (item.title) console.log(item.title);
|
|
161
|
-
console.log(`${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
181
|
+
console.log(`${refOf(item)} · ${item.kind} · ${item.status} · via ${item.source} · ${formatDateTime(item.created_at)}`);
|
|
162
182
|
console.log(`reported by ${item.reporter_name || item.reporter_email || "unknown"}` + (item.deployment_id ? ` on ${item.deployment_id}` : "") + (item.channel === "staging" ? " (staging)" : ""));
|
|
163
183
|
console.log(`\n${item.body}\n`);
|
|
164
184
|
if (item.context) {
|
|
@@ -207,7 +227,7 @@ const commentCommand = defineCommand({
|
|
|
207
227
|
args: {
|
|
208
228
|
id: {
|
|
209
229
|
type: "positional",
|
|
210
|
-
description: "
|
|
230
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
211
231
|
required: true
|
|
212
232
|
},
|
|
213
233
|
text: {
|
|
@@ -237,7 +257,7 @@ const statusCommand = defineCommand({
|
|
|
237
257
|
args: {
|
|
238
258
|
id: {
|
|
239
259
|
type: "positional",
|
|
240
|
-
description: "
|
|
260
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
241
261
|
required: true
|
|
242
262
|
},
|
|
243
263
|
to: {
|
|
@@ -281,7 +301,7 @@ const mineCommand = defineCommand({
|
|
|
281
301
|
console.log("You haven't filed any reports.");
|
|
282
302
|
return;
|
|
283
303
|
}
|
|
284
|
-
for (const item of items) console.log(`${item
|
|
304
|
+
for (const item of items) console.log(`${refOf(item)} ${item.status} ${item.kind} [${item.project_slug}] ${itemLabel(item)}`);
|
|
285
305
|
})
|
|
286
306
|
});
|
|
287
307
|
const reportCommand = defineCommand({
|
|
@@ -348,8 +368,8 @@ const reportCommand = defineCommand({
|
|
|
348
368
|
...args.status ? { status: args.status } : {},
|
|
349
369
|
context
|
|
350
370
|
});
|
|
351
|
-
console.log(`Filed ${kind} ${item.
|
|
352
|
-
if (paths.length > 0) await attachFiles(item.project, item.
|
|
371
|
+
console.log(`Filed ${kind} ${item.key} on ${item.project} as ${item.status}.`);
|
|
372
|
+
if (paths.length > 0) await attachFiles(item.project, item.key, paths, args.caption);
|
|
353
373
|
console.log(`Follow it with \`canopy feedback mine\` or on the dashboard's Reports page.`);
|
|
354
374
|
})
|
|
355
375
|
});
|
|
@@ -370,7 +390,7 @@ const feedbackCommand = defineCommand({
|
|
|
370
390
|
args: {
|
|
371
391
|
id: {
|
|
372
392
|
type: "positional",
|
|
373
|
-
description: "
|
|
393
|
+
description: "Report key (my-project-17), or its number inside the project",
|
|
374
394
|
required: true
|
|
375
395
|
},
|
|
376
396
|
file: {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "./package-
|
|
2
|
+
import { t as version } from "./package-CsmBH_fN.mjs";
|
|
3
3
|
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-DM2J10CB.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
@@ -60,30 +60,31 @@ const main = defineCommand({
|
|
|
60
60
|
description: "canopy — create and deploy vibecoded projects"
|
|
61
61
|
},
|
|
62
62
|
subCommands: {
|
|
63
|
-
login: () => import("./login-
|
|
64
|
-
whoami: () => import("./login-
|
|
65
|
-
logout: () => import("./login-
|
|
66
|
-
create: () => import("./projects-
|
|
67
|
-
init: () => import("./projects-
|
|
68
|
-
projects: () => import("./projects-
|
|
69
|
-
delete: () => import("./projects-
|
|
70
|
-
dev: () => import("./dev-
|
|
71
|
-
deploy: () => import("./deploy-
|
|
72
|
-
promote: () => import("./deploy-
|
|
73
|
-
staging: () => import("./staging-
|
|
74
|
-
rollback: () => import("./deploy-
|
|
75
|
-
download: () => import("./deploy-
|
|
76
|
-
token: () => import("./token-
|
|
77
|
-
egress: () => import("./egress-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
63
|
+
login: () => import("./login-C4MDP5NM.mjs").then((m) => m.loginCommand),
|
|
64
|
+
whoami: () => import("./login-C4MDP5NM.mjs").then((m) => m.whoamiCommand),
|
|
65
|
+
logout: () => import("./login-C4MDP5NM.mjs").then((m) => m.logoutCommand),
|
|
66
|
+
create: () => import("./projects-Dmm_976k.mjs").then((m) => m.createCommand),
|
|
67
|
+
init: () => import("./projects-Dmm_976k.mjs").then((m) => m.initCommand),
|
|
68
|
+
projects: () => import("./projects-Dmm_976k.mjs").then((m) => m.projectsCommand),
|
|
69
|
+
delete: () => import("./projects-Dmm_976k.mjs").then((m) => m.deleteCommand),
|
|
70
|
+
dev: () => import("./dev-BOvMCaML.mjs").then((m) => m.devCommand),
|
|
71
|
+
deploy: () => import("./deploy-CTMcwHhI.mjs").then((m) => m.deployCommand),
|
|
72
|
+
promote: () => import("./deploy-CTMcwHhI.mjs").then((m) => m.promoteCommand),
|
|
73
|
+
staging: () => import("./staging-CKEoDnac.mjs").then((m) => m.stagingCommand),
|
|
74
|
+
rollback: () => import("./deploy-CTMcwHhI.mjs").then((m) => m.rollbackCommand),
|
|
75
|
+
download: () => import("./deploy-CTMcwHhI.mjs").then((m) => m.downloadCommand),
|
|
76
|
+
token: () => import("./token-Dp2CoIYj.mjs").then((m) => m.tokenCommand),
|
|
77
|
+
egress: () => import("./egress-Cn-P46O3.mjs").then((m) => m.egressCommand),
|
|
78
|
+
secrets: () => import("./secrets-BD3DaTih.mjs").then((m) => m.secretsCommand),
|
|
79
|
+
schedule: () => import("./schedule-Buu71RWQ.mjs").then((m) => m.scheduleCommand),
|
|
80
|
+
feedback: () => import("./feedback-DormOsQa.mjs").then((m) => m.feedbackCommand),
|
|
81
|
+
db: () => import("./db-DVMwCLlU.mjs").then((m) => m.dbCommand),
|
|
82
|
+
usage: () => import("./usage-81JSiWIx.mjs").then((m) => m.usageCommand),
|
|
83
|
+
open: () => import("./open-rS1ARzOy.mjs").then((m) => m.openCommand),
|
|
84
|
+
docs: () => import("./docs-CuS9nIXm.mjs").then((m) => m.docsCommand),
|
|
85
|
+
skill: () => import("./skill-DX18WOap.mjs").then((m) => m.skillCommand),
|
|
86
|
+
upgrade: () => import("./upgrade-PfdH0NPe.mjs").then((m) => m.upgradeCommand),
|
|
87
|
+
logs: () => import("./logs-CfDINw7U.mjs").then((m) => m.logsCommand)
|
|
87
88
|
}
|
|
88
89
|
});
|
|
89
90
|
if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { s as apiDataRequest } from "./ui-
|
|
1
|
+
import { s as apiDataRequest } from "./ui-DR-siu2S.mjs";
|
|
2
2
|
import { a as blobRouter, i as META_COLUMNS, r as dbRouter } from "./src-CwSKZwNK.mjs";
|
|
3
|
-
import { r as rewriteApiPath } from "./dev-
|
|
3
|
+
import { r as rewriteApiPath } from "./dev-BvjbJcse.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join, relative, sep } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as writeConfigFile, f as clearAllProfileTokens, g as upsertProfileAuth, h as setActiveProfile, m as readConfigFile, n as siteConfig, p as listBakedProfiles, v as getBakedProfile, y as isProfileId } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { hostname } from "node:os";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/logs.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { s as apiDataRequest } from "./ui-
|
|
2
|
+
import { s as apiDataRequest } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
//#region src/migrations.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, l as stagingUrl, s as projectUrl } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as siteConfig, s as projectUrl, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { a as unwrap, i as requireTty, l as apiRequest, r as reportErrors } from "./ui-
|
|
2
|
+
import { a as unwrap, i as requireTty, l as apiRequest, r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/schedule.ts
|
|
5
5
|
/**
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject } from "./site-DM2J10CB.mjs";
|
|
2
|
+
import { a as unwrap, d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
//#region src/secrets.ts
|
|
6
|
+
/**
|
|
7
|
+
* canopy secrets — manage the project's worker secrets from the terminal so a
|
|
8
|
+
* first deploy can be scripted end to end (create → egress → secrets → deploy).
|
|
9
|
+
* Values travel one way: set via stdin or an environment variable (never a
|
|
10
|
+
* flag, so nothing lands in shell history), listed by name only, and never
|
|
11
|
+
* echoed or downloaded — the same rule the dashboard follows. Secrets are
|
|
12
|
+
* shared by the live and staging channels (docs/14 §4.6).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A piped value usually ends in the newline `echo` or a heredoc appends; a
|
|
16
|
+
* secret never legitimately does. Strip exactly one trailing line break and
|
|
17
|
+
* leave everything else verbatim (leading/inner whitespace may be meaningful).
|
|
18
|
+
*/
|
|
19
|
+
function normalizeSecretValue(raw) {
|
|
20
|
+
return raw.replace(/\r?\n$/, "");
|
|
21
|
+
}
|
|
22
|
+
async function readStdin() {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
25
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
26
|
+
}
|
|
27
|
+
/** Resolve the value: --from-env, then piped stdin, then a hidden prompt on a TTY. */
|
|
28
|
+
async function resolveValue(name, fromEnv) {
|
|
29
|
+
if (fromEnv !== void 0) {
|
|
30
|
+
const value = process.env[fromEnv];
|
|
31
|
+
if (value === void 0) throw new Error(`environment variable ${fromEnv} is not set (nothing to store for ${name}).`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
if (!process.stdin.isTTY) return normalizeSecretValue(await readStdin());
|
|
35
|
+
return unwrap(await p.password({
|
|
36
|
+
message: `Value for ${name} (input is hidden)`,
|
|
37
|
+
validate: (v) => v ? void 0 : "A secret value cannot be empty."
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
const secretsCommand = defineCommand({
|
|
41
|
+
meta: {
|
|
42
|
+
name: "secrets",
|
|
43
|
+
description: "Manage the project's worker secrets (set / list / remove — values never shown)"
|
|
44
|
+
},
|
|
45
|
+
subCommands: {
|
|
46
|
+
list: defineCommand({
|
|
47
|
+
meta: {
|
|
48
|
+
name: "list",
|
|
49
|
+
description: "List the project's secret names (values are never shown)"
|
|
50
|
+
},
|
|
51
|
+
args: { ...projectDirArgs },
|
|
52
|
+
run: reportErrors("secrets list", async ({ args }) => {
|
|
53
|
+
const { slug } = resolveProject(args);
|
|
54
|
+
const { secrets } = await apiRequest("GET", `/projects/${slug}/secrets`);
|
|
55
|
+
if (secrets.length === 0) {
|
|
56
|
+
console.log(`No secrets set for ${slug} — add one with \`canopy secrets set NAME\`.`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const width = Math.max(...secrets.map((s) => s.key.length));
|
|
60
|
+
for (const s of secrets) console.log(`${s.key.padEnd(width)} updated ${formatDateTime(s.updated_at)}`);
|
|
61
|
+
})
|
|
62
|
+
}),
|
|
63
|
+
set: defineCommand({
|
|
64
|
+
meta: {
|
|
65
|
+
name: "set",
|
|
66
|
+
description: "Set a secret — value from stdin (`printf %s \"$V\" | canopy secrets set NAME`), --from-env, or a hidden prompt"
|
|
67
|
+
},
|
|
68
|
+
args: {
|
|
69
|
+
name: {
|
|
70
|
+
type: "positional",
|
|
71
|
+
description: "Secret name (UPPER_SNAKE_CASE, e.g. STRIPE_SECRET)",
|
|
72
|
+
required: true
|
|
73
|
+
},
|
|
74
|
+
"from-env": {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Read the value from this environment variable instead of stdin"
|
|
77
|
+
},
|
|
78
|
+
...projectDirArgs
|
|
79
|
+
},
|
|
80
|
+
run: reportErrors("secrets set", async ({ args }) => {
|
|
81
|
+
const { slug } = resolveProject(args);
|
|
82
|
+
const name = args.name.trim();
|
|
83
|
+
const value = await resolveValue(name, args["from-env"]);
|
|
84
|
+
if (value.length === 0) {
|
|
85
|
+
console.error(`No value provided for ${name} — pipe it on stdin or pass --from-env VAR.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
await withContext(apiRequest("PUT", `/projects/${slug}/secrets/${name}`, { value }), {
|
|
89
|
+
action: `set secret ${name} on ${slug}`,
|
|
90
|
+
fixes: { not_supported_on_target: "This site has no worker runtime, so secrets have nowhere to go — set them on a site that runs workers (e.g. `--profile main`)." }
|
|
91
|
+
});
|
|
92
|
+
console.log(`Set ${name} for ${slug} — running workers pick it up now.`);
|
|
93
|
+
})
|
|
94
|
+
}),
|
|
95
|
+
remove: defineCommand({
|
|
96
|
+
meta: {
|
|
97
|
+
name: "remove",
|
|
98
|
+
description: "Remove a secret"
|
|
99
|
+
},
|
|
100
|
+
args: {
|
|
101
|
+
name: {
|
|
102
|
+
type: "positional",
|
|
103
|
+
description: "Secret name (as shown by `list`)",
|
|
104
|
+
required: true
|
|
105
|
+
},
|
|
106
|
+
...projectDirArgs
|
|
107
|
+
},
|
|
108
|
+
run: reportErrors("secrets remove", async ({ args }) => {
|
|
109
|
+
const { slug } = resolveProject(args);
|
|
110
|
+
const name = args.name.trim();
|
|
111
|
+
await withContext(apiRequest("DELETE", `/projects/${slug}/secrets/${name}`), {
|
|
112
|
+
action: `remove secret ${name} from ${slug}`,
|
|
113
|
+
fixes: { not_found: `${name} is not set on ${slug} (see \`canopy secrets list\`), or your account can't access the project.` }
|
|
114
|
+
});
|
|
115
|
+
console.log(`Removed ${name} from ${slug} — running workers no longer see it.`);
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
//#endregion
|
|
121
|
+
export { secretsCommand };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-CsmBH_fN.mjs";
|
|
2
|
+
import { r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region src/staging.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Local worker secrets for `canopy dev` — copy to .dev.vars (gitignored).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
3
|
+
# Project secrets (`canopy secrets set NAME` or Settings → Secrets) are never downloaded: the platform
|
|
4
4
|
# can't hand a stored value back, and live keys don't belong on a laptop. Set
|
|
5
5
|
# local stand-ins here; the worker reads them as c.env.NAME, exactly as it
|
|
6
6
|
# reads the dashboard's in production. `canopy dev` prints which dashboard
|
|
@@ -56,7 +56,7 @@ the [Canopy dashboard](https://dashboard.thanarie.dev).
|
|
|
56
56
|
- Do not add auth — the platform authenticates every request before it
|
|
57
57
|
reaches this app or the Worker.
|
|
58
58
|
- Do not store secrets in the repo or SPA. Set them in the Canopy dashboard
|
|
59
|
-
(Settings → Secrets); the Worker reads them as `c.env.SECRET_NAME`
|
|
59
|
+
(`canopy secrets set NAME`, or Settings → Secrets); the Worker reads them as `c.env.SECRET_NAME`
|
|
60
60
|
(Workers-shaped bindings — not `process.env`). Locally, put stand-in values
|
|
61
61
|
in `.dev.vars` (gitignored, see `.dev.vars.example`) — dashboard values are
|
|
62
62
|
never downloaded, and `canopy dev` prints which names are still missing. Platform access still goes
|
|
@@ -25,7 +25,7 @@ interface Env {
|
|
|
25
25
|
// DB — this project's database: `dialect` plus
|
|
26
26
|
// `query(text, params) => { rows, rowCount }`
|
|
27
27
|
// BLOB — this project's files: `get` / `put` / `list` / `delete`
|
|
28
|
-
//
|
|
28
|
+
// Project secrets (`canopy secrets set` / Settings → Secrets) land as Workers-shaped bindings:
|
|
29
29
|
// MY_API_KEY?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-
|
|
2
|
+
import { l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
const tokenCommand = defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -137,6 +137,26 @@ const ERROR_COPY = {
|
|
|
137
137
|
worker_egress_sync_failed: {
|
|
138
138
|
why: "the list was saved but redeploying the running worker with it failed",
|
|
139
139
|
fix: "Retry, or redeploy the project (`canopy deploy`) to apply the new allowlist."
|
|
140
|
+
},
|
|
141
|
+
invalid_key: {
|
|
142
|
+
why: "secret names must be UPPER_SNAKE_CASE (a letter, then letters/digits/underscores, at most 64 chars)",
|
|
143
|
+
fix: "Rename it, e.g. STRIPE_SECRET."
|
|
144
|
+
},
|
|
145
|
+
reserved_key: {
|
|
146
|
+
why: "that name is reserved for the platform runtime",
|
|
147
|
+
fix: "Pick a name that isn't a Canopy or runtime binding."
|
|
148
|
+
},
|
|
149
|
+
value_too_large: {
|
|
150
|
+
why: "a secret value is limited to 8 KiB",
|
|
151
|
+
fix: "Store a reference (an id or a shorter key) and fetch the large payload from your worker instead."
|
|
152
|
+
},
|
|
153
|
+
too_many_secrets: {
|
|
154
|
+
why: "the project already holds the maximum number of secrets (64)",
|
|
155
|
+
fix: "Remove one you no longer need (`canopy secrets remove NAME`) first."
|
|
156
|
+
},
|
|
157
|
+
worker_secret_sync_failed: {
|
|
158
|
+
why: "the secret was saved but redeploying the running worker with it failed",
|
|
159
|
+
fix: "Retry, or redeploy the project (`canopy deploy`) to apply it."
|
|
140
160
|
}
|
|
141
161
|
};
|
|
142
162
|
/** Generic explanation for a status code when we have no per-code copy. */
|
|
@@ -154,21 +174,40 @@ function statusFix(status) {
|
|
|
154
174
|
if (status >= 500) return "This is usually temporary — wait a moment and try again.";
|
|
155
175
|
return "Re-check the command arguments and try again.";
|
|
156
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* One trace per CLI invocation (docs/42-observability.md §3.7): every request
|
|
179
|
+
* this process makes carries the same W3C traceparent, so the platform's logs
|
|
180
|
+
* for a `canopy deploy` line up under one id. The api echoes it back as
|
|
181
|
+
* `x-canopy-request-id`, which failures print for the user to quote.
|
|
182
|
+
*/
|
|
183
|
+
const TRACE_ID = randomHex(16);
|
|
184
|
+
function randomHex(bytes) {
|
|
185
|
+
const buf = new Uint8Array(bytes);
|
|
186
|
+
crypto.getRandomValues(buf);
|
|
187
|
+
let out = "";
|
|
188
|
+
for (const b of buf) out += b.toString(16).padStart(2, "0");
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
function traceHeaders() {
|
|
192
|
+
return { traceparent: `00-${TRACE_ID}-${randomHex(8)}-01` };
|
|
193
|
+
}
|
|
157
194
|
var ApiError = class extends Error {
|
|
158
195
|
status;
|
|
159
196
|
code;
|
|
160
197
|
method;
|
|
161
198
|
path;
|
|
162
199
|
detail;
|
|
200
|
+
requestId;
|
|
163
201
|
/** Command-specific copy, layered on by `withContext` (or `fail` for 401s). */
|
|
164
202
|
context;
|
|
165
|
-
constructor(status, code, method, path, detail) {
|
|
203
|
+
constructor(status, code, method, path, detail, requestId) {
|
|
166
204
|
super(detail ? `${code}: ${detail}` : code);
|
|
167
205
|
this.status = status;
|
|
168
206
|
this.code = code;
|
|
169
207
|
this.method = method;
|
|
170
208
|
this.path = path;
|
|
171
209
|
this.detail = detail;
|
|
210
|
+
this.requestId = requestId;
|
|
172
211
|
this.name = "ApiError";
|
|
173
212
|
}
|
|
174
213
|
/**
|
|
@@ -179,10 +218,12 @@ var ApiError = class extends Error {
|
|
|
179
218
|
const copy = ERROR_COPY[this.code];
|
|
180
219
|
const headline = this.context?.action ? `could not ${this.context.action}` : `${this.method} ${this.path} failed`;
|
|
181
220
|
const detail = this.detail ? `: ${this.detail}` : "";
|
|
221
|
+
const why = `${this.status} ${this.code}${detail} — ${copy?.why ?? statusWhy(this.status)}`;
|
|
222
|
+
const fix = this.context?.fixes?.[this.code] ?? copy?.fix ?? statusFix(this.status);
|
|
182
223
|
return {
|
|
183
224
|
headline,
|
|
184
|
-
why: `${
|
|
185
|
-
fix
|
|
225
|
+
why: this.requestId ? `${why} (request id ${this.requestId})` : why,
|
|
226
|
+
fix
|
|
186
227
|
};
|
|
187
228
|
}
|
|
188
229
|
};
|
|
@@ -221,7 +262,7 @@ function credentials() {
|
|
|
221
262
|
async function fail(response, isDeployToken, method, path) {
|
|
222
263
|
const payload = await response.json().catch(() => ({}));
|
|
223
264
|
const code = payload.error ?? (response.status === 401 ? "unauthenticated" : `http_${response.status}`);
|
|
224
|
-
const err = new ApiError(response.status, code, method, path, payload.detail);
|
|
265
|
+
const err = new ApiError(response.status, code, method, path, payload.detail, response.headers.get("x-canopy-request-id") ?? void 0);
|
|
225
266
|
if (response.status === 401) err.context = {
|
|
226
267
|
action: "authenticate with Canopy",
|
|
227
268
|
fixes: { [code]: isDeployToken ? "The deploy token was rejected — it may have been revoked. Create a new one in the dashboard (Settings → Deploy tokens) or with `canopy token create`." : "Your session has expired — run `canopy login` again." }
|
|
@@ -234,6 +275,7 @@ async function apiRequest(method, path, body, rawContentType, headers) {
|
|
|
234
275
|
const response = await fetch(`${apiUrl}/control/v1${path}`, {
|
|
235
276
|
method,
|
|
236
277
|
headers: {
|
|
278
|
+
...traceHeaders(),
|
|
237
279
|
authorization: `Bearer ${token}`,
|
|
238
280
|
...body === void 0 || isForm ? {} : { "content-type": rawContentType ?? "application/json" },
|
|
239
281
|
...headers
|
|
@@ -253,6 +295,7 @@ async function apiUpload(path, bytes, contentType) {
|
|
|
253
295
|
const response = await fetch(`${apiUrl}/control/v1${path}`, {
|
|
254
296
|
method: "POST",
|
|
255
297
|
headers: {
|
|
298
|
+
...traceHeaders(),
|
|
256
299
|
authorization: `Bearer ${token}`,
|
|
257
300
|
"content-type": contentType
|
|
258
301
|
},
|
|
@@ -264,7 +307,10 @@ async function apiUpload(path, bytes, contentType) {
|
|
|
264
307
|
/** Authenticated GET returning raw bytes (e.g. a deploy zip). */
|
|
265
308
|
async function apiDownload(path) {
|
|
266
309
|
const { apiUrl, token, isDeployToken } = credentials();
|
|
267
|
-
const response = await fetch(`${apiUrl}/control/v1${path}`, { headers: {
|
|
310
|
+
const response = await fetch(`${apiUrl}/control/v1${path}`, { headers: {
|
|
311
|
+
...traceHeaders(),
|
|
312
|
+
authorization: `Bearer ${token}`
|
|
313
|
+
} });
|
|
268
314
|
if (!response.ok) return fail(response, isDeployToken, "GET", path);
|
|
269
315
|
return Buffer.from(await response.arrayBuffer());
|
|
270
316
|
}
|
|
@@ -278,6 +324,7 @@ async function apiDataRequest(method, path, projectSlug, body, rawContentType, c
|
|
|
278
324
|
const response = await fetch(`${apiUrl}/data/v1${path}`, {
|
|
279
325
|
method,
|
|
280
326
|
headers: {
|
|
327
|
+
...traceHeaders(),
|
|
281
328
|
authorization: `Bearer ${token}`,
|
|
282
329
|
"x-canopy-project": projectSlug,
|
|
283
330
|
...channel === "staging" ? { "x-canopy-channel": "staging" } : {},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
2
|
+
import { r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -112,7 +112,7 @@ const upgradeCommand = defineCommand({
|
|
|
112
112
|
const spec = `${PACKAGE_NAME}@${args.version ?? "latest"}`;
|
|
113
113
|
const command = pmCommand(pm);
|
|
114
114
|
const cmd = installArgs(pm, spec);
|
|
115
|
-
const { skillInstalled } = await import("./skill-
|
|
115
|
+
const { skillInstalled } = await import("./skill-DX18WOap.mjs");
|
|
116
116
|
const { homedir } = await import("node:os");
|
|
117
117
|
const refreshGlobal = skillInstalled(homedir());
|
|
118
118
|
const refreshRepo = process.cwd() !== homedir() && skillInstalled(process.cwd());
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-DM2J10CB.mjs";
|
|
2
|
-
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-
|
|
2
|
+
import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-DR-siu2S.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/usage.ts
|
|
5
5
|
/**
|
package/package.json
CHANGED
package/dist/dev-BAcZi226.mjs
DELETED