@valkyrianlabs/payload-markdown-docs 0.3.1 → 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.
Files changed (98) hide show
  1. package/README.md +76 -176
  2. package/dist/admin/DocsSetManager.js +5 -3
  3. package/dist/admin/DocsSetManager.js.map +1 -1
  4. package/dist/admin/docsSetManagerData.d.ts +6 -5
  5. package/dist/admin/docsSetManagerData.js +60 -33
  6. package/dist/admin/docsSetManagerData.js.map +1 -1
  7. package/dist/admin/docsSetManagerTypes.d.ts +12 -9
  8. package/dist/admin/docsSetManagerTypes.js.map +1 -1
  9. package/dist/cli/commands/manifest.js +1 -2
  10. package/dist/cli/commands/manifest.js.map +1 -1
  11. package/dist/cli/commands/plan.js +1 -2
  12. package/dist/cli/commands/plan.js.map +1 -1
  13. package/dist/cli/commands/push.js +2 -5
  14. package/dist/cli/commands/push.js.map +1 -1
  15. package/dist/cli/commands/validate.js +11 -6
  16. package/dist/cli/commands/validate.js.map +1 -1
  17. package/dist/cli/index.js +5 -14
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/parseArgs.js +0 -3
  20. package/dist/cli/parseArgs.js.map +1 -1
  21. package/dist/cli/types.d.ts +0 -3
  22. package/dist/cli/types.js.map +1 -1
  23. package/dist/collections/docs.js +0 -24
  24. package/dist/collections/docs.js.map +1 -1
  25. package/dist/collections/docsGroups.js +8 -9
  26. package/dist/collections/docsGroups.js.map +1 -1
  27. package/dist/collections/docsKeys.d.ts +5 -0
  28. package/dist/collections/docsKeys.js +44 -0
  29. package/dist/collections/docsKeys.js.map +1 -0
  30. package/dist/collections/docsSets.js +47 -202
  31. package/dist/collections/docsSets.js.map +1 -1
  32. package/dist/collections/docsTrusted.d.ts +5 -0
  33. package/dist/collections/docsTrusted.js +60 -0
  34. package/dist/collections/docsTrusted.js.map +1 -0
  35. package/dist/collections/index.d.ts +4 -0
  36. package/dist/collections/index.js +2 -0
  37. package/dist/collections/index.js.map +1 -1
  38. package/dist/constants.d.ts +3 -1
  39. package/dist/constants.js +3 -1
  40. package/dist/constants.js.map +1 -1
  41. package/dist/endpoints/sync.d.ts +6 -7
  42. package/dist/endpoints/sync.js +57 -124
  43. package/dist/endpoints/sync.js.map +1 -1
  44. package/dist/index.d.ts +2 -2
  45. package/dist/index.js +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/next/PayloadMarkdownDocsPage.js +2 -6
  48. package/dist/next/PayloadMarkdownDocsPage.js.map +1 -1
  49. package/dist/next/index.d.ts +2 -0
  50. package/dist/next/index.js +1 -0
  51. package/dist/next/index.js.map +1 -1
  52. package/dist/next/links.d.ts +11 -0
  53. package/dist/next/links.js +79 -0
  54. package/dist/next/links.js.map +1 -0
  55. package/dist/next/markdown.js +91 -19
  56. package/dist/next/markdown.js.map +1 -1
  57. package/dist/next/metadata.js +6 -6
  58. package/dist/next/metadata.js.map +1 -1
  59. package/dist/next/records.js +13 -23
  60. package/dist/next/records.js.map +1 -1
  61. package/dist/next/route.js +141 -49
  62. package/dist/next/route.js.map +1 -1
  63. package/dist/next/types.d.ts +0 -14
  64. package/dist/next/types.js.map +1 -1
  65. package/dist/payload/docsKeys.d.ts +20 -0
  66. package/dist/payload/docsKeys.js +29 -0
  67. package/dist/payload/docsKeys.js.map +1 -0
  68. package/dist/payload/docsSets.d.ts +32 -6
  69. package/dist/payload/docsSets.js +146 -83
  70. package/dist/payload/docsSets.js.map +1 -1
  71. package/dist/payload/docsTrusted.d.ts +16 -0
  72. package/dist/payload/docsTrusted.js +49 -0
  73. package/dist/payload/docsTrusted.js.map +1 -0
  74. package/dist/payload/index.d.ts +5 -1
  75. package/dist/payload/index.js +3 -1
  76. package/dist/payload/index.js.map +1 -1
  77. package/dist/plugin.js +36 -9
  78. package/dist/plugin.js.map +1 -1
  79. package/dist/security/githubOidc.d.ts +18 -5
  80. package/dist/security/githubOidc.js +44 -16
  81. package/dist/security/githubOidc.js.map +1 -1
  82. package/dist/security/index.d.ts +1 -1
  83. package/dist/security/index.js.map +1 -1
  84. package/dist/skills/codex/SKILL.md +3 -4
  85. package/dist/skills/codex/examples/github-actions.md +0 -2
  86. package/dist/skills/codex/reference/admin.md +0 -6
  87. package/dist/skills/codex/reference/routing.md +2 -1
  88. package/dist/skills/codex/reference/sync.md +7 -5
  89. package/dist/skills/codex/reference/troubleshooting.md +3 -4
  90. package/dist/skills/codex/reference/workflow.md +0 -1
  91. package/dist/sync/manifest.d.ts +1 -3
  92. package/dist/sync/manifest.js +2 -3
  93. package/dist/sync/manifest.js.map +1 -1
  94. package/dist/sync/validate.js +1 -2
  95. package/dist/sync/validate.js.map +1 -1
  96. package/dist/types.d.ts +7 -50
  97. package/dist/types.js.map +1 -1
  98. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { PayloadMarkdownDocsConfig } from './types.js'\n\nimport {\n createDocsCollection,\n createDocsGroupsCollection,\n createDocsSetsCollection,\n createNoncesCollection,\n createSyncRunsCollection,\n} from './collections/index.js'\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_ROUTE_BASE,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n DEFAULT_MARKDOWN_FIELD_NAME,\n DEFAULT_MAX_BODY_BYTES,\n DEFAULT_PAGES_BRIDGE_FIELD,\n DEFAULT_PAGES_COLLECTION_SLUG,\n DEFAULT_PAGES_ROUTE_FIELD,\n} from './constants.js'\nimport { createSyncEndpoint } from './endpoints/index.js'\n\ntype ResolvedCollectionOptions = {\n docsCollectionSlug: string\n docsEnabled: boolean\n docsGroupsCollectionSlug: string\n docsGroupsEnabled: boolean\n docsSetsCollectionSlug: string\n docsSetsEnabled: boolean\n enableDrafts: boolean\n markdownFieldName: string\n noncesCollectionSlug: string\n noncesEnabled: boolean\n syncRunsCollectionSlug: string\n syncRunsEnabled: boolean\n}\n\nconst normalizeEndpointPath = (path: string): string => {\n const normalized = `/${path.trim()}`.replace(/\\/+/g, '/')\n\n return normalized.length > 1 ? normalized.replace(/\\/+$/g, '') : normalized\n}\n\nconst resolveCollectionOptions = (\n pluginOptions: PayloadMarkdownDocsConfig,\n): ResolvedCollectionOptions => {\n if (pluginOptions.target?.type === 'existingCollection') {\n throw new Error(\n 'payloadMarkdownDocs: target.type \"existingCollection\" is not supported yet. Use target.type \"docsCollection\".',\n )\n }\n\n const docsSlugFromTarget = pluginOptions.target?.slug\n const docsSlugFromCollections = pluginOptions.collections?.docs?.slug\n\n if (\n docsSlugFromTarget &&\n docsSlugFromCollections &&\n docsSlugFromTarget !== docsSlugFromCollections\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.slug and collections.docs.slug must match when both are provided.',\n )\n }\n\n return {\n docsCollectionSlug:\n docsSlugFromTarget ?? docsSlugFromCollections ?? DEFAULT_DOCS_COLLECTION_SLUG,\n docsEnabled: pluginOptions.collections?.docs?.enabled !== false,\n docsGroupsCollectionSlug:\n pluginOptions.collections?.docsGroups?.slug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n docsGroupsEnabled: pluginOptions.collections?.docsGroups?.enabled !== false,\n docsSetsCollectionSlug:\n pluginOptions.collections?.docsSets?.slug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n docsSetsEnabled: pluginOptions.collections?.docsSets?.enabled !== false,\n enableDrafts:\n pluginOptions.target?.type === 'docsCollection'\n ? pluginOptions.target.enableDrafts === true\n : false,\n markdownFieldName:\n pluginOptions.target?.type === 'docsCollection'\n ? pluginOptions.target.markdownField ?? DEFAULT_MARKDOWN_FIELD_NAME\n : DEFAULT_MARKDOWN_FIELD_NAME,\n noncesCollectionSlug:\n pluginOptions.collections?.nonces?.slug ?? DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n noncesEnabled: pluginOptions.collections?.nonces?.enabled !== false,\n syncRunsCollectionSlug:\n pluginOptions.collections?.syncRuns?.slug ?? DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n syncRunsEnabled: pluginOptions.collections?.syncRuns?.enabled !== false,\n }\n}\n\nconst assertCollectionOptionCompatibility = ({\n docsGroupsEnabled,\n docsSetsEnabled,\n}: ResolvedCollectionOptions) => {\n if (docsSetsEnabled && !docsGroupsEnabled) {\n throw new Error(\n 'payloadMarkdownDocs: collections.docsSets requires collections.docsGroups to be enabled.',\n )\n }\n}\n\nconst assertNoCollectionSlugConflicts = (\n incomingConfig: Config,\n collectionSlugsToAdd: string[],\n) => {\n const duplicateRequestedSlug = collectionSlugsToAdd.find(\n (slug, index) => collectionSlugsToAdd.indexOf(slug) !== index,\n )\n\n if (duplicateRequestedSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${duplicateRequestedSlug}\" is configured more than once.`,\n )\n }\n\n const existingCollectionSlugs = new Set(\n incomingConfig.collections?.map((collection) => collection.slug) ?? [],\n )\n\n const conflictingSlug = collectionSlugsToAdd.find((slug) =>\n existingCollectionSlugs.has(slug),\n )\n\n if (conflictingSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${conflictingSlug}\" already exists in the Payload config.`,\n )\n }\n}\n\nexport const payloadMarkdownDocs =\n (pluginOptions: PayloadMarkdownDocsConfig = {}): Plugin =>\n (incomingConfig: Config): Config => {\n if (pluginOptions.enabled === false) {\n return incomingConfig\n }\n\n const {\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n } = resolveCollectionOptions(pluginOptions)\n assertCollectionOptionCompatibility({\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n })\n const endpointPath = normalizeEndpointPath(\n pluginOptions.endpoint?.path ?? DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n )\n\n const collectionSlugsToAdd = [\n ...(docsGroupsEnabled ? [docsGroupsCollectionSlug] : []),\n ...(docsSetsEnabled ? [docsSetsCollectionSlug] : []),\n ...(docsEnabled ? [docsCollectionSlug] : []),\n ...(syncRunsEnabled ? [syncRunsCollectionSlug] : []),\n ...(noncesEnabled ? [noncesCollectionSlug] : []),\n ]\n\n assertNoCollectionSlugConflicts(incomingConfig, collectionSlugsToAdd)\n\n const addedCollections = [\n ...(docsGroupsEnabled\n ? [\n createDocsGroupsCollection({\n slug: docsGroupsCollectionSlug,\n }),\n ]\n : []),\n ...(docsSetsEnabled\n ? [\n createDocsSetsCollection({\n slug: docsSetsCollectionSlug,\n docsCollectionSlug: docsEnabled ? docsCollectionSlug : undefined,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(docsEnabled\n ? [\n createDocsCollection({\n slug: docsCollectionSlug,\n docsSetsCollectionSlug: docsSetsEnabled\n ? docsSetsCollectionSlug\n : undefined,\n enableDrafts,\n markdownFieldName,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(syncRunsEnabled\n ? [\n createSyncRunsCollection({\n slug: syncRunsCollectionSlug,\n }),\n ]\n : []),\n ...(noncesEnabled\n ? [\n createNoncesCollection({\n slug: noncesCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ]\n\n return {\n ...incomingConfig,\n collections: [...(incomingConfig.collections ?? []), ...addedCollections],\n endpoints: [\n ...(incomingConfig.endpoints ?? []),\n createSyncEndpoint({\n allowHardDelete: pluginOptions.sync?.allowHardDelete,\n allowPublish: pluginOptions.sync?.allowPublish,\n allowWrites: pluginOptions.sync?.allowWrites,\n auth: pluginOptions.auth,\n defaultPublishMode: pluginOptions.sync?.defaultPublishMode,\n deleteBehavior: pluginOptions.sync?.deleteBehavior,\n docsCollectionSlug,\n docsEnabled,\n docsEnableDrafts: enableDrafts,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n endpointPath,\n markdownFieldName,\n maxBodyBytes: pluginOptions.endpoint?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n noncesCollectionSlug,\n noncesEnabled,\n requireDryRunBeforeApply: pluginOptions.sync?.requireDryRunBeforeApply,\n routeBase: DEFAULT_DOCS_ROUTE_BASE,\n routing: {\n pages: {\n allowBridgePages:\n pluginOptions.routing?.pages?.allowBridgePages ?? true,\n bridgeField:\n pluginOptions.routing?.pages?.bridgeField ?? DEFAULT_PAGES_BRIDGE_FIELD,\n collection:\n pluginOptions.routing?.pages?.collection ?? DEFAULT_PAGES_COLLECTION_SLUG,\n enabled: pluginOptions.routing?.pages?.enabled === true,\n routeField:\n pluginOptions.routing?.pages?.routeField ?? DEFAULT_PAGES_ROUTE_FIELD,\n },\n },\n sources: pluginOptions.sources,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n }),\n ],\n }\n }\n"],"names":["createDocsCollection","createDocsGroupsCollection","createDocsSetsCollection","createNoncesCollection","createSyncRunsCollection","DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_ROUTE_BASE","DEFAULT_DOCS_SETS_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_ENDPOINT_PATH","DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG","DEFAULT_MARKDOWN_FIELD_NAME","DEFAULT_MAX_BODY_BYTES","DEFAULT_PAGES_BRIDGE_FIELD","DEFAULT_PAGES_COLLECTION_SLUG","DEFAULT_PAGES_ROUTE_FIELD","createSyncEndpoint","normalizeEndpointPath","path","normalized","trim","replace","length","resolveCollectionOptions","pluginOptions","target","type","Error","docsSlugFromTarget","slug","docsSlugFromCollections","collections","docs","docsCollectionSlug","docsEnabled","enabled","docsGroupsCollectionSlug","docsGroups","docsGroupsEnabled","docsSetsCollectionSlug","docsSets","docsSetsEnabled","enableDrafts","markdownFieldName","markdownField","noncesCollectionSlug","nonces","noncesEnabled","syncRunsCollectionSlug","syncRuns","syncRunsEnabled","assertCollectionOptionCompatibility","assertNoCollectionSlugConflicts","incomingConfig","collectionSlugsToAdd","duplicateRequestedSlug","find","index","indexOf","existingCollectionSlugs","Set","map","collection","conflictingSlug","has","payloadMarkdownDocs","endpointPath","endpoint","addedCollections","undefined","endpoints","allowHardDelete","sync","allowPublish","allowWrites","auth","defaultPublishMode","deleteBehavior","docsEnableDrafts","maxBodyBytes","requireDryRunBeforeApply","routeBase","routing","pages","allowBridgePages","bridgeField","routeField","sources"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { PayloadMarkdownDocsConfig } from './types.js'\n\nimport {\n createDocsCollection,\n createDocsGroupsCollection,\n createDocsKeysCollection,\n createDocsSetsCollection,\n createDocsTrustedCollection,\n createNoncesCollection,\n createSyncRunsCollection,\n} from './collections/index.js'\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n DEFAULT_MARKDOWN_FIELD_NAME,\n DEFAULT_MAX_BODY_BYTES,\n DEFAULT_PAGES_BRIDGE_FIELD,\n DEFAULT_PAGES_COLLECTION_SLUG,\n DEFAULT_PAGES_ROUTE_FIELD,\n} from './constants.js'\nimport { createSyncEndpoint } from './endpoints/index.js'\n\ntype ResolvedCollectionOptions = {\n docsCollectionSlug: string\n docsEnabled: boolean\n docsGroupsCollectionSlug: string\n docsGroupsEnabled: boolean\n docsKeysCollectionSlug: string\n docsKeysEnabled: boolean\n docsSetsCollectionSlug: string\n docsSetsEnabled: boolean\n docsTrustedCollectionSlug: string\n docsTrustedEnabled: boolean\n enableDrafts: boolean\n markdownFieldName: string\n noncesCollectionSlug: string\n noncesEnabled: boolean\n syncRunsCollectionSlug: string\n syncRunsEnabled: boolean\n}\n\nconst normalizeEndpointPath = (path: string): string => {\n const normalized = `/${path.trim()}`.replace(/\\/+/g, '/')\n\n return normalized.length > 1 ? normalized.replace(/\\/+$/g, '') : normalized\n}\n\nconst resolveCollectionOptions = (\n pluginOptions: PayloadMarkdownDocsConfig,\n): ResolvedCollectionOptions => {\n if (\n pluginOptions.target?.type !== undefined &&\n pluginOptions.target.type !== 'docsCollection'\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.type only supports \"docsCollection\". existingCollection is not supported.',\n )\n }\n\n const docsSlugFromTarget = pluginOptions.target?.slug\n const docsSlugFromCollections = pluginOptions.collections?.docs?.slug\n\n if (\n docsSlugFromTarget &&\n docsSlugFromCollections &&\n docsSlugFromTarget !== docsSlugFromCollections\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.slug and collections.docs.slug must match when both are provided.',\n )\n }\n\n return {\n docsCollectionSlug:\n docsSlugFromTarget ?? docsSlugFromCollections ?? DEFAULT_DOCS_COLLECTION_SLUG,\n docsEnabled: pluginOptions.collections?.docs?.enabled !== false,\n docsGroupsCollectionSlug:\n pluginOptions.collections?.docsGroups?.slug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n docsGroupsEnabled: pluginOptions.collections?.docsGroups?.enabled !== false,\n docsKeysCollectionSlug:\n pluginOptions.collections?.docsKeys?.slug ?? DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n docsKeysEnabled: pluginOptions.collections?.docsKeys?.enabled !== false,\n docsSetsCollectionSlug:\n pluginOptions.collections?.docsSets?.slug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n docsSetsEnabled: pluginOptions.collections?.docsSets?.enabled !== false,\n docsTrustedCollectionSlug:\n pluginOptions.collections?.docsTrusted?.slug ?? DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n docsTrustedEnabled: pluginOptions.collections?.docsTrusted?.enabled !== false,\n enableDrafts:\n pluginOptions.target?.enableDrafts === true,\n markdownFieldName:\n pluginOptions.target?.markdownField ?? DEFAULT_MARKDOWN_FIELD_NAME,\n noncesCollectionSlug:\n pluginOptions.collections?.nonces?.slug ?? DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n noncesEnabled: pluginOptions.collections?.nonces?.enabled !== false,\n syncRunsCollectionSlug:\n pluginOptions.collections?.syncRuns?.slug ?? DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n syncRunsEnabled: pluginOptions.collections?.syncRuns?.enabled !== false,\n }\n}\n\nconst assertCollectionOptionCompatibility = ({\n docsGroupsEnabled,\n docsSetsEnabled,\n}: ResolvedCollectionOptions) => {\n if (docsSetsEnabled && !docsGroupsEnabled) {\n throw new Error(\n 'payloadMarkdownDocs: collections.docsSets requires collections.docsGroups to be enabled.',\n )\n }\n}\n\nconst assertNoCollectionSlugConflicts = (\n incomingConfig: Config,\n collectionSlugsToAdd: string[],\n) => {\n const duplicateRequestedSlug = collectionSlugsToAdd.find(\n (slug, index) => collectionSlugsToAdd.indexOf(slug) !== index,\n )\n\n if (duplicateRequestedSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${duplicateRequestedSlug}\" is configured more than once.`,\n )\n }\n\n const existingCollectionSlugs = new Set(\n incomingConfig.collections?.map((collection) => collection.slug) ?? [],\n )\n\n const conflictingSlug = collectionSlugsToAdd.find((slug) =>\n existingCollectionSlugs.has(slug),\n )\n\n if (conflictingSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${conflictingSlug}\" already exists in the Payload config.`,\n )\n }\n}\n\nexport const payloadMarkdownDocs =\n (pluginOptions: PayloadMarkdownDocsConfig = {}): Plugin =>\n (incomingConfig: Config): Config => {\n if (pluginOptions.enabled === false) {\n return incomingConfig\n }\n\n const {\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n } = resolveCollectionOptions(pluginOptions)\n assertCollectionOptionCompatibility({\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n })\n const endpointPath = normalizeEndpointPath(\n pluginOptions.endpoint?.path ?? DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n )\n\n const collectionSlugsToAdd = [\n ...(docsGroupsEnabled ? [docsGroupsCollectionSlug] : []),\n ...(docsSetsEnabled ? [docsSetsCollectionSlug] : []),\n ...(docsKeysEnabled ? [docsKeysCollectionSlug] : []),\n ...(docsTrustedEnabled ? [docsTrustedCollectionSlug] : []),\n ...(docsEnabled ? [docsCollectionSlug] : []),\n ...(syncRunsEnabled ? [syncRunsCollectionSlug] : []),\n ...(noncesEnabled ? [noncesCollectionSlug] : []),\n ]\n\n assertNoCollectionSlugConflicts(incomingConfig, collectionSlugsToAdd)\n\n const addedCollections = [\n ...(docsGroupsEnabled\n ? [\n createDocsGroupsCollection({\n slug: docsGroupsCollectionSlug,\n }),\n ]\n : []),\n ...(docsSetsEnabled\n ? [\n createDocsSetsCollection({\n slug: docsSetsCollectionSlug,\n docsCollectionSlug: docsEnabled ? docsCollectionSlug : undefined,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(docsKeysEnabled\n ? [\n createDocsKeysCollection({\n slug: docsKeysCollectionSlug,\n }),\n ]\n : []),\n ...(docsTrustedEnabled\n ? [\n createDocsTrustedCollection({\n slug: docsTrustedCollectionSlug,\n }),\n ]\n : []),\n ...(docsEnabled\n ? [\n createDocsCollection({\n slug: docsCollectionSlug,\n docsSetsCollectionSlug: docsSetsEnabled\n ? docsSetsCollectionSlug\n : undefined,\n enableDrafts,\n markdownFieldName,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(syncRunsEnabled\n ? [\n createSyncRunsCollection({\n slug: syncRunsCollectionSlug,\n }),\n ]\n : []),\n ...(noncesEnabled\n ? [\n createNoncesCollection({\n slug: noncesCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ]\n\n return {\n ...incomingConfig,\n collections: [...(incomingConfig.collections ?? []), ...addedCollections],\n endpoints: [\n ...(incomingConfig.endpoints ?? []),\n createSyncEndpoint({\n allowHardDelete: pluginOptions.sync?.allowHardDelete,\n allowPublish: pluginOptions.sync?.allowPublish,\n allowWrites: pluginOptions.sync?.allowWrites,\n auth: pluginOptions.auth,\n defaultPublishMode: pluginOptions.sync?.defaultPublishMode,\n deleteBehavior: pluginOptions.sync?.deleteBehavior,\n docsCollectionSlug,\n docsEnabled,\n docsEnableDrafts: enableDrafts,\n docsGroupsCollectionSlug,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n endpointPath,\n markdownFieldName,\n maxBodyBytes: pluginOptions.endpoint?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n noncesCollectionSlug,\n noncesEnabled,\n requireDryRunBeforeApply: pluginOptions.sync?.requireDryRunBeforeApply,\n routing: {\n pages: {\n allowBridgePages:\n pluginOptions.routing?.pages?.allowBridgePages ?? true,\n bridgeField:\n pluginOptions.routing?.pages?.bridgeField ?? DEFAULT_PAGES_BRIDGE_FIELD,\n collection:\n pluginOptions.routing?.pages?.collection ?? DEFAULT_PAGES_COLLECTION_SLUG,\n enabled: pluginOptions.routing?.pages?.enabled === true,\n routeField:\n pluginOptions.routing?.pages?.routeField ?? DEFAULT_PAGES_ROUTE_FIELD,\n },\n },\n syncRunsCollectionSlug,\n syncRunsEnabled,\n }),\n ],\n }\n }\n"],"names":["createDocsCollection","createDocsGroupsCollection","createDocsKeysCollection","createDocsSetsCollection","createDocsTrustedCollection","createNoncesCollection","createSyncRunsCollection","DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_KEYS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_ENDPOINT_PATH","DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG","DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG","DEFAULT_MARKDOWN_FIELD_NAME","DEFAULT_MAX_BODY_BYTES","DEFAULT_PAGES_BRIDGE_FIELD","DEFAULT_PAGES_COLLECTION_SLUG","DEFAULT_PAGES_ROUTE_FIELD","createSyncEndpoint","normalizeEndpointPath","path","normalized","trim","replace","length","resolveCollectionOptions","pluginOptions","target","type","undefined","Error","docsSlugFromTarget","slug","docsSlugFromCollections","collections","docs","docsCollectionSlug","docsEnabled","enabled","docsGroupsCollectionSlug","docsGroups","docsGroupsEnabled","docsKeysCollectionSlug","docsKeys","docsKeysEnabled","docsSetsCollectionSlug","docsSets","docsSetsEnabled","docsTrustedCollectionSlug","docsTrusted","docsTrustedEnabled","enableDrafts","markdownFieldName","markdownField","noncesCollectionSlug","nonces","noncesEnabled","syncRunsCollectionSlug","syncRuns","syncRunsEnabled","assertCollectionOptionCompatibility","assertNoCollectionSlugConflicts","incomingConfig","collectionSlugsToAdd","duplicateRequestedSlug","find","index","indexOf","existingCollectionSlugs","Set","map","collection","conflictingSlug","has","payloadMarkdownDocs","endpointPath","endpoint","addedCollections","endpoints","allowHardDelete","sync","allowPublish","allowWrites","auth","defaultPublishMode","deleteBehavior","docsEnableDrafts","maxBodyBytes","requireDryRunBeforeApply","routing","pages","allowBridgePages","bridgeField","routeField"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- import type { PayloadMarkdownDocsGitHubOidcAuthConfig } from '../types.js';
2
1
  import type { FetchJson } from './jwks.js';
3
- export type GitHubOidcErrorCode = 'oidc_environment_not_allowed' | 'oidc_expired' | 'oidc_invalid_audience' | 'oidc_invalid_issuer' | 'oidc_invalid_token' | 'oidc_jwks_unavailable' | 'oidc_missing_claim' | 'oidc_missing_jti' | 'oidc_not_yet_valid' | 'oidc_owner_not_allowed' | 'oidc_pull_request_not_allowed' | 'oidc_ref_not_allowed' | 'oidc_repository_not_allowed' | 'oidc_workflow_not_allowed';
2
+ export type GitHubOidcErrorCode = 'oidc_expired' | 'oidc_invalid_audience' | 'oidc_invalid_issuer' | 'oidc_invalid_token' | 'oidc_jwks_unavailable' | 'oidc_missing_claim' | 'oidc_missing_jti' | 'oidc_not_yet_valid' | 'oidc_owner_not_allowed' | 'oidc_pull_request_not_allowed' | 'oidc_ref_not_allowed' | 'oidc_repository_not_allowed' | 'oidc_workflow_not_allowed';
4
3
  export type GitHubOidcClaims = {
5
4
  actor?: string;
6
5
  aud: string | string[];
@@ -20,6 +19,22 @@ export type GitHubOidcClaims = {
20
19
  workflow?: string;
21
20
  workflow_ref?: string;
22
21
  };
22
+ export type GitHubOidcTrustedSource = {
23
+ limitRepos?: boolean;
24
+ owner: string;
25
+ repositories?: string[];
26
+ };
27
+ export type GitHubOidcVerifyConfig = {
28
+ allowedRefs?: string[];
29
+ allowedWorkflowRefs?: string[];
30
+ allowPullRequests?: boolean;
31
+ audience: string;
32
+ enforceWorkflowRefs?: boolean;
33
+ issuer?: string;
34
+ jwksUrl?: string;
35
+ maxSkewSeconds?: number;
36
+ trustedSources: GitHubOidcTrustedSource[];
37
+ };
23
38
  export type VerifiedGitHubOidcToken = {
24
39
  claims: GitHubOidcClaims;
25
40
  expiresAt: Date;
@@ -33,11 +48,9 @@ export type VerifyGitHubOidcTokenResult = {
33
48
  ok: true;
34
49
  token: VerifiedGitHubOidcToken;
35
50
  };
36
- type GitHubOidcAuthConfig = PayloadMarkdownDocsGitHubOidcAuthConfig;
37
51
  export declare const verifyGitHubOidcToken: ({ config, fetchJson, now, token, }: {
38
- config: GitHubOidcAuthConfig;
52
+ config: GitHubOidcVerifyConfig;
39
53
  fetchJson?: FetchJson;
40
54
  now?: Date;
41
55
  token: string;
42
56
  }) => Promise<VerifyGitHubOidcTokenResult>;
43
- export {};
@@ -65,6 +65,30 @@ const includesIfConfigured = (allowed, value)=>{
65
65
  return value !== undefined && allowed.includes(value);
66
66
  };
67
67
  const audienceMatches = (audience, expected)=>Array.isArray(audience) ? audience.includes(expected) : audience === expected;
68
+ const getRepositoryName = (repository)=>{
69
+ const [, name] = repository.split('/', 2);
70
+ return name ?? repository;
71
+ };
72
+ const repositoryMatches = ({ allowed, owner, repository })=>{
73
+ const normalized = allowed.trim();
74
+ if (!normalized) {
75
+ return false;
76
+ }
77
+ return normalized.includes('/') ? normalized.toLowerCase() === repository.toLowerCase() : `${owner}/${normalized}`.toLowerCase() === repository.toLowerCase();
78
+ };
79
+ const findTrustedSource = ({ repository, repositoryOwner, trustedSources })=>trustedSources.find((source)=>{
80
+ if (source.owner.toLowerCase() !== repositoryOwner.toLowerCase()) {
81
+ return false;
82
+ }
83
+ if (source.limitRepos !== true) {
84
+ return true;
85
+ }
86
+ return (source.repositories ?? []).some((allowedRepository)=>repositoryMatches({
87
+ allowed: allowedRepository,
88
+ owner: source.owner,
89
+ repository
90
+ }));
91
+ });
68
92
  const verifyJwtSignature = ({ jwk, signature, signingInput })=>{
69
93
  try {
70
94
  const publicKey = createPublicKey({
@@ -138,28 +162,32 @@ export const verifyGitHubOidcToken = async ({ config, fetchJson, now = new Date(
138
162
  if (claims.iat - maxSkewSeconds > nowSeconds) {
139
163
  return issue('oidc_not_yet_valid', 'GitHub OIDC token was issued in the future.');
140
164
  }
141
- const hasRepositoryAllowlist = (config.allowedRepositories?.length ?? 0) > 0 || (config.allowedRepositoryOwners?.length ?? 0) > 0;
142
- if (!hasRepositoryAllowlist) {
143
- return issue('oidc_repository_not_allowed', 'GitHub OIDC auth requires an allowed repository or repository owner.');
165
+ const trustedSources = config.trustedSources ?? [];
166
+ if (trustedSources.length === 0) {
167
+ return issue('oidc_repository_not_allowed', 'GitHub OIDC auth requires a trusted GitHub owner.');
144
168
  }
145
- if (!includesIfConfigured(config.allowedRepositories, claims.repository)) {
146
- return issue('oidc_repository_not_allowed', 'GitHub OIDC token repository is not allowed.');
147
- }
148
- if (!includesIfConfigured(config.allowedRepositoryOwners, claims.repository_owner)) {
149
- return issue('oidc_owner_not_allowed', 'GitHub OIDC token repository owner is not allowed.');
169
+ const trustedSource = findTrustedSource({
170
+ repository: claims.repository,
171
+ repositoryOwner: claims.repository_owner,
172
+ trustedSources
173
+ });
174
+ if (!trustedSource) {
175
+ const matchingOwner = trustedSources.find((source)=>source.owner.toLowerCase() === claims.repository_owner.toLowerCase());
176
+ if (matchingOwner) {
177
+ return issue('oidc_repository_not_allowed', `GitHub OIDC token repository "${claims.repository}" is not trusted for owner "${claims.repository_owner}".`);
178
+ }
179
+ return issue('oidc_owner_not_allowed', `GitHub OIDC token repository owner "${claims.repository_owner}" is not trusted.`);
150
180
  }
181
+ const repositoryName = getRepositoryName(claims.repository);
151
182
  if (!includesIfConfigured(config.allowedRefs, claims.ref)) {
152
- return issue('oidc_ref_not_allowed', 'GitHub OIDC token ref is not allowed.');
153
- }
154
- if (!includesIfConfigured(config.allowedWorkflows, claims.workflow)) {
155
- return issue('oidc_workflow_not_allowed', 'GitHub OIDC token workflow is not allowed.');
183
+ return issue('oidc_ref_not_allowed', `GitHub OIDC token ref "${claims.ref}" is not allowed for "${repositoryName}".`);
156
184
  }
157
185
  const workflowRef = claims.workflow_ref ?? claims.job_workflow_ref;
158
- if (!includesIfConfigured(config.allowedWorkflowRefs, workflowRef)) {
159
- return issue('oidc_workflow_not_allowed', 'GitHub OIDC token workflow ref is not allowed.');
186
+ if (config.enforceWorkflowRefs === true && (config.allowedWorkflowRefs?.length ?? 0) === 0) {
187
+ return issue('oidc_workflow_not_allowed', 'Advanced workflow security is enabled but no workflow refs are trusted.');
160
188
  }
161
- if (!includesIfConfigured(config.allowedEnvironments, claims.environment)) {
162
- return issue('oidc_environment_not_allowed', 'GitHub OIDC token environment is not allowed.');
189
+ if (config.enforceWorkflowRefs === true && !includesIfConfigured(config.allowedWorkflowRefs, workflowRef)) {
190
+ return issue('oidc_workflow_not_allowed', 'GitHub OIDC token workflow ref is not allowed.');
163
191
  }
164
192
  if (claims.event_name === 'pull_request' && config.allowPullRequests !== true) {
165
193
  return issue('oidc_pull_request_not_allowed', 'GitHub OIDC pull request events are not allowed.');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/security/githubOidc.ts"],"sourcesContent":["import {\n createPublicKey,\n type JsonWebKey,\n verify,\n} from 'node:crypto'\n\nimport type { PayloadMarkdownDocsGitHubOidcAuthConfig } from '../types.js'\nimport type { FetchJson } from './jwks.js'\n\nimport {\n DEFAULT_GITHUB_OIDC_ISSUER,\n DEFAULT_MAX_SKEW_SECONDS,\n} from '../constants.js'\nimport {\n fetchJwks,\n findJwkByKid,\n getGithubOidcJwksUrl,\n} from './jwks.js'\nimport { decodeJwt } from './jwt.js'\n\nexport type GitHubOidcErrorCode =\n | 'oidc_environment_not_allowed'\n | 'oidc_expired'\n | 'oidc_invalid_audience'\n | 'oidc_invalid_issuer'\n | 'oidc_invalid_token'\n | 'oidc_jwks_unavailable'\n | 'oidc_missing_claim'\n | 'oidc_missing_jti'\n | 'oidc_not_yet_valid'\n | 'oidc_owner_not_allowed'\n | 'oidc_pull_request_not_allowed'\n | 'oidc_ref_not_allowed'\n | 'oidc_repository_not_allowed'\n | 'oidc_workflow_not_allowed'\n\nexport type GitHubOidcClaims = {\n actor?: string\n aud: string | string[]\n environment?: string\n event_name?: string\n exp: number\n iat: number\n iss: string\n job_workflow_ref?: string\n jti: string\n nbf?: number\n ref: string\n repository: string\n repository_owner: string\n sha?: string\n sub: string\n workflow?: string\n workflow_ref?: string\n}\n\nexport type VerifiedGitHubOidcToken = {\n claims: GitHubOidcClaims\n expiresAt: Date\n keyId: string\n}\n\nexport type VerifyGitHubOidcTokenResult =\n | {\n code: GitHubOidcErrorCode\n message: string\n ok: false\n }\n | {\n ok: true\n token: VerifiedGitHubOidcToken\n }\n\ntype GitHubOidcAuthConfig = PayloadMarkdownDocsGitHubOidcAuthConfig\n\nconst isString = (value: unknown): value is string =>\n typeof value === 'string' && value.trim() !== ''\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(isString)\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\nconst getStringClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): string | undefined => {\n const value = payload[claim]\n\n return isString(value) ? value : undefined\n}\n\nconst getNumberClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): number | undefined => {\n const value = payload[claim]\n\n return isNumber(value) ? value : undefined\n}\n\nconst getAudienceClaim = (\n payload: Record<string, unknown>,\n): string | string[] | undefined => {\n const value = payload.aud\n\n if (isString(value) || isStringArray(value)) {\n return value\n }\n\n return undefined\n}\n\nconst toClaims = (\n payload: Record<string, unknown>,\n): GitHubOidcClaims | undefined => {\n const aud = getAudienceClaim(payload)\n const exp = getNumberClaim(payload, 'exp')\n const iat = getNumberClaim(payload, 'iat')\n const iss = getStringClaim(payload, 'iss')\n const jti = getStringClaim(payload, 'jti')\n const ref = getStringClaim(payload, 'ref')\n const repository = getStringClaim(payload, 'repository')\n const repositoryOwner = getStringClaim(payload, 'repository_owner')\n const sub = getStringClaim(payload, 'sub')\n\n if (\n !aud ||\n exp === undefined ||\n iat === undefined ||\n !iss ||\n !jti ||\n !ref ||\n !repository ||\n !repositoryOwner ||\n !sub\n ) {\n return undefined\n }\n\n return {\n actor: getStringClaim(payload, 'actor'),\n aud,\n environment: getStringClaim(payload, 'environment'),\n event_name: getStringClaim(payload, 'event_name'),\n exp,\n iat,\n iss,\n job_workflow_ref: getStringClaim(payload, 'job_workflow_ref'),\n jti,\n nbf: getNumberClaim(payload, 'nbf'),\n ref,\n repository,\n repository_owner: repositoryOwner,\n sha: getStringClaim(payload, 'sha'),\n sub,\n workflow: getStringClaim(payload, 'workflow'),\n workflow_ref: getStringClaim(payload, 'workflow_ref'),\n }\n}\n\nconst issue = (\n code: GitHubOidcErrorCode,\n message: string,\n): VerifyGitHubOidcTokenResult => ({\n code,\n message,\n ok: false,\n})\n\nconst includesIfConfigured = (\n allowed: string[] | undefined,\n value: string | undefined,\n): boolean => {\n if (!allowed || allowed.length === 0) {\n return true\n }\n\n return value !== undefined && allowed.includes(value)\n}\n\nconst audienceMatches = (\n audience: string | string[],\n expected: string,\n): boolean =>\n Array.isArray(audience) ? audience.includes(expected) : audience === expected\n\nconst verifyJwtSignature = ({\n jwk,\n signature,\n signingInput,\n}: {\n jwk: Record<string, unknown>\n signature: Buffer\n signingInput: string\n}): boolean => {\n try {\n const publicKey = createPublicKey({\n format: 'jwk',\n key: jwk as JsonWebKey,\n })\n\n return verify(\n 'RSA-SHA256',\n Buffer.from(signingInput, 'utf8'),\n publicKey,\n signature,\n )\n } catch {\n return false\n }\n}\n\nexport const verifyGitHubOidcToken = async ({\n config,\n fetchJson,\n now = new Date(),\n token,\n}: {\n config: GitHubOidcAuthConfig\n fetchJson?: FetchJson\n now?: Date\n token: string\n}): Promise<VerifyGitHubOidcTokenResult> => {\n const decoded = decodeJwt(token)\n\n if (!decoded) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is malformed.')\n }\n\n if (decoded.header.alg !== 'RS256') {\n return issue('oidc_invalid_token', 'GitHub OIDC token must use RS256.')\n }\n\n if (!isString(decoded.header.kid)) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is missing kid.')\n }\n\n const issuer = config.issuer ?? DEFAULT_GITHUB_OIDC_ISSUER\n let jwksUrl: string\n\n try {\n jwksUrl = await getGithubOidcJwksUrl({\n fetchJson,\n issuer,\n jwksUrl: config.jwksUrl,\n })\n const jwks = await fetchJwks({\n fetchJson,\n now,\n url: jwksUrl,\n })\n const jwk = findJwkByKid({\n jwks,\n kid: decoded.header.kid,\n })\n\n if (\n !jwk ||\n !verifyJwtSignature({\n jwk,\n signature: decoded.signature,\n signingInput: decoded.signingInput,\n })\n ) {\n return issue('oidc_invalid_token', 'GitHub OIDC token signature is invalid.')\n }\n } catch {\n return issue('oidc_jwks_unavailable', 'GitHub OIDC signing keys are unavailable.')\n }\n\n if (!isString(decoded.payload.jti)) {\n return issue('oidc_missing_jti', 'GitHub OIDC token is missing jti.')\n }\n\n const claims = toClaims(decoded.payload)\n\n if (!claims) {\n return issue('oidc_missing_claim', 'GitHub OIDC token is missing a required claim.')\n }\n\n if (claims.iss !== issuer) {\n return issue('oidc_invalid_issuer', 'GitHub OIDC token issuer is not allowed.')\n }\n\n if (!audienceMatches(claims.aud, config.audience)) {\n return issue('oidc_invalid_audience', 'GitHub OIDC token audience is not allowed.')\n }\n\n const maxSkewSeconds = config.maxSkewSeconds ?? DEFAULT_MAX_SKEW_SECONDS\n const nowSeconds = now.getTime() / 1000\n\n if (claims.exp + maxSkewSeconds < nowSeconds) {\n return issue('oidc_expired', 'GitHub OIDC token has expired.')\n }\n\n if (claims.nbf !== undefined && claims.nbf - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token is not valid yet.')\n }\n\n if (claims.iat - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token was issued in the future.')\n }\n\n const hasRepositoryAllowlist =\n (config.allowedRepositories?.length ?? 0) > 0 ||\n (config.allowedRepositoryOwners?.length ?? 0) > 0\n\n if (!hasRepositoryAllowlist) {\n return issue(\n 'oidc_repository_not_allowed',\n 'GitHub OIDC auth requires an allowed repository or repository owner.',\n )\n }\n\n if (!includesIfConfigured(config.allowedRepositories, claims.repository)) {\n return issue(\n 'oidc_repository_not_allowed',\n 'GitHub OIDC token repository is not allowed.',\n )\n }\n\n if (!includesIfConfigured(config.allowedRepositoryOwners, claims.repository_owner)) {\n return issue(\n 'oidc_owner_not_allowed',\n 'GitHub OIDC token repository owner is not allowed.',\n )\n }\n\n if (!includesIfConfigured(config.allowedRefs, claims.ref)) {\n return issue('oidc_ref_not_allowed', 'GitHub OIDC token ref is not allowed.')\n }\n\n if (!includesIfConfigured(config.allowedWorkflows, claims.workflow)) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'GitHub OIDC token workflow is not allowed.',\n )\n }\n\n const workflowRef = claims.workflow_ref ?? claims.job_workflow_ref\n\n if (!includesIfConfigured(config.allowedWorkflowRefs, workflowRef)) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'GitHub OIDC token workflow ref is not allowed.',\n )\n }\n\n if (!includesIfConfigured(config.allowedEnvironments, claims.environment)) {\n return issue(\n 'oidc_environment_not_allowed',\n 'GitHub OIDC token environment is not allowed.',\n )\n }\n\n if (claims.event_name === 'pull_request' && config.allowPullRequests !== true) {\n return issue(\n 'oidc_pull_request_not_allowed',\n 'GitHub OIDC pull request events are not allowed.',\n )\n }\n\n return {\n ok: true,\n token: {\n claims,\n expiresAt: new Date(claims.exp * 1000),\n keyId: `github-oidc:${claims.repository}`,\n },\n }\n}\n"],"names":["createPublicKey","verify","DEFAULT_GITHUB_OIDC_ISSUER","DEFAULT_MAX_SKEW_SECONDS","fetchJwks","findJwkByKid","getGithubOidcJwksUrl","decodeJwt","isString","value","trim","isStringArray","Array","isArray","every","isNumber","Number","isFinite","getStringClaim","payload","claim","undefined","getNumberClaim","getAudienceClaim","aud","toClaims","exp","iat","iss","jti","ref","repository","repositoryOwner","sub","actor","environment","event_name","job_workflow_ref","nbf","repository_owner","sha","workflow","workflow_ref","issue","code","message","ok","includesIfConfigured","allowed","length","includes","audienceMatches","audience","expected","verifyJwtSignature","jwk","signature","signingInput","publicKey","format","key","Buffer","from","verifyGitHubOidcToken","config","fetchJson","now","Date","token","decoded","header","alg","kid","issuer","jwksUrl","jwks","url","claims","maxSkewSeconds","nowSeconds","getTime","hasRepositoryAllowlist","allowedRepositories","allowedRepositoryOwners","allowedRefs","allowedWorkflows","workflowRef","allowedWorkflowRefs","allowedEnvironments","allowPullRequests","expiresAt","keyId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/security/githubOidc.ts"],"sourcesContent":["import {\n createPublicKey,\n type JsonWebKey,\n verify,\n} from 'node:crypto'\n\nimport type { FetchJson } from './jwks.js'\n\nimport {\n DEFAULT_GITHUB_OIDC_ISSUER,\n DEFAULT_MAX_SKEW_SECONDS,\n} from '../constants.js'\nimport {\n fetchJwks,\n findJwkByKid,\n getGithubOidcJwksUrl,\n} from './jwks.js'\nimport { decodeJwt } from './jwt.js'\n\nexport type GitHubOidcErrorCode =\n | 'oidc_expired'\n | 'oidc_invalid_audience'\n | 'oidc_invalid_issuer'\n | 'oidc_invalid_token'\n | 'oidc_jwks_unavailable'\n | 'oidc_missing_claim'\n | 'oidc_missing_jti'\n | 'oidc_not_yet_valid'\n | 'oidc_owner_not_allowed'\n | 'oidc_pull_request_not_allowed'\n | 'oidc_ref_not_allowed'\n | 'oidc_repository_not_allowed'\n | 'oidc_workflow_not_allowed'\n\nexport type GitHubOidcClaims = {\n actor?: string\n aud: string | string[]\n environment?: string\n event_name?: string\n exp: number\n iat: number\n iss: string\n job_workflow_ref?: string\n jti: string\n nbf?: number\n ref: string\n repository: string\n repository_owner: string\n sha?: string\n sub: string\n workflow?: string\n workflow_ref?: string\n}\n\nexport type GitHubOidcTrustedSource = {\n limitRepos?: boolean\n owner: string\n repositories?: string[]\n}\n\nexport type GitHubOidcVerifyConfig = {\n allowedRefs?: string[]\n allowedWorkflowRefs?: string[]\n allowPullRequests?: boolean\n audience: string\n enforceWorkflowRefs?: boolean\n issuer?: string\n jwksUrl?: string\n maxSkewSeconds?: number\n trustedSources: GitHubOidcTrustedSource[]\n}\n\nexport type VerifiedGitHubOidcToken = {\n claims: GitHubOidcClaims\n expiresAt: Date\n keyId: string\n}\n\nexport type VerifyGitHubOidcTokenResult =\n | {\n code: GitHubOidcErrorCode\n message: string\n ok: false\n }\n | {\n ok: true\n token: VerifiedGitHubOidcToken\n }\n\nconst isString = (value: unknown): value is string =>\n typeof value === 'string' && value.trim() !== ''\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(isString)\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\nconst getStringClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): string | undefined => {\n const value = payload[claim]\n\n return isString(value) ? value : undefined\n}\n\nconst getNumberClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): number | undefined => {\n const value = payload[claim]\n\n return isNumber(value) ? value : undefined\n}\n\nconst getAudienceClaim = (\n payload: Record<string, unknown>,\n): string | string[] | undefined => {\n const value = payload.aud\n\n if (isString(value) || isStringArray(value)) {\n return value\n }\n\n return undefined\n}\n\nconst toClaims = (\n payload: Record<string, unknown>,\n): GitHubOidcClaims | undefined => {\n const aud = getAudienceClaim(payload)\n const exp = getNumberClaim(payload, 'exp')\n const iat = getNumberClaim(payload, 'iat')\n const iss = getStringClaim(payload, 'iss')\n const jti = getStringClaim(payload, 'jti')\n const ref = getStringClaim(payload, 'ref')\n const repository = getStringClaim(payload, 'repository')\n const repositoryOwner = getStringClaim(payload, 'repository_owner')\n const sub = getStringClaim(payload, 'sub')\n\n if (\n !aud ||\n exp === undefined ||\n iat === undefined ||\n !iss ||\n !jti ||\n !ref ||\n !repository ||\n !repositoryOwner ||\n !sub\n ) {\n return undefined\n }\n\n return {\n actor: getStringClaim(payload, 'actor'),\n aud,\n environment: getStringClaim(payload, 'environment'),\n event_name: getStringClaim(payload, 'event_name'),\n exp,\n iat,\n iss,\n job_workflow_ref: getStringClaim(payload, 'job_workflow_ref'),\n jti,\n nbf: getNumberClaim(payload, 'nbf'),\n ref,\n repository,\n repository_owner: repositoryOwner,\n sha: getStringClaim(payload, 'sha'),\n sub,\n workflow: getStringClaim(payload, 'workflow'),\n workflow_ref: getStringClaim(payload, 'workflow_ref'),\n }\n}\n\nconst issue = (\n code: GitHubOidcErrorCode,\n message: string,\n): VerifyGitHubOidcTokenResult => ({\n code,\n message,\n ok: false,\n})\n\nconst includesIfConfigured = (\n allowed: string[] | undefined,\n value: string | undefined,\n): boolean => {\n if (!allowed || allowed.length === 0) {\n return true\n }\n\n return value !== undefined && allowed.includes(value)\n}\n\nconst audienceMatches = (\n audience: string | string[],\n expected: string,\n): boolean =>\n Array.isArray(audience) ? audience.includes(expected) : audience === expected\n\nconst getRepositoryName = (repository: string): string => {\n const [, name] = repository.split('/', 2)\n\n return name ?? repository\n}\n\nconst repositoryMatches = ({\n allowed,\n owner,\n repository,\n}: {\n allowed: string\n owner: string\n repository: string\n}): boolean => {\n const normalized = allowed.trim()\n\n if (!normalized) {\n return false\n }\n\n return normalized.includes('/')\n ? normalized.toLowerCase() === repository.toLowerCase()\n : `${owner}/${normalized}`.toLowerCase() === repository.toLowerCase()\n}\n\nconst findTrustedSource = ({\n repository,\n repositoryOwner,\n trustedSources,\n}: {\n repository: string\n repositoryOwner: string\n trustedSources: GitHubOidcTrustedSource[]\n}): GitHubOidcTrustedSource | undefined =>\n trustedSources.find((source) => {\n if (source.owner.toLowerCase() !== repositoryOwner.toLowerCase()) {\n return false\n }\n\n if (source.limitRepos !== true) {\n return true\n }\n\n return (source.repositories ?? []).some((allowedRepository) =>\n repositoryMatches({\n allowed: allowedRepository,\n owner: source.owner,\n repository,\n }),\n )\n })\n\nconst verifyJwtSignature = ({\n jwk,\n signature,\n signingInput,\n}: {\n jwk: Record<string, unknown>\n signature: Buffer\n signingInput: string\n}): boolean => {\n try {\n const publicKey = createPublicKey({\n format: 'jwk',\n key: jwk as JsonWebKey,\n })\n\n return verify(\n 'RSA-SHA256',\n Buffer.from(signingInput, 'utf8'),\n publicKey,\n signature,\n )\n } catch {\n return false\n }\n}\n\nexport const verifyGitHubOidcToken = async ({\n config,\n fetchJson,\n now = new Date(),\n token,\n}: {\n config: GitHubOidcVerifyConfig\n fetchJson?: FetchJson\n now?: Date\n token: string\n}): Promise<VerifyGitHubOidcTokenResult> => {\n const decoded = decodeJwt(token)\n\n if (!decoded) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is malformed.')\n }\n\n if (decoded.header.alg !== 'RS256') {\n return issue('oidc_invalid_token', 'GitHub OIDC token must use RS256.')\n }\n\n if (!isString(decoded.header.kid)) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is missing kid.')\n }\n\n const issuer = config.issuer ?? DEFAULT_GITHUB_OIDC_ISSUER\n let jwksUrl: string\n\n try {\n jwksUrl = await getGithubOidcJwksUrl({\n fetchJson,\n issuer,\n jwksUrl: config.jwksUrl,\n })\n const jwks = await fetchJwks({\n fetchJson,\n now,\n url: jwksUrl,\n })\n const jwk = findJwkByKid({\n jwks,\n kid: decoded.header.kid,\n })\n\n if (\n !jwk ||\n !verifyJwtSignature({\n jwk,\n signature: decoded.signature,\n signingInput: decoded.signingInput,\n })\n ) {\n return issue('oidc_invalid_token', 'GitHub OIDC token signature is invalid.')\n }\n } catch {\n return issue('oidc_jwks_unavailable', 'GitHub OIDC signing keys are unavailable.')\n }\n\n if (!isString(decoded.payload.jti)) {\n return issue('oidc_missing_jti', 'GitHub OIDC token is missing jti.')\n }\n\n const claims = toClaims(decoded.payload)\n\n if (!claims) {\n return issue('oidc_missing_claim', 'GitHub OIDC token is missing a required claim.')\n }\n\n if (claims.iss !== issuer) {\n return issue('oidc_invalid_issuer', 'GitHub OIDC token issuer is not allowed.')\n }\n\n if (!audienceMatches(claims.aud, config.audience)) {\n return issue('oidc_invalid_audience', 'GitHub OIDC token audience is not allowed.')\n }\n\n const maxSkewSeconds = config.maxSkewSeconds ?? DEFAULT_MAX_SKEW_SECONDS\n const nowSeconds = now.getTime() / 1000\n\n if (claims.exp + maxSkewSeconds < nowSeconds) {\n return issue('oidc_expired', 'GitHub OIDC token has expired.')\n }\n\n if (claims.nbf !== undefined && claims.nbf - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token is not valid yet.')\n }\n\n if (claims.iat - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token was issued in the future.')\n }\n\n const trustedSources = config.trustedSources ?? []\n\n if (trustedSources.length === 0) {\n return issue(\n 'oidc_repository_not_allowed',\n 'GitHub OIDC auth requires a trusted GitHub owner.',\n )\n }\n\n const trustedSource = findTrustedSource({\n repository: claims.repository,\n repositoryOwner: claims.repository_owner,\n trustedSources,\n })\n\n if (!trustedSource) {\n const matchingOwner = trustedSources.find(\n (source) =>\n source.owner.toLowerCase() === claims.repository_owner.toLowerCase(),\n )\n\n if (matchingOwner) {\n return issue(\n 'oidc_repository_not_allowed',\n `GitHub OIDC token repository \"${claims.repository}\" is not trusted for owner \"${claims.repository_owner}\".`,\n )\n }\n\n return issue(\n 'oidc_owner_not_allowed',\n `GitHub OIDC token repository owner \"${claims.repository_owner}\" is not trusted.`,\n )\n }\n\n const repositoryName = getRepositoryName(claims.repository)\n\n if (!includesIfConfigured(config.allowedRefs, claims.ref)) {\n return issue(\n 'oidc_ref_not_allowed',\n `GitHub OIDC token ref \"${claims.ref}\" is not allowed for \"${repositoryName}\".`,\n )\n }\n\n const workflowRef = claims.workflow_ref ?? claims.job_workflow_ref\n\n if (\n config.enforceWorkflowRefs === true &&\n (config.allowedWorkflowRefs?.length ?? 0) === 0\n ) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'Advanced workflow security is enabled but no workflow refs are trusted.',\n )\n }\n\n if (\n config.enforceWorkflowRefs === true &&\n !includesIfConfigured(config.allowedWorkflowRefs, workflowRef)\n ) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'GitHub OIDC token workflow ref is not allowed.',\n )\n }\n\n if (claims.event_name === 'pull_request' && config.allowPullRequests !== true) {\n return issue(\n 'oidc_pull_request_not_allowed',\n 'GitHub OIDC pull request events are not allowed.',\n )\n }\n\n return {\n ok: true,\n token: {\n claims,\n expiresAt: new Date(claims.exp * 1000),\n keyId: `github-oidc:${claims.repository}`,\n },\n }\n}\n"],"names":["createPublicKey","verify","DEFAULT_GITHUB_OIDC_ISSUER","DEFAULT_MAX_SKEW_SECONDS","fetchJwks","findJwkByKid","getGithubOidcJwksUrl","decodeJwt","isString","value","trim","isStringArray","Array","isArray","every","isNumber","Number","isFinite","getStringClaim","payload","claim","undefined","getNumberClaim","getAudienceClaim","aud","toClaims","exp","iat","iss","jti","ref","repository","repositoryOwner","sub","actor","environment","event_name","job_workflow_ref","nbf","repository_owner","sha","workflow","workflow_ref","issue","code","message","ok","includesIfConfigured","allowed","length","includes","audienceMatches","audience","expected","getRepositoryName","name","split","repositoryMatches","owner","normalized","toLowerCase","findTrustedSource","trustedSources","find","source","limitRepos","repositories","some","allowedRepository","verifyJwtSignature","jwk","signature","signingInput","publicKey","format","key","Buffer","from","verifyGitHubOidcToken","config","fetchJson","now","Date","token","decoded","header","alg","kid","issuer","jwksUrl","jwks","url","claims","maxSkewSeconds","nowSeconds","getTime","trustedSource","matchingOwner","repositoryName","allowedRefs","workflowRef","enforceWorkflowRefs","allowedWorkflowRefs","allowPullRequests","expiresAt","keyId"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  export { buildCanonicalSigningString, getCanonicalPathFromRequestUrl, } from './canonical.js';
