@mintlify/common 1.0.1266 → 1.0.1268
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.
|
@@ -25,19 +25,27 @@ const parse = (content, path) => {
|
|
|
25
25
|
return messageOf(error);
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
const
|
|
28
|
+
const IMPORT_STATEMENTS = new Set(['ImportDeclaration']);
|
|
29
|
+
const MODULE_SOURCE_STATEMENTS = new Set([
|
|
30
|
+
'ImportDeclaration',
|
|
31
|
+
'ExportNamedDeclaration',
|
|
32
|
+
'ExportAllDeclaration',
|
|
33
|
+
]);
|
|
34
|
+
const topLevelSources = (tree, statementTypes = IMPORT_STATEMENTS) => tree.children.flatMap((node) => {
|
|
29
35
|
var _a, _b, _c;
|
|
30
36
|
if (!isMdxJsEsm(node))
|
|
31
37
|
return [];
|
|
32
38
|
return ((_c = (_b = (_a = node.data) === null || _a === void 0 ? void 0 : _a.estree) === null || _b === void 0 ? void 0 : _b.body) !== null && _c !== void 0 ? _c : []).flatMap((statement) => {
|
|
33
|
-
var _a, _b, _c;
|
|
34
|
-
if (statement.type
|
|
39
|
+
var _a, _b, _c, _d;
|
|
40
|
+
if (!statementTypes.has(statement.type) ||
|
|
41
|
+
!('source' in statement) ||
|
|
42
|
+
typeof ((_a = statement.source) === null || _a === void 0 ? void 0 : _a.value) !== 'string') {
|
|
35
43
|
return [];
|
|
36
44
|
}
|
|
37
45
|
return [
|
|
38
46
|
{
|
|
39
47
|
source: statement.source.value,
|
|
40
|
-
line: (
|
|
48
|
+
line: (_c = (_b = statement.loc) === null || _b === void 0 ? void 0 : _b.start.line) !== null && _c !== void 0 ? _c : (_d = node.position) === null || _d === void 0 ? void 0 : _d.start.line,
|
|
41
49
|
},
|
|
42
50
|
];
|
|
43
51
|
});
|
|
@@ -46,14 +54,14 @@ export const sortPackageProblems = (problems) => [...problems].sort((a, b) => {
|
|
|
46
54
|
export const classifyPackageSources = (files, { packageNames, variables, normalizeImportSource = (source) => source, }) => {
|
|
47
55
|
var _a;
|
|
48
56
|
const resolve = (source, importer) => resolveImportPath(normalizeImportSource(source, importer), importer);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const source = normalizeImportSource((_a = from !== null && from !== void 0 ? from : bare) !== null && _a !== void 0 ? _a : '', path);
|
|
57
|
+
const importsPackage = (from, path) => {
|
|
58
|
+
const source = normalizeImportSource(from, path);
|
|
52
59
|
if (isDeclaredPackageImport(source, packageNames))
|
|
53
60
|
return true;
|
|
54
61
|
const resolved = resolveImportPath(source, path);
|
|
55
62
|
return resolved !== null && isPackageSnippetPath(resolved);
|
|
56
|
-
}
|
|
63
|
+
};
|
|
64
|
+
const textUsesPackages = ({ path, content }) => [...replaceVariables(content, variables).matchAll(IMPORT_SOURCE)].some(([, from, bare]) => { var _a; return importsPackage((_a = from !== null && from !== void 0 ? from : bare) !== null && _a !== void 0 ? _a : '', path); });
|
|
57
65
|
const parsed = [];
|
|
58
66
|
const failed = [];
|
|
59
67
|
for (const file of [...files].sort((a, b) => compare(a.path, b.path))) {
|
|
@@ -61,7 +69,7 @@ export const classifyPackageSources = (files, { packageNames, variables, normali
|
|
|
61
69
|
if (typeof tree === 'string')
|
|
62
70
|
failed.push(Object.assign(Object.assign({}, file), { message: tree }));
|
|
63
71
|
else
|
|
64
|
-
parsed.push(Object.assign(Object.assign({}, file), { tree, imports:
|
|
72
|
+
parsed.push(Object.assign(Object.assign({}, file), { tree, imports: topLevelSources(tree) }));
|
|
65
73
|
}
|
|
66
74
|
const importedFiles = new Set();
|
|
67
75
|
for (const file of parsed) {
|
|
@@ -90,12 +98,13 @@ export const classifyPackageSources = (files, { packageNames, variables, normali
|
|
|
90
98
|
for (const file of failed) {
|
|
91
99
|
const category = categoryOf(file.path);
|
|
92
100
|
result.unparsed.push({ path: file.path, content: file.content, category });
|
|
93
|
-
if (
|
|
101
|
+
if (textUsesPackages(file))
|
|
94
102
|
result.errors.push({ file: file.path, message: file.message });
|
|
95
103
|
}
|
|
96
104
|
for (const file of parsed) {
|
|
97
105
|
const category = categoryOf(file.path);
|
|
98
|
-
if (category === 'legacySnippet' &&
|
|
106
|
+
if (category === 'legacySnippet' &&
|
|
107
|
+
topLevelSources(file.tree, MODULE_SOURCE_STATEMENTS).some(({ source }) => importsPackage(source, file.path))) {
|
|
99
108
|
result.errors.push({
|
|
100
109
|
file: file.path,
|
|
101
110
|
message: "uses npm package components, which don't work in files included with <Snippet>. Import it into the page instead.",
|
|
@@ -3,7 +3,7 @@ import { divisions, } from '@mintlify/validation';
|
|
|
3
3
|
import { readObjectArrayProperty } from '../divisions/iterateObjectArrayProperty.js';
|
|
4
4
|
import { optionallyAddLeadingSlash } from '../fs/optionallySlash.js';
|
|
5
5
|
import { isAbsoluteUrl } from '../isAbsoluteUrl.js';
|
|
6
|
-
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
6
|
+
import { isEqualIgnoringLeadingSlash, normalizeComparablePath, } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
7
7
|
import { checkNavAccess } from './checkNavAccess.js';
|
|
8
8
|
import { getFirstPageFromNavigation } from './getFirstPageFromNavigation.js';
|
|
9
9
|
import { isPage } from './isPage.js';
|
|
@@ -169,6 +169,7 @@ export const filterDivisions = ({ currentPath, currentVersion, currentLanguage,
|
|
|
169
169
|
menuDivision,
|
|
170
170
|
isPreview,
|
|
171
171
|
parentDivisionNames: ancestorDivisionNames,
|
|
172
|
+
rootNav: decoratedNav,
|
|
172
173
|
});
|
|
173
174
|
})
|
|
174
175
|
.filter(Boolean);
|
|
@@ -269,8 +270,8 @@ function upsertGlobalDivisions(navigationDivisions, globalConfig) {
|
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
272
|
function buildDivisionItem(param) {
|
|
272
|
-
var _a, _b;
|
|
273
|
-
const { item, isActive, cache, currentPath, currentVersion, currentLanguage, userGroups, divisionKey, menuDivision, isPreview = false, parentDivisionNames = [], } = param;
|
|
273
|
+
var _a, _b, _c;
|
|
274
|
+
const { item, isActive, cache, currentPath, currentVersion, currentLanguage, userGroups, divisionKey, menuDivision, isPreview = false, parentDivisionNames = [], rootNav, } = param;
|
|
274
275
|
const { dropdownCache } = cache;
|
|
275
276
|
let href = '';
|
|
276
277
|
// TODO: support this for other divisions (versions, languages, etc.)
|
|
@@ -293,7 +294,7 @@ function buildDivisionItem(param) {
|
|
|
293
294
|
const isUserDefinedHref = 'href' in item && isAbsoluteUrl(item.href);
|
|
294
295
|
if (!isUserDefinedHref && divisionKey === 'dropdowns' && !isActive && currentPath) {
|
|
295
296
|
href =
|
|
296
|
-
(_b = findCorrespondingDropdownHref(item, currentPath, userGroups !== null && userGroups !== void 0 ? userGroups : new Set(), isPreview)) !== null && _b !== void 0 ? _b : '';
|
|
297
|
+
(_c = (_b = findCorrespondingDropdownHref(item, currentPath, userGroups !== null && userGroups !== void 0 ? userGroups : new Set(), isPreview)) !== null && _b !== void 0 ? _b : findSamePageInDropdownVersion(page === null || page === void 0 ? void 0 : page.href, findDivisionName(item), currentPath, rootNav, userGroups !== null && userGroups !== void 0 ? userGroups : new Set(), isPreview)) !== null && _c !== void 0 ? _c : '';
|
|
297
298
|
}
|
|
298
299
|
if (!href) {
|
|
299
300
|
href = isUserDefinedHref ? item.href : (page === null || page === void 0 ? void 0 : page.href) || '/';
|
|
@@ -327,6 +328,7 @@ function buildDivisionItem(param) {
|
|
|
327
328
|
userGroups,
|
|
328
329
|
divisionKey: 'menu',
|
|
329
330
|
isPreview,
|
|
331
|
+
rootNav,
|
|
330
332
|
parentDivisionNames: [
|
|
331
333
|
...parentDivisionNames,
|
|
332
334
|
...(parentDivisionName ? [parentDivisionName] : []),
|
|
@@ -384,6 +386,102 @@ function findCorrespondingDropdownHref(item, currentPath, userGroups, isPreview)
|
|
|
384
386
|
walk(item);
|
|
385
387
|
return exactMatch !== null && exactMatch !== void 0 ? exactMatch : segmentMatch;
|
|
386
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* The same page in the dropdown's version when that page lives in a different part of the
|
|
391
|
+
* navigation, e.g. `docs/v3/webhooks` under one tab but `docs/v2/webhooks` under another.
|
|
392
|
+
* The version segment is read off the dropdown's first page at the first segment where it differs
|
|
393
|
+
* from the current path. That segment is swapped in, added (`/webhooks` -> `/v1/webhooks`) or
|
|
394
|
+
* removed (`/v1/webhooks` -> `/webhooks`), and a candidate is used only if that exact page is in a
|
|
395
|
+
* dropdown of the same name. Without this the switcher lands on the dropdown's first page.
|
|
396
|
+
*/
|
|
397
|
+
function findSamePageInDropdownVersion(dropdownFirstHref, dropdownName, currentPath, rootNav, userGroups, isPreview) {
|
|
398
|
+
if (!dropdownFirstHref || !dropdownName || !rootNav)
|
|
399
|
+
return undefined;
|
|
400
|
+
const target = dropdownFirstHref.split('/').filter(Boolean);
|
|
401
|
+
const current = currentPath.split('/').filter(Boolean);
|
|
402
|
+
const index = current.findIndex((segment, i) => segment !== target[i]);
|
|
403
|
+
if (index === -1)
|
|
404
|
+
return undefined;
|
|
405
|
+
const versionSegment = target[index];
|
|
406
|
+
const candidates = [];
|
|
407
|
+
if (versionSegment !== undefined && index < current.length - 1) {
|
|
408
|
+
candidates.push(current.map((segment, i) => (i === index ? versionSegment : segment)));
|
|
409
|
+
}
|
|
410
|
+
if (versionSegment !== undefined) {
|
|
411
|
+
candidates.push([...current.slice(0, index), versionSegment, ...current.slice(index)]);
|
|
412
|
+
}
|
|
413
|
+
if (index < current.length - 1) {
|
|
414
|
+
candidates.push([...current.slice(0, index), ...current.slice(index + 1)]);
|
|
415
|
+
}
|
|
416
|
+
const pages = getDropdownPages(rootNav, userGroups, isPreview).get(dropdownName);
|
|
417
|
+
for (const segments of candidates) {
|
|
418
|
+
const found = pages === null || pages === void 0 ? void 0 : pages.get(normalizeComparablePath(segments.join('/')));
|
|
419
|
+
if (found)
|
|
420
|
+
return found;
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
const dropdownPagesCache = new WeakMap();
|
|
425
|
+
function getDropdownPages(rootNav, userGroups, isPreview) {
|
|
426
|
+
let byGroups = dropdownPagesCache.get(rootNav);
|
|
427
|
+
if (!byGroups)
|
|
428
|
+
dropdownPagesCache.set(rootNav, (byGroups = new WeakMap()));
|
|
429
|
+
let byPreview = byGroups.get(userGroups);
|
|
430
|
+
if (!byPreview)
|
|
431
|
+
byGroups.set(userGroups, (byPreview = new Map()));
|
|
432
|
+
let pages = byPreview.get(isPreview);
|
|
433
|
+
if (!pages) {
|
|
434
|
+
pages = collectDropdownPages(rootNav, userGroups, isPreview);
|
|
435
|
+
byPreview.set(isPreview, pages);
|
|
436
|
+
}
|
|
437
|
+
return pages;
|
|
438
|
+
}
|
|
439
|
+
function collectDropdownPages(rootNav, userGroups, isPreview) {
|
|
440
|
+
const pagesByDropdown = new Map();
|
|
441
|
+
function walk(entry, dropdownNames) {
|
|
442
|
+
if (typeof entry !== 'object')
|
|
443
|
+
return;
|
|
444
|
+
if (isPage(entry)) {
|
|
445
|
+
if (!dropdownNames.length || !checkNavAccess(entry, userGroups, isPreview))
|
|
446
|
+
return;
|
|
447
|
+
const key = normalizeComparablePath(entry.href);
|
|
448
|
+
for (const name of dropdownNames) {
|
|
449
|
+
let pages = pagesByDropdown.get(name);
|
|
450
|
+
if (!pages)
|
|
451
|
+
pagesByDropdown.set(name, (pages = new Map()));
|
|
452
|
+
if (!pages.has(key))
|
|
453
|
+
pages.set(key, entry.href);
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if ('hidden' in entry && entry.hidden)
|
|
458
|
+
return;
|
|
459
|
+
if ('root' in entry && typeof entry.root === 'object') {
|
|
460
|
+
walk(entry.root, dropdownNames);
|
|
461
|
+
}
|
|
462
|
+
if ('pages' in entry && Array.isArray(entry.pages)) {
|
|
463
|
+
for (const page of entry.pages) {
|
|
464
|
+
if (typeof page === 'object')
|
|
465
|
+
walk(page, dropdownNames);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if ('groups' in entry && Array.isArray(entry.groups)) {
|
|
469
|
+
for (const group of entry.groups) {
|
|
470
|
+
walk(group, dropdownNames);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const key of divisions) {
|
|
474
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
475
|
+
if (!subDivisions)
|
|
476
|
+
continue;
|
|
477
|
+
for (const subDivision of subDivisions) {
|
|
478
|
+
walk(subDivision, key === 'dropdowns' ? [...dropdownNames, findDivisionName(subDivision)] : dropdownNames);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
walk(rootNav, []);
|
|
483
|
+
return pagesByDropdown;
|
|
484
|
+
}
|
|
387
485
|
function getFirstPageFromCurrentDivisions(item, currentVersion, currentLanguage, userGroups, isPreview = false) {
|
|
388
486
|
var _a, _b;
|
|
389
487
|
if ('versions' in item && currentVersion) {
|
|
@@ -698,6 +796,7 @@ function findMostRelevantDivisions(currentPath, decoratedNav, currentVersion, cu
|
|
|
698
796
|
divisionKey: key,
|
|
699
797
|
isPreview,
|
|
700
798
|
parentDivisionNames: ancestorDivisionNames,
|
|
799
|
+
rootNav: decoratedNav,
|
|
701
800
|
});
|
|
702
801
|
})
|
|
703
802
|
.filter(Boolean);
|
|
@@ -858,4 +858,115 @@ describe('filterDivisions dropdown corresponding pages', () => {
|
|
|
858
858
|
}, currentPath: '/vega/0.21/install' }));
|
|
859
859
|
expect((_a = result.dropdowns.find((d) => d.name === 'Version 0.22')) === null || _a === void 0 ? void 0 : _a.href).toBe('/vega/0.22/install');
|
|
860
860
|
});
|
|
861
|
+
describe('when the page sits in a different tab in the target version', () => {
|
|
862
|
+
const movedPageNav = {
|
|
863
|
+
tabs: [
|
|
864
|
+
{
|
|
865
|
+
tab: 'Guides',
|
|
866
|
+
dropdowns: [
|
|
867
|
+
{
|
|
868
|
+
dropdown: 'v3',
|
|
869
|
+
pages: [
|
|
870
|
+
{ title: 'Quickstart', href: '/docs/v3/quickstart' },
|
|
871
|
+
{ title: 'Webhooks', href: '/docs/v3/webhooks' },
|
|
872
|
+
],
|
|
873
|
+
},
|
|
874
|
+
{ dropdown: 'v2', pages: [{ title: 'Quickstart', href: '/docs/v2/quickstart' }] },
|
|
875
|
+
{ dropdown: 'v1', pages: [{ title: 'Quickstart', href: '/docs/v1/quickstart' }] },
|
|
876
|
+
],
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
tab: 'Reference',
|
|
880
|
+
dropdowns: [
|
|
881
|
+
{
|
|
882
|
+
dropdown: 'v2',
|
|
883
|
+
pages: [
|
|
884
|
+
{ title: 'Errors', href: '/docs/v2/errors' },
|
|
885
|
+
{ title: 'Webhooks', href: '/docs/v2/webhooks' },
|
|
886
|
+
],
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
dropdown: 'v1',
|
|
890
|
+
pages: [
|
|
891
|
+
{ title: 'Errors', href: '/docs/v1/errors' },
|
|
892
|
+
{ title: 'Webhooks', href: '/docs/v1/webhooks', groups: ['admin'] },
|
|
893
|
+
],
|
|
894
|
+
},
|
|
895
|
+
],
|
|
896
|
+
},
|
|
897
|
+
],
|
|
898
|
+
};
|
|
899
|
+
it('links to the same page in that version wherever it is in the navigation', () => {
|
|
900
|
+
var _a;
|
|
901
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: movedPageNav, currentPath: '/docs/v3/webhooks' }));
|
|
902
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'v2')) === null || _a === void 0 ? void 0 : _a.href).toBe('/docs/v2/webhooks');
|
|
903
|
+
});
|
|
904
|
+
it('falls back to the first page when that version has no page the viewer can access', () => {
|
|
905
|
+
var _a;
|
|
906
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: movedPageNav, currentPath: '/docs/v3/webhooks' }));
|
|
907
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'v1')) === null || _a === void 0 ? void 0 : _a.href).toBe('/docs/v1/quickstart');
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
describe('when only one version has a path prefix', () => {
|
|
911
|
+
const prefixNav = {
|
|
912
|
+
tabs: [
|
|
913
|
+
{
|
|
914
|
+
tab: 'Guides',
|
|
915
|
+
dropdowns: [
|
|
916
|
+
{
|
|
917
|
+
dropdown: 'Latest',
|
|
918
|
+
pages: [
|
|
919
|
+
{ title: 'Quickstart', href: '/quickstart' },
|
|
920
|
+
{ title: 'Webhooks', href: '/webhooks' },
|
|
921
|
+
],
|
|
922
|
+
},
|
|
923
|
+
{ dropdown: 'v1', pages: [{ title: 'Quickstart', href: '/v1/quickstart' }] },
|
|
924
|
+
],
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
tab: 'Reference',
|
|
928
|
+
dropdowns: [
|
|
929
|
+
{
|
|
930
|
+
dropdown: 'v1',
|
|
931
|
+
pages: [
|
|
932
|
+
{ title: 'Errors', href: '/v1/errors' },
|
|
933
|
+
{ title: 'Webhooks', href: '/v1/webhooks' },
|
|
934
|
+
],
|
|
935
|
+
},
|
|
936
|
+
{ dropdown: 'Latest', pages: [{ title: 'Errors', href: '/errors' }] },
|
|
937
|
+
{ dropdown: 'v2', pages: [{ title: 'Errors', href: '/v2/errors' }] },
|
|
938
|
+
],
|
|
939
|
+
},
|
|
940
|
+
],
|
|
941
|
+
};
|
|
942
|
+
it('adds the version prefix when switching from an unprefixed page', () => {
|
|
943
|
+
var _a;
|
|
944
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: prefixNav, currentPath: '/webhooks' }));
|
|
945
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'v1')) === null || _a === void 0 ? void 0 : _a.href).toBe('/v1/webhooks');
|
|
946
|
+
});
|
|
947
|
+
it('drops the version prefix when switching to an unprefixed version', () => {
|
|
948
|
+
var _a;
|
|
949
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: prefixNav, currentPath: '/v1/errors' }));
|
|
950
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'Latest')) === null || _a === void 0 ? void 0 : _a.href).toBe('/errors');
|
|
951
|
+
});
|
|
952
|
+
it('never links to a page from another version', () => {
|
|
953
|
+
var _a;
|
|
954
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: prefixNav, currentPath: '/v1/webhooks' }));
|
|
955
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'v2')) === null || _a === void 0 ? void 0 : _a.href).toBe('/v2/errors');
|
|
956
|
+
});
|
|
957
|
+
it('never links to a page inside a hidden tab', () => {
|
|
958
|
+
var _a;
|
|
959
|
+
const result = filterDivisions(Object.assign(Object.assign({}, defaultArgs), { decoratedNav: {
|
|
960
|
+
tabs: [
|
|
961
|
+
...prefixNav.tabs,
|
|
962
|
+
{
|
|
963
|
+
tab: 'Internal',
|
|
964
|
+
hidden: true,
|
|
965
|
+
dropdowns: [{ dropdown: 'v2', pages: [{ title: 'Webhooks', href: '/v2/webhooks' }] }],
|
|
966
|
+
},
|
|
967
|
+
],
|
|
968
|
+
}, currentPath: '/v1/webhooks' }));
|
|
969
|
+
expect((_a = result.dropdowns.find((d) => d.name === 'v2')) === null || _a === void 0 ? void 0 : _a.href).toBe('/v2/errors');
|
|
970
|
+
});
|
|
971
|
+
});
|
|
861
972
|
});
|