canopycms 0.12.0 → 0.13.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 (177) hide show
  1. package/CHANGELOG.md +326 -48
  2. package/dist/api/admin.d.ts +4 -4
  3. package/dist/api/branch-review.d.ts +20 -2
  4. package/dist/api/branch-review.js +153 -32
  5. package/dist/api/branch-status.d.ts +23 -2
  6. package/dist/api/branch-status.js +16 -2
  7. package/dist/api/branch-update-from-base.d.ts +23 -0
  8. package/dist/api/branch-update-from-base.js +227 -0
  9. package/dist/api/branch.d.ts +10 -0
  10. package/dist/api/branch.js +28 -2
  11. package/dist/api/client.d.ts +15 -2
  12. package/dist/api/client.js +24 -2
  13. package/dist/api/comments.d.ts +2 -2
  14. package/dist/api/content.d.ts +38 -1
  15. package/dist/api/content.js +41 -5
  16. package/dist/api/merge-conflicts.d.ts +235 -0
  17. package/dist/api/merge-conflicts.js +709 -0
  18. package/dist/api/routes.js +2 -0
  19. package/dist/api/types.d.ts +8 -3
  20. package/dist/base-update.d.ts +32 -0
  21. package/dist/base-update.js +31 -0
  22. package/dist/branch-metadata-file.d.ts +66 -0
  23. package/dist/branch-metadata-file.js +16 -1
  24. package/dist/branch-schema-cache.d.ts +12 -0
  25. package/dist/branch-schema-cache.js +27 -2
  26. package/dist/cli/backfill-item-keys.d.ts +77 -0
  27. package/dist/cli/backfill-item-keys.js +320 -0
  28. package/dist/cli/cli.d.ts +1 -1
  29. package/dist/cli/cli.js +9798 -1509
  30. package/dist/cli/generate-ai-content.js +373 -249
  31. package/dist/cli/github-app-manifest.d.ts +4 -1
  32. package/dist/cli/github-app-manifest.js +5 -1
  33. package/dist/cli/init.js +12 -0
  34. package/dist/cli/site-config.d.ts +14 -2
  35. package/dist/cli/site-config.js +20 -4
  36. package/dist/comment-store.d.ts +8 -0
  37. package/dist/comment-store.js +28 -0
  38. package/dist/content-index-generation.d.ts +1 -4
  39. package/dist/content-index-generation.js +1 -4
  40. package/dist/content-store.d.ts +39 -3
  41. package/dist/content-store.js +48 -15
  42. package/dist/editor/BranchManager.d.ts +4 -1
  43. package/dist/editor/BranchManager.js +9 -5
  44. package/dist/editor/Editor.js +462 -62
  45. package/dist/editor/EntryNavigator.d.ts +10 -2
  46. package/dist/editor/EntryNavigator.js +12 -4
  47. package/dist/editor/FormRenderer.d.ts +8 -2
  48. package/dist/editor/FormRenderer.js +5 -6
  49. package/dist/editor/admin/ConflictsStatusAlert.d.ts +8 -0
  50. package/dist/editor/admin/ConflictsStatusAlert.js +22 -0
  51. package/dist/editor/admin/SystemHealthPanel.js +4 -3
  52. package/dist/editor/base-update-wait.d.ts +78 -0
  53. package/dist/editor/base-update-wait.js +98 -0
  54. package/dist/editor/branch-status.js +1 -1
  55. package/dist/editor/components/BaseUpdateNotice.d.ts +12 -0
  56. package/dist/editor/components/BaseUpdateNotice.js +24 -0
  57. package/dist/editor/components/BranchPill.d.ts +4 -1
  58. package/dist/editor/components/BranchPill.js +5 -2
  59. package/dist/editor/components/ConflictsGroup.d.ts +112 -0
  60. package/dist/editor/components/ConflictsGroup.js +404 -0
  61. package/dist/editor/components/EditorHeader.d.ts +18 -1
  62. package/dist/editor/components/EditorHeader.js +99 -33
  63. package/dist/editor/components/EntryChangesDrawer.d.ts +16 -2
  64. package/dist/editor/components/EntryChangesDrawer.js +85 -167
  65. package/dist/editor/components/EntryHeader.d.ts +3 -1
  66. package/dist/editor/components/EntryHeader.js +2 -2
  67. package/dist/editor/components/GetLatestChangesButton.d.ts +15 -0
  68. package/dist/editor/components/GetLatestChangesButton.js +29 -0
  69. package/dist/editor/components/ReleaseHoldButton.d.ts +20 -0
  70. package/dist/editor/components/ReleaseHoldButton.js +36 -0
  71. package/dist/editor/components/RetryToastMessage.js +1 -1
  72. package/dist/editor/components/change-parts.d.ts +51 -0
  73. package/dist/editor/components/change-parts.js +171 -0
  74. package/dist/editor/conflict-cards.d.ts +93 -0
  75. package/dist/editor/conflict-cards.js +284 -0
  76. package/dist/editor/conflict-choices.d.ts +60 -0
  77. package/dist/editor/conflict-choices.js +108 -0
  78. package/dist/editor/conflict-notice.d.ts +25 -0
  79. package/dist/editor/conflict-notice.js +39 -0
  80. package/dist/editor/copy.d.ts +12 -0
  81. package/dist/editor/copy.js +12 -0
  82. package/dist/editor/entry-diff.d.ts +2 -1
  83. package/dist/editor/entry-diff.js +5 -21
  84. package/dist/editor/hooks/useBaseUpdate.d.ts +38 -0
  85. package/dist/editor/hooks/useBaseUpdate.js +279 -0
  86. package/dist/editor/hooks/useBranchChangesData.d.ts +7 -1
  87. package/dist/editor/hooks/useBranchChangesData.js +13 -0
  88. package/dist/editor/hooks/useBranchManager.d.ts +8 -1
  89. package/dist/editor/hooks/useBranchManager.js +108 -13
  90. package/dist/editor/hooks/useBranchesData.js +3 -0
  91. package/dist/editor/hooks/useConflictChoices.d.ts +24 -0
  92. package/dist/editor/hooks/useConflictChoices.js +75 -0
  93. package/dist/editor/hooks/useConflictsData.d.ts +26 -0
  94. package/dist/editor/hooks/useConflictsData.js +89 -0
  95. package/dist/editor/hooks/useDraftManager.d.ts +13 -1
  96. package/dist/editor/hooks/useDraftManager.js +40 -9
  97. package/dist/editor/hooks/useEntryManager.d.ts +17 -3
  98. package/dist/editor/hooks/useEntryManager.js +11 -5
  99. package/dist/editor/release-hold.d.ts +12 -0
  100. package/dist/editor/release-hold.js +39 -0
  101. package/dist/editor/server-clock.d.ts +4 -0
  102. package/dist/editor/server-clock.js +15 -0
  103. package/dist/editor/undo-change.d.ts +7 -0
  104. package/dist/editor/undo-change.js +19 -4
  105. package/dist/git-manager.d.ts +6 -0
  106. package/dist/git-manager.js +38 -3
  107. package/dist/item-keys.d.ts +13 -0
  108. package/dist/item-keys.js +36 -14
  109. package/dist/merge/conflict-path.d.ts +40 -0
  110. package/dist/merge/conflict-path.js +128 -0
  111. package/dist/merge/diff3.d.ts +27 -0
  112. package/dist/merge/diff3.js +113 -0
  113. package/dist/merge/entry-merge.d.ts +94 -0
  114. package/dist/merge/entry-merge.js +876 -0
  115. package/dist/merge/list-alignment.d.ts +55 -0
  116. package/dist/merge/list-alignment.js +490 -0
  117. package/dist/merge/rekey.d.ts +11 -0
  118. package/dist/merge/rekey.js +75 -0
  119. package/dist/merge/text-merge.d.ts +29 -0
  120. package/dist/merge/text-merge.js +59 -0
  121. package/dist/merge/value-equality.d.ts +18 -0
  122. package/dist/merge/value-equality.js +50 -0
  123. package/dist/merge-conflicts.d.ts +232 -0
  124. package/dist/merge-conflicts.js +451 -0
  125. package/dist/operating-mode/client-safe-strategy.d.ts +2 -0
  126. package/dist/operating-mode/client-safe-strategy.js +6 -0
  127. package/dist/operating-mode/client-unsafe-strategy.js +6 -0
  128. package/dist/operating-mode/types.d.ts +13 -0
  129. package/dist/services.d.ts +24 -1
  130. package/dist/services.js +138 -22
  131. package/dist/task-queue/cms-task-queue.d.ts +3 -1
  132. package/dist/types.d.ts +63 -5
  133. package/dist/utils/content-serialize.d.ts +18 -1
  134. package/dist/utils/content-serialize.js +12 -0
  135. package/dist/utils/content-write-lock.d.ts +3 -3
  136. package/dist/utils/content-write-lock.js +3 -3
  137. package/dist/utils/git.d.ts +7 -0
  138. package/dist/utils/git.js +35 -0
  139. package/dist/utils/markdown-body-splice.d.ts +1 -1
  140. package/dist/utils/markdown-body-splice.js +1 -19
  141. package/dist/utils/markdown-parse.d.ts +9 -0
  142. package/dist/utils/markdown-parse.js +27 -0
  143. package/dist/version.d.ts +1 -1
  144. package/dist/version.js +1 -1
  145. package/dist/worker/branch-merge.d.ts +80 -40
  146. package/dist/worker/branch-merge.js +244 -58
  147. package/dist/worker/branch-policy.d.ts +18 -2
  148. package/dist/worker/branch-policy.js +36 -0
  149. package/dist/worker/cms-worker.d.ts +37 -3
  150. package/dist/worker/cms-worker.js +88 -16
  151. package/dist/worker/conflicts-status.d.ts +20 -0
  152. package/dist/worker/conflicts-status.js +204 -0
  153. package/dist/worker/field-merge.d.ts +80 -0
  154. package/dist/worker/field-merge.js +729 -0
  155. package/dist/worker/git-sync.d.ts +3 -2
  156. package/dist/worker/git-sync.js +16 -6
  157. package/dist/worker/github-commits.d.ts +42 -10
  158. package/dist/worker/github-commits.js +444 -52
  159. package/dist/worker/github-errors.d.ts +15 -0
  160. package/dist/worker/github-errors.js +47 -0
  161. package/dist/worker/github-gateway-http.js +24 -9
  162. package/dist/worker/github-gateway-wire.d.ts +49 -20
  163. package/dist/worker/github-gateway-wire.js +19 -3
  164. package/dist/worker/github-gateway.d.ts +27 -1
  165. package/dist/worker/github-gateway.js +55 -7
  166. package/dist/worker/github-transfer.d.ts +5 -3
  167. package/dist/worker/github-transfer.js +8 -5
  168. package/dist/worker/history-rewrite.d.ts +40 -8
  169. package/dist/worker/history-rewrite.js +51 -23
  170. package/dist/worker/publish-decision.d.ts +37 -0
  171. package/dist/worker/publish-decision.js +16 -0
  172. package/dist/worker/rebase.d.ts +29 -8
  173. package/dist/worker/rebase.js +492 -211
  174. package/dist/worker/task-runner.d.ts +1 -1
  175. package/dist/worker/task-runner.js +30 -41
  176. package/dist/worker/worker-context.d.ts +24 -2
  177. package/package.json +1 -1
