@mediamonks/canopy-cli 0.7.3 → 0.7.5
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/docs/sdk.md +17 -4
- package/dist/docs/troubleshooting.md +13 -0
- package/dist/{docs-BwtpnE8W.mjs → docs-DQmUReJR.mjs} +1 -1
- package/dist/index.mjs +6 -19
- package/dist/logs-DBLDS3aQ.mjs +109 -0
- package/dist/{package-BjMP21nL.mjs → package-yVoO2pq4.mjs} +1 -1
- package/dist/{skill-aTr-RQil.mjs → skill-12Ga-2UT.mjs} +1 -1
- package/dist/templates/spa/package.json +1 -1
- package/dist/templates/worker/package.json +1 -1
- package/dist/{upgrade-Dl6DiMhL.mjs → upgrade-Du6U0xjp.mjs} +1 -1
- package/package.json +2 -2
package/dist/docs/sdk.md
CHANGED
|
@@ -38,10 +38,11 @@ const canopy = createCanopy();
|
|
|
38
38
|
// canopy.auth, canopy.db, canopy.blob, canopy.ai, canopy.services
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
| Option | Type | Default | What it does
|
|
42
|
-
| --------- | -------------- | -------------- |
|
|
43
|
-
| `baseUrl` | `string` | `"/_api"` | Base URL of the platform data API. The default is same-origin, so it works untouched in the browser.
|
|
44
|
-
| `
|
|
41
|
+
| Option | Type | Default | What it does |
|
|
42
|
+
| --------- | -------------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `baseUrl` | `string` | `"/_api"` | Base URL of the platform data API. The default is same-origin, so it works untouched in the browser. |
|
|
44
|
+
| `token` | `string` | none | A project-scoped `cnpy_` token sent as a bearer on every request — for scripts and CI running outside Canopy hosting. |
|
|
45
|
+
| `fetch` | `typeof fetch` | global `fetch` | Override how requests are sent — a backend worker passes its platform service binding here. |
|
|
45
46
|
|
|
46
47
|
In the browser no config is needed. In a backend worker (the `worker`
|
|
47
48
|
template), the platform is reachable through the `CANOPY` service binding, so
|
|
@@ -55,6 +56,18 @@ app.get("/api/hello", async (c) => {
|
|
|
55
56
|
});
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
Outside Canopy hosting — a local Node script, a CI job — there is no session
|
|
60
|
+
and no service binding. Mint a dev token (`canopy dev` does this for you, or
|
|
61
|
+
`POST /control/v1/projects/:slug/dev-token`) and pass it with the platform's
|
|
62
|
+
API host:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const canopy = createCanopy({
|
|
66
|
+
baseUrl: "https://api.canopy.website",
|
|
67
|
+
token: process.env.CANOPY_TOKEN, // a project-scoped cnpy_ token
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
58
71
|
## One instance, shared
|
|
59
72
|
|
|
60
73
|
In the browser, create the client once and import it everywhere. The scaffolds
|
|
@@ -77,6 +77,19 @@ or use dashboard Settings → Outbound hosts. Details and entry rules:
|
|
|
77
77
|
target outbound requests are currently unrestricted, so this error only
|
|
78
78
|
appears on GCP.
|
|
79
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`.)
|
|
92
|
+
|
|
80
93
|
## Docs are stale
|
|
81
94
|
|
|
82
95
|
`canopy docs` caches the platform's docs. Force a refresh:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-yVoO2pq4.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
4
|
import { n as reportErrors } from "./ui-CpdtK7mt.mjs";
|
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-yVoO2pq4.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",
|
|
@@ -91,10 +78,10 @@ const main = defineCommand({
|
|
|
91
78
|
db: () => import("./db-D3RKaLVU.mjs").then((m) => m.dbCommand),
|
|
92
79
|
usage: () => import("./usage-wpew0a2M.mjs").then((m) => m.usageCommand),
|
|
93
80
|
open: () => import("./open-CLSh6oez.mjs").then((m) => m.openCommand),
|
|
94
|
-
docs: () => import("./docs-
|
|
95
|
-
skill: () => import("./skill-
|
|
96
|
-
upgrade: () => import("./upgrade-
|
|
97
|
-
logs:
|
|
81
|
+
docs: () => import("./docs-DQmUReJR.mjs").then((m) => m.docsCommand),
|
|
82
|
+
skill: () => import("./skill-12Ga-2UT.mjs").then((m) => m.skillCommand),
|
|
83
|
+
upgrade: () => import("./upgrade-Du6U0xjp.mjs").then((m) => m.upgradeCommand),
|
|
84
|
+
logs: () => import("./logs-DBLDS3aQ.mjs").then((m) => m.logsCommand)
|
|
98
85
|
}
|
|
99
86
|
});
|
|
100
87
|
if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-yVoO2pq4.mjs";
|
|
2
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";
|
|
@@ -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-12Ga-2UT.mjs");
|
|
116
116
|
const refreshSkill = skillInstalled(process.cwd());
|
|
117
117
|
console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
|
|
118
118
|
await run(command, cmd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediamonks/canopy-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Canopy CLI — create, develop and deploy Canopy projects.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"canopy": "./dist/index.mjs"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"esbuild": "^0.28.1",
|
|
21
21
|
"hono": "^4.8.0",
|
|
22
22
|
"vite-server": "npm:vite@^7",
|
|
23
|
-
"@mediamonks/canopy": "0.0.
|
|
23
|
+
"@mediamonks/canopy": "0.0.4",
|
|
24
24
|
"@mediamonks/canopy-data-plane": "0.0.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|