@mediamonks/canopy-cli 0.7.45 → 0.7.46

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 (52) hide show
  1. package/dist/{ai-FBHTSCLZ.mjs → ai-CWPQiUft.mjs} +2 -2
  2. package/dist/{api-Bl6SG8Oq.mjs → api-DmJrIsE7.mjs} +134 -19
  3. package/dist/{browse-B8-0dweq.mjs → browse-BPLWhoFI.mjs} +2 -2
  4. package/dist/{db-BJJGydgL.mjs → db-DpCMc0uz.mjs} +4 -4
  5. package/dist/{deploy-ExrIeX_m.mjs → deploy-BnRQusjJ.mjs} +51 -5
  6. package/dist/{dev-D-vwE8G-.mjs → dev-BmKeIdMi.mjs} +7 -7
  7. package/dist/dev-BvVlq9np.mjs +2 -0
  8. package/dist/docs/ci.md +112 -50
  9. package/dist/docs/cli-create.md +2 -0
  10. package/dist/docs/cli-deploy.md +1 -1
  11. package/dist/docs/cli-repo.md +34 -2
  12. package/dist/docs/cli-token.md +8 -6
  13. package/dist/docs/cli.md +6 -2
  14. package/dist/docs/deploy.md +3 -2
  15. package/dist/docs/index.json +3 -3
  16. package/dist/docs/platform-deploys-and-rollbacks.md +4 -4
  17. package/dist/{docs-Cpky2XNP.mjs → docs-CtMrMpt1.mjs} +4 -4
  18. package/dist/{egress-C2amvqmE.mjs → egress-BM4EpLBh.mjs} +3 -3
  19. package/dist/{feedback-CTV6inRg.mjs → feedback-BXhbxyXI.mjs} +5 -5
  20. package/dist/index.mjs +35 -35
  21. package/dist/{local-BThs382Y.mjs → local-C9Bi9f3y.mjs} +2 -2
  22. package/dist/{login-CREcZfLH.mjs → login-2MW9sGJO.mjs} +3 -3
  23. package/dist/{logs-BnrNZvLc.mjs → logs-dZMPTJLu.mjs} +3 -3
  24. package/dist/{mcp-Cy8EwhhD.mjs → mcp-CGmWkGN8.mjs} +3 -3
  25. package/dist/{migrations-D8NqjC5I.mjs → migrations-BReMlGWP.mjs} +2 -2
  26. package/dist/{open-Bpz4C2Sr.mjs → open-DcQSdfx5.mjs} +2 -2
  27. package/dist/{package-Dph4v1Cq.mjs → package-BdNHq8QV.mjs} +1 -1
  28. package/dist/{project-template-Cdr1uwwE.mjs → project-template-BPW_Dgxj.mjs} +3 -3
  29. package/dist/project-template-Dw11151p.mjs +2 -0
  30. package/dist/{projects-BRG2HnBs.mjs → projects-CnU3194_.mjs} +26 -8
  31. package/dist/{rename-BGKa25Rq.mjs → rename-BWvERJgA.mjs} +3 -3
  32. package/dist/{repo-CwT2RDoW.mjs → repo-6a7gRQaf.mjs} +171 -8
  33. package/dist/repo-C7AXDtFZ.mjs +2 -0
  34. package/dist/{roles-C7pq9bIx.mjs → roles-CrYf_8wk.mjs} +1 -1
  35. package/dist/{roles-DAPPXf4U.mjs → roles-S2D9Id0W.mjs} +3 -3
  36. package/dist/{schedule-BcqBspn3.mjs → schedule-DvHSSzfR.mjs} +3 -3
  37. package/dist/{secrets-BjbIi7Gp.mjs → secrets-BIn2RPoe.mjs} +3 -3
  38. package/dist/{site-0i7wAJqg.mjs → site-BL5Ik-yj.mjs} +50 -1
  39. package/dist/{skill-Zj19RNo2.mjs → skill-vzcCD1b4.mjs} +2 -2
  40. package/dist/{staging-_2Js0Fkc.mjs → staging-Dn8kH9cK.mjs} +3 -3
  41. package/dist/templates/spa/.github/workflows/canopy-deploy.yml +18 -12
  42. package/dist/templates/spa/README.md +1 -1
  43. package/dist/templates/worker/.github/workflows/canopy-deploy.yml +18 -12
  44. package/dist/templates/worker/README.md +1 -1
  45. package/dist/{token-7vSNuz-X.mjs → token-DMCj88A-.mjs} +6 -6
  46. package/dist/{ui-CLnICOne.mjs → ui-26ETk1n8.mjs} +1 -1
  47. package/dist/{upgrade-B9tCAbre.mjs → upgrade-8_pFNTo5.mjs} +2 -2
  48. package/dist/{usage-DGGmTwjJ.mjs → usage-Bpz5B1N1.mjs} +3 -3
  49. package/package.json +1 -1
  50. package/dist/dev-CSimnUP3.mjs +0 -2
  51. package/dist/project-template-715H_lnu.mjs +0 -2
  52. package/dist/repo-DmFs6H9m.mjs +0 -2
