canopycms 0.0.68-int.111 → 0.0.68-int.113

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 (41) hide show
  1. package/dist/branch-health.js +14 -9
  2. package/dist/cli/cli.js +4 -0
  3. package/dist/cli/generate-ai-content.js +2 -0
  4. package/dist/editor/Editor.js +2 -2
  5. package/dist/editor/components/StagedChangesDrawer.d.ts +37 -0
  6. package/dist/editor/components/StagedChangesDrawer.js +75 -0
  7. package/dist/editor/group-manager/GroupCard.d.ts +2 -0
  8. package/dist/editor/group-manager/GroupCard.js +2 -2
  9. package/dist/editor/group-manager/GroupForm.js +1 -1
  10. package/dist/editor/group-manager/InternalGroupsTab.d.ts +1 -0
  11. package/dist/editor/group-manager/InternalGroupsTab.js +2 -2
  12. package/dist/editor/group-manager/hooks/useGroupState.d.ts +2 -0
  13. package/dist/editor/group-manager/hooks/useGroupState.js +18 -1
  14. package/dist/editor/group-manager/index.js +9 -4
  15. package/dist/editor/group-manager/types.d.ts +3 -1
  16. package/dist/editor/hooks/usePermissionManager.js +4 -3
  17. package/dist/editor/permission-manager/index.js +9 -3
  18. package/dist/editor/permission-manager/types.d.ts +3 -1
  19. package/dist/git-manager.d.ts +10 -0
  20. package/dist/git-manager.js +6 -5
  21. package/dist/utils/error.js +6 -0
  22. package/dist/version.d.ts +1 -1
  23. package/dist/version.js +1 -1
  24. package/dist/worker/cms-worker.d.ts +39 -124
  25. package/dist/worker/cms-worker.js +232 -259
  26. package/dist/worker/git-sync.d.ts +7 -5
  27. package/dist/worker/git-sync.js +32 -29
  28. package/dist/worker/github-auth.d.ts +7 -7
  29. package/dist/worker/github-auth.js +12 -14
  30. package/dist/worker/github-gateway.d.ts +144 -0
  31. package/dist/worker/github-gateway.js +370 -0
  32. package/dist/worker/github-mirror.d.ts +84 -10
  33. package/dist/worker/github-mirror.js +237 -21
  34. package/dist/worker/rebase.d.ts +1 -1
  35. package/dist/worker/rebase.js +1 -6
  36. package/dist/worker/shared-repo-git.d.ts +6 -0
  37. package/dist/worker/shared-repo-git.js +8 -0
  38. package/dist/worker/task-runner.d.ts +2 -2
  39. package/dist/worker/task-runner.js +77 -127
  40. package/dist/worker/worker-context.d.ts +12 -40
  41. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import { completeTask, dequeueTask, failTask, recoverOrphanedTasks, releaseTask, retryTask, } from '../task-queue/cms-task-queue.js';
3
- import { createOrUpdatePullRequest, isNoCommitsBetweenError, isRefAlreadyGoneError, } from '../github-service.js';
3
+ import { isNoCommitsBetweenError, isRefAlreadyGoneError } from '../github-service.js';
4
4
  import { BranchMetadataCorruptError, BranchMetadataFileManager, getBranchMetadataFileManager, } from '../branch-metadata.js';
5
5
  import { sanitizeBranchName, RESERVED_SETTINGS_BRANCH_PREFIX } from '../paths/branch-name.js';
6
6
  import { getErrorMessage, redactCredentials } from '../utils/error.js';
7
- import { isNonFastForwardRejection, isStaleLeaseRejection, workflowPushRefusalFile, } from '../utils/git.js';
7
+ import { isNonFastForwardRejection, workflowPushRefusalFile } from '../utils/git.js';
8
+ import { GitHubPushError, describeMove } from './github-gateway.js';
9
+ import { RefusedPushError, assertPlainBranchName } from './github-mirror.js';
8
10
  import { clearHistoryRewrittenMarker, readPublishedSha } from './history-rewrite.js';
9
11
  import { writeWorkerStatus } from '../task-queue/worker-status.js';
10
12
  import { workerLog, workerLogError, workerLogWarn } from './log.js';
