canopycms 0.0.68-int.113 → 0.0.68-int.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/ai/generate.d.ts +4 -4
  2. package/dist/ai/generate.js +75 -14
  3. package/dist/ai/types.d.ts +12 -0
  4. package/dist/api/branch.js +13 -13
  5. package/dist/auth/cache.d.ts +1 -1
  6. package/dist/auth/cache.js +1 -1
  7. package/dist/auth/file-based-auth-cache.d.ts +8 -0
  8. package/dist/auth/file-based-auth-cache.js +11 -0
  9. package/dist/authorization/branch.d.ts +1 -1
  10. package/dist/authorization/branch.js +2 -1
  11. package/dist/cli/cli.js +160 -28
  12. package/dist/cli/generate-ai-content.js +156 -24
  13. package/dist/content-reader.js +2 -1
  14. package/dist/context.js +2 -1
  15. package/dist/editor/BranchManager.js +13 -17
  16. package/dist/editor/CommentsPanel.js +7 -6
  17. package/dist/editor/Editor.js +152 -30
  18. package/dist/editor/EditorAuthGate.js +2 -2
  19. package/dist/editor/EditorPanes.d.ts +10 -0
  20. package/dist/editor/EditorPanes.js +32 -8
  21. package/dist/editor/FormRenderer.d.ts +10 -0
  22. package/dist/editor/FormRenderer.js +92 -73
  23. package/dist/editor/PreviewFrame.d.ts +3 -1
  24. package/dist/editor/PreviewFrame.js +72 -9
  25. package/dist/editor/admin/SystemHealthPanel.js +27 -24
  26. package/dist/editor/branch-status.d.ts +16 -0
  27. package/dist/editor/branch-status.js +28 -0
  28. package/dist/editor/comments/BranchComments.js +1 -1
  29. package/dist/editor/comments/EntryComments.d.ts +3 -0
  30. package/dist/editor/comments/EntryComments.js +3 -3
  31. package/dist/editor/comments/FieldWrapper.d.ts +3 -0
  32. package/dist/editor/comments/FieldWrapper.js +2 -2
  33. package/dist/editor/comments/InlineCommentThread.js +2 -1
  34. package/dist/editor/comments/ThreadCarousel.js +2 -2
  35. package/dist/editor/components/ConfirmDeleteModal.js +1 -1
  36. package/dist/editor/components/EditorCrashScreen.js +1 -1
  37. package/dist/editor/components/EditorHeader.d.ts +2 -0
  38. package/dist/editor/components/EditorHeader.js +15 -36
  39. package/dist/editor/components/EditorSidebar.d.ts +5 -6
  40. package/dist/editor/components/EditorSidebar.js +2 -2
  41. package/dist/editor/components/EntryCreateModal.js +1 -1
  42. package/dist/editor/components/NoEditPermissionNotice.d.ts +3 -2
  43. package/dist/editor/components/NoEditPermissionNotice.js +3 -2
  44. package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
  45. package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
  46. package/dist/editor/components/RenameEntryModal.js +1 -1
  47. package/dist/editor/components/StagedChangesDrawer.js +3 -3
  48. package/dist/editor/components/UndoToastMessage.d.ts +14 -0
  49. package/dist/editor/components/UndoToastMessage.js +9 -0
  50. package/dist/editor/components/UserBadge.js +9 -5
  51. package/dist/editor/copy.d.ts +14 -0
  52. package/dist/editor/copy.js +14 -0
  53. package/dist/editor/fields/BlockField.d.ts +5 -0
  54. package/dist/editor/fields/BlockField.js +38 -32
  55. package/dist/editor/fields/CodeField.d.ts +2 -0
  56. package/dist/editor/fields/CodeField.js +5 -2
  57. package/dist/editor/fields/DateTimeField.d.ts +2 -0
  58. package/dist/editor/fields/DateTimeField.js +5 -2
  59. package/dist/editor/fields/FieldCrashFallback.d.ts +3 -3
  60. package/dist/editor/fields/FieldCrashFallback.js +4 -4
  61. package/dist/editor/fields/FieldLabel.d.ts +24 -0
  62. package/dist/editor/fields/FieldLabel.js +9 -0
  63. package/dist/editor/fields/ImageField.d.ts +4 -0
  64. package/dist/editor/fields/ImageField.js +17 -8
  65. package/dist/editor/fields/InlineGroupField.js +4 -3
  66. package/dist/editor/fields/MarkdownField.d.ts +6 -0
  67. package/dist/editor/fields/MarkdownField.js +41 -11
  68. package/dist/editor/fields/MdxImageDialog.js +1 -1
  69. package/dist/editor/fields/NumberField.d.ts +2 -0
  70. package/dist/editor/fields/NumberField.js +4 -2
  71. package/dist/editor/fields/NumberListField.d.ts +2 -0
  72. package/dist/editor/fields/NumberListField.js +4 -2
  73. package/dist/editor/fields/ObjectField.d.ts +1 -0
  74. package/dist/editor/fields/ObjectField.js +5 -4
  75. package/dist/editor/fields/ReferenceField.d.ts +2 -0
  76. package/dist/editor/fields/ReferenceField.js +9 -3
  77. package/dist/editor/fields/SelectField.d.ts +2 -0
  78. package/dist/editor/fields/SelectField.js +8 -2
  79. package/dist/editor/fields/StringListField.d.ts +2 -0
  80. package/dist/editor/fields/StringListField.js +5 -2
  81. package/dist/editor/fields/TextField.d.ts +2 -0
  82. package/dist/editor/fields/TextField.js +5 -2
  83. package/dist/editor/fields/ToggleField.d.ts +2 -0
  84. package/dist/editor/fields/ToggleField.js +6 -3
  85. package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
  86. package/dist/editor/fields/list-item-keys.d.ts +8 -0
  87. package/dist/editor/fields/list-item-keys.js +50 -0
  88. package/dist/editor/group-manager/GroupCard.js +1 -1
  89. package/dist/editor/group-manager/GroupForm.js +1 -1
  90. package/dist/editor/group-manager/InternalGroupsTab.js +1 -1
  91. package/dist/editor/group-manager/MemberList.js +1 -1
  92. package/dist/editor/group-manager/hooks/useGroupState.d.ts +1 -0
  93. package/dist/editor/group-manager/hooks/useGroupState.js +1 -1
  94. package/dist/editor/group-manager/index.js +26 -2
  95. package/dist/editor/hooks/useBranchActions.js +2 -2
  96. package/dist/editor/hooks/useBranchManager.js +36 -22
  97. package/dist/editor/hooks/useDraftManager.d.ts +8 -0
  98. package/dist/editor/hooks/useDraftManager.js +27 -5
  99. package/dist/editor/hooks/useEditorLayout.d.ts +45 -3
  100. package/dist/editor/hooks/useEditorLayout.js +97 -7
  101. package/dist/editor/hooks/useUserMetadata.js +12 -1
  102. package/dist/editor/media/AssetCard.js +1 -1
  103. package/dist/editor/media/CropStep.js +6 -1
  104. package/dist/editor/media/MediaLibraryBody.js +3 -3
  105. package/dist/editor/permission-manager/PermissionEditor.js +3 -3
  106. package/dist/editor/permission-manager/index.js +1 -1
  107. package/dist/editor/preview-bridge.js +2 -2
  108. package/dist/editor/schema-editor/CollectionEditor.js +1 -1
  109. package/dist/editor/schema-editor/EntryTypeEditor.js +1 -1
  110. package/dist/editor/theme.d.ts +1 -1
  111. package/dist/editor/theme.js +11 -11
  112. package/dist/editor/undo-removal.d.ts +44 -0
  113. package/dist/editor/undo-removal.js +129 -0
  114. package/dist/editor/user-display.d.ts +4 -0
  115. package/dist/editor/user-display.js +5 -0
  116. package/dist/editor/utils/confirm-modal.d.ts +8 -0
  117. package/dist/editor/utils/confirm-modal.js +12 -0
  118. package/dist/entry-link-resolver.d.ts +11 -1
  119. package/dist/entry-link-resolver.js +65 -5
  120. package/dist/git-manager.d.ts +6 -6
  121. package/dist/git-manager.js +16 -15
  122. package/dist/github-service.d.ts +25 -1
  123. package/dist/github-service.js +65 -6
  124. package/dist/http/handler.js +2 -1
  125. package/dist/static/seo.d.ts +5 -5
  126. package/dist/static/seo.js +5 -5
  127. package/dist/system-users.d.ts +12 -0
  128. package/dist/system-users.js +12 -0
  129. package/dist/types.d.ts +9 -1
  130. package/dist/utils/error.js +4 -3
  131. package/dist/utils/escape-text.d.ts +7 -0
  132. package/dist/utils/escape-text.js +27 -0
  133. package/dist/version.d.ts +1 -1
  134. package/dist/version.js +1 -1
  135. package/dist/worker/branch-policy.d.ts +124 -0
  136. package/dist/worker/branch-policy.js +243 -0
  137. package/dist/worker/bundle-header.d.ts +117 -0
  138. package/dist/worker/bundle-header.js +253 -0
  139. package/dist/worker/cms-worker.d.ts +68 -17
  140. package/dist/worker/cms-worker.js +116 -33
  141. package/dist/worker/gateway-egress.d.ts +43 -0
  142. package/dist/worker/gateway-egress.js +209 -0
  143. package/dist/worker/gateway-instance.d.ts +42 -0
  144. package/dist/worker/gateway-instance.js +129 -0
  145. package/dist/worker/git-process.d.ts +81 -0
  146. package/dist/worker/git-process.js +234 -0
  147. package/dist/worker/git-sync.d.ts +8 -7
  148. package/dist/worker/git-sync.js +184 -74
  149. package/dist/worker/github-gateway-http.d.ts +62 -0
  150. package/dist/worker/github-gateway-http.js +977 -0
  151. package/dist/worker/github-gateway-wire.d.ts +589 -0
  152. package/dist/worker/github-gateway-wire.js +484 -0
  153. package/dist/worker/github-gateway.d.ts +152 -58
  154. package/dist/worker/github-gateway.js +485 -129
  155. package/dist/worker/github-mirror-upkeep.d.ts +75 -0
  156. package/dist/worker/github-mirror-upkeep.js +266 -0
  157. package/dist/worker/github-mirror.d.ts +271 -71
  158. package/dist/worker/github-mirror.js +755 -238
  159. package/dist/worker/github-transfer.d.ts +56 -0
  160. package/dist/worker/github-transfer.js +444 -0
  161. package/dist/worker/self-checks.d.ts +107 -0
  162. package/dist/worker/self-checks.js +358 -0
  163. package/dist/worker/shared-repo-git.d.ts +20 -7
  164. package/dist/worker/shared-repo-git.js +51 -22
  165. package/dist/worker/stale-mirror.d.ts +82 -0
  166. package/dist/worker/stale-mirror.js +130 -0
  167. package/dist/worker/task-runner.d.ts +2 -0
  168. package/dist/worker/task-runner.js +49 -20
  169. package/dist/worker/worker-context.d.ts +6 -0
  170. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -23,7 +23,7 @@ function redactCredentials(message) {
23
23
  let result = message;
24
24
  result = result.replace(/(:\/\/)[^/\s@]+@/g, "$1***@");
25
25
  result = result.replace(/(\bauthorization\s*:\s*(?:basic|bearer|token)\s+)[A-Za-z0-9+/=._~-]+/gi, "$1***");
26
- result = result.replace(/eC1hY2Nlc3MtdG9rZW46[A-Za-z0-9+/]*={0,2}/g, "***");
26
+ result = result.replace(/eC1hY2Nlc3MtdG9rZW46(?:[A-Za-z0-9+/]|%2[BbFf])*(?:=|%3[Dd]){0,2}/g, "***");
27
27
  result = result.replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{8,}/g, "***");
