@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.
- package/dist/{db-DVMwCLlU.mjs → db-KTLn8J1u.mjs} +2 -2
- package/dist/{deploy-CTMcwHhI.mjs → deploy-CBf44bvG.mjs} +2 -2
- package/dist/dev-DGCu9MjB.mjs +2 -0
- package/dist/{dev-BvjbJcse.mjs → dev-JlzMZJow.mjs} +50 -21
- package/dist/{dev-worker-BerdPJ_U.mjs → dev-worker-DjJGSUGj.mjs} +1 -1
- package/dist/docs/cli-db.md +2 -0
- package/dist/docs/cli-index.md +1 -1
- package/dist/docs/getting-started.md +1 -1
- package/dist/docs/index.json +12 -2
- package/dist/docs/platform-egress.md +25 -0
- package/dist/docs/platform-members-and-access.md +13 -4
- package/dist/docs/platform-slack.md +6 -0
- package/dist/docs/sdk-notify.md +140 -0
- package/dist/docs/sdk-realtime.md +149 -0
- package/dist/docs/sdk-slack.md +3 -0
- package/dist/docs/sdk.md +5 -4
- package/dist/docs/static-serving.md +19 -0
- package/dist/{docs-CuS9nIXm.mjs → docs-ZQnhFSKT.mjs} +2 -2
- package/dist/{egress-Cn-P46O3.mjs → egress-Cn9zDfDT.mjs} +1 -1
- package/dist/{feedback-DormOsQa.mjs → feedback-CNfUjjNw.mjs} +2 -2
- package/dist/index.mjs +26 -26
- package/dist/{local-laAZEDVJ.mjs → local-BA_TPWpN.mjs} +56 -4
- package/dist/{login-C4MDP5NM.mjs → login-MX0xq8UE.mjs} +1 -1
- package/dist/{logs-CfDINw7U.mjs → logs-D5RWqsWl.mjs} +1 -1
- package/dist/{migrations-B9fXxMDj.mjs → migrations-C8yRPJIX.mjs} +1 -1
- package/dist/{open-rS1ARzOy.mjs → open-BJgd0R61.mjs} +1 -1
- package/dist/{package-CsmBH_fN.mjs → package-DzU_2IJi.mjs} +1 -1
- package/dist/{projects-Dmm_976k.mjs → projects-BG7Injpb.mjs} +1 -1
- package/dist/{schedule-Buu71RWQ.mjs → schedule-CLuksxSM.mjs} +1 -1
- package/dist/{secrets-BD3DaTih.mjs → secrets-Bl3uOx4l.mjs} +1 -1
- package/dist/skill/canopy/SKILL.md +1 -1
- package/dist/{skill-DX18WOap.mjs → skill-CQ4a__Ga.mjs} +2 -2
- package/dist/{src-CwSKZwNK.mjs → src-C7zPhbxi.mjs} +238 -1
- package/dist/{staging-CKEoDnac.mjs → staging-D06zobnf.mjs} +1 -1
- package/dist/templates/spa/.github/workflows/canopy-deploy.yml +3 -3
- package/dist/templates/worker/.github/workflows/canopy-deploy.yml +3 -3
- package/dist/{token-Dp2CoIYj.mjs → token-C_PFNqcv.mjs} +1 -1
- package/dist/{ui-DR-siu2S.mjs → ui-CeupNHNa.mjs} +4 -0
- package/dist/{upgrade-PfdH0NPe.mjs → upgrade-BzmIrnjs.mjs} +2 -2
- package/dist/{usage-81JSiWIx.mjs → usage-CV7TjTO8.mjs} +1 -1
- package/package.json +2 -2
- package/dist/dev-BOvMCaML.mjs +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
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-
|
|
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-
|
|
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-
|
|
64
|
-
whoami: () => import("./login-
|
|
65
|
-
logout: () => import("./login-
|
|
66
|
-
create: () => import("./projects-
|
|
67
|
-
init: () => import("./projects-
|
|
68
|
-
projects: () => import("./projects-
|
|
69
|
-
delete: () => import("./projects-
|
|
70
|
-
dev: () => import("./dev-
|
|
71
|
-
deploy: () => import("./deploy-
|
|
72
|
-
promote: () => import("./deploy-
|
|
73
|
-
staging: () => import("./staging-
|
|
74
|
-
rollback: () => import("./deploy-
|
|
75
|
-
download: () => import("./deploy-
|
|
76
|
-
token: () => import("./token-
|
|
77
|
-
egress: () => import("./egress-
|
|
78
|
-
secrets: () => import("./secrets-
|
|
79
|
-
schedule: () => import("./schedule-
|
|
80
|
-
feedback: () => import("./feedback-
|
|
81
|
-
db: () => import("./db-
|
|
82
|
-
usage: () => import("./usage-
|
|
83
|
-
open: () => import("./open-
|
|
84
|
-
docs: () => import("./docs-
|
|
85
|
-
skill: () => import("./skill-
|
|
86
|
-
upgrade: () => import("./upgrade-
|
|
87
|
-
logs: () => import("./logs-
|
|
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-
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
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: {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
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 {
|
|
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-
|
|
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@
|
|
22
|
-
- uses: actions/setup-node@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-node@v7
|
|
23
23
|
with:
|
|
24
|
-
node-version:
|
|
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@
|
|
22
|
-
- uses: actions/setup-node@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-node@v7
|
|
23
23
|
with:
|
|
24
|
-
node-version:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
23
|
+
"@mediamonks/canopy": "0.0.9",
|
|
24
24
|
"@mediamonks/canopy-data-plane": "0.0.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/dist/dev-BOvMCaML.mjs
DELETED