@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 +50 -6
- package/dist/next/PayloadMarkdownDocsNavbar.d.ts +28 -0
- package/dist/next/PayloadMarkdownDocsNavbar.js +107 -0
- package/dist/next/PayloadMarkdownDocsNavbar.js.map +1 -0
- package/dist/next/index.d.ts +4 -2
- package/dist/next/index.js +2 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/links.d.ts +54 -1
- package/dist/next/links.js +236 -4
- package/dist/next/links.js.map +1 -1
- package/dist/security/githubOidc.js +2 -1
- package/dist/security/githubOidc.js.map +1 -1
- package/dist/skills/codex/SKILL.md +1 -0
- package/package.json +3 -3
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
|
|
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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"}
|
package/dist/next/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/next/index.js
CHANGED
|
@@ -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';
|
package/dist/next/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/index.ts"],"sourcesContent":["export {
|
|
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"}
|
package/dist/next/links.d.ts
CHANGED
|
@@ -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
|
|
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)[]>;
|
package/dist/next/links.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
package/dist/next/links.js.map
CHANGED
|
@@ -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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valkyrianlabs/payload-markdown-docs",
|
|
3
|
-
"version": "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": "
|
|
89
|
+
"eslint-config-next": "latest",
|
|
90
90
|
"graphql": "^16.14.0",
|
|
91
91
|
"mongodb-memory-server": "10.1.4",
|
|
92
|
-
"next": "
|
|
92
|
+
"next": "latest",
|
|
93
93
|
"open": "^10.2.0",
|
|
94
94
|
"payload": "latest",
|
|
95
95
|
"postcss": "^8.5.14",
|