@mediamonks/canopy-cli 0.7.24 → 0.7.25
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/dev-BAcZi226.mjs +2 -0
- package/dist/{dev-DVUEJ80c.mjs → dev-BanjQNiD.mjs} +19 -8
- package/dist/{dev-worker-CWU6XFe_.mjs → dev-worker-BerdPJ_U.mjs} +1 -71
- package/dist/{docs-BLWio_65.mjs → docs-xRbVshXp.mjs} +1 -1
- package/dist/{feedback-Bnmi1Ukv.mjs → feedback-CGjOAHON.mjs} +1 -1
- package/dist/index.mjs +6 -6
- package/dist/{local-CNPBZ89U.mjs → local-DvzG4XUB.mjs} +2 -2
- package/dist/{package-DTm0EzxR.mjs → package-vZ0xH49Y.mjs} +1 -1
- package/dist/{skill-Dt49jdUT.mjs → skill-PoaZrCNy.mjs} +1 -1
- package/dist/{src-BK0Hiwsn.mjs → src-CwSKZwNK.mjs} +71 -1
- package/dist/{upgrade-Cf81feok.mjs → upgrade-DjrNZ-B4.mjs} +1 -1
- package/package.json +1 -1
- package/dist/dev-C5BmSJo1.mjs +0 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a as projectDirArgs, c as resolveProject, i as projectDialect, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
2
2
|
import { d as withContext, l as apiRequest, o as ApiError, r as reportErrors, t as formatDateTime } from "./ui-CUNkis89.mjs";
|
|
3
3
|
import { r as readMigrationFiles } from "./migrations-Btzr9_q7.mjs";
|
|
4
|
+
import { n as stampWorkerHeaders } from "./src-CwSKZwNK.mjs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { defineCommand } from "citty";
|
|
6
7
|
import { existsSync } from "node:fs";
|
|
@@ -37,7 +38,7 @@ async function workerPlugin(flags, slug) {
|
|
|
37
38
|
console.log(`note: canopy.json points "worker" at ${entry}, which does not exist — skipping.`);
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
|
-
const { missingSecretNames, readDevVars, workerDevPlugin } = await import("./dev-worker-
|
|
41
|
+
const { missingSecretNames, readDevVars, workerDevPlugin } = await import("./dev-worker-BerdPJ_U.mjs");
|
|
41
42
|
const secrets = readDevVars(flags.dir);
|
|
42
43
|
const email = siteConfig().email;
|
|
43
44
|
const plugin = workerDevPlugin({
|
|
@@ -132,6 +133,19 @@ function devTokenContext(slug) {
|
|
|
132
133
|
fixes: { not_found: `No project "${slug}" is registered on Canopy (or your account can't access it). Either rename "slug" in canopy.json to match an existing project (see \`canopy projects\`), or register this one — create it in the dashboard, then set that slug in canopy.json.` }
|
|
133
134
|
};
|
|
134
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Headers the remote-mode /_api proxy attaches to every browser-SDK call.
|
|
138
|
+
* Project scope is transport-level (docs/10 D17): the api. gateway refuses
|
|
139
|
+
* /data/v1 without X-Canopy-Project, so the proxy stamps it exactly the way
|
|
140
|
+
* the worker path does — one definition, no drift between the two.
|
|
141
|
+
*/
|
|
142
|
+
function devProxyHeaders(token, slug, channel) {
|
|
143
|
+
const headers = stampWorkerHeaders(new Headers({ authorization: `Bearer ${token}` }), {
|
|
144
|
+
projectSlug: slug,
|
|
145
|
+
channel
|
|
146
|
+
});
|
|
147
|
+
return Object.fromEntries(headers);
|
|
148
|
+
}
|
|
135
149
|
async function devRemote(flags, slug) {
|
|
136
150
|
const { token, expires_at } = await withContext(apiRequest("POST", `/projects/${slug}/dev-token`), devTokenContext(slug));
|
|
137
151
|
const { apiUrl } = siteConfig();
|
|
@@ -139,10 +153,7 @@ async function devRemote(flags, slug) {
|
|
|
139
153
|
const server = await startVite(flags, { proxy: { "/_api": {
|
|
140
154
|
target: apiUrl,
|
|
141
155
|
changeOrigin: true,
|
|
142
|
-
headers:
|
|
143
|
-
authorization: `Bearer ${token}`,
|
|
144
|
-
...flags.staging ? { "x-canopy-channel": "staging" } : {}
|
|
145
|
-
},
|
|
156
|
+
headers: devProxyHeaders(token, slug, flags.staging ? "staging" : "live"),
|
|
146
157
|
rewrite: rewriteApiPath
|
|
147
158
|
} } }, worker ? [worker.plugin] : []);
|
|
148
159
|
if (flags.staging) {
|
|
@@ -165,7 +176,7 @@ async function devRemote(flags, slug) {
|
|
|
165
176
|
async function devLocal(flags, slug) {
|
|
166
177
|
let dump;
|
|
167
178
|
if (flags.pull) {
|
|
168
|
-
const { fetchProjectDump } = await import("./local-
|
|
179
|
+
const { fetchProjectDump } = await import("./local-DvzG4XUB.mjs");
|
|
169
180
|
console.log(`Pulling a fresh export of ${slug}${flags.staging ? " [staging]" : ""}…`);
|
|
170
181
|
dump = await fetchProjectDump(slug, flags.staging ? "staging" : "live");
|
|
171
182
|
}
|
|
@@ -180,7 +191,7 @@ async function devLocal(flags, slug) {
|
|
|
180
191
|
const reason = err instanceof ApiError ? err.explain().why : String(err);
|
|
181
192
|
console.log(`Note: ai/services will be unavailable — could not reach the platform (${reason}).`);
|
|
182
193
|
}
|
|
183
|
-
const { createSandbox } = await import("./local-
|
|
194
|
+
const { createSandbox } = await import("./local-DvzG4XUB.mjs");
|
|
184
195
|
const sandbox = await createSandbox({
|
|
185
196
|
slug,
|
|
186
197
|
persistDir: join(flags.dir, ".canopy", "local"),
|
|
@@ -301,4 +312,4 @@ const devCommand = defineCommand({
|
|
|
301
312
|
})
|
|
302
313
|
});
|
|
303
314
|
//#endregion
|
|
304
|
-
export {
|
|
315
|
+
export { devProxyHeaders as n, rewriteApiPath as r, devCommand as t };
|
|
@@ -1,76 +1,6 @@
|
|
|
1
|
-
import "./src-
|
|
1
|
+
import { n as stampWorkerHeaders, t as redactCanopyCredentials } from "./src-CwSKZwNK.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { relative, resolve, sep } from "node:path";
|
|
4
|
-
//#region ../data-plane/src/worker-request.ts
|
|
5
|
-
/**
|
|
6
|
-
* How a project's backend addresses the platform — the one definition.
|
|
7
|
-
*
|
|
8
|
-
* Three hosts run project workers and each builds this request itself: the
|
|
9
|
-
* Workers-for-Platforms shim (apps/api/src/platform/wfp-shim.ts), the Node
|
|
10
|
-
* runtime (apps/project-runtime/src/canopy-fetch.ts), and `canopy dev`
|
|
11
|
-
* (packages/cli/src/dev-worker.ts). They had three copies of the header
|
|
12
|
-
* shaping, and the two that hold a worker token had two copies of the
|
|
13
|
-
* credential rule — which is exactly how the Google project grant came to
|
|
14
|
-
* work on one target and not the other.
|
|
15
|
-
*
|
|
16
|
-
* The shim cannot import this: it ships as a standalone module uploaded
|
|
17
|
-
* beside the user bundle, so it may only import `./bundle.mjs`. It keeps a
|
|
18
|
-
* transcription, and `wfp-shim.test.ts` runs it against the table in
|
|
19
|
-
* `worker-request.cases.ts` alongside this implementation, so the two cannot
|
|
20
|
-
* drift silently.
|
|
21
|
-
*/
|
|
22
|
-
/** Gateway-stamped identity, for the worker's own `env.USER` — never relayed. */
|
|
23
|
-
const IDENTITY_STAMP_HEADERS = [
|
|
24
|
-
"x-canopy-user-sub",
|
|
25
|
-
"x-canopy-user-email",
|
|
26
|
-
"x-canopy-user-name"
|
|
27
|
-
];
|
|
28
|
-
/** Cookies that authenticate to Canopy itself — the monk session and the client-page session. */
|
|
29
|
-
const CANOPY_COOKIE_NAMES = /* @__PURE__ */ new Set(["canopy_session", "canopy_client"]);
|
|
30
|
-
/**
|
|
31
|
-
* Strip Canopy's own credentials from a request before untrusted project code
|
|
32
|
-
* sees it (R-01).
|
|
33
|
-
*
|
|
34
|
-
* The visitor's session cookie arrives with every request the gateway
|
|
35
|
-
* dispatches, and a bundle that can read it can replay it against the platform
|
|
36
|
-
* as that person — against the control plane, and against other projects' data
|
|
37
|
-
* on the api host. It is a credential, so the host keeps it in closure scope
|
|
38
|
-
* like the raw DB and blob handles, and hands the bundle a redacted request.
|
|
39
|
-
*
|
|
40
|
-
* Redaction is surgical on purpose: an app's own cookies and its own
|
|
41
|
-
* Authorization scheme survive, because those belong to the app. Only
|
|
42
|
-
* `canopy_session` / `canopy_client` and a `cnpy_` bearer are removed.
|
|
43
|
-
*
|
|
44
|
-
* `env.CANOPY.fetch` still authenticates as the visitor — the host passes it
|
|
45
|
-
* the unredacted headers — so nothing about the platform-facing behaviour
|
|
46
|
-
* changes; the credential simply stops being reachable from bundle code.
|
|
47
|
-
*/
|
|
48
|
-
function redactCanopyCredentials(headers) {
|
|
49
|
-
const cookie = headers.get("cookie");
|
|
50
|
-
if (cookie !== null) {
|
|
51
|
-
const kept = cookie.split(";").filter((part) => {
|
|
52
|
-
const name = part.split("=")[0]?.trim() ?? "";
|
|
53
|
-
return name !== "" && !CANOPY_COOKIE_NAMES.has(name);
|
|
54
|
-
}).join(";").trim();
|
|
55
|
-
if (kept === "") headers.delete("cookie");
|
|
56
|
-
else headers.set("cookie", kept);
|
|
57
|
-
}
|
|
58
|
-
if (headers.get("authorization")?.startsWith("Bearer cnpy_")) headers.delete("authorization");
|
|
59
|
-
return headers;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Strip what must never travel upstream and stamp who is calling. Common to
|
|
63
|
-
* every host; says nothing about credentials.
|
|
64
|
-
*/
|
|
65
|
-
function stampWorkerHeaders(headers, ctx) {
|
|
66
|
-
headers.delete("host");
|
|
67
|
-
for (const stamp of IDENTITY_STAMP_HEADERS) headers.delete(stamp);
|
|
68
|
-
headers.set("x-canopy-project", ctx.projectSlug);
|
|
69
|
-
if (ctx.channel === "staging") headers.set("x-canopy-channel", "staging");
|
|
70
|
-
else headers.delete("x-canopy-channel");
|
|
71
|
-
return headers;
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
4
|
//#region src/dev-worker.ts
|
|
75
5
|
/**
|
|
76
6
|
* Paths that belong to the worker rather than the SPA — the gateway's rule
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-vZ0xH49Y.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
4
|
import { r as reportErrors } from "./ui-CUNkis89.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-vZ0xH49Y.mjs";
|
|
2
2
|
import { a as projectDirArgs, c as resolveProject, d as tryResolveProject, n as siteConfig, u as tryReadCanopyJson } from "./site-DM2J10CB.mjs";
|
|
3
3
|
import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-CUNkis89.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
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-vZ0xH49Y.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";
|
|
@@ -67,7 +67,7 @@ const main = defineCommand({
|
|
|
67
67
|
init: () => import("./projects-CLs6MEF7.mjs").then((m) => m.initCommand),
|
|
68
68
|
projects: () => import("./projects-CLs6MEF7.mjs").then((m) => m.projectsCommand),
|
|
69
69
|
delete: () => import("./projects-CLs6MEF7.mjs").then((m) => m.deleteCommand),
|
|
70
|
-
dev: () => import("./dev-
|
|
70
|
+
dev: () => import("./dev-BAcZi226.mjs").then((m) => m.devCommand),
|
|
71
71
|
deploy: () => import("./deploy-CYKiBnrg.mjs").then((m) => m.deployCommand),
|
|
72
72
|
promote: () => import("./deploy-CYKiBnrg.mjs").then((m) => m.promoteCommand),
|
|
73
73
|
staging: () => import("./staging-pZpdZuj_.mjs").then((m) => m.stagingCommand),
|
|
@@ -76,13 +76,13 @@ const main = defineCommand({
|
|
|
76
76
|
token: () => import("./token-BKwGa10J.mjs").then((m) => m.tokenCommand),
|
|
77
77
|
egress: () => import("./egress-BjN-7vil.mjs").then((m) => m.egressCommand),
|
|
78
78
|
schedule: () => import("./schedule-B8zA8WlG.mjs").then((m) => m.scheduleCommand),
|
|
79
|
-
feedback: () => import("./feedback-
|
|
79
|
+
feedback: () => import("./feedback-CGjOAHON.mjs").then((m) => m.feedbackCommand),
|
|
80
80
|
db: () => import("./db-jmYaum8F.mjs").then((m) => m.dbCommand),
|
|
81
81
|
usage: () => import("./usage-UmzU6sGp.mjs").then((m) => m.usageCommand),
|
|
82
82
|
open: () => import("./open-D2hH933K.mjs").then((m) => m.openCommand),
|
|
83
|
-
docs: () => import("./docs-
|
|
84
|
-
skill: () => import("./skill-
|
|
85
|
-
upgrade: () => import("./upgrade-
|
|
83
|
+
docs: () => import("./docs-xRbVshXp.mjs").then((m) => m.docsCommand),
|
|
84
|
+
skill: () => import("./skill-PoaZrCNy.mjs").then((m) => m.skillCommand),
|
|
85
|
+
upgrade: () => import("./upgrade-DjrNZ-B4.mjs").then((m) => m.upgradeCommand),
|
|
86
86
|
logs: () => import("./logs-B_YoflCq.mjs").then((m) => m.logsCommand)
|
|
87
87
|
}
|
|
88
88
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { s as apiDataRequest } from "./ui-CUNkis89.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { a as blobRouter, i as META_COLUMNS, r as dbRouter } from "./src-CwSKZwNK.mjs";
|
|
3
|
+
import { r as rewriteApiPath } from "./dev-BanjQNiD.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,4 +1,4 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
1
|
+
import { t as version } from "./package-vZ0xH49Y.mjs";
|
|
2
2
|
import { r as reportErrors } from "./ui-CUNkis89.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
@@ -582,4 +582,74 @@ function dbRouter(config) {
|
|
|
582
582
|
return db;
|
|
583
583
|
}
|
|
584
584
|
//#endregion
|
|
585
|
-
|
|
585
|
+
//#region ../data-plane/src/worker-request.ts
|
|
586
|
+
/**
|
|
587
|
+
* How a project's backend addresses the platform — the one definition.
|
|
588
|
+
*
|
|
589
|
+
* Three hosts run project workers and each builds this request itself: the
|
|
590
|
+
* Workers-for-Platforms shim (apps/api/src/platform/wfp-shim.ts), the Node
|
|
591
|
+
* runtime (apps/project-runtime/src/canopy-fetch.ts), and `canopy dev`
|
|
592
|
+
* (packages/cli/src/dev-worker.ts). They had three copies of the header
|
|
593
|
+
* shaping, and the two that hold a worker token had two copies of the
|
|
594
|
+
* credential rule — which is exactly how the Google project grant came to
|
|
595
|
+
* work on one target and not the other.
|
|
596
|
+
*
|
|
597
|
+
* The shim cannot import this: it ships as a standalone module uploaded
|
|
598
|
+
* beside the user bundle, so it may only import `./bundle.mjs`. It keeps a
|
|
599
|
+
* transcription, and `wfp-shim.test.ts` runs it against the table in
|
|
600
|
+
* `worker-request.cases.ts` alongside this implementation, so the two cannot
|
|
601
|
+
* drift silently.
|
|
602
|
+
*/
|
|
603
|
+
/** Gateway-stamped identity, for the worker's own `env.USER` — never relayed. */
|
|
604
|
+
const IDENTITY_STAMP_HEADERS = [
|
|
605
|
+
"x-canopy-user-sub",
|
|
606
|
+
"x-canopy-user-email",
|
|
607
|
+
"x-canopy-user-name"
|
|
608
|
+
];
|
|
609
|
+
/** Cookies that authenticate to Canopy itself — the monk session and the client-page session. */
|
|
610
|
+
const CANOPY_COOKIE_NAMES = /* @__PURE__ */ new Set(["canopy_session", "canopy_client"]);
|
|
611
|
+
/**
|
|
612
|
+
* Strip Canopy's own credentials from a request before untrusted project code
|
|
613
|
+
* sees it (R-01).
|
|
614
|
+
*
|
|
615
|
+
* The visitor's session cookie arrives with every request the gateway
|
|
616
|
+
* dispatches, and a bundle that can read it can replay it against the platform
|
|
617
|
+
* as that person — against the control plane, and against other projects' data
|
|
618
|
+
* on the api host. It is a credential, so the host keeps it in closure scope
|
|
619
|
+
* like the raw DB and blob handles, and hands the bundle a redacted request.
|
|
620
|
+
*
|
|
621
|
+
* Redaction is surgical on purpose: an app's own cookies and its own
|
|
622
|
+
* Authorization scheme survive, because those belong to the app. Only
|
|
623
|
+
* `canopy_session` / `canopy_client` and a `cnpy_` bearer are removed.
|
|
624
|
+
*
|
|
625
|
+
* `env.CANOPY.fetch` still authenticates as the visitor — the host passes it
|
|
626
|
+
* the unredacted headers — so nothing about the platform-facing behaviour
|
|
627
|
+
* changes; the credential simply stops being reachable from bundle code.
|
|
628
|
+
*/
|
|
629
|
+
function redactCanopyCredentials(headers) {
|
|
630
|
+
const cookie = headers.get("cookie");
|
|
631
|
+
if (cookie !== null) {
|
|
632
|
+
const kept = cookie.split(";").filter((part) => {
|
|
633
|
+
const name = part.split("=")[0]?.trim() ?? "";
|
|
634
|
+
return name !== "" && !CANOPY_COOKIE_NAMES.has(name);
|
|
635
|
+
}).join(";").trim();
|
|
636
|
+
if (kept === "") headers.delete("cookie");
|
|
637
|
+
else headers.set("cookie", kept);
|
|
638
|
+
}
|
|
639
|
+
if (headers.get("authorization")?.startsWith("Bearer cnpy_")) headers.delete("authorization");
|
|
640
|
+
return headers;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Strip what must never travel upstream and stamp who is calling. Common to
|
|
644
|
+
* every host; says nothing about credentials.
|
|
645
|
+
*/
|
|
646
|
+
function stampWorkerHeaders(headers, ctx) {
|
|
647
|
+
headers.delete("host");
|
|
648
|
+
for (const stamp of IDENTITY_STAMP_HEADERS) headers.delete(stamp);
|
|
649
|
+
headers.set("x-canopy-project", ctx.projectSlug);
|
|
650
|
+
if (ctx.channel === "staging") headers.set("x-canopy-channel", "staging");
|
|
651
|
+
else headers.delete("x-canopy-channel");
|
|
652
|
+
return headers;
|
|
653
|
+
}
|
|
654
|
+
//#endregion
|
|
655
|
+
export { blobRouter as a, META_COLUMNS as i, stampWorkerHeaders as n, dbRouter as r, redactCanopyCredentials as t };
|
|
@@ -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-PoaZrCNy.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());
|
package/package.json
CHANGED
package/dist/dev-C5BmSJo1.mjs
DELETED