canopycms 0.6.0 → 0.7.1
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 +188 -47
- package/dist/api/branch-review.d.ts +11 -1
- package/dist/api/branch-review.js +96 -18
- package/dist/api/branch-status.d.ts +26 -2
- package/dist/api/branch-status.js +38 -2
- package/dist/api/branch-withdraw.js +4 -7
- package/dist/api/branch.d.ts +2 -0
- package/dist/api/client.d.ts +3 -3
- package/dist/api/client.js +4 -4
- package/dist/api/comments.d.ts +2 -2
- package/dist/api/comments.js +4 -0
- package/dist/api/content.d.ts +2 -2
- package/dist/api/github-sync.d.ts +4 -0
- package/dist/api/github-sync.js +38 -6
- package/dist/api/pr-comment-sync.d.ts +18 -0
- package/dist/api/pr-comment-sync.js +77 -0
- package/dist/api/request-deadline.d.ts +15 -7
- package/dist/api/request-deadline.js +15 -7
- package/dist/api/schema.d.ts +16 -16
- package/dist/authorization/review-role.d.ts +33 -0
- package/dist/authorization/review-role.js +17 -0
- package/dist/branch-metadata-file.d.ts +3 -0
- package/dist/branch-metadata-file.js +1 -0
- package/dist/cli/cli.js +699 -130
- package/dist/cli/generate-ai-content.js +524 -59
- package/dist/cli/github-app-output.d.ts +34 -0
- package/dist/cli/github-app-output.js +85 -0
- package/dist/cli/init-github-app.d.ts +18 -0
- package/dist/cli/init-github-app.js +66 -58
- package/dist/cli/init.js +10 -1
- package/dist/comment-store.d.ts +5 -0
- package/dist/comment-store.js +24 -0
- package/dist/config/helpers.js +1 -3
- package/dist/config/schemas/collection.d.ts +2 -2
- package/dist/config/schemas/config.d.ts +3 -0
- package/dist/config/schemas/config.js +2 -1
- package/dist/config/schemas/url.d.ts +6 -0
- package/dist/config/schemas/url.js +16 -0
- package/dist/config/types.d.ts +12 -1
- package/dist/content-id-index.d.ts +9 -0
- package/dist/content-id-index.js +31 -0
- package/dist/editor/BranchManager.d.ts +3 -0
- package/dist/editor/BranchManager.js +75 -44
- package/dist/editor/CanopyEditor.js +2 -2
- package/dist/editor/CanopyEditorPage.js +3 -4
- package/dist/editor/Editor.js +47 -8
- package/dist/editor/branch-lock.d.ts +7 -0
- package/dist/editor/branch-lock.js +9 -2
- package/dist/editor/client-reference-resolver.d.ts +5 -3
- package/dist/editor/client-reference-resolver.js +10 -5
- package/dist/editor/components/BranchesLoadFailedNotice.d.ts +13 -0
- package/dist/editor/components/BranchesLoadFailedNotice.js +12 -0
- package/dist/editor/components/EditorHeader.d.ts +10 -4
- package/dist/editor/components/EditorHeader.js +108 -28
- package/dist/editor/components/EntryHeader.d.ts +5 -1
- package/dist/editor/components/EntryHeader.js +3 -1
- package/dist/editor/components/ReasonedMenuItem.d.ts +8 -0
- package/dist/editor/components/ReasonedMenuItem.js +2 -2
- package/dist/editor/components/ReviewDialogs.d.ts +19 -0
- package/dist/editor/components/ReviewDialogs.js +41 -0
- package/dist/editor/copy.d.ts +4 -0
- package/dist/editor/copy.js +4 -0
- package/dist/editor/fields/MarkdownField.js +1 -1
- package/dist/editor/hooks/create-branch-request.d.ts +6 -2
- package/dist/editor/hooks/create-branch-request.js +7 -2
- package/dist/editor/hooks/useBranchManager.d.ts +7 -2
- package/dist/editor/hooks/useBranchManager.js +252 -37
- package/dist/editor-url.d.ts +20 -0
- package/dist/editor-url.js +71 -0
- package/dist/git-manager.js +10 -6
- package/dist/github-service.d.ts +2 -2
- package/dist/github-service.js +4 -2
- package/dist/pr-comments.d.ts +33 -0
- package/dist/pr-comments.js +41 -0
- package/dist/schema/schema-store.d.ts +8 -8
- package/dist/services.d.ts +2 -17
- package/dist/services.js +38 -1
- package/dist/settings-workspace.d.ts +21 -7
- package/dist/settings-workspace.js +91 -16
- package/dist/submission-attribution.d.ts +38 -1
- package/dist/submission-attribution.js +197 -18
- package/dist/task-queue/cms-task-queue.d.ts +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/content-serialize.js +7 -6
- package/dist/utils/markdown-body-splice.d.ts +15 -10
- package/dist/utils/markdown-body-splice.js +71 -28
- package/dist/utils/markdown-export-options.d.ts +13 -0
- package/dist/utils/markdown-export-options.js +13 -0
- package/dist/utils/markdown-restyle.d.ts +58 -0
- package/dist/utils/markdown-restyle.js +482 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/bundle-header.d.ts +13 -9
- package/dist/worker/bundle-header.js +17 -11
- package/dist/worker/git-process.d.ts +2 -2
- package/dist/worker/git-sync.js +5 -0
- package/dist/worker/github-gateway-http.js +3 -2
- package/dist/worker/github-gateway-wire.d.ts +3 -0
- package/dist/worker/github-gateway-wire.js +1 -0
- package/dist/worker/github-gateway.d.ts +1 -1
- package/dist/worker/github-transfer.js +72 -2
- package/dist/worker/task-runner.js +53 -3
- package/package.json +2 -2
- package/dist/editor/fields/markdown-export-options.d.ts +0 -2
- package/dist/editor/fields/markdown-export-options.js +0 -8
|
@@ -107,6 +107,7 @@ export const answers = {
|
|
|
107
107
|
merged_at: z.string().max(64).nullable(),
|
|
108
108
|
state: z.enum(['open', 'closed']),
|
|
109
109
|
node_id: z.string().max(256),
|
|
110
|
+
body: pullRequestText.body.nullable(),
|
|
110
111
|
})
|
|
111
112
|
.strict(),
|
|
112
113
|
createOrUpdatePullRequest: pullRequestRef.extend({ created: z.boolean() }).strict(),
|
|
@@ -62,7 +62,7 @@ export declare function describeMove(update: Extract<GitHubRefUpdate, {
|
|
|
62
62
|
moved: true;
|
|
63
63
|
}>): string;
|
|
64
64
|
/** The fields of a pull request the worker reads. */
|
|
65
|
-
export type GitHubPullRequest = Pick<RestEndpointMethodTypes['pulls']['get']['response']['data'], 'merged' | 'merged_at' | 'state' | 'node_id'>;
|
|
65
|
+
export type GitHubPullRequest = Pick<RestEndpointMethodTypes['pulls']['get']['response']['data'], 'merged' | 'merged_at' | 'state' | 'node_id' | 'body'>;
|
|
66
66
|
export type GitHubCreateOrUpdatePullRequest = Omit<CreateOrUpdatePullRequestParams, 'octokit' | 'owner' | 'repo' | 'signal'>;
|
|
67
67
|
/**
|
|
68
68
|
* Everything the worker does with GitHub, and the only place the credential and Octokit are used.
|
|
@@ -246,16 +246,46 @@ async function causeOf(proc, err, reason) {
|
|
|
246
246
|
const own = await proc.exited.then(() => null, (gitErr) => gitErr);
|
|
247
247
|
return own !== null && getErrorMessage(own) !== reason ? own : err;
|
|
248
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* The most a fetch bundle's header may hold outside prerequisites' comments: a line per branch
|
|
251
|
+
* GitHub moved and one per boundary commit, so far more than a push's one head.
|
|
252
|
+
*/
|
|
253
|
+
const FETCH_BUNDLE_HEADER_MAX_BYTES = 16 * 1024 * 1024;
|
|
249
254
|
/**
|
|
250
255
|
* Unbundle the gateway's bundle `bundleId` into the repository at `gitDir`, under the pins. It
|
|
251
256
|
* adds objects and moves no ref.
|
|
257
|
+
*
|
|
258
|
+
* Not `git bundle unbundle`, which first runs a connectivity walk from every ref in `gitDir`:
|
|
259
|
+
* from git 2.56 one corrupt object in any branch's history is fatal to that walk, and so to every
|
|
260
|
+
* fetch until an operator repairs `remote.git`, where a reconcile skips that branch alone. Each
|
|
261
|
+
* prerequisite is checked to be a commit by its object header instead, so one torn after its
|
|
262
|
+
* header is taken like torn history anywhere else, and left to its branch's reconcile. The pack
|
|
263
|
+
* goes to the `index-pack --fix-thin --stdin` that `bundle unbundle` runs after its walk. A delta
|
|
264
|
+
* whose base is missing still fails, in index-pack.
|
|
252
265
|
*/
|
|
253
266
|
async function unbundle(ctx, gitDir, bundleId, signal) {
|
|
254
267
|
const stream = await ctx.github().openBundle(bundleId);
|
|
255
268
|
try {
|
|
256
|
-
|
|
269
|
+
const chunks = stream[Symbol.asyncIterator]();
|
|
270
|
+
const read = await readHeader(stream, chunks, ctx.taskTimeoutMs, signal);
|
|
271
|
+
const missing = await missingCommits(ctx, gitDir, read.header.prerequisites, signal);
|
|
272
|
+
if (missing.length > 0) {
|
|
273
|
+
throw new Error(`Refusing the bundle: its prerequisite ${missing[0]} is not a commit here`);
|
|
274
|
+
}
|
|
275
|
+
const { rest: first } = read;
|
|
276
|
+
const pack = Readable.from((async function* () {
|
|
277
|
+
if (first.length > 0)
|
|
278
|
+
yield first;
|
|
279
|
+
for (;;) {
|
|
280
|
+
const rest = await chunks.next();
|
|
281
|
+
if (rest.done)
|
|
282
|
+
return;
|
|
283
|
+
yield rest.value;
|
|
284
|
+
}
|
|
285
|
+
})());
|
|
286
|
+
await runGit(['index-pack', '-v', '--fix-thin', '--stdin'], {
|
|
257
287
|
...sharedRepoProcessOptions(gitDir, 'bare'),
|
|
258
|
-
stdin:
|
|
288
|
+
stdin: pack,
|
|
259
289
|
inactivityTimeoutMs: ctx.taskTimeoutMs,
|
|
260
290
|
signal,
|
|
261
291
|
});
|
|
@@ -264,6 +294,46 @@ async function unbundle(ctx, gitDir, bundleId, signal) {
|
|
|
264
294
|
stream.destroy();
|
|
265
295
|
}
|
|
266
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Read the header of the fetch bundle `stream`, bounded as git is (worker/git-process.ts): the
|
|
299
|
+
* stream is destroyed when `signal` aborts or nothing arrives for `inactivityTimeoutMs`, since no
|
|
300
|
+
* git runs yet to be stopped.
|
|
301
|
+
*/
|
|
302
|
+
async function readHeader(stream, chunks, inactivityTimeoutMs, signal) {
|
|
303
|
+
signal?.throwIfAborted();
|
|
304
|
+
let timer;
|
|
305
|
+
const onAbort = () => stream.destroy(new Error('Reading the bundle was aborted'));
|
|
306
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
307
|
+
try {
|
|
308
|
+
const reader = new BundleHeaderReader('skip', FETCH_BUNDLE_HEADER_MAX_BYTES);
|
|
309
|
+
for (;;) {
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
timer = setTimeout(() => stream.destroy(new Error(`The bundle sent nothing for ${inactivityTimeoutMs}ms`)), inactivityTimeoutMs);
|
|
312
|
+
const next = await chunks.next();
|
|
313
|
+
if (next.done)
|
|
314
|
+
throw new Error('The bundle ended before its header did');
|
|
315
|
+
const read = reader.push(next.value);
|
|
316
|
+
if (read !== null)
|
|
317
|
+
return read;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
signal?.removeEventListener('abort', onAbort);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/** Of `ids`, those that are not a commit in the repository at `gitDir`. */
|
|
326
|
+
async function missingCommits(ctx, gitDir, ids, signal) {
|
|
327
|
+
if (ids.length === 0)
|
|
328
|
+
return [];
|
|
329
|
+
const lines = (await runGit(['cat-file', '--batch-check=%(objectname) %(objecttype)', '--end-of-options'], {
|
|
330
|
+
...sharedRepoProcessOptions(gitDir, 'bare'),
|
|
331
|
+
stdin: ids.map((id) => `${id}\n`).join(''),
|
|
332
|
+
inactivityTimeoutMs: ctx.taskTimeoutMs,
|
|
333
|
+
signal,
|
|
334
|
+
})).split('\n');
|
|
335
|
+
return ids.filter((id, i) => lines[i] !== `${id} commit`);
|
|
336
|
+
}
|
|
267
337
|
/**
|
|
268
338
|
* Move the tracking namespace to exactly `refs`, every move stating the value it expects: all
|
|
269
339
|
* deletions in one `update-ref` transaction, then every create and update in another. One
|
|
@@ -12,6 +12,7 @@ import { RefusedPushError, assertPlainBranchName } from './github-mirror.js';
|
|
|
12
12
|
import { pushToGitHub } from './github-transfer.js';
|
|
13
13
|
import { clearHistoryRewrittenMarker, readPublishedSha } from './history-rewrite.js';
|
|
14
14
|
import { writeWorkerStatus } from '../task-queue/worker-status.js';
|
|
15
|
+
import { SYNC_PR_COMMENTS_ACTION, refreshCommentsBlock, renderPrCommentsBody } from '../pr-comments.js';
|
|
15
16
|
import { workerLog, workerLogError, workerLogWarn } from './log.js';
|
|
16
17
|
import { assertSharedRepoConfig } from './shared-repo-git.js';
|
|
17
18
|
/**
|
|
@@ -331,16 +332,27 @@ export async function executeTask(ctx, task, signal) {
|
|
|
331
332
|
return { pushed: true };
|
|
332
333
|
}
|
|
333
334
|
await ctx.pushBranchToGitHub(branch, signal);
|
|
335
|
+
const mergeSectionIntoBody = payload.mergeSectionIntoBody === true;
|
|
336
|
+
const queuedBody = optionalString(payload, 'body', '');
|
|
337
|
+
const body = mergeSectionIntoBody
|
|
338
|
+
? await refreshCommentsBlock({
|
|
339
|
+
branchRoot: ctx.branchWorkspacePath(branch),
|
|
340
|
+
branch,
|
|
341
|
+
editorUrl: typeof payload.editorUrl === 'string' ? payload.editorUrl : undefined,
|
|
342
|
+
section: queuedBody,
|
|
343
|
+
warn: workerLogWarn,
|
|
344
|
+
})
|
|
345
|
+
: queuedBody;
|
|
334
346
|
let result;
|
|
335
347
|
try {
|
|
336
348
|
result = await ctx.github().createOrUpdatePullRequest({
|
|
337
349
|
head: branch,
|
|
338
350
|
base,
|
|
339
351
|
title: optionalString(payload, 'title', `Submit ${branch}`),
|
|
340
|
-
body
|
|
352
|
+
body,
|
|
341
353
|
// Content submits (api/github-sync.ts) set both.
|
|
342
354
|
markReadyIfDraft: payload.markReadyIfDraft === true,
|
|
343
|
-
mergeSectionIntoBody
|
|
355
|
+
mergeSectionIntoBody,
|
|
344
356
|
}, signal);
|
|
345
357
|
}
|
|
346
358
|
catch (err) {
|
|
@@ -414,6 +426,44 @@ export async function executeTask(ctx, task, signal) {
|
|
|
414
426
|
workerLog(`Deleted GitHub branch ${branch}`);
|
|
415
427
|
return { deleted: true };
|
|
416
428
|
}
|
|
429
|
+
case 'sync-pr-comments': {
|
|
430
|
+
const branch = requireString(payload, 'branch');
|
|
431
|
+
const prNumber = requireNumber(payload, 'pullRequestNumber');
|
|
432
|
+
const editorUrl = typeof payload.editorUrl === 'string' ? payload.editorUrl : undefined;
|
|
433
|
+
const branchPath = ctx.branchWorkspacePath(branch);
|
|
434
|
+
// The branch under this name now: a deleted branch, or a newer one with another PR, has
|
|
435
|
+
// no say in this PR's summary. Corrupt metadata skips too, as the next submit rewrites it.
|
|
436
|
+
let live = null;
|
|
437
|
+
try {
|
|
438
|
+
live = await BranchMetadataFileManager.loadOnly(branchPath);
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
if (!(err instanceof BranchMetadataCorruptError))
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
if (live?.branch.pullRequestNumber !== prNumber) {
|
|
445
|
+
return { updated: false, skipped: 'not-this-pr' };
|
|
446
|
+
}
|
|
447
|
+
const pr = await ctx.github().getPullRequest(prNumber, signal);
|
|
448
|
+
if (pr.state !== 'open')
|
|
449
|
+
return { updated: false, skipped: 'not-open' };
|
|
450
|
+
// A comments.json that does not parse stays so until a comment write rewrites it.
|
|
451
|
+
const body = await renderPrCommentsBody({
|
|
452
|
+
branchRoot: branchPath,
|
|
453
|
+
branch,
|
|
454
|
+
editorUrl,
|
|
455
|
+
body: pr.body,
|
|
456
|
+
}).catch((err) => {
|
|
457
|
+
if (err instanceof SyntaxError)
|
|
458
|
+
throw new PermanentTaskError(getErrorMessage(err));
|
|
459
|
+
throw err;
|
|
460
|
+
});
|
|
461
|
+
if (body === undefined)
|
|
462
|
+
return { updated: false, skipped: 'unchanged' };
|
|
463
|
+
await ctx.github().updatePullRequest(prNumber, { body }, signal);
|
|
464
|
+
workerLog(`Updated the editor comments on PR #${prNumber} for ${branch}`);
|
|
465
|
+
return { updated: true };
|
|
466
|
+
}
|
|
417
467
|
default:
|
|
418
468
|
throw new PermanentTaskError(`Unknown task action: ${action}`);
|
|
419
469
|
}
|
|
@@ -424,7 +474,7 @@ export async function executeTask(ctx, task, signal) {
|
|
|
424
474
|
* name is leftover or a newer branch's that reused the name.
|
|
425
475
|
*/
|
|
426
476
|
function metadataBranchOf(task) {
|
|
427
|
-
if (task.action === 'delete-remote-branch')
|
|
477
|
+
if (task.action === 'delete-remote-branch' || task.action === SYNC_PR_COMMENTS_ACTION)
|
|
428
478
|
return null;
|
|
429
479
|
return typeof task.payload.branch === 'string' ? task.payload.branch : null;
|
|
430
480
|
}
|
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.7.1",
|
|
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": {
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
|
-
"@playwright/test": "^1.
|
|
164
|
+
"@playwright/test": "^1.64.1",
|
|
165
165
|
"@smithy/util-stream": "^3.0.0 || ^4.0.0",
|
|
166
166
|
"@storybook/addon-essentials": "^8.1.11",
|
|
167
167
|
"@storybook/react": "^8.1.11",
|