@contentrain/types 1.29.0 → 1.31.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/index.mjs CHANGED
@@ -659,11 +659,32 @@ function isClaimOrigin(x) {
659
659
  if (url.origin !== x || url.hostname.endsWith(".")) return false;
660
660
  return url.protocol === "https:" || url.protocol === "http:" && LOCAL_HOSTS.has(url.hostname);
661
661
  }
662
+ /** A compact JWS: three base64url segments, the signature non-empty. */
663
+ function isCompactJws(x) {
664
+ return typeof x === "string" && x.length <= 2048 && /^[\w-]+\.[\w-]+\.[\w-]+$/.test(x);
665
+ }
666
+ /** A fetchable export address: https (http for localhost), no credentials, query or fragment — nowhere for a secret. */
667
+ function isExportUrl(x) {
668
+ if (typeof x !== "string" || x.length > 2048) return false;
669
+ let url;
670
+ try {
671
+ url = new URL(x);
672
+ } catch {
673
+ return false;
674
+ }
675
+ if (url.username || url.password || url.search || url.hash || url.hostname.endsWith(".")) return false;
676
+ return url.protocol === "https:" || url.protocol === "http:" && LOCAL_HOSTS.has(url.hostname);
677
+ }
662
678
  /**
663
679
  * Check a decoded, signature-verified payload against the contract. Pass
664
680
  * `now` (seconds since the epoch) to also check the validity window; without
665
681
  * it only the shape and the window's own bounds are checked. Returns every
666
682
  * problem found, as stable machine-readable strings (`field: problem`).
683
+ *
684
+ * A malformed `comments_export` does not fail the claim: it is an add-on, and
685
+ * a Migrate still sending an older shape must not stop the trial. It is
686
+ * dropped from `claim` and reported in `warnings`; Studio then offers the
687
+ * file upload instead.
667
688
  */
668
689
  function validateMigrateStudioClaim(input, options = {}) {
669
690
  const errors = [];
@@ -707,17 +728,42 @@ function validateMigrateStudioClaim(input, options = {}) {
707
728
  else if (c.scale !== void 0 && c.scale !== null && typeof c.scale !== "string") errors.push(`capabilities[${i}].scale: invalid`);
708
729
  });
709
730
  if (x.origin !== void 0 && !isClaimOrigin(x.origin)) errors.push("origin: invalid");
710
- return errors.length === 0 ? {
711
- ok: true,
712
- claim: x
713
- } : {
731
+ const warnings = [];
732
+ if (x.comments_export !== void 0) {
733
+ const ce = x.comments_export;
734
+ if (!isObject(ce)) warnings.push("comments_export: invalid");
735
+ else {
736
+ if (!isExportUrl(ce.url)) warnings.push("comments_export.url: invalid");
737
+ if (!isCompactJws(ce.token)) warnings.push("comments_export.token: invalid");
738
+ if (!isSeconds(ce.expires_at)) warnings.push("comments_export.expires_at: invalid");
739
+ else if (isSeconds(x.iat) && ce.expires_at <= x.iat) warnings.push("comments_export.expires_at: not after iat");
740
+ if (typeof ce.comments !== "number" || !Number.isInteger(ce.comments) || ce.comments < 0) warnings.push("comments_export.comments: invalid");
741
+ }
742
+ }
743
+ if (errors.length > 0) return {
714
744
  ok: false,
715
745
  errors
716
746
  };
747
+ if (warnings.length === 0) return {
748
+ ok: true,
749
+ claim: x
750
+ };
751
+ const { comments_export: _dropped, ...rest } = x;
752
+ return {
753
+ ok: true,
754
+ claim: rest,
755
+ warnings
756
+ };
717
757
  }
718
- /** Shape-only type guard (no clock check). The signature is the consumer's job. */
758
+ /**
759
+ * Shape-only type guard (no clock check). The signature is the consumer's job. Stricter than
760
+ * `validateMigrateStudioClaim`: a claim whose `comments_export` would be dropped is `false` here,
761
+ * since the guard narrows the input itself, malformed export included. To keep such a claim, use
762
+ * `result.claim` from `validateMigrateStudioClaim`.
763
+ */
719
764
  function isMigrateStudioClaim(input) {
720
- return validateMigrateStudioClaim(input).ok;
765
+ const result = validateMigrateStudioClaim(input);
766
+ return result.ok && !result.warnings;
721
767
  }