2
2
  export type { CanonicalSigningStringInput } from './canonical.js';
3
3
  export { verifyGitHubOidcToken } from './githubOidc.js';
4
- export type { GitHubOidcClaims, GitHubOidcErrorCode, VerifiedGitHubOidcToken, VerifyGitHubOidcTokenResult, } from './githubOidc.js';
4
+ export type { GitHubOidcClaims, GitHubOidcErrorCode, GitHubOidcTrustedSource, GitHubOidcVerifyConfig, VerifiedGitHubOidcToken, VerifyGitHubOidcTokenResult, } from './githubOidc.js';
5
5
  export { extractSyncRequestHeaders, syncHeaderNames } from './headers.js';
6
6
  export type { ExtractSyncHeadersResult, SyncRequestHeaders, } from './headers.js';
7
7
  export type { FetchJson } from './jwks.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/security/index.ts"],"sourcesContent":["export {\n buildCanonicalSigningString,\n getCanonicalPathFromRequestUrl,\n} from './canonical.js'\nexport type { CanonicalSigningStringInput } from './canonical.js'\nexport { verifyGitHubOidcToken } from './githubOidc.js'\nexport type {\n GitHubOidcClaims,\n GitHubOidcErrorCode,\n VerifiedGitHubOidcToken,\n VerifyGitHubOidcTokenResult,\n} from './githubOidc.js'\nexport { extractSyncRequestHeaders, syncHeaderNames } from './headers.js'\nexport type {\n ExtractSyncHeadersResult,\n SyncRequestHeaders,\n} from './headers.js'\nexport type { FetchJson } from './jwks.js'\nexport {\n decodeJwt,\n toBase64Url,\n} from './jwt.js'\nexport type { DecodedJwt } from './jwt.js'\nexport {\n assertNonceNotReplayed,\n storeAcceptedNonce,\n} from './nonce.js'\nexport type { NoncePayloadOperations } from './nonce.js'\nexport { signDocsSyncRequest } from './sign.js'\nexport type {\n SignDocsSyncRequestOptions,\n SignedDocsSyncRequest,\n} from './sign.js'\nexport {\n validateTimestampSkew,\n verifyBodySha256,\n verifyEd25519Signature,\n} from './verify.js'\nexport type {\n ValidateTimestampResult,\n VerifyBodyHashResult,\n} from './verify.js'\n"],"names":["buildCanonicalSigningString","getCanonicalPathFromRequestUrl","verifyGitHubOidcToken","extractSyncRequestHeaders","syncHeaderNames","decodeJwt","toBase64Url","assertNonceNotReplayed","storeAcceptedNonce","signDocsSyncRequest","validateTimestampSkew","verifyBodySha256","verifyEd25519Signature"],"mappings":"AAAA,SACEA,2BAA2B,EAC3BC,8BAA8B,QACzB,iBAAgB;AAEvB,SAASC,qBAAqB,QAAQ,kBAAiB;AAOvD,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,eAAc;AAMzE,SACEC,SAAS,EACTC,WAAW,QACN,WAAU;AAEjB,SACEC,sBAAsB,EACtBC,kBAAkB,QACb,aAAY;AAEnB,SAASC,mBAAmB,QAAQ,YAAW;AAK/C,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,sBAAsB,QACjB,cAAa"}
