@mediamonks/canopy-cli 0.4.4 → 0.5.0

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 (40) hide show
  1. package/README.md +43 -0
  2. package/dist/{db-Bkq-_qkd.mjs → db-BW1ZolWn.mjs} +9 -12
  3. package/dist/{deploy-BO_-gK32.mjs → deploy-alC2--pZ.mjs} +92 -13
  4. package/dist/dev-BJ5xEwqW.mjs +2 -0
  5. package/dist/{dev-pPVCUaFp.mjs → dev-CQQhMOoD.mjs} +37 -29
  6. package/dist/docs/ci.md +6 -2
  7. package/dist/docs/cli.md +8 -3
  8. package/dist/docs/data.md +22 -4
  9. package/dist/docs/deploy.md +10 -6
  10. package/dist/docs/dev.md +10 -3
  11. package/dist/docs/infrastructure.md +397 -0
  12. package/dist/docs/static-serving.md +318 -0
  13. package/dist/docs/troubleshooting.md +14 -0
  14. package/dist/{docs-C3aOAx_A.mjs → docs-DvSPpSIR.mjs} +8 -8
  15. package/dist/index.mjs +53 -19
  16. package/dist/local-C5-GWJcx.mjs +826 -0
  17. package/dist/login-Bz-rwlFF.mjs +264 -0
  18. package/dist/{open-xLiGUz_W.mjs → open-DnlYDqrU.mjs} +2 -2
  19. package/dist/{package-BHlmIdUm.mjs → package-Bi5FfVSP.mjs} +1 -1
  20. package/dist/{projects-oyjC4ZGb.mjs → projects-ijfe7Ffx.mjs} +21 -4
  21. package/dist/site-BJjoePQS.mjs +254 -0
  22. package/dist/skill/canopy/SKILL.md +1 -1
  23. package/dist/{skill-BsZKAgGI.mjs → skill-IAAa2EkK.mjs} +2 -2
  24. package/dist/templates/static/AGENTS.md +1 -1
  25. package/dist/templates/static/main.js +1 -1
  26. package/dist/templates/worker/AGENTS.md +11 -9
  27. package/dist/templates/worker/worker/index.ts +27 -4
  28. package/dist/{token-BXc2klNm.mjs → token-BeF9zczg.mjs} +2 -3
  29. package/dist/ui-DThlyHgo.mjs +300 -0
  30. package/dist/{upgrade-nYLjXxPk.mjs → upgrade-DNxR-zh7.mjs} +3 -3
  31. package/dist/usage-DQpH7A3X.mjs +79 -0
  32. package/package.json +5 -4
  33. package/dist/api-BcX_w6Xf.mjs +0 -58
  34. package/dist/config-C-z7st1X.mjs +0 -33
  35. package/dist/dev-R6nzDtA-.mjs +0 -2
  36. package/dist/local-MLfuFVZb.mjs +0 -563
  37. package/dist/login-Dif9Y32q.mjs +0 -174
  38. package/dist/project-CpiPLNby.mjs +0 -54
  39. package/dist/ui-C8VuPzGp.mjs +0 -40
  40. package/dist/{version-check-CoWgQs2h.mjs → version-check-Cuh6HaLX.mjs} +1 -1
