@mediamonks/canopy-cli 0.7.27 → 0.7.29

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.
Files changed (42) hide show
  1. package/dist/{db-DVMwCLlU.mjs → db-KTLn8J1u.mjs} +2 -2
  2. package/dist/{deploy-CTMcwHhI.mjs → deploy-CBf44bvG.mjs} +2 -2
  3. package/dist/dev-DGCu9MjB.mjs +2 -0
  4. package/dist/{dev-BvjbJcse.mjs → dev-JlzMZJow.mjs} +50 -21
  5. package/dist/{dev-worker-BerdPJ_U.mjs → dev-worker-DjJGSUGj.mjs} +1 -1
  6. package/dist/docs/cli-db.md +2 -0
  7. package/dist/docs/cli-index.md +1 -1
  8. package/dist/docs/getting-started.md +1 -1
  9. package/dist/docs/index.json +12 -2
  10. package/dist/docs/platform-egress.md +25 -0
  11. package/dist/docs/platform-members-and-access.md +13 -4
  12. package/dist/docs/platform-slack.md +6 -0
  13. package/dist/docs/sdk-notify.md +140 -0
  14. package/dist/docs/sdk-realtime.md +149 -0
  15. package/dist/docs/sdk-slack.md +3 -0
  16. package/dist/docs/sdk.md +5 -4
  17. package/dist/docs/static-serving.md +19 -0
  18. package/dist/{docs-CuS9nIXm.mjs → docs-ZQnhFSKT.mjs} +2 -2
  19. package/dist/{egress-Cn-P46O3.mjs → egress-Cn9zDfDT.mjs} +1 -1
  20. package/dist/{feedback-DormOsQa.mjs → feedback-CNfUjjNw.mjs} +2 -2
  21. package/dist/index.mjs +26 -26
  22. package/dist/{local-laAZEDVJ.mjs → local-BA_TPWpN.mjs} +56 -4
  23. package/dist/{login-C4MDP5NM.mjs → login-MX0xq8UE.mjs} +1 -1
  24. package/dist/{logs-CfDINw7U.mjs → logs-D5RWqsWl.mjs} +1 -1
  25. package/dist/{migrations-B9fXxMDj.mjs → migrations-C8yRPJIX.mjs} +1 -1
  26. package/dist/{open-rS1ARzOy.mjs → open-BJgd0R61.mjs} +1 -1
  27. package/dist/{package-CsmBH_fN.mjs → package-DzU_2IJi.mjs} +1 -1
  28. package/dist/{projects-Dmm_976k.mjs → projects-BG7Injpb.mjs} +1 -1
  29. package/dist/{schedule-Buu71RWQ.mjs → schedule-CLuksxSM.mjs} +1 -1
  30. package/dist/{secrets-BD3DaTih.mjs → secrets-Bl3uOx4l.mjs} +1 -1
  31. package/dist/skill/canopy/SKILL.md +1 -1
  32. package/dist/{skill-DX18WOap.mjs → skill-CQ4a__Ga.mjs} +2 -2
  33. package/dist/{src-CwSKZwNK.mjs → src-C7zPhbxi.mjs} +238 -1
  34. package/dist/{staging-CKEoDnac.mjs → staging-D06zobnf.mjs} +1 -1
  35. package/dist/templates/spa/.github/workflows/canopy-deploy.yml +3 -3
  36. package/dist/templates/worker/.github/workflows/canopy-deploy.yml +3 -3
  37. package/dist/{token-Dp2CoIYj.mjs → token-C_PFNqcv.mjs} +1 -1
  38. package/dist/{ui-DR-siu2S.mjs → ui-CeupNHNa.mjs} +4 -0
  39. package/dist/{upgrade-PfdH0NPe.mjs → upgrade-BzmIrnjs.mjs} +2 -2
  40. package/dist/{usage-81JSiWIx.mjs → usage-CV7TjTO8.mjs} +1 -1
  41. package/package.json +2 -2
  42. package/dist/dev-BOvMCaML.mjs +0 -2
@@ -1,6 +1,6 @@
1
- import { t as version } from "./package-CsmBH_fN.mjs";
1
+ import { t as version } from "./package-DzU_2IJi.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-DR-siu2S.mjs";
3
+ import { c as apiDownload, l as apiRequest, r as reportErrors, t as formatDateTime, u as apiUpload } from "./ui-CeupNHNa.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";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "./package-CsmBH_fN.mjs";
2
+ import { t as version } from "./package-DzU_2IJi.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,31 +60,31 @@ const main = defineCommand({
60
60
  description: "canopy — create and deploy vibecoded projects"