1
+ {"version":3,"sources":["../../src/security/index.ts"],"sourcesContent":["export {\n buildCanonicalSigningString,\n getCanonicalPathFromRequestUrl,\n} from './canonical.js'\nexport type { CanonicalSigningStringInput } from './canonical.js'\nexport { verifyGitHubOidcToken } from './githubOidc.js'\nexport type {\n GitHubOidcClaims,\n GitHubOidcErrorCode,\n GitHubOidcTrustedSource,\n GitHubOidcVerifyConfig,\n VerifiedGitHubOidcToken,\n VerifyGitHubOidcTokenResult,\n} from './githubOidc.js'\nexport { extractSyncRequestHeaders, syncHeaderNames } from './headers.js'\nexport type {\n ExtractSyncHeadersResult,\n SyncRequestHeaders,\n} from './headers.js'\nexport type { FetchJson } from './jwks.js'\nexport {\n decodeJwt,\n toBase64Url,\n} from './jwt.js'\nexport type { DecodedJwt } from './jwt.js'\nexport {\n assertNonceNotReplayed,\n storeAcceptedNonce,\n} from './nonce.js'\nexport type { NoncePayloadOperations } from './nonce.js'\nexport { signDocsSyncRequest } from './sign.js'\nexport type {\n SignDocsSyncRequestOptions,\n SignedDocsSyncRequest,\n} from './sign.js'\nexport {\n validateTimestampSkew,\n verifyBodySha256,\n verifyEd25519Signature,\n} from './verify.js'\nexport type {\n ValidateTimestampResult,\n VerifyBodyHashResult,\n} from './verify.js'\n"],"names":["buildCanonicalSigningString","getCanonicalPathFromRequestUrl","verifyGitHubOidcToken","extractSyncRequestHeaders","syncHeaderNames","decodeJwt","toBase64Url","assertNonceNotReplayed","storeAcceptedNonce","signDocsSyncRequest","validateTimestampSkew","verifyBodySha256","verifyEd25519Signature"],"mappings":"AAAA,SACEA,2BAA2B,EAC3BC,8BAA8B,QACzB,iBAAgB;AAEvB,SAASC,qBAAqB,QAAQ,kBAAiB;AASvD,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,eAAc;AAMzE,SACEC,SAAS,EACTC,WAAW,QACN,WAAU;AAEjB,SACEC,sBAAsB,EACtBC,kBAAkB,QACb,aAAY;AAEnB,SAASC,mBAAmB,QAAQ,YAAW;AAK/C,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,sBAAsB,QACjB,cAAa"}
@@ -16,9 +16,9 @@ The docs source lives in `{{docsRoot}}` unless the user says otherwise. Edit Mar
16
16
  - Run validation before finishing docs edits.