722
768
  //#endregion
723
769
  //#region src/project-plan.ts
@@ -784,6 +830,24 @@ function footerMenusOf(site) {
784
830
  }
785
831
  /** A font size the starter writes into a style: a length, or a `clamp()`/`calc()`/`min()`/`max()` of lengths (a theme's fluid size). */
786
832
  const CSS_LENGTH = /^(?!.*url\()(?:\d+(?:\.\d+)?(?:px|rem|em|%)|(?:clamp|calc|min|max)\([\d\s.,+*/()a-z%-]+\))$/;
833
+ /**
834
+ * How many object levels a field nests: 0 for a scalar or a list of scalars, 1 for an object of scalars,
835
+ * 2 for an object holding a list of objects (`hero.actions[]`). A list adds no level of its own.
836
+ */
837
+ function fieldDepth(field) {
838
+ const children = [...Object.values(field.fields ?? {}), ...typeof field.items === "object" ? [field.items] : []];
839
+ return (field.type === "object" ? 1 : 0) + Math.max(0, ...children.map(fieldDepth));
840
+ }
841
+ /**
842
+ * Placement `rule`: a mapping table's section rule (`elementor:section:hero.split`) or element rule
843
+ * (`gutenberg:element:core/details`, `gutenberg:element:core/template-part:header`), a site component
844
+ * (`opus`), or the rich-text fallback.
845
+ */
846
+ const PLAN_SECTION_RULE = /^(?:(?:gutenberg|elementor|divi|classic):(?:section:[a-z][\w.-]*|element:[a-z][\w./:-]*)|opus|prose)$/;
847
+ /** A content prop's field name in a section object: the prop in snake_case (`ctaLabel` → `cta_label`). */
848
+ function sectionFieldName(prop) {
849
+ return prop.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
850
+ }
787
851
  function validateProjectPlan(plan) {
788
852
  const errors = [];
789
853
  const warnings = [];
@@ -802,13 +866,18 @@ function validateProjectPlan(plan) {
802
866
  ].includes(part)) || new Set(parts).size !== parts.length) errors.push("site.post.header lists a part twice or one the starter does not have");
803
867
  if (!Number.isSafeInteger(site.post.more) || site.post.more < 0 || site.post.more > 20) errors.push("site.post.more is not a count from 0 to 20");
804
868
  }
805
- if (site?.lists && !["cards", "full"].includes(site.lists.display)) errors.push(`site.lists.display ${site.lists.display} is not cards or full`);
869
+ if (site?.lists && ![
870
+ "cards",
871
+ "list",
872
+ "full"
873
+ ].includes(site.lists.display)) errors.push(`site.lists.display ${site.lists.display} is not cards, list or full`);
806
874
  if (site?.lists?.text !== void 0 && !CSS_LENGTH.test(site.lists.text)) errors.push(`site.lists.text ${site.lists.text} is not a CSS size`);
807
875
  const chrome = site?.chrome;
808
876
  if (chrome?.brand !== void 0 && !CSS_LENGTH.test(chrome.brand)) errors.push(`site.chrome.brand ${chrome.brand} is not a CSS size`);
809
877
  if (chrome?.tagline !== void 0 && typeof chrome.tagline !== "boolean") errors.push("site.chrome.tagline is not a boolean");
810
878
  if (chrome?.titleLinks !== void 0 && typeof chrome.titleLinks !== "boolean") errors.push("site.chrome.titleLinks is not a boolean");
811
879
  if (chrome?.navLinks !== void 0 && typeof chrome.navLinks !== "boolean") errors.push("site.chrome.navLinks is not a boolean");
880
+ if (chrome?.footerAlign !== void 0 && !["start", "center"].includes(chrome.footerAlign)) errors.push(`site.chrome.footerAlign ${chrome.footerAlign} is not start or center`);
812
881
  if (chrome?.copyright !== void 0 && (typeof chrome.copyright !== "string" || !chrome.copyright.trim() || chrome.copyright.length > 200 || /[<>]/.test(chrome.copyright))) errors.push("site.chrome.copyright is not a line of plain text (1–200 characters, no markup)");
813
882
  const footer = site?.menus?.footer;
