canopycms 0.3.2 → 0.4.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/dist/cli/cli.js CHANGED
@@ -4552,16 +4552,26 @@ function flattenGroupFields(fields) {
4552
4552
  return result;
4553
4553
  }
4554
4554
  function findBodyFieldName(fields) {
4555
+ return findExplicitBodyFieldName(fields) ?? "body";
4556
+ }
4557
+ function findExplicitBodyFieldName(fields) {
4555
4558
  for (const field of flattenGroupFields(fields)) {
4556
4559
  if ("isBody" in field && field.isBody)
4557
4560
  return field.name;
4558
4561
  }
4559
- return "body";
4562
+ return void 0;
4563
+ }
4564
+ function linkOnlyReferenceData(data, format, fields) {
4565
+ const bodyFieldName = isDataOnlyFormat(format) && fields ? findExplicitBodyFieldName(fields) : void 0;
4566
+ if (bodyFieldName === void 0 || !(bodyFieldName in data))
4567
+ return data;
4568
+ const { [bodyFieldName]: _body, ...rest } = data;
4569
+ return rest;
4560
4570
  }
4561
4571
  function extractSeoFields(entryData, opts = {}) {
4562
4572
  const names = { ...DEFAULT_SEO_FIELD_NAMES, ...opts.fields };
4563
- const entry = asRecord(entryData);
4564
- const source = opts.group ? asRecord(entry?.[opts.group]) : entry;
4573
+ const entry = asRecord2(entryData);
4574
+ const source = opts.group ? asRecord2(entry?.[opts.group]) : entry;
4565
4575
  return {
4566
4576
  title: asText(source?.[names.title]) ?? asText(opts.fallbackTitle),
4567
4577
  description: asText(source?.[names.description]) ?? asText(opts.fallbackDescription),
@@ -6285,14 +6295,18 @@ async function generateAIContent(options) {
6285
6295
  const pending = pendingById.get(id);
6286
6296
  if (!pending || !exported.has(id))
6287
6297
  return void 0;
6288
- const { slug } = pending.entry;
6289
- const target = buildResolvedReference(structuredClone(pending.storedData), {
6298
+ const { format } = pending;
6299
+ const { slug, fields } = pending.entry;
6300
+ const storedData = structuredClone(pending.storedData);
6301
+ const target = buildResolvedReference(linkOnlyReferenceData(storedData, format, fields), {
6290
6302
  id,
6291
6303
  slug,
6292
6304
  collection: pending.collectionPath,
6293
6305
  urlPath: computeEntryUrl(pending.collectionPath, slug, contentRoot)
6294
6306
  });
6295
- return { target, body: pending.body };
6307
+ const dataBodyField = isDataOnlyFormat(format) ? findExplicitBodyFieldName(fields) : void 0;
6308
+ const dataBody = dataBodyField === void 0 ? void 0 : storedData[dataBodyField];
6309
+ return { target, body: pending.body ?? (typeof dataBody === "string" ? dataBody : void 0) };
6296
6310
  };
6297
6311
  const linkFor = config?.referenceLink ? await decideReferenceLinks(config.referenceLink, allPending, subjectOf, entryLinkUrl) : void 0;
6298
6312
  const bodyLinkUrl = linkFor ? (location) => {
@@ -6434,6 +6448,7 @@ async function collectEntries(read, collection, cleanPath) {
6434
6448
  entry: aiEntry,
6435
6449
  body,
6436
6450
  storedData,
6451
+ format: doc.format,
6437
6452
  linkedIds: [],
6438
6453
  contentId: extractIdFromFilename(path13.basename(listEntry.relativePath)),
6439
6454
  absolutePath: doc.absolutePath,
@@ -7770,7 +7785,7 @@ async function generateAIContentCLI(options) {
7770
7785
  console.log(` Output: ${result.outputDir}
7771
7786
  `);
7772
7787
  }
7773
- var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, 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, envFlag2, 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, heldRoots, 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, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, 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;
7788
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, 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, envFlag2, 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, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, 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, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, 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;
7774
7789
  var init_generate_ai_content = __esm({
7775
7790
  "dist/cli/generate-ai-content.js"() {
7776
7791
  "use strict";
@@ -9216,6 +9231,17 @@ var init_generate_ai_content = __esm({
9216
9231
  });
9217
9232
  CONTENT_WRITE_LOCK_STALE_MS = 3e4;
9218
9233
  heldRoots = new AsyncLocalStorage();
9234
+ getFormatExtension = (format) => {
9235
+ if (format === "md")
9236
+ return ".md";
9237
+ if (format === "mdx")
9238
+ return ".mdx";
9239
+ if (format === "yaml")
9240
+ return ".yaml";
9241
+ return ".json";
9242
+ };
9243
+ isDataOnlyFormat = (format) => format === "json" || format === "yaml";
9244
+ asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
9219
9245
  init_item_keys();
9220
9246
  DEFAULT_SEO_FIELD_NAMES = {
9221
9247
  title: "metaTitle",
@@ -9226,7 +9252,7 @@ var init_generate_ai_content = __esm({
9226
9252
  noindex: "noindex",
9227
9253
  twitterCard: "twitterCard"
9228
9254
  };
9229
- asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
9255
+ asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
9230
9256
  asText = (value) => {
9231
9257
  if (typeof value !== "string")
9232
9258
  return void 0;
@@ -9238,7 +9264,7 @@ var init_generate_ai_content = __esm({
9238
9264
  const text3 = asText(value);
9239
9265
  if (text3)
9240
9266
  return text3;
9241
- return asText(asRecord(value)?.src);
9267
+ return asText(asRecord2(value)?.src);
9242
9268
  };
9243
9269
  asOgType = (value) => {
9244
9270
  const text3 = asText(value);
@@ -9939,17 +9965,6 @@ var init_generate_ai_content = __esm({
9939
9965
  RESOURCE = "content-index";
9940
9966
  init_error3();
9941
9967
  init_error3();
9942
- getFormatExtension = (format) => {
9943
- if (format === "md")
9944
- return ".md";
9945
- if (format === "mdx")
9946
- return ".mdx";
9947
- if (format === "yaml")
9948
- return ".yaml";
9949
- return ".json";
9950
- };
9951
- isDataOnlyFormat = (format) => format === "json" || format === "yaml";
9952
- asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
9953
9968
  init_normalize();
9954
9969
  init_operating_mode();
9955
9970
  RESERVED_SETTINGS_BRANCH_PREFIX = "canopycms-settings-";
@@ -10448,7 +10463,7 @@ var init_generate_ai_content = __esm({
10448
10463
  fields = entryTypeConfig?.schema || [];
10449
10464
  }
10450
10465
  if (format === "json") {
10451
- const data = asRecord2(JSON.parse(raw));
10466
+ const data = asRecord(JSON.parse(raw));
10452
10467
  doc = {
10453
10468
  collection: schemaItem.logicalPath,
10454
10469
  collectionName: schemaItem.name,
@@ -10458,7 +10473,7 @@ var init_generate_ai_content = __esm({
10458
10473
  absolutePath
10459
10474
  };
10460
10475
  } else if (format === "yaml") {
10461
- const data = asRecord2(yamlParse(raw));
10476
+ const data = asRecord(yamlParse(raw));
10462
10477
  doc = {
10463
10478
  collection: schemaItem.logicalPath,
10464
10479
  collectionName: schemaItem.name,
@@ -11061,10 +11076,11 @@ var init_generate_ai_content = __esm({
11061
11076
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
11062
11077
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
11063
11078
  * 265KB JSON target ~0.8x -- a wash at the pathological end, never a blow-up, and on the
11064
- * EFS/NFS this targets the syscall the memo removes dominates both. An md/mdx target
11065
- * resolves to its FRONTMATTER unless the field sets `includeBody`, which is why
11066
- * `includeBody` is opt-in per field: an embedded body sits inside the memoized object and is
11067
- * cloned once per referencing entry.
11079
+ * EFS/NFS this targets the syscall the memo removes dominates both. A target resolves
11080
+ * without its body (an md/mdx target to its frontmatter, a json/yaml one without its `isBody`
11081
+ * field) unless the field sets `includeBody`, which is why `includeBody` is opt-in per
11082
+ * field: an embedded body sits inside the memoized object and is cloned once per referencing
11083
+ * entry.
11068
11084
  */
11069
11085
  resolveSingleReference(id, idIndex, includeBody, cache, access) {
11070
11086
  if (!cache)
@@ -11159,7 +11175,8 @@ var init_generate_ai_content = __esm({
11159
11175
  }));
11160
11176
  }
11161
11177
  const bodyForEmbed = includeBody && "body" in doc ? { fieldName: doc.bodyFieldName, value: doc.body } : void 0;
11162
- return buildResolvedReference(doc.data, meta, bodyForEmbed);
11178
+ const data = includeBody ? doc.data : linkOnlyReferenceData(doc.data, doc.format, this.entryTypeOf(location.collection, location.relativePath)?.schema);
11179
+ return buildResolvedReference(data, meta, bodyForEmbed);
11163
11180
  } catch (error) {
11164
11181
  if (isNodeError3(error) && error.code === "ENOENT")
11165
11182
  return STALE_LOOKUP;
@@ -13182,10 +13199,10 @@ var init_generate_ai_content = __esm({
13182
13199
  try {
13183
13200
  const raw = await fs20.readFile(filePath, "utf8");
13184
13201
  if (format === "json") {
13185
- return asRecord2(JSON.parse(raw));
13202
+ return asRecord(JSON.parse(raw));
13186
13203
  }
13187
13204
  if (format === "yaml") {
13188
- return asRecord2(yamlParse2(raw));
13205
+ return asRecord(yamlParse2(raw));
13189
13206
  }
13190
13207
  const parsed = matter3(raw);
13191
13208
  const data = { ...parsed.data ?? {} };
@@ -2912,13 +2912,36 @@ function flattenGroupFields(fields) {
2912
2912
  return result;
2913
2913
  }
2914
2914
 
2915
+ // dist/utils/format.js
2916
+ var getFormatExtension = (format) => {
2917
+ if (format === "md")
2918
+ return ".md";
2919
+ if (format === "mdx")
2920
+ return ".mdx";
2921
+ if (format === "yaml")
2922
+ return ".yaml";
2923
+ return ".json";
2924
+ };
2925
+ var isDataOnlyFormat = (format) => format === "json" || format === "yaml";
2926
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
2927
+
2915
2928
  // dist/utils/body-field.js
2916
2929
  function findBodyFieldName(fields) {
2930
+ return findExplicitBodyFieldName(fields) ?? "body";
2931
+ }
2932
+ function findExplicitBodyFieldName(fields) {
2917
2933
  for (const field of flattenGroupFields(fields)) {
2918
2934
  if ("isBody" in field && field.isBody)
2919
2935
  return field.name;
2920
2936
  }
2921
- return "body";
2937
+ return void 0;
2938
+ }
2939
+ function linkOnlyReferenceData(data, format, fields) {
2940
+ const bodyFieldName = isDataOnlyFormat(format) && fields ? findExplicitBodyFieldName(fields) : void 0;
2941
+ if (bodyFieldName === void 0 || !(bodyFieldName in data))
2942
+ return data;
2943
+ const { [bodyFieldName]: _body, ...rest } = data;
2944
+ return rest;
2922
2945
  }
2923
2946
 
2924
2947
  // dist/content-store.js
@@ -2934,7 +2957,7 @@ var DEFAULT_SEO_FIELD_NAMES = {
2934
2957
  noindex: "noindex",
2935
2958
  twitterCard: "twitterCard"
2936
2959
  };
2937
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2960
+ var asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2938
2961
  var asText = (value) => {
2939
2962
  if (typeof value !== "string")
2940
2963
  return void 0;
@@ -2946,7 +2969,7 @@ var asImageUrl = (value) => {
2946
2969
  const text = asText(value);
2947
2970
  if (text)
2948
2971
  return text;
2949
- return asText(asRecord(value)?.src);
2972
+ return asText(asRecord2(value)?.src);
2950
2973
  };
2951
2974
  var asOgType = (value) => {
2952
2975
  const text = asText(value);
@@ -2958,8 +2981,8 @@ var asTwitterCard = (value) => {
2958
2981
  };
2959
2982
  function extractSeoFields(entryData, opts = {}) {
2960
2983
  const names = { ...DEFAULT_SEO_FIELD_NAMES, ...opts.fields };
2961
- const entry = asRecord(entryData);
2962
- const source = opts.group ? asRecord(entry?.[opts.group]) : entry;
2984
+ const entry = asRecord2(entryData);
2985
+ const source = opts.group ? asRecord2(entry?.[opts.group]) : entry;
2963
2986
  return {
2964
2987
  title: asText(source?.[names.title]) ?? asText(opts.fallbackTitle),
2965
2988
  description: asText(source?.[names.description]) ?? asText(opts.fallbackDescription),
@@ -4459,19 +4482,6 @@ async function readFileWithMtimeIfExists(filePath) {
4459
4482
  }
4460
4483
  }
4461
4484
 
4462
- // dist/utils/format.js
4463
- var getFormatExtension = (format) => {
4464
- if (format === "md")
4465
- return ".md";
4466
- if (format === "mdx")
4467
- return ".mdx";
4468
- if (format === "yaml")
4469
- return ".yaml";
4470
- return ".json";
4471
- };
4472
- var isDataOnlyFormat = (format) => format === "json" || format === "yaml";
4473
- var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
4474
-
4475
4485
  // dist/paths/index.js
4476
4486
  init_normalize();
4477
4487
 
@@ -5085,7 +5095,7 @@ var ContentStore = class {
5085
5095
  fields = entryTypeConfig?.schema || [];
5086
5096
  }
5087
5097
  if (format === "json") {
5088
- const data = asRecord2(JSON.parse(raw));
5098
+ const data = asRecord(JSON.parse(raw));
5089
5099
  doc = {
5090
5100
  collection: schemaItem.logicalPath,
5091
5101
  collectionName: schemaItem.name,
@@ -5095,7 +5105,7 @@ var ContentStore = class {
5095
5105
  absolutePath
5096
5106
  };
5097
5107
  } else if (format === "yaml") {
5098
- const data = asRecord2(yamlParse(raw));
5108
+ const data = asRecord(yamlParse(raw));
5099
5109
  doc = {
5100
5110
  collection: schemaItem.logicalPath,
5101
5111
  collectionName: schemaItem.name,
@@ -5698,10 +5708,11 @@ var ContentStore = class {
5698
5708
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
5699
5709
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
5700
5710
  * 265KB JSON target ~0.8x -- a wash at the pathological end, never a blow-up, and on the
5701
- * EFS/NFS this targets the syscall the memo removes dominates both. An md/mdx target
5702
- * resolves to its FRONTMATTER unless the field sets `includeBody`, which is why
5703
- * `includeBody` is opt-in per field: an embedded body sits inside the memoized object and is
5704
- * cloned once per referencing entry.
5711
+ * EFS/NFS this targets the syscall the memo removes dominates both. A target resolves
5712
+ * without its body (an md/mdx target to its frontmatter, a json/yaml one without its `isBody`
5713
+ * field) unless the field sets `includeBody`, which is why `includeBody` is opt-in per
5714
+ * field: an embedded body sits inside the memoized object and is cloned once per referencing
5715
+ * entry.
5705
5716
  */
5706
5717
  resolveSingleReference(id, idIndex, includeBody, cache, access) {
5707
5718
  if (!cache)
@@ -5796,7 +5807,8 @@ var ContentStore = class {
5796
5807
  }));
5797
5808
  }
5798
5809
  const bodyForEmbed = includeBody && "body" in doc ? { fieldName: doc.bodyFieldName, value: doc.body } : void 0;
5799
- return buildResolvedReference(doc.data, meta, bodyForEmbed);
5810
+ const data = includeBody ? doc.data : linkOnlyReferenceData(doc.data, doc.format, this.entryTypeOf(location.collection, location.relativePath)?.schema);
5811
+ return buildResolvedReference(data, meta, bodyForEmbed);
5800
5812
  } catch (error) {
5801
5813
  if (isNodeError(error) && error.code === "ENOENT")
5802
5814
  return STALE_LOOKUP;
@@ -6752,14 +6764,18 @@ async function generateAIContent(options) {
6752
6764
  const pending = pendingById.get(id);
6753
6765
  if (!pending || !exported.has(id))
6754
6766
  return void 0;
6755
- const { slug } = pending.entry;
6756
- const target = buildResolvedReference(structuredClone(pending.storedData), {
6767
+ const { format } = pending;
6768
+ const { slug, fields } = pending.entry;
6769
+ const storedData = structuredClone(pending.storedData);
6770
+ const target = buildResolvedReference(linkOnlyReferenceData(storedData, format, fields), {
6757
6771
  id,
6758
6772
  slug,
6759
6773
  collection: pending.collectionPath,
6760
6774
  urlPath: computeEntryUrl(pending.collectionPath, slug, contentRoot)
6761
6775
  });
6762
- return { target, body: pending.body };
6776
+ const dataBodyField = isDataOnlyFormat(format) ? findExplicitBodyFieldName(fields) : void 0;
6777
+ const dataBody = dataBodyField === void 0 ? void 0 : storedData[dataBodyField];
6778
+ return { target, body: pending.body ?? (typeof dataBody === "string" ? dataBody : void 0) };
6763
6779
  };
6764
6780
  const linkFor = config?.referenceLink ? await decideReferenceLinks(config.referenceLink, allPending, subjectOf, entryLinkUrl) : void 0;
6765
6781
  const bodyLinkUrl = linkFor ? (location) => {
@@ -6901,6 +6917,7 @@ async function collectEntries(read, collection, cleanPath) {
6901
6917
  entry: aiEntry,
6902
6918
  body,
6903
6919
  storedData,
6920
+ format: doc.format,
6904
6921
  linkedIds: [],
6905
6922
  contentId: extractIdFromFilename(path13.basename(listEntry.relativePath)),
6906
6923
  absolutePath: doc.absolutePath,
@@ -9794,10 +9811,10 @@ var readEntryData = async (filePath, format, bodyFieldName = "body") => {
9794
9811
  try {
9795
9812
  const raw = await fs20.readFile(filePath, "utf8");
9796
9813
  if (format === "json") {
9797
- return asRecord2(JSON.parse(raw));
9814
+ return asRecord(JSON.parse(raw));
9798
9815
  }
9799
9816
  if (format === "yaml") {
9800
- return asRecord2(yamlParse2(raw));
9817
+ return asRecord(yamlParse2(raw));
9801
9818
  }
9802
9819
  const parsed = matter3(raw);
9803
9820
  const data = { ...parsed.data ?? {} };
@@ -45,10 +45,11 @@ interface BaseFieldConfig {
45
45
  /** Mark this field as the display title for entries using this schema. At most one field per schema. */
46
46
  isTitle?: boolean;
47
47
  /**
48
- * Mark this field as the body content for md/mdx entries.
49
- * The field must be type 'markdown' or 'mdx'. At most one per schema.
50
- * When present, the file's markdown content is mapped to this field,
51
- * making TypeFromEntrySchema include it without manual `& { body: string }`.
48
+ * Mark this field as the entry's body. The field must be type 'markdown' or 'mdx'. At most one
49
+ * per schema. On an md/mdx entry, the file's markdown content is mapped to this field, making
50
+ * TypeFromEntrySchema include it without manual `& { body: string }`. On a json/yaml entry, it
51
+ * is the prose a link-only reference omits. Either way a reference carries it only with
52
+ * `includeBody: true`.
52
53
  */
53
54
  isBody?: boolean;
54
55
  }
@@ -117,7 +118,9 @@ export interface ReferenceFieldConfig extends BaseFieldConfig {
117
118
  options?: ReferenceOption[];
118
119
  /**
119
120
  * Include the referenced entry's body in the resolved value, under that entry type's own
120
- * body field name (`isBody: true`, else `body`). Defaults to `false`.
121
+ * body field name (`isBody: true`, else `body`). Defaults to `false`. The body is an md/mdx
122
+ * target's markdown content, or a json/yaml target's `isBody` field; a json/yaml field merely
123
+ * named `body` is ordinary data and always resolves.
121
124
  *
122
125
  * This is the embed-vs-link distinction, declared where it is actually known. A reference
123
126
  * that **embeds** its target — a shared call-to-action block rendered inline — needs the
@@ -131,9 +134,6 @@ export interface ReferenceFieldConfig extends BaseFieldConfig {
131
134
  * contains both kinds: a page with a shared CTA *and* a related-posts list cannot be served
132
135
  * by a single call-level setting, and a call-level flag would mean the same field resolved
133
136
  * to different shapes in different places.
134
- *
135
- * Only affects formats that HAVE a separate body — md and mdx. A json/yaml target's whole
136
- * document is already its data, so this is a no-op there.
137
137
  */
138
138
  includeBody?: boolean;
139
139
  }
@@ -557,10 +557,11 @@ export declare class ContentStore {
557
557
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
558
558
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
559
559
  * 265KB JSON target ~0.8x -- a wash at the pathological end, never a blow-up, and on the
560
- * EFS/NFS this targets the syscall the memo removes dominates both. An md/mdx target
561
- * resolves to its FRONTMATTER unless the field sets `includeBody`, which is why
562
- * `includeBody` is opt-in per field: an embedded body sits inside the memoized object and is
563
- * cloned once per referencing entry.
560
+ * EFS/NFS this targets the syscall the memo removes dominates both. A target resolves
561
+ * without its body (an md/mdx target to its frontmatter, a json/yaml one without its `isBody`
562
+ * field) unless the field sets `includeBody`, which is why `includeBody` is opt-in per
563
+ * field: an embedded body sits inside the memoized object and is cloned once per referencing
564
+ * entry.
564
565
  */
565
566
  private resolveSingleReference;
566
567
  /**
@@ -20,7 +20,7 @@ import { serializeFrontmatter, serializeYaml } from './utils/content-serialize.j
20
20
  import { serializeJson } from './utils/json-source-splice.js';
21
21
  import { withLock } from './utils/async-mutex.js';
22
22
  import { assertContentWriteLockHeld, ContentWriteLockBusyError, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, withContentWriteLock, } from './utils/content-write-lock.js';
23
- import { findBodyFieldName } from './utils/body-field.js';
23
+ import { findBodyFieldName, linkOnlyReferenceData } from './utils/body-field.js';
24
24
  import { withItemKeys } from './item-keys.js';
25
25
  import { buildMissingReference, buildResolvedReference, buildRestrictedReference, } from './entry-schema.js';
26
26
  import { warnDanglingReference } from './dangling-reference-log.js';
@@ -1611,10 +1611,11 @@ export class ContentStore {
1611
1611
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
1612
1612
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
1613
1613
  * 265KB JSON target ~0.8x -- a wash at the pathological end, never a blow-up, and on the
1614
- * EFS/NFS this targets the syscall the memo removes dominates both. An md/mdx target
1615
- * resolves to its FRONTMATTER unless the field sets `includeBody`, which is why
1616
- * `includeBody` is opt-in per field: an embedded body sits inside the memoized object and is
1617
- * cloned once per referencing entry.
1614
+ * EFS/NFS this targets the syscall the memo removes dominates both. A target resolves
1615
+ * without its body (an md/mdx target to its frontmatter, a json/yaml one without its `isBody`
1616
+ * field) unless the field sets `includeBody`, which is why `includeBody` is opt-in per
1617
+ * field: an embedded body sits inside the memoized object and is cloned once per referencing
1618
+ * entry.
1618
1619
  */
1619
1620
  resolveSingleReference(id, idIndex, includeBody, cache, access) {
1620
1621
  if (!cache)
@@ -1738,9 +1739,13 @@ export class ContentStore {
1738
1739
  //
1739
1740
  // `'body' in doc` narrows the ContentDocument union to its markdown variant, so
1740
1741
  // `doc.body`/`doc.bodyFieldName` are reachable at all -- a type guard, not a redundant
1741
- // runtime check. Only a field that asked to EMBED its target passes a body at all.
1742
+ // runtime check. Only a field that asked to EMBED its target passes a body at all, and
1743
+ // only such a field keeps a json/yaml target's `isBody` field.
1742
1744
  const bodyForEmbed = includeBody && 'body' in doc ? { fieldName: doc.bodyFieldName, value: doc.body } : undefined;
1743
- return buildResolvedReference(doc.data, meta, bodyForEmbed);
1745
+ const data = includeBody
1746
+ ? doc.data
1747
+ : linkOnlyReferenceData(doc.data, doc.format, this.entryTypeOf(location.collection, location.relativePath)?.schema);
1748
+ return buildResolvedReference(data, meta, bodyForEmbed);
1744
1749
  }
1745
1750
  catch (error) {
1746
1751
  // Index hit but the file is gone — the typical symptom of an external
@@ -0,0 +1,11 @@
1
+ /** GFM's autolink literals (bare `www.`, `http(s)://` and email addresses), by GFM's own parser. */
2
+ import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal';
3
+ import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal';
4
+ export { gfmAutolinkLiteral, gfmAutolinkLiteralFromMarkdown };
5
+ export interface AutolinkLiteral {
6
+ /** GFM's href: `www.` gains `http://`, an email `mailto:`. */
7
+ readonly url: string;
8
+ readonly length: number;
9
+ }
10
+ /** The literal GFM reads at `offset` in `text`, if any. */
11
+ export declare function autolinkLiteralAt(text: string, offset: number): AutolinkLiteral | undefined;
@@ -0,0 +1,45 @@
1
+ /** GFM's autolink literals (bare `www.`, `http(s)://` and email addresses), by GFM's own parser. */
2
+ import { fromMarkdown } from 'mdast-util-from-markdown';
3
+ import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal';
4
+ import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal';
5
+ export { gfmAutolinkLiteral, gfmAutolinkLiteralFromMarkdown };
6
+ /** An export asks about a paragraph once per link in it, so recent parses are kept. */
7
+ const MAX_CACHED = 16;
8
+ const cache = new Map();
9
+ /** `text`'s literals by offset. `[a](b)` and `<b>` are none, and lexical text holds neither. */
10
+ function literalsIn(text) {
11
+ const cached = cache.get(text);
12
+ if (cached)
13
+ return cached;
14
+ const tree = fromMarkdown(text, {
15
+ extensions: [gfmAutolinkLiteral()],
16
+ mdastExtensions: [gfmAutolinkLiteralFromMarkdown()],
17
+ });
18
+ const literals = new Map();
19
+ const pending = [tree];
20
+ for (let node = pending.pop(); node !== undefined; node = pending.pop()) {
21
+ const start = node.position?.start.offset;
22
+ const end = node.position?.end.offset;
23
+ const [only, ...rest] = node.children ?? [];
24
+ if (node.type === 'link' &&
25
+ node.url !== undefined &&
26
+ start !== undefined &&
27
+ end !== undefined &&
28
+ rest.length === 0 &&
29
+ only?.type === 'text' &&
30
+ only.value === text.slice(start, end)) {
31
+ literals.set(start, { url: node.url, length: end - start });
32
+ }
33
+ else {
34
+ pending.push(...(node.children ?? []));
35
+ }
36
+ }
37
+ if (cache.size >= MAX_CACHED)
38
+ cache.delete(cache.keys().next().value ?? '');
39
+ cache.set(text, literals);
40
+ return literals;
41
+ }
42
+ /** The literal GFM reads at `offset` in `text`, if any. */
43
+ export function autolinkLiteralAt(text, offset) {
44
+ return literalsIn(text).get(offset);
45
+ }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * MarkdownField's MDXEditor visitors for markdown its own import and export would change the
3
- * meaning of: an ordered list's `start`, a link inside a formatted span, an auto-link inside code,
4
- * and the code spans either side of one. MDXEditor is passed in, as `mdx-jsx-support.tsx`
5
- * explains.
3
+ * meaning of: an ordered list's `start`, a bare URL, a link inside a formatted span, an auto-link
4
+ * inside code, and the code spans either side of one. MDXEditor is passed in (`mdx-jsx-support`).
6
5
  */
7
6
  import type * as MdxEditor from '@mdxeditor/editor';
8
7
  type MdxEditorModule = typeof MdxEditor;