@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.
@@ -0,0 +1,2 @@
1
+ import { t as devCommand } from "./dev-BanjQNiD.mjs";
2
+ export { devCommand };
@@ -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-CWU6XFe_.mjs");
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-CNPBZ89U.mjs");
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-CNPBZ89U.mjs");
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 { rewriteApiPath as n, devCommand as t };
315
+ export { devProxyHeaders as n, rewriteApiPath as r, devCommand as t };
@@ -1,76 +1,6 @@
1
- import "./src-BK0Hiwsn.mjs";
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-DTm0EzxR.mjs";
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-DTm0EzxR.mjs";
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-DTm0EzxR.mjs";
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-C5BmSJo1.mjs").then((m) => m.devCommand),
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-Bnmi1Ukv.mjs").then((m) => m.feedbackCommand),
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-BLWio_65.mjs").then((m) => m.docsCommand),
84
- skill: () => import("./skill-Dt49jdUT.mjs").then((m) => m.skillCommand),
85
- upgrade: () => import("./upgrade-Cf81feok.mjs").then((m) => m.upgradeCommand),
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 { n as META_COLUMNS, r as blobRouter, t as dbRouter } from "./src-BK0Hiwsn.mjs";
3
- import { n as rewriteApiPath } from "./dev-DVUEJ80c.mjs";
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
1
  //#region package.json
2
- var version = "0.7.24";
2
+ var version = "0.7.25";
3
3
  //#endregion
4
4
  export { version as t };
@@ -1,4 +1,4 @@
1
- import { t as version } from "./package-DTm0EzxR.mjs";
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
- export { META_COLUMNS as n, blobRouter as r, dbRouter as t };
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-Dt49jdUT.mjs");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.24",
3
+ "version": "0.7.25",
4
4
  "description": "Canopy CLI — create, develop and deploy Canopy projects.",
5
5
  "bin": {
6
6
  "canopy": "./dist/index.mjs"
@@ -1,2 +0,0 @@
1
- import { t as devCommand } from "./dev-DVUEJ80c.mjs";
2
- export { devCommand };