@@ -1,6 +1,6 @@
1
- import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, r as siteConfig } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, d as noticeRenamesOnly, f as withContext, n as agentHeaders, u as followRename } from "./api-Bl6SG8Oq.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
1
+ import { a as parseGithubRemote, c as projectDirArgs, d as resolveProject, l as projectFlagArgs, m as tryReadCanopyJson, o as parseRepositoryArg, r as siteConfig } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, f as noticeRenamesOnly, m as withContext, n as agentHeaders, u as followRename } from "./api-DmJrIsE7.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
4
4
  import { a as parseCredentialInput, i as helperCommand, o as runCredentialHelper, r as fileTokenCache, t as RepoCredentialError } from "./repo-credential-CNDRM-tG.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  import { existsSync, realpathSync } from "node:fs";
@@ -104,6 +104,13 @@ async function setLocalConfig(dir, key, values) {
104
104
  * - `show` says which repository the project has (`--json` for agents);
105
105
  * - `delete` removes it and its history (owner only).
106
106
  *
107
+ * CI deploys from GitHub Actions (OpenSpec platform-oauth-oidc), owner only:
108
+ *
109
+ * - `link` links a GitHub repository outside Canopy (a Canopy repository needs none);
110
+ * - `unlink` removes that link;
111
+ * - `branches` sets the live and staging branches CI deploys from;
112
+ * - `deploys` turns CI deploys on or off without unlinking.
113
+ *
107
114
  * Setting up a folder points a remote at the repository and registers
108
115
  * `canopy repo credential` as git's credential helper for that one
109
116
  * repository. No token is ever written
@@ -208,16 +215,26 @@ const showCommand = defineCommand({
208
215
  return;
209
216
  }
210
217
  if (repo.kind === "none") console.log(`${slug} has no git repository. Create one with \`canopy repo create\`.`);
211
- else if (repo.kind === "linked") console.log(`${slug} is linked to ${repo.linked_repository}.`);
212
- else {
218
+ else if (repo.kind === "linked") {
219
+ console.log(`${slug} is linked to ${repo.host ? `${repo.host}/` : ""}${repo.linked_repository}.`);
220
+ printCiDeploys(repo);
221
+ } else {
213
222
  console.log(`${slug} has a Canopy git repository.`);
214
223
  console.log(` remote ${repo.remote}`);
215
224
  console.log(` created ${repo.created_at ? formatDateTime(repo.created_at) : "—"}`);
216
225
  console.log(` last push ${repo.last_push_at ? formatDateTime(repo.last_push_at) : "never"}`);
226
+ printCiDeploys(repo);
217
227
  console.log(`\nClone it with \`canopy repo clone ${slug}\`.`);
218
228
  }
219
229
  })
220
230
  });