@@ -184,9 +186,9 @@ export async function processTaskQueue(ctx) {
184
186
  const message = getErrorMessage(err);
185
187
  workerLogError(`Task ${task.id} (${task.action}) failed:`, message);
186
188
  // [REDACT] task.error is persisted (pending/failed task JSON) and served
187
- // to the browser by the admin panel's Tasks tab -- a push failure's
188
- // message can embed the bot token via buildGitHubUrl(). Console output
189
- // above stays raw (journald/CloudWatch is trusted).
189
+ // to the browser by the admin panel's Tasks tab, so a credential in any
190
+ // failure's message must not reach it. Console output above stays raw
191
+ // (journald/CloudWatch is trusted).
190
192
  const persistedMessage = redactCredentials(message);
191
193
  // DEP-L1: only transient failures are worth retrying; a permanent one
192
194
  // would burn the retry budget on an identical doomed request.
@@ -206,16 +208,6 @@ export async function processTaskQueue(ctx) {
206
208
  ? ' Permanently failed (non-retryable error)'
207
209
  : ` Permanently failed after ${maxRetries} retries`);
208
210
  }
209
- // The credential may have rotated, and the next retry resolves
210
- // buildGitHubUrl() afresh to pick a new token up. Without this a push
211
- // meeting a revoked token spends its whole retry budget (5s/10s/20s
212
- // backoff) waiting on the git-sync loop's refresh up to 5 minutes away,
213
- // and fails permanently while the working token sits in the secret store.
214
- //
215
- // Ungated, and AFTER the outcome is recorded rather than before: the task
216
- // is safely in pending/ or failed/ while a network read runs, and that
217
- // read is bounded and never throws. See CmsWorker.refreshGitHubCredential.
218
- await ctx.refreshGitHubCredential();
219
211
  }
220
212
  processed++;
221
213
  }
