@mediamonks/canopy-cli 0.7.2 → 0.7.4
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-B8c6nhQ8.mjs → db-D3RKaLVU.mjs} +2 -2
- package/dist/{deploy-DuW0-201.mjs → deploy-ClioT9ZC.mjs} +2 -2
- package/dist/{dev-DJwGvkDZ.mjs → dev-BbAVZcAR.mjs} +4 -4
- package/dist/dev-D6Sy15Wv.mjs +2 -0
- package/dist/docs/cli.md +6 -0
- package/dist/docs/porting-nextjs.md +4 -7
- package/dist/docs/troubleshooting.md +27 -8
- package/dist/{docs-tof6Ou2-.mjs → docs-IxKmYx_G.mjs} +2 -2
- package/dist/egress-BkkG9Jzs.mjs +88 -0
- package/dist/index.mjs +24 -36
- package/dist/{local-DUp7p_2m.mjs → local-BHxSZwUT.mjs} +2 -2
- package/dist/{login-DZWQF4fS.mjs → login-C1x1l3qg.mjs} +1 -1
- package/dist/logs-DBLDS3aQ.mjs +109 -0
- package/dist/{migrations-D1Va4TyD.mjs → migrations-DpWHuqkQ.mjs} +1 -1
- package/dist/{open-1ARSL5XC.mjs → open-CLSh6oez.mjs} +1 -1
- package/dist/{package-BGFOC-Tg.mjs → package-DBEmT6Uh.mjs} +1 -1
- package/dist/{projects-CaGxv-4p.mjs → projects-D2fDeWNU.mjs} +1 -1
- package/dist/{skill-DHmCX1U-.mjs → skill-D3MHtg4s.mjs} +2 -2
- package/dist/{staging-DQQZXoKz.mjs → staging-95xeXyZN.mjs} +1 -1
- package/dist/{token-CBuhzauo.mjs → token-BjlqT2Xj.mjs} +1 -1
- package/dist/{ui-OMOjXJ2y.mjs → ui-CpdtK7mt.mjs} +12 -0
- package/dist/{upgrade-B8LKMI4s.mjs → upgrade-PcE6l9uj.mjs} +2 -2
- package/dist/{usage-DATkqUN0.mjs → usage-wpew0a2M.mjs} +1 -1
- package/package.json +1 -1
- package/dist/dev-U9u6katn.mjs +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { i as unwrap, l as withContext, n as reportErrors, o as apiDataRequest, r as requireTty } from "./ui-
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { i as unwrap, l as withContext, n as reportErrors, o as apiDataRequest, r as requireTty } from "./ui-CpdtK7mt.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-DpWHuqkQ.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-BUlQR117.mjs";
|
|
2
|
-
import { c as apiRequest, i as unwrap, l as withContext, n as reportErrors, o as apiDataRequest, s as apiDownload } from "./ui-
|
|
3
|
-
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-
|
|
2
|
+
import { c as apiRequest, i as unwrap, l as withContext, n as reportErrors, o as apiDataRequest, s as apiDownload } from "./ui-CpdtK7mt.mjs";
|
|
3
|
+
import { n as migrateProject, r as readMigrationFiles, t as describeMigrateResult } from "./migrations-DpWHuqkQ.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-BUlQR117.mjs";
|
|
2
|
-
import { a as ApiError, c as apiRequest, l as withContext, n as reportErrors } from "./ui-
|
|
3
|
-
import { r as readMigrationFiles } from "./migrations-
|
|
2
|
+
import { a as ApiError, c as apiRequest, l as withContext, n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
|
+
import { r as readMigrationFiles } from "./migrations-DpWHuqkQ.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -112,7 +112,7 @@ async function devRemote(flags, slug) {
|
|
|
112
112
|
async function devLocal(flags, slug) {
|
|
113
113
|
let dump;
|
|
114
114
|
if (flags.pull) {
|
|
115
|
-
const { fetchProjectDump } = await import("./local-
|
|
115
|
+
const { fetchProjectDump } = await import("./local-BHxSZwUT.mjs");
|
|
116
116
|
console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
|
|
117
117
|
dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
|
|
118
118
|
}
|
|
@@ -127,7 +127,7 @@ async function devLocal(flags, slug) {
|
|
|
127
127
|
const reason = err instanceof ApiError ? err.explain().why : String(err);
|
|
128
128
|
console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
|
|
129
129
|
}
|
|
130
|
-
const { createSandbox } = await import("./local-
|
|
130
|
+
const { createSandbox } = await import("./local-BHxSZwUT.mjs");
|
|
131
131
|
const sandbox = await createSandbox({
|
|
132
132
|
slug,
|
|
133
133
|
persistDir: join(flags.dir, ".canopy", "local"),
|
package/dist/docs/cli.md
CHANGED
|
@@ -70,6 +70,12 @@ has a full page per command.
|
|
|
70
70
|
|
|
71
71
|
See `canopy docs ci` for wiring push-to-main deploys.
|
|
72
72
|
|
|
73
|
+
## Outbound hosts (egress)
|
|
74
|
+
|
|
75
|
+
- `canopy egress list` — hosts the project's worker may `fetch` directly.
|
|
76
|
+
- `canopy egress allow <host>` — allow a hostname (or `"*.sub.wildcard"`).
|
|
77
|
+
- `canopy egress remove <host>` — remove an entry.
|
|
78
|
+
|
|
73
79
|
## Maintenance
|
|
74
80
|
|
|
75
81
|
- `canopy upgrade` — update the CLI to the latest version. `--version <v>` pins a
|
|
@@ -166,13 +166,10 @@ Rules of the road for the worker:
|
|
|
166
166
|
stick to packages that don't need Node built-ins or native modules.
|
|
167
167
|
- **No filesystem, no long-lived state.** Persist through `canopy.db` and
|
|
168
168
|
`canopy.blob`.
|
|
169
|
-
- **Outbound requests are controlled.**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
needs (per-project allowlist management is planned; see `egress_denied` in
|
|
174
|
-
the [troubleshooting guide](https://docs.canopy.website/guides/troubleshooting)). On the Cloudflare
|
|
175
|
-
target outbound requests are currently unrestricted.
|
|
169
|
+
- **Outbound requests are controlled.** Calls to external APIs pass through a
|
|
170
|
+
per-project egress allowlist — deny-by-default, managed by you. Allow the
|
|
171
|
+
hosts your worker calls with `canopy egress allow <host>` or in dashboard
|
|
172
|
+
Settings → Outbound hosts (see [Outbound requests](https://docs.canopy.website/platform/egress)).
|
|
176
173
|
- Frontend calls are plain `fetch("/api/…")` — same origin, the platform
|
|
177
174
|
session cookie rides along automatically.
|
|
178
175
|
|
|
@@ -62,14 +62,33 @@ the dashboard.
|
|
|
62
62
|
## `egress_denied` (403)
|
|
63
63
|
|
|
64
64
|
An outbound `fetch` from your worker was blocked by the platform's egress
|
|
65
|
-
guard: `host <hostname> is not on the project allowlist`.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
guard: `host <hostname> is not on the project allowlist`. Outbound requests
|
|
66
|
+
are deny-by-default — only platform infrastructure and the hosts on your
|
|
67
|
+
project's own allowlist get through. Add the host and retry (running workers
|
|
68
|
+
pick the change up without a deploy):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
canopy egress allow api.example.com
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
or use dashboard Settings → Outbound hosts. Details and entry rules:
|
|
75
|
+
[Outbound requests](https://docs.canopy.website/platform/egress). Calls made through the SDK /
|
|
76
|
+
`env.CANOPY.fetch` don't count as egress and are unaffected. On the Cloudflare
|
|
77
|
+
target outbound requests are currently unrestricted, so this error only
|
|
78
|
+
appears on GCP.
|
|
79
|
+
|
|
80
|
+
## The worker returns errors and you can't see why
|
|
81
|
+
|
|
82
|
+
Read its console output instead of adding diagnostic endpoints:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
canopy logs --since 15m
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`console.log` / `console.error` from your deployed worker, straight in the
|
|
89
|
+
terminal; `--follow` tails it live while you reproduce the problem. See
|
|
90
|
+
[canopy logs](https://docs.canopy.website/cli/logs). (On sites without worker log capture the command
|
|
91
|
+
answers `not_supported_on_target`.)
|
|
73
92
|
|
|
74
93
|
## Docs are stale
|
|
75
94
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-DBEmT6Uh.mjs";
|
|
2
2
|
import { n as siteConfig } from "./site-BUlQR117.mjs";
|
|
3
3
|
import { i as latestKnownVersion, r as compareSemver } from "./version-check-CcsA1QjI.mjs";
|
|
4
|
-
import { n as reportErrors } from "./ui-
|
|
4
|
+
import { n as reportErrors } from "./ui-CpdtK7mt.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";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject } from "./site-BUlQR117.mjs";
|
|
2
|
+
import { c as apiRequest, n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
//#region src/egress.ts
|
|
5
|
+
/**
|
|
6
|
+
* canopy egress — manage the project's outbound-host allowlist
|
|
7
|
+
* (docs/16-egress-allowlist.md). Worker fetches to hosts outside the list are
|
|
8
|
+
* denied (`egress_denied`) on targets that enforce egress control; the list
|
|
9
|
+
* lives on the platform, not in canopy.json, so a repo file can never widen
|
|
10
|
+
* egress silently on deploy.
|
|
11
|
+
*/
|
|
12
|
+
function printList({ hosts, denied, enforced }, slug) {
|
|
13
|
+
if (hosts.length === 0) console.log(`No extra egress hosts for ${slug} — the worker can reach platform hosts only.`);
|
|
14
|
+
else for (const host of hosts) console.log(host);
|
|
15
|
+
if (denied.length > 0) {
|
|
16
|
+
console.log(`\nRecently blocked (allow with \`canopy egress allow <host>\`):`);
|
|
17
|
+
for (const d of denied) console.log(` ${d.host} ×${d.count} last ${d.last_seen}`);
|
|
18
|
+
}
|
|
19
|
+
if (!enforced) console.error("(note: this site's target does not enforce egress yet — the list is recorded for when it does)");
|
|
20
|
+
}
|
|
21
|
+
const egressCommand = defineCommand({
|
|
22
|
+
meta: {
|
|
23
|
+
name: "egress",
|
|
24
|
+
description: "Manage which outbound hosts the project's worker may fetch"
|
|
25
|
+
},
|
|
26
|
+
subCommands: {
|
|
27
|
+
list: defineCommand({
|
|
28
|
+
meta: {
|
|
29
|
+
name: "list",
|
|
30
|
+
description: "List the project's allowed outbound hosts"
|
|
31
|
+
},
|
|
32
|
+
args: { ...projectDirArgs },
|
|
33
|
+
run: reportErrors("egress list", async ({ args }) => {
|
|
34
|
+
const { slug } = resolveProject(args);
|
|
35
|
+
printList(await apiRequest("GET", `/projects/${slug}/egress`), slug);
|
|
36
|
+
})
|
|
37
|
+
}),
|
|
38
|
+
allow: defineCommand({
|
|
39
|
+
meta: {
|
|
40
|
+
name: "allow",
|
|
41
|
+
description: "Allow the worker to fetch a host (e.g. api.example.com or *.googleapis.com)"
|
|
42
|
+
},
|
|
43
|
+
args: {
|
|
44
|
+
host: {
|
|
45
|
+
type: "positional",
|
|
46
|
+
description: "Hostname to allow",
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
...projectDirArgs
|
|
50
|
+
},
|
|
51
|
+
run: reportErrors("egress allow", async ({ args }) => {
|
|
52
|
+
const { slug } = resolveProject(args);
|
|
53
|
+
const current = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
54
|
+
const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: [...current.hosts, args.host] });
|
|
55
|
+
console.log(`Allowed ${args.host} for ${slug} — running workers pick it up now.\n`);
|
|
56
|
+
printList(updated, slug);
|
|
57
|
+
})
|
|
58
|
+
}),
|
|
59
|
+
remove: defineCommand({
|
|
60
|
+
meta: {
|
|
61
|
+
name: "remove",
|
|
62
|
+
description: "Remove a host from the allowlist"
|
|
63
|
+
},
|
|
64
|
+
args: {
|
|
65
|
+
host: {
|
|
66
|
+
type: "positional",
|
|
67
|
+
description: "Hostname to remove",
|
|
68
|
+
required: true
|
|
69
|
+
},
|
|
70
|
+
...projectDirArgs
|
|
71
|
+
},
|
|
72
|
+
run: reportErrors("egress remove", async ({ args }) => {
|
|
73
|
+
const { slug } = resolveProject(args);
|
|
74
|
+
const current = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
75
|
+
const wanted = args.host.trim().toLowerCase();
|
|
76
|
+
if (!current.hosts.includes(wanted)) {
|
|
77
|
+
console.error(`${wanted} is not on the allowlist for ${slug} (see \`canopy egress list\`).`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: current.hosts.filter((h) => h !== wanted) });
|
|
81
|
+
console.log(`Removed ${wanted} from ${slug} — running workers stop reaching it now.\n`);
|
|
82
|
+
printList(updated, slug);
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
//#endregion
|
|
88
|
+
export { egressCommand };
|
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-DBEmT6Uh.mjs";
|
|
3
3
|
import { r as siteHeaderLine, t as setInvocationProfile } from "./site-BUlQR117.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
@@ -15,7 +15,7 @@ import { defineCommand, runMain } from "citty";
|
|
|
15
15
|
* deploy build & deploy assets (+ worker) [phase 1/2]
|
|
16
16
|
* token create|list|revoke deploy tokens for CI [phase 2]
|
|
17
17
|
* db pull|snapshot|restore [phase 2]
|
|
18
|
-
* logs
|
|
18
|
+
* logs [--follow] read the project's worker logs [phase 2]
|
|
19
19
|
* usage AI spend + storage for a project [phase 1]
|
|
20
20
|
* open open the deployed project / dashboard [phase 1]
|
|
21
21
|
*/
|
|
@@ -53,19 +53,6 @@ function takeGlobalProfile(argv) {
|
|
|
53
53
|
const profileFlag = takeGlobalProfile(process.argv);
|
|
54
54
|
if (profileFlag) setInvocationProfile(profileFlag);
|
|
55
55
|
else if (process.env["CANOPY_PROFILE"]) setInvocationProfile(process.env["CANOPY_PROFILE"]);
|
|
56
|
-
/** A roadmap command that has not landed yet (docs/04-roadmap.md). */
|
|
57
|
-
function planned(name, description, phase) {
|
|
58
|
-
return defineCommand({
|
|
59
|
-
meta: {
|
|
60
|
-
name,
|
|
61
|
-
description: `${description} (not implemented yet — Phase ${phase})`
|
|
62
|
-
},
|
|
63
|
-
run() {
|
|
64
|
-
console.error(`canopy ${name}: not implemented yet — lands in Phase ${phase} (docs/04-roadmap.md).`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
56
|
const main = defineCommand({
|
|
70
57
|
meta: {
|
|
71
58
|
name: "canopy",
|
|
@@ -73,27 +60,28 @@ const main = defineCommand({
|
|
|
73
60
|
description: "canopy — create and deploy vibecoded projects"
|
|
74
61
|
},
|
|
75
62
|
subCommands: {
|
|
76
|
-
login: () => import("./login-
|
|
77
|
-
whoami: () => import("./login-
|
|
78
|
-
logout: () => import("./login-
|
|
79
|
-
create: () => import("./projects-
|
|
80
|
-
init: () => import("./projects-
|
|
81
|
-
projects: () => import("./projects-
|
|
82
|
-
delete: () => import("./projects-
|
|
83
|
-
dev: () => import("./dev-
|
|
84
|
-
deploy: () => import("./deploy-
|
|
85
|
-
promote: () => import("./deploy-
|
|
86
|
-
staging: () => import("./staging-
|
|
87
|
-
rollback: () => import("./deploy-
|
|
88
|
-
download: () => import("./deploy-
|
|
89
|
-
token: () => import("./token-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
63
|
+
login: () => import("./login-C1x1l3qg.mjs").then((m) => m.loginCommand),
|
|
64
|
+
whoami: () => import("./login-C1x1l3qg.mjs").then((m) => m.whoamiCommand),
|
|
65
|
+
logout: () => import("./login-C1x1l3qg.mjs").then((m) => m.logoutCommand),
|
|
66
|
+
create: () => import("./projects-D2fDeWNU.mjs").then((m) => m.createCommand),
|
|
67
|
+
init: () => import("./projects-D2fDeWNU.mjs").then((m) => m.initCommand),
|
|
68
|
+
projects: () => import("./projects-D2fDeWNU.mjs").then((m) => m.projectsCommand),
|
|
69
|
+
delete: () => import("./projects-D2fDeWNU.mjs").then((m) => m.deleteCommand),
|
|
70
|
+
dev: () => import("./dev-D6Sy15Wv.mjs").then((m) => m.devCommand),
|
|
71
|
+
deploy: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.deployCommand),
|
|
72
|
+
promote: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.promoteCommand),
|
|
73
|
+
staging: () => import("./staging-95xeXyZN.mjs").then((m) => m.stagingCommand),
|
|
74
|
+
rollback: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.rollbackCommand),
|
|
75
|
+
download: () => import("./deploy-ClioT9ZC.mjs").then((m) => m.downloadCommand),
|
|
76
|
+
token: () => import("./token-BjlqT2Xj.mjs").then((m) => m.tokenCommand),
|
|
77
|
+
egress: () => import("./egress-BkkG9Jzs.mjs").then((m) => m.egressCommand),
|
|
78
|
+
db: () => import("./db-D3RKaLVU.mjs").then((m) => m.dbCommand),
|
|
79
|
+
usage: () => import("./usage-wpew0a2M.mjs").then((m) => m.usageCommand),
|
|
80
|
+
open: () => import("./open-CLSh6oez.mjs").then((m) => m.openCommand),
|
|
81
|
+
docs: () => import("./docs-IxKmYx_G.mjs").then((m) => m.docsCommand),
|
|
82
|
+
skill: () => import("./skill-D3MHtg4s.mjs").then((m) => m.skillCommand),
|
|
83
|
+
upgrade: () => import("./upgrade-PcE6l9uj.mjs").then((m) => m.upgradeCommand),
|
|
84
|
+
logs: () => import("./logs-DBLDS3aQ.mjs").then((m) => m.logsCommand)
|
|
97
85
|
}
|
|
98
86
|
});
|
|
99
87
|
if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as apiDataRequest } from "./ui-
|
|
2
|
-
import { n as rewriteApiPath } from "./dev-
|
|
1
|
+
import { o as apiDataRequest } from "./ui-CpdtK7mt.mjs";
|
|
2
|
+
import { n as rewriteApiPath } from "./dev-BbAVZcAR.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, relative, sep } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as getBakedProfile, d as clearAllProfileTokens, f as listBakedProfiles, g as writeConfigFile, h as upsertProfileAuth, m as setActiveProfile, n as siteConfig, p as readConfigFile, v as isProfileId } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { n as reportErrors, t as openBrowser } from "./ui-
|
|
2
|
+
import { n as reportErrors, t as openBrowser } from "./ui-CpdtK7mt.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-BUlQR117.mjs";
|
|
2
|
+
import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
//#region src/logs.ts
|
|
5
|
+
/**
|
|
6
|
+
* canopy logs — read the project worker's stdout/stderr
|
|
7
|
+
* (GET /v1/projects/:slug/logs, docs/17-worker-logs.md). `--follow` is a
|
|
8
|
+
* polling loop on since=<last ts>; output is stdout-clean for piping.
|
|
9
|
+
*/
|
|
10
|
+
const FOLLOW_INTERVAL_MS = 4e3;
|
|
11
|
+
/** `--since` accepts a duration (30s, 15m, 2h, 1d) or an RFC 3339 timestamp. */
|
|
12
|
+
function parseSince(value, now = Date.now()) {
|
|
13
|
+
const duration = /^(\d+)([smhd])$/.exec(value.trim());
|
|
14
|
+
if (duration) {
|
|
15
|
+
const unitMs = {
|
|
16
|
+
s: 1e3,
|
|
17
|
+
m: 6e4,
|
|
18
|
+
h: 36e5,
|
|
19
|
+
d: 864e5
|
|
20
|
+
}[duration[2]];
|
|
21
|
+
return new Date(now - Number(duration[1]) * unitMs).toISOString();
|
|
22
|
+
}
|
|
23
|
+
const ms = Date.parse(value);
|
|
24
|
+
if (Number.isNaN(ms)) throw new Error(`--since must be a duration (30s, 15m, 2h, 1d) or a timestamp, not ${JSON.stringify(value)}.`);
|
|
25
|
+
return new Date(ms).toISOString();
|
|
26
|
+
}
|
|
27
|
+
/** `SEVERITY ts text` — severity padded so lines column-align. */
|
|
28
|
+
function formatEntry(entry) {
|
|
29
|
+
return `${entry.severity.padEnd(8)} ${entry.ts} ${entry.text}`;
|
|
30
|
+
}
|
|
31
|
+
async function fetchLogs(slug, params) {
|
|
32
|
+
const query = new URLSearchParams({ channel: params.channel });
|
|
33
|
+
if (params.since) query.set("since", params.since);
|
|
34
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
35
|
+
return withContext(apiRequest("GET", `/projects/${slug}/logs?${query}`), {
|
|
36
|
+
action: `read worker logs for "${slug}"`,
|
|
37
|
+
fixes: { not_supported_on_target: "Worker logs aren't available on this site yet — GCP sites only." }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const logsCommand = defineCommand({
|
|
41
|
+
meta: {
|
|
42
|
+
name: "logs",
|
|
43
|
+
description: "Show the project worker's logs (stdout/stderr)"
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
...projectFlagArgs,
|
|
47
|
+
staging: {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
default: false,
|
|
50
|
+
description: "Read the staging channel's worker instead of live"
|
|
51
|
+
},
|
|
52
|
+
since: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "How far back to read: a duration (30s, 15m, 2h, 1d) or a timestamp",
|
|
55
|
+
default: "1h"
|
|
56
|
+
},
|
|
57
|
+
limit: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Max entries to print (1–1000)",
|
|
60
|
+
default: "100"
|
|
61
|
+
},
|
|
62
|
+
follow: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
alias: "f",
|
|
65
|
+
default: false,
|
|
66
|
+
description: "Keep polling for new entries (Ctrl-C to stop)"
|
|
67
|
+
},
|
|
68
|
+
json: {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
default: false,
|
|
71
|
+
description: "Print entries as JSON lines instead of formatted text"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
run: reportErrors("logs", async ({ args }) => {
|
|
75
|
+
const { slug } = resolveProject(args);
|
|
76
|
+
const channel = args.staging ? "staging" : "live";
|
|
77
|
+
const limit = Number(args.limit);
|
|
78
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) throw new Error("--limit must be an integer between 1 and 1000.");
|
|
79
|
+
let since = parseSince(args.since);
|
|
80
|
+
const print = (entries) => {
|
|
81
|
+
for (const entry of entries) console.log(args.json ? JSON.stringify(entry) : formatEntry(entry));
|
|
82
|
+
};
|
|
83
|
+
const initial = [...(await fetchLogs(slug, {
|
|
84
|
+
channel,
|
|
85
|
+
since,
|
|
86
|
+
limit
|
|
87
|
+
})).entries].reverse();
|
|
88
|
+
print(initial);
|
|
89
|
+
if (!args.follow) {
|
|
90
|
+
if (initial.length === 0) console.error(`No log entries since ${since} — is the worker getting traffic?`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.error(`Following ${slug}${channel === "staging" ? " [staging]" : ""}… (Ctrl-C to stop)`);
|
|
94
|
+
let lastTs = initial.at(-1)?.ts ?? since;
|
|
95
|
+
for (;;) {
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS));
|
|
97
|
+
const fresh = [...(await fetchLogs(slug, {
|
|
98
|
+
channel,
|
|
99
|
+
since: lastTs,
|
|
100
|
+
limit: 1e3
|
|
101
|
+
})).entries].reverse().filter((e) => e.ts > lastTs);
|
|
102
|
+
if (fresh.length === 0) continue;
|
|
103
|
+
print(fresh);
|
|
104
|
+
lastTs = fresh.at(-1).ts;
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
//#endregion
|
|
109
|
+
export { logsCommand };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { u as tryReadCanopyJson } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { o as apiDataRequest } from "./ui-
|
|
2
|
+
import { o as apiDataRequest } from "./ui-CpdtK7mt.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-BUlQR117.mjs";
|
|
2
|
-
import { n as reportErrors, t as openBrowser } from "./ui-
|
|
2
|
+
import { n as reportErrors, t as openBrowser } from "./ui-CpdtK7mt.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-BUlQR117.mjs";
|
|
2
|
-
import { c as apiRequest, i as unwrap, n as reportErrors, r as requireTty } from "./ui-
|
|
2
|
+
import { c as apiRequest, i as unwrap, n as reportErrors, r as requireTty } from "./ui-CpdtK7mt.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
|
-
import { t as version } from "./package-
|
|
2
|
-
import { n as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-DBEmT6Uh.mjs";
|
|
2
|
+
import { n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, relative } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, l as stagingUrl, o as projectFlagArgs, s as projectUrl } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { c as apiRequest, i as unwrap, l as withContext, n as reportErrors, r as requireTty } from "./ui-
|
|
2
|
+
import { c as apiRequest, i as unwrap, l as withContext, n as reportErrors, r as requireTty } from "./ui-CpdtK7mt.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region src/staging.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { c as apiRequest, n as reportErrors } from "./ui-
|
|
2
|
+
import { c as apiRequest, n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
const tokenCommand = defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -93,6 +93,18 @@ const ERROR_COPY = {
|
|
|
93
93
|
version_not_found: {
|
|
94
94
|
why: "no deploy with that version exists for this project",
|
|
95
95
|
fix: "Run `canopy deploys` to list versions you can roll back to."
|
|
96
|
+
},
|
|
97
|
+
invalid_host: {
|
|
98
|
+
why: "that entry is not a plain hostname",
|
|
99
|
+
fix: "Use a bare hostname (api.example.com) or a subdomain wildcard (*.googleapis.com) — no IPs, ports, schemes or paths."
|
|
100
|
+
},
|
|
101
|
+
too_many_hosts: {
|
|
102
|
+
why: "the allowlist is at its 32-entry limit",
|
|
103
|
+
fix: "Remove hosts you no longer need (`canopy egress list` / `canopy egress remove`), or cover several subdomains with one *. wildcard."
|
|
104
|
+
},
|
|
105
|
+
worker_egress_sync_failed: {
|
|
106
|
+
why: "the list was saved but redeploying the running worker with it failed",
|
|
107
|
+
fix: "Retry, or redeploy the project (`canopy deploy`) to apply the new allowlist."
|
|
96
108
|
}
|
|
97
109
|
};
|
|
98
110
|
/** Generic explanation for a status code when we have no per-code copy. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
|
|
2
|
-
import { n as reportErrors } from "./ui-
|
|
2
|
+
import { n as reportErrors } from "./ui-CpdtK7mt.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-D3MHtg4s.mjs");
|
|
116
116
|
const refreshSkill = skillInstalled(process.cwd());
|
|
117
117
|
console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
|
|
118
118
|
await run(command, cmd);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as resolveProject, o as projectFlagArgs } from "./site-BUlQR117.mjs";
|
|
2
|
-
import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-
|
|
2
|
+
import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/usage.ts
|
|
5
5
|
/**
|
package/package.json
CHANGED
package/dist/dev-U9u6katn.mjs
DELETED