231
+ /** The CI lines of `repo show`: both branches and whether CI deploys are on. */
232
+ function printCiDeploys(repo) {
233
+ if (repo.deploys_enabled === void 0 || repo.deploys_enabled === null) return;
234
+ console.log(` live ${repo.ref ?? "refs/heads/main"}`);
235
+ console.log(` staging ${repo.staging_ref ?? "—"}`);
236
+ console.log(` CI deploys ${repo.deploys_enabled ? "on" : "off"}`);
237
+ }
221
238
  const createCommand = defineCommand({
222
239
  meta: {
223
240
  name: "create",
@@ -380,13 +397,159 @@ async function readStdin() {
380
397
  for await (const chunk of process.stdin) chunks.push(chunk);
381
398
  return Buffer.concat(chunks).toString("utf8");
382
399
  }
400
+ /** Plain words for the link routes' errors; `invalid_host` here is a GitHub host. */
401
+ const LINK_FIXES = {
402
+ owner_only: "Only the project owner (or a platform admin) can change where CI deploys come from.",
403
+ repo_exists: "This project has a Canopy repository, and CI deploys from it without a link. Set its branches with `canopy repo branches`.",
404
+ invalid_host: "Link a repository on github.com or this site's GitHub Enterprise host (e.g. monks.ghe.com/monks/my-tool).",
405
+ repository_not_linked: "Link a repository first with `canopy repo link <owner/name>`.",
406
+ not_linked: "This project has no linked GitHub repository — `canopy repo show` says what it has."
407
+ };
408
+ /** `main` → `refs/heads/main`; a full ref passes through. */
409
+ function toRef(value) {
410
+ const trimmed = value.trim();
411
+ return trimmed.startsWith("refs/") ? trimmed : `refs/heads/${trimmed}`;
412
+ }
413
+ /** `refs/heads/develop` → `develop`, for the workflow's `on.push.branches`. */
414
+ const branchName = (ref) => ref.replace(/^refs\/heads\//, "");
415
+ /** The GitHub repository the folder's `origin` remote points at, if any. */
416
+ async function githubOrigin(dir) {
417
+ if (!await isWorkingCopy(dir).catch(() => false)) return null;
418
+ const origin = (await remotes(dir).catch(() => /* @__PURE__ */ new Map())).get("origin");
419
+ return origin ? parseGithubRemote(origin) : null;
420
+ }
421
+ function printLink(slug, link) {
422
+ console.log(` repository ${link.host}/${link.repository}`);
423
+ console.log(` live ${link.ref}`);
424
+ console.log(` staging ${link.staging_ref ?? "—"}`);
425
+ console.log(` CI deploys ${link.deploys_enabled ? "on" : "off"}`);
426
+ if (link.staging_ref) p.log.info(`Pushes to ${branchName(link.staging_ref)} deploy ${slug}'s staging channel once the workflow runs on them: add "${branchName(link.staging_ref)}" to on.push.branches in .github/workflows/canopy-deploy.yml.`);
427
+ }
383
428
  const repoCommand = defineCommand({
384
429
  meta: {
385
430
  name: "repo",
386
- description: "The project's git repository (clone, connect, create)"
431
+ description: "The project's git repository (clone, connect, create, link CI deploys)"
387
432
  },
388
433
  subCommands: {
389
434
  show: showCommand,
435
+ link: defineCommand({
436
+ meta: {
437
+ name: "link",
438
+ description: "Link a GitHub repository so its pushes deploy the project (owner only)"
439
+ },
440
+ args: {
441
+ repository: {
442
+ type: "positional",
443
+ description: "[<host>/]<owner/name> — defaults to this folder's origin remote",
444
+ required: false
445
+ },
446
+ ...projectFlagArgs,
447
+ ref: {
448
+ type: "string",
449
+ description: "Live branch (default main)"
450
+ },
451
+ "staging-ref": {
452
+ type: "string",
453
+ description: "Staging branch: its pushes deploy to the staging channel only"
454
+ }
455
+ },
456
+ run: reportErrors("repo link", async ({ args }) => {
457
+ const { dir, slug } = resolveProject({ project: args.project });
458
+ let target;
459
+ if (args.repository) {
460
+ target = parseRepositoryArg(args.repository);
461
+ if (!target) throw new Error(`"${args.repository}" is not a repository — pass <owner/name> or <host>/<owner/name>, e.g. monks/my-tool.`);
462
+ } else {
463
+ target = await githubOrigin(dir);
464
+ if (!target) throw new Error("This folder has no GitHub origin remote to link — pass the repository: `canopy repo link <owner/name>`.");
465
+ }
466
+ const link = await withContext(apiRequest("PUT", `/projects/${slug}/github`, {
467
+ repository: target.repository,
468
+ ...target.host && { host: target.host },
469
+ ...args.ref && { ref: toRef(args.ref) },
470
+ ...args["staging-ref"] && { staging_ref: toRef(args["staging-ref"]) }
471
+ }), {
472
+ action: `link ${slug} to ${target.repository}`,
473
+ fixes: LINK_FIXES
474
+ });
475
+ p.log.success(`Linked ${slug} to ${link.host}/${link.repository} @ ${link.ref}. A push there now deploys ${slug}.`);
476
+ printLink(slug, link);
477
+ })
478
+ }),
479
+ unlink: defineCommand({
480
+ meta: {
481
+ name: "unlink",
482
+ description: "Remove the project's GitHub repository link (owner only)"
483
+ },
484
+ args: { ...projectDirArgs },
485
+ run: reportErrors("repo unlink", async ({ args }) => {
486
+ const { slug } = resolveProject(args);
487
+ await withContext(apiRequest("DELETE", `/projects/${slug}/github`), {
488
+ action: `unlink ${slug}'s repository`,
489
+ fixes: LINK_FIXES
490
+ });
491
+ p.log.success(`Unlinked ${slug}. GitHub Actions runs now skip the deploy.`);
492
+ })
493
+ }),
494
+ branches: defineCommand({
495
+ meta: {
496
+ name: "branches",
497
+ description: "Set the live and staging branches CI deploys from (owner only)"
498
+ },
499
+ args: {
500
+ ...projectDirArgs,
501
+ ref: {
502
+ type: "string",
503
+ description: "Live branch, e.g. main"
504
+ },
505
+ "staging-ref": {
506
+ type: "string",
507
+ description: "Staging branch, e.g. develop (`none` removes it)"
508
+ }
509
+ },
510
+ run: reportErrors("repo branches", async ({ args }) => {
511
+ const { slug } = resolveProject(args);
512
+ const stagingArg = args["staging-ref"];
513
+ if (!args.ref && stagingArg === void 0) throw new Error("Pass --ref and/or --staging-ref (see `canopy repo show` for the current ones).");
514
+ const link = await withContext(apiRequest("PATCH", `/projects/${slug}/github`, {
515
+ ...args.ref && { ref: toRef(args.ref) },
516
+ ...stagingArg !== void 0 && { staging_ref: stagingArg === "none" || stagingArg === "" ? null : toRef(stagingArg) }
517
+ }), {
518
+ action: `set ${slug}'s CI branches`,
519
+ fixes: LINK_FIXES
520
+ });
521
+ p.log.success(`CI deploys for ${slug} come from:`);
522
+ printLink(slug, link);
523
+ })
524
+ }),
525
+ deploys: defineCommand({
526
+ meta: {
527
+ name: "deploys",
528
+ description: "Turn CI deploys from GitHub Actions on or off (owner only)"
529
+ },
530
+ args: {
531
+ state: {
532
+ type: "positional",
533
+ description: "on | off",
534
+ required: true
535
+ },
536
+ ...projectFlagArgs
537
+ },
538
+ run: reportErrors("repo deploys", async ({ args }) => {
539
+ if (args.state !== "on" && args.state !== "off") throw new Error(`Pass on or off (got "${args.state}").`);
540
+ const { slug } = resolveProject({ project: args.project });
541
+ const enabled = args.state === "on";
542
+ await withContext(apiRequest("PATCH", `/projects/${slug}/github`, { deploys_enabled: enabled }), {
543
+ action: `turn CI deploys ${args.state} for ${slug}`,
544
+ fixes: LINK_FIXES
545
+ });
546
+ if (enabled) p.log.success(`CI deploys are on for ${slug}: pushes to its branches deploy again.`);
547
+ else {
548
+ p.log.success(`CI deploys are off for ${slug}: GitHub Actions runs skip the deploy with a warning. The repository link stays.`);
549
+ p.log.info("Deploy tokens are not affected — a workflow with CANOPY_DEPLOY_TOKEN still deploys. Revoke them with `canopy token revoke` to stop those too.");
550
+ }
551
+ })
552
+ }),
390
553
  clone: cloneCommand,
391
554
  connect: connectCommand,
392
555
  create: createCommand,
@@ -411,7 +574,7 @@ async function offerRepoOnCreate(slug, dir, flag) {
411
574
  if (flag === void 0) {
412
575
  if (!process.stdin.isTTY || !await reposAvailable()) return null;
413
576
  const origin = await isWorkingCopy(dir).catch(() => false) ? (await remotes(dir)).get("origin") : void 0;
414
- if (origin && /github\.com[:/]/.test(origin)) return null;
577
+ if (origin && parseGithubRemote(origin)) return null;
415
578
  if (!unwrap(await p.confirm({
416
579
  message: "Create a git repository for this project on Canopy?",
417
580
  initialValue: false
@@ -428,4 +591,4 @@ async function offerRepoOnCreate(slug, dir, flag) {
428
591
  }
429
592
  }
430
593
  //#endregion
431
- export { repoCommand as a, offerRepoOnCreate as i, cloneProjectRepo as n, connectFolder as r, cliInvocation as t };
594
+ export { offerRepoOnCreate as a, githubOrigin as i, cloneProjectRepo as n, repoCommand as o, connectFolder as r, toRef as s, cliInvocation as t };
@@ -0,0 +1,2 @@
1
+ import { o as repoCommand } from "./repo-6a7gRQaf.mjs";
2
+ export { repoCommand };
@@ -1,2 +1,2 @@
1
- import { i as membersCommand, o as rolesCommand } from "./roles-DAPPXf4U.mjs";
1
+ import { i as membersCommand, o as rolesCommand } from "./roles-S2D9Id0W.mjs";
2
2
  export { membersCommand, rolesCommand };
@@ -1,6 +1,6 @@
1
- import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CLnICOne.mjs";
1
+ import { c as projectDirArgs, d as resolveProject } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/roles.ts
@@ -1,6 +1,6 @@
1
- import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
1
+ import { d as resolveProject, l as projectFlagArgs } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  //#region src/schedule.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
3
- import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
1
+ import { c as projectDirArgs, d as resolveProject } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/secrets.ts
@@ -259,6 +259,55 @@ function tryResolveProject(args) {
259
259
  slug: args.project ?? tryReadCanopyJson(dir)?.slug
260
260
  };
261
261
  }
262
+ const GITHUB_HOST = /^(?:github\.com|[a-z0-9][a-z0-9-]*\.ghe\.com)$/;
263
+ /**
264
+ * The GitHub repository a git remote URL points at, or null when it is not on
265
+ * github.com or a `*.ghe.com` host (OpenSpec platform-oauth-oidc). Whether the
266
+ * host is one this site trusts is the API's call, not ours.
267
+ *
268
+ * git@monks.ghe.com:monks/my-tool.git
269
+ * ssh://git@github.com/monks/my-tool.git
270
+ * https://github.com/monks/my-tool(.git)
271
+ */
272
+ function parseGithubRemote(url) {
273
+ const trimmed = url.trim();
274
+ const scp = /^[\w.-]+@([^:/]+):([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(trimmed);
275
+ let host;
276
+ let parts;
277
+ if (scp) {
278
+ host = scp[1];
279
+ parts = [scp[2], scp[3]];
280
+ } else try {
281
+ const parsed = new URL(trimmed);
282
+ if (![
283
+ "https:",
284
+ "http:",
285
+ "ssh:",
286
+ "git:"
287
+ ].includes(parsed.protocol)) return null;
288
+ host = parsed.hostname;
289
+ parts = parsed.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "").split("/");
290
+ } catch {
291
+ return null;
292
+ }
293
+ host = host?.toLowerCase();
294
+ if (!host || !GITHUB_HOST.test(host) || parts?.length !== 2 || !parts[0] || !parts[1]) return null;
295
+ return {
296
+ host,
297
+ repository: `${parts[0]}/${parts[1]}`
298
+ };
299
+ }
300
+ /** `[<host>/]owner/name` as typed on the command line; null when it is neither. */
301
+ function parseRepositoryArg(value) {
302
+ const parts = value.trim().replace(/\.git$/, "").split("/");
303
+ if (parts.some((part) => part === "")) return null;
304
+ if (parts.length === 2) return { repository: `${parts[0]}/${parts[1]}` };
305
+ if (parts.length === 3 && parts[0].includes(".")) return {
306
+ host: parts[0].toLowerCase(),
307
+ repository: `${parts[1]}/${parts[2]}`
308
+ };
309
+ return null;
310
+ }
262
311
  //#endregion
263
312
  //#region src/site.ts
264
313
  /**
@@ -300,4 +349,4 @@ function siteHeaderLine(cliVersion) {
300
349
  return `canopy ${cliVersion} · ${c.envOverride || !c.profileId ? c.apiUrl : `${c.profileId} (${c.projectDomain ?? new URL(c.apiUrl).hostname})`}`;
301
350
  }
302
351
  //#endregion
303
- export { setActiveProfile as _, projectDialect as a, getBakedProfile as b, projectUrl as c, stagingUrl as d, tryReadCanopyJson as f, readConfigFile as g, listBakedProfiles as h, siteHeaderLine as i, resolveProject as l, clearAllProfileTokens as m, setInvocationProfile as n, projectDirArgs as o, tryResolveProject as p, siteConfig as r, projectFlagArgs as s, projectDir as t, rewriteCanopyJsonSlug as u, upsertProfileAuth as v, isProfileId as x, writeConfigFile as y };
352
+ export { isProfileId as C, getBakedProfile as S, listBakedProfiles as _, parseGithubRemote as a, upsertProfileAuth as b, projectDirArgs as c, resolveProject as d, rewriteCanopyJsonSlug as f, clearAllProfileTokens as g, tryResolveProject as h, siteHeaderLine as i, projectFlagArgs as l, tryReadCanopyJson as m, setInvocationProfile as n, parseRepositoryArg as o, stagingUrl as p, siteConfig as r, projectDialect as s, projectDir as t, projectUrl as u, readConfigFile as v, writeConfigFile as x, setActiveProfile as y };
@@ -1,5 +1,5 @@
1
- import { t as version } from "./package-Dph4v1Cq.mjs";
2
- import { r as reportErrors } from "./ui-CLnICOne.mjs";
1
+ import { t as version } from "./package-BdNHq8QV.mjs";
2
+ import { r as reportErrors } from "./ui-26ETk1n8.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";
@@ -1,6 +1,6 @@
1
- import { c as projectUrl, d as stagingUrl, l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
3
- import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
1
+ import { d as resolveProject, l as projectFlagArgs, p as stagingUrl, u as projectUrl } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region src/staging.ts
@@ -1,34 +1,40 @@
1
1
  # Deploy to Canopy on every push to main. GitHub's runners are the build
2
2
  # farm — the platform owns no build infrastructure.
3
3
  #
4
- # Needs a CANOPY_DEPLOY_TOKEN repo secret: create a deploy token in the
5
- # dashboard (project → Settings → Deploy tokens) or with `canopy token
6
- # create`, then add it via `gh secret set CANOPY_DEPLOY_TOKEN` or the repo's
7
- # Settings → Secrets. Until the secret is set, this workflow skips itself
8
- # instead of failing the build.
4
+ # No secret needed: the run proves which repository and branch it comes from
5
+ # with GitHub's own identity token (the id-token permission below), and
6
+ # Canopy deploys it once the project owner has linked this repository:
7
+ #
8
+ # canopy repo link (in this folder: links the origin remote)
9
+ #
10
+ # Until then, every run skips the deploy with a warning instead of failing.
11
+ # The owner can pause CI deploys without editing this file:
12
+ #
13
+ # canopy repo deploys off (and `on` to resume)
14
+ #
15
+ # A staging branch (`canopy repo branches --staging-ref develop`) deploys to
16
+ # the project's staging channel only. Add it to `branches` below, e.g.
17
+ # [main, develop], so pushes to it run this workflow.
9
18
  name: Deploy to Canopy
10
19
 
11
20
  on:
12
21
  push:
13
22
  branches: [main]
14
23
 
24
+ permissions:
25
+ id-token: write
26
+ contents: read
27
+
15
28
  jobs:
16
29
  deploy:
17
30
  runs-on: ubuntu-latest
18
- env:
19
- CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
31
  steps:
21
32
  - uses: actions/checkout@v7
22
33
  - uses: actions/setup-node@v7
23
34
  with:
24
35
  node-version: 24
25
36
  - name: Install dependencies
26
- if: env.CANOPY_DEPLOY_TOKEN != ''
27
37
  run: |
28
38
  if [ -f package-lock.json ]; then npm ci; else npm install; fi
29
39
  - name: Deploy
30
- if: env.CANOPY_DEPLOY_TOKEN != ''
31
40
  run: npm exec --yes @mediamonks/canopy-cli deploy
32
- - name: Skipped (no deploy token)
33
- if: env.CANOPY_DEPLOY_TOKEN == ''
34
- run: echo "CANOPY_DEPLOY_TOKEN secret not set — skipping deploy."
@@ -11,4 +11,4 @@ A [Canopy](https://dashboard.thanarie.dev) project — Vite + React. Live
11
11
  - `AGENTS.md` — project guardrails and skill setup for coding agents
12
12
 
13
13
  Push-to-main deploys are prepared in `.github/workflows/canopy-deploy.yml`
14
- (needs a `CANOPY_DEPLOY_TOKEN` repo secret — see the workflow file).
14
+ (no secret: link the repository once with `canopy repo link` — see the workflow file).
@@ -1,34 +1,40 @@
1
1
  # Deploy to Canopy on every push to main. GitHub's runners are the build
2
2
  # farm — the platform owns no build infrastructure.
3
3
  #
4
- # Needs a CANOPY_DEPLOY_TOKEN repo secret: create a deploy token in the
5
- # dashboard (project → Settings → Deploy tokens) or with `canopy token
6
- # create`, then add it via `gh secret set CANOPY_DEPLOY_TOKEN` or the repo's
7
- # Settings → Secrets. Until the secret is set, this workflow skips itself
8
- # instead of failing the build.
4
+ # No secret needed: the run proves which repository and branch it comes from
5
+ # with GitHub's own identity token (the id-token permission below), and
6
+ # Canopy deploys it once the project owner has linked this repository:
7
+ #
8
+ # canopy repo link (in this folder: links the origin remote)
9
+ #
10
+ # Until then, every run skips the deploy with a warning instead of failing.
11
+ # The owner can pause CI deploys without editing this file:
12
+ #
13
+ # canopy repo deploys off (and `on` to resume)
14
+ #
15
+ # A staging branch (`canopy repo branches --staging-ref develop`) deploys to
16
+ # the project's staging channel only. Add it to `branches` below, e.g.
17
+ # [main, develop], so pushes to it run this workflow.
9
18
  name: Deploy to Canopy
10
19
 
11
20
  on:
12
21
  push:
13
22
  branches: [main]
14
23
 
24
+ permissions:
25
+ id-token: write
26
+ contents: read
27
+
15
28
  jobs:
16
29
  deploy:
17
30
  runs-on: ubuntu-latest
18
- env:
19
- CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
20
31
  steps:
21
32
  - uses: actions/checkout@v7
22
33
  - uses: actions/setup-node@v7
23
34
  with:
24
35
  node-version: 24
25
36
  - name: Install dependencies
26
- if: env.CANOPY_DEPLOY_TOKEN != ''
27
37
  run: |
28
38
  if [ -f package-lock.json ]; then npm ci; else npm install; fi
29
39
  - name: Deploy
30
- if: env.CANOPY_DEPLOY_TOKEN != ''
31
40
  run: npm exec --yes @mediamonks/canopy-cli deploy
32
- - name: Skipped (no deploy token)
33
- if: env.CANOPY_DEPLOY_TOKEN == ''
34
- run: echo "CANOPY_DEPLOY_TOKEN secret not set — skipping deploy."
@@ -14,4 +14,4 @@ https://{{slug}}.thanarie.dev after the first `canopy deploy`.
14
14
  - `AGENTS.md` — project guardrails and skill setup for coding agents
15
15
 
16
16
  Push-to-main deploys are prepared in `.github/workflows/canopy-deploy.yml`
17
- (needs a `CANOPY_DEPLOY_TOKEN` repo secret — see the workflow file).
17
+ (no secret: link the repository once with `canopy repo link` — see the workflow file).
@@ -1,6 +1,6 @@
1
- import { l as resolveProject, o as projectDirArgs, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest } from "./api-Bl6SG8Oq.mjs";
3
- import { r as reportErrors, t as formatDateTime } from "./ui-CLnICOne.mjs";
1
+ import { c as projectDirArgs, d as resolveProject, l as projectFlagArgs } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest } from "./api-DmJrIsE7.mjs";
3
+ import { r as reportErrors, t as formatDateTime } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  const tokenCommand = defineCommand({
6
6
  meta: {
@@ -23,12 +23,12 @@ const tokenCommand = defineCommand({
23
23
  run: reportErrors("token create", async ({ args }) => {
24
24
  const { slug } = resolveProject(args);
25
25
  const { token, idle_days } = await apiRequest("POST", `/projects/${slug}/deploy-tokens`, { name: args.name });
26
+ console.log("You do not need this for GitHub Actions — link the repository instead (`canopy repo link <owner/name>`) and pushes deploy with no secret.\n");
26
27
  console.log(`Deploy token for ${slug} (shown once — store it now):\n`);
27
28
  console.log(` ${token}\n`);
28
29
  console.log(`It belongs to the project and stops working after ${idle_days} days without a deploy.`);
29
- console.log("Add it to the GitHub repo as secret CANOPY_DEPLOY_TOKEN");
30
- console.log("(gh secret set CANOPY_DEPLOY_TOKEN, or repo Settings → Secrets).");
31
- console.log("Pushes to main then deploy via .github/workflows/canopy-deploy.yml.");
30
+ console.log("For other CI, set it as the CANOPY_DEPLOY_TOKEN environment variable;");
31
+ console.log("`canopy deploy` then deploys as this project with no `canopy login`.");
32
32
  })
33
33
  }),
34
34
  list: defineCommand({
@@ -1,4 +1,4 @@
1
- import { t as ApiError } from "./api-Bl6SG8Oq.mjs";
1
+ import { t as ApiError } from "./api-DmJrIsE7.mjs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { cancel, isCancel } from "@clack/prompts";
4
4
  //#region src/ui.ts
@@ -1,5 +1,5 @@
1
1
  import { t as PACKAGE_NAME } from "./version-check-CcsA1QjI.mjs";
2
- import { r as reportErrors } from "./ui-CLnICOne.mjs";
2
+ import { r as reportErrors } from "./ui-26ETk1n8.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-Zj19RNo2.mjs");
115
+ const { skillInstalled } = await import("./skill-vzcCD1b4.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,6 +1,6 @@
1
- import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
2
- import { a as apiRequest, f as withContext } from "./api-Bl6SG8Oq.mjs";
3
- import { r as reportErrors } from "./ui-CLnICOne.mjs";
1
+ import { d as resolveProject, l as projectFlagArgs } from "./site-BL5Ik-yj.mjs";
2
+ import { a as apiRequest, m as withContext } from "./api-DmJrIsE7.mjs";
3
+ import { r as reportErrors } from "./ui-26ETk1n8.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  //#region src/usage.ts
6
6
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediamonks/canopy-cli",
3
- "version": "0.7.45",
3
+ "version": "0.7.46",
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 { n as devCommand } from "./dev-D-vwE8G-.mjs";
2
- export { devCommand };
@@ -1,2 +0,0 @@
1
- import { a as templateCommand } from "./project-template-Cdr1uwwE.mjs";
2
- export { templateCommand };
@@ -1,2 +0,0 @@
1
- import { a as repoCommand } from "./repo-CwT2RDoW.mjs";
2
- export { repoCommand };