@valkyrianlabs/payload-markdown-docs 0.6.0 → 0.7.1

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 CHANGED
@@ -86,11 +86,7 @@ import {
86
86
  import { notFound } from 'next/navigation'
87
87
  import { getPayload } from 'payload'
88
88
 
89
- export default async function Page({
90
- params,
91
- }: {
92
- params: Promise<{ slug?: string[] }>
93
- }) {
89
+ export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
94
90
  const { slug } = await params
95
91
  const payload = await getPayload({ config })
96
92
  const resolved = await resolvePayloadMarkdownDocsRoute({ payload, slug })
@@ -103,7 +99,54 @@ export default async function Page({
103
99
  }
104
100
  ```
105
101
 
106
- For header navigation, use the link helper:
102
+ For docs navigation, use the drop-in navbar when you want the plugin to own the
103
+ docs menu UI:
104
+
105
+ ```tsx
106
+ import { PayloadMarkdownDocsNavbar } from '@valkyrianlabs/payload-markdown-docs/next'
107
+ import type { Payload } from 'payload'
108
+
109
+ export async function HeaderDocsNav({ payload }: { payload: Payload }) {
110
+ return (
111
+ <PayloadMarkdownDocsNavbar currentPath="/plugins/payload-markdown-docs" payload={payload} />
112
+ )
113
+ }
114
+ ```
115
+
116
+ The navbar reads docs groups and docs sets, renders nested docs navigation, and
117
+ accepts `classNames` and `renderLink` overrides for app-specific Tailwind,
118
+ routing, and analytics.
119
+
120
+ If you already have a site header, use the Header adapter to append top-level
121
+ docs groups and top-level ungrouped docs sets without exceeding your existing
122
+ menu cap:
123
+
124
+ ```ts
125
+ import { appendPayloadMarkdownDocsHeaderNavItems } from '@valkyrianlabs/payload-markdown-docs/next'
126
+
127
+ const navItems = await appendPayloadMarkdownDocsHeaderNavItems({
128
+ existingItems: header.navItems ?? [],
129
+ maxItems: headerNavItemsMaxRows,
130
+ payload,
131
+ })
132
+ ```
133
+
134
+ The adapter defaults to custom URL links so it does not require CMSLink changes.
135
+ Use `mode: 'relationship'` only when your renderer understands `docs-groups`
136
+ and `docs-sets` relationships.
137
+
138
+ For fully custom navigation, use the headless nav builder:
139
+
140
+ ```ts
141
+ import { getPayloadMarkdownDocsNavItems } from '@valkyrianlabs/payload-markdown-docs/next'
142
+
143
+ const docsNav = await getPayloadMarkdownDocsNavItems({
144
+ availableSlots: 4,
145
+ payload,
146
+ })
147
+ ```
148
+
149
+ For simple flat header links, use the compatibility link helper:
107
150
 
108
151
  ```ts
109
152
  import { getPayloadMarkdownDocsLinks } from '@valkyrianlabs/payload-markdown-docs/next'
@@ -234,5 +277,6 @@ empty list rejects all workflow publishing for that docs set.
234
277
  - [Quick Start](docs/getting-started/quick-start.md)
235
278
  - [Plugin Config](docs/configuration/plugin-config.md)
236
279
  - [GitHub Actions](docs/workflow/ci-github-actions.md)
280
+ - [Docs Navbar](docs/frontend/navbar.md)
237
281
  - [CLI](docs/reference/cli.md)
238
282
  - [Migration Notes](docs/reference/migration.md)
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GetPayloadMarkdownDocsNavItemsOptions, PayloadMarkdownDocsNavItem } from './links.js';
3
+ export type PayloadMarkdownDocsNavbarClassNames = {
4
+ activeLink?: string;
5
+ childrenList?: string;
6
+ item?: string;
7
+ link?: string;
8
+ list?: string;
9
+ panel?: string;
10
+ root?: string;
11
+ trigger?: string;
12
+ };
13
+ export type PayloadMarkdownDocsNavbarRenderLinkOptions = {
14
+ active: boolean;
15
+ children: ReactNode;
16
+ className: string;
17
+ current: boolean;
18
+ href: string;
19
+ item: PayloadMarkdownDocsNavItem;
20
+ };
21
+ export type PayloadMarkdownDocsNavbarProps = {
22
+ ariaLabel?: string;
23
+ classNames?: PayloadMarkdownDocsNavbarClassNames;
24
+ currentPath?: string;
25
+ items?: PayloadMarkdownDocsNavItem[];
26
+ renderLink?: (options: PayloadMarkdownDocsNavbarRenderLinkOptions) => ReactNode;
27
+ } & Partial<GetPayloadMarkdownDocsNavItemsOptions>;
28
+ export declare const PayloadMarkdownDocsNavbar: ({ ariaLabel, classNames, currentPath, items, renderLink, ...options }: PayloadMarkdownDocsNavbarProps) => Promise<import("react/jsx-runtime").JSX.Element | null>;
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isRouteDescendant, normalizeRoutePath } from '../routing/index.js';
3
+ import { getPayloadMarkdownDocsNavItems } from './links.js';
4
+ const cx = (...values)=>values.filter(Boolean).join(' ');
5
+ const defaultClassNames = {
6
+ activeLink: 'bg-cyan-400/10 text-cyan-200',
7
+ childrenList: 'space-y-1',
8
+ item: 'relative list-none',
9
+ link: 'block rounded-lg px-3 py-2 text-sm leading-5 text-foreground/70 transition-colors hover:bg-white/[0.04] hover:text-foreground',
10
+ list: 'm-0 flex list-none items-center gap-1 p-0',
11
+ panel: 'absolute left-0 top-full z-50 hidden min-w-60 rounded-xl border border-border bg-background p-2 shadow-xl group-hover:block group-focus-within:block',
12
+ root: 'relative',
13
+ trigger: 'block rounded-lg px-3 py-2 text-sm leading-5 text-foreground/60 transition-colors group-hover:bg-white/[0.04] group-hover:text-foreground'
14
+ };
15
+ const isItemActive = ({ currentPath, item })=>{
16
+ if (!currentPath) {
17
+ return false;
18
+ }
19
+ if (item.url) {
20
+ const normalizedUrl = normalizeRoutePath(item.url);
21
+ if (currentPath === normalizedUrl || isRouteDescendant(normalizedUrl, currentPath)) {
22
+ return true;
23
+ }
24
+ }
25
+ return (item.children ?? []).some((child)=>isItemActive({
26
+ currentPath,
27
+ item: child
28
+ }));
29
+ };
30
+ const isItemCurrent = ({ currentPath, item })=>Boolean(currentPath && item.url && currentPath === normalizeRoutePath(item.url));
31
+ const renderDefaultLink = ({ children, className, current, href })=>/*#__PURE__*/ _jsx("a", {
32
+ "aria-current": current ? 'page' : undefined,
33
+ className: className,
34
+ href: href,
35
+ children: children
36
+ });
37
+ const renderNavItems = ({ classNames, currentPath, depth = 0, items, renderLink = renderDefaultLink })=>{
38
+ if (items.length === 0) {
39
+ return null;
40
+ }
41
+ return /*#__PURE__*/ _jsx("ul", {
42
+ className: cx(depth === 0 ? classNames.list : classNames.childrenList),
43
+ children: items.map((item)=>{
44
+ const active = isItemActive({
45
+ currentPath,
46
+ item
47
+ });
48
+ const current = isItemCurrent({
49
+ currentPath,
50
+ item
51
+ });
52
+ const linkClassName = cx(classNames.link, active && classNames.activeLink);
53
+ const children = item.children?.length ? renderNavItems({
54
+ classNames,
55
+ currentPath,
56
+ depth: depth + 1,
57
+ items: item.children,
58
+ renderLink
59
+ }) : null;
60
+ return /*#__PURE__*/ _jsxs("li", {
61
+ className: cx('group', classNames.item),
62
+ children: [
63
+ item.url ? renderLink({
64
+ active,
65
+ children: item.label,
66
+ className: linkClassName,
67
+ current,
68
+ href: item.url,
69
+ item
70
+ }) : /*#__PURE__*/ _jsx("span", {
71
+ className: cx(classNames.trigger, active && classNames.activeLink),
72
+ children: item.label
73
+ }),
74
+ children ? /*#__PURE__*/ _jsx("div", {
75
+ className: classNames.panel,
76
+ children: children
77
+ }) : null
78
+ ]
79
+ }, `${item.collection}:${item.id}`);
80
+ })
81
+ });
82
+ };
83
+ export const PayloadMarkdownDocsNavbar = async ({ ariaLabel = 'Docs navigation', classNames, currentPath, items, renderLink, ...options })=>{
84
+ const navItems = items ?? (options.payload ? await getPayloadMarkdownDocsNavItems({
85
+ ...options,
86
+ payload: options.payload
87
+ }) : []);
88
+ if (navItems.length === 0) {
89
+ return null;
90
+ }
91
+ const mergedClassNames = {
92
+ ...defaultClassNames,
93
+ ...classNames
94
+ };
95
+ return /*#__PURE__*/ _jsx("nav", {
96
+ "aria-label": ariaLabel,
97
+ className: mergedClassNames.root,
98
+ children: renderNavItems({
99
+ classNames: mergedClassNames,
100
+ currentPath: currentPath ? normalizeRoutePath(currentPath) : undefined,
101
+ items: navItems,
102
+ renderLink
103
+ })
104
+ });
105
+ };
106
+
107
+ //# sourceMappingURL=PayloadMarkdownDocsNavbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/next/PayloadMarkdownDocsNavbar.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport type { GetPayloadMarkdownDocsNavItemsOptions, PayloadMarkdownDocsNavItem } from './links.js'\n\nimport { isRouteDescendant, normalizeRoutePath } from '../routing/index.js'\nimport { getPayloadMarkdownDocsNavItems } from './links.js'\n\nexport type PayloadMarkdownDocsNavbarClassNames = {\n activeLink?: string\n childrenList?: string\n item?: string\n link?: string\n list?: string\n panel?: string\n root?: string\n trigger?: string\n}\n\nexport type PayloadMarkdownDocsNavbarRenderLinkOptions = {\n active: boolean\n children: ReactNode\n className: string\n current: boolean\n href: string\n item: PayloadMarkdownDocsNavItem\n}\n\nexport type PayloadMarkdownDocsNavbarProps = {\n ariaLabel?: string\n classNames?: PayloadMarkdownDocsNavbarClassNames\n currentPath?: string\n items?: PayloadMarkdownDocsNavItem[]\n renderLink?: (options: PayloadMarkdownDocsNavbarRenderLinkOptions) => ReactNode\n} & Partial<GetPayloadMarkdownDocsNavItemsOptions>\n\nconst cx = (...values: (false | null | string | undefined)[]): string =>\n values.filter(Boolean).join(' ')\n\nconst defaultClassNames = {\n activeLink: 'bg-cyan-400/10 text-cyan-200',\n childrenList: 'space-y-1',\n item: 'relative list-none',\n link: 'block rounded-lg px-3 py-2 text-sm leading-5 text-foreground/70 transition-colors hover:bg-white/[0.04] hover:text-foreground',\n list: 'm-0 flex list-none items-center gap-1 p-0',\n panel:\n 'absolute left-0 top-full z-50 hidden min-w-60 rounded-xl border border-border bg-background p-2 shadow-xl group-hover:block group-focus-within:block',\n root: 'relative',\n trigger:\n 'block rounded-lg px-3 py-2 text-sm leading-5 text-foreground/60 transition-colors group-hover:bg-white/[0.04] group-hover:text-foreground',\n} satisfies Required<PayloadMarkdownDocsNavbarClassNames>\n\nconst isItemActive = ({\n currentPath,\n item,\n}: {\n currentPath?: string\n item: PayloadMarkdownDocsNavItem\n}): boolean => {\n if (!currentPath) {\n return false\n }\n\n if (item.url) {\n const normalizedUrl = normalizeRoutePath(item.url)\n\n if (currentPath === normalizedUrl || isRouteDescendant(normalizedUrl, currentPath)) {\n return true\n }\n }\n\n return (item.children ?? []).some((child) =>\n isItemActive({\n currentPath,\n item: child,\n }),\n )\n}\n\nconst isItemCurrent = ({\n currentPath,\n item,\n}: {\n currentPath?: string\n item: PayloadMarkdownDocsNavItem\n}): boolean => Boolean(currentPath && item.url && currentPath === normalizeRoutePath(item.url))\n\nconst renderDefaultLink = ({\n children,\n className,\n current,\n href,\n}: PayloadMarkdownDocsNavbarRenderLinkOptions) => (\n <a aria-current={current ? 'page' : undefined} className={className} href={href}>\n {children}\n </a>\n)\n\nconst renderNavItems = ({\n classNames,\n currentPath,\n depth = 0,\n items,\n renderLink = renderDefaultLink,\n}: {\n classNames: PayloadMarkdownDocsNavbarClassNames\n currentPath?: string\n depth?: number\n items: PayloadMarkdownDocsNavItem[]\n renderLink?: (options: PayloadMarkdownDocsNavbarRenderLinkOptions) => ReactNode\n}): ReactNode => {\n if (items.length === 0) {\n return null\n }\n\n return (\n <ul className={cx(depth === 0 ? classNames.list : classNames.childrenList)}>\n {items.map((item) => {\n const active = isItemActive({\n currentPath,\n item,\n })\n const current = isItemCurrent({\n currentPath,\n item,\n })\n const linkClassName = cx(classNames.link, active && classNames.activeLink)\n const children = item.children?.length\n ? renderNavItems({\n classNames,\n currentPath,\n depth: depth + 1,\n items: item.children,\n renderLink,\n })\n : null\n\n return (\n <li className={cx('group', classNames.item)} key={`${item.collection}:${item.id}`}>\n {item.url ? (\n renderLink({\n active,\n children: item.label,\n className: linkClassName,\n current,\n href: item.url,\n item,\n })\n ) : (\n <span className={cx(classNames.trigger, active && classNames.activeLink)}>\n {item.label}\n </span>\n )}\n {children ? <div className={classNames.panel}>{children}</div> : null}\n </li>\n )\n })}\n </ul>\n )\n}\n\nexport const PayloadMarkdownDocsNavbar = async ({\n ariaLabel = 'Docs navigation',\n classNames,\n currentPath,\n items,\n renderLink,\n ...options\n}: PayloadMarkdownDocsNavbarProps) => {\n const navItems =\n items ??\n (options.payload\n ? await getPayloadMarkdownDocsNavItems({\n ...options,\n payload: options.payload,\n })\n : [])\n\n if (navItems.length === 0) {\n return null\n }\n\n const mergedClassNames = {\n ...defaultClassNames,\n ...classNames,\n }\n\n return (\n <nav aria-label={ariaLabel} className={mergedClassNames.root}>\n {renderNavItems({\n classNames: mergedClassNames,\n currentPath: currentPath ? normalizeRoutePath(currentPath) : undefined,\n items: navItems,\n renderLink,\n })}\n </nav>\n )\n}\n"],"names":["isRouteDescendant","normalizeRoutePath","getPayloadMarkdownDocsNavItems","cx","values","filter","Boolean","join","defaultClassNames","activeLink","childrenList","item","link","list","panel","root","trigger","isItemActive","currentPath","url","normalizedUrl","children","some","child","isItemCurrent","renderDefaultLink","className","current","href","a","aria-current","undefined","renderNavItems","classNames","depth","items","renderLink","length","ul","map","active","linkClassName","li","label","span","div","collection","id","PayloadMarkdownDocsNavbar","ariaLabel","options","navItems","payload","mergedClassNames","nav","aria-label"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
- export { getPayloadMarkdownDocsLinks } from './links.js';
2
- export type { GetPayloadMarkdownDocsLinksOptions, PayloadMarkdownDocsLink } from './links.js';
1
+ export { appendPayloadMarkdownDocsHeaderNavItems, getPayloadMarkdownDocsHeaderNavItems, getPayloadMarkdownDocsLinks, getPayloadMarkdownDocsNavItems, } from './links.js';
2
+ export type { AppendPayloadMarkdownDocsHeaderNavItemsOptions, GetPayloadMarkdownDocsHeaderNavItemsOptions, GetPayloadMarkdownDocsLinksOptions, GetPayloadMarkdownDocsNavItemsOptions, PayloadMarkdownDocsHeaderNavItem, PayloadMarkdownDocsHeaderNavLink, PayloadMarkdownDocsLink, PayloadMarkdownDocsNavCapacityOptions, PayloadMarkdownDocsNavItem, PayloadMarkdownDocsNavItemType, } from './links.js';
3
3
  export { createPayloadMarkdownDocsMarkdownResponse, resolvePayloadMarkdownDocsMarkdownRoute, } from './markdown.js';
4
4
  export type { ResolvedPayloadMarkdownDocsMarkdownRoute, ResolvePayloadMarkdownDocsMarkdownRouteOptions, } from './markdown.js';
5
5
  export { generatePayloadMarkdownDocsMetadata, getPayloadMarkdownDocsMetadata } from './metadata.js';
6
+ export { PayloadMarkdownDocsNavbar } from './PayloadMarkdownDocsNavbar.js';
7
+ export type { PayloadMarkdownDocsNavbarClassNames, PayloadMarkdownDocsNavbarProps, PayloadMarkdownDocsNavbarRenderLinkOptions, } from './PayloadMarkdownDocsNavbar.js';
6
8
  export { PayloadMarkdownDocsPage } from './PayloadMarkdownDocsPage.js';
7
9
  export type { PayloadMarkdownDocsPageProps } from './PayloadMarkdownDocsPage.js';
8
10
  export { getPayloadMarkdownDocsRoutePath, resolvePayloadMarkdownDocsRoute } from './route.js';
@@ -1,6 +1,7 @@
1
- export { getPayloadMarkdownDocsLinks } from './links.js';
1
+ export { appendPayloadMarkdownDocsHeaderNavItems, getPayloadMarkdownDocsHeaderNavItems, getPayloadMarkdownDocsLinks, getPayloadMarkdownDocsNavItems } from './links.js';
2
2
  export { createPayloadMarkdownDocsMarkdownResponse, resolvePayloadMarkdownDocsMarkdownRoute } from './markdown.js';
3
3
  export { generatePayloadMarkdownDocsMetadata, getPayloadMarkdownDocsMetadata } from './metadata.js';
4
+ export { PayloadMarkdownDocsNavbar } from './PayloadMarkdownDocsNavbar.js';
4
5
  export { PayloadMarkdownDocsPage } from './PayloadMarkdownDocsPage.js';
5
6
  export { getPayloadMarkdownDocsRoutePath, resolvePayloadMarkdownDocsRoute } from './route.js';
6
7
  export { buildPayloadMarkdownDocsSidebar, getPayloadMarkdownDocsSidebar } from './sidebar.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/index.ts"],"sourcesContent":["export { getPayloadMarkdownDocsLinks } from './links.js'\nexport type { GetPayloadMarkdownDocsLinksOptions, PayloadMarkdownDocsLink } from './links.js'\nexport {\n createPayloadMarkdownDocsMarkdownResponse,\n resolvePayloadMarkdownDocsMarkdownRoute,\n} from './markdown.js'\nexport type {\n ResolvedPayloadMarkdownDocsMarkdownRoute,\n ResolvePayloadMarkdownDocsMarkdownRouteOptions,\n} from './markdown.js'\nexport { generatePayloadMarkdownDocsMetadata, getPayloadMarkdownDocsMetadata } from './metadata.js'\nexport { PayloadMarkdownDocsPage } from './PayloadMarkdownDocsPage.js'\nexport type { PayloadMarkdownDocsPageProps } from './PayloadMarkdownDocsPage.js'\nexport { getPayloadMarkdownDocsRoutePath, resolvePayloadMarkdownDocsRoute } from './route.js'\nexport { buildPayloadMarkdownDocsSidebar, getPayloadMarkdownDocsSidebar } from './sidebar.js'\nexport type {\n BuildPayloadMarkdownDocsSidebarOptions,\n GetPayloadMarkdownDocsSidebarOptions,\n} from './sidebar.js'\nexport type {\n PayloadMarkdownDocsCollectionSlugs,\n PayloadMarkdownDocsDefaults,\n PayloadMarkdownDocsFindArgs,\n PayloadMarkdownDocsHeroImage,\n PayloadMarkdownDocsMetadata,\n PayloadMarkdownDocsOverrides,\n PayloadMarkdownDocsReadPayload,\n PayloadMarkdownDocsSidebarItem,\n ResolvedPayloadMarkdownDocsGroup,\n ResolvedPayloadMarkdownDocsRecord,\n ResolvedPayloadMarkdownDocsRoute,\n ResolvedPayloadMarkdownDocsSet,\n ResolvePayloadMarkdownDocsRouteOptions,\n} from './types.js'\n"],"names":["getPayloadMarkdownDocsLinks","createPayloadMarkdownDocsMarkdownResponse","resolvePayloadMarkdownDocsMarkdownRoute","generatePayloadMarkdownDocsMetadata","getPayloadMarkdownDocsMetadata","PayloadMarkdownDocsPage","getPayloadMarkdownDocsRoutePath","resolvePayloadMarkdownDocsRoute","buildPayloadMarkdownDocsSidebar","getPayloadMarkdownDocsSidebar"],"mappings":"AAAA,SAASA,2BAA2B,QAAQ,aAAY;AAExD,SACEC,yCAAyC,EACzCC,uCAAuC,QAClC,gBAAe;AAKtB,SAASC,mCAAmC,EAAEC,8BAA8B,QAAQ,gBAAe;AACnG,SAASC,uBAAuB,QAAQ,+BAA8B;AAEtE,SAASC,+BAA+B,EAAEC,+BAA+B,QAAQ,aAAY;AAC7F,SAASC,+BAA+B,EAAEC,6BAA6B,QAAQ,eAAc"}
1
+ {"version":3,"sources":["../../src/next/index.ts"],"sourcesContent":["export {\n appendPayloadMarkdownDocsHeaderNavItems,\n getPayloadMarkdownDocsHeaderNavItems,\n getPayloadMarkdownDocsLinks,\n getPayloadMarkdownDocsNavItems,\n} from './links.js'\nexport type {\n AppendPayloadMarkdownDocsHeaderNavItemsOptions,\n GetPayloadMarkdownDocsHeaderNavItemsOptions,\n GetPayloadMarkdownDocsLinksOptions,\n GetPayloadMarkdownDocsNavItemsOptions,\n PayloadMarkdownDocsHeaderNavItem,\n PayloadMarkdownDocsHeaderNavLink,\n PayloadMarkdownDocsLink,\n PayloadMarkdownDocsNavCapacityOptions,\n PayloadMarkdownDocsNavItem,\n PayloadMarkdownDocsNavItemType,\n} from './links.js'\nexport {\n createPayloadMarkdownDocsMarkdownResponse,\n resolvePayloadMarkdownDocsMarkdownRoute,\n} from './markdown.js'\nexport type {\n ResolvedPayloadMarkdownDocsMarkdownRoute,\n ResolvePayloadMarkdownDocsMarkdownRouteOptions,\n} from './markdown.js'\nexport { generatePayloadMarkdownDocsMetadata, getPayloadMarkdownDocsMetadata } from './metadata.js'\nexport { PayloadMarkdownDocsNavbar } from './PayloadMarkdownDocsNavbar.js'\nexport type {\n PayloadMarkdownDocsNavbarClassNames,\n PayloadMarkdownDocsNavbarProps,\n PayloadMarkdownDocsNavbarRenderLinkOptions,\n} from './PayloadMarkdownDocsNavbar.js'\nexport { PayloadMarkdownDocsPage } from './PayloadMarkdownDocsPage.js'\nexport type { PayloadMarkdownDocsPageProps } from './PayloadMarkdownDocsPage.js'\nexport { getPayloadMarkdownDocsRoutePath, resolvePayloadMarkdownDocsRoute } from './route.js'\nexport { buildPayloadMarkdownDocsSidebar, getPayloadMarkdownDocsSidebar } from './sidebar.js'\nexport type {\n BuildPayloadMarkdownDocsSidebarOptions,\n GetPayloadMarkdownDocsSidebarOptions,\n} from './sidebar.js'\nexport type {\n PayloadMarkdownDocsCollectionSlugs,\n PayloadMarkdownDocsDefaults,\n PayloadMarkdownDocsFindArgs,\n PayloadMarkdownDocsHeroImage,\n PayloadMarkdownDocsMetadata,\n PayloadMarkdownDocsOverrides,\n PayloadMarkdownDocsReadPayload,\n PayloadMarkdownDocsSidebarItem,\n ResolvedPayloadMarkdownDocsGroup,\n ResolvedPayloadMarkdownDocsRecord,\n ResolvedPayloadMarkdownDocsRoute,\n ResolvedPayloadMarkdownDocsSet,\n ResolvePayloadMarkdownDocsRouteOptions,\n} from './types.js'\n"],"names":["appendPayloadMarkdownDocsHeaderNavItems","getPayloadMarkdownDocsHeaderNavItems","getPayloadMarkdownDocsLinks","getPayloadMarkdownDocsNavItems","createPayloadMarkdownDocsMarkdownResponse","resolvePayloadMarkdownDocsMarkdownRoute","generatePayloadMarkdownDocsMetadata","getPayloadMarkdownDocsMetadata","PayloadMarkdownDocsNavbar","PayloadMarkdownDocsPage","getPayloadMarkdownDocsRoutePath","resolvePayloadMarkdownDocsRoute","buildPayloadMarkdownDocsSidebar","getPayloadMarkdownDocsSidebar"],"mappings":"AAAA,SACEA,uCAAuC,EACvCC,oCAAoC,EACpCC,2BAA2B,EAC3BC,8BAA8B,QACzB,aAAY;AAanB,SACEC,yCAAyC,EACzCC,uCAAuC,QAClC,gBAAe;AAKtB,SAASC,mCAAmC,EAAEC,8BAA8B,QAAQ,gBAAe;AACnG,SAASC,yBAAyB,QAAQ,iCAAgC;AAM1E,SAASC,uBAAuB,QAAQ,+BAA8B;AAEtE,SAASC,+BAA+B,EAAEC,+BAA+B,QAAQ,aAAY;AAC7F,SAASC,+BAA+B,EAAEC,6BAA6B,QAAQ,eAAc"}
@@ -3,9 +3,62 @@ export type PayloadMarkdownDocsLink = {
3
3
  label: string;
4
4
  url: string;
5
5
  };
6
+ export type PayloadMarkdownDocsNavItemType = 'docsGroup' | 'docsSet';
7
+ export type PayloadMarkdownDocsNavItem = {
8
+ children?: PayloadMarkdownDocsNavItem[];
9
+ collection: string;
10
+ id: string;
11
+ label: string;
12
+ order: number;
13
+ route: string;
14
+ type: PayloadMarkdownDocsNavItemType;
15
+ url?: string;
16
+ };
17
+ export type PayloadMarkdownDocsNavCapacityOptions = {
18
+ availableSlots?: number;
19
+ existingItemsCount?: number;
20
+ maxItems?: number;
21
+ };
22
+ export type GetPayloadMarkdownDocsNavItemsOptions = {
23
+ collections?: Pick<PayloadMarkdownDocsCollectionSlugs, 'docsGroups' | 'docsSets'>;
24
+ fetchLimit?: number;
25
+ includeDrafts?: boolean;
26
+ overrideAccess?: boolean;
27
+ payload: PayloadMarkdownDocsReadPayload;
28
+ } & PayloadMarkdownDocsNavCapacityOptions;
6
29
  export type GetPayloadMarkdownDocsLinksOptions = {
7
30
  collections?: Pick<PayloadMarkdownDocsCollectionSlugs, 'docsGroups' | 'docsSets'>;
31
+ includeDrafts?: boolean;
8
32
  overrideAccess?: boolean;
9
33
  payload: PayloadMarkdownDocsReadPayload;
10
34
  };
11
- export declare const getPayloadMarkdownDocsLinks: ({ collections, overrideAccess, payload, }: GetPayloadMarkdownDocsLinksOptions) => Promise<PayloadMarkdownDocsLink[]>;
35
+ export type PayloadMarkdownDocsHeaderNavLink = {
36
+ label: string;
37
+ newTab?: false;
38
+ reference: {
39
+ relationTo: string;
40
+ value: string;
41
+ };
42
+ type: 'reference';
43
+ } | {
44
+ label: string;
45
+ newTab?: false;
46
+ type: 'custom';
47
+ url: string;
48
+ };
49
+ export type PayloadMarkdownDocsHeaderNavItem = {
50
+ childItems?: PayloadMarkdownDocsHeaderNavItem[];
51
+ link: PayloadMarkdownDocsHeaderNavLink;
52
+ subItems?: PayloadMarkdownDocsHeaderNavItem[];
53
+ };
54
+ export type GetPayloadMarkdownDocsHeaderNavItemsOptions = {
55
+ existingItems?: unknown[];
56
+ mode?: 'relationship' | 'url';
57
+ } & GetPayloadMarkdownDocsNavItemsOptions;
58
+ export type AppendPayloadMarkdownDocsHeaderNavItemsOptions<TExistingItem> = {
59
+ existingItems: TExistingItem[];
60
+ } & GetPayloadMarkdownDocsHeaderNavItemsOptions;
61
+ export declare const getPayloadMarkdownDocsNavItems: ({ collections, fetchLimit, includeDrafts, overrideAccess, payload, ...capacityOptions }: GetPayloadMarkdownDocsNavItemsOptions) => Promise<PayloadMarkdownDocsNavItem[]>;
62
+ export declare const getPayloadMarkdownDocsLinks: ({ collections, includeDrafts, overrideAccess, payload, }: GetPayloadMarkdownDocsLinksOptions) => Promise<PayloadMarkdownDocsLink[]>;
63
+ export declare const getPayloadMarkdownDocsHeaderNavItems: ({ existingItems, existingItemsCount, mode, ...options }: GetPayloadMarkdownDocsHeaderNavItemsOptions) => Promise<PayloadMarkdownDocsHeaderNavItem[]>;
64
+ export declare const appendPayloadMarkdownDocsHeaderNavItems: <TExistingItem>({ existingItems, ...options }: AppendPayloadMarkdownDocsHeaderNavItemsOptions<TExistingItem>) => Promise<(PayloadMarkdownDocsHeaderNavItem | TExistingItem)[]>;
@@ -1,6 +1,19 @@
1
1
  import { DEFAULT_DOCS_GROUPS_COLLECTION_SLUG, DEFAULT_DOCS_SETS_COLLECTION_SLUG } from '../constants.js';
2
2
  import { deriveDocsSetRouteBase, joinRouteSegments } from '../routing/index.js';
3
- import { getRelationshipId, isRecord, isVisibleDocsSet, toResolvedDocsSet } from './records.js';
3
+ import { getRelationshipId, isRecord, isVisibleDocsSet, toResolvedDocsGroup, toResolvedDocsSet } from './records.js';
4
+ const getAvailableSlots = ({ availableSlots, existingItemsCount = 0, maxItems })=>{
5
+ if (availableSlots !== undefined) {
6
+ return Math.max(0, availableSlots);
7
+ }
8
+ if (maxItems === undefined) {
9
+ return undefined;
10
+ }
11
+ return Math.max(0, maxItems - existingItemsCount);
12
+ };
13
+ const applyTopLevelCapacity = (items, options)=>{
14
+ const availableSlots = getAvailableSlots(options);
15
+ return availableSlots === undefined ? items : items.slice(0, availableSlots);
16
+ };
4
17
  const getGroupRoutePath = ({ groupId, groupsById, seen = new Set() })=>{
5
18
  if (!groupId || seen.has(groupId)) {
6
19
  return undefined;
@@ -18,14 +31,172 @@ const getGroupRoutePath = ({ groupId, groupsById, seen = new Set() })=>{
18
31
  ])
19
32
  }), group.slug);
