@mintlify/common 1.0.1251 → 1.0.1253

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.
@@ -1,3 +1,5 @@
1
+ export declare function packageBuildBaseUrl(buildId: string): string;
2
+ export declare function packageBuildIdFromBaseUrl(baseUrl: string): string | undefined;
1
3
  export declare function packagePageBaseUrl(baseUrl: string, pagePath: string): string;
2
4
  export declare function packagePageStylesheets(baseUrl: string, pagePath: string, islandIds: readonly string[], manifest: {
3
5
  css: readonly {
@@ -15,7 +17,7 @@ export declare function compilePackageBuild<T extends {
15
17
  export declare function withPackageGlobalStylesheets<T extends {
16
18
  packageCssFiles?: string[];
17
19
  }>(mdxExtracts: T, packageBuild: {
18
- baseUrl: string;
20
+ buildId: string;
19
21
  css?: readonly {
20
22
  path: string;
21
23
  }[];
@@ -1,6 +1,21 @@
1
+ const PACKAGE_ROUTE_PREFIX = '/_mintlify/pkg';
2
+ const PACKAGE_BUILD_ID = /^[A-Za-z0-9_-]{1,64}$/;
1
3
  const PRODUCTION_PACKAGE_BASE_URL = /^\/_mintlify\/pkg\/[a-f0-9]{16,64}$/;
2
4
  const ROUTE_CANONICAL_BASE = 'https://pkg-access.invalid';
3
5
  const MAX_ACCESS_ROUTE_LENGTH = 1024;
6
+ export function packageBuildBaseUrl(buildId) {
7
+ if (!PACKAGE_BUILD_ID.test(buildId)) {
8
+ throw new Error(`Invalid package build ID: ${buildId}`);
9
+ }
10
+ return `${PACKAGE_ROUTE_PREFIX}/${buildId}`;
11
+ }
12
+ export function packageBuildIdFromBaseUrl(baseUrl) {
13
+ const prefix = `${PACKAGE_ROUTE_PREFIX}/`;
14
+ if (!baseUrl.startsWith(prefix))
15
+ return undefined;
16
+ const buildId = baseUrl.slice(prefix.length);
17
+ return PACKAGE_BUILD_ID.test(buildId) ? buildId : undefined;
18
+ }
4
19
  export function packagePageBaseUrl(baseUrl, pagePath) {
5
20
  if (!PRODUCTION_PACKAGE_BASE_URL.test(baseUrl))
6
21
  return baseUrl;
@@ -41,7 +56,7 @@ export function withPackageGlobalStylesheets(mdxExtracts, packageBuild, pagePath
41
56
  return mdxExtracts;
42
57
  let globalFiles;
43
58
  try {
44
- globalFiles = packagePageStylesheets(packageBuild.baseUrl, pagePath, [], {
59
+ globalFiles = packagePageStylesheets(packageBuildBaseUrl(packageBuild.buildId), pagePath, [], {
45
60
  css: packageBuild.css,
46
61
  islands: {},
47
62
  });
@@ -6,7 +6,7 @@ import { MdxExtracts } from '../../index.js';
6
6
  import { createSnippetTreeMap, type Snippet } from './getMdx/snippets.js';
7
7
  type RunWithTracingFn = <T>(spanName: string, fn: () => Promise<T>) => Promise<T>;
8
8
  export type PackageBuildInput = {
9
- baseUrl: string;
9
+ buildId: string;
10
10
  islands: Record<string, {
11
11
  html?: string;
12
12
  prefix: string;
@@ -11,7 +11,7 @@ import { serialize } from '@mintlify/mdx/server';
11
11
  import { getTailwindSelectors } from '../../css/tailwind.js';
12
12
  import { getMDXOptions, remarkMdxRemoveJs, remarkExpandContent, remarkSplitCodeGroup, remarkSplitTabs, remarkWrapCodeBlocks, remarkValidateAccordions, remarkValidateSteps, remarkValidateVisibility, remarkValidateTabs, remarkValidateApiExamples, } from '../../index.js';
13
13
  import { codeStylingToThemeOrThemes } from '../getCodeStyling.js';
14
- import { packagePageBaseUrl, packagePageStylesheets } from '../islands/package-page-url.js';
14
+ import { packageBuildBaseUrl, packagePageBaseUrl, packagePageStylesheets, } from '../islands/package-page-url.js';
15
15
  import { MINT_ISLAND_COMPONENT } from '../islands/packageImports.js';
16
16
  import { PackageIslandsError, parsePackageIslands, preprocessPageContent, } from '../islands/remarkPackageIslands.js';
17
17
  import { preprocessCustomHeadingIds } from '../preprocessCustomHeadingIds.js';
@@ -19,11 +19,8 @@ import { preprocessMdxFragmentCode } from '../preprocessMdxFragmentCode.js';
19
19
  import { replaceVariables } from '../replaceVariables.js';
20
20
  import { createSnippetTreeMap } from './getMdx/snippets.js';
21
21
  import { getSerializeExtras } from './getSerializeExtras.js';
22
- const PACKAGE_BASE_URL = /^\/_mintlify\/pkg\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/;
23
22
  const getPackageIslandOptions = (packageBuild, pagePath, processedContent, snippetTreeMap) => {
24
- if (!PACKAGE_BASE_URL.test(packageBuild.baseUrl)) {
25
- throw new Error(`packageBuild.baseUrl must be a /_mintlify/pkg/ path: ${packageBuild.baseUrl}`);
26
- }
23
+ const buildBaseUrl = packageBuildBaseUrl(packageBuild.buildId);
27
24
  const options = Object.assign({ packageNames: new Set(packageBuild.packageNames), pagePath }, (packageBuild.packageSnippets && {
28
25
  packageSnippets: new Set(packageBuild.packageSnippets),
29
26
  }));
@@ -34,7 +31,7 @@ const getPackageIslandOptions = (packageBuild, pagePath, processedContent, snipp
34
31
  catch (_a) {
35
32
  plan = undefined;
36
33
  }
37
- const baseUrl = packagePageBaseUrl(packageBuild.baseUrl, pagePath);
34
+ const baseUrl = packagePageBaseUrl(buildBaseUrl, pagePath);
38
35
  const islandHtml = Object.fromEntries(Object.entries(packageBuild.islands).map(([id, island]) => {
39
36
  if (typeof island.html !== 'string')
40
37
  return [id, island];
@@ -167,7 +164,7 @@ export function getMdx(_a) {
167
164
  mdxExtracts.multiViewItems = mdxExtracts.multiViewItems.map((item, index) => (Object.assign(Object.assign({}, item), { active: index === 0 })));
168
165
  }
169
166
  if (packageBuild) {
170
- const packageCssFiles = packagePageStylesheets(packageBuild.baseUrl, path, Object.keys((_d = mdxExtracts.islands) !== null && _d !== void 0 ? _d : {}), { css: (_e = packageBuild.css) !== null && _e !== void 0 ? _e : [], islands: packageBuild.islands });
167
+ const packageCssFiles = packagePageStylesheets(packageBuildBaseUrl(packageBuild.buildId), path, Object.keys((_d = mdxExtracts.islands) !== null && _d !== void 0 ? _d : {}), { css: (_e = packageBuild.css) !== null && _e !== void 0 ? _e : [], islands: packageBuild.islands });
171
168
  if (packageCssFiles.length > 0)
172
169
  mdxExtracts.packageCssFiles = packageCssFiles;
173
170
  }
@@ -2,4 +2,4 @@ export { createSnippetTreeMap, getMdx, type GetMdxType, type PackageBuildInput,
2
2
  export { getSerializeExtras } from './getSerializeExtras.js';
3
3
  export { createPackageSnippetTreeMap } from './package-snippet-tree-map.js';
4
4
  export { compileWithPackageBuildFallback } from '../islands/package-build-fallback.js';
5
- export { compilePackageBuild, packagePageBaseUrl, packagePageStylesheets, withPackageGlobalStylesheets, } from '../islands/package-page-url.js';
5
+ export { compilePackageBuild, packageBuildBaseUrl, packageBuildIdFromBaseUrl, packagePageBaseUrl, packagePageStylesheets, withPackageGlobalStylesheets, } from '../islands/package-page-url.js';
@@ -2,4 +2,4 @@ export { createSnippetTreeMap, getMdx, } from './getMdx.js';
2
2
  export { getSerializeExtras } from './getSerializeExtras.js';
3
3
  export { createPackageSnippetTreeMap } from './package-snippet-tree-map.js';
4
4
  export { compileWithPackageBuildFallback } from '../islands/package-build-fallback.js';
5
- export { compilePackageBuild, packagePageBaseUrl, packagePageStylesheets, withPackageGlobalStylesheets, } from '../islands/package-page-url.js';
5
+ export { compilePackageBuild, packageBuildBaseUrl, packageBuildIdFromBaseUrl, packagePageBaseUrl, packagePageStylesheets, withPackageGlobalStylesheets, } from '../islands/package-page-url.js';
@@ -0,0 +1,3 @@
1
+ import type { DecoratedPageOrGroupConfig } from '@mintlify/validation';
2
+ export declare function clientNavView(entries: DecoratedPageOrGroupConfig[]): unknown[];
3
+ export declare function paginationDescriptions(entries: DecoratedPageOrGroupConfig[], currentPath: string): [unknown, unknown];
@@ -0,0 +1,44 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
13
+ import { replaceSlashIndex } from '../slug/replaceSlashIndex.js';
14
+ import { isPage } from './isPage.js';
15
+ import { CLIENT_NAV_PAGE_FIELDS } from './trimScopedNavPages.js';
16
+ function clientPage(page) {
17
+ return Object.fromEntries(CLIENT_NAV_PAGE_FIELDS.filter((field) => Object.hasOwn(page, field)).map((field) => [
18
+ field,
19
+ page[field],
20
+ ]));
21
+ }
22
+ export function clientNavView(entries) {
23
+ return entries.map((entry) => {
24
+ if (isPage(entry))
25
+ return clientPage(entry);
26
+ const { root, pages } = entry, rest = __rest(entry, ["root", "pages"]);
27
+ return Object.assign(Object.assign(Object.assign({}, rest), (root ? { root: clientPage(root) } : {})), { pages: clientNavView(pages) });
28
+ });
29
+ }
30
+ function flattenPages(entries) {
31
+ return entries.flatMap((entry) => {
32
+ if (isPage(entry))
33
+ return [entry];
34
+ return [...(entry.root ? [entry.root] : []), ...flattenPages(entry.pages)];
35
+ });
36
+ }
37
+ export function paginationDescriptions(entries, currentPath) {
38
+ var _a, _b;
39
+ const pages = flattenPages(entries);
40
+ const index = pages.findIndex((page) => isEqualIgnoringLeadingSlash(replaceSlashIndex(page.href), currentPath));
41
+ if (index === -1)
42
+ return [undefined, undefined];
43
+ return [(_a = pages[index - 1]) === null || _a === void 0 ? void 0 : _a.description, (_b = pages[index + 1]) === null || _b === void 0 ? void 0 : _b.description];
44
+ }
@@ -1,3 +1,4 @@
1
+ import { clientNavView, paginationDescriptions } from './clientNavView.js';
1
2
  import { decoratedNavigationConfig } from './decoratedNavigationConfig.js';
2
3
  import { filterDivisions } from './filterDivisions.js';
3
4
  import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
@@ -99,7 +100,8 @@ function assertInvariantForEveryPath(nav, includeProductVersionMetadata = false)
99
100
  });
100
101
  const full = filterWith(nav, currentPath, scoped.currentVersion, scoped.currentLanguage);
101
102
  const rescoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, scoped.currentLanguage);
102
- expect(rescoped.groupsOrPages, currentPath).toEqual(full.groupsOrPages);
103
+ expect(clientNavView(rescoped.groupsOrPages), currentPath).toEqual(clientNavView(full.groupsOrPages));
104
+ expect(paginationDescriptions(rescoped.groupsOrPages, currentPath), currentPath).toEqual(paginationDescriptions(full.groupsOrPages, currentPath));
103
105
  expect(divisionSummary(rescoped), currentPath).toEqual(divisionSummary(full));
104
106
  expect(mapHrefs(rescoped.firstHrefInVersion), currentPath).toEqual(mapHrefs(full.firstHrefInVersion));
105
107
  expect(mapHrefs(rescoped.firstHrefInLanguage), currentPath).toEqual(mapHrefs(full.firstHrefInLanguage));
@@ -1,4 +1,5 @@
1
1
  import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
2
+ import { clientNavView, paginationDescriptions } from './clientNavView.js';
2
3
  import { decoratedNavigationConfig } from './decoratedNavigationConfig.js';
3
4
  import { filterDivisions } from './filterDivisions.js';
4
5
  import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
@@ -56,7 +57,8 @@ function assertStateFlipSafety(nav) {
56
57
  const label = `${currentPath} -> version ${version.name}`;
57
58
  const flippedFull = filterWith(nav, currentPath, version.name, scoped.currentLanguage);
58
59
  const flippedScoped = filterWith(scoped.scopedNav, currentPath, version.name, scoped.currentLanguage);
59
- expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
60
+ expect(clientNavView(flippedScoped.groupsOrPages), label).toEqual(clientNavView(flippedFull.groupsOrPages));
61
+ expect(paginationDescriptions(flippedScoped.groupsOrPages, currentPath), label).toEqual(paginationDescriptions(flippedFull.groupsOrPages, currentPath));
60
62
  expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
61
63
  }
62
64
  // deliberately unguarded (no stays check): even a flip to a stubbed,
@@ -66,7 +68,8 @@ function assertStateFlipSafety(nav) {
66
68
  const label = `${currentPath} -> product ${product.name}`;
67
69
  const flippedFull = filterWith(nav, currentPath, scoped.currentVersion, scoped.currentLanguage, product.name);
68
70
  const flippedScoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, scoped.currentLanguage, product.name);
69
- expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
71
+ expect(clientNavView(flippedScoped.groupsOrPages), label).toEqual(clientNavView(flippedFull.groupsOrPages));
72
+ expect(paginationDescriptions(flippedScoped.groupsOrPages, currentPath), label).toEqual(paginationDescriptions(flippedFull.groupsOrPages, currentPath));
70
73
  expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
71
74
  }
72
75
  for (const language of full.languages) {
@@ -77,7 +80,8 @@ function assertStateFlipSafety(nav) {
77
80
  const label = `${currentPath} -> language ${language.language}`;
78
81
  const flippedFull = filterWith(nav, currentPath, scoped.currentVersion, language.language);
79
82
  const flippedScoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, language.language);
80
- expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
83
+ expect(clientNavView(flippedScoped.groupsOrPages), label).toEqual(clientNavView(flippedFull.groupsOrPages));
84
+ expect(paginationDescriptions(flippedScoped.groupsOrPages, currentPath), label).toEqual(paginationDescriptions(flippedFull.groupsOrPages, currentPath));
81
85
  expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
82
86
  }
83
87
  }
@@ -18,6 +18,7 @@ import { filterDivisions } from './filterDivisions.js';
18
18
  import { getFirstPageFromNavigation } from './getFirstPageFromNavigation.js';
19
19
  import { getVersionOrLanguageFromPath } from './getVersionOrLanguageFromPath.js';
20
20
  import { isPageGroups } from './groupAccess.js';
21
+ import { trimScopedNavPages } from './trimScopedNavPages.js';
21
22
  import { pathsDifferByOneSegment } from './updateFirstHrefInDivision.js';
22
23
  const SCOPED_DIVISION_KEYS = ['versions', 'languages'];
23
24
  const DIVISION_TYPE_BY_KEY = {
@@ -621,7 +622,7 @@ export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = ne
621
622
  cache: { dropdownCache: new Map() },
622
623
  isPreview,
623
624
  });
624
- const scopedNav = scopeNode(
625
+ const scopedNav = trimScopedNavPages(scopeNode(
625
626
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
626
627
  decoratedNav, {
627
628
  currentPath,
@@ -634,7 +635,7 @@ export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = ne
634
635
  isPreview,
635
636
  skipVersionLanguageStubs: productAmbiguous,
636
637
  includeProductVersionMetadata,
637
- }
638
+ }), currentPath
638
639
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
639
640
  );
640
641
  return {
@@ -0,0 +1,4 @@
1
+ type NavNode = Record<string, unknown>;
2
+ export declare const CLIENT_NAV_PAGE_FIELDS: readonly ["href", "title", "hidden", "groups", "sidebarTitle", "url", "icon", "iconType", "tag", "api", "openapi", "asyncapi", "graphql", "deprecated", "hideApiMarker", "mode", "playground", "version"];
3
+ export declare function trimScopedNavPages(nav: NavNode, currentPath: string): NavNode;
4
+ export {};
@@ -0,0 +1,134 @@
1
+ import { normalizeComparablePath } from '../paths/isEqualIgnoringLeadingSlash.js';
2
+ import { replaceSlashIndex } from '../slug/replaceSlashIndex.js';
3
+ function pageFieldAllowlist(fields) {
4
+ return fields;
5
+ }
6
+ export const CLIENT_NAV_PAGE_FIELDS = pageFieldAllowlist([
7
+ 'href',
8
+ 'title',
9
+ 'hidden',
10
+ 'groups',
11
+ 'sidebarTitle',
12
+ 'url',
13
+ 'icon',
14
+ 'iconType',
15
+ 'tag',
16
+ 'api',
17
+ 'openapi',
18
+ 'asyncapi',
19
+ 'graphql',
20
+ 'deprecated',
21
+ 'hideApiMarker',
22
+ 'mode',
23
+ 'playground',
24
+ 'version',
25
+ ]);
26
+ const isNavNode = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
27
+ const isPageNode = (node) => Object.hasOwn(node, 'href') && Object.hasOwn(node, 'title');
28
+ const hrefMatches = (href, target) => typeof href === 'string' && normalizeComparablePath(replaceSlashIndex(href)) === target;
29
+ function collectPagesInOrder(node, out, inHidden = false) {
30
+ if (Array.isArray(node)) {
31
+ for (const entry of node)
32
+ collectPagesInOrder(entry, out, inHidden);
33
+ return;
34
+ }
35
+ if (!isNavNode(node))
36
+ return;
37
+ if (isPageNode(node)) {
38
+ out.push({
39
+ page: node,
40
+ visibleToEveryone: !inHidden && node.hidden !== true && node.groups === undefined,
41
+ });
42
+ return;
43
+ }
44
+ const hidden = inHidden || node.hidden === true;
45
+ if (isNavNode(node.root) && isPageNode(node.root))
46
+ collectPagesInOrder(node.root, out, hidden);
47
+ for (const [key, value] of Object.entries(node)) {
48
+ if (key === 'root' || key === 'global')
49
+ continue;
50
+ if (Array.isArray(value))
51
+ collectPagesInOrder(value, out, hidden);
52
+ }
53
+ }
54
+ function addPaginationNeighbors(nav, target, keep) {
55
+ const pages = [];
56
+ collectPagesInOrder(nav, pages);
57
+ pages.forEach(({ page }, index) => {
58
+ if (!hrefMatches(page.href, target))
59
+ return;
60
+ keepUntilVisible(pages, index, -1, keep);
61
+ keepUntilVisible(pages, index, 1, keep);
62
+ });
63
+ }
64
+ function keepUntilVisible(pages, from, step, keep) {
65
+ for (let i = from + step; i >= 0 && i < pages.length; i += step) {
66
+ const entry = pages[i];
67
+ if (!entry)
68
+ return;
69
+ keep.add(entry.page);
70
+ if (entry.visibleToEveryone)
71
+ return;
72
+ }
73
+ }
74
+ function addDirectPages(group, keep) {
75
+ if (!Array.isArray(group.pages))
76
+ return;
77
+ for (const entry of group.pages) {
78
+ if (isNavNode(entry) && isPageNode(entry))
79
+ keep.add(entry);
80
+ }
81
+ }
82
+ function addDirectoryChildren(node, target, keep) {
83
+ if (Array.isArray(node)) {
84
+ for (const entry of node)
85
+ addDirectoryChildren(entry, target, keep);
86
+ return;
87
+ }
88
+ if (!isNavNode(node) || isPageNode(node))
89
+ return;
90
+ if (isNavNode(node.root) && hrefMatches(node.root.href, target)) {
91
+ addDirectPages(node, keep);
92
+ if (Array.isArray(node.pages)) {
93
+ for (const entry of node.pages) {
94
+ if (isNavNode(entry) && !isPageNode(entry))
95
+ addDirectPages(entry, keep);
96
+ }
97
+ }
98
+ }
99
+ for (const [key, value] of Object.entries(node)) {
100
+ if (key !== 'global' && Array.isArray(value))
101
+ addDirectoryChildren(value, target, keep);
102
+ }
103
+ }
104
+ const CLIENT_FIELDS = new Set(CLIENT_NAV_PAGE_FIELDS);
105
+ function trimPage(page, keepDescription) {
106
+ const trimmed = {};
107
+ for (const key of Object.keys(page)) {
108
+ if (CLIENT_FIELDS.has(key) || (keepDescription && key === 'description')) {
109
+ trimmed[key] = page[key];
110
+ }
111
+ }
112
+ return trimmed;
113
+ }
114
+ function trimNode(node, keepDescription) {
115
+ if (Array.isArray(node))
116
+ return node.map((entry) => trimNode(entry, keepDescription));
117
+ if (!isNavNode(node))
118
+ return node;
119
+ if (isPageNode(node))
120
+ return trimPage(node, keepDescription.has(node));
121
+ const trimmed = {};
122
+ for (const [key, value] of Object.entries(node)) {
123
+ trimmed[key] = key === 'global' ? value : trimNode(value, keepDescription);
124
+ }
125
+ return trimmed;
126
+ }
127
+ export function trimScopedNavPages(nav, currentPath) {
128
+ const keepDescription = new Set();
129
+ const target = normalizeComparablePath(replaceSlashIndex(currentPath));
130
+ addPaginationNeighbors(nav, target, keepDescription);
131
+ addDirectoryChildren(nav, target, keepDescription);
132
+ const trimmed = trimNode(nav, keepDescription);
133
+ return isNavNode(trimmed) ? trimmed : nav;
134
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,194 @@
1
+ import { trimScopedNavPages } from './trimScopedNavPages.js';
2
+ const page = (slug, extra = {}) => (Object.assign({ title: slug, href: `/${slug}`, description: `${slug} description` }, extra));
3
+ function descriptions(nav) {
4
+ const out = {};
5
+ const walk = (node) => {
6
+ if (Array.isArray(node))
7
+ return node.forEach(walk);
8
+ if (typeof node !== 'object' || node === null)
9
+ return;
10
+ if ('href' in node && 'title' in node && typeof node.href === 'string') {
11
+ out[node.href] = 'description' in node ? node.description : undefined;
12
+ return;
13
+ }
14
+ Object.values(node).forEach(walk);
15
+ };
16
+ walk(nav);
17
+ return out;
18
+ }
19
+ describe('trimScopedNavPages', () => {
20
+ it('keeps only client fields on pages', () => {
21
+ const nav = {
22
+ pages: [
23
+ page('a', {
24
+ sidebarTitle: 'A',
25
+ openapi: 'get /a',
26
+ deprecated: true,
27
+ playground: 'simple',
28
+ version: 'v2',
29
+ mode: 'wide',
30
+ autogeneratedByOpenApi: true,
31
+ authors: [{ name: 'x' }],
32
+ keywords: ['k'],
33
+ customFrontmatter: 'x',
34
+ }),
35
+ ],
36
+ };
37
+ expect(trimScopedNavPages(nav, '/a')).toEqual({
38
+ pages: [
39
+ {
40
+ title: 'a',
41
+ href: '/a',
42
+ sidebarTitle: 'A',
43
+ openapi: 'get /a',
44
+ deprecated: true,
45
+ playground: 'simple',
46
+ version: 'v2',
47
+ mode: 'wide',
48
+ },
49
+ ],
50
+ });
51
+ });
52
+ it('keeps descriptions only on pagination neighbors', () => {
53
+ const nav = {
54
+ groups: [
55
+ { group: 'One', pages: [page('a'), page('b')] },
56
+ { group: 'Two', pages: [page('c'), page('d')] },
57
+ ],
58
+ };
59
+ expect(descriptions(trimScopedNavPages(nav, '/b'))).toEqual({
60
+ '/a': 'a description',
61
+ '/b': undefined,
62
+ '/c': 'c description',
63
+ '/d': undefined,
64
+ });
65
+ });
66
+ it('orders a group root before its pages', () => {
67
+ const nav = {
68
+ groups: [{ group: 'G', root: page('root'), pages: [page('a'), page('b')] }],
69
+ };
70
+ expect(descriptions(trimScopedNavPages(nav, '/a'))).toEqual({
71
+ '/root': 'root description',
72
+ '/a': undefined,
73
+ '/b': 'b description',
74
+ });
75
+ });
76
+ it('steps past neighbors some viewers cannot see', () => {
77
+ const nav = {
78
+ pages: [
79
+ page('a'),
80
+ page('hidden', { hidden: true }),
81
+ page('current'),
82
+ page('gated', { groups: ['admin'] }),
83
+ page('b'),
84
+ page('c'),
85
+ ],
86
+ };
87
+ expect(descriptions(trimScopedNavPages(nav, '/current'))).toEqual({
88
+ '/a': 'a description',
89
+ '/hidden': 'hidden description',
90
+ '/current': undefined,
91
+ '/gated': 'gated description',
92
+ '/b': 'b description',
93
+ '/c': undefined,
94
+ });
95
+ });
96
+ it('steps past pages under a hidden group or division', () => {
97
+ const nav = {
98
+ tabs: [
99
+ {
100
+ tab: 'Docs',
101
+ groups: [
102
+ { group: 'One', pages: [page('current')] },
103
+ { group: 'Drafts', hidden: true, root: page('draft-root'), pages: [page('draft')] },
104
+ { group: 'Two', pages: [page('next'), page('after')] },
105
+ ],
106
+ },
107
+ { tab: 'Hidden', hidden: true, pages: [page('hidden-tab')] },
108
+ { tab: 'Other', pages: [page('other')] },
109
+ ],
110
+ };
111
+ expect(descriptions(trimScopedNavPages(nav, '/current'))).toEqual({
112
+ '/current': undefined,
113
+ '/draft-root': 'draft-root description',
114
+ '/draft': 'draft description',
115
+ '/next': 'next description',
116
+ '/after': undefined,
117
+ '/hidden-tab': undefined,
118
+ '/other': undefined,
119
+ });
120
+ expect(descriptions(trimScopedNavPages(nav, '/after'))).toMatchObject({
121
+ '/hidden-tab': 'hidden-tab description',
122
+ '/other': 'other description',
123
+ });
124
+ });
125
+ it('keeps descriptions a directory root lists', () => {
126
+ const nav = {
127
+ groups: [
128
+ {
129
+ group: 'Dir',
130
+ directory: 'card',
131
+ root: page('dir'),
132
+ pages: [
133
+ page('child'),
134
+ {
135
+ group: 'Sub',
136
+ pages: [page('grandchild'), { group: 'Deep', pages: [page('deep')] }],
137
+ },
138
+ ],
139
+ },
140
+ { group: 'Other', pages: [page('x'), page('y')] },
141
+ ],
142
+ };
143
+ expect(descriptions(trimScopedNavPages(nav, '/dir'))).toEqual({
144
+ '/dir': undefined,
145
+ '/child': 'child description',
146
+ '/grandchild': 'grandchild description',
147
+ '/deep': undefined,
148
+ '/x': undefined,
149
+ '/y': undefined,
150
+ });
151
+ });
152
+ it('matches index hrefs like the client', () => {
153
+ const nav = { pages: [page('a'), page('guide/index'), page('b')] };
154
+ expect(descriptions(trimScopedNavPages(nav, '/guide'))).toEqual({
155
+ '/a': 'a description',
156
+ '/guide/index': undefined,
157
+ '/b': 'b description',
158
+ });
159
+ });
160
+ it('leaves division descriptions and global links alone', () => {
161
+ const nav = {
162
+ global: {
163
+ anchors: [{ anchor: 'Blog', href: 'https://example.com', description: 'kept' }],
164
+ },
165
+ dropdowns: [
166
+ {
167
+ dropdown: 'D',
168
+ description: 'dropdown description',
169
+ pages: [page('a')],
170
+ },
171
+ ],
172
+ };
173
+ expect(trimScopedNavPages(nav, '/a')).toEqual({
174
+ global: {
175
+ anchors: [{ anchor: 'Blog', href: 'https://example.com', description: 'kept' }],
176
+ },
177
+ dropdowns: [
178
+ {
179
+ dropdown: 'D',
180
+ description: 'dropdown description',
181
+ pages: [{ title: 'a', href: '/a' }],
182
+ },
183
+ ],
184
+ });
185
+ });
186
+ it('does not mutate the input', () => {
187
+ const nav = {
188
+ pages: [page('a', { keywords: ['k'] }), page('b'), page('c')],
189
+ };
190
+ const before = structuredClone(nav);
191
+ trimScopedNavPages(nav, '/a');
192
+ expect(nav).toEqual(before);
193
+ });
194
+ });
@@ -1 +1,2 @@
1
1
  export declare function isEqualIgnoringLeadingSlash(path1: string | undefined, path2: string | undefined): boolean;
2
+ export declare function normalizeComparablePath(path: string): string;
@@ -15,7 +15,8 @@ export function isEqualIgnoringLeadingSlash(path1, path2) {
15
15
  if (path1 == null || path2 == null || typeof path1 !== 'string' || typeof path2 !== 'string') {
16
16
  return false;
17
17
  }
18
- const first = optionallyRemoveTrailingSlash(optionallyRemoveLeadingSlash(path1));
19
- const second = optionallyRemoveTrailingSlash(optionallyRemoveLeadingSlash(path2));
20
- return normalizeIndexPath(first) === normalizeIndexPath(second);
18
+ return normalizeComparablePath(path1) === normalizeComparablePath(path2);
19
+ }
20
+ export function normalizeComparablePath(path) {
21
+ return normalizeIndexPath(optionallyRemoveTrailingSlash(optionallyRemoveLeadingSlash(path)));
21
22
  }