61
61
  },
62
62
  subCommands: {
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)
63
+ login: () => import("./login-MX0xq8UE.mjs").then((m) => m.loginCommand),
64
+ whoami: () => import("./login-MX0xq8UE.mjs").then((m) => m.whoamiCommand),
65
+ logout: () => import("./login-MX0xq8UE.mjs").then((m) => m.logoutCommand),
66
+ create: () => import("./projects-BG7Injpb.mjs").then((m) => m.createCommand),
67
+ init: () => import("./projects-BG7Injpb.mjs").then((m) => m.initCommand),
68
+ projects: () => import("./projects-BG7Injpb.mjs").then((m) => m.projectsCommand),
69
+ delete: () => import("./projects-BG7Injpb.mjs").then((m) => m.deleteCommand),
70
+ dev: () => import("./dev-DGCu9MjB.mjs").then((m) => m.devCommand),
71
+ deploy: () => import("./deploy-CBf44bvG.mjs").then((m) => m.deployCommand),
72
+ promote: () => import("./deploy-CBf44bvG.mjs").then((m) => m.promoteCommand),
73
+ staging: () => import("./staging-D06zobnf.mjs").then((m) => m.stagingCommand),
74
+ rollback: () => import("./deploy-CBf44bvG.mjs").then((m) => m.rollbackCommand),
75
+ download: () => import("./deploy-CBf44bvG.mjs").then((m) => m.downloadCommand),
76
+ token: () => import("./token-C_PFNqcv.mjs").then((m) => m.tokenCommand),
77
+ egress: () => import("./egress-Cn9zDfDT.mjs").then((m) => m.egressCommand),
78
+ secrets: () => import("./secrets-Bl3uOx4l.mjs").then((m) => m.secretsCommand),
79
+ schedule: () => import("./schedule-CLuksxSM.mjs").then((m) => m.scheduleCommand),
80
+ feedback: () => import("./feedback-CNfUjjNw.mjs").then((m) => m.feedbackCommand),
81
+ db: () => import("./db-KTLn8J1u.mjs").then((m) => m.dbCommand),
82
+ usage: () => import("./usage-CV7TjTO8.mjs").then((m) => m.usageCommand),
83
+ open: () => import("./open-BJgd0R61.mjs").then((m) => m.openCommand),
84
+ docs: () => import("./docs-ZQnhFSKT.mjs").then((m) => m.docsCommand),
85
+ skill: () => import("./skill-CQ4a__Ga.mjs").then((m) => m.skillCommand),
86
+ upgrade: () => import("./upgrade-BzmIrnjs.mjs").then((m) => m.upgradeCommand),
87
+ logs: () => import("./logs-D5RWqsWl.mjs").then((m) => m.logsCommand)
88
88
  }
89
89
  });
90
90
  if (process.argv[2] !== "upgrade") notifyOfUpdate(version);
@@ -1,6 +1,6 @@
1
- import { s as apiDataRequest } from "./ui-DR-siu2S.mjs";
2
- import { a as blobRouter, i as META_COLUMNS, r as dbRouter } from "./src-CwSKZwNK.mjs";
3
- import { r as rewriteApiPath } from "./dev-BvjbJcse.mjs";
1
+ import { s as apiDataRequest } from "./ui-CeupNHNa.mjs";
2
+ import { a as META_COLUMNS, i as dbRouter, o as blobRouter, r as realtimeRouter } from "./src-C7zPhbxi.mjs";
3
+ import { i as rewriteApiPath } from "./dev-JlzMZJow.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";
@@ -532,6 +532,39 @@ async function applyMigrations(store, dialect, files) {
532
532
  };
533
533
  }
534
534
  //#endregion
