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
|
@@ -186,6 +186,21 @@ function withItemKeys(fields, data, fill) {
|
|
|
186
186
|
return out;
|
|
187
187
|
}
|
|
188
188
|
function keyList(field, items, fill) {
|
|
189
|
+
const keys = listKeys(items, fill);
|
|
190
|
+
let changed = false;
|
|
191
|
+
const out = items.map((item, i) => {
|
|
192
|
+
if (!isPlainRecord(item))
|
|
193
|
+
return item;
|
|
194
|
+
let next = keyInside(field, item, fill);
|
|
195
|
+
if (keys[i] !== item[ITEM_KEY])
|
|
196
|
+
next = { ...next, [ITEM_KEY]: keys[i] };
|
|
197
|
+
if (next !== item)
|
|
198
|
+
changed = true;
|
|
199
|
+
return next;
|
|
200
|
+
});
|
|
201
|
+
return changed ? out : items;
|
|
202
|
+
}
|
|
203
|
+
function listKeys(items, fill) {
|
|
189
204
|
const held = /* @__PURE__ */ new Set();
|
|
190
205
|
if (fill !== "fresh") {
|
|
191
206
|
for (const item of items) {
|
|
@@ -194,25 +209,19 @@ function keyList(field, items, fill) {
|
|
|
194
209
|
}
|
|
195
210
|
}
|
|
196
211
|
const kept = /* @__PURE__ */ new Set();
|
|
197
|
-
|
|
198
|
-
const out = items.map((item) => {
|
|
212
|
+
return items.map((item) => {
|
|
199
213
|
if (!isPlainRecord(item))
|
|
200
|
-
return
|
|
201
|
-
let next = keyInside(field, item, fill);
|
|
214
|
+
return void 0;
|
|
202
215
|
const own = item[ITEM_KEY];
|
|
203
216
|
if (fill !== "fresh" && isItemKey(own) && !kept.has(own)) {
|
|
204
217
|
kept.add(own);
|
|
205
|
-
|
|
206
|
-
const key = fill === "content" ? unheld(contentKey(item), held) : mintItemKey();
|
|
207
|
-
held.add(key);
|
|
208
|
-
kept.add(key);
|
|
209
|
-
next = { ...next, [ITEM_KEY]: key };
|
|
218
|
+
return own;
|
|
210
219
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
const key = fill === "content" ? unheld(contentKey(item), held) : mintItemKey();
|
|
221
|
+
held.add(key);
|
|
222
|
+
kept.add(key);
|
|
223
|
+
return key;
|
|
214
224
|
});
|
|
215
|
-
return changed ? out : items;
|
|
216
225
|
}
|
|
217
226
|
function unheld(key, held) {
|
|
218
227
|
let out = key;
|
|
@@ -314,11 +323,11 @@ var init_sanitize_href = __esm({
|
|
|
314
323
|
});
|
|
315
324
|
|
|
316
325
|
// dist/paths/normalize.js
|
|
317
|
-
function normalizeFilesystemPath(
|
|
318
|
-
return
|
|
326
|
+
function normalizeFilesystemPath(path28) {
|
|
327
|
+
return path28.split(/[\\/]+/).filter(Boolean).join("/");
|
|
319
328
|
}
|
|
320
|
-
function hasTraversalSequence(
|
|
321
|
-
const normalized = normalizeFilesystemPath(
|
|
329
|
+
function hasTraversalSequence(path28) {
|
|
330
|
+
const normalized = normalizeFilesystemPath(path28);
|
|
322
331
|
return normalized.includes("..");
|
|
323
332
|
}
|
|
324
333
|
function createLogicalPath(...segments) {
|
|
@@ -331,14 +340,14 @@ function createLogicalPath(...segments) {
|
|
|
331
340
|
function entryLogicalPath(collectionLogicalPath, slug) {
|
|
332
341
|
return collectionLogicalPath ? `${collectionLogicalPath}/${slug}` : slug;
|
|
333
342
|
}
|
|
334
|
-
function trimSlashes(
|
|
343
|
+
function trimSlashes(path28) {
|
|
335
344
|
let start = 0;
|
|
336
|
-
let end =
|
|
337
|
-
while (start < end &&
|
|
345
|
+
let end = path28.length;
|
|
346
|
+
while (start < end && path28[start] === "/")
|
|
338
347
|
start++;
|
|
339
|
-
while (end > start &&
|
|
348
|
+
while (end > start && path28[end - 1] === "/")
|
|
340
349
|
end--;
|
|
341
|
-
return
|
|
350
|
+
return path28.slice(start, end);
|
|
342
351
|
}
|
|
343
352
|
var init_normalize = __esm({
|
|
344
353
|
"dist/paths/normalize.js"() {
|
|
@@ -1175,6 +1184,9 @@ var init_client_safe_strategy = __esm({
|
|
|
1175
1184
|
supportsPullRequests() {
|
|
1176
1185
|
return true;
|
|
1177
1186
|
}
|
|
1187
|
+
syncsBranchesWithBase() {
|
|
1188
|
+
return true;
|
|
1189
|
+
}
|
|
1178
1190
|
getPermissionsFileName() {
|
|
1179
1191
|
return "permissions.json";
|
|
1180
1192
|
}
|
|
@@ -1201,6 +1213,9 @@ var init_client_safe_strategy = __esm({
|
|
|
1201
1213
|
supportsPullRequests() {
|
|
1202
1214
|
return false;
|
|
1203
1215
|
}
|
|
1216
|
+
syncsBranchesWithBase() {
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1204
1219
|
getPermissionsFileName() {
|
|
1205
1220
|
return "permissions.json";
|
|
1206
1221
|
}
|
|
@@ -1390,6 +1405,9 @@ var init_client_unsafe_strategy = __esm({
|
|
|
1390
1405
|
autoDetectRemotePath: path9.join(this.getWorkspaceRoot(), "remote.git")
|
|
1391
1406
|
};
|
|
1392
1407
|
}
|
|
1408
|
+
baseUpdateRunsInRequest() {
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1393
1411
|
requiresExistingRepo() {
|
|
1394
1412
|
return false;
|
|
1395
1413
|
}
|
|
@@ -1442,6 +1460,9 @@ var init_client_unsafe_strategy = __esm({
|
|
|
1442
1460
|
envVarName: "CANOPYCMS_REMOTE_URL"
|
|
1443
1461
|
};
|
|
1444
1462
|
}
|
|
1463
|
+
baseUpdateRunsInRequest() {
|
|
1464
|
+
return true;
|
|
1465
|
+
}
|
|
1445
1466
|
requiresExistingRepo() {
|
|
1446
1467
|
return false;
|
|
1447
1468
|
}
|
|
@@ -1610,8 +1631,7 @@ var init_worker_status = __esm({
|
|
|
1610
1631
|
});
|
|
1611
1632
|
|
|
1612
1633
|
// dist/cli/generate-ai-content.js
|
|
1613
|
-
import
|
|
1614
|
-
import { createJiti } from "jiti";
|
|
1634
|
+
import path27 from "node:path";
|
|
1615
1635
|
|
|
1616
1636
|
// dist/build/generate-ai-content.js
|
|
1617
1637
|
init_error();
|
|
@@ -1626,7 +1646,7 @@ import matter2 from "gray-matter";
|
|
|
1626
1646
|
import { parse as yamlParse } from "yaml";
|
|
1627
1647
|
|
|
1628
1648
|
// dist/utils/content-serialize.js
|
|
1629
|
-
import { isDeepStrictEqual as
|
|
1649
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
1630
1650
|
import matter from "gray-matter";
|
|
1631
1651
|
import { isCollection as isCollection2, isMap as isMap2, isNode as isNode2, isScalar as isScalar2, isSeq as isSeq2, parseDocument as parseDocument2, Scalar, stringify as yamlStringify, visit as visit2 } from "yaml";
|
|
1632
1652
|
|
|
@@ -1805,13 +1825,112 @@ var testLogger = createDebugLogger({
|
|
|
1805
1825
|
// dist/utils/content-serialize.js
|
|
1806
1826
|
init_error();
|
|
1807
1827
|
|
|
1828
|
+
// dist/utils/json-source-splice.js
|
|
1829
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1830
|
+
import { applyEdits, modify } from "jsonc-parser";
|
|
1831
|
+
init_error();
|
|
1832
|
+
var log = createDebugLogger({ prefix: "JsonSourceSplice" });
|
|
1833
|
+
function isRecord(value) {
|
|
1834
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1835
|
+
}
|
|
1836
|
+
function plainStringify(data) {
|
|
1837
|
+
return `${JSON.stringify(data, null, 2)}
|
|
1838
|
+
`;
|
|
1839
|
+
}
|
|
1840
|
+
function detectFormatting(text) {
|
|
1841
|
+
const indent = /^([ \t]+)\S/m.exec(text)?.[1];
|
|
1842
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
1843
|
+
if (indent?.startsWith(" "))
|
|
1844
|
+
return { insertSpaces: false, tabSize: 1, eol };
|
|
1845
|
+
return { insertSpaces: true, tabSize: indent?.length ?? 2, eol };
|
|
1846
|
+
}
|
|
1847
|
+
function insertMember(text, path28, key, value, formattingOptions) {
|
|
1848
|
+
const at = [...path28, key];
|
|
1849
|
+
const [edit] = modify(text, at, value, {});
|
|
1850
|
+
const closing = edit ? /^[ \t]*\r?\n([ \t]*)\}/.exec(text.slice(edit.offset)) : null;
|
|
1851
|
+
if (edit && closing && edit.length === 0 && edit.content.startsWith(",")) {
|
|
1852
|
+
const unit = formattingOptions.insertSpaces ? " ".repeat(formattingOptions.tabSize ?? 2) : " ";
|
|
1853
|
+
const eol = formattingOptions.eol ?? "\n";
|
|
1854
|
+
const indent = closing[1] + unit;
|
|
1855
|
+
const rendered = JSON.stringify(value, null, unit).split("\n").join(eol + indent);
|
|
1856
|
+
return applyEdits(text, [
|
|
1857
|
+
{ ...edit, content: `,${eol}${indent}${JSON.stringify(key)}: ${rendered}` }
|
|
1858
|
+
]);
|
|
1859
|
+
}
|
|
1860
|
+
return applyEdits(text, modify(text, at, value, { formattingOptions }));
|
|
1861
|
+
}
|
|
1862
|
+
function removeMember(text, path28) {
|
|
1863
|
+
return applyEdits(text, modify(text, path28, void 0, {}).map((edit) => {
|
|
1864
|
+
const removed = text.slice(edit.offset, edit.offset + edit.length);
|
|
1865
|
+
const lead = /^\s+/.exec(removed)?.[0].length ?? 0;
|
|
1866
|
+
const atMember = /\S/.test(text.charAt(edit.offset + edit.length));
|
|
1867
|
+
return edit.content === "" && lead > 0 && atMember ? { ...edit, offset: edit.offset + lead, length: edit.length - lead } : edit;
|
|
1868
|
+
}));
|
|
1869
|
+
}
|
|
1870
|
+
function reconcile(text, path28, before, after, formattingOptions) {
|
|
1871
|
+
if (isRecord(before) && isRecord(after)) {
|
|
1872
|
+
if (Object.keys(after).length === 0 && Object.keys(before).length > 0) {
|
|
1873
|
+
return applyEdits(text, modify(text, path28, {}, { formattingOptions }));
|
|
1874
|
+
}
|
|
1875
|
+
let next = text;
|
|
1876
|
+
for (const key of Object.keys(before)) {
|
|
1877
|
+
if (!Object.prototype.hasOwnProperty.call(after, key))
|
|
1878
|
+
next = removeMember(next, [...path28, key]);
|
|
1879
|
+
}
|
|
1880
|
+
for (const key of Object.keys(after)) {
|
|
1881
|
+
next = Object.prototype.hasOwnProperty.call(before, key) ? reconcile(next, [...path28, key], before[key], after[key], formattingOptions) : insertMember(next, path28, key, after[key], formattingOptions);
|
|
1882
|
+
}
|
|
1883
|
+
return next;
|
|
1884
|
+
}
|
|
1885
|
+
if (Array.isArray(before) && Array.isArray(after) && before.length === after.length) {
|
|
1886
|
+
return after.reduce((next, item, index) => reconcile(next, [...path28, index], before[index], item, formattingOptions), text);
|
|
1887
|
+
}
|
|
1888
|
+
if (JSON.stringify(before) === JSON.stringify(after))
|
|
1889
|
+
return text;
|
|
1890
|
+
return applyEdits(text, modify(text, path28, after, { formattingOptions }));
|
|
1891
|
+
}
|
|
1892
|
+
function serializeJson(data, existingRaw) {
|
|
1893
|
+
if (existingRaw === void 0)
|
|
1894
|
+
return plainStringify(data);
|
|
1895
|
+
try {
|
|
1896
|
+
const normalised = JSON.parse(JSON.stringify(data));
|
|
1897
|
+
const existing = JSON.parse(existingRaw);
|
|
1898
|
+
if (!isRecord(normalised) || !isRecord(existing))
|
|
1899
|
+
return plainStringify(data);
|
|
1900
|
+
const spliced = reconcile(existingRaw, [], existing, normalised, detectFormatting(existingRaw));
|
|
1901
|
+
if (isDeepStrictEqual(JSON.parse(spliced), normalised))
|
|
1902
|
+
return spliced;
|
|
1903
|
+
log.debug("json-source-splice", "splice does not read back as data; writing JSON.stringify");
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
log.debug("json-source-splice", "splice failed; writing JSON.stringify", {
|
|
1906
|
+
error: getErrorMessage(err)
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
return plainStringify(data);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1808
1912
|
// dist/utils/markdown-body-splice.js
|
|
1913
|
+
init_error();
|
|
1914
|
+
|
|
1915
|
+
// dist/utils/markdown-parse.js
|
|
1809
1916
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
1810
1917
|
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
1811
1918
|
import { mdxFromMarkdown } from "mdast-util-mdx";
|
|
1812
1919
|
import { gfm } from "micromark-extension-gfm";
|
|
1813
1920
|
import { mdxjs } from "micromark-extension-mdxjs";
|
|
1814
1921
|
init_error();
|
|
1922
|
+
var log2 = createDebugLogger({ prefix: "MarkdownParse" });
|
|
1923
|
+
function parseMarkdownBody(text, format) {
|
|
1924
|
+
try {
|
|
1925
|
+
return format === "mdx" ? fromMarkdown(text, {
|
|
1926
|
+
extensions: [gfm(), mdxjs()],
|
|
1927
|
+
mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()]
|
|
1928
|
+
}) : fromMarkdown(text, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
log2.debug("markdown-parse", "body does not parse", { error: getErrorMessage(err) });
|
|
1931
|
+
return void 0;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1815
1934
|
|
|
1816
1935
|
// dist/utils/markdown-export-options.js
|
|
1817
1936
|
var MARKDOWN_EXPORT_OPTIONS = {
|
|
@@ -2236,7 +2355,7 @@ function restyleMarkdown(source, tree, fixed, readsBack) {
|
|
|
2236
2355
|
}
|
|
2237
2356
|
|
|
2238
2357
|
// dist/utils/yaml-source-splice.js
|
|
2239
|
-
import { isDeepStrictEqual } from "node:util";
|
|
2358
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
2240
2359
|
import { Document, parseDocument, isAlias, isCollection, isMap, isNode, isPair, isScalar, isSeq, YAMLMap, visit, YAMLSeq } from "yaml";
|
|
2241
2360
|
var END = Symbol("end");
|
|
2242
2361
|
var FRESH = Symbol("fresh");
|
|
@@ -2639,7 +2758,7 @@ function printsAs(candidate, reconciled, printed) {
|
|
|
2639
2758
|
const reparsed = parseDocument(candidate);
|
|
2640
2759
|
if (reparsed.errors.length > 0)
|
|
2641
2760
|
return false;
|
|
2642
|
-
if (!
|
|
2761
|
+
if (!isDeepStrictEqual2(reparsed.toJS(), reconciled.toJS()))
|
|
2643
2762
|
return false;
|
|
2644
2763
|
return layoutFree(reparsed.toString()) === layoutFree(parseDocument(printed).toString());
|
|
2645
2764
|
}
|
|
@@ -2648,19 +2767,8 @@ function layoutFree(text) {
|
|
|
2648
2767
|
}
|
|
2649
2768
|
|
|
2650
2769
|
// dist/utils/markdown-body-splice.js
|
|
2651
|
-
var
|
|
2770
|
+
var log3 = createDebugLogger({ prefix: "MarkdownBodySplice" });
|
|
2652
2771
|
var MAX_LCS_CELLS = 4e6;
|
|
2653
|
-
function parse(text, format) {
|
|
2654
|
-
try {
|
|
2655
|
-
return format === "mdx" ? fromMarkdown(text, {
|
|
2656
|
-
extensions: [gfm(), mdxjs()],
|
|
2657
|
-
mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()]
|
|
2658
|
-
}) : fromMarkdown(text, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });
|
|
2659
|
-
} catch (err) {
|
|
2660
|
-
log.debug("markdown-body-splice", "body does not parse", { error: getErrorMessage(err) });
|
|
2661
|
-
return void 0;
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
2772
|
function canonical(node) {
|
|
2665
2773
|
return JSON.stringify(node, (key, value) => {
|
|
2666
2774
|
if (key === "position" || key === "data")
|
|
@@ -2941,7 +3049,7 @@ function preserveMarkdownSource(original, updated, format) {
|
|
|
2941
3049
|
try {
|
|
2942
3050
|
return splice(original, updated, format) ?? updated;
|
|
2943
3051
|
} catch (err) {
|
|
2944
|
-
|
|
3052
|
+
log3.debug("markdown-body-splice", "splice failed", { error: getErrorMessage(err) });
|
|
2945
3053
|
return updated;
|
|
2946
3054
|
}
|
|
2947
3055
|
}
|
|
@@ -2966,14 +3074,14 @@ function diskBlocks(oNodes, nNodes) {
|
|
|
2966
3074
|
return fromDisk;
|
|
2967
3075
|
}
|
|
2968
3076
|
function splice(original, updated, format) {
|
|
2969
|
-
const before = original === void 0 ? void 0 :
|
|
2970
|
-
const after =
|
|
3077
|
+
const before = original === void 0 ? void 0 : parseMarkdownBody(original, format);
|
|
3078
|
+
const after = parseMarkdownBody(updated, format);
|
|
2971
3079
|
if (after === void 0)
|
|
2972
3080
|
return void 0;
|
|
2973
3081
|
const target = canonical(after);
|
|
2974
3082
|
const fromDisk = diskBlocks(before?.children ?? [], after.children);
|
|
2975
3083
|
const restyled = restyleMarkdown(updated, after, (index) => fromDisk.has(index), (text) => {
|
|
2976
|
-
const tree =
|
|
3084
|
+
const tree = parseMarkdownBody(text, format);
|
|
2977
3085
|
return tree !== void 0 && canonical(tree) === target ? tree : void 0;
|
|
2978
3086
|
});
|
|
2979
3087
|
if (original === void 0 || before === void 0)
|
|
@@ -2985,10 +3093,10 @@ function splice(original, updated, format) {
|
|
|
2985
3093
|
const candidate = new Splicer(original, edit.text, deep).body(before, edit.tree);
|
|
2986
3094
|
if (candidate === void 0)
|
|
2987
3095
|
continue;
|
|
2988
|
-
const reparsed =
|
|
3096
|
+
const reparsed = parseMarkdownBody(candidate, format);
|
|
2989
3097
|
if (reparsed !== void 0 && canonical(reparsed) === target)
|
|
2990
3098
|
return candidate;
|
|
2991
|
-
|
|
3099
|
+
log3.debug("markdown-body-splice", "splice does not read back as the edit", {
|
|
2992
3100
|
deep,
|
|
2993
3101
|
restyled: edit !== asSent
|
|
2994
3102
|
});
|
|
@@ -2998,7 +3106,7 @@ function splice(original, updated, format) {
|
|
|
2998
3106
|
}
|
|
2999
3107
|
|
|
3000
3108
|
// dist/utils/content-serialize.js
|
|
3001
|
-
var
|
|
3109
|
+
var log4 = createDebugLogger({ prefix: "ContentSerialize" });
|
|
3002
3110
|
function createNode(ctx, value, asKey = false) {
|
|
3003
3111
|
const node = ctx.doc.createNode(value);
|
|
3004
3112
|
const { quote } = ctx;
|
|
@@ -3242,9 +3350,9 @@ function reconcileYamlSource(raw, data, { rootAtColumnZero = false, quote } = {}
|
|
|
3242
3350
|
if (spliced !== void 0 && !(rootAtColumnZero && /^\s*[ \t]\S/.test(spliced))) {
|
|
3243
3351
|
return spliced;
|
|
3244
3352
|
}
|
|
3245
|
-
|
|
3353
|
+
log4.debug("content-serialize", "source splice not used; writing as yaml prints it");
|
|
3246
3354
|
} catch (err) {
|
|
3247
|
-
|
|
3355
|
+
log4.debug("content-serialize", "source splice failed; writing as yaml prints it", {
|
|
3248
3356
|
error: getErrorMessage(err)
|
|
3249
3357
|
});
|
|
3250
3358
|
}
|
|
@@ -3252,7 +3360,7 @@ function reconcileYamlSource(raw, data, { rootAtColumnZero = false, quote } = {}
|
|
|
3252
3360
|
}
|
|
3253
3361
|
function readsBackAs(text, doc) {
|
|
3254
3362
|
const reparsed = parseDocument2(text);
|
|
3255
|
-
return reparsed.errors.length === 0 &&
|
|
3363
|
+
return reparsed.errors.length === 0 && isDeepStrictEqual3(reparsed.toJS(), doc.toJS());
|
|
3256
3364
|
}
|
|
3257
3365
|
function serializeYaml(data, existingRaw) {
|
|
3258
3366
|
if (existingRaw === void 0)
|
|
@@ -3389,94 +3497,17 @@ function serializeFrontmatter(editorBody, data, existingRaw, format, fields = []
|
|
|
3389
3497
|
}
|
|
3390
3498
|
}
|
|
3391
3499
|
});
|
|
3392
|
-
if (
|
|
3500
|
+
if (isDeepStrictEqual3(frontmatterAsRead(written, fields), asJson(data)))
|
|
3393
3501
|
return written;
|
|
3394
|
-
|
|
3502
|
+
log4.debug("content-serialize", "reconciled frontmatter reads back differently; writing as gray-matter prints it");
|
|
3395
3503
|
return matter.stringify(file, data);
|
|
3396
3504
|
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
function isRecord(value) {
|
|
3404
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3405
|
-
}
|
|
3406
|
-
function plainStringify(data) {
|
|
3407
|
-
return `${JSON.stringify(data, null, 2)}
|
|
3408
|
-
`;
|
|
3409
|
-
}
|
|
3410
|
-
function detectFormatting(text) {
|
|
3411
|
-
const indent = /^([ \t]+)\S/m.exec(text)?.[1];
|
|
3412
|
-
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
3413
|
-
if (indent?.startsWith(" "))
|
|
3414
|
-
return { insertSpaces: false, tabSize: 1, eol };
|
|
3415
|
-
return { insertSpaces: true, tabSize: indent?.length ?? 2, eol };
|
|
3416
|
-
}
|
|
3417
|
-
function insertMember(text, path27, key, value, formattingOptions) {
|
|
3418
|
-
const at = [...path27, key];
|
|
3419
|
-
const [edit] = modify(text, at, value, {});
|
|
3420
|
-
const closing = edit ? /^[ \t]*\r?\n([ \t]*)\}/.exec(text.slice(edit.offset)) : null;
|
|
3421
|
-
if (edit && closing && edit.length === 0 && edit.content.startsWith(",")) {
|
|
3422
|
-
const unit = formattingOptions.insertSpaces ? " ".repeat(formattingOptions.tabSize ?? 2) : " ";
|
|
3423
|
-
const eol = formattingOptions.eol ?? "\n";
|
|
3424
|
-
const indent = closing[1] + unit;
|
|
3425
|
-
const rendered = JSON.stringify(value, null, unit).split("\n").join(eol + indent);
|
|
3426
|
-
return applyEdits(text, [
|
|
3427
|
-
{ ...edit, content: `,${eol}${indent}${JSON.stringify(key)}: ${rendered}` }
|
|
3428
|
-
]);
|
|
3429
|
-
}
|
|
3430
|
-
return applyEdits(text, modify(text, at, value, { formattingOptions }));
|
|
3431
|
-
}
|
|
3432
|
-
function removeMember(text, path27) {
|
|
3433
|
-
return applyEdits(text, modify(text, path27, void 0, {}).map((edit) => {
|
|
3434
|
-
const removed = text.slice(edit.offset, edit.offset + edit.length);
|
|
3435
|
-
const lead = /^\s+/.exec(removed)?.[0].length ?? 0;
|
|
3436
|
-
const atMember = /\S/.test(text.charAt(edit.offset + edit.length));
|
|
3437
|
-
return edit.content === "" && lead > 0 && atMember ? { ...edit, offset: edit.offset + lead, length: edit.length - lead } : edit;
|
|
3438
|
-
}));
|
|
3439
|
-
}
|
|
3440
|
-
function reconcile(text, path27, before, after, formattingOptions) {
|
|
3441
|
-
if (isRecord(before) && isRecord(after)) {
|
|
3442
|
-
if (Object.keys(after).length === 0 && Object.keys(before).length > 0) {
|
|
3443
|
-
return applyEdits(text, modify(text, path27, {}, { formattingOptions }));
|
|
3444
|
-
}
|
|
3445
|
-
let next = text;
|
|
3446
|
-
for (const key of Object.keys(before)) {
|
|
3447
|
-
if (!Object.prototype.hasOwnProperty.call(after, key))
|
|
3448
|
-
next = removeMember(next, [...path27, key]);
|
|
3449
|
-
}
|
|
3450
|
-
for (const key of Object.keys(after)) {
|
|
3451
|
-
next = Object.prototype.hasOwnProperty.call(before, key) ? reconcile(next, [...path27, key], before[key], after[key], formattingOptions) : insertMember(next, path27, key, after[key], formattingOptions);
|
|
3452
|
-
}
|
|
3453
|
-
return next;
|
|
3454
|
-
}
|
|
3455
|
-
if (Array.isArray(before) && Array.isArray(after) && before.length === after.length) {
|
|
3456
|
-
return after.reduce((next, item, index) => reconcile(next, [...path27, index], before[index], item, formattingOptions), text);
|
|
3457
|
-
}
|
|
3458
|
-
if (JSON.stringify(before) === JSON.stringify(after))
|
|
3459
|
-
return text;
|
|
3460
|
-
return applyEdits(text, modify(text, path27, after, { formattingOptions }));
|
|
3461
|
-
}
|
|
3462
|
-
function serializeJson(data, existingRaw) {
|
|
3463
|
-
if (existingRaw === void 0)
|
|
3464
|
-
return plainStringify(data);
|
|
3465
|
-
try {
|
|
3466
|
-
const normalised = JSON.parse(JSON.stringify(data));
|
|
3467
|
-
const existing = JSON.parse(existingRaw);
|
|
3468
|
-
if (!isRecord(normalised) || !isRecord(existing))
|
|
3469
|
-
return plainStringify(data);
|
|
3470
|
-
const spliced = reconcile(existingRaw, [], existing, normalised, detectFormatting(existingRaw));
|
|
3471
|
-
if (isDeepStrictEqual3(JSON.parse(spliced), normalised))
|
|
3472
|
-
return spliced;
|
|
3473
|
-
log3.debug("json-source-splice", "splice does not read back as data; writing JSON.stringify");
|
|
3474
|
-
} catch (err) {
|
|
3475
|
-
log3.debug("json-source-splice", "splice failed; writing JSON.stringify", {
|
|
3476
|
-
error: getErrorMessage(err)
|
|
3477
|
-
});
|
|
3478
|
-
}
|
|
3479
|
-
return plainStringify(data);
|
|
3505
|
+
function serializeEntry(entry, existingRaw, fields) {
|
|
3506
|
+
if (entry.format === "json")
|
|
3507
|
+
return serializeJson(entry.data, existingRaw);
|
|
3508
|
+
if (entry.format === "yaml")
|
|
3509
|
+
return serializeYaml(entry.data, existingRaw);
|
|
3510
|
+
return serializeFrontmatter(entry.body, entry.data ?? {}, existingRaw, entry.format, fields);
|
|
3480
3511
|
}
|
|
3481
3512
|
|
|
3482
3513
|
// dist/utils/async-mutex.js
|
|
@@ -3922,8 +3953,8 @@ function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
3922
3953
|
if (slug && !isIndexSlug(slug)) {
|
|
3923
3954
|
segments.push(slug);
|
|
3924
3955
|
}
|
|
3925
|
-
const
|
|
3926
|
-
return
|
|
3956
|
+
const path28 = segments.length > 0 ? `/${segments.join("/")}` : "/";
|
|
3957
|
+
return path28.toLowerCase();
|
|
3927
3958
|
}
|
|
3928
3959
|
|
|
3929
3960
|
// dist/url-collision.js
|
|
@@ -4286,7 +4317,7 @@ function extractIdFromFilename(filename) {
|
|
|
4286
4317
|
}
|
|
4287
4318
|
async function resolveCollectionPath(root, logicalPath) {
|
|
4288
4319
|
const fs22 = await import("node:fs/promises");
|
|
4289
|
-
const
|
|
4320
|
+
const path28 = await import("node:path");
|
|
4290
4321
|
const segments = logicalPath.split("/").filter(Boolean);
|
|
4291
4322
|
let currentPath = root;
|
|
4292
4323
|
for (const segment of segments) {
|
|
@@ -4299,7 +4330,7 @@ async function resolveCollectionPath(root, logicalPath) {
|
|
|
4299
4330
|
return logicalName === segment.toLowerCase();
|
|
4300
4331
|
});
|
|
4301
4332
|
if (matchingDir) {
|
|
4302
|
-
currentPath =
|
|
4333
|
+
currentPath = path28.join(currentPath, matchingDir.name);
|
|
4303
4334
|
} else {
|
|
4304
4335
|
return null;
|
|
4305
4336
|
}
|
|
@@ -4466,7 +4497,7 @@ import fs5 from "node:fs/promises";
|
|
|
4466
4497
|
import path7 from "node:path";
|
|
4467
4498
|
import { randomUUID } from "node:crypto";
|
|
4468
4499
|
init_logger();
|
|
4469
|
-
var
|
|
4500
|
+
var log5 = createDebugLogger({ prefix: "ResourceGeneration" });
|
|
4470
4501
|
var META_DIR2 = ".canopy-meta";
|
|
4471
4502
|
function resourceGenerationPath(root, resource) {
|
|
4472
4503
|
return path7.resolve(root, META_DIR2, `${resource}.generation`);
|
|
@@ -4490,7 +4521,7 @@ async function readResourceGeneration(root, resource) {
|
|
|
4490
4521
|
} catch (err) {
|
|
4491
4522
|
if (isNodeError(err) && err.code === "ENOENT")
|
|
4492
4523
|
return { ok: true, token: null };
|
|
4493
|
-
|
|
4524
|
+
log5.warn("generation", `Failed to read ${resource} generation for ${root}`, {
|
|
4494
4525
|
error: getErrorMessage(err)
|
|
4495
4526
|
});
|
|
4496
4527
|
return { ok: false };
|
|
@@ -4858,7 +4889,7 @@ var STATIC_DEPLOY_USER = Object.freeze({
|
|
|
4858
4889
|
// dist/utils/request-timing.js
|
|
4859
4890
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
4860
4891
|
var storage = new AsyncLocalStorage2();
|
|
4861
|
-
var
|
|
4892
|
+
var log6 = createDebugLogger({ prefix: "CanopyCMS" });
|
|
4862
4893
|
async function timeRequestPhase(phase, fn) {
|
|
4863
4894
|
const parent = storage.getStore();
|
|
4864
4895
|
if (!parent)
|
|
@@ -5389,13 +5420,13 @@ function entryRecord(parsed, emptyIsRecord) {
|
|
|
5389
5420
|
}
|
|
5390
5421
|
function fillEmptyTopLevelField(raw, format, field, value) {
|
|
5391
5422
|
if (format === "json" || format === "yaml") {
|
|
5392
|
-
const
|
|
5393
|
-
const data =
|
|
5423
|
+
const parse = (text) => format === "json" ? entryRecord(JSON.parse(text), false) : entryRecord(yamlParse(text), true);
|
|
5424
|
+
const data = parse(raw);
|
|
5394
5425
|
if (!isEmptyFieldValue(data[field]))
|
|
5395
5426
|
return void 0;
|
|
5396
5427
|
const next = { ...data, [field]: value };
|
|
5397
5428
|
const content2 = format === "json" ? serializeJson(next, raw) : serializeYaml(next, raw);
|
|
5398
|
-
if (
|
|
5429
|
+
if (parse(content2)[field] !== value)
|
|
5399
5430
|
throw new Error(`"${field}" does not read back as written`);
|
|
5400
5431
|
return content2;
|
|
5401
5432
|
}
|
|
@@ -5585,11 +5616,11 @@ var ContentStore = class {
|
|
|
5585
5616
|
getSchemaItems() {
|
|
5586
5617
|
return this.schemaIndex.values();
|
|
5587
5618
|
}
|
|
5588
|
-
assertSchemaItem(
|
|
5589
|
-
const normalized = normalizeFilesystemPath(
|
|
5619
|
+
assertSchemaItem(path28) {
|
|
5620
|
+
const normalized = normalizeFilesystemPath(path28);
|
|
5590
5621
|
const item = this.schemaIndex.get(normalized);
|
|
5591
5622
|
if (!item) {
|
|
5592
|
-
throw new ContentStoreError(`Unknown schema item: ${
|
|
5623
|
+
throw new ContentStoreError(`Unknown schema item: ${path28}`, "NO_SCHEMA_ITEM");
|
|
5593
5624
|
}
|
|
5594
5625
|
return item;
|
|
5595
5626
|
}
|
|
@@ -5907,7 +5938,7 @@ var ContentStore = class {
|
|
|
5907
5938
|
doc.version = stat.mtimeMs;
|
|
5908
5939
|
return doc;
|
|
5909
5940
|
}
|
|
5910
|
-
async write(collectionPath, slug = "", input, entryTypeName, existingId) {
|
|
5941
|
+
async write(collectionPath, slug = "", input, entryTypeName, existingId, options = {}) {
|
|
5911
5942
|
await this.idIndex();
|
|
5912
5943
|
const schemaItem = this.assertSchemaItem(collectionPath);
|
|
5913
5944
|
let expectedFormat;
|
|
@@ -5995,14 +6026,7 @@ var ContentStore = class {
|
|
|
5995
6026
|
}
|
|
5996
6027
|
}
|
|
5997
6028
|
const existingRaw = current?.content;
|
|
5998
|
-
|
|
5999
|
-
if (input.format === "json") {
|
|
6000
|
-
content = serializeJson(input.data ?? {}, existingRaw);
|
|
6001
|
-
} else if (input.format === "yaml") {
|
|
6002
|
-
content = serializeYaml(input.data ?? {}, existingRaw);
|
|
6003
|
-
} else {
|
|
6004
|
-
content = serializeFrontmatter(input.body, input.data ?? {}, existingRaw, input.format, fields);
|
|
6005
|
-
}
|
|
6029
|
+
const content = serializeEntry(input, existingRaw, fields);
|
|
6006
6030
|
await atomicWriteFile(absolutePath, content);
|
|
6007
6031
|
const liveIndex = this._idIndex;
|
|
6008
6032
|
let staleOldAbsPath = null;
|
|
@@ -6029,6 +6053,7 @@ var ContentStore = class {
|
|
|
6029
6053
|
});
|
|
6030
6054
|
}
|
|
6031
6055
|
await this.recordOwnMutation(liveIndex);
|
|
6056
|
+
await options.afterWrite?.({ id, relativePath });
|
|
6032
6057
|
const afterStat = await fs9.stat(absolutePath);
|
|
6033
6058
|
const base = {
|
|
6034
6059
|
collection: schemaItem.logicalPath,
|
|
@@ -6181,14 +6206,21 @@ var ContentStore = class {
|
|
|
6181
6206
|
*/
|
|
6182
6207
|
async afterPrePassForTesting() {
|
|
6183
6208
|
}
|
|
6184
|
-
/**
|
|
6185
|
-
|
|
6209
|
+
/**
|
|
6210
|
+
* Delete an entry and remove it from the index. With `expectId`, only the entry of that content
|
|
6211
|
+
* ID: another at the slug, or none, is `EntryGoneError`. `beforeDelete` runs under both of the
|
|
6212
|
+
* delete's locks before anything changes, and throwing refuses; `afterDelete` runs once it is
|
|
6213
|
+
* gone, under them too.
|
|
6214
|
+
*/
|
|
6215
|
+
async delete(collectionPath, slug, options = {}) {
|
|
6186
6216
|
await this.idIndex();
|
|
6187
6217
|
const collection = this.assertCollection(collectionPath);
|
|
6188
6218
|
const prePass = await this.buildPaths(collection, slug);
|
|
6189
6219
|
if (prePass.existed)
|
|
6190
6220
|
this.assertEntryTypeAvailable(collection, prePass.entryTypeName);
|
|
6191
|
-
if (!prePass.existed) {
|
|
6221
|
+
if (!prePass.existed || options.expectId !== void 0 && prePass.id !== options.expectId) {
|
|
6222
|
+
if (options.expectId !== void 0)
|
|
6223
|
+
throw new EntryGoneError();
|
|
6192
6224
|
await fs9.unlink(prePass.absolutePath);
|
|
6193
6225
|
return;
|
|
6194
6226
|
}
|
|
@@ -6205,6 +6237,10 @@ var ContentStore = class {
|
|
|
6205
6237
|
return RETRY_KEY;
|
|
6206
6238
|
}
|
|
6207
6239
|
const { absolutePath, relativePath } = inLock;
|
|
6240
|
+
if (options.expectId !== void 0 && (!inLock.existed || inLock.id !== options.expectId)) {
|
|
6241
|
+
throw new EntryGoneError();
|
|
6242
|
+
}
|
|
6243
|
+
await options.beforeDelete?.();
|
|
6208
6244
|
await fs9.unlink(absolutePath);
|
|
6209
6245
|
const liveIndex = this._idIndex;
|
|
6210
6246
|
const id = liveIndex.findByPath(relativePath);
|
|
@@ -6212,6 +6248,7 @@ var ContentStore = class {
|
|
|
6212
6248
|
liveIndex.remove(id);
|
|
6213
6249
|
}
|
|
6214
6250
|
await this.recordOwnMutation(liveIndex);
|
|
6251
|
+
await options.afterDelete?.({ id: id ?? inLock.id });
|
|
6215
6252
|
return void 0;
|
|
6216
6253
|
});
|
|
6217
6254
|
if (outcome !== RETRY_KEY)
|
|
@@ -6275,10 +6312,38 @@ var ContentStore = class {
|
|
|
6275
6312
|
});
|
|
6276
6313
|
}
|
|
6277
6314
|
await this.recordOwnMutation(index);
|
|
6315
|
+
await options.afterWrite?.();
|
|
6278
6316
|
return { relativePath: target.relativePath };
|
|
6279
6317
|
});
|
|
6280
6318
|
});
|
|
6281
6319
|
}
|
|
6320
|
+
/**
|
|
6321
|
+
* Replace the file of entry `id`, at `slug` in `collectionPath`, with `raw`, verbatim, under
|
|
6322
|
+
* write()'s locks; `beforeWrite` runs under them first, and throwing refuses, and `afterWrite`
|
|
6323
|
+
* after. For a file no parse can be trusted with, so nothing is re-serialised. `EntryGoneError`
|
|
6324
|
+
* when no file of that id is there.
|
|
6325
|
+
*/
|
|
6326
|
+
async replaceRaw(collectionPath, slug, entry, options = {}) {
|
|
6327
|
+
await this.idIndex();
|
|
6328
|
+
const collection = this.assertCollection(collectionPath);
|
|
6329
|
+
const prePass = await this.buildPaths(collection, slug);
|
|
6330
|
+
if (!prePass.existed || prePass.id !== entry.id)
|
|
6331
|
+
throw new EntryGoneError();
|
|
6332
|
+
this.assertEntryTypeAvailable(collection, prePass.entryTypeName);
|
|
6333
|
+
const lockKey = this.entryLockKey(collection, slug, prePass);
|
|
6334
|
+
return this.withContentWriteExclusion(() => withLock(lockKey, async () => {
|
|
6335
|
+
const inLock = await this.buildPaths(collection, slug);
|
|
6336
|
+
if (!inLock.existed || inLock.id !== entry.id)
|
|
6337
|
+
throw new EntryGoneError();
|
|
6338
|
+
if (this.entryLockKey(collection, slug, inLock) !== lockKey) {
|
|
6339
|
+
throw new ContentConflictError();
|
|
6340
|
+
}
|
|
6341
|
+
await options.beforeWrite?.();
|
|
6342
|
+
await atomicWriteFile(inLock.absolutePath, entry.raw);
|
|
6343
|
+
await this.recordOwnMutation(this._idIndex);
|
|
6344
|
+
await options.afterWrite?.();
|
|
6345
|
+
}));
|
|
6346
|
+
}
|
|
6282
6347
|
/**
|
|
6283
6348
|
* Rename an entry by changing its slug (middle segment of filename).
|
|
6284
6349
|
* Entry filename pattern: {entryTypeName}.{slug}.{id}.{ext}
|
|
@@ -7239,7 +7304,7 @@ function yamlValue(value) {
|
|
|
7239
7304
|
import path12 from "node:path";
|
|
7240
7305
|
|
|
7241
7306
|
// dist/entry-link-resolver.js
|
|
7242
|
-
var
|
|
7307
|
+
var log7 = createDebugLogger({ prefix: "EntryLinks" });
|
|
7243
7308
|
var BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
7244
7309
|
var ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
|
|
7245
7310
|
function resolveEntryUrl(location, contentRoot) {
|
|
@@ -7255,7 +7320,7 @@ function resolveEntryLinksInText(text, idIndex, contentRoot, customResolver, opt
|
|
|
7255
7320
|
return partText.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
|
|
7256
7321
|
const location = findEntry(idIndex, id);
|
|
7257
7322
|
if (!location) {
|
|
7258
|
-
|
|
7323
|
+
log7.warn("resolve", `Entry link target not found: entry:${id}`);
|
|
7259
7324
|
return isLinkable ? "#" : anchor ?? "#";
|
|
7260
7325
|
}
|
|
7261
7326
|
if (isLinkable && !isLinkable(id))
|
|
@@ -7972,6 +8037,7 @@ var branchMetadataShape = {
|
|
|
7972
8037
|
archivedAt: z7.string().optional(),
|
|
7973
8038
|
rebaseFailure: z7.object({ message: z7.string(), firstAt: z7.string(), lastAt: z7.string() }).optional(),
|
|
7974
8039
|
historyRewrittenFrom: z7.string().optional(),
|
|
8040
|
+
historyRewrittenTo: z7.string().optional(),
|
|
7975
8041
|
lastGitHubTip: z7.string().optional(),
|
|
7976
8042
|
keptOverGitHub: z7.array(z7.string()).optional(),
|
|
7977
8043
|
rewrittenOnGitHub: z7.string().optional(),
|
|
@@ -7986,11 +8052,22 @@ var branchMetadataShape = {
|
|
|
7986
8052
|
z7.object({ kind: z7.literal("conflicts") }),
|
|
7987
8053
|
z7.object({ kind: z7.literal("retargeted"), base: z7.string() })
|
|
7988
8054
|
]).optional().catch(void 0),
|
|
7989
|
-
// Dropped when malformed: it
|
|
8055
|
+
// Dropped when malformed, never failing a read: it decorates the PR body, and without `commit`
|
|
8056
|
+
// the worker judges a move against the clone's head before it (worker/github-commits.ts).
|
|
7990
8057
|
review: z7.object({
|
|
7991
8058
|
state: z7.enum(["approved", "changes-requested"]),
|
|
7992
8059
|
by: z7.object({ userId: z7.string(), name: z7.string().optional() }),
|
|
7993
|
-
at: z7.string()
|
|
8060
|
+
at: z7.string(),
|
|
8061
|
+
commit: z7.string().optional()
|
|
8062
|
+
}).optional().catch(void 0),
|
|
8063
|
+
baseUpdateRequestedAt: z7.string().optional().catch(void 0),
|
|
8064
|
+
latestChangesWaiting: z7.string().optional().catch(void 0),
|
|
8065
|
+
// Dropped when malformed: it only ends the editor's wait, which then times out instead.
|
|
8066
|
+
lastBaseUpdate: z7.object({
|
|
8067
|
+
at: z7.string(),
|
|
8068
|
+
outcome: z7.enum(["merged", "up-to-date", "conflicts", "failed"]),
|
|
8069
|
+
baseSha: z7.string().optional(),
|
|
8070
|
+
keptEntries: z7.number().int().nonnegative().optional()
|
|
7994
8071
|
}).optional().catch(void 0)
|
|
7995
8072
|
};
|
|
7996
8073
|
var branchMetadataSchema = z7.object(branchMetadataShape).passthrough();
|
|
@@ -8046,7 +8123,7 @@ function parseBranchMetadataFile(branchRoot, raw) {
|
|
|
8046
8123
|
// dist/branch-registry.js
|
|
8047
8124
|
init_error();
|
|
8048
8125
|
init_logger();
|
|
8049
|
-
var
|
|
8126
|
+
var log8 = createDebugLogger({ prefix: "BranchRegistry" });
|
|
8050
8127
|
var REGISTRY_FILE = "branches.json";
|
|
8051
8128
|
var REGISTRY_STALE_FILE = "branches.stale.json";
|
|
8052
8129
|
var REGISTRY_TEMP_FILE = "branches.tmp.json";
|
|
@@ -8172,7 +8249,7 @@ var BranchRegistry = class {
|
|
|
8172
8249
|
});
|
|
8173
8250
|
await this.regenerate();
|
|
8174
8251
|
} catch (err) {
|
|
8175
|
-
|
|
8252
|
+
log8.warn("invalidate", "Eager regeneration after invalidate() failed", {
|
|
8176
8253
|
error: getErrorMessage(err)
|
|
8177
8254
|
});
|
|
8178
8255
|
}
|
|
@@ -8268,7 +8345,7 @@ import path16 from "node:path";
|
|
|
8268
8345
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8269
8346
|
import lockfile2 from "proper-lockfile";
|
|
8270
8347
|
init_logger();
|
|
8271
|
-
var
|
|
8348
|
+
var log9 = createDebugLogger({ prefix: "OccJsonWrite" });
|
|
8272
8349
|
var OccWriteConflictError = class extends Error {
|
|
8273
8350
|
constructor(message = "Concurrent modification detected") {
|
|
8274
8351
|
super(message);
|
|
@@ -8386,7 +8463,7 @@ async function withOccFileLock(filePath, fn) {
|
|
|
8386
8463
|
await release().catch((err) => {
|
|
8387
8464
|
if (isNodeError(err) && err.code === "ENOENT")
|
|
8388
8465
|
return;
|
|
8389
|
-
|
|
8466
|
+
log9.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
|
|
8390
8467
|
});
|
|
8391
8468
|
}
|
|
8392
8469
|
}
|
|
@@ -8839,7 +8916,7 @@ function feedStdin(child, source, touch, onSourceError) {
|
|
|
8839
8916
|
}
|
|
8840
8917
|
|
|
8841
8918
|
// dist/git-manager.js
|
|
8842
|
-
var
|
|
8919
|
+
var log10 = createDebugLogger({ prefix: "GitManager" });
|
|
8843
8920
|
var GIT_ENV_PASSTHROUGH = /^(PATH|HOME|USER|LANG|LC_[A-Z]+|TZ|TMPDIR|GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE)|GIT_TERMINAL_PROMPT|GIT_TRACE[0-9A-Z_]*)$/;
|
|
8844
8921
|
var FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
|
|
8845
8922
|
function gitChildEnv(overrides) {
|
|
@@ -8961,13 +9038,13 @@ async function ensureGitExcludePattern(repoPath, pattern) {
|
|
|
8961
9038
|
}
|
|
8962
9039
|
const lines = content.split("\n");
|
|
8963
9040
|
if (lines.some((line) => line.trim() === pattern)) {
|
|
8964
|
-
|
|
9041
|
+
log10.debug("git", "Pattern already in .git/info/exclude", { pattern });
|
|
8965
9042
|
return;
|
|
8966
9043
|
}
|
|
8967
9044
|
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
8968
9045
|
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
8969
9046
|
await fs16.writeFile(excludePath, newContent, "utf-8");
|
|
8970
|
-
|
|
9047
|
+
log10.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
8971
9048
|
}
|
|
8972
9049
|
var BaseBranchMissingError = class extends Error {
|
|
8973
9050
|
constructor(remote, baseBranch) {
|
|
@@ -9053,7 +9130,7 @@ var GitManager = class _GitManager {
|
|
|
9053
9130
|
* target that held files beforehand is never touched.
|
|
9054
9131
|
*/
|
|
9055
9132
|
static async cloneRepo(remoteUrl, targetPath, baseBranch = "main", options = {}) {
|
|
9056
|
-
|
|
9133
|
+
log10.debug("git", "Cloning repository", {
|
|
9057
9134
|
remoteUrl,
|
|
9058
9135
|
targetPath,
|
|
9059
9136
|
baseBranch
|
|
@@ -9085,7 +9162,7 @@ var GitManager = class _GitManager {
|
|
|
9085
9162
|
await fs16.mkdir(targetPath);
|
|
9086
9163
|
await git.clone(remoteUrl, targetPath, args);
|
|
9087
9164
|
}
|
|
9088
|
-
|
|
9165
|
+
log10.debug("git", "Clone complete");
|
|
9089
9166
|
}
|
|
9090
9167
|
/**
|
|
9091
9168
|
* Initializes a local bare git repository to simulate a remote for dev mode,
|
|
@@ -9099,15 +9176,15 @@ var GitManager = class _GitManager {
|
|
|
9099
9176
|
static async ensureLocalSimulatedRemote(options) {
|
|
9100
9177
|
const existingLock = remoteInitLocks.get(options.remotePath);
|
|
9101
9178
|
if (existingLock) {
|
|
9102
|
-
|
|
9179
|
+
log10.debug("git", "Waiting for existing remote initialization", {
|
|
9103
9180
|
remotePath: options.remotePath
|
|
9104
9181
|
});
|
|
9105
9182
|
await existingLock;
|
|
9106
9183
|
}
|
|
9107
|
-
const lockPromise =
|
|
9184
|
+
const lockPromise = log10.timed("git", "ensureLocalSimulatedRemote", async () => {
|
|
9108
9185
|
let releaseLock;
|
|
9109
9186
|
try {
|
|
9110
|
-
|
|
9187
|
+
log10.debug("git", "Initializing local simulated remote", {
|
|
9111
9188
|
remotePath: options.remotePath,
|
|
9112
9189
|
baseBranch: options.baseBranch
|
|
9113
9190
|
});
|
|
@@ -9125,15 +9202,15 @@ var GitManager = class _GitManager {
|
|
|
9125
9202
|
try {
|
|
9126
9203
|
const repack = await repackBareRemoteIfNeeded(options.remotePath);
|
|
9127
9204
|
if (repack.repacked)
|
|
9128
|
-
|
|
9205
|
+
log10.debug("git", "Repacked local simulated remote", repack);
|
|
9129
9206
|
} catch (err) {
|
|
9130
|
-
|
|
9207
|
+
log10.debug("git", "Could not repack local simulated remote", {
|
|
9131
9208
|
error: getErrorMessage(err)
|
|
9132
9209
|
});
|
|
9133
9210
|
}
|
|
9134
9211
|
}
|
|
9135
9212
|
if (remoteExists && await _GitManager.bareRemoteHasBranch(options.remotePath, options.baseBranch)) {
|
|
9136
|
-
|
|
9213
|
+
log10.debug("git", "Remote already has base branch, skipping");
|
|
9137
9214
|
await ensureRemoteGitConfig(options.remotePath);
|
|
9138
9215
|
return;
|
|
9139
9216
|
}
|
|
@@ -9153,8 +9230,8 @@ var GitManager = class _GitManager {
|
|
|
9153
9230
|
}
|
|
9154
9231
|
let hasCommits = false;
|
|
9155
9232
|
try {
|
|
9156
|
-
const
|
|
9157
|
-
hasCommits =
|
|
9233
|
+
const log12 = await sourceGit.log(["-1"]);
|
|
9234
|
+
hasCommits = log12.total > 0;
|
|
9158
9235
|
} catch {
|
|
9159
9236
|
hasCommits = false;
|
|
9160
9237
|
}
|
|
@@ -9166,12 +9243,12 @@ var GitManager = class _GitManager {
|
|
|
9166
9243
|
throw new Error(`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`);
|
|
9167
9244
|
}
|
|
9168
9245
|
if (remoteExists) {
|
|
9169
|
-
|
|
9246
|
+
log10.debug("git", "Existing remote is missing base branch \u2014 pushing it from source", {
|
|
9170
9247
|
remotePath: options.remotePath,
|
|
9171
9248
|
baseBranch: options.baseBranch
|
|
9172
9249
|
});
|
|
9173
9250
|
} else {
|
|
9174
|
-
|
|
9251
|
+
log10.debug("git", "Creating bare remote repository");
|
|
9175
9252
|
await simpleGit2().raw([
|
|
9176
9253
|
"init",
|
|
9177
9254
|
"--bare",
|
|
@@ -9186,13 +9263,13 @@ var GitManager = class _GitManager {
|
|
|
9186
9263
|
baseBranch: options.baseBranch,
|
|
9187
9264
|
subdirectory: options.subdirectory
|
|
9188
9265
|
});
|
|
9189
|
-
|
|
9266
|
+
log10.debug("git", "Remote initialization complete");
|
|
9190
9267
|
} finally {
|
|
9191
9268
|
if (releaseLock) {
|
|
9192
9269
|
try {
|
|
9193
9270
|
await releaseLock();
|
|
9194
9271
|
} catch (err) {
|
|
9195
|
-
|
|
9272
|
+
log10.debug("git", "Failed to release remote-init lock", { err });
|
|
9196
9273
|
}
|
|
9197
9274
|
}
|
|
9198
9275
|
remoteInitLocks.delete(options.remotePath);
|
|
@@ -9474,7 +9551,7 @@ var GitManager = class _GitManager {
|
|
|
9474
9551
|
throw new Error(`CanopyCMS: cannot read the git remote at ${remotePath}: ${getErrorMessage(err)}`);
|
|
9475
9552
|
});
|
|
9476
9553
|
if (stat?.isDirectory()) {
|
|
9477
|
-
|
|
9554
|
+
log10.debug("git", "Auto-detected local remote", { path: remotePath });
|
|
9478
9555
|
return remotePath;
|
|
9479
9556
|
}
|
|
9480
9557
|
if (stat) {
|
|
@@ -9600,7 +9677,7 @@ var GitManager = class _GitManager {
|
|
|
9600
9677
|
try {
|
|
9601
9678
|
const stat = await fs16.stat(gitPath);
|
|
9602
9679
|
if (stat.isDirectory()) {
|
|
9603
|
-
|
|
9680
|
+
log10.debug("git", "Removing corrupt .git directory", {
|
|
9604
9681
|
workspacePath: options.workspacePath
|
|
9605
9682
|
});
|
|
9606
9683
|
await fs16.rm(gitPath, { recursive: true });
|
|
@@ -10066,17 +10143,17 @@ var GitManager = class _GitManager {
|
|
|
10066
10143
|
const changes = [];
|
|
10067
10144
|
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
10068
10145
|
const [srcMode, dstMode, srcSha, dstSha, code] = tokens[i].slice(1).split(" ");
|
|
10069
|
-
const
|
|
10146
|
+
const path28 = tokens[i + 1];
|
|
10070
10147
|
const letter = code?.charAt(0);
|
|
10071
10148
|
if (letter === "A" || letter === "D")
|
|
10072
|
-
changes.push({ status: letter, path:
|
|
10149
|
+
changes.push({ status: letter, path: path28 });
|
|
10073
10150
|
else if (letter === "M" || letter === "T") {
|
|
10074
10151
|
const unhashed = /^0+$/.test(dstSha) && srcMode === dstMode ? srcSha : void 0;
|
|
10075
|
-
changes.push({ status: "M", path:
|
|
10152
|
+
changes.push({ status: "M", path: path28, unhashed });
|
|
10076
10153
|
}
|
|
10077
10154
|
}
|
|
10078
10155
|
const unchanged = await this.matchingBlobs(changes.flatMap((c) => c.unhashed ? [{ path: c.path, sha: c.unhashed }] : []));
|
|
10079
|
-
return changes.filter((c) => !unchanged.has(c.path)).map(({ status, path:
|
|
10156
|
+
return changes.filter((c) => !unchanged.has(c.path)).map(({ status, path: path28 }) => ({ status, path: path28 }));
|
|
10080
10157
|
}
|
|
10081
10158
|
/**
|
|
10082
10159
|
* The paths whose working-tree file hashes to `sha`, as git hashes it: a symlink's blob is its
|
|
@@ -10171,6 +10248,32 @@ var GitManager = class _GitManager {
|
|
|
10171
10248
|
throw err;
|
|
10172
10249
|
}
|
|
10173
10250
|
}
|
|
10251
|
+
/**
|
|
10252
|
+
* The text of the blob `sha`, `'too-large'` when it is over `maxBytes`, or `undefined` when this
|
|
10253
|
+
* clone has no such blob. Any other failure throws.
|
|
10254
|
+
*/
|
|
10255
|
+
async readBlob(sha, maxBytes) {
|
|
10256
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(sha))
|
|
10257
|
+
return void 0;
|
|
10258
|
+
let sizeText;
|
|
10259
|
+
try {
|
|
10260
|
+
sizeText = (await this.git.raw(["cat-file", "-s", "--end-of-options", sha])).trim();
|
|
10261
|
+
} catch (err) {
|
|
10262
|
+
if (/could not get object info|Not a valid object name/.test(getErrorMessage(err))) {
|
|
10263
|
+
return void 0;
|
|
10264
|
+
}
|
|
10265
|
+
throw err;
|
|
10266
|
+
}
|
|
10267
|
+
if (!/^\d+$/.test(sizeText))
|
|
10268
|
+
throw new Error(`git cat-file -s ${sha} printed no size`);
|
|
10269
|
+
const size = Number(sizeText);
|
|
10270
|
+
if (size > maxBytes)
|
|
10271
|
+
return "too-large";
|
|
10272
|
+
const text = await this.git.raw(["cat-file", "blob", "--end-of-options", sha]);
|
|
10273
|
+
if (size > 0 && text === "")
|
|
10274
|
+
throw new Error(`git cat-file blob ${sha} printed nothing`);
|
|
10275
|
+
return text;
|
|
10276
|
+
}
|
|
10174
10277
|
async ensureAuthor(author) {
|
|
10175
10278
|
const config = await this.git.listConfig();
|
|
10176
10279
|
const isManaged = config.all["canopycms.managed"] === "true";
|
|
@@ -10219,10 +10322,14 @@ var GitManager = class _GitManager {
|
|
|
10219
10322
|
const status = await this.status();
|
|
10220
10323
|
return status.files.map((f) => f.path);
|
|
10221
10324
|
}
|
|
10325
|
+
/** The remote's URL as configured; `git remote -v`'s listing would split a path with a space. */
|
|
10222
10326
|
async getRemoteUrl() {
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10327
|
+
try {
|
|
10328
|
+
const url = (await this.git.raw(["remote", "get-url", this.remote])).trim();
|
|
10329
|
+
return url === "" ? void 0 : url;
|
|
10330
|
+
} catch {
|
|
10331
|
+
return void 0;
|
|
10332
|
+
}
|
|
10226
10333
|
}
|
|
10227
10334
|
/**
|
|
10228
10335
|
* Excludes `pattern` (e.g. `.canopy-meta/`) from this workspace's git, so it
|
|
@@ -10259,7 +10366,7 @@ var GitManager = class _GitManager {
|
|
|
10259
10366
|
}
|
|
10260
10367
|
}
|
|
10261
10368
|
async createOrphanSettingsBranchInner(branchName, initialFiles) {
|
|
10262
|
-
|
|
10369
|
+
log10.debug("git", "Ensuring settings branch", { branchName });
|
|
10263
10370
|
const branches = await this.git.branch();
|
|
10264
10371
|
const localExists = branches.all.includes(branchName);
|
|
10265
10372
|
if (localExists) {
|
|
@@ -10270,7 +10377,7 @@ var GitManager = class _GitManager {
|
|
|
10270
10377
|
remoteTip = await this.remoteBranchTip(branchName);
|
|
10271
10378
|
} catch (err) {
|
|
10272
10379
|
if (localExists && !await this.isEmptyInitialBranch(branchName)) {
|
|
10273
|
-
|
|
10380
|
+
log10.debug("git", "Remote unreadable; serving the local settings branch", { branchName });
|
|
10274
10381
|
return;
|
|
10275
10382
|
}
|
|
10276
10383
|
throw new SettingsRemoteUnreadableError(branchName, this.remote, err);
|
|
@@ -10283,7 +10390,7 @@ var GitManager = class _GitManager {
|
|
|
10283
10390
|
const fetchedTip = await this.fetchBranchTip(branchName);
|
|
10284
10391
|
await this.assertNotContentHistory(branchName, fetchedTip);
|
|
10285
10392
|
await this.git.raw(["checkout", "-b", branchName, fetchedTip]);
|
|
10286
|
-
|
|
10393
|
+
log10.debug("git", "Checked out the remote settings branch", { branchName });
|
|
10287
10394
|
return;
|
|
10288
10395
|
}
|
|
10289
10396
|
await this.git.raw(["switch", "--orphan", branchName]);
|
|
@@ -10299,7 +10406,7 @@ var GitManager = class _GitManager {
|
|
|
10299
10406
|
await this.git.add(filePath);
|
|
10300
10407
|
}
|
|
10301
10408
|
await this.git.commit("Initialize settings branch", ["--allow-empty"]);
|
|
10302
|
-
|
|
10409
|
+
log10.debug("git", "Orphan settings branch created", { branchName });
|
|
10303
10410
|
}
|
|
10304
10411
|
/**
|
|
10305
10412
|
* Compares the checked-out local settings branch with the remote's tip.
|
|
@@ -10332,7 +10439,7 @@ var GitManager = class _GitManager {
|
|
|
10332
10439
|
}
|
|
10333
10440
|
await this.assertNotContentHistory(branchName, fetchedTip);
|
|
10334
10441
|
await this.git.raw(["checkout", "-B", branchName, fetchedTip]);
|
|
10335
|
-
|
|
10442
|
+
log10.debug("git", "Replaced an empty settings branch with the remote one", { branchName });
|
|
10336
10443
|
}
|
|
10337
10444
|
/**
|
|
10338
10445
|
* The remote's tip for `branch`, or `undefined` when the remote has no such
|
|
@@ -10602,10 +10709,10 @@ async function hasBranchMetadata(branchRoot) {
|
|
|
10602
10709
|
return fs17.access(path21.join(branchRoot, BRANCH_META_DIR, BRANCH_META_FILE)).then(() => true, () => false);
|
|
10603
10710
|
}
|
|
10604
10711
|
async function stageBranchWorkspace(options) {
|
|
10605
|
-
const { provisionLog:
|
|
10712
|
+
const { provisionLog: log12 } = options;
|
|
10606
10713
|
const stagingPath = path21.join(options.baseRoot, stagingDirName(options.dirName));
|
|
10607
10714
|
try {
|
|
10608
|
-
await
|
|
10715
|
+
await log12.step("clone", () => GitManager.cloneWorkspace(options.remoteUrl, stagingPath, options.baseBranch, {
|
|
10609
10716
|
...options.clone,
|
|
10610
10717
|
noCheckout: true
|
|
10611
10718
|
}));
|
|
@@ -10616,14 +10723,14 @@ async function stageBranchWorkspace(options) {
|
|
|
10616
10723
|
});
|
|
10617
10724
|
const cone = options.sparseCone;
|
|
10618
10725
|
if (cone)
|
|
10619
|
-
await
|
|
10726
|
+
await log12.step("sparse", () => git.setSparseCone(cone));
|
|
10620
10727
|
if (await hasBranchMetadata(path21.join(options.baseRoot, options.dirName))) {
|
|
10621
10728
|
await removeLeftoverDir(stagingPath);
|
|
10622
10729
|
return { kind: "exists" };
|
|
10623
10730
|
}
|
|
10624
|
-
await
|
|
10625
|
-
await
|
|
10626
|
-
const written = await
|
|
10731
|
+
await log12.step("checkout", () => git.checkoutFreshClone(options.branchName));
|
|
10732
|
+
await log12.step("exclude", () => git.ensureGitExclude(options.gitExcludePattern));
|
|
10733
|
+
const written = await log12.step("metadata", () => writeOccJsonFile(path21.join(stagingPath, BRANCH_META_DIR, BRANCH_META_FILE), { ...options.metadata }, { expectedVersion: null, trailingNewline: true }));
|
|
10627
10734
|
return {
|
|
10628
10735
|
kind: "staged",
|
|
10629
10736
|
stagingPath,
|
|
@@ -11095,7 +11202,7 @@ import path24 from "node:path";
|
|
|
11095
11202
|
import matter3 from "gray-matter";
|
|
11096
11203
|
import { parse as yamlParse2 } from "yaml";
|
|
11097
11204
|
init_error();
|
|
11098
|
-
var
|
|
11205
|
+
var log11 = createDebugLogger({ prefix: "ContentListing" });
|
|
11099
11206
|
var normalizePath = (root, target) => {
|
|
11100
11207
|
const result = validateAndNormalizePath(root, target);
|
|
11101
11208
|
if (!result.valid) {
|
|
@@ -11123,7 +11230,7 @@ var readEntryData = async (filePath, format, bodyFieldName = "body") => {
|
|
|
11123
11230
|
} catch (err) {
|
|
11124
11231
|
if (isNotFoundError(err))
|
|
11125
11232
|
return {};
|
|
11126
|
-
|
|
11233
|
+
log11.warn("readEntryData", `Failed to read entry data from ${filePath}: ${getErrorMessage(err)}`);
|
|
11127
11234
|
return {};
|
|
11128
11235
|
}
|
|
11129
11236
|
};
|
|
@@ -11249,7 +11356,7 @@ var listCollectionEntries = async (root, collection, onSkip, readsId) => {
|
|
|
11249
11356
|
const relativePath = normalizePath(root, absolutePath);
|
|
11250
11357
|
const parsed = parseTypedFilename(file.name, entryTypes);
|
|
11251
11358
|
if (!parsed) {
|
|
11252
|
-
|
|
11359
|
+
log11.warn("listCollectionEntries", `Skipping file with unrecognized filename format: ${file.name} (expected {type}.{slug}.{id}.{ext} with a known entry type and valid 12-char Base58 ID)`);
|
|
11253
11360
|
if (looksLikeMalformedEntry(file.name, entryTypes)) {
|
|
11254
11361
|
onSkip?.({ filename: file.name, collectionPath: collection.logicalPath });
|
|
11255
11362
|
}
|
|
@@ -11338,77 +11445,77 @@ function isValidImageCropValue(value) {
|
|
|
11338
11445
|
const rounded = roundCropRect({ x, y, w, h });
|
|
11339
11446
|
return isValidCropRect(rounded.x, rounded.y, rounded.w, rounded.h);
|
|
11340
11447
|
}
|
|
11341
|
-
function validateImageValue(field, value,
|
|
11448
|
+
function validateImageValue(field, value, path28) {
|
|
11342
11449
|
if (!isPlainRecord3(value)) {
|
|
11343
|
-
return [{ fieldPath:
|
|
11450
|
+
return [{ fieldPath: path28, message: "Expected an image object with { src, alt }" }];
|
|
11344
11451
|
}
|
|
11345
11452
|
const errors = [];
|
|
11346
11453
|
if (typeof value.src !== "string" || value.src.trim() === "") {
|
|
11347
|
-
errors.push({ fieldPath: `${
|
|
11454
|
+
errors.push({ fieldPath: `${path28}.src`, message: "Image src is required" });
|
|
11348
11455
|
}
|
|
11349
11456
|
if (typeof value.alt !== "string") {
|
|
11350
|
-
errors.push({ fieldPath: `${
|
|
11457
|
+
errors.push({ fieldPath: `${path28}.alt`, message: "Image alt text is required" });
|
|
11351
11458
|
} else if (value.alt.trim() === "" && field.altOptional !== true) {
|
|
11352
|
-
errors.push({ fieldPath: `${
|
|
11459
|
+
errors.push({ fieldPath: `${path28}.alt`, message: "Image alt text is required" });
|
|
11353
11460
|
}
|
|
11354
11461
|
if (value.width !== void 0 && !isPositiveInt(value.width)) {
|
|
11355
|
-
errors.push({ fieldPath: `${
|
|
11462
|
+
errors.push({ fieldPath: `${path28}.width`, message: "Image width must be a positive integer" });
|
|
11356
11463
|
}
|
|
11357
11464
|
if (value.height !== void 0 && !isPositiveInt(value.height)) {
|
|
11358
11465
|
errors.push({
|
|
11359
|
-
fieldPath: `${
|
|
11466
|
+
fieldPath: `${path28}.height`,
|
|
11360
11467
|
message: "Image height must be a positive integer"
|
|
11361
11468
|
});
|
|
11362
11469
|
}
|
|
11363
11470
|
if (value.crop !== void 0 && !isValidImageCropValue(value.crop)) {
|
|
11364
|
-
errors.push({ fieldPath: `${
|
|
11471
|
+
errors.push({ fieldPath: `${path28}.crop`, message: "Invalid image crop rect" });
|
|
11365
11472
|
}
|
|
11366
11473
|
return errors;
|
|
11367
11474
|
}
|
|
11368
11475
|
var selectOptionValues = (field) => (field.options ?? []).map((opt) => typeof opt === "string" ? opt : opt.value);
|
|
11369
|
-
function validateScalar(field, value,
|
|
11476
|
+
function validateScalar(field, value, path28) {
|
|
11370
11477
|
switch (field.type) {
|
|
11371
11478
|
case "number":
|
|
11372
11479
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
11373
|
-
return [{ fieldPath:
|
|
11480
|
+
return [{ fieldPath: path28, message: "Expected a number" }];
|
|
11374
11481
|
}
|
|
11375
11482
|
return [];
|
|
11376
11483
|
case "boolean":
|
|
11377
11484
|
if (typeof value !== "boolean") {
|
|
11378
|
-
return [{ fieldPath:
|
|
11485
|
+
return [{ fieldPath: path28, message: "Expected true or false" }];
|
|
11379
11486
|
}
|
|
11380
11487
|
return [];
|
|
11381
11488
|
case "date":
|
|
11382
11489
|
if (value !== "" && !isCalendarDay(value)) {
|
|
11383
|
-
return [{ fieldPath:
|
|
11490
|
+
return [{ fieldPath: path28, message: "Expected a real date, written YYYY-MM-DD" }];
|
|
11384
11491
|
}
|
|
11385
11492
|
return [];
|
|
11386
11493
|
case "datetime":
|
|
11387
11494
|
if (typeof value !== "string" || value !== "" && Number.isNaN(Date.parse(value))) {
|
|
11388
|
-
return [{ fieldPath:
|
|
11495
|
+
return [{ fieldPath: path28, message: "Expected a valid date/time string" }];
|
|
11389
11496
|
}
|
|
11390
11497
|
return [];
|
|
11391
11498
|
case "select": {
|
|
11392
11499
|
if (typeof value !== "string") {
|
|
11393
|
-
return [{ fieldPath:
|
|
11500
|
+
return [{ fieldPath: path28, message: "Expected a selection" }];
|
|
11394
11501
|
}
|
|
11395
11502
|
const options = selectOptionValues(field);
|
|
11396
11503
|
if (value !== "" && !options.includes(value)) {
|
|
11397
|
-
return [{ fieldPath:
|
|
11504
|
+
return [{ fieldPath: path28, message: `Must be one of: ${options.join(", ")}` }];
|
|
11398
11505
|
}
|
|
11399
11506
|
return [];
|
|
11400
11507
|
}
|
|
11401
11508
|
case "reference":
|
|
11402
11509
|
if (referenceValueId(value) === void 0) {
|
|
11403
|
-
return [{ fieldPath:
|
|
11510
|
+
return [{ fieldPath: path28, message: "Expected a reference id" }];
|
|
11404
11511
|
}
|
|
11405
11512
|
return [];
|
|
11406
11513
|
case "image":
|
|
11407
|
-
return validateImageValue(field, value,
|
|
11514
|
+
return validateImageValue(field, value, path28);
|
|
11408
11515
|
default:
|
|
11409
11516
|
if (STRING_FIELD_TYPES.has(field.type)) {
|
|
11410
11517
|
if (typeof value !== "string") {
|
|
11411
|
-
return [{ fieldPath:
|
|
11518
|
+
return [{ fieldPath: path28, message: "Expected text" }];
|
|
11412
11519
|
}
|
|
11413
11520
|
}
|
|
11414
11521
|
return [];
|
|
@@ -11431,12 +11538,12 @@ function validateEntryData(fields, data, pathPrefix = "", options = {}) {
|
|
|
11431
11538
|
errors.push(...validateEntryData(field.fields, data, pathPrefix, options));
|
|
11432
11539
|
continue;
|
|
11433
11540
|
}
|
|
11434
|
-
const
|
|
11541
|
+
const path28 = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
|
|
11435
11542
|
const value = data[field.name];
|
|
11436
11543
|
const isRequired = options.required !== false && "required" in field && field.required === true;
|
|
11437
11544
|
if (isRequired && isEmptyForRequired(field, value)) {
|
|
11438
11545
|
const message = Array.isArray(value) ? "At least one item is required" : "This field is required";
|
|
11439
|
-
errors.push({ fieldPath:
|
|
11546
|
+
errors.push({ fieldPath: path28, message });
|
|
11440
11547
|
continue;
|
|
11441
11548
|
}
|
|
11442
11549
|
if (value === void 0 || value === null)
|
|
@@ -11444,7 +11551,7 @@ function validateEntryData(fields, data, pathPrefix = "", options = {}) {
|
|
|
11444
11551
|
if (Array.isArray(value) && "maxItems" in field && typeof field.maxItems === "number" && value.length > field.maxItems) {
|
|
11445
11552
|
const cap = field.maxItems;
|
|
11446
11553
|
errors.push({
|
|
11447
|
-
fieldPath:
|
|
11554
|
+
fieldPath: path28,
|
|
11448
11555
|
message: `At most ${cap} ${cap === 1 ? "item" : "items"}; this list has ${value.length}`,
|
|
11449
11556
|
maxItems: { count: value.length, cap }
|
|
11450
11557
|
});
|
|
@@ -11452,11 +11559,11 @@ function validateEntryData(fields, data, pathPrefix = "", options = {}) {
|
|
|
11452
11559
|
if (field.type === "block") {
|
|
11453
11560
|
const blockField = field;
|
|
11454
11561
|
if (!Array.isArray(value)) {
|
|
11455
|
-
errors.push({ fieldPath:
|
|
11562
|
+
errors.push({ fieldPath: path28, message: "Expected a list of blocks" });
|
|
11456
11563
|
continue;
|
|
11457
11564
|
}
|
|
11458
11565
|
value.forEach((item, index) => {
|
|
11459
|
-
const itemPath = `${
|
|
11566
|
+
const itemPath = `${path28}[${index}]`;
|
|
11460
11567
|
if (!isPlainRecord3(item)) {
|
|
11461
11568
|
errors.push({ fieldPath: itemPath, message: "Expected a block object" });
|
|
11462
11569
|
return;
|
|
@@ -11478,11 +11585,11 @@ function validateEntryData(fields, data, pathPrefix = "", options = {}) {
|
|
|
11478
11585
|
const objectField = field;
|
|
11479
11586
|
if (objectField.list) {
|
|
11480
11587
|
if (!Array.isArray(value)) {
|
|
11481
|
-
errors.push({ fieldPath:
|
|
11588
|
+
errors.push({ fieldPath: path28, message: "Expected a list of items" });
|
|
11482
11589
|
continue;
|
|
11483
11590
|
}
|
|
11484
11591
|
value.forEach((item, index) => {
|
|
11485
|
-
const itemPath = `${
|
|
11592
|
+
const itemPath = `${path28}[${index}]`;
|
|
11486
11593
|
if (!isPlainRecord3(item)) {
|
|
11487
11594
|
errors.push({ fieldPath: itemPath, message: "Expected an object" });
|
|
11488
11595
|
return;
|
|
@@ -11492,27 +11599,27 @@ function validateEntryData(fields, data, pathPrefix = "", options = {}) {
|
|
|
11492
11599
|
}
|
|
11493
11600
|
});
|
|
11494
11601
|
} else if (!isPlainRecord3(value)) {
|
|
11495
|
-
errors.push({ fieldPath:
|
|
11602
|
+
errors.push({ fieldPath: path28, message: "Expected an object" });
|
|
11496
11603
|
} else if (objectField.fields) {
|
|
11497
|
-
errors.push(...validateEntryData(objectField.fields, value,
|
|
11604
|
+
errors.push(...validateEntryData(objectField.fields, value, path28, options));
|
|
11498
11605
|
}
|
|
11499
11606
|
continue;
|
|
11500
11607
|
}
|
|
11501
11608
|
if ("list" in field && field.list) {
|
|
11502
11609
|
if (!Array.isArray(value)) {
|
|
11503
|
-
errors.push({ fieldPath:
|
|
11610
|
+
errors.push({ fieldPath: path28, message: "Expected a list of values" });
|
|
11504
11611
|
continue;
|
|
11505
11612
|
}
|
|
11506
11613
|
value.forEach((item, index) => {
|
|
11507
|
-
errors.push(...validateScalar(field, item, `${
|
|
11614
|
+
errors.push(...validateScalar(field, item, `${path28}[${index}]`));
|
|
11508
11615
|
});
|
|
11509
11616
|
continue;
|
|
11510
11617
|
}
|
|
11511
11618
|
if (Array.isArray(value) && KNOWN_FIELD_TYPES.has(field.type)) {
|
|
11512
|
-
errors.push({ fieldPath:
|
|
11619
|
+
errors.push({ fieldPath: path28, message: "Expected a single value, not a list" });
|
|
11513
11620
|
continue;
|
|
11514
11621
|
}
|
|
11515
|
-
errors.push(...validateScalar(field, value,
|
|
11622
|
+
errors.push(...validateScalar(field, value, path28));
|
|
11516
11623
|
}
|
|
11517
11624
|
return errors;
|
|
11518
11625
|
}
|
|
@@ -11714,33 +11821,50 @@ ${cached.issues.map((issue) => ` - ${issue.message}`).join("\n")}`);
|
|
|
11714
11821
|
|
|
11715
11822
|
// dist/cli/generate-ai-content.js
|
|
11716
11823
|
init_error();
|
|
11824
|
+
|
|
11825
|
+
// dist/cli/site-config.js
|
|
11826
|
+
import path26 from "node:path";
|
|
11827
|
+
import { createJiti } from "jiti";
|
|
11717
11828
|
var jiti = createJiti(import.meta.url);
|
|
11829
|
+
async function importSiteModule(filePath) {
|
|
11830
|
+
return await jiti.import(filePath);
|
|
11831
|
+
}
|
|
11832
|
+
async function importSiteServerConfig(projectDir) {
|
|
11833
|
+
const configPath = path26.join(projectDir, "canopycms.config.ts");
|
|
11834
|
+
const mod = await importSiteModule(configPath);
|
|
11835
|
+
const configExport = mod.default ?? mod.config ?? mod;
|
|
11836
|
+
const server = typeof configExport === "object" && configExport !== null && "server" in configExport ? configExport.server : configExport;
|
|
11837
|
+
return { configPath, server };
|
|
11838
|
+
}
|
|
11839
|
+
async function importSiteEntrySchemaRegistry(projectDir, appDir) {
|
|
11840
|
+
const schemasPath = path26.join(projectDir, appDir, "schemas.ts");
|
|
11841
|
+
const mod = await importSiteModule(schemasPath);
|
|
11842
|
+
const registry2 = mod.entrySchemaRegistry ?? mod;
|
|
11843
|
+
return { schemasPath, registry: registry2 };
|
|
11844
|
+
}
|
|
11845
|
+
|
|
11846
|
+
// dist/cli/generate-ai-content.js
|
|
11718
11847
|
async function generateAIContentCLI(options) {
|
|
11719
11848
|
const { projectDir, outputDir = "public/ai", configPath, appDir = "app" } = options;
|
|
11720
11849
|
console.log("\nCanopyCMS generate-ai-content\n");
|
|
11721
|
-
|
|
11722
|
-
let canopyConfigModule;
|
|
11850
|
+
let serverConfig;
|
|
11723
11851
|
try {
|
|
11724
|
-
|
|
11852
|
+
serverConfig = (await importSiteServerConfig(projectDir)).server;
|
|
11725
11853
|
} catch (err) {
|
|
11726
|
-
console.error(`Could not load config from ${
|
|
11854
|
+
console.error(`Could not load config from ${path27.join(projectDir, "canopycms.config.ts")}`);
|
|
11727
11855
|
console.error(getErrorMessage(err));
|
|
11728
11856
|
process.exit(1);
|
|
11729
11857
|
}
|
|
11730
|
-
const configExport = canopyConfigModule.default ?? canopyConfigModule.config ?? canopyConfigModule;
|
|
11731
|
-
const serverConfig = typeof configExport === "object" && configExport !== null && "server" in configExport ? configExport.server : configExport;
|
|
11732
|
-
const schemasPath = path26.join(projectDir, appDir, "schemas.ts");
|
|
11733
11858
|
let entrySchemaRegistry = {};
|
|
11734
11859
|
try {
|
|
11735
|
-
|
|
11736
|
-
entrySchemaRegistry = schemasModule.entrySchemaRegistry ?? schemasModule;
|
|
11860
|
+
entrySchemaRegistry = (await importSiteEntrySchemaRegistry(projectDir, appDir)).registry;
|
|
11737
11861
|
} catch {
|
|
11738
11862
|
console.warn(` No ${appDir}/schemas.ts found, using empty entry schema registry`);
|
|
11739
11863
|
}
|
|
11740
11864
|
let aiConfig;
|
|
11741
11865
|
if (configPath) {
|
|
11742
11866
|
try {
|
|
11743
|
-
const aiConfigModule = await
|
|
11867
|
+
const aiConfigModule = await importSiteModule(path27.resolve(configPath));
|
|
11744
11868
|
aiConfig = aiConfigModule.aiContentConfig ?? aiConfigModule.default ?? aiConfigModule.config;
|
|
11745
11869
|
} catch (err) {
|
|
11746
11870
|
console.error(`Could not load AI config from ${configPath}`);
|
|
@@ -11757,7 +11881,7 @@ async function generateAIContentCLI(options) {
|
|
|
11757
11881
|
console.error("Make sure canopycms.config.ts uses defineCanopyConfig().");
|
|
11758
11882
|
process.exit(1);
|
|
11759
11883
|
}
|
|
11760
|
-
const resolvedOutput =
|
|
11884
|
+
const resolvedOutput = path27.resolve(projectDir, outputDir);
|
|
11761
11885
|
console.log(` Output: ${resolvedOutput}`);
|
|
11762
11886
|
console.log(` Mode: ${serverConfig.mode ?? "dev"}`);
|
|
11763
11887
|
const result = await generateAIContentFiles({
|