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.
- package/CHANGELOG.md +326 -48
- package/dist/api/admin.d.ts +4 -4
- package/dist/api/branch-review.d.ts +20 -2
- package/dist/api/branch-review.js +153 -32
- package/dist/api/branch-status.d.ts +23 -2
- package/dist/api/branch-status.js +16 -2
- package/dist/api/branch-update-from-base.d.ts +23 -0
- package/dist/api/branch-update-from-base.js +227 -0
- package/dist/api/branch.d.ts +10 -0
- package/dist/api/branch.js +28 -2
- package/dist/api/client.d.ts +15 -2
- package/dist/api/client.js +24 -2
- package/dist/api/comments.d.ts +2 -2
- package/dist/api/content.d.ts +38 -1
- package/dist/api/content.js +41 -5
- package/dist/api/merge-conflicts.d.ts +235 -0
- package/dist/api/merge-conflicts.js +709 -0
- package/dist/api/routes.js +2 -0
- package/dist/api/types.d.ts +8 -3
- package/dist/base-update.d.ts +32 -0
- package/dist/base-update.js +31 -0
- package/dist/branch-metadata-file.d.ts +66 -0
- package/dist/branch-metadata-file.js +16 -1
- package/dist/branch-schema-cache.d.ts +12 -0
- package/dist/branch-schema-cache.js +27 -2
- package/dist/cli/backfill-item-keys.d.ts +77 -0
- package/dist/cli/backfill-item-keys.js +320 -0
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.js +9798 -1509
- package/dist/cli/generate-ai-content.js +373 -249
- package/dist/cli/github-app-manifest.d.ts +4 -1
- package/dist/cli/github-app-manifest.js +5 -1
- package/dist/cli/init.js +12 -0
- package/dist/cli/site-config.d.ts +14 -2
- package/dist/cli/site-config.js +20 -4
- package/dist/comment-store.d.ts +8 -0
- package/dist/comment-store.js +28 -0
- package/dist/content-index-generation.d.ts +1 -4
- package/dist/content-index-generation.js +1 -4
- package/dist/content-store.d.ts +39 -3
- package/dist/content-store.js +48 -15
- package/dist/editor/BranchManager.d.ts +4 -1
- package/dist/editor/BranchManager.js +9 -5
- package/dist/editor/Editor.js +462 -62
- package/dist/editor/EntryNavigator.d.ts +10 -2
- package/dist/editor/EntryNavigator.js +12 -4
- package/dist/editor/FormRenderer.d.ts +8 -2
- package/dist/editor/FormRenderer.js +5 -6
- package/dist/editor/admin/ConflictsStatusAlert.d.ts +8 -0
- package/dist/editor/admin/ConflictsStatusAlert.js +22 -0
- package/dist/editor/admin/SystemHealthPanel.js +4 -3
- package/dist/editor/base-update-wait.d.ts +78 -0
- package/dist/editor/base-update-wait.js +98 -0
- package/dist/editor/branch-status.js +1 -1
- package/dist/editor/components/BaseUpdateNotice.d.ts +12 -0
- package/dist/editor/components/BaseUpdateNotice.js +24 -0
- package/dist/editor/components/BranchPill.d.ts +4 -1
- package/dist/editor/components/BranchPill.js +5 -2
- package/dist/editor/components/ConflictsGroup.d.ts +112 -0
- package/dist/editor/components/ConflictsGroup.js +404 -0
- package/dist/editor/components/EditorHeader.d.ts +18 -1
- package/dist/editor/components/EditorHeader.js +99 -33
- package/dist/editor/components/EntryChangesDrawer.d.ts +16 -2
- package/dist/editor/components/EntryChangesDrawer.js +85 -167
- package/dist/editor/components/EntryHeader.d.ts +3 -1
- package/dist/editor/components/EntryHeader.js +2 -2
- package/dist/editor/components/GetLatestChangesButton.d.ts +15 -0
- package/dist/editor/components/GetLatestChangesButton.js +29 -0
- package/dist/editor/components/ReleaseHoldButton.d.ts +20 -0
- package/dist/editor/components/ReleaseHoldButton.js +36 -0
- package/dist/editor/components/RetryToastMessage.js +1 -1
- package/dist/editor/components/change-parts.d.ts +51 -0
- package/dist/editor/components/change-parts.js +171 -0
- package/dist/editor/conflict-cards.d.ts +93 -0
- package/dist/editor/conflict-cards.js +284 -0
- package/dist/editor/conflict-choices.d.ts +60 -0
- package/dist/editor/conflict-choices.js +108 -0
- package/dist/editor/conflict-notice.d.ts +25 -0
- package/dist/editor/conflict-notice.js +39 -0
- package/dist/editor/copy.d.ts +12 -0
- package/dist/editor/copy.js +12 -0
- package/dist/editor/entry-diff.d.ts +2 -1
- package/dist/editor/entry-diff.js +5 -21
- package/dist/editor/hooks/useBaseUpdate.d.ts +38 -0
- package/dist/editor/hooks/useBaseUpdate.js +279 -0
- package/dist/editor/hooks/useBranchChangesData.d.ts +7 -1
- package/dist/editor/hooks/useBranchChangesData.js +13 -0
- package/dist/editor/hooks/useBranchManager.d.ts +8 -1
- package/dist/editor/hooks/useBranchManager.js +108 -13
- package/dist/editor/hooks/useBranchesData.js +3 -0
- package/dist/editor/hooks/useConflictChoices.d.ts +24 -0
- package/dist/editor/hooks/useConflictChoices.js +75 -0
- package/dist/editor/hooks/useConflictsData.d.ts +26 -0
- package/dist/editor/hooks/useConflictsData.js +89 -0
- package/dist/editor/hooks/useDraftManager.d.ts +13 -1
- package/dist/editor/hooks/useDraftManager.js +40 -9
- package/dist/editor/hooks/useEntryManager.d.ts +17 -3
- package/dist/editor/hooks/useEntryManager.js +11 -5
- package/dist/editor/release-hold.d.ts +12 -0
- package/dist/editor/release-hold.js +39 -0
- package/dist/editor/server-clock.d.ts +4 -0
- package/dist/editor/server-clock.js +15 -0
- package/dist/editor/undo-change.d.ts +7 -0
- package/dist/editor/undo-change.js +19 -4
- package/dist/git-manager.d.ts +6 -0
- package/dist/git-manager.js +38 -3
- package/dist/item-keys.d.ts +13 -0
- package/dist/item-keys.js +36 -14
- package/dist/merge/conflict-path.d.ts +40 -0
- package/dist/merge/conflict-path.js +128 -0
- package/dist/merge/diff3.d.ts +27 -0
- package/dist/merge/diff3.js +113 -0
- package/dist/merge/entry-merge.d.ts +94 -0
- package/dist/merge/entry-merge.js +876 -0
- package/dist/merge/list-alignment.d.ts +55 -0
- package/dist/merge/list-alignment.js +490 -0
- package/dist/merge/rekey.d.ts +11 -0
- package/dist/merge/rekey.js +75 -0
- package/dist/merge/text-merge.d.ts +29 -0
- package/dist/merge/text-merge.js +59 -0
- package/dist/merge/value-equality.d.ts +18 -0
- package/dist/merge/value-equality.js +50 -0
- package/dist/merge-conflicts.d.ts +232 -0
- package/dist/merge-conflicts.js +451 -0
- package/dist/operating-mode/client-safe-strategy.d.ts +2 -0
- package/dist/operating-mode/client-safe-strategy.js +6 -0
- package/dist/operating-mode/client-unsafe-strategy.js +6 -0
- package/dist/operating-mode/types.d.ts +13 -0
- package/dist/services.d.ts +24 -1
- package/dist/services.js +138 -22
- package/dist/task-queue/cms-task-queue.d.ts +3 -1
- package/dist/types.d.ts +63 -5
- package/dist/utils/content-serialize.d.ts +18 -1
- package/dist/utils/content-serialize.js +12 -0
- package/dist/utils/content-write-lock.d.ts +3 -3
- package/dist/utils/content-write-lock.js +3 -3
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.js +35 -0
- package/dist/utils/markdown-body-splice.d.ts +1 -1
- package/dist/utils/markdown-body-splice.js +1 -19
- package/dist/utils/markdown-parse.d.ts +9 -0
- package/dist/utils/markdown-parse.js +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-merge.d.ts +80 -40
- package/dist/worker/branch-merge.js +244 -58
- package/dist/worker/branch-policy.d.ts +18 -2
- package/dist/worker/branch-policy.js +36 -0
- package/dist/worker/cms-worker.d.ts +37 -3
- package/dist/worker/cms-worker.js +88 -16
- package/dist/worker/conflicts-status.d.ts +20 -0
- package/dist/worker/conflicts-status.js +204 -0
- package/dist/worker/field-merge.d.ts +80 -0
- package/dist/worker/field-merge.js +729 -0
- package/dist/worker/git-sync.d.ts +3 -2
- package/dist/worker/git-sync.js +16 -6
- package/dist/worker/github-commits.d.ts +42 -10
- package/dist/worker/github-commits.js +444 -52
- package/dist/worker/github-errors.d.ts +15 -0
- package/dist/worker/github-errors.js +47 -0
- package/dist/worker/github-gateway-http.js +24 -9
- package/dist/worker/github-gateway-wire.d.ts +49 -20
- package/dist/worker/github-gateway-wire.js +19 -3
- package/dist/worker/github-gateway.d.ts +27 -1
- package/dist/worker/github-gateway.js +55 -7
- package/dist/worker/github-transfer.d.ts +5 -3
- package/dist/worker/github-transfer.js +8 -5
- package/dist/worker/history-rewrite.d.ts +40 -8
- package/dist/worker/history-rewrite.js +51 -23
- package/dist/worker/publish-decision.d.ts +37 -0
- package/dist/worker/publish-decision.js +16 -0
- package/dist/worker/rebase.d.ts +29 -8
- package/dist/worker/rebase.js +492 -211
- package/dist/worker/task-runner.d.ts +1 -1
- package/dist/worker/task-runner.js +30 -41
- package/dist/worker/worker-context.d.ts +24 -2
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ import type { WorkerContext } from './worker-context.js';
|
|
|
15
15
|
* here that reads branch metadata written by the rebase loop MUST re-read it
|
|
16
16
|
* rather than trust a snapshot.
|
|
17
17
|
*/
|
|
18
|
-
export type TaskRunnerContext = Pick<WorkerContext, 'baseBranch' | 'sanitizedBaseBranch' | 'taskDir' | 'remoteGitPath' | 'contentBranchesPath' | 'taskTimeoutMs' | 'gitSyncIntervalMs' | 'maxTasksPerCycle' | 'maxRetries' | 'log' | 'github' | 'branchWorkspacePath' | 'executeTask' | 'pushBranchToGitHub' | 'isRunning' | 'isDraining' | 'shutdownSignal' | 'ensureStatusReport'>;
|
|
18
|
+
export type TaskRunnerContext = Pick<WorkerContext, 'baseBranch' | 'sanitizedBaseBranch' | 'taskDir' | 'remoteGitPath' | 'contentBranchesPath' | 'taskTimeoutMs' | 'gitSyncIntervalMs' | 'maxTasksPerCycle' | 'maxRetries' | 'log' | 'github' | 'branchWorkspacePath' | 'executeTask' | 'pushBranchToGitHub' | 'isRunning' | 'isDraining' | 'shutdownSignal' | 'ensureStatusReport' | 'requestBranchSync'>;
|
|
19
19
|
/**
|
|
20
20
|
* An error inherent to the task itself (malformed payload, unknown action):
|
|
21
21
|
* retrying can never succeed, so the task should fail fast instead of
|
|
@@ -14,7 +14,9 @@ import { pushToGitHub } from './github-transfer.js';
|
|
|
14
14
|
import { clearHistoryRewrittenMarker, readPublishedSha } from './history-rewrite.js';
|
|
15
15
|
import { writeWorkerStatus } from '../task-queue/worker-status.js';
|
|
16
16
|
import { SYNC_PR_COMMENTS_ACTION, refreshCommentsBlock, renderPrCommentsBody } from '../pr-comments.js';
|
|
17
|
+
import { SYNC_BRANCH_ACTION } from '../base-update.js';
|
|
17
18
|
import { COMMIT_ID, REWRITTEN_PUSH_REFUSAL, SEEN_COMMIT_GONE_PUSH_REFUSAL, enqueueReviewBlockSync, } from './github-commits.js';
|
|
19
|
+
import { getHttpStatus, isRateLimitSignal403 } from './github-errors.js';
|
|
18
20
|
import { workerLog, workerLogError, workerLogWarn } from './log.js';
|
|
19
21
|
import { assertSharedRepoConfig } from './shared-repo-git.js';
|
|
20
22
|
/**
|
|
@@ -84,45 +86,6 @@ export function isPermanentTaskFailure(err) {
|
|
|
84
86
|
return false;
|
|
85
87
|
return status >= 400 && status < 500;
|
|
86
88
|
}
|
|
87
|
-
/** Extract an HTTP status from an error, if present (Octokit RequestError shape). */
|
|
88
|
-
function getHttpStatus(err) {
|
|
89
|
-
if (err instanceof Error && 'status' in err) {
|
|
90
|
-
const status = err.status;
|
|
91
|
-
if (typeof status === 'number')
|
|
92
|
-
return status;
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
/** Narrow an unknown value to a response-headers record, if present (Octokit lowercases header names). */
|
|
97
|
-
function getResponseHeaders(err) {
|
|
98
|
-
if (typeof err !== 'object' || err === null || !('response' in err))
|
|
99
|
-
return null;
|
|
100
|
-
const response = err.response;
|
|
101
|
-
if (typeof response !== 'object' || response === null || !('headers' in response))
|
|
102
|
-
return null;
|
|
103
|
-
const headers = response.headers;
|
|
104
|
-
if (typeof headers !== 'object' || headers === null)
|
|
105
|
-
return null;
|
|
106
|
-
return headers;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Whether a 403 is a GitHub rate-limit response rather than a plain permission
|
|
110
|
-
* denial. GitHub signals rate limiting on 403s three ways: the primary limit
|
|
111
|
-
* zeroes `x-ratelimit-remaining`, secondary limits often carry `retry-after`,
|
|
112
|
-
* and both produce a message containing "rate limit".
|
|
113
|
-
*/
|
|
114
|
-
function isRateLimitSignal403(err) {
|
|
115
|
-
const headers = getResponseHeaders(err);
|
|
116
|
-
if (headers) {
|
|
117
|
-
if (headers['x-ratelimit-remaining'] === '0')
|
|
118
|
-
return true;
|
|
119
|
-
if (typeof headers['retry-after'] === 'string' && headers['retry-after'].length > 0)
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
if (err instanceof Error && /rate limit/i.test(err.message))
|
|
123
|
-
return true;
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
89
|
// Payload validation helpers — fail fast with clear errors instead of silent `as` casts
|
|
127
90
|
/**
|
|
128
91
|
* Whether `branch` carries the reserved settings-branch prefix, which routes a submit to a push
|
|
@@ -491,6 +454,13 @@ export async function executeTask(ctx, task, signal) {
|
|
|
491
454
|
workerLog(`Updated the comment and review blocks of PR #${prNumber} for ${branch}`);
|
|
492
455
|
return { updated: true };
|
|
493
456
|
}
|
|
457
|
+
case SYNC_BRANCH_ACTION: {
|
|
458
|
+
// Only a wake-up: the git sync does the work, so the fetch, the schema gate and the locks
|
|
459
|
+
// keep one path. The request itself is recorded on branch.json, which outlives this.
|
|
460
|
+
const branch = requireString(payload, 'branch');
|
|
461
|
+
ctx.requestBranchSync(branch);
|
|
462
|
+
return { requested: true };
|
|
463
|
+
}
|
|
494
464
|
default:
|
|
495
465
|
throw new PermanentTaskError(`Unknown task action: ${action}`);
|
|
496
466
|
}
|
|
@@ -535,11 +505,15 @@ function editorBranchOf(payload, branch) {
|
|
|
535
505
|
/**
|
|
536
506
|
* The branch whose metadata records a task's outcome, or null when none does. A
|
|
537
507
|
* delete-remote-branch task names a branch already deleted here, so a workspace under that
|
|
538
|
-
* name is leftover or a newer branch's that reused the name.
|
|
508
|
+
* name is leftover or a newer branch's that reused the name. A sync-branch task syncs nothing
|
|
509
|
+
* with GitHub, so recording it would clear the branch's own failed sync.
|
|
539
510
|
*/
|
|
540
511
|
function metadataBranchOf(task) {
|
|
541
|
-
if (task.action === 'delete-remote-branch' ||
|
|
512
|
+
if (task.action === 'delete-remote-branch' ||
|
|
513
|
+
task.action === SYNC_PR_COMMENTS_ACTION ||
|
|
514
|
+
task.action === SYNC_BRANCH_ACTION) {
|
|
542
515
|
return null;
|
|
516
|
+
}
|
|
543
517
|
return typeof task.payload.branch === 'string' ? task.payload.branch : null;
|
|
544
518
|
}
|
|
545
519
|
/**
|
|
@@ -590,6 +564,7 @@ export async function updateBranchMetadata(ctx, task, result) {
|
|
|
590
564
|
return;
|
|
591
565
|
}
|
|
592
566
|
let found = false;
|
|
567
|
+
let newPullRequest = false;
|
|
593
568
|
// Bookkeeping: the save that queued this already said when the branch last changed.
|
|
594
569
|
await meta.update((existing) => {
|
|
595
570
|
if (existing?.branch.name !== branch)
|
|
@@ -614,6 +589,7 @@ export async function updateBranchMetadata(ctx, task, result) {
|
|
|
614
589
|
if (result.prUrl && !archived)
|
|
615
590
|
updates.pullRequestUrl = result.prUrl;
|
|
616
591
|
if (result.prNumber && !archived) {
|
|
592
|
+
newPullRequest = existing.branch.pullRequestNumber !== result.prNumber;
|
|
617
593
|
updates.pullRequestNumber = result.prNumber;
|
|
618
594
|
// As soon as the PR exists the field reads 'open', rather than staying
|
|
619
595
|
// absent until the next poll cycle observes it. Exception: the git-sync
|
|
@@ -629,6 +605,9 @@ export async function updateBranchMetadata(ctx, task, result) {
|
|
|
629
605
|
}, { keepUpdatedAt: true });
|
|
630
606
|
if (!found)
|
|
631
607
|
noMetadataToRecord(branch, `${task.action} outcome`);
|
|
608
|
+
// The sync its push woke may have run before the number was recorded, and skipped the branch.
|
|
609
|
+
if (newPullRequest)
|
|
610
|
+
ctx.requestBranchSync(branch);
|
|
632
611
|
}
|
|
633
612
|
catch (err) {
|
|
634
613
|
workerLogError(`Failed to update metadata for ${branch}:`, err instanceof Error ? err.message : err);
|
|
@@ -836,6 +815,7 @@ export async function pushBranchToGitHub(ctx, branch, signal) {
|
|
|
836
815
|
// has moved past the rewritten commit and the marker is spent.
|
|
837
816
|
await clearHistoryRewrittenMarker(ctx, branchPath, branch);
|
|
838
817
|
await recordPushedToGitHub(ctx, branchPath, branch, outgoingSha);
|
|
818
|
+
wakeForConflictsStatus(ctx, branch, outcome);
|
|
839
819
|
logPushOutcome(ctx, branch, outcome, 'GitHub had already moved past the rewritten commit');
|
|
840
820
|
return;
|
|
841
821
|
}
|
|
@@ -847,8 +827,17 @@ export async function pushBranchToGitHub(ctx, branch, signal) {
|
|
|
847
827
|
await clearHistoryRewrittenMarker(ctx, branchPath, branch);
|
|
848
828
|
}
|
|
849
829
|
await recordPushedToGitHub(ctx, branchPath, branch, outgoingSha);
|
|
830
|
+
wakeForConflictsStatus(ctx, branch, outcome);
|
|
850
831
|
logPushOutcome(ctx, branch, outcome);
|
|
851
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* A pull request's new head has no conflicts status until a git sync sets one
|
|
835
|
+
* (worker/conflicts-status.ts), and a required check waits on it: wake the sync for the branch.
|
|
836
|
+
*/
|
|
837
|
+
function wakeForConflictsStatus(ctx, branch, outcome) {
|
|
838
|
+
if (outcome.moved && !isSettingsBranch(branch))
|
|
839
|
+
ctx.requestBranchSync(branch);
|
|
840
|
+
}
|
|
852
841
|
/** An unmoved ref is routine (a task re-run after a crash), so it is logged at debug level. */
|
|
853
842
|
function logPushOutcome(ctx, branch, outcome, note) {
|
|
854
843
|
const suffix = note === undefined ? '' : `; ${note}`;
|
|
@@ -111,8 +111,30 @@ export interface WorkerContext {
|
|
|
111
111
|
*/
|
|
112
112
|
warnedDivergences(): Map<string, string>;
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
114
|
+
* The conflicts status this worker last set on each branch's pull request head, by branch name,
|
|
115
|
+
* and the tip each `not-head:` key last woke a sync for (worker/conflicts-status.ts). Held per
|
|
116
|
+
* instance, so a restarted worker sets each once again.
|
|
117
|
+
*/
|
|
118
|
+
conflictsStatusesSet(): Map<string, string>;
|
|
119
|
+
/**
|
|
120
|
+
* Run the git sync early, coalesced: once it has rested `EARLY_RUN_MIN_REST_MS` (cms-worker.ts),
|
|
121
|
+
* or its own interval if shorter, counting from when the last cycle ended, so a request during a
|
|
122
|
+
* cycle waits that long after it. `branchRef`, when given, is synced ahead of the rest. In memory only: a restart loses
|
|
123
|
+
* it, and the request recorded on the branch's branch.json still gets its answer from the next
|
|
124
|
+
* regular cycle (worker/rebase.ts).
|
|
125
|
+
*/
|
|
126
|
+
requestBranchSync(branchRef?: string): void;
|
|
127
|
+
/**
|
|
128
|
+
* What was asked since the last call, which clears it: the branches, by workspace directory name,
|
|
129
|
+
* and whether anything asked at all (a request with no branch wakes the sync too).
|
|
130
|
+
*/
|
|
131
|
+
takeBranchSyncRequests(): {
|
|
132
|
+
branches: Set<string>;
|
|
133
|
+
requested: boolean;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Test hook: fires when a rebase round has reported conflicted files, before
|
|
137
|
+
* the rebase is aborted for a merge. No-op in production.
|
|
116
138
|
*/
|
|
117
139
|
afterConflictDetectedForTesting(): Promise<void>;
|
|
118
140
|
/**
|
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.
|
|
4
|
+
"version": "0.13.0",
|
|
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": {
|