@@ -16,7 +16,8 @@ import type { WorkerContext } from './worker-context.js';
16
16
  * says the serving editor lacks a schema the incoming content names), push this
17
17
  * deployment's own settings
18
18
  * branch, fast-forward the base branch's workspace, ask the gateway for its branch policy,
19
- * rebase every branch that is behind it (rebase.ts), then sweep old tasks, expired trashed
19
+ * rebase every branch that is behind it (rebase.ts), set each pull request's conflicts status
20
+ * (conflicts-status.ts), then sweep old tasks, expired trashed
20
21
  * branch directories and, where retention is on, expired archived branches.
21
22
  *
22
23
  * One ordering is load-bearing and nothing enforces it: `runRebaseCycle` MUST
@@ -32,7 +33,7 @@ import type { WorkerContext } from './worker-context.js';
32
33
  * The whole cycle is wrapped so both outcomes record a worker-status.json
33
34
  * snapshot, since an operator's only view of this loop is the admin panel.
34
35
  */
35
- export type GitSyncContext = Pick<WorkerContext, 'baseBranch' | 'sanitizedBaseBranch' | 'contentBranchesPath' | 'remoteGitPath' | 'schemaHoldMaxMs' | 'archivedBranchRetentionDays' | 'taskDir' | 'taskTimeoutMs' | 'log' | 'github' | 'ensureSettingsBranch' | 'ensureStatusReport' | 'warnedDivergences' | 'isRunning' | 'isDraining' | 'shutdownSignal'> & RebaseContext;
36
+ export type GitSyncContext = Pick<WorkerContext, 'baseBranch' | 'sanitizedBaseBranch' | 'contentBranchesPath' | 'remoteGitPath' | 'schemaHoldMaxMs' | 'archivedBranchRetentionDays' | 'taskDir' | 'taskTimeoutMs' | 'log' | 'github' | 'ensureSettingsBranch' | 'ensureStatusReport' | 'warnedDivergences' | 'conflictsStatusesSet' | 'isRunning' | 'takeBranchSyncRequests' | 'isDraining' | 'shutdownSignal'> & RebaseContext;
36
37
  /**
37
38
  * Push THIS deployment's own settings branch (`ensureSettingsBranch()`) from
38
39
  * remote.git to GitHub. Non-fatal. Skipped when `trackedTips`, GitHub's tips
@@ -14,6 +14,7 @@ import { escapeForLog } from '../utils/escape-text.js';
14
14
  import { CANOPY_META_DIR, isCanopyInternalPath, isNonFastForwardRejection } from '../utils/git.js';
15
15
  import { MAX_REPORTED_PATHS, TRACKED_CANOPY_STATE_FIX, isUntracked, listTrackedCanopyState, restoreRetiredSchemaCache, splitByUpstreamTracking, untrackInIndex, } from './canopy-state.js';
16
16
  import { sweepArchivedBranches } from './archived-sweep.js';
17
+ import { setConflictsStatuses } from './conflicts-status.js';
17
18
  import { hasPendingHistoryRewrite, recordSeenOnGitHub } from './history-rewrite.js';
18
19
  import { runRebaseCycle } from './rebase.js';
19
20
  import { describeMove } from './github-gateway.js';
@@ -418,6 +419,9 @@ function warnOnce(warned, lines) {
418
419
  export async function syncGit(ctx) {
419
420
  if (!ctx.isRunning())
420
421
  return;
422
+ // Taken first, so a cycle that fails before its fetch can't leave the loop waking early for good.
423
+ // A request made later in the cycle waits for the next one, whose fetch begins after it.
424
+ const requested = ctx.takeBranchSyncRequests();
421
425
  // The CDK worker-down alarm counts this line (WORKER_SYNC_LOG_PHRASE in
422
426
  // canopycms-cdk worker-lifecycle.ts); changing its text breaks the alarm.
423
427
  workerLog('Syncing git...');
@@ -468,8 +472,9 @@ export async function syncGit(ctx) {
468
472
  // deadline: the wait for the gateway's GitHub fetch, which runs on detached, then the gateway's
469
473
  // bundle, the unbundle and the ref update; a killed ref update leaves each ref as it was or
470
474
  // fully moved.
471
- const fetchedAt = Date.now();
472
- const { refs: githubTips, needsOperator } = await fetchGitHubIntoRemoteGit(ctx, ctx.shutdownSignal());
475
+ // A woken cycle takes no GitHub fetch the gateway began earlier: answers to a request for the
476
+ // latest changes are timed by this fetch's start (worker/rebase.ts, answerBaseUpdate).
477
+ const { refs: githubTips, fetchedAt, needsOperator, } = await fetchGitHubIntoRemoteGit(ctx, ctx.shutdownSignal(), requested.requested);
473
478
  workerLog('Fetched from GitHub');
474
479
  if (needsOperator !== null)
475
480
  workerLogError(needsOperator);
@@ -512,12 +517,12 @@ export async function syncGit(ctx) {
512
517
  report.gatewayProtectedBranches = branchPolicy.configured.filter((n) => !n.includes('*'));
513
518
  await writeWorkerStatus(ctx.taskDir, report).catch((writeErr) => workerLogError('Failed to write worker status:', getErrorMessage(writeErr)));
514
519
  }
515
- const rebaseSummary = await runRebaseCycle(ctx, branchPolicy, {
516
- tips: githubTips,
517
- fetchedAt,
518
- });
520
+ const rebaseSummary = await runRebaseCycle(ctx, branchPolicy, { tips: githubTips, fetchedAt }, requested.branches);
519
521
  if (stoppedForDrain(ctx, 'the cleanup sweeps'))
520
522
  return;
523
+ // After the rebase cycle, whose merges can change a branch's conflicts. Heads the cycle's own
524
+ // pushes move are set by the cycle each push wakes.
525
+ const conflictsStatusFailure = await setConflictsStatuses(ctx, githubTips);
521
526
  await cleanupOldTasks(ctx.taskDir, undefined, ctx.log);
522
527
  // For System health: the branches the gateway would refuse to publish, read after the rebase
523
528
  // cycle, which archives merged branches and restores renamed ones. Without a policy, a fatal
@@ -550,6 +555,11 @@ export async function syncGit(ctx) {
550
555
  const report = ctx.ensureStatusReport();
551
556
  report.lastGitSyncAt = new Date().toISOString();
552
557
  delete report.lastGitSyncError;
558
+ // null: the stage could not say, so the last report stands.
559
+ if (conflictsStatusFailure)
560
+ report.conflictsStatusFailure = conflictsStatusFailure;
561
+ else if (conflictsStatusFailure === undefined)
562
+ delete report.conflictsStatusFailure;
553
563
  // Recorded once the cycle has done everything it can, so System health shows it.
554
564
  if (needsOperator !== null) {
555
565
  report.lastGitSyncError = { message: needsOperator, at: report.lastGitSyncAt };
@@ -44,9 +44,13 @@ export type GitHubCommitsOutcome =
44
44
  | {
45
45
  kind: 'unpushed';
46
46
  }
47
- /** A merge landed. A rebase now would replace a merge GitHub has not received. */
47
+ /**
48
+ * A merge landed. A rebase now would replace a merge GitHub has not received. `keptEntries`
49
+ * counts the entries with a conflict it left for a person.
50
+ */
48
51
  | {
49
52
  kind: 'merged';
53
+ keptEntries: number;
50
54
  }