17
17
  - Treat sync and publishing as CMS/server-owned. The request may ask; Payload
18
18
  docs sets and plugin config decide.
19
- - Do not hardcode new docs sources into plugin config. A docs source should map
20
- to a Payload Admin docs set with `sourceId`, `sourceRoot`, `routeBase`, and
21
- source-specific auth policy.
19
+ - Do not hardcode new docs packages into plugin config. A docs package should map
20
+ to a Payload Admin docs set slug. Routes are derived from groups and slugs;
21
+ trust belongs in global Keys and Trusted records.
22
22
 
23
23
  ## AI Markdown Export Manifest
24
24
 
@@ -135,7 +135,6 @@ Only push when the user asks for an upload and provides endpoint/auth context. P
135
135
  --endpoint "$DOCS_SYNC_ENDPOINT" \
136
136
  --source main-docs \
137
137
  --github-oidc \
138
- --oidc-audience payload-markdown-docs \
139
138
  --dry-run
140
139
  ```
141
140
 
@@ -44,7 +44,6 @@ jobs:
44
44
  --endpoint "$DOCS_SYNC_ENDPOINT" \
45
45
  --source main-docs \
46
46
  --github-oidc \
47
- --oidc-audience payload-markdown-docs \
48
47
  --dry-run
49
48
  env:
50
49
  DOCS_SYNC_ENDPOINT: ${{ secrets.DOCS_SYNC_ENDPOINT }}
@@ -56,7 +55,6 @@ jobs:
56
55
  --endpoint "$DOCS_SYNC_ENDPOINT" \
57
56
  --source main-docs \
58
57
  --github-oidc \
59
- --oidc-audience payload-markdown-docs \
60
58
  --sync \
61
59
  --publish
62
60
  env:
@@ -18,11 +18,5 @@ Per-doc overrides live on generated docs records:
18
18
 
19
19
  - `navTitle`
20
20
  - `hideFromNav`
21
- - `theme`
22
- - `heroEyebrow`
23
- - `heroTitle`
24
- - `heroDescription`
25
- - `seoTitle`
26
- - `seoDescription`
27
21
 
28
22
  Inline override editing from the manager is not implemented yet. Open the generated docs record to edit overrides.
@@ -8,7 +8,8 @@ Docs groups reserve namespaces such as `/plugins` or `/internal/tools`.
8
8
 
9
9
  ## Docs Sets
10
10
 
11
- Docs sets represent one documentation site. They map a `sourceId` to a server-owned `routeBase`, such as:
11
+ Docs sets represent one documentation site. Their route base is derived from an
12
+ optional group plus the docs set slug, such as:
12
13
 
13
14
  ```text