28
28
  result = result.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/g, "$1***");
29
29
  result = result.replace(/-----BEGIN [A-Z]{0,9} ?PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z]{0,9} ?PRIVATE KEY-----|$)/g, "<private-key>");
@@ -2523,8 +2523,8 @@ async function githubRequest(apiPath, init2 = {}) {
2523
2523
  } catch {
2524
2524
  body = null;
2525
2525
  }
2526
- const asRecord2 = body;
2527
- const message = response.ok ? "" : redactCredentials(typeof asRecord2?.message === "string" ? asRecord2.message : text3.slice(0, 200));
2526
+ const asRecord3 = body;
2527
+ const message = response.ok ? "" : redactCredentials(typeof asRecord3?.message === "string" ? asRecord3.message : text3.slice(0, 200));
2528
2528
  return { ok: response.ok, status: response.status, body, message };
2529
2529
  }
2530
2530
  function spawnKeyDestination(command, args) {
@@ -3236,7 +3236,7 @@ function redactCredentials2(message) {
3236
3236
  let result = message;
3237
3237
  result = result.replace(/(:\/\/)[^/\s@]+@/g, "$1***@");
3238
3238
  result = result.replace(/(\bauthorization\s*:\s*(?:basic|bearer|token)\s+)[A-Za-z0-9+/=._~-]+/gi, "$1***");
3239
- result = result.replace(/eC1hY2Nlc3MtdG9rZW46[A-Za-z0-9+/]*={0,2}/g, "***");
3239
+ result = result.replace(/eC1hY2Nlc3MtdG9rZW46(?:[A-Za-z0-9+/]|%2[BbFf])*(?:=|%3[Dd]){0,2}/g, "***");
3240
3240
  result = result.replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{8,}/g, "***");
3241
3241
  result = result.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/g, "$1***");