51
55
  /** Left as it is until a person acts. `reason` says what to do. */
52
56
  | {
@@ -76,28 +80,56 @@ export declare const SEEN_COMMIT_GONE_PUSH_REFUSAL = "A commit this branch had o
76
80
  * be combined. The failed sync it left goes too, unless {@link awaitsSubmitRetry}.
77
81
  */
78
82
  export declare function rewriteCleared(branch: BranchMetadata): Partial<BranchMetadata>;
83
+ /** A branch this deployment put on GitHub. Any other GitHub branch of that name is not its own. */
84
+ export declare function isPublished({ branch }: BranchMetadataFile): boolean;
85
+ /**
86
+ * Fast-forward the clone to the merge this worker was landing, when `remote.git` holds it on top of
87
+ * the clone, as a crash or a lost lock between publishing it and moving the clone leaves it,
88
+ * whether or not the branch is on GitHub. Resolves why it stopped, or null when there was nothing
89
+ * to catch up with or it caught up.
90
+ */
91
+ export declare function catchUpWithPublished(ctx: GitHubCommitsContext, args: GitHubCommitsArgs): Promise<GitHubCommitsOutcome | null>;
79
92
  /** Bring GitHub's commits into the clone. Throws what git throws; the caller records it. */
80
93
  export declare function bringInGitHubCommits(ctx: GitHubCommitsContext, args: GitHubCommitsArgs): Promise<GitHubCommitsOutcome>;
81
94
  /**
82
- * `other` merged into the branch: built, published to `remote.git`, queued for GitHub, and the
83
- * clone moved to it, in that order, so each step leaves what the next cycle picks up and only the
84
- * last touches the working tree. `fromGitHub` says `other` is GitHub's tip rather than the base.
85
- * `stopOnConflict` leaves a branch in review as it is: nobody is at the editor to see what a kept
86
- * version replaced, and GitHub's own conflict notice stands on the pull request.
95
+ * `other` merged into the branch: built, recorded, published to `remote.git` and queued for GitHub
96
+ * unless it holds unsubmitted work (publish-decision.ts), and the clone moved to it, in that order,
97
+ * so each step leaves what the next cycle picks up and only the last touches the working tree. `fromGitHub` says `other` is GitHub's tip rather than the
98
+ * base. `inReview` takes only a merge with nothing for a person to choose; any other leaves the
99
+ * branch as it is and records nothing, and GitHub's own conflict notice stands on the pull request. A lock lost before the clone moves leaves it where it was: `wait`.
87
100
  */
88
101
  export declare function mergeIntoBranch(ctx: GitHubCommitsContext, args: GitHubCommitsArgs, merge: {
89
102
  head: string;
90
103
  other: string;
91
104
  message: string;
92
105
  fromGitHub: boolean;
93
- stopOnConflict?: boolean;
106
+ inReview?: boolean;
94
107
  }): Promise<GitHubCommitsOutcome>;
95
108
  /**
96
- * Send an approved branch back to review and clear the verdict from the PR body. Before the clone
97
- * moves, and a failed save throws, so a crash or a failure leaves the commits for the next cycle
98
- * to judge again rather than an approval standing over them.
109
+ * Whether `commit` contains `ancestor`. A commit this clone does not hold counts as no. Any other
110
+ * failure throws, since it says nothing about either commit, and the caller's catch retries next
111
+ * cycle: read as no, it would report a rewrite that never happened.
112
+ */
113
+ export declare function contains(git: SimpleGit, commit: string, ancestor: string): Promise<boolean>;
114
+ /**
115
+ * Send an approved branch back to review and clear the verdict from the PR body. Called before the
116
+ * clone moves, where a failed save throws, so a crash or a failure leaves the commits for the next
117
+ * cycle to judge again rather than an approval standing over them.
99
118
  */
100
119
  export declare function returnToReview(ctx: GitHubCommitsContext, { branchPath, branchDir, branchRef, metaFile }: GitHubCommitsArgs): Promise<void>;
120
+ /**
121
+ * Bring merge-conflicts.json in line with the branch's history: a record changed by a merge the
122
+ * branch never got gives back the one it replaced (`withoutMergesGone`), and `branch.json`
123
+ * `conflictFiles` follows the record when the two differ. A merge counts as held while the clone or
124
+ * `remote.git` holds it, since a clone behind its published merge catches up with it; comment
125
+ * threads take a merge's item keys only once the clone holds it. While either can't be read in the
126
+ * clone, nothing is judged. A record that does not parse is moved aside, so the branch's merges
127
+ * carry on with a new one. Each cycle, before the branch takes anything in. A clone with no record
128
+ * has no conflict open, so `conflictFiles` written before the record existed clears. Never throws.
129
+ */
130
+ export declare function reconcileMergeConflicts(ctx: GitHubCommitsContext, { git, branchPath, branchDir, branchRef, }: Pick<GitHubCommitsArgs, 'git' | 'branchPath' | 'branchDir'> & {
131
+ branchRef: string | null;
132
+ }): Promise<void>;
101
133
  /**
102
134
  * Re-render the PR body's review block alone: the worker has no `config.editorUrl`, so a full
103
135
  * sync from here would strip the comment summary's links. Best-effort: a failure leaves the block