@thallylabs/migrate 0.2.17 → 0.2.18
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 +7 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +243 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,13 @@ authored nesting or order. The adapter supports tabs, dropdowns, products,
|
|
|
11
11
|
versions, languages, and reusable `$ref` navigation files; it also preserves
|
|
12
12
|
dropdown presentation, collection descriptions and icons, explicit default
|
|
13
13
|
versions, page route casing, redirects, local snippets, static assets,
|
|
14
|
-
compatible page frontmatter, site identity, and OpenAPI configuration.
|
|
14
|
+
compatible page frontmatter, site identity, and OpenAPI configuration.
|
|
15
|
+
Analytics come across too: docs.json `integrations` and legacy mint.json
|
|
16
|
+
`analytics` for GA4, Google Tag Manager, PostHog, and Plausible are validated
|
|
17
|
+
and written to `docs.json` `integrations` (`integrations` wins per provider when
|
|
18
|
+
both exist). Invalid values are skipped with a warning, and every other
|
|
19
|
+
Mintlify provider (Amplitude, Mixpanel, Segment, and so on) is listed once in a
|
|
20
|
+
warning instead of being dropped silently. For public Mintlify
|
|
15
21
|
sites, the URL adapter reads the structured configuration embedded in the
|
|
16
22
|
rendered application before falling back to DOM link inference.
|
|
17
23
|
|
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,24 @@ interface MigrationDocsConfig {
|
|
|
128
128
|
analytics?: {
|
|
129
129
|
enabled?: boolean;
|
|
130
130
|
};
|
|
131
|
+
/** Third-party analytics in Mintlify's `integrations` shape; validated again by the renderer. */
|
|
132
|
+
integrations?: {
|
|
133
|
+
ga4?: {
|
|
134
|
+
measurementId: string;
|
|
135
|
+
};
|
|
136
|
+
gtm?: {
|
|
137
|
+
tagId: string;
|
|
138
|
+
};
|
|
139
|
+
posthog?: {
|
|
140
|
+
apiKey: string;
|
|
141
|
+
apiHost?: string;
|
|
142
|
+
sessionRecording?: boolean;
|
|
143
|
+
};
|
|
144
|
+
plausible?: {
|
|
145
|
+
domain: string;
|
|
146
|
+
server?: string;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
131
149
|
redirects?: Array<{
|
|
132
150
|
source: string;
|
|
133
151
|
destination: string;
|
|
@@ -447,6 +465,26 @@ declare function parseMarkdownPage(input: {
|
|
|
447
465
|
resolveIdentity?: (frontmatter: Record<string, unknown>, fallback: MarkdownPageIdentity) => MarkdownPageIdentity;
|
|
448
466
|
}): MigrationPage | null;
|
|
449
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Mintlify analytics -> Thally `docs.json` `integrations`.
|
|
470
|
+
*
|
|
471
|
+
* Reads docs.json `integrations` and the legacy mint.json `analytics` block.
|
|
472
|
+
* Values land in an inline script on the rendered site, so each is validated
|
|
473
|
+
* here with the same patterns the renderer applies (src/lib/analytics-config.ts;
|
|
474
|
+
* a test keeps the two in agreement). Invalid values are skipped with a warning,
|
|
475
|
+
* never coerced, and no warning echoes a full value.
|
|
476
|
+
*/
|
|
477
|
+
type MigrationIntegrations = NonNullable<MigrationDocsConfig['integrations']>;
|
|
478
|
+
declare function projectMintlifyIntegrations(config: Record<string, unknown>): {
|
|
479
|
+
integrations?: MigrationIntegrations;
|
|
480
|
+
warnings: Array<MigrationWarning>;
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* Validate an authored Thally `docs.json` `integrations` value with the same
|
|
484
|
+
* rules as the import and the renderer; one value-free message per problem.
|
|
485
|
+
*/
|
|
486
|
+
declare function validateIntegrations(integrations: unknown): Array<string>;
|
|
487
|
+
|
|
450
488
|
/** Render canonical migration bundles into a portable Thally repository tree. */
|
|
451
489
|
|
|
452
490
|
/** Merge imported tabs into an existing site without duplicating changelog tabs. */
|
|
@@ -486,4 +524,4 @@ declare function migrateUrl(options: UrlMigrationOptions): Promise<MigrationBund
|
|
|
486
524
|
*/
|
|
487
525
|
declare function hydrateRemoteApiSpecs(bundle: MigrationBundle, fetcher?: MigrationFetcher): Promise<MigrationBundle>;
|
|
488
526
|
|
|
489
|
-
export { type GitHubRepositorySource, type MigrationAsset, type MigrationBundle, type MigrationDocsConfig, type MigrationFetchRequest, type MigrationFetchResponse, type MigrationFetcher, type MigrationNavigationGroup, type MigrationNavigationTab, type MigrationPage, type MigrationPlatform, type MigrationWarning, type RenderedMigrationFile, type RepositoryMigrationOptions, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, gitmodulePaths, hydrateRemoteApiSpecs, mergeMigrationConfig, migrateRepository, migrateUrl, normalizeMdx, parseGitHubRepositoryUrl, parseMarkdownPage, projectFernNavigation, projectMintlifyNavigation, pruneMissingNavigationPages, readFernConfig, readMintlifyConfig, renderMigrationFiles, validateMigrationUrl };
|
|
527
|
+
export { type GitHubRepositorySource, type MigrationAsset, type MigrationBundle, type MigrationDocsConfig, type MigrationFetchRequest, type MigrationFetchResponse, type MigrationFetcher, type MigrationNavigationGroup, type MigrationNavigationTab, type MigrationPage, type MigrationPlatform, type MigrationWarning, type RenderedMigrationFile, type RepositoryMigrationOptions, type UrlMigrationOptions, addMintlifyDirectoryRedirects, buildNavigationFromPages, cloneGitHubRepository, defaultMigrationFetcher, detectRepositoryDocsDir, detectRepositoryPlatform, gitmodulePaths, hydrateRemoteApiSpecs, mergeMigrationConfig, migrateRepository, migrateUrl, normalizeMdx, parseGitHubRepositoryUrl, parseMarkdownPage, projectFernNavigation, projectMintlifyIntegrations, projectMintlifyNavigation, pruneMissingNavigationPages, readFernConfig, readMintlifyConfig, renderMigrationFiles, validateIntegrations, validateMigrationUrl };
|
package/dist/index.js
CHANGED
|
@@ -326,8 +326,8 @@ function leadingPageHeading(body) {
|
|
|
326
326
|
const root = descriptionParser.parse(body);
|
|
327
327
|
const first = root.children?.find((node) => node.type !== "mdxjsEsm" && node.type !== "html");
|
|
328
328
|
if (first?.type !== "heading" || first.depth !== 1) return void 0;
|
|
329
|
-
const
|
|
330
|
-
const title =
|
|
329
|
+
const text2 = (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" ? "" : node.value ?? (node.children ?? []).map(text2).join("");
|
|
330
|
+
const title = text2(first).replace(/\s+/g, " ").trim();
|
|
331
331
|
const start = first.position?.start.offset;
|
|
332
332
|
const end = first.position?.end.offset;
|
|
333
333
|
return title && start !== void 0 && end !== void 0 ? { title, start, end } : void 0;
|
|
@@ -400,16 +400,16 @@ function collectEsmBindingsByPattern(source, declared) {
|
|
|
400
400
|
var FENCE_OPEN = /^(`{3,}|~{3,})/;
|
|
401
401
|
function maskInlineSpansForMath(line) {
|
|
402
402
|
const blocks = [];
|
|
403
|
-
const stash = (
|
|
404
|
-
blocks.push(
|
|
403
|
+
const stash = (text2) => {
|
|
404
|
+
blocks.push(text2);
|
|
405
405
|
return `\0${blocks.length - 1}\0`;
|
|
406
406
|
};
|
|
407
407
|
const codeMasked = line.split(/(`[^`]*`)/).map((segment, index) => index % 2 === 1 ? stash(segment) : segment).join("");
|
|
408
408
|
const tagMasked = codeMasked.replace(/<\/?[a-zA-Z][^<>]*>/g, stash);
|
|
409
|
-
const masked = [tagMasked, tagMasked].reduce((
|
|
409
|
+
const masked = [tagMasked, tagMasked].reduce((text2) => text2.replace(/(?<!\$)\{[^{}]*\}/g, stash), tagMasked);
|
|
410
410
|
return {
|
|
411
411
|
masked,
|
|
412
|
-
unmask: (
|
|
412
|
+
unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
415
|
var TEX_SIGNAL = /\\[a-zA-Z]|[\^_{]/;
|
|
@@ -562,11 +562,11 @@ function splitFrontmatterBlock(raw) {
|
|
|
562
562
|
return { front: bom + source.slice(0, end), body: source.slice(end) };
|
|
563
563
|
}
|
|
564
564
|
var MIGRATED_COMPONENT_TAG = /^Migrated[0-9a-f]{12}$/;
|
|
565
|
-
function isSelfClosingJsx(
|
|
566
|
-
return /\/>\s*$/.test(
|
|
565
|
+
function isSelfClosingJsx(text2) {
|
|
566
|
+
return /\/>\s*$/.test(text2);
|
|
567
567
|
}
|
|
568
|
-
function renameTagOccurrences(
|
|
569
|
-
let result =
|
|
568
|
+
function renameTagOccurrences(text2, name, newName) {
|
|
569
|
+
let result = text2;
|
|
570
570
|
const openIndex = result.indexOf(`<${name}`);
|
|
571
571
|
if (openIndex >= 0) result = `${result.slice(0, openIndex + 1)}${newName}${result.slice(openIndex + 1 + name.length)}`;
|
|
572
572
|
const closeIndex = result.lastIndexOf(`</${name}`);
|
|
@@ -612,9 +612,9 @@ function replaceUnknownComponents(raw, warn, options = {}) {
|
|
|
612
612
|
if (start === void 0 || end === void 0) return "";
|
|
613
613
|
const isJsx = node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
|
|
614
614
|
const tagRoot = node.name ? node.name.split(".")[0] : void 0;
|
|
615
|
-
const
|
|
615
|
+
const text2 = body.slice(start, end);
|
|
616
616
|
if (isJsx && tagRoot === "Link" && !declared.has("Link")) {
|
|
617
|
-
if (isSelfClosingJsx(
|
|
617
|
+
if (isSelfClosingJsx(text2) || !node.children?.length) return renameTagOccurrences(text2, node.name, "a");
|
|
618
618
|
const openEnd = node.children[0].position?.start.offset ?? start;
|
|
619
619
|
const closeStart = node.children.at(-1).position?.end.offset ?? end;
|
|
620
620
|
return renameTagOccurrences(body.slice(start, openEnd), node.name, "a") + renderChildrenSpan(node.children, openEnd, closeStart, body, renderNode) + renameTagOccurrences(body.slice(closeStart, end), node.name, "a");
|
|
@@ -624,7 +624,7 @@ function replaceUnknownComponents(raw, warn, options = {}) {
|
|
|
624
624
|
warned.add(node.name);
|
|
625
625
|
warn(node.name);
|
|
626
626
|
}
|
|
627
|
-
if (isSelfClosingJsx(
|
|
627
|
+
if (isSelfClosingJsx(text2)) return "";
|
|
628
628
|
if (!node.children?.length) return "<div></div>";
|
|
629
629
|
const openEnd = node.children[0].position?.start.offset ?? start;
|
|
630
630
|
const closeStart = node.children.at(-1).position?.end.offset ?? end;
|
|
@@ -635,7 +635,7 @@ ${children}
|
|
|
635
635
|
|
|
636
636
|
</div>` : `<div>${children}</div>`;
|
|
637
637
|
}
|
|
638
|
-
if (!node.children?.length) return
|
|
638
|
+
if (!node.children?.length) return text2;
|
|
639
639
|
return renderChildrenSpan(node.children, start, end, body, renderNode);
|
|
640
640
|
};
|
|
641
641
|
return front + renderNode(root);
|
|
@@ -643,8 +643,8 @@ ${children}
|
|
|
643
643
|
function maskCode(body) {
|
|
644
644
|
const source = body.replace(/\u0000/g, "");
|
|
645
645
|
const blocks = [];
|
|
646
|
-
const stash = (
|
|
647
|
-
blocks.push(
|
|
646
|
+
const stash = (text2) => {
|
|
647
|
+
blocks.push(text2);
|
|
648
648
|
return `\0${blocks.length - 1}\0`;
|
|
649
649
|
};
|
|
650
650
|
const lines = [];
|
|
@@ -671,7 +671,7 @@ function maskCode(body) {
|
|
|
671
671
|
if (fenceBuffer.length) lines.push(stash(fenceBuffer.join("\n")));
|
|
672
672
|
return {
|
|
673
673
|
masked: lines.join("\n"),
|
|
674
|
-
unmask: (
|
|
674
|
+
unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
|
|
675
675
|
};
|
|
676
676
|
}
|
|
677
677
|
function replaceOutsideCode(body, transform) {
|
|
@@ -690,7 +690,7 @@ function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePat
|
|
|
690
690
|
const route = candidates.map((candidate) => routesBySourcePath.get(candidate)).find(Boolean);
|
|
691
691
|
return route ? `/${route.replace(/^\/+/, "")}${suffix}` : target;
|
|
692
692
|
};
|
|
693
|
-
return replaceOutsideCode(body, (
|
|
693
|
+
return replaceOutsideCode(body, (text2) => text2.replace(
|
|
694
694
|
/(\]\()(<[^>\n]+>|[^\s)]+)(?=\s|\))/g,
|
|
695
695
|
(_match, opening, target) => `${opening}${target.startsWith("<") && target.endsWith(">") ? `<${rewrite(target.slice(1, -1))}>` : rewrite(target)}`
|
|
696
696
|
).replace(
|
|
@@ -2165,7 +2165,7 @@ const MintlifyComponents = {
|
|
|
2165
2165
|
}
|
|
2166
2166
|
function applyReplacements(source, replacements) {
|
|
2167
2167
|
return replacements.sort((a, b) => b.start - a.start).reduce(
|
|
2168
|
-
(
|
|
2168
|
+
(text2, edit) => text2.slice(0, edit.start) + edit.value + text2.slice(edit.end),
|
|
2169
2169
|
source
|
|
2170
2170
|
);
|
|
2171
2171
|
}
|
|
@@ -2454,14 +2454,14 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2454
2454
|
}
|
|
2455
2455
|
return;
|
|
2456
2456
|
}
|
|
2457
|
-
const
|
|
2458
|
-
const syntax = ts2.transpileModule(
|
|
2457
|
+
const text2 = content.toString("utf8");
|
|
2458
|
+
const syntax = ts2.transpileModule(text2, {
|
|
2459
2459
|
fileName: path,
|
|
2460
2460
|
reportDiagnostics: true,
|
|
2461
2461
|
compilerOptions: { jsx: ts2.JsxEmit.Preserve, target: ts2.ScriptTarget.ESNext, module: ts2.ModuleKind.ESNext }
|
|
2462
2462
|
});
|
|
2463
2463
|
if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
|
|
2464
|
-
const ast = sourceFile(
|
|
2464
|
+
const ast = sourceFile(text2, path);
|
|
2465
2465
|
if (ast.statements.some((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression) && statement.expression.text === "use server")) throw new Error("server-only component modules require manual migration");
|
|
2466
2466
|
const edits = [];
|
|
2467
2467
|
function dependency(literal, importDeclaration) {
|
|
@@ -2509,7 +2509,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
2509
2509
|
ts2.forEachChild(node, inspect);
|
|
2510
2510
|
}
|
|
2511
2511
|
inspect(ast);
|
|
2512
|
-
const replaced = applyReplacements(
|
|
2512
|
+
const replaced = applyReplacements(text2, edits);
|
|
2513
2513
|
const shebangMatch = /^#!.*\r?\n/.exec(replaced);
|
|
2514
2514
|
const prologue = "// @ts-nocheck\n'use client';\n\n";
|
|
2515
2515
|
staged.set(destination, {
|
|
@@ -3016,10 +3016,10 @@ ${inlineSource}` });
|
|
|
3016
3016
|
const start = node.position.start.offset;
|
|
3017
3017
|
const end = node.position.end.offset;
|
|
3018
3018
|
if (extracted.some((item) => start >= item.node.position.start.offset && end <= item.node.position.end.offset)) return;
|
|
3019
|
-
const
|
|
3020
|
-
const opening =
|
|
3019
|
+
const text2 = content.slice(start, end);
|
|
3020
|
+
const opening = text2.indexOf(`<${node.name}`);
|
|
3021
3021
|
if (opening >= 0) edits.push({ start: start + opening + 1, end: start + opening + 1 + node.name.length, value: replacement });
|
|
3022
|
-
const closing =
|
|
3022
|
+
const closing = text2.lastIndexOf(`</${node.name}`);
|
|
3023
3023
|
if (closing >= 0) edits.push({ start: start + closing + 2, end: start + closing + 2 + node.name.length, value: replacement });
|
|
3024
3024
|
});
|
|
3025
3025
|
walk(tree, (node) => {
|
|
@@ -5141,9 +5141,9 @@ function announcementMarkdown(message) {
|
|
|
5141
5141
|
links.set(marker, destination && label ? `[${escapeText(label)}](${destination})` : escapeText(label));
|
|
5142
5142
|
document(element).replaceWith(marker);
|
|
5143
5143
|
});
|
|
5144
|
-
let
|
|
5145
|
-
for (const [marker, link] of links)
|
|
5146
|
-
return
|
|
5144
|
+
let text2 = escapeText(document("body").text());
|
|
5145
|
+
for (const [marker, link] of links) text2 = text2.replaceAll(marker, link);
|
|
5146
|
+
return text2.trim();
|
|
5147
5147
|
}
|
|
5148
5148
|
function objectValue4(value) {
|
|
5149
5149
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -5661,6 +5661,179 @@ function projectFernNavigation(input) {
|
|
|
5661
5661
|
};
|
|
5662
5662
|
}
|
|
5663
5663
|
|
|
5664
|
+
// src/analytics.ts
|
|
5665
|
+
var GA4_ID = /^G-[A-Z0-9]{4,20}$/;
|
|
5666
|
+
var GTM_ID = /^GTM-[A-Z0-9]{4,12}$/;
|
|
5667
|
+
var POSTHOG_KEY = /^phc_[A-Za-z0-9]{20,64}$/;
|
|
5668
|
+
var HOSTNAME_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
5669
|
+
var HOSTNAME = new RegExp(`^${HOSTNAME_LABEL}(?:\\.${HOSTNAME_LABEL})*$`);
|
|
5670
|
+
var SAFE_PATH = /^\/[A-Za-z0-9._~\-/]*$/;
|
|
5671
|
+
var MAX_PLAUSIBLE_DOMAINS = 10;
|
|
5672
|
+
var SUPPORTED = ["ga4", "gtm", "posthog", "plausible"];
|
|
5673
|
+
var LEGACY_GA4_ALIAS = "googleAnalytics";
|
|
5674
|
+
function own(record, key) {
|
|
5675
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
5676
|
+
}
|
|
5677
|
+
function isRecord(value) {
|
|
5678
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5679
|
+
}
|
|
5680
|
+
function redact(value) {
|
|
5681
|
+
if (typeof value !== "string") return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
5682
|
+
return value.length === 0 ? "empty string" : `"${value.trim().slice(0, 4).replace(/[^\x20-\x7e]/g, "?")}..." (${value.length} chars)`;
|
|
5683
|
+
}
|
|
5684
|
+
function isPublicHostname(hostname) {
|
|
5685
|
+
return !/(^|\.)localhost$/.test(hostname) && !/(^|\.)\d+$/.test(hostname) && !hostname.includes(":") && !hostname.startsWith("[");
|
|
5686
|
+
}
|
|
5687
|
+
function normalizeHttpsUrl(value, allowPath) {
|
|
5688
|
+
if (!/^https:\/\//i.test(value) || /[?#\\]/.test(value)) return null;
|
|
5689
|
+
let url;
|
|
5690
|
+
try {
|
|
5691
|
+
url = new URL(value);
|
|
5692
|
+
} catch {
|
|
5693
|
+
return null;
|
|
5694
|
+
}
|
|
5695
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) return null;
|
|
5696
|
+
if (!HOSTNAME.test(url.hostname) || !isPublicHostname(url.hostname) || url.port === "0") return null;
|
|
5697
|
+
const fullPath = /^https:\/\/[^/]*(\/.*)?$/i.exec(value)?.[1] ?? "";
|
|
5698
|
+
if (fullPath.includes("//")) return null;
|
|
5699
|
+
const path = fullPath.replace(/\/+$/, "");
|
|
5700
|
+
if (path && (!allowPath || !SAFE_PATH.test(path) || path.split("/").some((s) => s === ".." || s === "."))) return null;
|
|
5701
|
+
return `${url.origin}${path}`;
|
|
5702
|
+
}
|
|
5703
|
+
function normalizeDomains(value) {
|
|
5704
|
+
const parts = value.split(",").map((part) => part.trim().toLowerCase());
|
|
5705
|
+
if (parts.length > MAX_PLAUSIBLE_DOMAINS) return null;
|
|
5706
|
+
return parts.every((part) => part.length <= 253 && HOSTNAME.test(part)) ? parts.join(",") : null;
|
|
5707
|
+
}
|
|
5708
|
+
function normalizeServer(value) {
|
|
5709
|
+
if (/^https:\/\//i.test(value)) {
|
|
5710
|
+
const origin2 = normalizeHttpsUrl(value, false);
|
|
5711
|
+
return origin2 ? origin2.replace(/^https:\/\//, "") : null;
|
|
5712
|
+
}
|
|
5713
|
+
const bare = /^([a-z0-9.-]+)(?::(\d{1,5}))?$/i.exec(value);
|
|
5714
|
+
if (!bare || bare[2] !== void 0 && (Number(bare[2]) < 1 || Number(bare[2]) > 65535)) return null;
|
|
5715
|
+
const origin = normalizeHttpsUrl(`https://${bare[1].toLowerCase()}${bare[2] === void 0 ? "" : `:${Number(bare[2])}`}`, false);
|
|
5716
|
+
return origin ? origin.replace(/^https:\/\//, "") : null;
|
|
5717
|
+
}
|
|
5718
|
+
function text(value) {
|
|
5719
|
+
return typeof value === "string" && value.trim() && value.length <= 2048 ? value.trim() : null;
|
|
5720
|
+
}
|
|
5721
|
+
function projectMintlifyIntegrations(config) {
|
|
5722
|
+
if (!isRecord(config)) return { warnings: [] };
|
|
5723
|
+
try {
|
|
5724
|
+
return project(config);
|
|
5725
|
+
} catch {
|
|
5726
|
+
return { warnings: [{ code: "unsupported-config", message: "Mintlify analytics configuration could not be read; analytics were not imported." }] };
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
function project(config) {
|
|
5730
|
+
const warnings = [];
|
|
5731
|
+
const warn = (message) => warnings.push({ code: "unsupported-config", message });
|
|
5732
|
+
const sources = [];
|
|
5733
|
+
for (const [label, key] of [["integrations", "integrations"], ["analytics", "analytics"]]) {
|
|
5734
|
+
const value = own(config, key);
|
|
5735
|
+
if (value === void 0) continue;
|
|
5736
|
+
if (isRecord(value)) sources.push({ label, block: value });
|
|
5737
|
+
else warn(`Mintlify ${label} must be an object (found ${redact(value)}); analytics from it were not imported.`);
|
|
5738
|
+
}
|
|
5739
|
+
const integrations = {};
|
|
5740
|
+
const pick = (name) => {
|
|
5741
|
+
const found = [];
|
|
5742
|
+
for (const source of sources) {
|
|
5743
|
+
let value = own(source.block, name);
|
|
5744
|
+
if (value === void 0 && name === "ga4" && source.label === "analytics") value = own(source.block, LEGACY_GA4_ALIAS);
|
|
5745
|
+
if (value !== void 0) found.push({ label: `${source.label}.${name}`, value });
|
|
5746
|
+
}
|
|
5747
|
+
if (found.length > 1) {
|
|
5748
|
+
warn(`Mintlify ${found[0].label} and ${found[1].label} are both set; using ${found[0].label} for ${name}.`);
|
|
5749
|
+
}
|
|
5750
|
+
return found;
|
|
5751
|
+
};
|
|
5752
|
+
const field = (label, block, key, required) => {
|
|
5753
|
+
const raw = own(block, key);
|
|
5754
|
+
if (raw === void 0) {
|
|
5755
|
+
if (required) warn(`Mintlify ${label}.${key} is missing; ${label} was not imported.`);
|
|
5756
|
+
return required ? null : void 0;
|
|
5757
|
+
}
|
|
5758
|
+
const value = text(raw);
|
|
5759
|
+
if (value === null) warn(`Mintlify ${label}.${key} is not a usable string (found ${redact(raw)}); ${label} was not imported.`);
|
|
5760
|
+
return value;
|
|
5761
|
+
};
|
|
5762
|
+
const objectFor = (entry) => {
|
|
5763
|
+
if (isRecord(entry.value)) return entry.value;
|
|
5764
|
+
warn(`Mintlify ${entry.label} must be an object (found ${redact(entry.value)}); provider was not imported.`);
|
|
5765
|
+
return null;
|
|
5766
|
+
};
|
|
5767
|
+
const ga4 = pick("ga4")[0];
|
|
5768
|
+
const ga4Block = ga4 ? objectFor(ga4) : null;
|
|
5769
|
+
if (ga4 && ga4Block) {
|
|
5770
|
+
const id = field(ga4.label, ga4Block, "measurementId", true);
|
|
5771
|
+
if (id && GA4_ID.test(id)) integrations.ga4 = { measurementId: id };
|
|
5772
|
+
else if (id) warn(`Mintlify ${ga4.label}.measurementId ${redact(id)} is not a GA4 ID (G-XXXXXXXXXX); Universal Analytics and lowercase IDs are not imported.`);
|
|
5773
|
+
}
|
|
5774
|
+
const gtm = pick("gtm")[0];
|
|
5775
|
+
const gtmBlock = gtm ? objectFor(gtm) : null;
|
|
5776
|
+
if (gtm && gtmBlock) {
|
|
5777
|
+
const id = field(gtm.label, gtmBlock, "tagId", true);
|
|
5778
|
+
if (id && GTM_ID.test(id)) integrations.gtm = { tagId: id };
|
|
5779
|
+
else if (id) warn(`Mintlify ${gtm.label}.tagId ${redact(id)} is not a GTM container ID (GTM-XXXXXXX); GTM was not imported.`);
|
|
5780
|
+
}
|
|
5781
|
+
const plausible = pick("plausible")[0];
|
|
5782
|
+
const plausibleBlock = plausible ? objectFor(plausible) : null;
|
|
5783
|
+
if (plausible && plausibleBlock) {
|
|
5784
|
+
const domainRaw = field(plausible.label, plausibleBlock, "domain", true);
|
|
5785
|
+
const serverRaw = field(plausible.label, plausibleBlock, "server", false);
|
|
5786
|
+
const domain = domainRaw ? normalizeDomains(domainRaw) : null;
|
|
5787
|
+
const server = serverRaw ? normalizeServer(serverRaw) : serverRaw;
|
|
5788
|
+
if (domainRaw && !domain) warn(`Mintlify ${plausible.label}.domain ${redact(domainRaw)} must be hostname(s) without a protocol or path; Plausible was not imported.`);
|
|
5789
|
+
if (serverRaw && !server) warn(`Mintlify ${plausible.label}.server ${redact(serverRaw)} must be a hostname; Plausible was not imported.`);
|
|
5790
|
+
if (domain && server !== null) integrations.plausible = { domain, ...server ? { server } : {} };
|
|
5791
|
+
}
|
|
5792
|
+
const posthog = pick("posthog")[0];
|
|
5793
|
+
const posthogBlock = posthog ? objectFor(posthog) : null;
|
|
5794
|
+
if (posthog && posthogBlock) {
|
|
5795
|
+
const key = field(posthog.label, posthogBlock, "apiKey", true);
|
|
5796
|
+
const hostRaw = field(posthog.label, posthogBlock, "apiHost", false);
|
|
5797
|
+
const host = hostRaw ? normalizeHttpsUrl(hostRaw, true) : hostRaw;
|
|
5798
|
+
const recording = own(posthogBlock, "sessionRecording");
|
|
5799
|
+
if (recording !== void 0 && typeof recording !== "boolean") warn(`Mintlify ${posthog.label}.sessionRecording must be a boolean (found ${redact(recording)}); session recording disabled.`);
|
|
5800
|
+
if (key && !POSTHOG_KEY.test(key)) warn(`Mintlify ${posthog.label}.apiKey ${redact(key)} is not a PostHog project key (phc_...); PostHog was not imported.`);
|
|
5801
|
+
if (hostRaw && !host) warn(`Mintlify ${posthog.label}.apiHost ${redact(hostRaw)} must be an https URL without credentials, query, or fragment; PostHog was not imported.`);
|
|
5802
|
+
if (key && POSTHOG_KEY.test(key) && host !== null) {
|
|
5803
|
+
integrations.posthog = {
|
|
5804
|
+
apiKey: key,
|
|
5805
|
+
...host ? { apiHost: host } : {},
|
|
5806
|
+
// Fail closed: a present non-boolean value turns recording off.
|
|
5807
|
+
...recording !== void 0 ? { sessionRecording: recording === true } : {}
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
const unsupported = /* @__PURE__ */ new Set();
|
|
5812
|
+
for (const source of sources) {
|
|
5813
|
+
for (const key of Object.keys(source.block)) {
|
|
5814
|
+
if (SUPPORTED.includes(key)) continue;
|
|
5815
|
+
if (key === LEGACY_GA4_ALIAS && source.label === "analytics") continue;
|
|
5816
|
+
unsupported.add(/^[A-Za-z0-9_-]{1,40}$/.test(key) ? key : "(invalid name)");
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
if (unsupported.size > 0) {
|
|
5820
|
+
const names = [...unsupported].slice(0, 30);
|
|
5821
|
+
warn(`Mintlify analytics/integration providers Thally cannot render were not imported: ${names.join(", ")}${unsupported.size > names.length ? ", ..." : ""}. Supported: ga4, gtm, posthog, plausible.`);
|
|
5822
|
+
}
|
|
5823
|
+
return { ...Object.keys(integrations).length > 0 ? { integrations } : {}, warnings };
|
|
5824
|
+
}
|
|
5825
|
+
function validateIntegrations(integrations) {
|
|
5826
|
+
if (integrations === void 0) return [];
|
|
5827
|
+
const result = projectMintlifyIntegrations({ integrations });
|
|
5828
|
+
const messages = result.warnings.map(
|
|
5829
|
+
(warning) => warning.message.replace(/^Mintlify analytics\/integration providers Thally cannot render were not imported: /, "docs.json integrations: Thally does not support these providers, so they will not be rendered: ").replace(/^Mintlify /, "docs.json ").replace(/\b(?:was|were|are) not imported\b/g, "will not be rendered")
|
|
5830
|
+
);
|
|
5831
|
+
if (result.integrations?.ga4 && result.integrations.gtm) {
|
|
5832
|
+
messages.push("docs.json integrations enable both ga4 and gtm; a GA4 tag inside the GTM container would double-count page views.");
|
|
5833
|
+
}
|
|
5834
|
+
return messages;
|
|
5835
|
+
}
|
|
5836
|
+
|
|
5664
5837
|
// src/repository.ts
|
|
5665
5838
|
var MAX_SOURCE_FILES = 5e3;
|
|
5666
5839
|
var MAX_PAGE_BYTES = 2e6;
|
|
@@ -6477,8 +6650,8 @@ var PATH_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\`*!?#&]+/;
|
|
|
6477
6650
|
function publicAssetKey(repoRelative) {
|
|
6478
6651
|
return repoRelative.split("/", 1)[0].toLowerCase() === "public" ? repoRelative.slice("public/".length) : repoRelative;
|
|
6479
6652
|
}
|
|
6480
|
-
function addPathReferences(
|
|
6481
|
-
for (const word of
|
|
6653
|
+
function addPathReferences(text2, baseDir, exact, loose) {
|
|
6654
|
+
for (const word of text2.split(PATH_SEPARATORS)) {
|
|
6482
6655
|
let token = word.replace(/\.+$/, "");
|
|
6483
6656
|
if (!/\.[a-z0-9]{2,5}$/i.test(token) || token.startsWith("//")) continue;
|
|
6484
6657
|
if (token.includes("%")) {
|
|
@@ -6520,8 +6693,8 @@ function classifyApiSpec(path) {
|
|
|
6520
6693
|
} catch {
|
|
6521
6694
|
return "unknown";
|
|
6522
6695
|
}
|
|
6523
|
-
const
|
|
6524
|
-
const topLevel =
|
|
6696
|
+
const text2 = buffer.toString("utf8", 0, bytesRead);
|
|
6697
|
+
const topLevel = text2.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text2.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
|
|
6525
6698
|
if (!topLevel) return "unknown";
|
|
6526
6699
|
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
6527
6700
|
}
|
|
@@ -7009,12 +7182,12 @@ function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized)
|
|
|
7009
7182
|
return target;
|
|
7010
7183
|
}
|
|
7011
7184
|
};
|
|
7012
|
-
return replaceOutsideCode(body, (
|
|
7185
|
+
return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
|
|
7013
7186
|
}
|
|
7014
7187
|
function stripFernBasePathFromLinks(body, basePath) {
|
|
7015
7188
|
if (!basePath) return body;
|
|
7016
7189
|
const escaped = basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7017
|
-
return replaceOutsideCode(body, (
|
|
7190
|
+
return replaceOutsideCode(body, (text2) => text2.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
|
|
7018
7191
|
}
|
|
7019
7192
|
function rewriteMintlifyMountedLinks(body, pageIds) {
|
|
7020
7193
|
const rewrite = (target) => {
|
|
@@ -7031,7 +7204,7 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
|
|
|
7031
7204
|
}
|
|
7032
7205
|
return target;
|
|
7033
7206
|
};
|
|
7034
|
-
return replaceOutsideCode(body, (
|
|
7207
|
+
return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
|
|
7035
7208
|
}
|
|
7036
7209
|
function preserveLinkedAnchors(pages) {
|
|
7037
7210
|
const pageById = new Map(pages.map((page) => [page.id, page]));
|
|
@@ -7055,10 +7228,10 @@ function preserveLinkedAnchors(pages) {
|
|
|
7055
7228
|
if (id !== currentId) crossPageRequests.add(`${id}#${fragment}`);
|
|
7056
7229
|
};
|
|
7057
7230
|
for (const page of pages) {
|
|
7058
|
-
replaceOutsideCode(page.body, (
|
|
7059
|
-
for (const match of
|
|
7060
|
-
for (const match of
|
|
7061
|
-
return
|
|
7231
|
+
replaceOutsideCode(page.body, (text2) => {
|
|
7232
|
+
for (const match of text2.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
|
|
7233
|
+
for (const match of text2.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
|
|
7234
|
+
return text2;
|
|
7062
7235
|
});
|
|
7063
7236
|
}
|
|
7064
7237
|
const comparable = (value) => value.replace(/&(?:lt|gt|amp|quot);/g, " ").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
@@ -7082,9 +7255,9 @@ function preserveLinkedAnchors(pages) {
|
|
|
7082
7255
|
return match ? { index, text: match[1] } : null;
|
|
7083
7256
|
}).filter((entry) => entry !== null);
|
|
7084
7257
|
const existing = /* @__PURE__ */ new Set();
|
|
7085
|
-
replaceOutsideCode(page.body, (
|
|
7086
|
-
for (const match of
|
|
7087
|
-
return
|
|
7258
|
+
replaceOutsideCode(page.body, (text2) => {
|
|
7259
|
+
for (const match of text2.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
|
|
7260
|
+
return text2;
|
|
7088
7261
|
});
|
|
7089
7262
|
const candidateIndexes = /* @__PURE__ */ new Map();
|
|
7090
7263
|
for (const heading of headings) {
|
|
@@ -7246,7 +7419,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
|
7246
7419
|
}
|
|
7247
7420
|
}
|
|
7248
7421
|
function rewriteFernIdRenameLinks(body, renames) {
|
|
7249
|
-
return replaceOutsideCode(body, (
|
|
7422
|
+
return replaceOutsideCode(body, (text2) => {
|
|
7250
7423
|
const rewriteTarget = (target) => {
|
|
7251
7424
|
const suffixIndex = target.search(/[?#]/);
|
|
7252
7425
|
const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "");
|
|
@@ -7254,7 +7427,7 @@ function rewriteFernIdRenameLinks(body, renames) {
|
|
|
7254
7427
|
const renamed = renames.get(path);
|
|
7255
7428
|
return renamed ? `/${renamed}${suffix}` : target;
|
|
7256
7429
|
};
|
|
7257
|
-
return
|
|
7430
|
+
return text2.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
|
|
7258
7431
|
});
|
|
7259
7432
|
}
|
|
7260
7433
|
function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced) {
|
|
@@ -8778,6 +8951,9 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
8778
8951
|
docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
|
|
8779
8952
|
}
|
|
8780
8953
|
if (mintlifyConfig) {
|
|
8954
|
+
const analytics = projectMintlifyIntegrations(mintlifyConfig);
|
|
8955
|
+
warnings.push(...analytics.warnings);
|
|
8956
|
+
if (analytics.integrations) docsConfig = { ...docsConfig, integrations: analytics.integrations };
|
|
8781
8957
|
const appearance = mintlifyAppearance(mintlifyConfig, warnings);
|
|
8782
8958
|
if (Object.keys(appearance).length > 0) {
|
|
8783
8959
|
docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, ...appearance } };
|
|
@@ -8896,6 +9072,9 @@ function renderPage(bundle, page) {
|
|
|
8896
9072
|
""
|
|
8897
9073
|
].filter((line) => line !== null).join("\n");
|
|
8898
9074
|
}
|
|
9075
|
+
function isIntegrationsObject(value) {
|
|
9076
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9077
|
+
}
|
|
8899
9078
|
function mergeMigrationConfig(existing, incoming) {
|
|
8900
9079
|
const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
|
|
8901
9080
|
const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
|
|
@@ -8966,7 +9145,15 @@ function mergeMigrationConfig(existing, incoming) {
|
|
|
8966
9145
|
// Collection presentation belongs to the imported information
|
|
8967
9146
|
// architecture; starter defaults must not coerce source dropdowns to tabs.
|
|
8968
9147
|
navigation: incoming.navigation ?? existing.navigation,
|
|
8969
|
-
...i18n ? { i18n } : {}
|
|
9148
|
+
...i18n ? { i18n } : {},
|
|
9149
|
+
// Existing providers win per provider; imported ones fill the gaps.
|
|
9150
|
+
// A malformed (string/array) existing value must not spread into index keys.
|
|
9151
|
+
...isIntegrationsObject(existing.integrations) || isIntegrationsObject(incoming.integrations) ? {
|
|
9152
|
+
integrations: {
|
|
9153
|
+
...isIntegrationsObject(incoming.integrations) ? incoming.integrations : {},
|
|
9154
|
+
...isIntegrationsObject(existing.integrations) ? existing.integrations : {}
|
|
9155
|
+
}
|
|
9156
|
+
} : {}
|
|
8970
9157
|
};
|
|
8971
9158
|
}
|
|
8972
9159
|
function renderMigrationFiles(bundle, options = {}) {
|
|
@@ -9957,7 +10144,7 @@ ${fence}
|
|
|
9957
10144
|
}
|
|
9958
10145
|
});
|
|
9959
10146
|
const escape = turndown.escape.bind(turndown);
|
|
9960
|
-
turndown.escape = (
|
|
10147
|
+
turndown.escape = (text2) => escape(text2).replace(/[<{]/g, (match) => `\\${match}`);
|
|
9961
10148
|
const body = turndown.turndown(root.html() ?? "").trim();
|
|
9962
10149
|
if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
|
|
9963
10150
|
return {
|
|
@@ -10510,6 +10697,11 @@ async function migrateUrl(options) {
|
|
|
10510
10697
|
topLevelNavigation
|
|
10511
10698
|
});
|
|
10512
10699
|
if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
|
|
10700
|
+
if (sourceMintlifyConfig) {
|
|
10701
|
+
const analytics = projectMintlifyIntegrations(sourceMintlifyConfig);
|
|
10702
|
+
warnings.push(...analytics.warnings);
|
|
10703
|
+
if (analytics.integrations) docsConfig.integrations = analytics.integrations;
|
|
10704
|
+
}
|
|
10513
10705
|
if (docusaurusRedirects.length > 0) {
|
|
10514
10706
|
docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
|
|
10515
10707
|
`${redirect.source}:${redirect.destination}`,
|
|
@@ -10658,8 +10850,8 @@ async function downloadSpec(value, fetcher) {
|
|
|
10658
10850
|
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
10659
10851
|
}
|
|
10660
10852
|
function parseOpenApi(content, extension) {
|
|
10661
|
-
const
|
|
10662
|
-
const parsed = extension === "yaml" || !
|
|
10853
|
+
const text2 = content.toString("utf8");
|
|
10854
|
+
const parsed = extension === "yaml" || !text2.trimStart().startsWith("{") ? parseYaml6(text2) : JSON.parse(text2);
|
|
10663
10855
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
10664
10856
|
const document = parsed;
|
|
10665
10857
|
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
@@ -10769,11 +10961,13 @@ export {
|
|
|
10769
10961
|
parseGitHubRepositoryUrl,
|
|
10770
10962
|
parseMarkdownPage,
|
|
10771
10963
|
projectFernNavigation,
|
|
10964
|
+
projectMintlifyIntegrations,
|
|
10772
10965
|
projectMintlifyNavigation,
|
|
10773
10966
|
pruneMissingNavigationPages,
|
|
10774
10967
|
readFernConfig,
|
|
10775
10968
|
readMintlifyConfig,
|
|
10776
10969
|
renderMigrationFiles,
|
|
10970
|
+
validateIntegrations,
|
|
10777
10971
|
validateMigrationUrl
|
|
10778
10972
|
};
|
|
10779
10973
|
//# sourceMappingURL=index.js.map
|