@@ -235,7 +227,7 @@ export async function processTaskQueue(ctx) {
235
227
  * - an AbortSignal cancels Octokit HTTP calls and kills the push's git process
236
228
  * (pushBranchToGitHub passes it to simple-git's `abort`);
237
229
  * - a Promise.race rejects when either fires, so work that cannot observe the
238
- * signal (a hung `ctx.buildGitHubUrl()` in pushBranchToGitHub) still ends
230
+ * signal (a hung credential resolution in the gateway's push) still ends
239
231
  * the attempt and the worker moves on instead of stalling forever.
240
232
  *
241
233
  * A raced-out credential resolution is not cancelled. On the App path the mint
@@ -278,30 +270,23 @@ export async function executeTask(ctx, task, signal) {
278
270
  case 'push-and-create-pr': {
279
271
  const branch = requireString(payload, 'branch');
280
272
  await ctx.pushBranchToGitHub(branch, signal);
281
- const pr = await ctx.octokit().pulls.create({
282
- owner: ctx.githubOwner,
283
- repo: ctx.githubRepo,
273
+ const pr = await ctx.github().createPullRequest({
284
274
  head: branch,
285
275
  base: optionalString(payload, 'baseBranch', ctx.baseBranch),
286
276
  title: optionalString(payload, 'title', `Submit ${branch}`),
287
277
  body: optionalString(payload, 'body', ''),
288
- request: { signal },
289
- });
290
- workerLog(`Created PR #${pr.data.number} for ${branch}`);
291
- return { prUrl: pr.data.html_url, prNumber: pr.data.number };
278
+ }, signal);
279
+ workerLog(`Created PR #${pr.number} for ${branch}`);
280
+ return { prUrl: pr.url, prNumber: pr.number };
292
281
  }
293
282
  case 'push-and-update-pr': {
294
283
  const branch = requireString(payload, 'branch');
295
284
  const prNumber = requireNumber(payload, 'pullRequestNumber');
296
285
  await ctx.pushBranchToGitHub(branch, signal);
297
- await ctx.octokit().pulls.update({
298
- owner: ctx.githubOwner,
299
- repo: ctx.githubRepo,
300
- pull_number: prNumber,
286
+ await ctx.github().updatePullRequest(prNumber, {
301
287
  title: optionalString(payload, 'title', `Submit ${branch}`),
302
288
  body: optionalString(payload, 'body', ''),
303
- request: { signal },
304
- });
289
+ }, signal);
305
290
  workerLog(`Updated PR #${prNumber} for ${branch}`);
306
291
  return { prNumber };
307
292
  }
@@ -326,16 +311,13 @@ export async function executeTask(ctx, task, signal) {
326
311
  // base, so GitHub 422s a PR for it: push it and stop.
327
312
  if (isSettingsBranch(branch)) {
328
313
  await ctx.pushBranchToGitHub(branch, signal);
329
- workerLog(`Pushed settings branch ${branch}; settings branches never get a PR`);
314
+ workerLog(`No PR for ${branch}: settings branches never get one`);
330
315
  return { pushed: true };
331
316
  }
332
317
  await ctx.pushBranchToGitHub(branch, signal);
333
318
  let result;
334
319
  try {
335
- result = await createOrUpdatePullRequest({
336
- octokit: ctx.octokit(),
337
- owner: ctx.githubOwner,
338
- repo: ctx.githubRepo,
320
+ result = await ctx.github().createOrUpdatePullRequest({
339
321
  head: branch,
340
322
  base,
341
323
  title: optionalString(payload, 'title', `Submit ${branch}`),
@@ -359,29 +341,13 @@ export async function executeTask(ctx, task, signal) {
359
341
  }
360
342
  case 'convert-to-draft': {
361
343
  const draftPrNumber = requireNumber(payload, 'pullRequestNumber');
362
- // GitHub REST API doesn't support converting to draft directly.
363
- // Use the GraphQL API via Octokit.
364
- const { data: pr } = await ctx.octokit().pulls.get({
365
- owner: ctx.githubOwner,
366
- repo: ctx.githubRepo,
367
- pull_number: draftPrNumber,
368
- request: { signal },
369
- });
370
- await ctx
371
- .octokit()
372
- .graphql(`mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, { id: pr.node_id, request: { signal } });
344
+ await ctx.github().convertPullRequestToDraft(draftPrNumber, signal);
373
345
  workerLog(`Converted PR #${draftPrNumber} to draft`);
374
346
  return { prNumber: draftPrNumber, draft: true };
375
347
  }
376
348
  case 'close-pr': {
377
349
  const closePrNumber = requireNumber(payload, 'pullRequestNumber');
378
- await ctx.octokit().pulls.update({
379
- owner: ctx.githubOwner,
380
- repo: ctx.githubRepo,
381
- pull_number: closePrNumber,
382
- state: 'closed',
383
- request: { signal },
384
- });
350
+ await ctx.github().updatePullRequest(closePrNumber, { state: 'closed' }, signal);
385
351
  return { closed: true };
386
352
  }
387
353
  case 'delete-remote-branch': {
@@ -421,12 +387,7 @@ export async function executeTask(ctx, task, signal) {
421
387
  return { deleted: false, skipped: 'name-reused' };
422
388
  }
423
389
  try {
424
- await ctx.octokit().git.deleteRef({
425
- owner: ctx.githubOwner,
426
- repo: ctx.githubRepo,
427
- ref: `heads/${branch}`,
428
- request: { signal },
429
- });
390
+ await ctx.github().deleteBranch(branch, signal);
430
391
  }
431
392
  catch (err) {
432
393
  // Already gone is the outcome this task wants; failing it would only park it in failed/.
@@ -565,15 +526,16 @@ export async function pushBranchToGitHub(ctx, branch, signal) {
565
526
  // commit remote.git held when it was read, so what the marker logic at the end compares is
566
527
  // what GitHub received.
567
528
  await assertSharedRepoConfig(ctx.remoteGitPath, 'bare');
568
- // Resolve the tokenized URL ONCE, here: all three pushes below use this
569
- // const and none calls ctx.buildGitHubUrl() again. A correctness
570
- // requirement, not tidiness, since resolution is async: the retry push sits
571
- // INSIDE the stale-lease catch, and a resolution that threw there would
572
- // replace the push error being classified, so neither isStaleLeaseRejection
573
- // nor isNonFastForwardRejection would run and a genuinely diverged branch
574
- // would be retried instead of raising PermanentTaskError. It also means every
575
- // push provably carries the same credential.
576
- const githubUrl = await ctx.buildGitHubUrl();
529
+ // Before `branch` reaches any git: `readPublishedSha` below would read a `<rev>:<path>` form as a
530
+ // tree lookup in remote.git. A refusal is permanent; a failure to run git is retried.
531
+ try {
532
+ await assertPlainBranchName(branch);
533
+ }
534
+ catch (err) {
535
+ if (err instanceof RefusedPushError)
536
+ throw new PermanentTaskError(err.message);
537
+ throw err;
538
+ }
577
539
  // [SYNC-H1] If the rebase loop rewrote this branch's already-published
578
540
  // history, GitHub still holds the commit it replaced, so an ordinary
579
541
  // push is non-fast-forward forever. Push under a lease keyed to exactly
@@ -586,71 +548,49 @@ export async function pushBranchToGitHub(ctx, branch, signal) {
586
548
  if (outgoingSha === null) {
587
549
  throw new Error(`Branch "${branch}" is not in remote.git, so there is nothing to push`);
588
550
  }
589
- // The whole exchange, retry included, is one mirror session; the mirror kills its git when
590
- // `signal` aborts (the task timed out, or the worker's drain deadline hit). GitHub moves the ref
591
- // only after receiving the whole pack: a push killed before that changes nothing, and one
592
- // killed after it is found already done by the re-run.
593
- const outcome = await ctx.githubMirror().exclusive(async (mirror) => {
594
- const push = (lease) => mirror.pushToGitHub(githubUrl, branch, outgoingSha, { lease, signal });
595
- try {
596
- await push(marker);
597
- return 'pushed';
598
- }
599
- catch (err) {
600
- const message = getErrorMessage(err);
601
- throwIfWorkflowRefusal(branch, message);
602
- // A refused lease means GitHub is not at the commit we rewrote, so the
603
- // marker is stale -- routine, not exceptional: tasks are re-run after a
604
- // crash (recoverOrphanedTasks) and the marker survives any failure to
605
- // clear it. The two benign shapes that reach here (GitHub already holds
606
- // the rewritten history, or the branch moved past it) are ordinary
607
- // fast-forwards a lease has no business blocking.
608
- //
609
- // So retry PLAIN and let git adjudicate: a non-forced push succeeds if and
610
- // only if it fast-forwards, so it can never destroy anything, with no
611
- // ancestry check to get wrong and no extra round trip to read GitHub's
612
- // tip. Only if THAT is also rejected has the branch genuinely diverged.
613
- //
614
- // (git evaluates the lease only when it actually has an update to apply,
615
- // so an up-to-date ref with a stale lease prints "Everything up-to-date",
616
- // exits 0, and is absorbed above without reaching here.)
617
- if (marker && isStaleLeaseRejection(message)) {
618
- try {
619
- await push();
620
- }
621
- catch (retryErr) {
622
- const retryMessage = getErrorMessage(retryErr);
623
- throwIfWorkflowRefusal(branch, retryMessage);
624
- if (isNonFastForwardRejection(retryMessage)) {
625
- throw new PermanentTaskError(`Push rejected for branch "${branch}": GitHub's tip is neither the commit this ` +
626
- `deployment last published nor an ancestor of what it is pushing, so the branch ` +
627
- `has genuinely diverged and nothing was overwritten. Something else moved it on ` +
628
- `GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`);
629
- }
630
- throw retryErr;
631
- }
632
- return 'pushed-past-stale-lease';
633
- }
634
- // An ordinary non-fast-forward rejection: GitHub has commits this
635
- // deployment never published, so retrying the identical push can never
636
- // succeed (DEP-L1's git-failure-is-transient carve-out does NOT apply) and
637
- // it fails fast instead of burning the retry budget. Deliberately does NOT
638
- // advise renaming the branch: one reaching this point usually has an open
639
- // PR, which renaming would orphan.
640
- if (isNonFastForwardRejection(message)) {
641
- throw new PermanentTaskError(`Push rejected for branch "${branch}": GitHub's tip is not what this deployment last ` +
642
- `published, so the branch has diverged and needs reconciling. Something else moved it ` +
643
- `on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`);
644
- }
551
+ let outcome;
552
+ try {
553
+ outcome = await ctx
554
+ .github()
555
+ .push({ branch, sha: outgoingSha, lease: marker, protectedBranches: [ctx.baseBranch] }, signal);
556
+ }
557
+ catch (err) {
558
+ // A retry can never make the branch not protected.
559
+ if (err instanceof RefusedPushError)
560
+ throw new PermanentTaskError(err.message);
561
+ if (!(err instanceof GitHubPushError))
645
562
  throw err;
563
+ throwIfWorkflowRefusal(branch, err.message);
564
+ // A refused lease was already retried plain, and that retry rejected too: only a
565
+ // non-fast-forward then means the branch has genuinely diverged.
566
+ if (err.kind === 'rejected-after-stale-lease') {
567
+ if (isNonFastForwardRejection(err.message)) {
568
+ throw new PermanentTaskError(`Push rejected for branch "${branch}": GitHub's tip is neither the commit this ` +
569
+ `deployment last published nor an ancestor of what it is pushing, so the branch ` +
570
+ `has genuinely diverged and nothing was overwritten. Something else moved it on ` +
571
+ `GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`);
572
+ }
573
+ throw err.cause;
646
574
  }
647
- });
648
- if (outcome === 'pushed-past-stale-lease') {
575
+ // An ordinary non-fast-forward rejection: GitHub has commits this
576
+ // deployment never published, so retrying the identical push can never
577
+ // succeed (DEP-L1's git-failure-is-transient carve-out does NOT apply) and
578
+ // it fails fast instead of burning the retry budget. Deliberately does NOT
579
+ // advise renaming the branch: one reaching this point usually has an open
580
+ // PR, which renaming would orphan.
581
+ if (isNonFastForwardRejection(err.message)) {
582
+ throw new PermanentTaskError(`Push rejected for branch "${branch}": GitHub's tip is not what this deployment last ` +
583
+ `published, so the branch has diverged and needs reconciling. Something else moved it ` +
584
+ `on GitHub -- a direct push, or another CanopyCMS deployment sharing this repository.`);
585
+ }
586
+ throw err.cause;
587
+ }
588
+ if (outcome.pastStaleLease) {
649
589
  // The lease was refused, so GitHub is provably not at the marker: it
650
590
  // has moved past the rewritten commit and the marker is spent.
651
591
  await clearHistoryRewrittenMarker(ctx, branchPath, branch);
652
592
  await recordPushedToGitHub(ctx, branchPath, branch);
653
- workerLog(`Pushed ${branch} to GitHub (GitHub had already moved past the rewritten commit)`);
593
+ logPushOutcome(ctx, branch, outcome, 'GitHub had already moved past the rewritten commit');
654
594
  return;
655
595
  }
656
596
  // Clear the marker only once GitHub is confirmed to hold something other
@@ -661,7 +601,17 @@ export async function pushBranchToGitHub(ctx, branch, signal) {
661
601
  await clearHistoryRewrittenMarker(ctx, branchPath, branch);
662
602
  }
663
603
  await recordPushedToGitHub(ctx, branchPath, branch);
664
- workerLog(`Pushed ${branch} to GitHub`);
604
+ logPushOutcome(ctx, branch, outcome);
605
+ }
606
+ /** An unmoved ref is routine (a task re-run after a crash), so it is logged at debug level. */
607
+ function logPushOutcome(ctx, branch, outcome, note) {
608
+ const suffix = note === undefined ? '' : `; ${note}`;
609
+ if (outcome.moved) {
610
+ workerLog(`Pushed ${branch} to GitHub (${describeMove(outcome)}${suffix})`);
611
+ }
612
+ else {
613
+ ctx.log.debug(`${branch} already up to date on GitHub${suffix}`);
614
+ }
665
615
  }
666
616
  /**
667
617
  * Stamp `pushedToGitHubAt`, branch delete's proof that the GitHub branch is this one. Recorded
@@ -1,8 +1,7 @@
1
- import type { Octokit } from '@octokit/rest';
2
1
  import type { SanitizedBranchName } from '../paths/types.js';
3
2
  import type { Task, TaskQueueLogger } from '../task-queue/cms-task-queue.js';
4
3
  import type { WorkerStatusReport } from '../types.js';
5
- import type { GitHubMirror } from './github-mirror.js';
4
+ import type { GitHubGateway } from './github-gateway.js';
6
5
  /**
7
6
  * The slice of {@link import('./cms-worker.js').CmsWorker} that its extracted
8
7
  * clusters (task-runner.ts, git-sync.ts, rebase.ts, history-rewrite.ts) may
@@ -14,19 +13,16 @@ import type { GitHubMirror } from './github-mirror.js';
14
13
  * back onto the live instance and never snapshotted, and `CmsWorker.ctx()`
15
14
  * builds a fresh context per call. That is load-bearing, not stylistic. The
16
15
  * `cms-worker*.test.ts` files drive the class by reaching through the instance:
17
- * they REPLACE `buildGitHubUrl` (aiming pushes at a local fixture repo rather
18
- * than github.com), `executeTask` and `pushBranchToGitHub`, ASSIGN a mock over
19
- * the `octokit` field, set `running` directly, and SUBCLASS to override the two
20
- * rebase test hooks. A context that captured any of those at construction would
21
- * hand the extracted code the pre-test value — which for `buildGitHubUrl` means
22
- * a test's push going to github.com for real. Functions (`ctx.octokit()`) make
23
- * the late binding visible at every call site, which a getter would hide.
16
+ * they INSTALL a GitHub gateway aimed at a local fixture repository (and an
17
+ * Octokit stub) with `useLocalGitHubGateway` (test-utils/worker-gateway.ts),
18
+ * REPLACE `executeTask` and `pushBranchToGitHub`, set `running` directly, and
19
+ * SUBCLASS to override the two rebase test hooks. A context that captured any
20
+ * of those at construction would hand the extracted code the pre-test value --
21
+ * which for the gateway means a test's push going to github.com for real.
22
+ * Functions (`ctx.github()`) make the late binding visible at every call site,
23
+ * which a getter would hide.
24
24
  */
25
25
  export interface WorkerContext {
26
- /** GitHub owner, e.g. 'safeinsights'. */
27
- readonly githubOwner: string;
28
- /** GitHub repo name. */
29
- readonly githubRepo: string;
30
26
  /**
31
27
  * The base branch's RAW git ref name. Git refs (fetch/rev-list/merge against
32
28
  * it) must use this; filesystem paths must use `sanitizedBaseBranch`.
@@ -60,35 +56,11 @@ export interface WorkerContext {
60
56
  readonly maxRetries: number;
61
57
  /** Task-queue debug logger. */
62
58
  readonly log: TaskQueueLogger;
63
- /** The worker's Octokit client, read at call time (tests replace it). */
64
- octokit(): Octokit;
65
59
  /**
66
- * The tokenized GitHub clone URL, resolved at call time (tests replace this
67
- * method to point at a local fixture repo). Async because under GitHub App
68
- * auth the credential is minted on demand and lasts about an hour (see
69
- * worker/github-auth.ts); the token path resolves immediately.
70
- *
71
- * A caller that needs the URL more than once resolves it ONCE into a local --
72
- * see pushBranchToGitHub, where a second resolution inside the stale-lease
73
- * catch would replace the very error being classified. Anything derived from
74
- * it can embed the bot token, so a message reaching worker-status.json,
75
- * branch.json or a task file goes through `redactCredentials` first.
60
+ * The worker's GitHub gateway (worker/github-gateway.ts), read at call time:
61
+ * every use of the GitHub credential and of Octokit goes through it.
76
62
  */
77
- buildGitHubUrl(): Promise<string>;
78
- /**
79
- * The worker's private GitHub mirror (worker/github-mirror.ts): every git command given a
80
- * `buildGitHubUrl()` URL runs inside one of its sessions, never in `remote.git` or a clone.
81
- */
82
- githubMirror(): GitHubMirror;
83
- /**
84
- * Re-read the GitHub credential because an operation that used it just
85
- * failed, read at call time. Best-effort and NEVER throws: a failed read, or
86
- * one that does not settle within `taskTimeoutMs`, is logged and swallowed,
87
- * because every caller is already reporting the failure that matters. The
88
- * next `buildGitHubUrl()`/`octokit()` sees any rotated value. See
89
- * `CmsWorker.refreshGitHubCredential`.
90
- */
91
- refreshGitHubCredential(): Promise<void>;
63
+ github(): GitHubGateway;
92
64
  /**
93
65
  * Workspace directory for a branch named by its GIT REF name — the form task
94
66
  * payloads carry. Sanitizes; `feature/x` lives in `feature-x`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@codemirror/language, @lezer/highlight: workaround — @mdxeditor/editor uses cm6-theme-basic-light which peer-requires these but mdxeditor doesn't declare them as dependencies",
3
3
  "name": "canopycms",
4
- "version": "0.0.68-int.111",
4
+ "version": "0.0.68-int.113",
5
5
  "description": "CanopyCMS core package: schema-driven content, branch-aware editing, and editor UI for Next.js.",
6
6
  "license": "MIT",
7
7
  "repository": {