814
883
  if (typeof footer === "string") {
@@ -839,11 +908,13 @@ function validateProjectPlan(plan) {
839
908
  if (m.origin === "plan") {
840
909
  if (!m.fields) errors.push(`plan model ${m.id} has no fields`);
841
910
  if (!m.name || !m.domain) errors.push(`plan model ${m.id} needs name and domain`);
842
- if (m.kind === "collection" || m.kind === "document") {
843
- const title = m.title_field ? m.fields?.[m.title_field] : void 0;
844
- if (!m.title_field) errors.push(`plan model ${m.id} has no title_field`);
845
- else if (!title) errors.push(`plan model ${m.id}: title_field ${m.title_field} is not one of its fields`);
846
- else if (!isTitleFieldType(title.type)) errors.push(`plan model ${m.id}: title_field ${m.title_field} is ${title.type}, not a text-like type`);
911
+ if (m.kind === "collection" || m.kind === "document" || m.kind === "singleton" && m.title_field) {
912
+ const title = m.title_field ? titleFieldTarget(m.fields, m.title_field) : void 0;
913
+ if (!m.title_field || !title) errors.push(`plan model ${m.id} has no title_field`);
914
+ else if (title.kind === "too-deep") errors.push(`plan model ${m.id}: title_field ${m.title_field} reaches more than one object deep`);
915
+ else if (title.kind === "not-object") errors.push(`plan model ${m.id}: title_field ${m.title_field} goes through ${title.at} (${title.type}), not an object field`);
916
+ else if (title.kind === "missing") errors.push(`plan model ${m.id}: title_field ${m.title_field} is not one of its fields`);
917
+ else if (!isTitleFieldType(title.def.type)) errors.push(`plan model ${m.id}: title_field ${m.title_field} is ${title.def.type}, not a text-like type`);
847
918
  }
848
919
  if (!m.extract?.length && !m.form) warnings.push(`plan model ${m.id} has no extraction — its entries start empty`);
849
920
  }
@@ -856,9 +927,11 @@ function validateProjectPlan(plan) {
856
927
  }
857
928
  for (const x of m.extract ?? []) {
858
929
  if (!x.rule && !x.fields) errors.push(`model ${m.id} extraction from ${x.from} has neither rule nor fields`);
930
+ if (x.field !== void 0 && m.fields && m.fields[x.field]?.type !== "object") errors.push(`model ${m.id} extraction from ${x.from} fills ${x.field}, which is not an object field`);
859
931
  if (x.rule && !/^(gutenberg|elementor|divi|classic):[\w./:-]+$/.test(x.rule)) errors.push(`model ${m.id} extraction rule ${x.rule} is not <builder>:<match>`);
932
+ const target = x.field !== void 0 ? m.fields?.[x.field]?.fields : m.fields;
860
933
  for (const [field, path] of Object.entries(x.fields ?? {})) {
861
- if (m.fields && !m.fields[field]) errors.push(`model ${m.id} extraction fills unknown field ${field}`);
934
+ if (target && !target[field]) errors.push(`model ${m.id} extraction fills unknown field ${field}`);
862
935
  if (!/^attr:[\w.-]+$|^[\d.]+\|(text|href|src|alt|datetime)$|^(?:dom|img|link|html):[^@]*(?:@[\w-]+)?$/.test(path)) errors.push(`model ${m.id} field ${field}: path ${path} is not "<path>|<prop>", "attr:<name>" or an element expression`);
863
936
  }
864
937
  }
@@ -883,6 +956,21 @@ function validateProjectPlan(plan) {
883
956
  const b = s.bind;
884
957
  if ("model" in b && !knownModel(b.model)) errors.push(`${at}: model ${b.model} is not declared`);
885
958
  if (b.kind === "entry" && !route?.source) errors.push(`${at}: binds the route entry but ${route ? "the route has no source" : "layout has no entry"}`);
959
+ if (s.id !== void 0 && !/^[a-z][a-z0-9-]*$/.test(s.id)) errors.push(`${at}: id ${s.id} is not kebab-case`);
960
+ if (s.rule !== void 0 && !PLAN_SECTION_RULE.test(s.rule)) errors.push(`${at}: rule ${s.rule} is not <builder>:section:<id>, <builder>:element:<match>, opus or prose`);
961
+ if (b.kind === "section") {
962
+ const m = models.get(b.model);
963
+ const field = m?.fields?.[b.field];
964
+ const ownEntry = route?.page === b.model && route.source?.model === b.model;
965
+ if (m && m.origin === "plan" && m.kind !== "singleton" && b.entry === void 0 && !ownEntry) errors.push(`${at}: section of ${b.model} names no entry and ${b.model} is neither a singleton nor this route's page`);
966
+ if (m?.fields && !field) errors.push(`${at}: ${b.model} has no field ${b.field}`);
967
+ else if (field && field.type !== "object") errors.push(`${at}: ${b.model}.${b.field} is ${field.type}, not an object`);
968
+ else if (field && fieldDepth(field) > 2) errors.push(`${at}: ${b.model}.${b.field} nests deeper than 2 (an object inside a list item's object)`);
969
+ const fieldOf = new Map(Object.keys(c.props ?? {}).map((prop) => [sectionFieldName(prop), prop]));
970
+ if (c.props && field?.fields) {
971
+ for (const key of Object.keys(field.fields)) if (!fieldOf.has(key)) errors.push(`${at}: ${c.id} has no prop for field ${b.model}.${b.field}.${key} (fields are the props in snake_case)`);
972
+ }
973
+ }
886
974
  const values = {
887
975
  ..."props" in b ? b.props : {},
888
976
  ...b.kind === "collection" && !b.into ? b.item : {}
@@ -892,8 +980,11 @@ function validateProjectPlan(plan) {
892
980
  ...b.kind === "collection" ? b.item : {}
893
981
  })) if (!PLAN_VALUE_PATTERN.test(value)) errors.push(`${at}: ${prop} = ${value} is not a plan value (field: media: ref: href: term: ui: site: menu: page: const:)`);
894
982
  if (c.props) {
983
+ const byField = new Map(Object.keys(c.props).map((prop) => [sectionFieldName(prop), prop]));
984
+ const spread = b.kind === "section" ? Object.keys(models.get(b.model)?.fields?.[b.field]?.fields ?? {}).flatMap((key) => byField.get(key) ?? []) : [];
895
985
  const bound = new Set([
896
986
  ...Object.keys(values),
987
+ ...spread,
897
988
  ...Object.keys(s.labels ?? {}),
898
989
  ..."into" in b && b.into ? [b.into] : b.kind === "menu" ? ["items"] : []
899
990
  ]);
@@ -914,6 +1005,18 @@ function validateProjectPlan(plan) {
914
1005
  (byModel.get(r.source.model) ?? byModel.set(r.source.model, []).get(r.source.model)).push(r);
915
1006
  }
916
1007
  if (r.body === "composed" && !r.sections.length) warnings.push(`route ${r.id} is composed but has no sections`);
1008
+ if (r.page !== void 0) {
1009
+ const page = models.get(r.page);
1010
+ if (!page) errors.push(`route ${r.id}: page ${r.page} is not declared`);
1011
+ else if (page.kind === "collection" ? r.source?.model !== r.page : page.kind !== "singleton") errors.push(`route ${r.id}: page ${r.page} is a ${page.kind}; a page is a singleton, or a collection that is the route's source`);
1012
+ if (r.body !== "composed") errors.push(`route ${r.id}: a page model needs body composed`);
1013
+ }
1014
+ const sectionIds = /* @__PURE__ */ new Set();
1015
+ for (const s of r.sections) {
1016
+ if (s.id === void 0) continue;
1017
+ if (sectionIds.has(s.id)) errors.push(`route ${r.id}: section id ${s.id} is used twice`);
1018
+ sectionIds.add(s.id);
1019
+ }
917
1020
  r.sections.forEach((s, i) => placement(s, `route ${r.id} section ${i}`, r));
918
1021
  }
919
1022
  const byPattern = /* @__PURE__ */ new Map();
@@ -1566,6 +1669,49 @@ const TITLE_FIELD_TYPE_SET = new Set(TITLE_FIELD_TYPES);
1566
1669
  function isTitleFieldType(type) {
1567
1670
  return TITLE_FIELD_TYPE_SET.has(type);
1568
1671
  }
1672
+ /** Resolve a `title_field` path against a model's fields. A field named exactly `path` wins over splitting it. */
1673
+ function titleFieldTarget(fields, path) {
1674
+ if (fields && Object.hasOwn(fields, path)) return {
1675
+ kind: "field",
1676
+ def: fields[path]
1677
+ };
1678
+ const segments = path.split(".");
1679
+ if (segments.length > 2) return { kind: "too-deep" };
1680
+ const [head, leaf] = segments;
1681
+ const def = fields && Object.hasOwn(fields, head) ? fields[head] : void 0;
1682
+ if (!def) return {
1683
+ kind: "missing",
1684
+ at: head
1685
+ };
1686
+ if (leaf === void 0) return {
1687
+ kind: "field",
1688
+ def
1689
+ };
1690
+ if (def.type !== "object") return {
1691
+ kind: "not-object",
1692
+ at: head,
1693
+ type: def.type
1694
+ };
1695
+ const child = def.fields && Object.hasOwn(def.fields, leaf) ? def.fields[leaf] : void 0;
1696
+ if (!child) return {
1697
+ kind: "missing",
1698
+ at: path
1699
+ };
1700
+ return {
1701
+ kind: "field",
1702
+ def: child,
1703
+ parent: def
1704
+ };
1705
+ }
1706
+ /** An entry's title value: `data[title_field]`, or one level into an object for a dotted path. */
1707
+ function titleFieldValue(data, path) {
1708
+ if (data && Object.hasOwn(data, path)) return data[path];
1709
+ const [head, leaf, ...rest] = path.split(".");
1710
+ if (!data || head === void 0 || rest.length > 0) return void 0;
1711
+ const value = data[head];
1712
+ if (leaf === void 0) return value;
1713
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value[leaf] : void 0;
1714
+ }
1569
1715
  /** Extensions we can name a MIME type for. Deliberately small — this is a sniff. */
1570
1716
  const EXTENSION_MIME = {
1571
1717
  jpg: "image/jpeg",
@@ -1986,6 +2132,6 @@ function serializeMarkdownFrontmatter(data, body) {
1986
2132
  return lines.join("\n");
1987
2133
  }
1988
2134
  //#endregion
1989
- export { APPROVAL_GATES, APPROVAL_MODES, CANONICAL_JSON, CAPABILITY_KEYS, CHROME_BODY_SLOT, CHROME_COMPONENT_CLOSE, CHROME_COMPONENT_OPEN, CHROME_IF_CLOSE, CHROME_IF_OPEN, CHROME_REPEAT_CLOSE, CHROME_REPEAT_OPEN, COMMENTS_EXPORT_FORMAT, COMPONENT_TYPES, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, DEFAULT_APPROVAL_POLICY, DICTIONARY_TITLE_FIELD, ENTRY_ID_PATTERN, EXECUTION_CONTRACT_VERSION, INTEGRATION_CATEGORIES, INTEGRATION_EVIDENCE_KINDS, LEGACY_CSS_LAYER, LIST_ITEMS_SLOT, LOCALE_PATTERN, LOCAL_CAPABILITIES, MIGRATE_STUDIO_CLAIM_ALG, MIGRATE_STUDIO_CLAIM_AUDIENCE, MIGRATE_STUDIO_CLAIM_CLOCK_SKEW_SECONDS, MIGRATE_STUDIO_CLAIM_ISSUER, MIGRATE_STUDIO_CLAIM_MAX_TRIAL_DAYS, MIGRATE_STUDIO_CLAIM_MAX_TTL_SECONDS, MIGRATE_STUDIO_CLAIM_VERSION, MIGRATE_STUDIO_PLANS, MIGRATION_CONTRACT_VERSION, MODEL_EXTENSION_KEYS, MODEL_FIELD_ORDER, PAGE_BODY_KINDS, PATH_PATTERNS, PLAN_BEHAVIOR_REASON, PLAN_STARTER_FEATURES, PLAN_TOKEN_ROLES, PLAN_VALUE_PATTERN, PROJECT_PLAN_FORMAT, RAW_MARK_SUFFIX, RESERVED_PATHS, RISK_CLASSES, RUN_STATUSES, SCHEDULE_KINDS, SECRET_PATTERNS, SEO_PROVIDERS, SLUG_PATTERN, SOURCE_ACCESS_LADDER, SOURCE_DELTA_OPS, TERMINAL_RUN_STATUSES, TEXT_CANDIDATE_KINDS, TEXT_EXCLUDE_REASONS, TITLE_FIELD_TYPES, approversFor, canonicalStringify, commentsExportSource, componentSlot, computePlanHash, conflictId, describeModelLocaleScope, detectSecrets, effectiveRisk, evaluateApproval, footerMenusOf, generateEntryId, highestRisk, isMediaType, isMigrateStudioClaim, isRepoRelativePath, isReservedPath, isStoredMediaPath, isTerminalRunStatus, isTitleFieldType, looksLikeCredential, orderedFieldNames, parseFrontmatterScalar, parseFrontmatterScalarString, parseMarkdownFrontmatter, planHashPayload, requiredApprovals, resolveFieldLabel, resolveModelLocales, rewriteEntryMedia, rewriteFieldMedia, rewriteMarkdownMedia, rewriteMediaUrl, riskRank, serializeMarkdownFrontmatter, sortKeys, splitFrontmatterList, stableHash, toDeliveryUrl, validateAccept, validateEntryId, validateFieldValue, validateHandoffCommentsExport, validateLocale, validateMigrateStudioClaim, validateModelLocales, validateProjectPlan, validateSemanticType, validateSlug };
2135
+ export { APPROVAL_GATES, APPROVAL_MODES, CANONICAL_JSON, CAPABILITY_KEYS, CHROME_BODY_SLOT, CHROME_COMPONENT_CLOSE, CHROME_COMPONENT_OPEN, CHROME_IF_CLOSE, CHROME_IF_OPEN, CHROME_REPEAT_CLOSE, CHROME_REPEAT_OPEN, COMMENTS_EXPORT_FORMAT, COMPONENT_TYPES, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, DEFAULT_APPROVAL_POLICY, DICTIONARY_TITLE_FIELD, ENTRY_ID_PATTERN, EXECUTION_CONTRACT_VERSION, INTEGRATION_CATEGORIES, INTEGRATION_EVIDENCE_KINDS, LEGACY_CSS_LAYER, LIST_ITEMS_SLOT, LOCALE_PATTERN, LOCAL_CAPABILITIES, MIGRATE_STUDIO_CLAIM_ALG, MIGRATE_STUDIO_CLAIM_AUDIENCE, MIGRATE_STUDIO_CLAIM_CLOCK_SKEW_SECONDS, MIGRATE_STUDIO_CLAIM_ISSUER, MIGRATE_STUDIO_CLAIM_MAX_TRIAL_DAYS, MIGRATE_STUDIO_CLAIM_MAX_TTL_SECONDS, MIGRATE_STUDIO_CLAIM_VERSION, MIGRATE_STUDIO_PLANS, MIGRATION_CONTRACT_VERSION, MODEL_EXTENSION_KEYS, MODEL_FIELD_ORDER, PAGE_BODY_KINDS, PATH_PATTERNS, PLAN_BEHAVIOR_REASON, PLAN_SECTION_RULE, PLAN_STARTER_FEATURES, PLAN_TOKEN_ROLES, PLAN_VALUE_PATTERN, PROJECT_PLAN_FORMAT, RAW_MARK_SUFFIX, RESERVED_PATHS, RISK_CLASSES, RUN_STATUSES, SCHEDULE_KINDS, SECRET_PATTERNS, SEO_PROVIDERS, SLUG_PATTERN, SOURCE_ACCESS_LADDER, SOURCE_DELTA_OPS, TERMINAL_RUN_STATUSES, TEXT_CANDIDATE_KINDS, TEXT_EXCLUDE_REASONS, TITLE_FIELD_TYPES, approversFor, canonicalStringify, commentsExportSource, componentSlot, computePlanHash, conflictId, describeModelLocaleScope, detectSecrets, effectiveRisk, evaluateApproval, fieldDepth, footerMenusOf, generateEntryId, highestRisk, isMediaType, isMigrateStudioClaim, isRepoRelativePath, isReservedPath, isStoredMediaPath, isTerminalRunStatus, isTitleFieldType, looksLikeCredential, orderedFieldNames, parseFrontmatterScalar, parseFrontmatterScalarString, parseMarkdownFrontmatter, planHashPayload, requiredApprovals, resolveFieldLabel, resolveModelLocales, rewriteEntryMedia, rewriteFieldMedia, rewriteMarkdownMedia, rewriteMediaUrl, riskRank, sectionFieldName, serializeMarkdownFrontmatter, sortKeys, splitFrontmatterList, stableHash, titleFieldTarget, titleFieldValue, toDeliveryUrl, validateAccept, validateEntryId, validateFieldValue, validateHandoffCommentsExport, validateLocale, validateMigrateStudioClaim, validateModelLocales, validateProjectPlan, validateSemanticType, validateSlug };
1990
2136
 
1991
2137
  //# sourceMappingURL=index.mjs.map