@@ -1,174 +0,0 @@
1
- import { n as writeConfig, t as readConfig } from "./config-C-z7st1X.mjs";
2
- import { n as reportErrors, t as openBrowser } from "./ui-C8VuPzGp.mjs";
3
- import { defineCommand } from "citty";
4
- import * as p from "@clack/prompts";
5
- import { createHash, randomBytes } from "node:crypto";
6
- import { createServer } from "node:http";
7
- //#region src/login.ts
8
- /**
9
- * canopy login | whoami | logout — browser PKCE flow against OneLogin (public
10
- * client), then trade the ID token for a platform CLI token via POST
11
- * /v1/auth/cli-token. See docs/03-architecture.md "CLI auth".
12
- */
13
- const CALLBACK_PORT = 8976;
14
- const REDIRECT_URI = `http://127.0.0.1:${CALLBACK_PORT}/callback`;
15
- const LOGIN_TIMEOUT_MS = 300 * 1e3;
16
- const base64url = (buf) => buf.toString("base64url");
17
- const whoamiCommand = defineCommand({
18
- meta: {
19
- name: "whoami",
20
- description: "Show the current Canopy login"
21
- },
22
- run: reportErrors("whoami", async () => {
23
- const config = readConfig();
24
- if (!config.token) throw new Error("Not logged in — run `canopy login`.");
25
- console.log(`${config.email ?? "<unknown email>"} — ${config.apiUrl}`);
26
- if (config.tokenExpiresAt !== void 0) {
27
- const expired = Date.parse(config.tokenExpiresAt) < Date.now();
28
- console.log(expired ? `Token EXPIRED ${config.tokenExpiresAt} — run \`canopy login\` again.` : `Token expires ${config.tokenExpiresAt}.`);
29
- }
30
- })
31
- });
32
- const logoutCommand = defineCommand({
33
- meta: {
34
- name: "logout",
35
- description: "Sign out: forget the local CLI token"
36
- },
37
- run: reportErrors("logout", async () => {
38
- const config = readConfig();
39
- if (!config.token) {
40
- console.log("Not logged in.");
41
- return;
42
- }
43
- const next = { ...config };
44
- delete next.token;
45
- delete next.tokenExpiresAt;
46
- delete next.email;
47
- writeConfig(next);
48
- console.log(`Logged out${config.email ? ` (${config.email})` : ""} — token removed from ~/.canopy/config.json.`);
49
- })
50
- });
51
- /** Wait for OneLogin to redirect back to the local loopback server. */
52
- function waitForCallback(expectedState) {
53
- return new Promise((resolve, reject) => {
54
- const server = createServer((req, res) => {
55
- const url = new URL(req.url ?? "/", REDIRECT_URI);
56
- if (url.pathname !== "/callback") {
57
- res.writeHead(404).end();
58
- return;
59
- }
60
- const code = url.searchParams.get("code");
61
- const state = url.searchParams.get("state");
62
- const ok = Boolean(code) && state === expectedState;
63
- res.writeHead(ok ? 200 : 400, { "content-type": "text/html" }).end(ok ? "<h1>Logged in</h1><p>You can close this tab and return to the terminal.</p>" : "<h1>Login failed</h1><p>Return to the terminal and try again.</p>");
64
- server.close();
65
- clearTimeout(timer);
66
- if (ok) resolve(code);
67
- else reject(new Error(url.searchParams.get("error_description") ?? "Login rejected"));
68
- });
69
- const timer = setTimeout(() => {
70
- server.close();
71
- reject(/* @__PURE__ */ new Error("Timed out waiting for the browser login (5 minutes)."));
72
- }, LOGIN_TIMEOUT_MS);
73
- server.on("error", (err) => reject(err.message.includes("EADDRINUSE") ? /* @__PURE__ */ new Error(`Port ${CALLBACK_PORT} is in use — close the other process and retry.`) : err));
74
- server.listen(CALLBACK_PORT, "127.0.0.1");
75
- });
76
- }
77
- const loginCommand = defineCommand({
78
- meta: {
79
- name: "login",
80
- description: "Sign in to Canopy via OneLogin"
81
- },
82
- args: { json: {
83
- type: "boolean",
84
- default: false,
85
- description: "Emit machine-readable status to stdout (for agents/CI)"
86
- } },
87
- run: reportErrors("login", async ({ args }) => {
88
- const json = args.json;
89
- try {
90
- await login(json);
91
- } catch (err) {
92
- if (json) console.log(JSON.stringify({
93
- status: "error",
94
- message: err instanceof Error ? err.message : String(err)
95
- }));
96
- throw err;
97
- }
98
- })
99
- });
100
- async function login(json) {
101
- if (!json) p.intro("canopy login");
102
- const config = readConfig();
103
- const { issuer, cliClientId, apiUrl } = config;
104
- if (cliClientId.includes("REPLACE")) throw new Error("The CLI's OneLogin app is not configured yet (set CANOPY_OIDC_CLIENT_ID or packages/cli/src/config.ts).");
105
- const discoveryRes = await fetch(`${issuer}/.well-known/openid-configuration`);
106
- if (!discoveryRes.ok) throw new Error(`OIDC discovery failed: ${discoveryRes.status}`);
107
- const discovery = await discoveryRes.json();
108
- const state = base64url(randomBytes(32));
109
- const codeVerifier = base64url(randomBytes(32));
110
- const authorizeUrl = new URL(discovery.authorization_endpoint);
111
- authorizeUrl.search = new URLSearchParams({
112
- response_type: "code",
113
- client_id: cliClientId,
114
- redirect_uri: REDIRECT_URI,
115
- scope: "openid profile email",
116
- state,
117
- code_challenge: base64url(createHash("sha256").update(codeVerifier).digest()),
118
- code_challenge_method: "S256"
119
- }).toString();
120
- const authorizeHref = authorizeUrl.toString();
121
- openBrowser(authorizeHref);
122
- const spin = json ? void 0 : p.spinner();
123
- if (json) console.log(JSON.stringify({
124
- status: "waiting",
125
- url: authorizeHref
126
- }));
127
- else {
128
- p.note(`If nothing opens, visit:\n${authorizeHref}`, "Opening OneLogin in your browser");
129
- spin?.start("Waiting for you to sign in in the browser…");
130
- }
131
- let code;
132
- try {
133
- code = await waitForCallback(state);
134
- } catch (err) {
135
- spin?.error("Sign-in failed.");
136
- throw err;
137
- }
138
- spin?.stop("Signed in with OneLogin.");
139
- const tokenRes = await fetch(discovery.token_endpoint, {
140
- method: "POST",
141
- headers: { "content-type": "application/x-www-form-urlencoded" },
142
- body: new URLSearchParams({
143
- grant_type: "authorization_code",
144
- client_id: cliClientId,
145
- code,
146
- redirect_uri: REDIRECT_URI,
147
- code_verifier: codeVerifier
148
- })
149
- });
150
- if (!tokenRes.ok) throw new Error(`Token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`);
151
- const { id_token: idToken } = await tokenRes.json();
152
- if (!idToken) throw new Error("OneLogin returned no id_token.");
153
- const cliTokenRes = await fetch(`${apiUrl}/v1/auth/cli-token`, {
154
- method: "POST",
155
- headers: { "content-type": "application/json" },
156
- body: JSON.stringify({ id_token: idToken })
157
- });
158
- if (!cliTokenRes.ok) throw new Error(`Canopy rejected the login: ${cliTokenRes.status} ${await cliTokenRes.text()}`);
159
- const { token, expires_at, user } = await cliTokenRes.json();
160
- writeConfig({
161
- ...config,
162
- token,
163
- tokenExpiresAt: expires_at,
164
- email: user.email
165
- });
166
- if (json) console.log(JSON.stringify({
167
- status: "ok",
168
- email: user.email,
169
- expiresAt: expires_at
170
- }));
171
- else p.outro(`Logged in as ${user.name} <${user.email}> (token expires ${expires_at}).`);
172
- }
173
- //#endregion
174
- export { loginCommand, logoutCommand, whoamiCommand };
@@ -1,54 +0,0 @@
1
- import { t as readConfig } from "./config-C-z7st1X.mjs";
2
- import { readFileSync } from "node:fs";
3
- import { join, resolve } from "node:path";
4
- //#region src/project.ts
5
- /**
6
- * Which project is a command about? Every project-scoped command resolves it
7
- * the same way: `--project <slug>` wins, otherwise canopy.json in the target
8
- * directory (the optional [dir] positional, defaulting to the cwd).
9
- */
10
- /** The wildcard apex projects are served under, derived from the API origin's registrable domain. */
11
- function projectUrl(slug) {
12
- const apiHost = new URL(readConfig().apiUrl).hostname;
13
- return `https://${slug}.${apiHost.endsWith("workers.dev") ? "thanarie.dev" : apiHost.split(".").slice(-2).join(".")}`;
14
- }
15
- /** canopy.json parsed if present; undefined when the file is missing. */
16
- function tryReadCanopyJson(dir) {
17
- let raw;
18
- try {
19
- raw = readFileSync(join(dir, "canopy.json"), "utf8");
20
- } catch {
21
- return;
22
- }
23
- return JSON.parse(raw);
24
- }
25
- function readCanopyJson(dir) {
26
- const config = tryReadCanopyJson(dir);
27
- if (config === void 0) throw new Error(`No canopy.json in ${dir} — run from a project created by \`canopy create\`, add one ({"slug": "<slug>", "dist": "dist"}), or pass --project <slug>.`);
28
- if (!config.slug) throw new Error("canopy.json is missing \"slug\".");
29
- return config;
30
- }
31
- /** For commands scoped to a project but not to a directory (db, rollback). */
32
- const projectFlagArgs = { project: {
33
- type: "string",
34
- description: "Project slug (overrides canopy.json in the target directory)"
35
- } };
36
- /** For commands that operate on a project directory (dev, deploy, open). */
37
- const projectDirArgs = {
38
- dir: {
39
- type: "positional",
40
- description: "Project directory (defaults to the current directory)",
41
- required: false
42
- },
43
- ...projectFlagArgs
44
- };
45
- /** Resolve the target directory and project slug from the shared args. */
46
- function resolveProject(args) {
47
- const dir = resolve(args.dir ?? ".");
48
- return {
49
- dir,
50
- slug: args.project ?? readCanopyJson(dir).slug
51
- };
52
- }
53
- //#endregion
54
- export { tryReadCanopyJson as a, resolveProject as i, projectFlagArgs as n, projectUrl as r, projectDirArgs as t };
@@ -1,40 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { cancel, isCancel } from "@clack/prompts";
3
- //#region src/ui.ts
4
- /**
5
- * Shared terminal UX plumbing: clack prompt unwrapping and uniform
6
- * command error reporting.
7
- */
8
- /** Unwrap a clack prompt result, exiting cleanly when the user cancels (ctrl-c). */
9
- function unwrap(result) {
10
- if (isCancel(result)) {
11
- cancel("Aborted.");
12
- process.exit(130);
13
- }
14
- return result;
15
- }
16
- /** Wrap a command action so failures print as `canopy <name>: <message>` and exit 1. */
17
- function reportErrors(name, action) {
18
- return async (...args) => {
19
- try {
20
- await action(...args);
21
- } catch (err) {
22
- console.error(`canopy ${name}: ${err instanceof Error ? err.message : String(err)}`);
23
- process.exit(1);
24
- }
25
- };
26
- }
27
- /** Guard interactive prompts: bail with a actionable message on non-TTY runs. */
28
- function requireTty(hint) {
29
- if (!process.stdin.isTTY) throw new Error(`This needs an interactive terminal — ${hint}`);
30
- }
31
- /** Open a URL in the default browser, fire-and-forget. */
32
- function openBrowser(url) {
33
- spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open", [url], {
34
- stdio: "ignore",
35
- detached: true,
36
- shell: process.platform === "win32"
37
- }).on("error", () => {}).unref();
38
- }
39
- //#endregion
40
- export { unwrap as i, reportErrors as n, requireTty as r, openBrowser as t };
@@ -1,7 +1,7 @@
1
- import { spawn } from "node:child_process";
2
1
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
2
  import { homedir } from "node:os";
4
3
  import { join } from "node:path";
4
+ import { spawn } from "node:child_process";
5
5
  //#region src/version-check.ts
6
6
  /**
7
7
  * Best-effort "you're behind" notice for the CLI.