20
33
  };
21
- export const getPayloadMarkdownDocsLinks = async ({ collections, overrideAccess = true, payload })=>{
34
+ const sortByOrderThenLabel = (items)=>[
35
+ ...items
36
+ ].sort((first, second)=>{
37
+ if (first.order !== second.order) {
38
+ return first.order - second.order;
39
+ }
40
+ return first.label.localeCompare(second.label);
41
+ });
42
+ const getFirstLinkableUrl = (item)=>{
43
+ if (item.url) {
44
+ return item.url;
45
+ }
46
+ for (const child of item.children ?? []){
47
+ const url = getFirstLinkableUrl(child);
48
+ if (url) {
49
+ return url;
50
+ }
51
+ }
52
+ return undefined;
53
+ };
54
+ export const getPayloadMarkdownDocsNavItems = async ({ collections, fetchLimit = 1000, includeDrafts = false, overrideAccess = true, payload, ...capacityOptions })=>{
55
+ const docsGroupsCollectionSlug = collections?.docsGroups ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG;
56
+ const docsSetsCollectionSlug = collections?.docsSets ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG;
57
+ const [docsSetsResult, docsGroupsResult] = await Promise.all([
58
+ payload.find({
59
+ collection: docsSetsCollectionSlug,
60
+ depth: 0,
61
+ draft: includeDrafts,
62
+ limit: fetchLimit,
63
+ overrideAccess
64
+ }),
65
+ payload.find({
66
+ collection: docsGroupsCollectionSlug,
67
+ depth: 0,
68
+ limit: fetchLimit,
69
+ overrideAccess
70
+ })
71
+ ]);
72
+ const groupsById = new Map(docsGroupsResult.docs.flatMap((group)=>{
73
+ if (!isRecord(group)) {
74
+ return [];
75
+ }
76
+ const id = getRelationshipId(group);
77
+ return id ? [
78
+ [
79
+ id,
80
+ group
81
+ ]
82
+ ] : [];
83
+ }));
84
+ const childGroupIdsByParentId = new Map();
85
+ const topLevelGroupIds = [];
86
+ for (const [groupId, group] of groupsById){
87
+ const parentId = getRelationshipId(group.parent);
88
+ if (parentId) {
89
+ childGroupIdsByParentId.set(parentId, [
90
+ ...childGroupIdsByParentId.get(parentId) ?? [],
91
+ groupId
92
+ ]);
93
+ } else {
94
+ topLevelGroupIds.push(groupId);
95
+ }
96
+ }
97
+ const docsSetItemsByGroupId = new Map();
98
+ const topLevelDocsSetItems = [];
99
+ for (const doc of docsSetsResult.docs){
100
+ const docsSet = toResolvedDocsSet(doc);
101
+ if (!docsSet?.slug || !isRecord(doc) || !isVisibleDocsSet({
102
+ docsSet,
103
+ includeDrafts
104
+ })) {
105
+ continue;
106
+ }
107
+ const groupId = getRelationshipId(doc.group);
108
+ const groupRoutePath = groupId ? getGroupRoutePath({
109
+ groupId,
110
+ groupsById
111
+ }) : undefined;
112
+ if (groupId && !groupRoutePath) {
113
+ continue;
114
+ }
115
+ const item = {
116
+ id: docsSet.id,
117
+ type: 'docsSet',
118
+ collection: docsSetsCollectionSlug,
119
+ label: docsSet.navTitle ?? docsSet.title,
120
+ order: docsSet.order,
121
+ route: deriveDocsSetRouteBase({
122
+ docsSetSlug: docsSet.slug,
123
+ groupRoutePath
124
+ }),
125
+ url: deriveDocsSetRouteBase({
126
+ docsSetSlug: docsSet.slug,
127
+ groupRoutePath
128
+ })
129
+ };
130
+ if (groupId) {
131
+ docsSetItemsByGroupId.set(groupId, [
132
+ ...docsSetItemsByGroupId.get(groupId) ?? [],
133
+ item
134
+ ]);
135
+ } else {
136
+ topLevelDocsSetItems.push(item);
137
+ }
138
+ }
139
+ const buildGroupItem = (groupId, seen = new Set())=>{
140
+ if (seen.has(groupId)) {
141
+ return undefined;
142
+ }
143
+ const doc = groupsById.get(groupId);
144
+ const group = toResolvedDocsGroup(doc);
145
+ const routePath = getGroupRoutePath({
146
+ groupId,
147
+ groupsById
148
+ });
149
+ if (!group || !routePath) {
150
+ return undefined;
151
+ }
152
+ const nextSeen = new Set([
153
+ groupId,
154
+ ...seen
155
+ ]);
156
+ const childGroups = (childGroupIdsByParentId.get(groupId) ?? []).flatMap((childGroupId)=>{
157
+ const item = buildGroupItem(childGroupId, nextSeen);
158
+ return item ? [
159
+ item
160
+ ] : [];
161
+ });
162
+ const childDocsSets = docsSetItemsByGroupId.get(groupId) ?? [];
163
+ const children = sortByOrderThenLabel([
164
+ ...childGroups,
165
+ ...childDocsSets
166
+ ]);
167
+ return {
168
+ ...children.length > 0 ? {
169
+ children
170
+ } : {},
171
+ id: group.id,
172
+ type: 'docsGroup',
173
+ collection: docsGroupsCollectionSlug,
174
+ label: group.navTitle ?? group.title,
175
+ order: group.order,
176
+ route: routePath,
177
+ ...group.serveIndex ? {
178
+ url: routePath
179
+ } : {}
180
+ };
181
+ };
182
+ return applyTopLevelCapacity(sortByOrderThenLabel([
183
+ ...topLevelGroupIds.flatMap((groupId)=>{
184
+ const item = buildGroupItem(groupId);
185
+ return item ? [
186
+ item
187
+ ] : [];
188
+ }),
189
+ ...topLevelDocsSetItems
190
+ ]), capacityOptions);
191
+ };
192
+ export const getPayloadMarkdownDocsLinks = async ({ collections, includeDrafts = false, overrideAccess = true, payload })=>{
22
193
  const docsGroupsCollectionSlug = collections?.docsGroups ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG;
23
194
  const docsSetsCollectionSlug = collections?.docsSets ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG;
24
195
  const [docsSetsResult, docsGroupsResult] = await Promise.all([
25
196
  payload.find({
26
197
  collection: docsSetsCollectionSlug,
27
198
  depth: 0,
28
- draft: false,
199
+ draft: includeDrafts,
29
200
  limit: 1000,
30
201
  overrideAccess
31
202
  }),
@@ -51,7 +222,8 @@ export const getPayloadMarkdownDocsLinks = async ({ collections, overrideAccess
51
222
  return docsSetsResult.docs.flatMap((doc)=>{
52
223
  const docsSet = toResolvedDocsSet(doc);
53
224
  if (!docsSet?.slug || !isRecord(doc) || !isVisibleDocsSet({
54
- docsSet
225
+ docsSet,
226
+ includeDrafts
55
227
  })) {
56
228
  return [];
57
229
  }
@@ -78,5 +250,65 @@ export const getPayloadMarkdownDocsLinks = async ({ collections, overrideAccess
78
250
  url
79
251
  }));
80
252
  };
253
+ const toHeaderLink = ({ item, mode })=>{
254
+ if (mode === 'relationship') {
255
+ return {
256
+ type: 'reference',
257
+ label: item.label,
258
+ reference: {
259
+ relationTo: item.collection,
260
+ value: item.id
261
+ }
262
+ };
263
+ }
264
+ const url = getFirstLinkableUrl(item);
265
+ return url ? {
266
+ type: 'custom',
267
+ label: item.label,
268
+ url
269
+ } : undefined;
270
+ };
271
+ const toHeaderNavItem = (item, mode, depth = 0)=>{
272
+ const link = toHeaderLink({
273
+ item,
274
+ mode
275
+ });
276
+ if (!link) {
277
+ return undefined;
278
+ }
279
+ const children = (item.children ?? []).flatMap((child)=>{
280
+ const childItem = toHeaderNavItem(child, mode, depth + 1);
281
+ return childItem ? [
282
+ childItem
283
+ ] : [];
284
+ });
285
+ return {
286
+ link,
287
+ ...children.length > 0 ? depth === 0 ? {
288
+ subItems: children
289
+ } : {
290
+ childItems: children
291
+ } : {}
292
+ };
293
+ };
294
+ export const getPayloadMarkdownDocsHeaderNavItems = async ({ existingItems, existingItemsCount = existingItems?.length ?? 0, mode = 'url', ...options })=>{
295
+ const items = await getPayloadMarkdownDocsNavItems({
296
+ ...options,
297
+ existingItemsCount
298
+ });
299
+ return items.flatMap((item)=>{
300
+ const headerItem = toHeaderNavItem(item, mode);
301
+ return headerItem ? [
302
+ headerItem
303
+ ] : [];
304
+ });
305
+ };
306
+ export const appendPayloadMarkdownDocsHeaderNavItems = async ({ existingItems, ...options })=>[
307
+ ...existingItems,
308
+ ...await getPayloadMarkdownDocsHeaderNavItems({
309
+ ...options,
310
+ existingItems
311
+ })
312
+ ];
81
313
 
82
314
  //# sourceMappingURL=links.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/links.ts"],"sourcesContent":["import type { PayloadMarkdownDocsCollectionSlugs, PayloadMarkdownDocsReadPayload } from './types.js'\n\nimport {\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n} from '../constants.js'\nimport { deriveDocsSetRouteBase, joinRouteSegments } from '../routing/index.js'\nimport { getRelationshipId, isRecord, isVisibleDocsSet, toResolvedDocsSet } from './records.js'\n\nexport type PayloadMarkdownDocsLink = {\n label: string\n url: string\n}\n\nexport type GetPayloadMarkdownDocsLinksOptions = {\n collections?: Pick<PayloadMarkdownDocsCollectionSlugs, 'docsGroups' | 'docsSets'>\n overrideAccess?: boolean\n payload: PayloadMarkdownDocsReadPayload\n}\n\nconst getGroupRoutePath = ({\n groupId,\n groupsById,\n seen = new Set<string>(),\n}: {\n groupId?: string\n groupsById: Map<string, unknown>\n seen?: Set<string>\n}): string | undefined => {\n if (!groupId || seen.has(groupId)) {\n return undefined\n }\n\n const group = groupsById.get(groupId)\n\n if (!isRecord(group) || typeof group.slug !== 'string') {\n return undefined\n }\n\n return joinRouteSegments(\n getGroupRoutePath({\n groupId: getRelationshipId(group.parent),\n groupsById,\n seen: new Set([groupId, ...seen]),\n }),\n group.slug,\n )\n}\n\nexport const getPayloadMarkdownDocsLinks = async ({\n collections,\n overrideAccess = true,\n payload,\n}: GetPayloadMarkdownDocsLinksOptions): Promise<PayloadMarkdownDocsLink[]> => {\n const docsGroupsCollectionSlug = collections?.docsGroups ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG\n const docsSetsCollectionSlug = collections?.docsSets ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG\n const [docsSetsResult, docsGroupsResult] = await Promise.all([\n payload.find({\n collection: docsSetsCollectionSlug,\n depth: 0,\n draft: false,\n limit: 1000,\n overrideAccess,\n }),\n payload.find({\n collection: docsGroupsCollectionSlug,\n depth: 0,\n limit: 1000,\n overrideAccess,\n }),\n ])\n const groupsById = new Map(\n docsGroupsResult.docs.flatMap((group) => {\n if (!isRecord(group)) {\n return []\n }\n\n const id = getRelationshipId(group)\n\n return id ? [[id, group]] : []\n }),\n )\n\n return docsSetsResult.docs\n .flatMap((doc) => {\n const docsSet = toResolvedDocsSet(doc)\n\n if (!docsSet?.slug || !isRecord(doc) || !isVisibleDocsSet({ docsSet })) {\n return []\n }\n\n return [\n {\n label: docsSet.navTitle ?? docsSet.title,\n order: docsSet.order,\n url: deriveDocsSetRouteBase({\n docsSetSlug: docsSet.slug,\n groupRoutePath: getGroupRoutePath({\n groupId: getRelationshipId(doc.group),\n groupsById,\n }),\n }),\n },\n ]\n })\n .sort((first, second) => {\n if (first.order !== second.order) {\n return first.order - second.order\n }\n\n return first.label.localeCompare(second.label)\n })\n .map(({ label, url }) => ({\n label,\n url,\n }))\n}\n"],"names":["DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","deriveDocsSetRouteBase","joinRouteSegments","getRelationshipId","isRecord","isVisibleDocsSet","toResolvedDocsSet","getGroupRoutePath","groupId","groupsById","seen","Set","has","undefined","group","get","slug","parent","getPayloadMarkdownDocsLinks","collections","overrideAccess","payload","docsGroupsCollectionSlug","docsGroups","docsSetsCollectionSlug","docsSets","docsSetsResult","docsGroupsResult","Promise","all","find","collection","depth","draft","limit","Map","docs","flatMap","id","doc","docsSet","label","navTitle","title","order","url","docsSetSlug","groupRoutePath","sort","first","second","localeCompare","map"],"mappings":"AAEA,SACEA,mCAAmC,EACnCC,iCAAiC,QAC5B,kBAAiB;AACxB,SAASC,sBAAsB,EAAEC,iBAAiB,QAAQ,sBAAqB;AAC/E,SAASC,iBAAiB,EAAEC,QAAQ,EAAEC,gBAAgB,EAAEC,iBAAiB,QAAQ,eAAc;AAa/F,MAAMC,oBAAoB,CAAC,EACzBC,OAAO,EACPC,UAAU,EACVC,OAAO,IAAIC,KAAa,EAKzB;IACC,IAAI,CAACH,WAAWE,KAAKE,GAAG,CAACJ,UAAU;QACjC,OAAOK;IACT;IAEA,MAAMC,QAAQL,WAAWM,GAAG,CAACP;IAE7B,IAAI,CAACJ,SAASU,UAAU,OAAOA,MAAME,IAAI,KAAK,UAAU;QACtD,OAAOH;IACT;IAEA,OAAOX,kBACLK,kBAAkB;QAChBC,SAASL,kBAAkBW,MAAMG,MAAM;QACvCR;QACAC,MAAM,IAAIC,IAAI;YAACH;eAAYE;SAAK;IAClC,IACAI,MAAME,IAAI;AAEd;AAEA,OAAO,MAAME,8BAA8B,OAAO,EAChDC,WAAW,EACXC,iBAAiB,IAAI,EACrBC,OAAO,EAC4B;IACnC,MAAMC,2BAA2BH,aAAaI,cAAcxB;IAC5D,MAAMyB,yBAAyBL,aAAaM,YAAYzB;IACxD,MAAM,CAAC0B,gBAAgBC,iBAAiB,GAAG,MAAMC,QAAQC,GAAG,CAAC;QAC3DR,QAAQS,IAAI,CAAC;YACXC,YAAYP;YACZQ,OAAO;YACPC,OAAO;YACPC,OAAO;YACPd;QACF;QACAC,QAAQS,IAAI,CAAC;YACXC,YAAYT;YACZU,OAAO;YACPE,OAAO;YACPd;QACF;KACD;IACD,MAAMX,aAAa,IAAI0B,IACrBR,iBAAiBS,IAAI,CAACC,OAAO,CAAC,CAACvB;QAC7B,IAAI,CAACV,SAASU,QAAQ;YACpB,OAAO,EAAE;QACX;QAEA,MAAMwB,KAAKnC,kBAAkBW;QAE7B,OAAOwB,KAAK;YAAC;gBAACA;gBAAIxB;aAAM;SAAC,GAAG,EAAE;IAChC;IAGF,OAAOY,eAAeU,IAAI,CACvBC,OAAO,CAAC,CAACE;QACR,MAAMC,UAAUlC,kBAAkBiC;QAElC,IAAI,CAACC,SAASxB,QAAQ,CAACZ,SAASmC,QAAQ,CAAClC,iBAAiB;YAAEmC;QAAQ,IAAI;YACtE,OAAO,EAAE;QACX;QAEA,OAAO;YACL;gBACEC,OAAOD,QAAQE,QAAQ,IAAIF,QAAQG,KAAK;gBACxCC,OAAOJ,QAAQI,KAAK;gBACpBC,KAAK5C,uBAAuB;oBAC1B6C,aAAaN,QAAQxB,IAAI;oBACzB+B,gBAAgBxC,kBAAkB;wBAChCC,SAASL,kBAAkBoC,IAAIzB,KAAK;wBACpCL;oBACF;gBACF;YACF;SACD;IACH,GACCuC,IAAI,CAAC,CAACC,OAAOC;QACZ,IAAID,MAAML,KAAK,KAAKM,OAAON,KAAK,EAAE;YAChC,OAAOK,MAAML,KAAK,GAAGM,OAAON,KAAK;QACnC;QAEA,OAAOK,MAAMR,KAAK,CAACU,aAAa,CAACD,OAAOT,KAAK;IAC/C,GACCW,GAAG,CAAC,CAAC,EAAEX,KAAK,EAAEI,GAAG,EAAE,GAAM,CAAA;YACxBJ;YACAI;QACF,CAAA;AACJ,EAAC"}
1
+ {"version":3,"sources":["../../src/next/links.ts"],"sourcesContent":["import type { PayloadMarkdownDocsCollectionSlugs, PayloadMarkdownDocsReadPayload } from './types.js'\n\nimport {\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n} from '../constants.js'\nimport { deriveDocsSetRouteBase, joinRouteSegments } from '../routing/index.js'\nimport {\n getRelationshipId,\n isRecord,\n isVisibleDocsSet,\n toResolvedDocsGroup,\n toResolvedDocsSet,\n} from './records.js'\n\nexport type PayloadMarkdownDocsLink = {\n label: string\n url: string\n}\n\nexport type PayloadMarkdownDocsNavItemType = 'docsGroup' | 'docsSet'\n\nexport type PayloadMarkdownDocsNavItem = {\n children?: PayloadMarkdownDocsNavItem[]\n collection: string\n id: string\n label: string\n order: number\n route: string\n type: PayloadMarkdownDocsNavItemType\n url?: string\n}\n\nexport type PayloadMarkdownDocsNavCapacityOptions = {\n availableSlots?: number\n existingItemsCount?: number\n maxItems?: number\n}\n\nexport type GetPayloadMarkdownDocsNavItemsOptions = {\n collections?: Pick<PayloadMarkdownDocsCollectionSlugs, 'docsGroups' | 'docsSets'>\n fetchLimit?: number\n includeDrafts?: boolean\n overrideAccess?: boolean\n payload: PayloadMarkdownDocsReadPayload\n} & PayloadMarkdownDocsNavCapacityOptions\n\nexport type GetPayloadMarkdownDocsLinksOptions = {\n collections?: Pick<PayloadMarkdownDocsCollectionSlugs, 'docsGroups' | 'docsSets'>\n includeDrafts?: boolean\n overrideAccess?: boolean\n payload: PayloadMarkdownDocsReadPayload\n}\n\nexport type PayloadMarkdownDocsHeaderNavLink =\n | {\n label: string\n newTab?: false\n reference: {\n relationTo: string\n value: string\n }\n type: 'reference'\n }\n | {\n label: string\n newTab?: false\n type: 'custom'\n url: string\n }\n\nexport type PayloadMarkdownDocsHeaderNavItem = {\n childItems?: PayloadMarkdownDocsHeaderNavItem[]\n link: PayloadMarkdownDocsHeaderNavLink\n subItems?: PayloadMarkdownDocsHeaderNavItem[]\n}\n\nexport type GetPayloadMarkdownDocsHeaderNavItemsOptions = {\n existingItems?: unknown[]\n mode?: 'relationship' | 'url'\n} & GetPayloadMarkdownDocsNavItemsOptions\n\nexport type AppendPayloadMarkdownDocsHeaderNavItemsOptions<TExistingItem> = {\n existingItems: TExistingItem[]\n} & GetPayloadMarkdownDocsHeaderNavItemsOptions\n\nconst getAvailableSlots = ({\n availableSlots,\n existingItemsCount = 0,\n maxItems,\n}: PayloadMarkdownDocsNavCapacityOptions): number | undefined => {\n if (availableSlots !== undefined) {\n return Math.max(0, availableSlots)\n }\n\n if (maxItems === undefined) {\n return undefined\n }\n\n return Math.max(0, maxItems - existingItemsCount)\n}\n\nconst applyTopLevelCapacity = (\n items: PayloadMarkdownDocsNavItem[],\n options: PayloadMarkdownDocsNavCapacityOptions,\n): PayloadMarkdownDocsNavItem[] => {\n const availableSlots = getAvailableSlots(options)\n\n return availableSlots === undefined ? items : items.slice(0, availableSlots)\n}\n\nconst getGroupRoutePath = ({\n groupId,\n groupsById,\n seen = new Set<string>(),\n}: {\n groupId?: string\n groupsById: Map<string, unknown>\n seen?: Set<string>\n}): string | undefined => {\n if (!groupId || seen.has(groupId)) {\n return undefined\n }\n\n const group = groupsById.get(groupId)\n\n if (!isRecord(group) || typeof group.slug !== 'string') {\n return undefined\n }\n\n return joinRouteSegments(\n getGroupRoutePath({\n groupId: getRelationshipId(group.parent),\n groupsById,\n seen: new Set([groupId, ...seen]),\n }),\n group.slug,\n )\n}\n\nconst sortByOrderThenLabel = <T extends { label: string; order: number }>(items: T[]): T[] =>\n [...items].sort((first, second) => {\n if (first.order !== second.order) {\n return first.order - second.order\n }\n\n return first.label.localeCompare(second.label)\n })\n\nconst getFirstLinkableUrl = (item: PayloadMarkdownDocsNavItem): string | undefined => {\n if (item.url) {\n return item.url\n }\n\n for (const child of item.children ?? []) {\n const url = getFirstLinkableUrl(child)\n\n if (url) {\n return url\n }\n }\n\n return undefined\n}\n\nexport const getPayloadMarkdownDocsNavItems = async ({\n collections,\n fetchLimit = 1000,\n includeDrafts = false,\n overrideAccess = true,\n payload,\n ...capacityOptions\n}: GetPayloadMarkdownDocsNavItemsOptions): Promise<PayloadMarkdownDocsNavItem[]> => {\n const docsGroupsCollectionSlug = collections?.docsGroups ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG\n const docsSetsCollectionSlug = collections?.docsSets ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG\n const [docsSetsResult, docsGroupsResult] = await Promise.all([\n payload.find({\n collection: docsSetsCollectionSlug,\n depth: 0,\n draft: includeDrafts,\n limit: fetchLimit,\n overrideAccess,\n }),\n payload.find({\n collection: docsGroupsCollectionSlug,\n depth: 0,\n limit: fetchLimit,\n overrideAccess,\n }),\n ])\n const groupsById = new Map(\n docsGroupsResult.docs.flatMap((group) => {\n if (!isRecord(group)) {\n return []\n }\n\n const id = getRelationshipId(group)\n\n return id ? [[id, group]] : []\n }),\n )\n const childGroupIdsByParentId = new Map<string, string[]>()\n const topLevelGroupIds: string[] = []\n\n for (const [groupId, group] of groupsById) {\n const parentId = getRelationshipId(group.parent)\n\n if (parentId) {\n childGroupIdsByParentId.set(parentId, [\n ...(childGroupIdsByParentId.get(parentId) ?? []),\n groupId,\n ])\n } else {\n topLevelGroupIds.push(groupId)\n }\n }\n\n const docsSetItemsByGroupId = new Map<string, PayloadMarkdownDocsNavItem[]>()\n const topLevelDocsSetItems: PayloadMarkdownDocsNavItem[] = []\n\n for (const doc of docsSetsResult.docs) {\n const docsSet = toResolvedDocsSet(doc)\n\n if (!docsSet?.slug || !isRecord(doc) || !isVisibleDocsSet({ docsSet, includeDrafts })) {\n continue\n }\n\n const groupId = getRelationshipId(doc.group)\n const groupRoutePath = groupId\n ? getGroupRoutePath({\n groupId,\n groupsById,\n })\n : undefined\n\n if (groupId && !groupRoutePath) {\n continue\n }\n\n const item: PayloadMarkdownDocsNavItem = {\n id: docsSet.id,\n type: 'docsSet',\n collection: docsSetsCollectionSlug,\n label: docsSet.navTitle ?? docsSet.title,\n order: docsSet.order,\n route: deriveDocsSetRouteBase({\n docsSetSlug: docsSet.slug,\n groupRoutePath,\n }),\n url: deriveDocsSetRouteBase({\n docsSetSlug: docsSet.slug,\n groupRoutePath,\n }),\n }\n\n if (groupId) {\n docsSetItemsByGroupId.set(groupId, [...(docsSetItemsByGroupId.get(groupId) ?? []), item])\n } else {\n topLevelDocsSetItems.push(item)\n }\n }\n\n const buildGroupItem = (\n groupId: string,\n seen = new Set<string>(),\n ): PayloadMarkdownDocsNavItem | undefined => {\n if (seen.has(groupId)) {\n return undefined\n }\n\n const doc = groupsById.get(groupId)\n const group = toResolvedDocsGroup(doc)\n const routePath = getGroupRoutePath({\n groupId,\n groupsById,\n })\n\n if (!group || !routePath) {\n return undefined\n }\n\n const nextSeen = new Set([groupId, ...seen])\n const childGroups = (childGroupIdsByParentId.get(groupId) ?? []).flatMap((childGroupId) => {\n const item = buildGroupItem(childGroupId, nextSeen)\n\n return item ? [item] : []\n })\n const childDocsSets = docsSetItemsByGroupId.get(groupId) ?? []\n const children = sortByOrderThenLabel([...childGroups, ...childDocsSets])\n\n return {\n ...(children.length > 0 ? { children } : {}),\n id: group.id,\n type: 'docsGroup',\n collection: docsGroupsCollectionSlug,\n label: group.navTitle ?? group.title,\n order: group.order,\n route: routePath,\n ...(group.serveIndex ? { url: routePath } : {}),\n }\n }\n\n return applyTopLevelCapacity(\n sortByOrderThenLabel([\n ...topLevelGroupIds.flatMap((groupId) => {\n const item = buildGroupItem(groupId)\n\n return item ? [item] : []\n }),\n ...topLevelDocsSetItems,\n ]),\n capacityOptions,\n )\n}\n\nexport const getPayloadMarkdownDocsLinks = async ({\n collections,\n includeDrafts = false,\n overrideAccess = true,\n payload,\n}: GetPayloadMarkdownDocsLinksOptions): Promise<PayloadMarkdownDocsLink[]> => {\n const docsGroupsCollectionSlug = collections?.docsGroups ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG\n const docsSetsCollectionSlug = collections?.docsSets ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG\n const [docsSetsResult, docsGroupsResult] = await Promise.all([\n payload.find({\n collection: docsSetsCollectionSlug,\n depth: 0,\n draft: includeDrafts,\n limit: 1000,\n overrideAccess,\n }),\n payload.find({\n collection: docsGroupsCollectionSlug,\n depth: 0,\n limit: 1000,\n overrideAccess,\n }),\n ])\n const groupsById = new Map(\n docsGroupsResult.docs.flatMap((group) => {\n if (!isRecord(group)) {\n return []\n }\n\n const id = getRelationshipId(group)\n\n return id ? [[id, group]] : []\n }),\n )\n\n return docsSetsResult.docs\n .flatMap((doc) => {\n const docsSet = toResolvedDocsSet(doc)\n\n if (!docsSet?.slug || !isRecord(doc) || !isVisibleDocsSet({ docsSet, includeDrafts })) {\n return []\n }\n\n return [\n {\n label: docsSet.navTitle ?? docsSet.title,\n order: docsSet.order,\n url: deriveDocsSetRouteBase({\n docsSetSlug: docsSet.slug,\n groupRoutePath: getGroupRoutePath({\n groupId: getRelationshipId(doc.group),\n groupsById,\n }),\n }),\n },\n ]\n })\n .sort((first, second) => {\n if (first.order !== second.order) {\n return first.order - second.order\n }\n\n return first.label.localeCompare(second.label)\n })\n .map(({ label, url }) => ({\n label,\n url,\n }))\n}\n\nconst toHeaderLink = ({\n item,\n mode,\n}: {\n item: PayloadMarkdownDocsNavItem\n mode: 'relationship' | 'url'\n}): PayloadMarkdownDocsHeaderNavLink | undefined => {\n if (mode === 'relationship') {\n return {\n type: 'reference',\n label: item.label,\n reference: {\n relationTo: item.collection,\n value: item.id,\n },\n }\n }\n\n const url = getFirstLinkableUrl(item)\n\n return url\n ? {\n type: 'custom',\n label: item.label,\n url,\n }\n : undefined\n}\n\nconst toHeaderNavItem = (\n item: PayloadMarkdownDocsNavItem,\n mode: 'relationship' | 'url',\n depth = 0,\n): PayloadMarkdownDocsHeaderNavItem | undefined => {\n const link = toHeaderLink({\n item,\n mode,\n })\n\n if (!link) {\n return undefined\n }\n\n const children = (item.children ?? []).flatMap((child) => {\n const childItem = toHeaderNavItem(child, mode, depth + 1)\n\n return childItem ? [childItem] : []\n })\n\n return {\n link,\n ...(children.length > 0\n ? depth === 0\n ? { subItems: children }\n : { childItems: children }\n : {}),\n }\n}\n\nexport const getPayloadMarkdownDocsHeaderNavItems = async ({\n existingItems,\n existingItemsCount = existingItems?.length ?? 0,\n mode = 'url',\n ...options\n}: GetPayloadMarkdownDocsHeaderNavItemsOptions): Promise<PayloadMarkdownDocsHeaderNavItem[]> => {\n const items = await getPayloadMarkdownDocsNavItems({\n ...options,\n existingItemsCount,\n })\n\n return items.flatMap((item) => {\n const headerItem = toHeaderNavItem(item, mode)\n\n return headerItem ? [headerItem] : []\n })\n}\n\nexport const appendPayloadMarkdownDocsHeaderNavItems = async <TExistingItem>({\n existingItems,\n ...options\n}: AppendPayloadMarkdownDocsHeaderNavItemsOptions<TExistingItem>): Promise<\n (PayloadMarkdownDocsHeaderNavItem | TExistingItem)[]\n> => [\n ...existingItems,\n ...(await getPayloadMarkdownDocsHeaderNavItems({\n ...options,\n existingItems,\n })),\n]\n"],"names":["DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","deriveDocsSetRouteBase","joinRouteSegments","getRelationshipId","isRecord","isVisibleDocsSet","toResolvedDocsGroup","toResolvedDocsSet","getAvailableSlots","availableSlots","existingItemsCount","maxItems","undefined","Math","max","applyTopLevelCapacity","items","options","slice","getGroupRoutePath","groupId","groupsById","seen","Set","has","group","get","slug","parent","sortByOrderThenLabel","sort","first","second","order","label","localeCompare","getFirstLinkableUrl","item","url","child","children","getPayloadMarkdownDocsNavItems","collections","fetchLimit","includeDrafts","overrideAccess","payload","capacityOptions","docsGroupsCollectionSlug","docsGroups","docsSetsCollectionSlug","docsSets","docsSetsResult","docsGroupsResult","Promise","all","find","collection","depth","draft","limit","Map","docs","flatMap","id","childGroupIdsByParentId","topLevelGroupIds","parentId","set","push","docsSetItemsByGroupId","topLevelDocsSetItems","doc","docsSet","groupRoutePath","type","navTitle","title","route","docsSetSlug","buildGroupItem","routePath","nextSeen","childGroups","childGroupId","childDocsSets","length","serveIndex","getPayloadMarkdownDocsLinks","map","toHeaderLink","mode","reference","relationTo","value","toHeaderNavItem","link","childItem","subItems","childItems","getPayloadMarkdownDocsHeaderNavItems","existingItems","headerItem","appendPayloadMarkdownDocsHeaderNavItems"],"mappings":"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"}
@@ -69,6 +69,7 @@ const getRepositoryName = (repository)=>{
69
69
  const [, name] = repository.split('/', 2);
70
70
  return name ?? repository;
71
71
  };
72
+ const isReleaseTagRef = (claims)=>claims.event_name === 'release' && claims.ref.startsWith('refs/tags/');
72
73
  const repositoryMatches = ({ allowed, owner, repository })=>{
73
74
  const normalized = allowed.trim();
74
75
  if (!normalized) {
@@ -179,7 +180,7 @@ export const verifyGitHubOidcToken = async ({ config, fetchJson, now = new Date(
179
180
  return issue('oidc_owner_not_allowed', `GitHub OIDC token repository owner "${claims.repository_owner}" is not trusted.`);
180
181
  }
181
182
  const repositoryName = getRepositoryName(claims.repository);
182
- if (!includesIfConfigured(config.allowedRefs, claims.ref)) {
183
+ if (!includesIfConfigured(config.allowedRefs, claims.ref) && !isReleaseTagRef(claims)) {
183
184
  return issue('oidc_ref_not_allowed', `GitHub OIDC token ref "${claims.ref}" is not allowed for "${repositoryName}".`);
184
185
  }
185
186
  const workflowRef = claims.workflow_ref ?? claims.job_workflow_ref;
@@ -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 { FetchJson } from './jwks.js'\n\nimport {\n DEFAULT_GITHUB_OIDC_ISSUER,\n DEFAULT_MAX_SKEW_SECONDS,\n} from '../constants.js'\nimport {\n fetchJwks,\n findJwkByKid,\n getGithubOidcJwksUrl,\n} from './jwks.js'\nimport { decodeJwt } from './jwt.js'\n\nexport type GitHubOidcErrorCode =\n | 'oidc_expired'\n | 'oidc_invalid_audience'\n | 'oidc_invalid_issuer'\n | 'oidc_invalid_token'\n | 'oidc_jwks_unavailable'\n | 'oidc_missing_claim'\n | 'oidc_missing_jti'\n | 'oidc_not_yet_valid'\n | 'oidc_owner_not_allowed'\n | 'oidc_pull_request_not_allowed'\n | 'oidc_ref_not_allowed'\n | 'oidc_repository_not_allowed'\n | 'oidc_workflow_not_allowed'\n\nexport type GitHubOidcClaims = {\n actor?: string\n aud: string | string[]\n environment?: string\n event_name?: string\n exp: number\n iat: number\n iss: string\n job_workflow_ref?: string\n jti: string\n nbf?: number\n ref: string\n repository: string\n repository_owner: string\n sha?: string\n sub: string\n workflow?: string\n workflow_ref?: string\n}\n\nexport type GitHubOidcTrustedSource = {\n limitRepos?: boolean\n owner: string\n repositories?: string[]\n}\n\nexport type GitHubOidcVerifyConfig = {\n allowedRefs?: string[]\n allowedWorkflowRefs?: string[]\n allowPullRequests?: boolean\n audience: string\n enforceWorkflowRefs?: boolean\n issuer?: string\n jwksUrl?: string\n maxSkewSeconds?: number\n trustedSources: GitHubOidcTrustedSource[]\n}\n\nexport type VerifiedGitHubOidcToken = {\n claims: GitHubOidcClaims\n expiresAt: Date\n keyId: string\n}\n\nexport type VerifyGitHubOidcTokenResult =\n | {\n code: GitHubOidcErrorCode\n message: string\n ok: false\n }\n | {\n ok: true\n token: VerifiedGitHubOidcToken\n }\n\nconst isString = (value: unknown): value is string =>\n typeof value === 'string' && value.trim() !== ''\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(isString)\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\nconst getStringClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): string | undefined => {\n const value = payload[claim]\n\n return isString(value) ? value : undefined\n}\n\nconst getNumberClaim = (\n payload: Record<string, unknown>,\n claim: string,\n): number | undefined => {\n const value = payload[claim]\n\n return isNumber(value) ? value : undefined\n}\n\nconst getAudienceClaim = (\n payload: Record<string, unknown>,\n): string | string[] | undefined => {\n const value = payload.aud\n\n if (isString(value) || isStringArray(value)) {\n return value\n }\n\n return undefined\n}\n\nconst toClaims = (\n payload: Record<string, unknown>,\n): GitHubOidcClaims | undefined => {\n const aud = getAudienceClaim(payload)\n const exp = getNumberClaim(payload, 'exp')\n const iat = getNumberClaim(payload, 'iat')\n const iss = getStringClaim(payload, 'iss')\n const jti = getStringClaim(payload, 'jti')\n const ref = getStringClaim(payload, 'ref')\n const repository = getStringClaim(payload, 'repository')\n const repositoryOwner = getStringClaim(payload, 'repository_owner')\n const sub = getStringClaim(payload, 'sub')\n\n if (\n !aud ||\n exp === undefined ||\n iat === undefined ||\n !iss ||\n !jti ||\n !ref ||\n !repository ||\n !repositoryOwner ||\n !sub\n ) {\n return undefined\n }\n\n return {\n actor: getStringClaim(payload, 'actor'),\n aud,\n environment: getStringClaim(payload, 'environment'),\n event_name: getStringClaim(payload, 'event_name'),\n exp,\n iat,\n iss,\n job_workflow_ref: getStringClaim(payload, 'job_workflow_ref'),\n jti,\n nbf: getNumberClaim(payload, 'nbf'),\n ref,\n repository,\n repository_owner: repositoryOwner,\n sha: getStringClaim(payload, 'sha'),\n sub,\n workflow: getStringClaim(payload, 'workflow'),\n workflow_ref: getStringClaim(payload, 'workflow_ref'),\n }\n}\n\nconst issue = (\n code: GitHubOidcErrorCode,\n message: string,\n): VerifyGitHubOidcTokenResult => ({\n code,\n message,\n ok: false,\n})\n\nconst includesIfConfigured = (\n allowed: string[] | undefined,\n value: string | undefined,\n): boolean => {\n if (!allowed || allowed.length === 0) {\n return true\n }\n\n return value !== undefined && allowed.includes(value)\n}\n\nconst audienceMatches = (\n audience: string | string[],\n expected: string,\n): boolean =>\n Array.isArray(audience) ? audience.includes(expected) : audience === expected\n\nconst getRepositoryName = (repository: string): string => {\n const [, name] = repository.split('/', 2)\n\n return name ?? repository\n}\n\nconst repositoryMatches = ({\n allowed,\n owner,\n repository,\n}: {\n allowed: string\n owner: string\n repository: string\n}): boolean => {\n const normalized = allowed.trim()\n\n if (!normalized) {\n return false\n }\n\n return normalized.includes('/')\n ? normalized.toLowerCase() === repository.toLowerCase()\n : `${owner}/${normalized}`.toLowerCase() === repository.toLowerCase()\n}\n\nconst findTrustedSource = ({\n repository,\n repositoryOwner,\n trustedSources,\n}: {\n repository: string\n repositoryOwner: string\n trustedSources: GitHubOidcTrustedSource[]\n}): GitHubOidcTrustedSource | undefined =>\n trustedSources.find((source) => {\n if (source.owner.toLowerCase() !== repositoryOwner.toLowerCase()) {\n return false\n }\n\n if (source.limitRepos !== true) {\n return true\n }\n\n return (source.repositories ?? []).some((allowedRepository) =>\n repositoryMatches({\n allowed: allowedRepository,\n owner: source.owner,\n repository,\n }),\n )\n })\n\nconst verifyJwtSignature = ({\n jwk,\n signature,\n signingInput,\n}: {\n jwk: Record<string, unknown>\n signature: Buffer\n signingInput: string\n}): boolean => {\n try {\n const publicKey = createPublicKey({\n format: 'jwk',\n key: jwk as JsonWebKey,\n })\n\n return verify(\n 'RSA-SHA256',\n Buffer.from(signingInput, 'utf8'),\n publicKey,\n signature,\n )\n } catch {\n return false\n }\n}\n\nexport const verifyGitHubOidcToken = async ({\n config,\n fetchJson,\n now = new Date(),\n token,\n}: {\n config: GitHubOidcVerifyConfig\n fetchJson?: FetchJson\n now?: Date\n token: string\n}): Promise<VerifyGitHubOidcTokenResult> => {\n const decoded = decodeJwt(token)\n\n if (!decoded) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is malformed.')\n }\n\n if (decoded.header.alg !== 'RS256') {\n return issue('oidc_invalid_token', 'GitHub OIDC token must use RS256.')\n }\n\n if (!isString(decoded.header.kid)) {\n return issue('oidc_invalid_token', 'GitHub OIDC token is missing kid.')\n }\n\n const issuer = config.issuer ?? DEFAULT_GITHUB_OIDC_ISSUER\n let jwksUrl: string\n\n try {\n jwksUrl = await getGithubOidcJwksUrl({\n fetchJson,\n issuer,\n jwksUrl: config.jwksUrl,\n })\n const jwks = await fetchJwks({\n fetchJson,\n now,\n url: jwksUrl,\n })\n const jwk = findJwkByKid({\n jwks,\n kid: decoded.header.kid,\n })\n\n if (\n !jwk ||\n !verifyJwtSignature({\n jwk,\n signature: decoded.signature,\n signingInput: decoded.signingInput,\n })\n ) {\n return issue('oidc_invalid_token', 'GitHub OIDC token signature is invalid.')\n }\n } catch {\n return issue('oidc_jwks_unavailable', 'GitHub OIDC signing keys are unavailable.')\n }\n\n if (!isString(decoded.payload.jti)) {\n return issue('oidc_missing_jti', 'GitHub OIDC token is missing jti.')\n }\n\n const claims = toClaims(decoded.payload)\n\n if (!claims) {\n return issue('oidc_missing_claim', 'GitHub OIDC token is missing a required claim.')\n }\n\n if (claims.iss !== issuer) {\n return issue('oidc_invalid_issuer', 'GitHub OIDC token issuer is not allowed.')\n }\n\n if (!audienceMatches(claims.aud, config.audience)) {\n return issue('oidc_invalid_audience', 'GitHub OIDC token audience is not allowed.')\n }\n\n const maxSkewSeconds = config.maxSkewSeconds ?? DEFAULT_MAX_SKEW_SECONDS\n const nowSeconds = now.getTime() / 1000\n\n if (claims.exp + maxSkewSeconds < nowSeconds) {\n return issue('oidc_expired', 'GitHub OIDC token has expired.')\n }\n\n if (claims.nbf !== undefined && claims.nbf - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token is not valid yet.')\n }\n\n if (claims.iat - maxSkewSeconds > nowSeconds) {\n return issue('oidc_not_yet_valid', 'GitHub OIDC token was issued in the future.')\n }\n\n const trustedSources = config.trustedSources ?? []\n\n if (trustedSources.length === 0) {\n return issue(\n 'oidc_repository_not_allowed',\n 'GitHub OIDC auth requires a trusted GitHub owner.',\n )\n }\n\n const trustedSource = findTrustedSource({\n repository: claims.repository,\n repositoryOwner: claims.repository_owner,\n trustedSources,\n })\n\n if (!trustedSource) {\n const matchingOwner = trustedSources.find(\n (source) =>\n source.owner.toLowerCase() === claims.repository_owner.toLowerCase(),\n )\n\n if (matchingOwner) {\n return issue(\n 'oidc_repository_not_allowed',\n `GitHub OIDC token repository \"${claims.repository}\" is not trusted for owner \"${claims.repository_owner}\".`,\n )\n }\n\n return issue(\n 'oidc_owner_not_allowed',\n `GitHub OIDC token repository owner \"${claims.repository_owner}\" is not trusted.`,\n )\n }\n\n const repositoryName = getRepositoryName(claims.repository)\n\n if (!includesIfConfigured(config.allowedRefs, claims.ref)) {\n return issue(\n 'oidc_ref_not_allowed',\n `GitHub OIDC token ref \"${claims.ref}\" is not allowed for \"${repositoryName}\".`,\n )\n }\n\n const workflowRef = claims.workflow_ref ?? claims.job_workflow_ref\n\n if (\n config.enforceWorkflowRefs === true &&\n (config.allowedWorkflowRefs?.length ?? 0) === 0\n ) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'Advanced workflow security is enabled but no workflow refs are trusted.',\n )\n }\n\n if (\n config.enforceWorkflowRefs === true &&\n !includesIfConfigured(config.allowedWorkflowRefs, workflowRef)\n ) {\n return issue(\n 'oidc_workflow_not_allowed',\n 'GitHub OIDC token workflow ref is not allowed.',\n )\n }\n\n if (claims.event_name === 'pull_request' && config.allowPullRequests !== true) {\n return issue(\n 'oidc_pull_request_not_allowed',\n 'GitHub OIDC pull request events are not allowed.',\n )\n }\n\n return {\n ok: true,\n token: {\n claims,\n expiresAt: new Date(claims.exp * 1000),\n keyId: `github-oidc:${claims.repository}`,\n },\n }\n}\n"],"names":["createPublicKey","verify","DEFAULT_GITHUB_OIDC_ISSUER","DEFAULT_MAX_SKEW_SECONDS","fetchJwks","findJwkByKid","getGithubOidcJwksUrl","decodeJwt","isString","value","trim","isStringArray","Array","isArray","every","isNumber","Number","isFinite","getStringClaim","payload","claim","undefined","getNumberClaim","getAudienceClaim","aud","toClaims","exp","iat","iss","jti","ref","repository","repositoryOwner","sub","actor","environment","event_name","job_workflow_ref","nbf","repository_owner","sha","workflow","workflow_ref","issue","code","message","ok","includesIfConfigured","allowed","length","includes","audienceMatches","audience","expected","getRepositoryName","name","split","repositoryMatches","owner","normalized","toLowerCase","findTrustedSource","trustedSources","find","source","limitRepos","repositories","some","allowedRepository","verifyJwtSignature","jwk","signature","signingInput","publicKey","format","key","Buffer","from","verifyGitHubOidcToken","config","fetchJson","now","Date","token","decoded","header","alg","kid","issuer","jwksUrl","jwks","url","claims","maxSkewSeconds","nowSeconds","getTime","trustedSource","matchingOwner","repositoryName","allowedRefs","workflowRef","enforceWorkflowRefs","allowedWorkflowRefs","allowPullRequests","expiresAt","keyId"],"mappings":"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"}
1
+ {"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 isReleaseTagRef = (claims: GitHubOidcClaims): boolean =>\n claims.event_name === 'release' && claims.ref.startsWith('refs/tags/')\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) && !isReleaseTagRef(claims)) {\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","isReleaseTagRef","claims","startsWith","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","maxSkewSeconds","nowSeconds","getTime","trustedSource","matchingOwner","repositoryName","allowedRefs","workflowRef","enforceWorkflowRefs","allowedWorkflowRefs","allowPullRequests","expiresAt","keyId"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: payload-markdown-docs
3
3
  description: Use this skill when maintaining Git-backed documentation for a project that uses `@valkyrianlabs/payload-markdown-docs`.
4
+ ---
4
5
 
5
6
  # Payload Markdown Docs Skill
6
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyrianlabs/payload-markdown-docs",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Git-backed Markdown documentation sync for Payload CMS, powered by payload-markdown.",
5
5
  "bin": {
6
6
  "payload-markdown-docs": "./dist/cli/index.js"
@@ -86,10 +86,10 @@
86
86
  "copyfiles": "2.4.1",
87
87
  "cross-env": "^7.0.3",
88
88
  "eslint": "^9.39.4",
89
- "eslint-config-next": "16.2.3",
89
+ "eslint-config-next": "latest",
90
90
  "graphql": "^16.14.0",
91
91
  "mongodb-memory-server": "10.1.4",
92
- "next": "16.2.3",
92
+ "next": "latest",
93
93
  "open": "^10.2.0",
94
94
  "payload": "latest",
95
95
  "postcss": "^8.5.14",