535
+ //#region ../data-plane/src/realtime-memory.ts
536
+ const sameScope = (a, b) => a.projectId === b.projectId && a.channel === b.channel;
537
+ function memoryRealtimeBroker() {
538
+ const subscriptions = /* @__PURE__ */ new Set();
539
+ const dropProject = (projectId) => {
540
+ for (const sub of subscriptions) {
541
+ if (sub.scope.projectId !== projectId) continue;
542
+ subscriptions.delete(sub);
543
+ sub.sink.close();
544
+ }
545
+ };
546
+ return {
547
+ async subscribe(scope, topics, sink) {
548
+ const sub = {
549
+ scope,
550
+ topics: new Set(topics),
551
+ sink
552
+ };
553
+ subscriptions.add(sub);
554
+ return { close: () => void subscriptions.delete(sub) };
555
+ },
556
+ async publish(scope, envelope) {
557
+ for (const sub of subscriptions) if (sameScope(sub.scope, scope) && sub.topics.has(envelope.topic)) sub.sink.deliver(envelope);
558
+ },
559
+ async disconnect(projectId) {
560
+ dropProject(projectId);
561
+ },
562
+ async purge(projectId) {
563
+ dropProject(projectId);
564
+ }
565
+ };
566
+ }
567
+ //#endregion
535
568
  //#region src/fs-blob.ts