14
15
  /plugins/payload-markdown-docs
@@ -5,7 +5,8 @@ The sync workflow is authenticated and server-owned.
5
5
  Important concepts:
6
6
 
7
7
  - `source.id` maps to a Payload Admin docs set.
8
- - The docs set owns the route base and source-specific auth policy.
8
+ - The docs set slug and optional group determine the route base.
9
+ - Global Keys and Trusted records own reusable authentication trust.
9
10
  - The manifest does not choose target collections or fields.
10
11
  - `sync.allowWrites: true` is required for `mode: "sync"`.
11
12
  - `sync.allowPublish: true` and `target.enableDrafts: true` are required for publishing.
@@ -25,12 +26,13 @@ Ed25519 signed pushes verify:
25
26
  GitHub OIDC pushes verify:
26
27
 
27
28
  - bearer JWT signature through GitHub JWKS
28
- - issuer and audience
29
- - repository, owner, ref, workflow, and environment allowlists from the docs set
29
+ - docs set slug as audience
30
+ - trusted owner/repository and docs set branch
31
+ - advanced workflow refs only when explicitly enabled on the docs set
30
32
  - pull request policy
31
33
  - JWT `jti` replay protection
32
34
  - body SHA-256
33
35
  - manifest validity
34
36
 
35
- Do not bypass failed auth or body verification. Fix the key, endpoint, docs set,
36
- source id, body, or server config.
37
+ Do not bypass failed auth or body verification. Fix the key, endpoint, docs set
38
+ slug, body, or server config.
@@ -6,12 +6,11 @@ Check key id, private key, endpoint pathname, timestamp, nonce, and exact body s
6
6
 
