@thallylabs/migrate 0.2.12 → 0.2.14
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 +29 -10
- package/dist/index.d.ts +6 -1
- package/dist/index.js +273 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3235,7 +3235,7 @@ function projectedHref(value) {
|
|
|
3235
3235
|
return href;
|
|
3236
3236
|
}
|
|
3237
3237
|
function labelFor(value, fallback) {
|
|
3238
|
-
for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "label", "name", "title"]) {
|
|
3238
|
+
for (const key of ["tab", "group", "anchor", "product", "dropdown", "version", "menu", "item", "label", "name", "title"]) {
|
|
3239
3239
|
if (typeof value[key] === "string" && value[key]) return String(value[key]);
|
|
3240
3240
|
}
|
|
3241
3241
|
return fallback;
|
|
@@ -3307,29 +3307,40 @@ function readMintlifyConfig(repositoryRoot) {
|
|
|
3307
3307
|
const raw = readMintlifyJson(configPath, repositoryRoot);
|
|
3308
3308
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
3309
3309
|
}
|
|
3310
|
+
function specSource(value) {
|
|
3311
|
+
const source = typeof value === "string" ? value : objectValue(value)?.source;
|
|
3312
|
+
return typeof source === "string" && source.trim() ? source.trim() : void 0;
|
|
3313
|
+
}
|
|
3314
|
+
var hasNavigationChildren = (node) => ["groups", "pages"].some((key) => Array.isArray(node[key]) && node[key].length > 0);
|
|
3310
3315
|
function mintlifyNavigationApiReferences(config) {
|
|
3311
3316
|
const navigation = objectValue(config.navigation) ?? config;
|
|
3312
3317
|
const references = [];
|
|
3313
3318
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus", "languages"];
|
|
3314
3319
|
const nestedKeys = ["groups", "pages"];
|
|
3315
|
-
function
|
|
3316
|
-
const node = withFlattenedProductGroups(rawNode);
|
|
3320
|
+
function collect(node, tabLabel, sibling) {
|
|
3317
3321
|
for (const kind of ["openapi", "asyncapi"]) {
|
|
3318
|
-
const value = node[kind];
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
continue;
|
|
3322
|
-
}
|
|
3323
|
-
const object = objectValue(value);
|
|
3324
|
-
if (object && typeof object.source === "string" && object.source.trim()) {
|
|
3322
|
+
const value = specSource(node[kind]);
|
|
3323
|
+
const directory = objectValue(node[kind])?.directory;
|
|
3324
|
+
if (value) {
|
|
3325
3325
|
references.push({
|
|
3326
|
-
value
|
|
3326
|
+
value,
|
|
3327
3327
|
kind,
|
|
3328
3328
|
tabLabel,
|
|
3329
|
-
...
|
|
3329
|
+
...sibling,
|
|
3330
|
+
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {}
|
|
3330
3331
|
});
|
|
3331
3332
|
}
|
|
3332
3333
|
}
|
|
3334
|
+
}
|
|
3335
|
+
const siblingSources = /* @__PURE__ */ new Map();
|
|
3336
|
+
function uniqueSiblingLabel(label, source) {
|
|
3337
|
+
let candidate = label;
|
|
3338
|
+
for (let n = 2; siblingSources.has(candidate) && siblingSources.get(candidate) !== source; n++) candidate = `${label} (${n})`;
|
|
3339
|
+
siblingSources.set(candidate, source);
|
|
3340
|
+
return candidate;
|
|
3341
|
+
}
|
|
3342
|
+
function visitChildren(rawNode, tabLabel) {
|
|
3343
|
+
const node = withFlattenedProductGroups(rawNode);
|
|
3333
3344
|
for (const key of containerKeys) {
|
|
3334
3345
|
const entries = node[key];
|
|
3335
3346
|
if (!Array.isArray(entries)) continue;
|
|
@@ -3348,10 +3359,45 @@ function mintlifyNavigationApiReferences(config) {
|
|
|
3348
3359
|
if (object) visit(object, tabLabel);
|
|
3349
3360
|
}
|
|
3350
3361
|
}
|
|
3362
|
+
if (Array.isArray(node.menu)) {
|
|
3363
|
+
const tab = tabLabel ?? "Documentation";
|
|
3364
|
+
const items = node.menu.flatMap((entry) => {
|
|
3365
|
+
const object = objectValue(entry);
|
|
3366
|
+
return object ? [object] : [];
|
|
3367
|
+
});
|
|
3368
|
+
const apiItems = items.filter((item) => specSource(item.openapi) || specSource(item.asyncapi));
|
|
3369
|
+
const soleContent = apiItems.length === 1 && !hasNavigationChildren(node) && !items.some(hasNavigationChildren);
|
|
3370
|
+
items.forEach((item, index) => {
|
|
3371
|
+
if (soleContent && apiItems[0] === item) collect(item, tab);
|
|
3372
|
+
else if (apiItems.includes(item)) {
|
|
3373
|
+
const label = uniqueSiblingLabel(
|
|
3374
|
+
`${tab}: ${labelFor(item, `Menu item ${index + 1}`)}`,
|
|
3375
|
+
`${specSource(item.openapi) ?? ""}|${specSource(item.asyncapi) ?? ""}`
|
|
3376
|
+
);
|
|
3377
|
+
const icon = iconName(item.icon);
|
|
3378
|
+
collect(item, label, { parentTab: tab, ...icon ? { icon } : {}, ...item.hidden === true ? { hidden: true } : {} });
|
|
3379
|
+
}
|
|
3380
|
+
visitChildren(item, tabLabel);
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
function visit(node, tabLabel) {
|
|
3385
|
+
collect(node, tabLabel);
|
|
3386
|
+
visitChildren(node, tabLabel);
|
|
3351
3387
|
}
|
|
3352
3388
|
visit(navigation, void 0);
|
|
3353
3389
|
return references;
|
|
3354
3390
|
}
|
|
3391
|
+
function insertApiTab(tabs, tab, parentTab) {
|
|
3392
|
+
let index = -1;
|
|
3393
|
+
if (parentTab) {
|
|
3394
|
+
tabs.forEach((candidate, position) => {
|
|
3395
|
+
if (candidate.tab === parentTab || candidate.tab.startsWith(`${parentTab}: `)) index = position;
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
tabs.splice(index === -1 ? tabs.length : index + 1, 0, tab);
|
|
3399
|
+
return tabs;
|
|
3400
|
+
}
|
|
3355
3401
|
function normalizePageRef(value, pathPrefix = "") {
|
|
3356
3402
|
if (/^(?:https?:)?\/\//i.test(value) || value.startsWith("#")) return null;
|
|
3357
3403
|
let ref = localReference(value).split("?", 1)[0].replace(/^\/+/, "");
|
|
@@ -3437,6 +3483,65 @@ function convertPage(value, context) {
|
|
|
3437
3483
|
}
|
|
3438
3484
|
return null;
|
|
3439
3485
|
}
|
|
3486
|
+
var KNOWN_NAVIGATION_KEYS = /* @__PURE__ */ new Set([
|
|
3487
|
+
"tabs",
|
|
3488
|
+
"anchors",
|
|
3489
|
+
"products",
|
|
3490
|
+
"dropdowns",
|
|
3491
|
+
"versions",
|
|
3492
|
+
"menus",
|
|
3493
|
+
"languages",
|
|
3494
|
+
"productGroups",
|
|
3495
|
+
"groups",
|
|
3496
|
+
"pages",
|
|
3497
|
+
"menu",
|
|
3498
|
+
"global"
|
|
3499
|
+
]);
|
|
3500
|
+
function convertMenuItems(items, context) {
|
|
3501
|
+
return items.flatMap((value, index) => {
|
|
3502
|
+
const item = objectValue(value);
|
|
3503
|
+
if (!item) return [];
|
|
3504
|
+
const label = labelFor(item, `Menu item ${index + 1}`);
|
|
3505
|
+
if (typeof item.description === "string" && item.description.trim()) {
|
|
3506
|
+
warnOnce(
|
|
3507
|
+
context,
|
|
3508
|
+
`menu-item-description:${label}`,
|
|
3509
|
+
`Mintlify menu item "${label}" has a description, which Thally groups cannot display; it was omitted.`
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
const hasUnsourcedSpec = Boolean(item.openapi || item.asyncapi) && !specSource(item.openapi) && !specSource(item.asyncapi);
|
|
3513
|
+
if (hasUnsourcedSpec) {
|
|
3514
|
+
warnOnce(
|
|
3515
|
+
context,
|
|
3516
|
+
`menu-item-spec:${label}`,
|
|
3517
|
+
`Mintlify menu item "${label}" has an openapi/asyncapi value without a "source", so its API reference was not migrated.`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const children = convertNavigationValues([
|
|
3521
|
+
...Array.isArray(item.groups) ? item.groups : [],
|
|
3522
|
+
...Array.isArray(item.pages) ? item.pages : []
|
|
3523
|
+
], context);
|
|
3524
|
+
if (typeof item.href === "string" && item.href) {
|
|
3525
|
+
warnOnce(
|
|
3526
|
+
context,
|
|
3527
|
+
`menu-item-href:${label}`,
|
|
3528
|
+
`Mintlify menu item "${label}" links to ${item.href}, which cannot be represented as a Thally group; the link was omitted.`
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
if (children.length === 0) {
|
|
3532
|
+
if (!specSource(item.openapi) && !specSource(item.asyncapi) && !hasUnsourcedSpec) {
|
|
3533
|
+
warnOnce(context, `menu-item-empty:${label}`, `Mintlify menu item "${label}" has no projectable pages and was omitted.`);
|
|
3534
|
+
}
|
|
3535
|
+
return [];
|
|
3536
|
+
}
|
|
3537
|
+
return [{
|
|
3538
|
+
group: label,
|
|
3539
|
+
...iconName(item.icon) ? { icon: iconName(item.icon) } : {},
|
|
3540
|
+
...item.hidden === true ? { hidden: true } : {},
|
|
3541
|
+
pages: children
|
|
3542
|
+
}];
|
|
3543
|
+
});
|
|
3544
|
+
}
|
|
3440
3545
|
function convertNavigationValues(values, context) {
|
|
3441
3546
|
return values.flatMap((value) => {
|
|
3442
3547
|
const page = convertPage(value, context);
|
|
@@ -3448,6 +3553,13 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
3448
3553
|
if (!rawContainer) return [];
|
|
3449
3554
|
const container = withFlattenedProductGroups(rawContainer);
|
|
3450
3555
|
const containerKeys = ["tabs", "anchors", "products", "dropdowns", "versions", "menus"];
|
|
3556
|
+
for (const key of Object.keys(container)) {
|
|
3557
|
+
if (KNOWN_NAVIGATION_KEYS.has(key)) continue;
|
|
3558
|
+
const entries = container[key];
|
|
3559
|
+
if (Array.isArray(entries) && entries.some((entry) => objectValue(entry))) {
|
|
3560
|
+
warnOnce(context, `unknown-container:${key}`, `Mintlify navigation key "${key}" is not supported and its contents were not projected.`);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3451
3563
|
for (const key of containerKeys) {
|
|
3452
3564
|
if (!Array.isArray(container[key])) continue;
|
|
3453
3565
|
const entries = [...container[key]];
|
|
@@ -3459,7 +3571,7 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
3459
3571
|
if (!object) return [];
|
|
3460
3572
|
const tab = labelFor(object, `${fallbackTab} ${index + 1}`);
|
|
3461
3573
|
const href = projectedHref(object.href);
|
|
3462
|
-
const hasNestedContainers = containerKeys.some((containerKey) => Array.isArray(object[containerKey]));
|
|
3574
|
+
const hasNestedContainers = [...containerKeys, "menu"].some((containerKey) => Array.isArray(object[containerKey]));
|
|
3463
3575
|
if (href && !object.pages && !object.groups && !hasNestedContainers) {
|
|
3464
3576
|
return [{
|
|
3465
3577
|
tab,
|
|
@@ -3491,6 +3603,11 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
3491
3603
|
return [];
|
|
3492
3604
|
});
|
|
3493
3605
|
if (tabs.length > 0) {
|
|
3606
|
+
for (const sibling of ["groups", "pages", "menu"]) {
|
|
3607
|
+
if (Array.isArray(container[sibling]) && container[sibling].length > 0) {
|
|
3608
|
+
warnOnce(context, `container-sibling:${key}:${sibling}`, `Mintlify "${sibling}" next to "${key}" was not projected; only the "${key}" containers were kept.`);
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3494
3611
|
if (depth === 0 && trace) trace.rootContainerKind = key;
|
|
3495
3612
|
return tabs;
|
|
3496
3613
|
}
|
|
@@ -3503,7 +3620,10 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
3503
3620
|
...rawGroups,
|
|
3504
3621
|
...rawPages
|
|
3505
3622
|
];
|
|
3506
|
-
const children =
|
|
3623
|
+
const children = [
|
|
3624
|
+
...convertNavigationValues(values, context),
|
|
3625
|
+
...Array.isArray(container.menu) ? convertMenuItems(container.menu, context) : []
|
|
3626
|
+
];
|
|
3507
3627
|
return children.length > 0 ? [{
|
|
3508
3628
|
tab: fallbackTab,
|
|
3509
3629
|
...hasRootNodes ? { pages: children } : { groups: children },
|
|
@@ -3553,7 +3673,7 @@ function projectedGlobalNavigationLinks(value) {
|
|
|
3553
3673
|
const label = labelFor(item, "");
|
|
3554
3674
|
if (label) links.push({ label, href });
|
|
3555
3675
|
}
|
|
3556
|
-
for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus"]) {
|
|
3676
|
+
for (const key of ["tabs", "anchors", "dropdowns", "products", "versions", "languages", "menus", "menu"]) {
|
|
3557
3677
|
if (Array.isArray(item[key])) visit(item[key]);
|
|
3558
3678
|
}
|
|
3559
3679
|
};
|
|
@@ -5387,6 +5507,10 @@ var MAX_PAGE_BYTES = 2e6;
|
|
|
5387
5507
|
var MAX_ASSET_BYTES = 25e6;
|
|
5388
5508
|
var MAX_TOTAL_ASSET_BYTES = 5e8;
|
|
5389
5509
|
var GIT_LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
|
|
5510
|
+
function listAssetPaths(paths) {
|
|
5511
|
+
const shown = paths.slice(0, 5).join(", ");
|
|
5512
|
+
return paths.length > 5 ? `${shown}, and ${paths.length - 5} more` : shown;
|
|
5513
|
+
}
|
|
5390
5514
|
function isGitLfsPointer(content) {
|
|
5391
5515
|
return content.length < 1024 && content.toString("utf8", 0, GIT_LFS_POINTER_PREFIX.length) === GIT_LFS_POINTER_PREFIX;
|
|
5392
5516
|
}
|
|
@@ -5810,17 +5934,23 @@ function parseGitHubRepositoryUrl(rawUrl) {
|
|
|
5810
5934
|
}
|
|
5811
5935
|
var CLONE_RETRY_ATTEMPTS = 3;
|
|
5812
5936
|
var CLONE_RETRY_DELAY_MS = 1e3;
|
|
5813
|
-
var
|
|
5937
|
+
var DEFAULT_CLONE_IDLE_TIMEOUT_MS = 2 * 6e4;
|
|
5938
|
+
var MAX_GIT_PROCESS_MS = 60 * 6e4;
|
|
5939
|
+
var HARD_TIMEOUT_REASON = `still running after ${MAX_GIT_PROCESS_MS / 6e4} minutes`;
|
|
5940
|
+
var GIT_EXIT_DRAIN_MS = 2e3;
|
|
5814
5941
|
var RETRYABLE_CLONE_ERROR = /RPC failed|Recv failure|early EOF|curl \d+|Could not resolve host|Connection (?:reset|refused|timed out)|The remote end hung up|SSL[_ ]?(?:read|connect|write) error|timed out|network is unreachable/i;
|
|
5815
5942
|
function delay(ms) {
|
|
5816
5943
|
return new Promise((resolve3) => {
|
|
5817
5944
|
setTimeout(resolve3, ms);
|
|
5818
5945
|
});
|
|
5819
5946
|
}
|
|
5820
|
-
function
|
|
5947
|
+
function gitIdleTimeoutMs() {
|
|
5821
5948
|
const raw = process.env.THALLY_MIGRATE_CLONE_TIMEOUT_MS;
|
|
5822
5949
|
const parsed = raw ? Number(raw) : NaN;
|
|
5823
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed :
|
|
5950
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_IDLE_TIMEOUT_MS;
|
|
5951
|
+
}
|
|
5952
|
+
function gitErrorTail(stderr) {
|
|
5953
|
+
return stderr.split(/[\r\n]+/).map((line) => line.trim()).filter((line) => line && !/^(?:remote: )?[A-Za-z][A-Za-z ]*:\s+\d+%/.test(line)).slice(-5).join("\n");
|
|
5824
5954
|
}
|
|
5825
5955
|
function runGit(args, options) {
|
|
5826
5956
|
return new Promise((resolve3, reject) => {
|
|
@@ -5830,24 +5960,66 @@ function runGit(args, options) {
|
|
|
5830
5960
|
env: { ...process.env, ...options.env }
|
|
5831
5961
|
});
|
|
5832
5962
|
let stderr = "";
|
|
5833
|
-
let
|
|
5834
|
-
|
|
5835
|
-
|
|
5963
|
+
let timeoutReason = "";
|
|
5964
|
+
let settled = false;
|
|
5965
|
+
let exitCode;
|
|
5966
|
+
let drainTimer;
|
|
5967
|
+
const idleMs = gitIdleTimeoutMs();
|
|
5968
|
+
const idleMessage = `no progress for ${idleMs < 1e3 ? `${idleMs}ms` : `${Math.round(idleMs / 1e3)}s`}`;
|
|
5969
|
+
const clearTimers = () => {
|
|
5970
|
+
clearTimeout(idleTimer);
|
|
5971
|
+
clearTimeout(hardTimer);
|
|
5972
|
+
clearTimeout(drainTimer);
|
|
5973
|
+
};
|
|
5974
|
+
const finish = (code) => {
|
|
5975
|
+
if (settled) return;
|
|
5976
|
+
settled = true;
|
|
5977
|
+
clearTimers();
|
|
5978
|
+
child.stderr.destroy();
|
|
5979
|
+
if (code === 0) resolve3();
|
|
5980
|
+
else if (timeoutReason) {
|
|
5981
|
+
const hint = timeoutReason === HARD_TIMEOUT_REASON ? "" : "; on a very slow link, raise THALLY_MIGRATE_CLONE_TIMEOUT_MS (milliseconds of allowed silence)";
|
|
5982
|
+
reject(new Error(`${options.label}: timed out (${timeoutReason}) and was killed. Check your network connection and try again${hint}.`));
|
|
5983
|
+
} else reject(new Error(`${options.label}: ${gitErrorTail(stderr) || `git exited ${code}`}`));
|
|
5984
|
+
};
|
|
5985
|
+
const kill = (reason) => {
|
|
5986
|
+
if (exitCode !== void 0) {
|
|
5987
|
+
finish(exitCode);
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
timeoutReason = reason;
|
|
5836
5991
|
child.kill("SIGKILL");
|
|
5837
|
-
}
|
|
5992
|
+
};
|
|
5993
|
+
let idleTimer = setTimeout(() => {
|
|
5994
|
+
kill(idleMessage);
|
|
5995
|
+
}, idleMs);
|
|
5996
|
+
const hardTimer = setTimeout(() => {
|
|
5997
|
+
kill(HARD_TIMEOUT_REASON);
|
|
5998
|
+
}, MAX_GIT_PROCESS_MS);
|
|
5838
5999
|
child.stderr.setEncoding("utf8");
|
|
5839
6000
|
child.stderr.on("data", (chunk) => {
|
|
5840
|
-
|
|
6001
|
+
stderr = (stderr + chunk).slice(-16e3);
|
|
6002
|
+
if (exitCode !== void 0 || settled) return;
|
|
6003
|
+
clearTimeout(idleTimer);
|
|
6004
|
+
idleTimer = setTimeout(() => {
|
|
6005
|
+
kill(idleMessage);
|
|
6006
|
+
}, idleMs);
|
|
5841
6007
|
});
|
|
5842
6008
|
child.on("error", (error) => {
|
|
5843
|
-
|
|
6009
|
+
if (settled) return;
|
|
6010
|
+
settled = true;
|
|
6011
|
+
clearTimers();
|
|
5844
6012
|
reject(error);
|
|
5845
6013
|
});
|
|
5846
|
-
child.on("close",
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
6014
|
+
child.on("close", finish);
|
|
6015
|
+
child.on("exit", (code) => {
|
|
6016
|
+
exitCode = code;
|
|
6017
|
+
clearTimeout(idleTimer);
|
|
6018
|
+
clearTimeout(hardTimer);
|
|
6019
|
+
if (timeoutReason) finish(code);
|
|
6020
|
+
else drainTimer = setTimeout(() => {
|
|
6021
|
+
finish(code);
|
|
6022
|
+
}, GIT_EXIT_DRAIN_MS);
|
|
5851
6023
|
});
|
|
5852
6024
|
});
|
|
5853
6025
|
}
|
|
@@ -5863,7 +6035,7 @@ var LFS_FILTER_OVERRIDE_ENV = {
|
|
|
5863
6035
|
GIT_CONFIG_VALUE_3: "false"
|
|
5864
6036
|
};
|
|
5865
6037
|
function cloneOnce(source, targetDir) {
|
|
5866
|
-
const args = ["clone", "--depth", "1", "--single-branch"];
|
|
6038
|
+
const args = ["clone", "--progress", "--depth", "1", "--single-branch"];
|
|
5867
6039
|
if (source.branch !== "HEAD") args.push("--branch", source.branch);
|
|
5868
6040
|
args.push("--", source.cloneUrl, targetDir);
|
|
5869
6041
|
return runGit(args, {
|
|
@@ -5912,6 +6084,7 @@ async function initSubmodules(targetDir, warnings) {
|
|
|
5912
6084
|
"submodule",
|
|
5913
6085
|
"update",
|
|
5914
6086
|
"--init",
|
|
6087
|
+
"--progress",
|
|
5915
6088
|
"--depth",
|
|
5916
6089
|
"1",
|
|
5917
6090
|
"--",
|
|
@@ -6149,6 +6322,13 @@ function classifyApiSpec(path) {
|
|
|
6149
6322
|
if (!topLevel) return "unknown";
|
|
6150
6323
|
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
6151
6324
|
}
|
|
6325
|
+
var SPEC_DIRECTORY = "openapi";
|
|
6326
|
+
function specAssetPath(filename) {
|
|
6327
|
+
return `${SPEC_DIRECTORY}/${filename}`;
|
|
6328
|
+
}
|
|
6329
|
+
function specAsset(filename, content) {
|
|
6330
|
+
return { path: specAssetPath(filename), content, projectRelative: true };
|
|
6331
|
+
}
|
|
6152
6332
|
function mintlifyTopLevelApiReferences(config) {
|
|
6153
6333
|
const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
|
|
6154
6334
|
if (!api) return [];
|
|
@@ -6169,7 +6349,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
6169
6349
|
const seen = /* @__PURE__ */ new Set();
|
|
6170
6350
|
const specs = [];
|
|
6171
6351
|
for (const reference of references) {
|
|
6172
|
-
const dedupeKey = `${reference.kind}:${reference.value}`;
|
|
6352
|
+
const dedupeKey = `${reference.kind}:${reference.value}:${reference.tabLabel ?? ""}`;
|
|
6173
6353
|
if (seen.has(dedupeKey)) continue;
|
|
6174
6354
|
seen.add(dedupeKey);
|
|
6175
6355
|
const tabSuffix = reference.tabLabel ? ` (tab "${reference.tabLabel}")` : "";
|
|
@@ -6181,7 +6361,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
6181
6361
|
continue;
|
|
6182
6362
|
}
|
|
6183
6363
|
if (/^https:\/\//i.test(reference.value)) {
|
|
6184
|
-
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {} });
|
|
6364
|
+
remoteSpecs.push({ url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {}, ...reference.parentTab ? { parentTab: reference.parentTab } : {}, ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {} });
|
|
6185
6365
|
warnings.push({
|
|
6186
6366
|
code: "unsupported-config",
|
|
6187
6367
|
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
@@ -6212,15 +6392,19 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
6212
6392
|
filename: basename3(match.relativePath),
|
|
6213
6393
|
content: readFileSync5(match.absolutePath),
|
|
6214
6394
|
tabLabel: reference.tabLabel,
|
|
6395
|
+
parentTab: reference.parentTab,
|
|
6396
|
+
icon: reference.icon,
|
|
6397
|
+
hidden: reference.hidden,
|
|
6215
6398
|
sourcePath: match.relativePath,
|
|
6216
6399
|
directory: reference.directory
|
|
6217
6400
|
});
|
|
6218
6401
|
}
|
|
6219
6402
|
const filenameOwners = /* @__PURE__ */ new Map();
|
|
6403
|
+
const filenameSources = /* @__PURE__ */ new Map();
|
|
6220
6404
|
for (const spec of specs) {
|
|
6221
6405
|
const groupKey = spec.tabLabel ?? "\0default";
|
|
6222
6406
|
const owner = filenameOwners.get(spec.filename);
|
|
6223
|
-
if (owner !== void 0 && owner !== groupKey) {
|
|
6407
|
+
if (owner !== void 0 && owner !== groupKey && filenameSources.get(spec.filename) !== spec.sourcePath) {
|
|
6224
6408
|
let disambiguated = spec.sourcePath.replace(/\//g, "-");
|
|
6225
6409
|
while (filenameOwners.has(disambiguated) && filenameOwners.get(disambiguated) !== groupKey) {
|
|
6226
6410
|
disambiguated = `${groupKey.replace(/\W+/g, "-")}-${disambiguated}`;
|
|
@@ -6228,6 +6412,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
|
|
|
6228
6412
|
spec.filename = disambiguated;
|
|
6229
6413
|
}
|
|
6230
6414
|
filenameOwners.set(spec.filename, groupKey);
|
|
6415
|
+
filenameSources.set(spec.filename, spec.sourcePath);
|
|
6231
6416
|
}
|
|
6232
6417
|
return specs;
|
|
6233
6418
|
}
|
|
@@ -6282,7 +6467,7 @@ function apiOperationLinkMap(sources, docsConfig) {
|
|
|
6282
6467
|
const prefixLandings = /* @__PURE__ */ new Map();
|
|
6283
6468
|
const apiTabs = docsConfig.tabs.filter((tab) => !tab.hidden && tab.api);
|
|
6284
6469
|
apiTabs.forEach((tab, index) => {
|
|
6285
|
-
const source = sources.find((entry) => tab.api?.source ===
|
|
6470
|
+
const source = sources.find((entry) => tab.api?.source === specAssetPath(entry.filename));
|
|
6286
6471
|
if (!source || !source.prefix) return;
|
|
6287
6472
|
const specId = index === 0 ? "default" : slugifyApiSpecId(tab.tab);
|
|
6288
6473
|
const prefix = source.prefix.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
@@ -6682,7 +6867,15 @@ function preserveLinkedAnchors(pages) {
|
|
|
6682
6867
|
const key = comparable(cell.text);
|
|
6683
6868
|
candidateIndexes.set(key, [...candidateIndexes.get(key) ?? [], { index: cell.index, kind: "table" }]);
|
|
6684
6869
|
}
|
|
6685
|
-
|
|
6870
|
+
const headingSlugs = /* @__PURE__ */ new Map();
|
|
6871
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
6872
|
+
for (const heading of headings) {
|
|
6873
|
+
const slug2 = heading.text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
6874
|
+
existing.add(slug2);
|
|
6875
|
+
const seen = slugCounts.get(slug2) ?? 0;
|
|
6876
|
+
slugCounts.set(slug2, seen + 1);
|
|
6877
|
+
if (slug2) headingSlugs.set(seen === 0 ? slug2 : `${slug2}-${seen}`, heading.index);
|
|
6878
|
+
}
|
|
6686
6879
|
const headingAliases = /* @__PURE__ */ new Map();
|
|
6687
6880
|
const tableAliases = /* @__PURE__ */ new Map();
|
|
6688
6881
|
for (const fragment of fragments) {
|
|
@@ -6696,6 +6889,14 @@ function preserveLinkedAnchors(pages) {
|
|
|
6696
6889
|
const selections = linkIndexes.map((index2) => matches.filter((candidate) => candidate.index < index2).at(-1));
|
|
6697
6890
|
if (selections.length && selections.every((candidate) => candidate?.index === selections[0]?.index)) match = selections[0];
|
|
6698
6891
|
}
|
|
6892
|
+
if (!match && matches.length > 1) {
|
|
6893
|
+
const headingMatches = matches.filter((candidate) => candidate.kind === "heading");
|
|
6894
|
+
if (headingMatches.length === 1) match = headingMatches[0];
|
|
6895
|
+
}
|
|
6896
|
+
if (!match) {
|
|
6897
|
+
const index2 = headingSlugs.get(fragment.toLowerCase());
|
|
6898
|
+
if (index2 !== void 0) match = { index: index2, kind: "heading" };
|
|
6899
|
+
}
|
|
6699
6900
|
if (!match) continue;
|
|
6700
6901
|
const { index, kind } = match;
|
|
6701
6902
|
const aliases = kind === "heading" ? headingAliases : tableAliases;
|
|
@@ -6982,25 +7183,30 @@ ${result.slice(prefix.length)}`;
|
|
|
6982
7183
|
return depth === 0 ? hoistMdxImports(result) : result;
|
|
6983
7184
|
}
|
|
6984
7185
|
function injectOpenApiSpecs(config, specs, warnings) {
|
|
6985
|
-
|
|
7186
|
+
const tabs = config.tabs.map((tab) => ({ ...tab }));
|
|
6986
7187
|
for (const spec of specs) {
|
|
6987
7188
|
const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
|
|
6988
7189
|
if (apiTab) {
|
|
6989
7190
|
if (apiTab.api && warnings) {
|
|
6990
7191
|
warnings.push({
|
|
6991
7192
|
code: "unsupported-config",
|
|
6992
|
-
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "
|
|
7193
|
+
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "${specAssetPath(spec.filename)}" was not added. Put it in its own tab to include it.`
|
|
6993
7194
|
});
|
|
6994
7195
|
continue;
|
|
6995
7196
|
}
|
|
6996
7197
|
apiTab.api = {
|
|
6997
|
-
source:
|
|
7198
|
+
source: specAssetPath(spec.filename),
|
|
6998
7199
|
// An API-only tab needs generated endpoint navigation; an authored
|
|
6999
7200
|
// page tab keeps its own groups alongside the bound spec.
|
|
7000
7201
|
...apiTab.groups?.length || apiTab.pages?.length ? { navigation: false } : {}
|
|
7001
7202
|
};
|
|
7002
7203
|
} else {
|
|
7003
|
-
tabs
|
|
7204
|
+
insertApiTab(tabs, {
|
|
7205
|
+
tab: spec.tabLabel ?? "API Reference",
|
|
7206
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
7207
|
+
...spec.hidden ? { hidden: true } : {},
|
|
7208
|
+
api: { source: specAssetPath(spec.filename) }
|
|
7209
|
+
}, spec.parentTab);
|
|
7004
7210
|
}
|
|
7005
7211
|
}
|
|
7006
7212
|
return { ...config, tabs };
|
|
@@ -7498,28 +7704,35 @@ function migrateRepository(options) {
|
|
|
7498
7704
|
...assetCandidates.filter((candidate) => !isReferenced(candidate))
|
|
7499
7705
|
];
|
|
7500
7706
|
let totalAssetBytes = 0;
|
|
7707
|
+
const overBudgetAssets = [];
|
|
7708
|
+
let overBudgetReferenced = 0;
|
|
7709
|
+
const lfsPointerAssets = [];
|
|
7501
7710
|
for (const { file, assetPath, size } of orderedAssetCandidates) {
|
|
7502
7711
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
code: "limit-reached",
|
|
7506
|
-
message: "This file was not copied because it is larger than 25 MB or the copied files would exceed 500 MB in total. Add it to public/ manually" + (referencingPages ? `; it is used by ${[...referencingPages].slice(0, 3).join(", ")}${referencingPages.size > 3 ? `, and ${referencingPages.size - 3} more` : ""}.` : "."),
|
|
7507
|
-
source: file.relativePath
|
|
7508
|
-
});
|
|
7712
|
+
overBudgetAssets.push(file.relativePath);
|
|
7713
|
+
if (referencedAssetPaths.has(assetPath)) overBudgetReferenced++;
|
|
7509
7714
|
continue;
|
|
7510
7715
|
}
|
|
7511
7716
|
const content = readFileSync5(file.absolutePath);
|
|
7512
7717
|
if (isGitLfsPointer(content)) {
|
|
7513
|
-
|
|
7514
|
-
code: "unsupported-config",
|
|
7515
|
-
message: "This asset is a Git LFS pointer, not its real content (Git LFS was skipped during clone because the host has no git-lfs binary). Install git-lfs and re-run the migration, or add the real file to public/ manually.",
|
|
7516
|
-
source: file.relativePath
|
|
7517
|
-
});
|
|
7718
|
+
lfsPointerAssets.push(file.relativePath);
|
|
7518
7719
|
continue;
|
|
7519
7720
|
}
|
|
7520
7721
|
assets.push({ path: assetPath, content });
|
|
7521
7722
|
totalAssetBytes += size;
|
|
7522
7723
|
}
|
|
7724
|
+
if (overBudgetAssets.length > 0) {
|
|
7725
|
+
warnings.push({
|
|
7726
|
+
code: "limit-reached",
|
|
7727
|
+
message: `${overBudgetAssets.length} asset file${overBudgetAssets.length === 1 ? " was" : "s were"} not copied because files over ${MAX_ASSET_BYTES / 1e6} MB, or beyond ${MAX_TOTAL_ASSET_BYTES / 1e6} MB in total, are skipped: ${listAssetPaths(overBudgetAssets)}. ` + (overBudgetReferenced > 0 ? `${overBudgetReferenced} of them ${overBudgetReferenced === 1 ? "is" : "are"} used by pages, so those images will be broken until you copy ${overBudgetAssets.length === 1 ? "it" : "them"}. ` : "") + `Copy ${overBudgetAssets.length === 1 ? "it" : "them"} into public/ manually.`
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
if (lfsPointerAssets.length > 0) {
|
|
7731
|
+
warnings.push({
|
|
7732
|
+
code: "unsupported-config",
|
|
7733
|
+
message: `${lfsPointerAssets.length} asset file${lfsPointerAssets.length === 1 ? " is a Git LFS pointer" : "s are Git LFS pointers"}, not real content, and ${lfsPointerAssets.length === 1 ? "was" : "were"} not copied (Git LFS was skipped during clone because the host has no git-lfs binary): ${listAssetPaths(lfsPointerAssets)}. Install git-lfs and re-run the migration, or copy the real files into public/ manually.`
|
|
7734
|
+
});
|
|
7735
|
+
}
|
|
7523
7736
|
if (!discoveryBudgetApplied && files.length >= MAX_SOURCE_FILES) {
|
|
7524
7737
|
warnings.push({ code: "limit-reached", message: `Stopped scanning after ${MAX_SOURCE_FILES} files, so the rest of the repository was not looked at. Run the migration on a smaller part of the repository with --docs-dir.` });
|
|
7525
7738
|
}
|
|
@@ -7608,8 +7821,8 @@ function migrateRepository(options) {
|
|
|
7608
7821
|
}
|
|
7609
7822
|
specFilenameSources.set(filename, spec.absolutePath);
|
|
7610
7823
|
const specContent = readFileSync5(spec.absolutePath);
|
|
7611
|
-
if (!assets.some((asset) => asset.path === filename)) {
|
|
7612
|
-
assets.push(
|
|
7824
|
+
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
7825
|
+
assets.push(specAsset(filename, specContent));
|
|
7613
7826
|
}
|
|
7614
7827
|
resolvedSpecs.push({ filename, tabLabel: section.tabLabel, content: specContent, routeSegments: section.routeSegments ?? [] });
|
|
7615
7828
|
continue;
|
|
@@ -7650,8 +7863,8 @@ function migrateRepository(options) {
|
|
|
7650
7863
|
} else if (platform === "mintlify") {
|
|
7651
7864
|
const resolvedSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs);
|
|
7652
7865
|
for (const spec of resolvedSpecs) {
|
|
7653
|
-
if (!assets.some((asset) => asset.path === spec.filename)) {
|
|
7654
|
-
assets.push(
|
|
7866
|
+
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
|
|
7867
|
+
assets.push(specAsset(spec.filename, spec.content));
|
|
7655
7868
|
}
|
|
7656
7869
|
}
|
|
7657
7870
|
if (resolvedSpecs.length > 0) {
|
|
@@ -7665,8 +7878,8 @@ function migrateRepository(options) {
|
|
|
7665
7878
|
const fallback = findOpenApi(files);
|
|
7666
7879
|
if (fallback) {
|
|
7667
7880
|
const filename = basename3(fallback.relativePath);
|
|
7668
|
-
if (!assets.some((asset) => asset.path === filename)) {
|
|
7669
|
-
assets.push(
|
|
7881
|
+
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
7882
|
+
assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
|
|
7670
7883
|
}
|
|
7671
7884
|
docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
|
|
7672
7885
|
}
|
|
@@ -8157,7 +8370,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
8157
8370
|
path: `src/content/${page.id}.mdx`,
|
|
8158
8371
|
content: renderPage(bundle, page)
|
|
8159
8372
|
})),
|
|
8160
|
-
...bundle.assets.map((asset) => ({ path: `public/${asset.path}`, content: asset.content })),
|
|
8373
|
+
...bundle.assets.map((asset) => ({ path: asset.projectRelative ? asset.path : `public/${asset.path}`, content: asset.content })),
|
|
8161
8374
|
...(bundle.componentFiles ?? []).flatMap((file) => file.path === "src/mdx/custom-components.tsx" && options.existingComponentRegistry !== void 0 && typeof file.content === "string" ? mergeComponentRegistry(options.existingComponentRegistry, file.content) : [file]),
|
|
8162
8375
|
{ path: "docs.json", content: `${JSON.stringify(config, null, 2)}
|
|
8163
8376
|
` }
|
|
@@ -9899,10 +10112,10 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
9899
10112
|
const body = await downloadSpec(reference.url, fetcher);
|
|
9900
10113
|
const operations = parseOpenApi(body, extension);
|
|
9901
10114
|
const filename = `openapi-${createHash2("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
9902
|
-
assets.push({ path: filename
|
|
10115
|
+
assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
9903
10116
|
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
9904
|
-
if (tab) tab.api = { source:
|
|
9905
|
-
else tabs
|
|
10117
|
+
if (tab) tab.api = { source: `openapi/${filename}` };
|
|
10118
|
+
else insertApiTab(tabs, { tab: reference.tabLabel ?? "API Reference", ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {}, api: { source: `openapi/${filename}` } }, reference.parentTab);
|
|
9906
10119
|
const routes = /* @__PURE__ */ new Map();
|
|
9907
10120
|
for (const operation of operations) {
|
|
9908
10121
|
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|