536
569
  /**
537
570
  * Filesystem BlobBucket for `canopy dev --local` — same structural interface
@@ -808,6 +841,22 @@ async function createSandbox(options) {
808
841
  },
809
842
  signDownload: async () => "local"
810
843
  }));
844
+ const realtimeHub = memoryRealtimeBroker();
845
+ app.route("/data/v1", realtimeRouter({ context: async (c) => ownProject(c) ?? {
846
+ broker: realtimeHub,
847
+ enabled: true,
848
+ scope: {
849
+ projectId: options.slug,
850
+ channel: "live"
851
+ },
852
+ sender: {
853
+ kind: "monk",
854
+ email: options.email ?? "dev@localhost"
855
+ },
856
+ redactEmails: false,
857
+ isRuntime: true,
858
+ owner: "local"
859
+ } }));
811
860
  app.get("/data/v1/sdk.js", () => {
812
861
  return new Response(readFileSync(resolveSdkBundle(), "utf8"), { headers: {
813
862
  "content-type": "text/javascript",
@@ -816,7 +865,10 @@ async function createSandbox(options) {
816
865
  });
817
866
  app.get("/data/v1/platform", (c) => c.json({
818
867
  dialect: dialect.name,
819
- capabilities: { fts: dialect.fts }
868
+ capabilities: {
869
+ fts: dialect.fts,
870
+ realtime: true
871
+ }
820
872
  }));
821
873
  app.get("/data/v1/auth/me", (c) => c.json({
822
874
  id: "local-dev",
@@ -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-DR-siu2S.mjs";
2
+ import { n as openBrowser, r as reportErrors } from "./ui-CeupNHNa.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-DR-siu2S.mjs";
2
+ import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-CeupNHNa.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-DR-siu2S.mjs";
2
+ import { s as apiDataRequest } from "./ui-CeupNHNa.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-DR-siu2S.mjs";
2
+ import { n as openBrowser, r as reportErrors } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/open.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.7.27";
2
+ var version = "0.7.29";
3
3
  //#endregion
4
4
  export { version as t };
@@ -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-DR-siu2S.mjs";
2
+ import { a as unwrap, i as requireTty, l as apiRequest, r as reportErrors } from "./ui-CeupNHNa.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-DR-siu2S.mjs";
2
+ import { d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/schedule.ts
5
5
  /**
@@ -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, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-DR-siu2S.mjs";
2
+ import { a as unwrap, d as withContext, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import * as p from "@clack/prompts";
5
5
  //#region src/secrets.ts
@@ -30,7 +30,7 @@ canopy whoami # is there a valid login?
30
30
  npm install -g @mediamonks/canopy-cli
31
31
  ```
32
32
 
33
- (Needs Node.js 20+. If `npm` is missing, tell the user to install Node first.)
33
+ (Needs Node.js 24+. If `npm` is missing, tell the user to install Node first.)
34
34
 
35
35
  - **`canopy whoami` says not logged in / expired** → run:
36
36
  ```bash
@@ -1,5 +1,5 @@
1
- import { t as version } from "./package-CsmBH_fN.mjs";
2
- import { r as reportErrors } from "./ui-DR-siu2S.mjs";
1
+ import { t as version } from "./package-DzU_2IJi.mjs";
2
+ import { r as reportErrors } from "./ui-CeupNHNa.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";
@@ -581,6 +581,243 @@ function dbRouter(config) {
581
581
  });
582
582
  return db;
583
583
  }
584
+ /** Serialized JSON bytes of one message's `data`. */
585
+ const REALTIME_MAX_DATA_BYTES = 4096;
586
+ /** Reserved prefix: topics only the project's backend may publish to. */
587
+ const WORKER_TOPIC_PREFIX = "worker:";
588
+ /** How often an idle stream sends a comment so proxies keep it open. */
589
+ const REALTIME_KEEPALIVE_MS = 25e3;
590
+ /**
591
+ * A stream whose reader is this far behind is closed rather than buffered
592
+ * without bound (a stalled or deliberately slow client). The SDK reconnects
593
+ * and re-syncs, exactly as after any other close.
594
+ */
595
+ const REALTIME_MAX_BUFFER_BYTES = 256 * 1024;
596
+ /** Every stream closes between these two ages, so auth is re-checked on reconnect. */
597
+ const REALTIME_STREAM_MIN_MS = 13 * 6e4;
598
+ const REALTIME_STREAM_MAX_MS = 15 * 6e4;
599
+ /** 1-64 of [A-Za-z0-9_.-], not dots only (`.`/`..` would be eaten by URL path normalization). */
600
+ const TOPIC_BODY = /^(?!\.+$)[A-Za-z0-9_.-]{1,64}$/;
601
+ function validateTopic(name) {
602
+ const body = name.startsWith("worker:") ? name.slice(7) : name;
603
+ return TOPIC_BODY.test(body);
604
+ }
605
+ const isWorkerTopic = (name) => name.startsWith(WORKER_TOPIC_PREFIX);
606
+ /** A subscription's topics: valid, de-duplicated, 1 to REALTIME_MAX_TOPICS. */
607
+ function parseTopicList(raw) {
608
+ const topics = [...new Set(raw)];
609
+ if (topics.length === 0) return {
610
+ ok: false,
611
+ error: "invalid_topic",
612
+ detail: "subscribe to at least one topic"
613
+ };
614
+ const bad = topics.find((t) => !validateTopic(t));
615
+ if (bad !== void 0) return {
616
+ ok: false,
617
+ error: "invalid_topic",
618
+ detail: topicDetail(bad)
619
+ };
620
+ if (topics.length > 20) return {
621
+ ok: false,
622
+ error: "too_many_topics",
623
+ detail: `one stream carries at most 20 topics`
624
+ };
625
+ return {
626
+ ok: true,
627
+ topics
628
+ };
629
+ }
630
+ function topicDetail(name) {
631
+ return `"${name.slice(0, 80)}" is not a topic name: use 1-64 letters, digits, "-", "_" or ".", optionally after "${WORKER_TOPIC_PREFIX}"`;
632
+ }
633
+ /** Time-ordered, unguessable enough to be a message id, not a credential. */
634
+ function newMessageId(now = Date.now()) {
635
+ const random = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
636
+ let hex = "";
637
+ for (const byte of random) hex += byte.toString(16).padStart(2, "0");
638
+ return `${now.toString(36).padStart(9, "0")}${hex}`;
639
+ }
640
+ function stampFrom(sender) {
641
+ return sender.kind === "monk" && sender.email ? {
642
+ kind: "monk",
643
+ email: sender.email
644
+ } : { kind: sender.kind };
645
+ }
646
+ function refusalJson(c, refusal) {
647
+ return c.json({
648
+ error: refusal.error,
649
+ detail: refusal.detail
650
+ }, refusal.status);
651
+ }
652
+ function gate(c, ctx) {
653
+ if (!ctx.broker) return c.json({
654
+ error: "not_supported_on_target",
655
+ capability: "realtime",
656
+ detail: "realtime is not available on this Canopy site"
657
+ }, 501);
658
+ if (!ctx.enabled) return c.json({
659
+ error: "realtime_not_enabled",
660
+ detail: "realtime is turned off for this project — a Canopy admin can turn it on for you"
661
+ }, 403);
662
+ }
663
+ const encoder = new TextEncoder();
664
+ const frame = (event, data) => encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
665
+ const KEEPALIVE = encoder.encode(": keep-alive\n\n");
666
+ function realtimeRouter(config) {
667
+ const realtime = new Hono();
668
+ const random = config.random ?? Math.random;
669
+ realtime.get("/realtime", async (c) => {
670
+ if (c.req.method !== "GET") return c.json({
671
+ error: "method_not_allowed",
672
+ detail: "subscribe with GET"
673
+ }, 405);
674
+ const ctx = await config.context(c);
675
+ if (ctx instanceof Response) return ctx;
676
+ const refused = gate(c, ctx);
677
+ if (refused) return refused;
678
+ const list = parseTopicList(c.req.queries("topic") ?? []);
679
+ if (!list.ok) return c.json({
680
+ error: list.error,
681
+ detail: list.detail
682
+ }, 400);
683
+ const broker = ctx.broker;
684
+ let controller;
685
+ let handle;
686
+ let ended = false;
687
+ let keepAlive;
688
+ let lifetime;
689
+ const send = (bytes) => {
690
+ if (ended) return;
691
+ try {
692
+ controller.enqueue(bytes);
693
+ } catch {
694
+ end(false);
695
+ return;
696
+ }
697
+ if ((controller.desiredSize ?? 0) < 0) {
698
+ end(false);
699
+ try {
700
+ controller.error(/* @__PURE__ */ new Error("realtime stream reader fell behind"));
701
+ } catch {}
702
+ }
703
+ };
704
+ const end = (closeStream) => {
705
+ if (ended) return;
706
+ ended = true;
707
+ clearInterval(keepAlive);
708
+ clearTimeout(lifetime);
709
+ handle?.close();
710
+ if (closeStream) try {
711
+ controller.close();
712
+ } catch {}
713
+ };
714
+ const stream = new ReadableStream({
715
+ start(ctrl) {
716
+ controller = ctrl;
717
+ },
718
+ cancel() {
719
+ end(false);
720
+ }
721
+ }, new ByteLengthQueuingStrategy({ highWaterMark: REALTIME_MAX_BUFFER_BYTES }));
722
+ const sink = {
723
+ deliver(envelope) {
724
+ const out = ctx.redactEmails && envelope.from.email !== void 0 ? {
725
+ ...envelope,
726
+ from: { kind: envelope.from.kind }
727
+ } : envelope;
728
+ send(frame("message", out));
729
+ },
730
+ close() {
731
+ end(true);
732
+ }
733
+ };
734
+ send(frame("open", { topics: list.topics }));
735
+ const signal = c.req.raw.signal;
736
+ signal?.addEventListener("abort", () => end(false), { once: true });
737
+ let subscribed;
738
+ try {
739
+ subscribed = await broker.subscribe(ctx.scope, list.topics, sink, ctx.owner);
740
+ } catch (error) {
741
+ config.onError?.(c, error);
742
+ ended = true;
743
+ return c.json({
744
+ error: "realtime_unavailable",
745
+ detail: "realtime is briefly unavailable, retry shortly"
746
+ }, 503);
747
+ }
748
+ if ("refused" in subscribed) {
749
+ ended = true;
750
+ return refusalJson(c, subscribed);
751
+ }
752
+ handle = subscribed;
753
+ if (ended || signal?.aborted) {
754
+ end(false);
755
+ handle.close();
756
+ } else {
757
+ keepAlive = setInterval(() => send(KEEPALIVE), REALTIME_KEEPALIVE_MS);
758
+ const span = REALTIME_STREAM_MAX_MS - REALTIME_STREAM_MIN_MS;
759
+ lifetime = setTimeout(() => end(true), REALTIME_STREAM_MIN_MS + Math.floor(random() * span));
760
+ }
761
+ return new Response(stream, {
762
+ status: 200,
763
+ headers: {
764
+ "content-type": "text/event-stream; charset=utf-8",
765
+ "cache-control": "private, no-store, no-transform",
766
+ "x-accel-buffering": "no"
767
+ }
768
+ });
769
+ });
770
+ realtime.post("/realtime/:topic", async (c) => {
771
+ const ctx = await config.context(c);
772
+ if (ctx instanceof Response) return ctx;
773
+ const refused = gate(c, ctx);
774
+ if (refused) return refused;
775
+ const topic = c.req.param("topic");
776
+ if (!validateTopic(topic)) return c.json({
777
+ error: "invalid_topic",
778
+ detail: topicDetail(topic)
779
+ }, 400);
780
+ if (!ctx.isRuntime && isWorkerTopic(topic)) return c.json({
781
+ error: "topic_worker_only",
782
+ detail: `only the project's backend can publish to "${topic}" — pages can subscribe to it, but publish from a worker route instead`
783
+ }, 403);
784
+ const body = await c.req.json().catch(() => void 0);
785
+ if (typeof body !== "object" || body === null || Array.isArray(body) || body.data === void 0) return c.json({
786
+ error: "invalid_message",
787
+ detail: "send a JSON body of the form { \"data\": … }"
788
+ }, 400);
789
+ const data = body.data;
790
+ const bytes = encoder.encode(JSON.stringify(data)).byteLength;
791
+ if (bytes > 4096) return c.json({
792
+ error: "message_too_large",
793
+ detail: `message data is ${bytes} bytes; the limit is ${REALTIME_MAX_DATA_BYTES}. Send an id and load the rest from the database`
794
+ }, 413);
795
+ const envelope = {
796
+ id: newMessageId(),
797
+ topic,
798
+ data,
799
+ from: stampFrom(ctx.sender),
800
+ ts: (/* @__PURE__ */ new Date()).toISOString()
801
+ };
802
+ let refusal;
803
+ try {
804
+ refusal = await ctx.broker.publish(ctx.scope, envelope, {
805
+ bytes,
806
+ dailyQuota: ctx.dailyQuota,
807
+ dailyBytesQuota: ctx.dailyBytesQuota
808
+ });
809
+ } catch (error) {
810
+ config.onError?.(c, error);
811
+ return c.json({
812
+ error: "realtime_unavailable",
813
+ detail: "realtime is briefly unavailable, retry shortly"
814
+ }, 503);
815
+ }
816
+ if (refusal) return refusalJson(c, refusal);
817
+ return c.json({ id: envelope.id }, 202);
818
+ });
819
+ return realtime;
820
+ }
584
821
  //#endregion
