@mediamonks/canopy-cli 0.7.45 → 0.7.47
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/{ai-FBHTSCLZ.mjs → ai-CWPQiUft.mjs} +2 -2
- package/dist/{api-Bl6SG8Oq.mjs → api-DmJrIsE7.mjs} +134 -19
- package/dist/audience-B68YZBGy.mjs +121 -0
- package/dist/{browse-B8-0dweq.mjs → browse-BPLWhoFI.mjs} +2 -2
- package/dist/{db-BJJGydgL.mjs → db-DpCMc0uz.mjs} +4 -4
- package/dist/{deploy-ExrIeX_m.mjs → deploy-BnRQusjJ.mjs} +51 -5
- package/dist/{dev-D-vwE8G-.mjs → dev-BmKeIdMi.mjs} +7 -7
- package/dist/dev-BvVlq9np.mjs +2 -0
- package/dist/docs/ci.md +112 -50
- package/dist/docs/cli-audience.md +47 -0
- package/dist/docs/cli-create.md +2 -0
- package/dist/docs/cli-deploy.md +1 -1
- package/dist/docs/cli-repo.md +34 -2
- package/dist/docs/cli-token.md +8 -6
- package/dist/docs/cli.md +6 -2
- package/dist/docs/deploy.md +3 -2
- package/dist/docs/index.json +8 -3
- package/dist/docs/platform-deploys-and-rollbacks.md +4 -4
- package/dist/docs/platform-members-and-access.md +20 -0
- package/dist/{docs-Cpky2XNP.mjs → docs-D5gmZRJk.mjs} +4 -4
- package/dist/{egress-C2amvqmE.mjs → egress-BM4EpLBh.mjs} +3 -3
- package/dist/{feedback-CTV6inRg.mjs → feedback-C6kiYRro.mjs} +5 -5
- package/dist/index.mjs +36 -35
- package/dist/{local-BThs382Y.mjs → local-C9Bi9f3y.mjs} +2 -2
- package/dist/{login-CREcZfLH.mjs → login-2MW9sGJO.mjs} +3 -3
- package/dist/{logs-BnrNZvLc.mjs → logs-dZMPTJLu.mjs} +3 -3
- package/dist/{mcp-Cy8EwhhD.mjs → mcp-DuCxfb85.mjs} +3 -3
- package/dist/{migrations-D8NqjC5I.mjs → migrations-BReMlGWP.mjs} +2 -2
- package/dist/{open-Bpz4C2Sr.mjs → open-DcQSdfx5.mjs} +2 -2
- package/dist/{package-Dph4v1Cq.mjs → package-BksVeJZB.mjs} +1 -1
- package/dist/{project-template-Cdr1uwwE.mjs → project-template-BPW_Dgxj.mjs} +3 -3
- package/dist/project-template-Dw11151p.mjs +2 -0
- package/dist/{projects-BRG2HnBs.mjs → projects-CnU3194_.mjs} +26 -8
- package/dist/{rename-BGKa25Rq.mjs → rename-BWvERJgA.mjs} +3 -3
- package/dist/{repo-CwT2RDoW.mjs → repo-6a7gRQaf.mjs} +171 -8
- package/dist/repo-C7AXDtFZ.mjs +2 -0
- package/dist/{roles-C7pq9bIx.mjs → roles-CrYf_8wk.mjs} +1 -1
- package/dist/{roles-DAPPXf4U.mjs → roles-S2D9Id0W.mjs} +3 -3
- package/dist/{schedule-BcqBspn3.mjs → schedule-DvHSSzfR.mjs} +3 -3
- package/dist/{secrets-BjbIi7Gp.mjs → secrets-BIn2RPoe.mjs} +3 -3
- package/dist/{site-0i7wAJqg.mjs → site-BL5Ik-yj.mjs} +50 -1
- package/dist/{skill-Zj19RNo2.mjs → skill-Bz39NhTN.mjs} +2 -2
- package/dist/{staging-_2Js0Fkc.mjs → staging-Dn8kH9cK.mjs} +3 -3
- package/dist/templates/spa/.github/workflows/canopy-deploy.yml +18 -12
- package/dist/templates/spa/README.md +1 -1
- package/dist/templates/worker/.github/workflows/canopy-deploy.yml +18 -12
- package/dist/templates/worker/README.md +1 -1
- package/dist/{token-7vSNuz-X.mjs → token-DMCj88A-.mjs} +6 -6
- package/dist/{ui-CLnICOne.mjs → ui-26ETk1n8.mjs} +1 -1
- package/dist/{upgrade-B9tCAbre.mjs → upgrade-BjDaghNf.mjs} +2 -2
- package/dist/{usage-DGGmTwjJ.mjs → usage-Bpz5B1N1.mjs} +3 -3
- package/package.json +1 -1
- package/dist/dev-CSimnUP3.mjs +0 -2
- package/dist/project-template-715H_lnu.mjs +0 -2
- package/dist/repo-DmFs6H9m.mjs +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as agentHeaders } from "./api-
|
|
3
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
1
|
+
import { C as isProfileId, S as getBakedProfile, _ as listBakedProfiles, b as upsertProfileAuth, g as clearAllProfileTokens, r as siteConfig, v as readConfigFile, x as writeConfigFile, y as setActiveProfile } from "./site-BL5Ik-yj.mjs";
|
|
2
|
+
import { n as agentHeaders } from "./api-DmJrIsE7.mjs";
|
|
3
|
+
import { n as openBrowser, r as reportErrors } from "./ui-26ETk1n8.mjs";
|
|
4
4
|
import { n as clearRepoTokenCache } from "./repo-credential-CNDRM-tG.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { hostname } from "node:os";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { r as reportErrors } from "./ui-
|
|
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/logs.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import {
|
|
3
|
-
import { a as apiRequest,
|
|
1
|
+
import { t as version } from "./package-BksVeJZB.mjs";
|
|
2
|
+
import { h as tryResolveProject, p as stagingUrl, r as siteConfig, u as projectUrl } from "./site-BL5Ik-yj.mjs";
|
|
3
|
+
import { a as apiRequest, f as noticeRenamesOnly, l as currentSlug, t as ApiError } from "./api-DmJrIsE7.mjs";
|
|
4
4
|
import { t as liveDevServer } from "./dev-server-file-Dk9pk4AR.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as apiDataRequest } from "./api-
|
|
1
|
+
import { m as tryReadCanopyJson } from "./site-BL5Ik-yj.mjs";
|
|
2
|
+
import { r as apiDataRequest } from "./api-DmJrIsE7.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
|
-
import { c as
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
1
|
+
import { c as projectDirArgs, d as resolveProject, p as stagingUrl, u as projectUrl } from "./site-BL5Ik-yj.mjs";
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-26ETk1n8.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
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
|
/** Plain words for the API's template errors. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
4
|
-
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-
|
|
5
|
-
import {
|
|
1
|
+
import { m as tryReadCanopyJson, r as siteConfig, 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 } from "./ui-26ETk1n8.mjs";
|
|
4
|
+
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-BPW_Dgxj.mjs";
|
|
5
|
+
import { a as offerRepoOnCreate, i as githubOrigin, n as cloneProjectRepo } from "./repo-6a7gRQaf.mjs";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -84,6 +84,17 @@ function slugify(input) {
|
|
|
84
84
|
function shouldScaffoldCreate(requested, cwd = process.cwd()) {
|
|
85
85
|
return requested ?? !existsSync(join(cwd, "canopy.json"));
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The "deploy from GitHub" next step (OpenSpec platform-oauth-oidc). Never a
|
|
89
|
+
* link: linking turns CI deploys on, which is the owner's call. With a GitHub
|
|
90
|
+
* `origin` the exact command; without one the shape of it. A folder that
|
|
91
|
+
* pushes to a Canopy repository needs no link, so no step.
|
|
92
|
+
*/
|
|
93
|
+
async function ciLinkStep(dir, canopyRemote) {
|
|
94
|
+
if (canopyRemote) return [];
|
|
95
|
+
const origin = await githubOrigin(dir);
|
|
96
|
+
return origin ? [`canopy repo link ${origin.host}/${origin.repository} (deploy on every push to main)`] : ["canopy repo link <owner/name> (once it's on GitHub: deploy on every push to main)"];
|
|
97
|
+
}
|
|
87
98
|
const createCommand = defineCommand({
|
|
88
99
|
meta: {
|
|
89
100
|
name: "create",
|
|
@@ -186,13 +197,15 @@ const createCommand = defineCommand({
|
|
|
186
197
|
`cd ${slug}`,
|
|
187
198
|
...template.needsInstall ? ["npm install"] : [],
|
|
188
199
|
...remote ? [`git add -A && git commit -m "Initial commit" && git push -u ${remote} main`] : [],
|
|
189
|
-
"canopy dev (or deploy right away: canopy deploy)"
|
|
200
|
+
"canopy dev (or deploy right away: canopy deploy)",
|
|
201
|
+
...await ciLinkStep(targetDir, remote)
|
|
190
202
|
];
|
|
191
203
|
p.note(steps.join("\n"), `Scaffolded ./${slug}/ (${template.id}) — next`);
|
|
192
204
|
p.outro(`Live URL (after first deploy): ${projectUrl(project.slug)}`);
|
|
193
205
|
} else {
|
|
194
206
|
const remote = await offerRepoOnCreate(slug, process.cwd(), args.repo);
|
|
195
207
|
if (remote) p.log.info(`This folder pushes to the project's git repository as "${remote}".`);
|
|
208
|
+
for (const step of await ciLinkStep(process.cwd(), remote)) p.log.info(`Next: ${step}`);
|
|
196
209
|
p.outro(existsSync(join(process.cwd(), "canopy.json")) ? "Registered without scaffolding — the existing canopy.json was left unchanged." : "Registered without scaffolding — add a canopy.json to deploy an existing app.");
|
|
197
210
|
}
|
|
198
211
|
})
|
|
@@ -319,7 +332,8 @@ const initCommand = defineCommand({
|
|
|
319
332
|
const steps = [
|
|
320
333
|
...rel === "." ? [] : [`cd ${rel}`],
|
|
321
334
|
...template.needsInstall ? ["npm install"] : [],
|
|
322
|
-
"canopy dev (or deploy right away: canopy deploy)"
|
|
335
|
+
"canopy dev (or deploy right away: canopy deploy)",
|
|
336
|
+
...await ciLinkStep(targetDir, null)
|
|
323
337
|
];
|
|
324
338
|
p.note(steps.join("\n"), `Scaffolded ${rel}/ (${template.id}) for ${slug} — next`);
|
|
325
339
|
p.outro(`Live URL (after first deploy): ${projectUrl(slug)}`);
|
|
@@ -333,7 +347,11 @@ const initCommand = defineCommand({
|
|
|
333
347
|
return;
|
|
334
348
|
}
|
|
335
349
|
writeCanopyJson(targetDir, { slug });
|
|
336
|
-
p.note(
|
|
350
|
+
p.note([
|
|
351
|
+
"Add \"dist\"/\"build\" to canopy.json if this app has a build step, then:",
|
|
352
|
+
"canopy deploy",
|
|
353
|
+
...await ciLinkStep(targetDir, null)
|
|
354
|
+
].join("\n"), `Linked ${rel} to ${slug}`);
|
|
337
355
|
p.outro(`Live URL (after first deploy): ${projectUrl(slug)}`);
|
|
338
356
|
})
|
|
339
357
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
1
|
+
import { c as projectDirArgs, d as resolveProject, f as rewriteCanopyJsonSlug, 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 } from "./ui-26ETk1n8.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region src/rename.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
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")
|
|
212
|
-
|
|
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 &&
|
|
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 {
|
|
594
|
+
export { offerRepoOnCreate as a, githubOrigin as i, cloneProjectRepo as n, repoCommand as o, connectFolder as r, toRef as s, cliInvocation as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as membersCommand, o as rolesCommand } from "./roles-
|
|
1
|
+
import { i as membersCommand, o as rolesCommand } from "./roles-S2D9Id0W.mjs";
|
|
2
2
|
export { membersCommand, rolesCommand };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
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 {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
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 {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, r as reportErrors, t as formatDateTime } from "./ui-
|
|
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 {
|
|
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-
|
|
2
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-BksVeJZB.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 {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
(
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
(
|
|
17
|
+
(no secret: link the repository once with `canopy repo link` — see the workflow file).
|