7
7
  ## OIDC invalid token
8
8
 
9
- Check that the workflow uses `--github-oidc`, grants `id-token: write`, and requests the configured audience.
9
+ Check that the workflow uses `--github-oidc`, grants `id-token: write`, and uses a source matching the docs set slug.
10
10
 
11
11
  ## OIDC repository or ref not allowed
12
12
 
13
- Check the docs set OIDC allowlists. The request may ask; the CMS decides which
14
- repository and ref are trusted.
13
+ Check `Docs Globals > Trusted` for owner/repository trust and the docs set branch for ref trust.
15
14
 
16
15
  ## OIDC replay
17
16
 
@@ -27,7 +26,7 @@ Generate a fresh request. Do not reuse signed headers.
27
26
 
28
27
  ## Source not allowed
29
28
 
30
- Create or update a docs set with the expected `sourceId`.
29
+ Create or update a docs set with the expected slug.
31
30
 
32
31
  ## Publish disabled
33
32
 
@@ -30,7 +30,6 @@ Dry-run upload with GitHub OIDC:
30
30
  --endpoint "$DOCS_SYNC_ENDPOINT" \
31
31
  --source main-docs \
32
32
  --github-oidc \
33
- --oidc-audience payload-markdown-docs \
34
33
  --dry-run