585
822
  //#region ../data-plane/src/worker-request.ts
586
823
  /**
@@ -652,4 +889,4 @@ function stampWorkerHeaders(headers, ctx) {
652
889
  return headers;
653
890
  }
654
891
  //#endregion
655
- export { blobRouter as a, META_COLUMNS as i, stampWorkerHeaders as n, dbRouter as r, redactCanopyCredentials as t };
892
+ export { META_COLUMNS as a, dbRouter as i, stampWorkerHeaders as n, blobRouter as o, realtimeRouter as r, redactCanopyCredentials as t };
@@ -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-DR-siu2S.mjs";
2
+ import { a as unwrap, d as withContext, i as requireTty, l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import * as p from "@clack/prompts";
5
5
  //#region src/staging.ts
@@ -18,10 +18,10 @@ jobs:
18
18
  env:
19
19
  CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
20
  steps:
21
- - uses: actions/checkout@v4
22
- - uses: actions/setup-node@v4
21
+ - uses: actions/checkout@v7
22
+ - uses: actions/setup-node@v7
23
23
  with:
24
- node-version: 22
24
+ node-version: 24
25
25
  - name: Install dependencies
26
26
  if: env.CANOPY_DEPLOY_TOKEN != ''
27
27
  run: |
@@ -18,10 +18,10 @@ jobs:
18
18
  env:
19
19
  CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
20
  steps:
21
- - uses: actions/checkout@v4
22
- - uses: actions/setup-node@v4
21
+ - uses: actions/checkout@v7
22
+ - uses: actions/setup-node@v7
23
23
  with:
24
- node-version: 22
24
+ node-version: 24
25
25
  - name: Install dependencies
26
26
  if: env.CANOPY_DEPLOY_TOKEN != ''
27
27
  run: |
@@ -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-DR-siu2S.mjs";
2
+ import { l as apiRequest, r as reportErrors, t as formatDateTime } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  const tokenCommand = defineCommand({
5
5
  meta: {
@@ -38,6 +38,10 @@ const ERROR_COPY = {
38
38
  why: "the project is paused",
39
39
  fix: "Resume it from the dashboard, then retry."
40
40
  },
41
+ project_moved: {
42
+ why: "the project was marked as moved to another URL, so its hostnames only redirect",
43
+ fix: "Deploy to the project's new home instead, or undo the move under Settings in the dashboard and retry."
44
+ },
41
45
  no_database: {
42
46
  why: "the project has no database yet",
43
47
  fix: "Deploy the project once (`canopy deploy`) to provision its database, then retry."
@@ -1,5 +1,5 @@
1
1
  import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
2
- import { r as reportErrors } from "./ui-DR-siu2S.mjs";
2
+ import { r as reportErrors } from "./ui-CeupNHNa.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-DX18WOap.mjs");
115
+ const { skillInstalled } = await import("./skill-CQ4a__Ga.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-DR-siu2S.mjs";
2
+ import { d as withContext, l as apiRequest, r as reportErrors } from "./ui-CeupNHNa.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  //#region src/usage.ts
5
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.27",
3
+ "version": "0.7.29",
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.7",
23
+ "@mediamonks/canopy": "0.0.9",
24
24
  "@mediamonks/canopy-data-plane": "0.0.4"
25
25
  },
26
26
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- import { t as devCommand } from "./dev-BvjbJcse.mjs";
2
- export { devCommand };