3242
3242
  result = result.replace(/-----BEGIN [A-Z]{0,9} ?PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z]{0,9} ?PRIVATE KEY-----|$)/g, "<private-key>");
@@ -4336,6 +4336,23 @@ function findBodyFieldName(fields) {
4336
4336
  }
4337
4337
  return "body";
4338
4338
  }
4339
+ function extractSeoFields(entryData, opts = {}) {
4340
+ const names = { ...DEFAULT_SEO_FIELD_NAMES, ...opts.fields };
4341
+ const entry = asRecord(entryData);
4342
+ const source = opts.group ? asRecord(entry?.[opts.group]) : entry;
4343
+ return {
4344
+ title: asText(source?.[names.title]) ?? asText(opts.fallbackTitle),
4345
+ description: asText(source?.[names.description]) ?? asText(opts.fallbackDescription),
4346
+ ogImage: asImageUrl(source?.[names.ogImage]),
4347
+ ogType: asOgType(source?.[names.ogType]),
4348
+ canonical: asText(source?.[names.canonical]),
4349
+ noindex: asFlag(source?.[names.noindex]),
4350
+ twitterCard: asTwitterCard(source?.[names.twitterCard])
4351
+ };
4352
+ }
4353
+ function isNoindexEntry(entryData, opts = {}) {
4354
+ return extractSeoFields(entryData, opts).noindex === true;
4355
+ }
4339
4356
  function buildResolvedReference(data, meta, body) {
4340
4357
  const resolved = { ...data };
4341
4358
  if (body && body.value)
@@ -5816,17 +5833,21 @@ function createReferenceRendering(idIndex, flatSchema, entryLinkUrl, markdownCop
5816
5833
  function resolveEntryUrl(location, contentRoot) {
5817
5834
  return computeEntryUrl(location.collection ?? "", location.slug ?? "", contentRoot);
5818
5835
  }
5819
- function resolveEntryLinksInText(text3, idIndex, contentRoot, customResolver) {
5836
+ function resolveEntryLinksInText(text3, idIndex, contentRoot, customResolver, options = {}) {
5837
+ const { isLinkable } = options;
5820
5838
  const parts = splitByCodeRegions(text3);
5821
5839
  return parts.map((part) => {
5822
5840
  if (part.isCode)
5823
5841
  return part.text;
5824
- return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
5825
- const location = idIndex.findById(id);
5826
- if (!location || location.type !== "entry" || !location.collection || !location.slug) {
5842
+ const partText = isLinkable ? unwrapInlineLinks(part.text, (id) => !isLinkable(id) || !findEntry(idIndex, id)) : part.text;
5843
+ return partText.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
5844
+ const location = findEntry(idIndex, id);
5845
+ if (!location) {
5827
5846
  log6.warn("resolve", `Entry link target not found: entry:${id}`);
5828
- return anchor ?? "#";
5847
+ return isLinkable ? "#" : anchor ?? "#";
5829
5848
  }
5849
+ if (isLinkable && !isLinkable(id))
5850
+ return "#";
5830
5851
  let url;
5831
5852
  if (customResolver) {
5832
5853
  url = customResolver({
@@ -5841,6 +5862,45 @@ function resolveEntryLinksInText(text3, idIndex, contentRoot, customResolver) {
5841
5862
  });
5842
5863
  }).join("");
5843
5864
  }
5865
+ function findEntry(idIndex, id) {
5866
+ const location = idIndex.findById(id);
5867
+ if (!location || location.type !== "entry" || !location.collection || !location.slug) {
5868
+ return void 0;
5869
+ }
5870
+ return location;
5871
+ }
5872
+ function unwrapInlineLinks(text3, shouldUnwrap) {
5873
+ let out = "";
5874
+ let cursor = 0;
5875
+ let scanFloor = 0;
5876
+ for (const match of text3.matchAll(INLINE_LINK_TARGET)) {
5877
+ const close = match.index;
5878
+ if (close < cursor || !shouldUnwrap(match[1]))
5879
+ continue;
5880
+ const open = findOpeningBracket(text3, close, Math.max(cursor, scanFloor));
5881
+ if (open === -1) {
5882
+ scanFloor = close;
5883
+ continue;
5884
+ }
5885
+ const isImage = open > cursor && text3[open - 1] === "!" && text3[open - 2] !== "\\";
5886
+ const start = isImage ? open - 1 : open;
5887
+ out += text3.slice(cursor, start) + text3.slice(open + 1, close);
5888
+ cursor = close + match[0].length;
5889
+ }
5890
+ return out + text3.slice(cursor);
5891
+ }
5892
+ function findOpeningBracket(text3, close, floor) {
5893
+ let depth = 1;
5894
+ for (let i = close - 1; i >= floor; i--) {
5895
+ if (i > floor && text3[i - 1] === "\\")
5896
+ continue;
5897
+ if (text3[i] === "]")
5898
+ depth++;
5899
+ else if (text3[i] === "[" && --depth === 0)
5900
+ return i;
5901
+ }
5902
+ return -1;
5903
+ }
5844
5904
  function splitByCodeRegions(text3) {
5845
5905
  const parts = [];
5846
5906
  let current = "";
@@ -5913,6 +5973,7 @@ function splitByCodeRegions(text3) {
5913
5973
  }
5914
5974
  async function generateAIContent(options) {
5915
5975
  const { store, flatSchema, contentRoot, config, entryLinkUrl, generatedAt, buildId } = options;
5976
+ assertValidNoindexConfig(config);
5916
5977
  const files = /* @__PURE__ */ new Map();
5917
5978
  const idIndex = await store.idIndex();
5918
5979
  const read = {
@@ -5949,6 +6010,16 @@ async function generateAIContent(options) {
5949
6010
  let round = allPending;
5950
6011
  for (; ; ) {
5951
6012
  for (const pending of round) {
6013
+ if (pending.body !== void 0) {
6014
+ const linked = /* @__PURE__ */ new Set();
6015
+ pending.entry.body = resolveEntryLinksInText(pending.body, idIndex, contentRoot, entryLinkUrl, {
6016
+ isLinkable: (id) => {
6017
+ linked.add(id);
6018
+ return exported.has(id);
6019
+ }
6020
+ });
6021
+ pending.linkedIds = [...linked];
6022
+ }
5952
6023
  maskUnexportedTargets(pending.entry, exported);
5953
6024
  pending.entry.appendedSections = void 0;
5954
6025
  await runEntryTransform(pending.entry, pending.absolutePath, pending.contentId, config);
@@ -5956,11 +6027,19 @@ async function generateAIContent(options) {
5956
6027
  const failed = renderAll(round, config, references);
5957
6028
  if (failed.length === 0)
5958
6029
  break;
6030
+ const failedIds = /* @__PURE__ */ new Set();
5959
6031
  for (const pending of failed) {
5960
- if (pending.contentId !== null)
5961
- exported.delete(pending.contentId);
6032
+ if (pending.contentId === null)
6033
+ continue;
6034
+ exported.delete(pending.contentId);
6035
+ failedIds.add(pending.contentId);
5962
6036
  }
5963
- round = allPending.filter((pending) => isRendered(pending) && maskUnexportedTargets(pending.entry, exported));
6037
+ round = allPending.filter((pending) => {
6038
+ if (!isRendered(pending))
6039
+ return false;
6040
+ const masked = maskUnexportedTargets(pending.entry, exported);
6041
+ return masked || pending.linkedIds.some((id) => failedIds.has(id));
6042
+ });
5964
6043
  }
5965
6044
  const manifestCollections = collectionNodes.map((node) => emitCollection(node, files));
5966
6045
  const manifestRootEntries = emitEntries(rootEntries, files);
@@ -6029,6 +6108,7 @@ async function collectCollection(read, collection, flatSchema) {
6029
6108
  }
6030
6109
  async function collectEntries(read, collection, cleanPath) {
6031
6110
  const { store, contentRoot, config, idIndex, entryLinkUrl, resolveTarget: resolveTarget2 } = read;
6111
+ const skipNoindex = config?.exclude?.noindex !== false;
6032
6112
  const pending = [];
6033
6113
  const listed = await store.getCollectionEntryPaths(collection.logicalPath);
6034
6114
  const directEntries = listed.filter((e) => e.collection === collection.logicalPath);
@@ -6045,15 +6125,19 @@ async function collectEntries(read, collection, cleanPath) {
6045
6125
  const doc = await store.read(listEntry.collection, listEntry.slug, {
6046
6126
  resolveReferences: false
6047
6127
  });
6128
+ if (skipNoindex && isNoindexEntry(doc.data, config?.seo))
6129
+ continue;
6048
6130
  doc.data = await resolveReferenceFields(doc.data, entryTypeConfig.schema, resolveTarget2);
6049
6131
  const aiEntry = docToAIEntry(doc, listEntry.slug, entryTypeName, entryTypeConfig, cleanPath);
6050
- if (aiEntry.body) {
6051
- aiEntry.body = resolveEntryLinksInText(aiEntry.body, idIndex, contentRoot, entryLinkUrl);
6052
- }
6132
+ const body = aiEntry.body;
6133
+ if (body)
6134
+ aiEntry.body = resolveEntryLinksInText(body, idIndex, contentRoot, entryLinkUrl);
6053
6135
  if (config?.exclude?.where?.(aiEntry))
6054
6136
  continue;
6055
6137
  pending.push({
6056
6138
  entry: aiEntry,
6139
+ body,
6140
+ linkedIds: [],
6057
6141
  contentId: extractIdFromFilename(path13.basename(listEntry.relativePath)),
6058
6142
  absolutePath: doc.absolutePath,
6059
6143
  collectionPath: collection.logicalPath,
@@ -6094,6 +6178,31 @@ function emitCollection(node, files) {
6094
6178
  subcollections: manifestSubcollections.length > 0 ? manifestSubcollections : void 0
6095
6179
  };
6096
6180
  }
6181
+ function assertValidNoindexConfig(config) {
6182
+ const noindex = config?.exclude?.noindex;
6183
+ if (noindex !== void 0 && typeof noindex !== "boolean") {
6184
+ throw new Error(`AI content config: exclude.noindex must be a boolean, got ${typeof noindex}`);
6185
+ }
6186
+ const seo = config?.seo;
6187
+ if (seo === void 0)
6188
+ return;
6189
+ if (typeof seo !== "object" || seo === null || Array.isArray(seo)) {
6190
+ throw new Error("AI content config: seo must be an object of { group?, fields? }");
6191
+ }
6192
+ const { group, fields } = seo;
6193
+ if (group !== void 0 && typeof group !== "string") {
6194
+ throw new Error("AI content config: seo.group must be a string");
6195
+ }
6196
+ if (fields === void 0)
6197
+ return;
6198
+ if (typeof fields !== "object" || fields === null || Array.isArray(fields) || Object.values(fields).some((name) => typeof name !== "string")) {
6199
+ throw new Error("AI content config: seo.fields must map SEO fields to field-name strings");
6200
+ }
6201
+ const unknown = Object.keys(fields).filter((key) => !(key in DEFAULT_SEO_FIELD_NAMES));
6202
+ if (unknown.length > 0) {
6203
+ throw new Error(`AI content config: unknown seo.fields key(s): ${unknown.join(", ")}`);
6204
+ }
6205
+ }
6097
6206
  function stripContentRoot(logicalPath, contentRoot) {
6098
6207
  if (logicalPath.startsWith(contentRoot + "/")) {
6099
6208
  return logicalPath.slice(contentRoot.length + 1);
@@ -6418,7 +6527,7 @@ async function ensureRemoteGitConfig(gitDir, git = simpleGit22().env(gitChildEnv
6418
6527
  }
6419
6528
  }
6420
6529
  async function countObjects(git, gitDir) {
6421
- const output = await git.raw(["--git-dir", gitDir, "count-objects", "-v"]);
6530
+ const output = await git.raw(["--git-dir", gitDir, "count-objects", "-v", "--end-of-options"]);
6422
6531
  const fields = new Map(output.split("\n").map((line) => {
6423
6532
  const [name, value = ""] = line.split(": ");
6424
6533
  return [name, value.trim()];
@@ -6438,8 +6547,8 @@ async function repackBareRemoteIfNeeded(gitDir, git = simpleGit22().env(gitChild
6438
6547
  return { repacked: false, before };
6439
6548
  }
6440
6549
  const startedAt = Date.now();
6441
- await git.raw(["--git-dir", gitDir, "repack", "-a", "-d", "--cruft", "-q"]);
6442
- await git.raw(["--git-dir", gitDir, "pack-refs", "--all"]);
6550
+ await git.raw(["--git-dir", gitDir, "repack", "-a", "-d", "--cruft", "-q", "--end-of-options"]);
6551
+ await git.raw(["--git-dir", gitDir, "pack-refs", "--all", "--end-of-options"]);
6443
6552
  const after = await countObjects(git, gitDir);
6444
6553
  return { repacked: true, before, after, ms: Date.now() - startedAt };
6445
6554
  }
@@ -7358,7 +7467,7 @@ async function generateAIContentCLI(options) {
7358
7467
  console.log(` Output: ${result.outputDir}
7359
7468
  `);
7360
7469
  }
7361
- var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, LOG_LEVELS2, DebugLogger2, testLogger2, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, DEFAULT_SEO_FIELD_NAMES, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
7470
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, LOG_LEVELS2, DebugLogger2, testLogger2, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, DEFAULT_SEO_FIELD_NAMES, asRecord, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord2, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
7362
7471
  var init_generate_ai_content = __esm({
7363
7472
  "dist/cli/generate-ai-content.js"() {
7364
7473
  "use strict";
@@ -8797,6 +8906,28 @@ var init_generate_ai_content = __esm({
8797
8906
  noindex: "noindex",
8798
8907
  twitterCard: "twitterCard"
8799
8908
  };
8909
+ asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
8910
+ asText = (value) => {
8911
+ if (typeof value !== "string")
8912
+ return void 0;
8913
+ const trimmed = value.trim();
8914
+ return trimmed === "" ? void 0 : trimmed;
8915
+ };
8916
+ asFlag = (value) => typeof value === "boolean" ? value : void 0;
8917
+ asImageUrl = (value) => {
8918
+ const text3 = asText(value);
8919
+ if (text3)
8920
+ return text3;
8921
+ return asText(asRecord(value)?.src);
8922
+ };
8923
+ asOgType = (value) => {
8924
+ const text3 = asText(value);
8925
+ return text3 === "website" || text3 === "article" || text3 === "profile" ? text3 : void 0;
8926
+ };
8927
+ asTwitterCard = (value) => {
8928
+ const text3 = asText(value);
8929
+ return text3 === "summary" || text3 === "summary_large_image" ? text3 : void 0;
8930
+ };
8800
8931
  RESTRICTED_REFERENCE_MARKER = "unavailable";
8801
8932
  SEO_GROUP_FIELDS = [
8802
8933
  {
@@ -9497,7 +9628,7 @@ var init_generate_ai_content = __esm({
9497
9628
  return ".json";
9498
9629
  };
9499
9630
  isDataOnlyFormat = (format) => format === "json" || format === "yaml";
9500
- asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
9631
+ asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
9501
9632
  init_normalize();
9502
9633
  init_operating_mode();
9503
9634
  RESERVED_SETTINGS_BRANCH_PREFIX = "canopycms-settings-";
@@ -9989,7 +10120,7 @@ var init_generate_ai_content = __esm({
9989
10120
  fields = entryTypeConfig?.schema || [];
9990
10121
  }
9991
10122
  if (format === "json") {
9992
- const data = asRecord(JSON.parse(raw));
10123
+ const data = asRecord2(JSON.parse(raw));
9993
10124
  doc = {
9994
10125
  collection: schemaItem.logicalPath,
9995
10126
  collectionName: schemaItem.name,
@@ -9999,7 +10130,7 @@ var init_generate_ai_content = __esm({
9999
10130
  absolutePath
10000
10131
  };
10001
10132
  } else if (format === "yaml") {
10002
- const data = asRecord(yamlParse(raw));
10133
+ const data = asRecord2(yamlParse(raw));
10003
10134
  doc = {
10004
10135
  collection: schemaItem.logicalPath,
10005
10136
  collectionName: schemaItem.name,
@@ -10712,6 +10843,7 @@ var init_generate_ai_content = __esm({
10712
10843
  log6 = createDebugLogger2({ prefix: "EntryLinks" });
10713
10844
  BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
10714
10845
  ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
10846
+ INLINE_LINK_TARGET = new RegExp(`\\]\\(entry:(${BASE58_CHAR}{12})(?:#[^\\s)>"'\\]]*)?(?:[ \\t]+"[^"\\n]*")?\\)`, "g");
10715
10847
  init_error3();
10716
10848
  BranchMetadataCorruptError = class extends Error {
10717
10849
  constructor(branchRoot, cause) {
@@ -11476,10 +11608,10 @@ var init_generate_ai_content = __esm({
11476
11608
  * Delete `refs/heads/<branch>` from a bare local mirror, if present. A
11477
11609
  * no-op (not an error) when the ref doesn't exist.
11478
11610
  *
11479
- * The explicit path for removing a deleted branch's local head, which the
11480
- * sync loop deliberately is not (reconcileTrackedBranches never deletes a
11481
- * head — see GITHUB_TRACKING_REF_PREFIX). Called by api/branch.ts's
11482
- * deleteBranchHandler: a head left in `remote.git` forever makes the
11611
+ * The path that removes a deleted branch's local head at once; the sync
11612
+ * loop's reconcileTrackedBranches removes one only after GitHub no longer
11613
+ * has the branch. Called by api/branch.ts's deleteBranchHandler: a head
11614
+ * left in `remote.git` makes the
11483
11615
  * create -> publish -> squash-merge -> delete -> reuse-the-name cycle reject
11484
11616
  * the reused branch's first publish non-fast-forward against the stale head
11485
11617
  * (`GitManager.push()` pushes the branch to the same name, and a squash-merged old tip is
@@ -12664,10 +12796,10 @@ var init_generate_ai_content = __esm({
12664
12796
  try {
12665
12797
  const raw = await fs20.readFile(filePath, "utf8");
12666
12798
  if (format === "json") {
12667
- return asRecord(JSON.parse(raw));
12799
+ return asRecord2(JSON.parse(raw));
12668
12800
  }
12669
12801
  if (format === "yaml") {
12670
- return asRecord(yamlParse2(raw));
12802
+ return asRecord2(yamlParse2(raw));
12671
12803
  }
12672
12804
  const parsed = matter3(raw);
12673
12805
  const data = { ...parsed.data ?? {} };