@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.
- package/README.md +76 -176
- package/dist/admin/DocsSetManager.js +5 -3
- package/dist/admin/DocsSetManager.js.map +1 -1
- package/dist/admin/docsSetManagerData.d.ts +6 -5
- package/dist/admin/docsSetManagerData.js +60 -33
- package/dist/admin/docsSetManagerData.js.map +1 -1
- package/dist/admin/docsSetManagerTypes.d.ts +12 -9
- package/dist/admin/docsSetManagerTypes.js.map +1 -1
- package/dist/cli/commands/manifest.js +1 -2
- package/dist/cli/commands/manifest.js.map +1 -1
- package/dist/cli/commands/plan.js +1 -2
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/push.js +2 -5
- package/dist/cli/commands/push.js.map +1 -1
- package/dist/cli/commands/validate.js +11 -6
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/index.js +5 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parseArgs.js +0 -3
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/cli/types.d.ts +0 -3
- package/dist/cli/types.js.map +1 -1
- package/dist/collections/docs.js +0 -24
- package/dist/collections/docs.js.map +1 -1
- package/dist/collections/docsGroups.js +8 -9
- package/dist/collections/docsGroups.js.map +1 -1
- package/dist/collections/docsKeys.d.ts +5 -0
- package/dist/collections/docsKeys.js +44 -0
- package/dist/collections/docsKeys.js.map +1 -0
- package/dist/collections/docsSets.js +47 -202
- package/dist/collections/docsSets.js.map +1 -1
- package/dist/collections/docsTrusted.d.ts +5 -0
- package/dist/collections/docsTrusted.js +60 -0
- package/dist/collections/docsTrusted.js.map +1 -0
- package/dist/collections/index.d.ts +4 -0
- package/dist/collections/index.js +2 -0
- package/dist/collections/index.js.map +1 -1
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/endpoints/sync.d.ts +6 -7
- package/dist/endpoints/sync.js +57 -124
- package/dist/endpoints/sync.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/next/PayloadMarkdownDocsPage.js +2 -6
- package/dist/next/PayloadMarkdownDocsPage.js.map +1 -1
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/next/index.js.map +1 -1
- package/dist/next/links.d.ts +11 -0
- package/dist/next/links.js +79 -0
- package/dist/next/links.js.map +1 -0
- package/dist/next/markdown.js +91 -19
- package/dist/next/markdown.js.map +1 -1
- package/dist/next/metadata.js +6 -6
- package/dist/next/metadata.js.map +1 -1
- package/dist/next/records.js +13 -23
- package/dist/next/records.js.map +1 -1
- package/dist/next/route.js +141 -49
- package/dist/next/route.js.map +1 -1
- package/dist/next/types.d.ts +0 -14
- package/dist/next/types.js.map +1 -1
- package/dist/payload/docsKeys.d.ts +20 -0
- package/dist/payload/docsKeys.js +29 -0
- package/dist/payload/docsKeys.js.map +1 -0
- package/dist/payload/docsSets.d.ts +32 -6
- package/dist/payload/docsSets.js +146 -83
- package/dist/payload/docsSets.js.map +1 -1
- package/dist/payload/docsTrusted.d.ts +16 -0
- package/dist/payload/docsTrusted.js +49 -0
- package/dist/payload/docsTrusted.js.map +1 -0
- package/dist/payload/index.d.ts +5 -1
- package/dist/payload/index.js +3 -1
- package/dist/payload/index.js.map +1 -1
- package/dist/plugin.js +36 -9
- package/dist/plugin.js.map +1 -1
- package/dist/security/githubOidc.d.ts +18 -5
- package/dist/security/githubOidc.js +44 -16
- package/dist/security/githubOidc.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/skills/codex/SKILL.md +3 -4
- package/dist/skills/codex/examples/github-actions.md +0 -2
- package/dist/skills/codex/reference/admin.md +0 -6
- package/dist/skills/codex/reference/routing.md +2 -1
- package/dist/skills/codex/reference/sync.md +7 -5
- package/dist/skills/codex/reference/troubleshooting.md +3 -4
- package/dist/skills/codex/reference/workflow.md +0 -1
- package/dist/sync/manifest.d.ts +1 -3
- package/dist/sync/manifest.js +2 -3
- package/dist/sync/manifest.js.map +1 -1
- package/dist/sync/validate.js +1 -2
- package/dist/sync/validate.js.map +1 -1
- package/dist/types.d.ts +7 -50
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
package/dist/plugin.js.map
CHANGED
|
@@ -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 = '
|
|
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:
|
|
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
|
|
142
|
-
if (
|
|
143
|
-
return issue('oidc_repository_not_allowed', 'GitHub OIDC auth requires
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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',
|
|
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 (
|
|
159
|
-
return issue('oidc_workflow_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.
|
|
162
|
-
return issue('
|
|
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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"}
|
package/dist/security/index.d.ts
CHANGED
|
@@ -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;
|
|
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
|
|
20
|
-
to a Payload Admin docs set
|
|
21
|
-
|
|
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.
|
|
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
|
|
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
|
-
-
|
|
29
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
29
|
+
Create or update a docs set with the expected slug.
|
|
31
30
|
|
|
32
31
|
## Publish disabled
|
|
33
32
|
|
package/dist/sync/manifest.d.ts
CHANGED
|
@@ -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,
|
|
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;
|
package/dist/sync/manifest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sha256Hex } from './hash.js';
|
|
2
|
-
export const buildDocsManifest = ({ aiExport, branch, commit, deleteBehavior, files, mode, publish, repository,
|
|
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
|
|
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"}
|
package/dist/sync/validate.js
CHANGED
|
@@ -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":"AAUA,SACEA,uBAAuB,EACvBC,2BAA2B,EAC3BC,sBAAsB,EACtBC,4BAA4B,QACvB,kBAAiB;AACxB,SAASC,4BAA4B,QAAQ,wBAAuB;AACpE,SACEC,oBAAoB,EACpBC,gBAAgB,QACX,mBAAkB;AACzB,SAASC,SAAS,QAAQ,YAAW;AACrC,SAASC,yBAAyB,EAAEC,iBAAiB,QAAQ,aAAY;AAiDzE,MAAMC,YAAY,IAAIC,IAAkB;IAAC;IAAW;CAAO;AAC3D,MAAMC,kBAAkB,IAAID,IAAwB;IAClD;IACA;IACA;IACA;CACD;AAED,MAAME,WAAW,CAACC,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AAEhE,MAAMG,cAAc,CAAC,EACnBC,IAAI,EACJC,OAAO,EACPC,IAAI,EACgB,GAA2B,CAAA;QAC/CF;QACAC;QACAC;IACF,CAAA;AAEA,MAAMC,aAAa,CAACC,UAA4BC,OAAOF,UAAU,CAACC,SAAS;AAE3E,MAAME,iBAAiB,CAAC,EACtBC,gBAAgB,EAChBC,MAAM,EAIP;IAIC,IAAI,CAACb,SAASa,WAAW,OAAOA,OAAOC,EAAE,KAAK,YAAYD,OAAOC,EAAE,CAACC,IAAI,OAAO,IAAI;QACjF,OAAO;YACLC,QAAQ;gBACNZ,YAAY;oBACVC,MAAM;oBACNC,SAAS;gBACX;aACD;QACH;IACF;IAEA,IAAIM,oBAAoB,CAACA,iBAAiBK,QAAQ,CAACJ,OAAOC,EAAE,GAAG;QAC7D,OAAO;YACLE,QAAQ;gBACNZ,YAAY;oBACVC,MAAM;oBACNC,SAAS,CAAC,oBAAoB,EAAEO,OAAOC,EAAE,CAAC,iBAAiB,CAAC;gBAC9D;aACD;QACH;IACF;IAEA,OAAO;QACLE,QAAQ,EAAE;QACVH,QAAQ;YACNC,IAAID,OAAOC,EAAE;YACbI,QAAQ,OAAOL,OAAOK,MAAM,KAAK,WAAWL,OAAOK,MAAM,GAAGC;YAC5DC,QAAQ,OAAOP,OAAOO,MAAM,KAAK,WAAWP,OAAOO,MAAM,GAAGD;YAC5DE,YAAY,OAAOR,OAAOQ,UAAU,KAAK,WAAWR,OAAOQ,UAAU,GAAGF;QAC1E;IACF;AACF;AAEA,MAAMG,eAAe,CAACC;IAIpB,IAAIA,SAASJ,WAAW;QACtB,OAAO;YACLH,QAAQ,EAAE;YACVO,MAAM;QACR;IACF;IAEA,IAAI1B,UAAU2B,GAAG,CAACD,OAAuB;QACvC,OAAO;YACLP,QAAQ,EAAE;YACVO,MAAMA;QACR;IACF;IAEA,OAAO;QACLP,QAAQ;YACNZ,YAAY;gBACVC,MAAM;gBACNC,SAAS;YACX;SACD;QACDiB,MAAM;IACR;AACF;AAEA,MAAME,yBAAyB,CAACC;IAI9B,IAAIA,mBAAmBP,WAAW;QAChC,OAAO;YACLO,gBAAgB;YAChBV,QAAQ,EAAE;QACZ;IACF;IAEA,IAAIjB,gBAAgByB,GAAG,CAACE,iBAAuC;QAC7D,OAAO;YACLA,gBAAgBA;YAChBV,QAAQ,EAAE;QACZ;IACF;IAEA,OAAO;QACLU,gBAAgB;QAChBV,QAAQ;YACNZ,YAAY;gBACVC,MAAM;gBACNC,SAAS;YACX;SACD;IACH;AACF;AAEA,MAAMqB,uBAAuB,CAAC,EAC5BC,IAAI,EACJC,YAAY,EACZC,SAAS,EAKV;IAOC,MAAMd,SAAgC,EAAE;IACxC,MAAMe,WAAkC,EAAE;IAE1C,IAAI,CAAC/B,SAAS4B,OAAO;QACnB,OAAO;YACLI,WAAW;YACXhB,QAAQ;gBACNZ,YAAY;oBACVC,MAAM;oBACNC,SAAS;gBACX;aACD;YACDyB;QACF;IACF;IAEA,MAAMxB,OAAO,OAAOqB,KAAKrB,IAAI,KAAK,WAAWqB,KAAKrB,IAAI,GAAGY;IACzD,MAAMV,UAAU,OAAOmB,KAAKnB,OAAO,KAAK,WAAWmB,KAAKnB,OAAO,GAAGU;IAElE,IAAI,CAACZ,QAAQE,YAAYU,WAAW;QAClC,OAAO;YACLa,WAAW;YACXhB,QAAQ;gBACNZ,YAAY;oBACVC,MAAM;oBACNC,SAAS;oBACTC;gBACF;aACD;YACDwB;QACF;IACF;IAEA,MAAME,iBAAiBrC,kBAAkBW;IAEzC,IAAI,CAAC0B,eAAeC,EAAE,EAAE;QACtB,OAAO;YACLF,WAAW;YACXhB,QAAQ;gBACNZ,YAAY;oBACVC,MAAM4B,eAAe5B,IAAI;oBACzBC,SAAS2B,eAAe3B,OAAO;oBAC/BC;gBACF;aACD;YACDwB;QACF;IACF;IAEA,MAAMC,YAAYxB,WAAWC;IAE7B,IAAIuB,YAAYH,cAAc;QAC5Bb,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS,CAAC,6BAA6B,EAAEuB,aAAa,OAAO,CAAC;YAC9DtB,MAAM0B,eAAe1B,IAAI;QAC3B;IAEJ;IAEA,MAAM6B,eAAe1C,UAAUe;IAE/B,IACEmB,KAAKS,MAAM,KAAKlB,aACf,CAAA,OAAOS,KAAKS,MAAM,KAAK,YACtB,CAAC,kBAAkBC,IAAI,CAACV,KAAKS,MAAM,KACnCT,KAAKS,MAAM,CAACE,WAAW,OAAOH,YAAW,GAC3C;QACApB,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS;YACTC,MAAM0B,eAAe1B,IAAI;QAC3B;IAEJ;IAEA,MAAMiC,oBAAoBhD,qBAAqBiB,SAAS;QACtDF,MAAM0B,eAAe1B,IAAI;IAC3B;IAEAS,OAAOmB,IAAI,IAAIK,kBAAkBxB,MAAM;IACvCe,SAASI,IAAI,IAAIK,kBAAkBT,QAAQ;IAE3C,MAAMU,QAAQ9C,0BAA0B;QACtC+C,MAAMF,kBAAkBG,WAAW,CAACD,IAAI;QACxCZ;QACAc,YAAYX,eAAe1B,IAAI;IACjC;IAEA,OAAO;QACLyB;QACAhB;QACAiB,gBAAgBA,eAAe1B,IAAI;QACnCsC,eAAe;YACbpC,SAAS+B,kBAAkB/B,OAAO;YAClCkC,aAAaH,kBAAkBG,WAAW;YAC1CpC,MAAM0B,eAAe1B,IAAI;YACzBkC;YACAJ,QAAQD;YACRU,OAAOrD,iBAAiB;gBACtBgB,SAAS+B,kBAAkB/B,OAAO;gBAClCkC,aAAaH,kBAAkBG,WAAW;gBAC1CC,YAAYX,eAAe1B,IAAI;YACjC;QACF;QACAwB;IACF;AACF;AAEA,OAAO,MAAMgB,uBAAuB,CAClCC,UACAC,UAAiC,CAAC,CAAC;IAEnC,MAAMjC,SAAgC,EAAE;IACxC,MAAMe,WAAkC,EAAE;IAC1C,MAAMF,eAAeoB,QAAQpB,YAAY,IAAIzC;IAC7C,MAAM8D,WAAWD,QAAQC,QAAQ,IAAI7D;IACrC,MAAM8D,gBAAgBF,QAAQE,aAAa,IAAI7D;IAC/C,MAAMwC,YAAYmB,QAAQnB,SAAS,IAAI3C;IAEvC,IAAI,CAACa,SAASgD,WAAW;QACvB,OAAO;YACLhC,QAAQ;gBACNZ,YAAY;oBACVC,MAAM;oBACNC,SAAS;gBACX;aACD;YACD4B,IAAI;YACJH;QACF;IACF;IAEA,IAAIiB,SAASI,OAAO,KAAK,GAAG;QAC1BpC,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS;QACX;IAEJ;IAEA,MAAM+C,mBAAmB1C,eAAe;QACtCC,kBAAkBqC,QAAQrC,gBAAgB;QAC1CC,QAAQmC,SAASnC,MAAM;IACzB;IAEAG,OAAOmB,IAAI,IAAIkB,iBAAiBrC,MAAM;IAEtC,MAAMsC,iBAAiBhC,aAAa0B,SAASzB,IAAI;IACjDP,OAAOmB,IAAI,IAAImB,eAAetC,MAAM;IAEpC,MAAMuC,2BAA2B9B,uBAAuBuB,SAAStB,cAAc;IAC/EV,OAAOmB,IAAI,IAAIoB,yBAAyBvC,MAAM;IAE9C,MAAMwC,UACJR,SAASQ,OAAO,KAAKrC,YAAY,QAAQ6B,SAASQ,OAAO,KAAK,OAAO,OAAO;IAE9E,IAAIR,SAASQ,OAAO,KAAKrC,aAAa,OAAO6B,SAASQ,OAAO,KAAK,WAAW;QAC3ExC,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS;QACX;IAEJ;IAEA,MAAMmD,QAAQvD,MAAMC,OAAO,CAAC6C,SAASS,KAAK,IAAIT,SAASS,KAAK,GAAGtC;IAE/D,IAAI,CAACsC,SAASA,MAAMC,MAAM,KAAK,GAAG;QAChC1C,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS;QACX;IAEJ;IAEA,IAAImD,SAASA,MAAMC,MAAM,GAAGR,UAAU;QACpClC,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS,CAAC,uCAAuC,EAAE4C,SAAS,CAAC,CAAC;QAChE;IAEJ;IAEA,MAAMS,iBAA8C,EAAE;IACtD,MAAMC,kBAAkB,IAAI9D;IAC5B,IAAI+D,aAAa;IAEjB,KAAK,MAAMjC,QAAQ6B,SAAS,EAAE,CAAE;QAC9B,MAAMK,iBAAiBnC,qBAAqB;YAC1CC;YACAC;YACAC;QACF;QAEA+B,cAAcC,eAAe9B,SAAS;QACtChB,OAAOmB,IAAI,IAAI2B,eAAe9C,MAAM;QACpCe,SAASI,IAAI,IAAI2B,eAAe/B,QAAQ;QAExC,IAAI+B,eAAe7B,cAAc,EAAE;YACjC,IAAI2B,gBAAgBpC,GAAG,CAACsC,eAAe7B,cAAc,GAAG;gBACtDjB,OAAOmB,IAAI,CACT/B,YAAY;oBACVC,MAAM;oBACNC,SAAS;oBACTC,MAAMuD,eAAe7B,cAAc;gBACrC;YAEJ;YAEA2B,gBAAgBG,GAAG,CAACD,eAAe7B,cAAc;QACnD;QAEA,IAAI6B,eAAejB,aAAa,EAAE;YAChCc,eAAexB,IAAI,CAAC2B,eAAejB,aAAa;QAClD;IACF;IAEA,IAAIgB,aAAaV,eAAe;QAC9BnC,OAAOmB,IAAI,CACT/B,YAAY;YACVC,MAAM;YACNC,SAAS,CAAC,+CAA+C,EAAE6C,cAAc,OAAO,CAAC;QACnF;IAEJ;IAEA,MAAMa,qBACJhB,SAASiB,QAAQ,KAAK9C,YAClBA,YACA5B,6BAA6ByD,SAASiB,QAAQ,EAAE;QAC9CC,gBAAgBN;IAClB;IAEN,IAAII,oBAAoB;QACtBhD,OAAOmB,IAAI,IAAI6B,mBAAmBhD,MAAM;QACxCe,SAASI,IAAI,IAAI6B,mBAAmBjC,QAAQ;IAC9C;IAEA,IACEf,OAAO0C,MAAM,GAAG,KAChB,CAACL,iBAAiBxC,MAAM,IACxBmD,oBAAoB9B,OAAO,OAC3B;QACA,OAAO;YACLlB;YACAkB,IAAI;YACJH;QACF;IACF;IAEA,OAAO;QACLoC,MAAM;YACJzC,gBAAgB6B,yBAAyB7B,cAAc;YACvD,GAAIsC,oBAAoB9B,KAAK;gBAAE+B,UAAUD,mBAAmBhB,QAAQ;YAAC,IAAI,CAAC,CAAC;YAC3ES,OAAOE;YACPpC,MAAM+B,eAAe/B,IAAI;YACzBiC;YACA3C,QAAQwC,iBAAiBxC,MAAM;YAC/BuC,SAAS;QACX;QACApC;QACAkB,IAAI;QACJH;IACF;AACF,EAAC"}
|