35
34
  ```
36
35
 
@@ -7,7 +7,6 @@ export type DocsManifestSource = {
7
7
  commit?: string;
8
8
  id: string;
9
9
  repository?: string;
10
- root?: string;
11
10
  };
12
11
  export type DocsManifestFile = {
13
12
  content: string;
@@ -44,7 +43,7 @@ export type DocsManifestInputFile = {
44
43
  content: string;
45
44
  path: string;
46
45
  };
47
- export declare const buildDocsManifest: ({ aiExport, branch, commit, deleteBehavior, files, mode, publish, repository, root, sourceId, }: {
46
+ export declare const buildDocsManifest: ({ aiExport, branch, commit, deleteBehavior, files, mode, publish, repository, sourceId, }: {
48
47
  aiExport?: DocsAiExportManifest;
49
48
  branch?: string;
50
49
  commit?: string;
@@ -53,6 +52,5 @@ export declare const buildDocsManifest: ({ aiExport, branch, commit, deleteBehav
53
52
  mode?: DocsSyncMode;
54
53
  publish?: boolean;
55
54
  repository?: string;
56
- root?: string;
57
55
  sourceId: string;
58
56
  }) => DocsManifest;
@@ -1,5 +1,5 @@
1
1
  import { sha256Hex } from './hash.js';
2
- export const buildDocsManifest = ({ aiExport, branch, commit, deleteBehavior, files, mode, publish, repository, root, sourceId })=>({
2
+ export const buildDocsManifest = ({ aiExport, branch, commit, deleteBehavior, files, mode, publish, repository, sourceId })=>({
3
3
  aiExport,
4
4
  deleteBehavior,
5
5
  files: files.map((file)=>({
@@ -12,8 +12,7 @@ export const buildDocsManifest = ({ aiExport, branch, commit, deleteBehavior, fi
12
12
  id: sourceId,
13
13
  branch,
14
14
  commit,
15
- repository,
16
- root
15
+ repository
17
16
  },
18
17
  version: 1
19
18
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sync/manifest.ts"],"sourcesContent":["import type { DocsAiExportManifest } from './aiExportManifest.js'\nimport type { DocsFrontmatter } from './frontmatter.js'\n\nimport { sha256Hex } from './hash.js'\n\nexport type DocsSyncMode = 'dry-run' | 'sync'\n\nexport type DocsDeleteBehavior = 'archive' | 'delete' | 'draft' | 'ignore'\n\nexport type DocsManifestSource = {\n branch?: string\n commit?: string\n id: string\n repository?: string\n root?: string\n}\n\nexport type DocsManifestFile = {\n content: string\n path: string\n sha256?: string\n}\n\nexport type DocsManifest = {\n aiExport?: DocsAiExportManifest\n deleteBehavior?: DocsDeleteBehavior\n files: DocsManifestFile[]\n mode?: DocsSyncMode\n publish?: boolean\n source: DocsManifestSource\n version: 1\n}\n\nexport type ValidatedDocsManifestFile = {\n content: string\n frontmatter: DocsFrontmatter\n path: string\n route: string\n sha256: string\n title: string\n}\n\nexport type ValidatedDocsManifest = {\n aiExport?: DocsAiExportManifest\n deleteBehavior: DocsDeleteBehavior\n files: ValidatedDocsManifestFile[]\n mode: DocsSyncMode\n publish: boolean\n source: DocsManifestSource\n version: 1\n}\n\nexport type DocsManifestInputFile = {\n content: string\n path: string\n}\n\nexport const buildDocsManifest = ({\n aiExport,\n branch,\n commit,\n deleteBehavior,\n files,\n mode,\n publish,\n repository,\n root,\n sourceId,\n}: {\n aiExport?: DocsAiExportManifest\n branch?: string\n commit?: string\n deleteBehavior?: DocsDeleteBehavior\n files: DocsManifestInputFile[]\n mode?: DocsSyncMode\n publish?: boolean\n repository?: string\n root?: string\n sourceId: string\n}): DocsManifest => ({\n aiExport,\n deleteBehavior,\n files: files.map((file) => ({\n ...file,\n sha256: sha256Hex(file.content),\n })),\n mode,\n publish,\n source: {\n id: sourceId,\n branch,\n commit,\n repository,\n root,\n },\n version: 1,\n})\n"],"names":["sha256Hex","buildDocsManifest","aiExport","branch","commit","deleteBehavior","files","mode","publish","repository","root","sourceId","map","file","sha256","content","source","id","version"],"mappings":"AAGA,SAASA,SAAS,QAAQ,YAAW;AAsDrC,OAAO,MAAMC,oBAAoB,CAAC,EAChCC,QAAQ,EACRC,MAAM,EACNC,MAAM,EACNC,cAAc,EACdC,KAAK,EACLC,IAAI,EACJC,OAAO,EACPC,UAAU,EACVC,IAAI,EACJC,QAAQ,EAYT,GAAoB,CAAA;QACnBT;QACAG;QACAC,OAAOA,MAAMM,GAAG,CAAC,CAACC,OAAU,CAAA;gBAC1B,GAAGA,IAAI;gBACPC,QAAQd,UAAUa,KAAKE,OAAO;YAChC,CAAA;QACAR;QACAC;QACAQ,QAAQ;YACNC,IAAIN;YACJR;YACAC;YACAK;YACAC;QACF;QACAQ,SAAS;IACX,CAAA,EAAE"}
1
+ {"version":3,"sources":["../../src/sync/manifest.ts"],"sourcesContent":["import type { DocsAiExportManifest } from './aiExportManifest.js'\nimport type { DocsFrontmatter } from './frontmatter.js'\n\nimport { sha256Hex } from './hash.js'\n\nexport type DocsSyncMode = 'dry-run' | 'sync'\n\nexport type DocsDeleteBehavior = 'archive' | 'delete' | 'draft' | 'ignore'\n\nexport type DocsManifestSource = {\n branch?: string\n commit?: string\n id: string\n repository?: string\n}\n\nexport type DocsManifestFile = {\n content: string\n path: string\n sha256?: string\n}\n\nexport type DocsManifest = {\n aiExport?: DocsAiExportManifest\n deleteBehavior?: DocsDeleteBehavior\n files: DocsManifestFile[]\n mode?: DocsSyncMode\n publish?: boolean\n source: DocsManifestSource\n version: 1\n}\n\nexport type ValidatedDocsManifestFile = {\n content: string\n frontmatter: DocsFrontmatter\n path: string\n route: string\n sha256: string\n title: string\n}\n\nexport type ValidatedDocsManifest = {\n aiExport?: DocsAiExportManifest\n deleteBehavior: DocsDeleteBehavior\n files: ValidatedDocsManifestFile[]\n mode: DocsSyncMode\n publish: boolean\n source: DocsManifestSource\n version: 1\n}\n\nexport type DocsManifestInputFile = {\n content: string\n path: string\n}\n\nexport const buildDocsManifest = ({\n aiExport,\n branch,\n commit,\n deleteBehavior,\n files,\n mode,\n publish,\n repository,\n sourceId,\n}: {\n aiExport?: DocsAiExportManifest\n branch?: string\n commit?: string\n deleteBehavior?: DocsDeleteBehavior\n files: DocsManifestInputFile[]\n mode?: DocsSyncMode\n publish?: boolean\n repository?: string\n sourceId: string\n}): DocsManifest => ({\n aiExport,\n deleteBehavior,\n files: files.map((file) => ({\n ...file,\n sha256: sha256Hex(file.content),\n })),\n mode,\n publish,\n source: {\n id: sourceId,\n branch,\n commit,\n repository,\n },\n version: 1,\n})\n"],"names":["sha256Hex","buildDocsManifest","aiExport","branch","commit","deleteBehavior","files","mode","publish","repository","sourceId","map","file","sha256","content","source","id","version"],"mappings":"AAGA,SAASA,SAAS,QAAQ,YAAW;AAqDrC,OAAO,MAAMC,oBAAoB,CAAC,EAChCC,QAAQ,EACRC,MAAM,EACNC,MAAM,EACNC,cAAc,EACdC,KAAK,EACLC,IAAI,EACJC,OAAO,EACPC,UAAU,EACVC,QAAQ,EAWT,GAAoB,CAAA;QACnBR;QACAG;QACAC,OAAOA,MAAMK,GAAG,CAAC,CAACC,OAAU,CAAA;gBAC1B,GAAGA,IAAI;gBACPC,QAAQb,UAAUY,KAAKE,OAAO;YAChC,CAAA;QACAP;QACAC;QACAO,QAAQ;YACNC,IAAIN;YACJP;YACAC;YACAK;QACF;QACAQ,SAAS;IACX,CAAA,EAAE"}
@@ -47,8 +47,7 @@ const validateSource = ({ allowedSourceIds, source })=>{
47
47
  id: source.id,
48
48
  branch: typeof source.branch === 'string' ? source.branch : undefined,
49
49
  commit: typeof source.commit === 'string' ? source.commit : undefined,
50
- repository: typeof source.repository === 'string' ? source.repository : undefined,
51
- root: typeof source.root === 'string' ? source.root : undefined
50
+ repository: typeof source.repository === 'string' ? source.repository : undefined
52
51
  }
53
52
  };
54
53
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sync/validate.ts"],"sourcesContent":["import type {\n DocsDeleteBehavior,\n DocsManifest,\n DocsManifestFile,\n DocsManifestSource,\n DocsSyncMode,\n ValidatedDocsManifest,\n ValidatedDocsManifestFile,\n} from './manifest.js'\n\nimport {\n DEFAULT_DOCS_ROUTE_BASE,\n DEFAULT_MAX_DOCS_FILE_BYTES,\n DEFAULT_MAX_DOCS_FILES,\n DEFAULT_MAX_DOCS_TOTAL_BYTES,\n} from '../constants.js'\nimport { validateDocsAiExportManifest } from './aiExportManifest.js'\nimport {\n parseDocsFrontmatter,\n resolveDocsTitle,\n} from './frontmatter.js'\nimport { sha256Hex } from './hash.js'\nimport { deriveRouteFromSourcePath, normalizeDocsPath } from './paths.js'\n\nexport type DocsValidationErrorCode =\n | 'duplicate_existing_path'\n | 'duplicate_path'\n | 'empty_manifest'\n | 'file_too_large'\n | 'invalid_ai_export_manifest'\n | 'invalid_delete_behavior'\n | 'invalid_frontmatter'\n | 'invalid_hash'\n | 'invalid_manifest'\n | 'invalid_mode'\n | 'invalid_path'\n | 'invalid_source'\n | 'invalid_version'\n | 'manifest_too_large'\n | 'missing_ai_export_order_path'\n | 'non_markdown_file'\n | 'path_traversal'\n | 'too_many_files'\n\nexport type DocsValidationIssue = {\n code: DocsValidationErrorCode\n message: string\n path?: string\n}\n\nexport type DocsValidationResult<T = unknown> =\n | {\n data: T\n issues: DocsValidationIssue[]\n ok: true\n warnings: DocsValidationIssue[]\n }\n | {\n issues: DocsValidationIssue[]\n ok: false\n warnings: DocsValidationIssue[]\n }\n\nexport type DocsValidationOptions = {\n allowedSourceIds?: string[]\n maxFileBytes?: number\n maxFiles?: number\n maxTotalBytes?: number\n routeBase?: string\n}\n\nconst syncModes = new Set<DocsSyncMode>(['dry-run', 'sync'])\nconst deleteBehaviors = new Set<DocsDeleteBehavior>([\n 'archive',\n 'delete',\n 'draft',\n 'ignore',\n])\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst createIssue = ({\n code,\n message,\n path,\n}: DocsValidationIssue): DocsValidationIssue => ({\n code,\n message,\n path,\n})\n\nconst byteLength = (content: string): number => Buffer.byteLength(content, 'utf8')\n\nconst validateSource = ({\n allowedSourceIds,\n source,\n}: {\n allowedSourceIds?: string[]\n source: unknown\n}): {\n issues: DocsValidationIssue[]\n source?: DocsManifestSource\n} => {\n if (!isRecord(source) || typeof source.id !== 'string' || source.id.trim() === '') {\n return {\n issues: [\n createIssue({\n code: 'invalid_source',\n message: 'Manifest source.id is required.',\n }),\n ],\n }\n }\n\n if (allowedSourceIds && !allowedSourceIds.includes(source.id)) {\n return {\n issues: [\n createIssue({\n code: 'invalid_source',\n message: `Manifest source.id \"${source.id}\" is not allowed.`,\n }),\n ],\n }\n }\n\n return {\n issues: [],\n source: {\n id: source.id,\n branch: typeof source.branch === 'string' ? source.branch : undefined,\n commit: typeof source.commit === 'string' ? source.commit : undefined,\n repository: typeof source.repository === 'string' ? source.repository : undefined,\n root: typeof source.root === 'string' ? source.root : undefined,\n },\n }\n}\n\nconst validateMode = (mode: unknown): {\n issues: DocsValidationIssue[]\n mode: DocsSyncMode\n} => {\n if (mode === undefined) {\n return {\n issues: [],\n mode: 'dry-run',\n }\n }\n\n if (syncModes.has(mode as DocsSyncMode)) {\n return {\n issues: [],\n mode: mode as DocsSyncMode,\n }\n }\n\n return {\n issues: [\n createIssue({\n code: 'invalid_mode',\n message: 'Manifest mode must be \"dry-run\" or \"sync\".',\n }),\n ],\n mode: 'dry-run',\n }\n}\n\nconst validateDeleteBehavior = (deleteBehavior: unknown): {\n deleteBehavior: DocsDeleteBehavior\n issues: DocsValidationIssue[]\n} => {\n if (deleteBehavior === undefined) {\n return {\n deleteBehavior: 'archive',\n issues: [],\n }\n }\n\n if (deleteBehaviors.has(deleteBehavior as DocsDeleteBehavior)) {\n return {\n deleteBehavior: deleteBehavior as DocsDeleteBehavior,\n issues: [],\n }\n }\n\n return {\n deleteBehavior: 'archive',\n issues: [\n createIssue({\n code: 'invalid_delete_behavior',\n message: 'Manifest deleteBehavior must be archive, delete, draft, or ignore.',\n }),\n ],\n }\n}\n\nconst validateManifestFile = ({\n file,\n maxFileBytes,\n routeBase,\n}: {\n file: unknown\n maxFileBytes: number\n routeBase: string\n}): {\n fileBytes: number\n issues: DocsValidationIssue[]\n normalizedPath?: string\n validatedFile?: ValidatedDocsManifestFile\n warnings: DocsValidationIssue[]\n} => {\n const issues: DocsValidationIssue[] = []\n const warnings: DocsValidationIssue[] = []\n\n if (!isRecord(file)) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest file entries must be objects.',\n }),\n ],\n warnings,\n }\n }\n\n const path = typeof file.path === 'string' ? file.path : undefined\n const content = typeof file.content === 'string' ? file.content : undefined\n\n if (!path || content === undefined) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest file entries require string path and content.',\n path,\n }),\n ],\n warnings,\n }\n }\n\n const normalizedPath = normalizeDocsPath(path)\n\n if (!normalizedPath.ok) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: normalizedPath.code,\n message: normalizedPath.message,\n path,\n }),\n ],\n warnings,\n }\n }\n\n const fileBytes = byteLength(content)\n\n if (fileBytes > maxFileBytes) {\n issues.push(\n createIssue({\n code: 'file_too_large',\n message: `File exceeds maximum size of ${maxFileBytes} bytes.`,\n path: normalizedPath.path,\n }),\n )\n }\n\n const computedHash = sha256Hex(content)\n\n if (\n file.sha256 !== undefined &&\n (typeof file.sha256 !== 'string' ||\n !/^[a-f0-9]{64}$/i.test(file.sha256) ||\n file.sha256.toLowerCase() !== computedHash)\n ) {\n issues.push(\n createIssue({\n code: 'invalid_hash',\n message: 'Manifest file sha256 does not match content.',\n path: normalizedPath.path,\n }),\n )\n }\n\n const parsedFrontmatter = parseDocsFrontmatter(content, {\n path: normalizedPath.path,\n })\n\n issues.push(...parsedFrontmatter.issues)\n warnings.push(...parsedFrontmatter.warnings)\n\n const route = deriveRouteFromSourcePath({\n slug: parsedFrontmatter.frontmatter.slug,\n routeBase,\n sourcePath: normalizedPath.path,\n })\n\n return {\n fileBytes,\n issues,\n normalizedPath: normalizedPath.path,\n validatedFile: {\n content: parsedFrontmatter.content,\n frontmatter: parsedFrontmatter.frontmatter,\n path: normalizedPath.path,\n route,\n sha256: computedHash,\n title: resolveDocsTitle({\n content: parsedFrontmatter.content,\n frontmatter: parsedFrontmatter.frontmatter,\n sourcePath: normalizedPath.path,\n }),\n },\n warnings,\n }\n}\n\nexport const validateDocsManifest = (\n manifest: unknown,\n options: DocsValidationOptions = {},\n): DocsValidationResult<ValidatedDocsManifest> => {\n const issues: DocsValidationIssue[] = []\n const warnings: DocsValidationIssue[] = []\n const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_DOCS_FILE_BYTES\n const maxFiles = options.maxFiles ?? DEFAULT_MAX_DOCS_FILES\n const maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_DOCS_TOTAL_BYTES\n const routeBase = options.routeBase ?? DEFAULT_DOCS_ROUTE_BASE\n\n if (!isRecord(manifest)) {\n return {\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest must be an object.',\n }),\n ],\n ok: false,\n warnings,\n }\n }\n\n if (manifest.version !== 1) {\n issues.push(\n createIssue({\n code: 'invalid_version',\n message: 'Manifest version must be 1.',\n }),\n )\n }\n\n const sourceValidation = validateSource({\n allowedSourceIds: options.allowedSourceIds,\n source: manifest.source,\n })\n\n issues.push(...sourceValidation.issues)\n\n const modeValidation = validateMode(manifest.mode)\n issues.push(...modeValidation.issues)\n\n const deleteBehaviorValidation = validateDeleteBehavior(manifest.deleteBehavior)\n issues.push(...deleteBehaviorValidation.issues)\n\n const publish =\n manifest.publish === undefined ? false : manifest.publish === true ? true : false\n\n if (manifest.publish !== undefined && typeof manifest.publish !== 'boolean') {\n issues.push(\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest publish must be a boolean.',\n }),\n )\n }\n\n const files = Array.isArray(manifest.files) ? manifest.files : undefined\n\n if (!files || files.length === 0) {\n issues.push(\n createIssue({\n code: 'empty_manifest',\n message: 'Manifest must include at least one file.',\n }),\n )\n }\n\n if (files && files.length > maxFiles) {\n issues.push(\n createIssue({\n code: 'too_many_files',\n message: `Manifest exceeds maximum file count of ${maxFiles}.`,\n }),\n )\n }\n\n const validatedFiles: ValidatedDocsManifestFile[] = []\n const normalizedPaths = new Set<string>()\n let totalBytes = 0\n\n for (const file of files ?? []) {\n const fileValidation = validateManifestFile({\n file,\n maxFileBytes,\n routeBase,\n })\n\n totalBytes += fileValidation.fileBytes\n issues.push(...fileValidation.issues)\n warnings.push(...fileValidation.warnings)\n\n if (fileValidation.normalizedPath) {\n if (normalizedPaths.has(fileValidation.normalizedPath)) {\n issues.push(\n createIssue({\n code: 'duplicate_path',\n message: 'Manifest contains duplicate normalized paths.',\n path: fileValidation.normalizedPath,\n }),\n )\n }\n\n normalizedPaths.add(fileValidation.normalizedPath)\n }\n\n if (fileValidation.validatedFile) {\n validatedFiles.push(fileValidation.validatedFile)\n }\n }\n\n if (totalBytes > maxTotalBytes) {\n issues.push(\n createIssue({\n code: 'manifest_too_large',\n message: `Manifest content exceeds maximum total size of ${maxTotalBytes} bytes.`,\n }),\n )\n }\n\n const aiExportValidation =\n manifest.aiExport === undefined\n ? undefined\n : validateDocsAiExportManifest(manifest.aiExport, {\n knownDocsPaths: normalizedPaths,\n })\n\n if (aiExportValidation) {\n issues.push(...aiExportValidation.issues)\n warnings.push(...aiExportValidation.warnings)\n }\n\n if (\n issues.length > 0 ||\n !sourceValidation.source ||\n aiExportValidation?.ok === false\n ) {\n return {\n issues,\n ok: false,\n warnings,\n }\n }\n\n return {\n data: {\n deleteBehavior: deleteBehaviorValidation.deleteBehavior,\n ...(aiExportValidation?.ok ? { aiExport: aiExportValidation.manifest } : {}),\n files: validatedFiles,\n mode: modeValidation.mode,\n publish,\n source: sourceValidation.source,\n version: 1,\n },\n issues,\n ok: true,\n warnings,\n }\n}\n\nexport type { DocsManifest, DocsManifestFile, DocsManifestSource }\n"],"names":["DEFAULT_DOCS_ROUTE_BASE","DEFAULT_MAX_DOCS_FILE_BYTES","DEFAULT_MAX_DOCS_FILES","DEFAULT_MAX_DOCS_TOTAL_BYTES","validateDocsAiExportManifest","parseDocsFrontmatter","resolveDocsTitle","sha256Hex","deriveRouteFromSourcePath","normalizeDocsPath","syncModes","Set","deleteBehaviors","isRecord","value","Array","isArray","createIssue","code","message","path","byteLength","content","Buffer","validateSource","allowedSourceIds","source","id","trim","issues","includes","branch","undefined","commit","repository","root","validateMode","mode","has","validateDeleteBehavior","deleteBehavior","validateManifestFile","file","maxFileBytes","routeBase","warnings","fileBytes","normalizedPath","ok","push","computedHash","sha256","test","toLowerCase","parsedFrontmatter","route","slug","frontmatter","sourcePath","validatedFile","title","validateDocsManifest","manifest","options","maxFiles","maxTotalBytes","version","sourceValidation","modeValidation","deleteBehaviorValidation","publish","files","length","validatedFiles","normalizedPaths","totalBytes","fileValidation","add","aiExportValidation","aiExport","knownDocsPaths","data"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/sync/validate.ts"],"sourcesContent":["import type {\n DocsDeleteBehavior,\n DocsManifest,\n DocsManifestFile,\n DocsManifestSource,\n DocsSyncMode,\n ValidatedDocsManifest,\n ValidatedDocsManifestFile,\n} from './manifest.js'\n\nimport {\n DEFAULT_DOCS_ROUTE_BASE,\n DEFAULT_MAX_DOCS_FILE_BYTES,\n DEFAULT_MAX_DOCS_FILES,\n DEFAULT_MAX_DOCS_TOTAL_BYTES,\n} from '../constants.js'\nimport { validateDocsAiExportManifest } from './aiExportManifest.js'\nimport {\n parseDocsFrontmatter,\n resolveDocsTitle,\n} from './frontmatter.js'\nimport { sha256Hex } from './hash.js'\nimport { deriveRouteFromSourcePath, normalizeDocsPath } from './paths.js'\n\nexport type DocsValidationErrorCode =\n | 'duplicate_existing_path'\n | 'duplicate_path'\n | 'empty_manifest'\n | 'file_too_large'\n | 'invalid_ai_export_manifest'\n | 'invalid_delete_behavior'\n | 'invalid_frontmatter'\n | 'invalid_hash'\n | 'invalid_manifest'\n | 'invalid_mode'\n | 'invalid_path'\n | 'invalid_source'\n | 'invalid_version'\n | 'manifest_too_large'\n | 'missing_ai_export_order_path'\n | 'non_markdown_file'\n | 'path_traversal'\n | 'too_many_files'\n\nexport type DocsValidationIssue = {\n code: DocsValidationErrorCode\n message: string\n path?: string\n}\n\nexport type DocsValidationResult<T = unknown> =\n | {\n data: T\n issues: DocsValidationIssue[]\n ok: true\n warnings: DocsValidationIssue[]\n }\n | {\n issues: DocsValidationIssue[]\n ok: false\n warnings: DocsValidationIssue[]\n }\n\nexport type DocsValidationOptions = {\n allowedSourceIds?: string[]\n maxFileBytes?: number\n maxFiles?: number\n maxTotalBytes?: number\n routeBase?: string\n}\n\nconst syncModes = new Set<DocsSyncMode>(['dry-run', 'sync'])\nconst deleteBehaviors = new Set<DocsDeleteBehavior>([\n 'archive',\n 'delete',\n 'draft',\n 'ignore',\n])\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst createIssue = ({\n code,\n message,\n path,\n}: DocsValidationIssue): DocsValidationIssue => ({\n code,\n message,\n path,\n})\n\nconst byteLength = (content: string): number => Buffer.byteLength(content, 'utf8')\n\nconst validateSource = ({\n allowedSourceIds,\n source,\n}: {\n allowedSourceIds?: string[]\n source: unknown\n}): {\n issues: DocsValidationIssue[]\n source?: DocsManifestSource\n} => {\n if (!isRecord(source) || typeof source.id !== 'string' || source.id.trim() === '') {\n return {\n issues: [\n createIssue({\n code: 'invalid_source',\n message: 'Manifest source.id is required.',\n }),\n ],\n }\n }\n\n if (allowedSourceIds && !allowedSourceIds.includes(source.id)) {\n return {\n issues: [\n createIssue({\n code: 'invalid_source',\n message: `Manifest source.id \"${source.id}\" is not allowed.`,\n }),\n ],\n }\n }\n\n return {\n issues: [],\n source: {\n id: source.id,\n branch: typeof source.branch === 'string' ? source.branch : undefined,\n commit: typeof source.commit === 'string' ? source.commit : undefined,\n repository: typeof source.repository === 'string' ? source.repository : undefined,\n },\n }\n}\n\nconst validateMode = (mode: unknown): {\n issues: DocsValidationIssue[]\n mode: DocsSyncMode\n} => {\n if (mode === undefined) {\n return {\n issues: [],\n mode: 'dry-run',\n }\n }\n\n if (syncModes.has(mode as DocsSyncMode)) {\n return {\n issues: [],\n mode: mode as DocsSyncMode,\n }\n }\n\n return {\n issues: [\n createIssue({\n code: 'invalid_mode',\n message: 'Manifest mode must be \"dry-run\" or \"sync\".',\n }),\n ],\n mode: 'dry-run',\n }\n}\n\nconst validateDeleteBehavior = (deleteBehavior: unknown): {\n deleteBehavior: DocsDeleteBehavior\n issues: DocsValidationIssue[]\n} => {\n if (deleteBehavior === undefined) {\n return {\n deleteBehavior: 'archive',\n issues: [],\n }\n }\n\n if (deleteBehaviors.has(deleteBehavior as DocsDeleteBehavior)) {\n return {\n deleteBehavior: deleteBehavior as DocsDeleteBehavior,\n issues: [],\n }\n }\n\n return {\n deleteBehavior: 'archive',\n issues: [\n createIssue({\n code: 'invalid_delete_behavior',\n message: 'Manifest deleteBehavior must be archive, delete, draft, or ignore.',\n }),\n ],\n }\n}\n\nconst validateManifestFile = ({\n file,\n maxFileBytes,\n routeBase,\n}: {\n file: unknown\n maxFileBytes: number\n routeBase: string\n}): {\n fileBytes: number\n issues: DocsValidationIssue[]\n normalizedPath?: string\n validatedFile?: ValidatedDocsManifestFile\n warnings: DocsValidationIssue[]\n} => {\n const issues: DocsValidationIssue[] = []\n const warnings: DocsValidationIssue[] = []\n\n if (!isRecord(file)) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest file entries must be objects.',\n }),\n ],\n warnings,\n }\n }\n\n const path = typeof file.path === 'string' ? file.path : undefined\n const content = typeof file.content === 'string' ? file.content : undefined\n\n if (!path || content === undefined) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest file entries require string path and content.',\n path,\n }),\n ],\n warnings,\n }\n }\n\n const normalizedPath = normalizeDocsPath(path)\n\n if (!normalizedPath.ok) {\n return {\n fileBytes: 0,\n issues: [\n createIssue({\n code: normalizedPath.code,\n message: normalizedPath.message,\n path,\n }),\n ],\n warnings,\n }\n }\n\n const fileBytes = byteLength(content)\n\n if (fileBytes > maxFileBytes) {\n issues.push(\n createIssue({\n code: 'file_too_large',\n message: `File exceeds maximum size of ${maxFileBytes} bytes.`,\n path: normalizedPath.path,\n }),\n )\n }\n\n const computedHash = sha256Hex(content)\n\n if (\n file.sha256 !== undefined &&\n (typeof file.sha256 !== 'string' ||\n !/^[a-f0-9]{64}$/i.test(file.sha256) ||\n file.sha256.toLowerCase() !== computedHash)\n ) {\n issues.push(\n createIssue({\n code: 'invalid_hash',\n message: 'Manifest file sha256 does not match content.',\n path: normalizedPath.path,\n }),\n )\n }\n\n const parsedFrontmatter = parseDocsFrontmatter(content, {\n path: normalizedPath.path,\n })\n\n issues.push(...parsedFrontmatter.issues)\n warnings.push(...parsedFrontmatter.warnings)\n\n const route = deriveRouteFromSourcePath({\n slug: parsedFrontmatter.frontmatter.slug,\n routeBase,\n sourcePath: normalizedPath.path,\n })\n\n return {\n fileBytes,\n issues,\n normalizedPath: normalizedPath.path,\n validatedFile: {\n content: parsedFrontmatter.content,\n frontmatter: parsedFrontmatter.frontmatter,\n path: normalizedPath.path,\n route,\n sha256: computedHash,\n title: resolveDocsTitle({\n content: parsedFrontmatter.content,\n frontmatter: parsedFrontmatter.frontmatter,\n sourcePath: normalizedPath.path,\n }),\n },\n warnings,\n }\n}\n\nexport const validateDocsManifest = (\n manifest: unknown,\n options: DocsValidationOptions = {},\n): DocsValidationResult<ValidatedDocsManifest> => {\n const issues: DocsValidationIssue[] = []\n const warnings: DocsValidationIssue[] = []\n const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_DOCS_FILE_BYTES\n const maxFiles = options.maxFiles ?? DEFAULT_MAX_DOCS_FILES\n const maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_DOCS_TOTAL_BYTES\n const routeBase = options.routeBase ?? DEFAULT_DOCS_ROUTE_BASE\n\n if (!isRecord(manifest)) {\n return {\n issues: [\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest must be an object.',\n }),\n ],\n ok: false,\n warnings,\n }\n }\n\n if (manifest.version !== 1) {\n issues.push(\n createIssue({\n code: 'invalid_version',\n message: 'Manifest version must be 1.',\n }),\n )\n }\n\n const sourceValidation = validateSource({\n allowedSourceIds: options.allowedSourceIds,\n source: manifest.source,\n })\n\n issues.push(...sourceValidation.issues)\n\n const modeValidation = validateMode(manifest.mode)\n issues.push(...modeValidation.issues)\n\n const deleteBehaviorValidation = validateDeleteBehavior(manifest.deleteBehavior)\n issues.push(...deleteBehaviorValidation.issues)\n\n const publish =\n manifest.publish === undefined ? false : manifest.publish === true ? true : false\n\n if (manifest.publish !== undefined && typeof manifest.publish !== 'boolean') {\n issues.push(\n createIssue({\n code: 'invalid_manifest',\n message: 'Manifest publish must be a boolean.',\n }),\n )\n }\n\n const files = Array.isArray(manifest.files) ? manifest.files : undefined\n\n if (!files || files.length === 0) {\n issues.push(\n createIssue({\n code: 'empty_manifest',\n message: 'Manifest must include at least one file.',\n }),\n )\n }\n\n if (files && files.length > maxFiles) {\n issues.push(\n createIssue({\n code: 'too_many_files',\n message: `Manifest exceeds maximum file count of ${maxFiles}.`,\n }),\n )\n }\n\n const validatedFiles: ValidatedDocsManifestFile[] = []\n const normalizedPaths = new Set<string>()\n let totalBytes = 0\n\n for (const file of files ?? []) {\n const fileValidation = validateManifestFile({\n file,\n maxFileBytes,\n routeBase,\n })\n\n totalBytes += fileValidation.fileBytes\n issues.push(...fileValidation.issues)\n warnings.push(...fileValidation.warnings)\n\n if (fileValidation.normalizedPath) {\n if (normalizedPaths.has(fileValidation.normalizedPath)) {\n issues.push(\n createIssue({\n code: 'duplicate_path',\n message: 'Manifest contains duplicate normalized paths.',\n path: fileValidation.normalizedPath,\n }),\n )\n }\n\n normalizedPaths.add(fileValidation.normalizedPath)\n }\n\n if (fileValidation.validatedFile) {\n validatedFiles.push(fileValidation.validatedFile)\n }\n }\n\n if (totalBytes > maxTotalBytes) {\n issues.push(\n createIssue({\n code: 'manifest_too_large',\n message: `Manifest content exceeds maximum total size of ${maxTotalBytes} bytes.`,\n }),\n )\n }\n\n const aiExportValidation =\n manifest.aiExport === undefined\n ? undefined\n : validateDocsAiExportManifest(manifest.aiExport, {\n knownDocsPaths: normalizedPaths,\n })\n\n if (aiExportValidation) {\n issues.push(...aiExportValidation.issues)\n warnings.push(...aiExportValidation.warnings)\n }\n\n if (\n issues.length > 0 ||\n !sourceValidation.source ||\n aiExportValidation?.ok === false\n ) {\n return {\n issues,\n ok: false,\n warnings,\n }\n }\n\n return {\n data: {\n deleteBehavior: deleteBehaviorValidation.deleteBehavior,\n ...(aiExportValidation?.ok ? { aiExport: aiExportValidation.manifest } : {}),\n files: validatedFiles,\n mode: modeValidation.mode,\n publish,\n source: sourceValidation.source,\n version: 1,\n },\n issues,\n ok: true,\n warnings,\n }\n}\n\nexport type { DocsManifest, DocsManifestFile, DocsManifestSource }\n"],"names":["DEFAULT_DOCS_ROUTE_BASE","DEFAULT_MAX_DOCS_FILE_BYTES","DEFAULT_MAX_DOCS_FILES","DEFAULT_MAX_DOCS_TOTAL_BYTES","validateDocsAiExportManifest","parseDocsFrontmatter","resolveDocsTitle","sha256Hex","deriveRouteFromSourcePath","normalizeDocsPath","syncModes","Set","deleteBehaviors","isRecord","value","Array","isArray","createIssue","code","message","path","byteLength","content","Buffer","validateSource","allowedSourceIds","source","id","trim","issues","includes","branch","undefined","commit","repository","validateMode","mode","has","validateDeleteBehavior","deleteBehavior","validateManifestFile","file","maxFileBytes","routeBase","warnings","fileBytes","normalizedPath","ok","push","computedHash","sha256","test","toLowerCase","parsedFrontmatter","route","slug","frontmatter","sourcePath","validatedFile","title","validateDocsManifest","manifest","options","maxFiles","maxTotalBytes","version","sourceValidation","modeValidation","deleteBehaviorValidation","publish","files","length","validatedFiles","normalizedPaths","totalBytes","fileValidation","add","aiExportValidation","aiExport","knownDocsPaths","data"],"mappings":"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"}