@thallylabs/migrate 0.2.23 → 0.2.24
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/dist/index.d.ts +13 -7
- package/dist/index.js +167 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -255,6 +255,18 @@ interface MigrationWarning {
|
|
|
255
255
|
message: string;
|
|
256
256
|
source?: string;
|
|
257
257
|
}
|
|
258
|
+
/** A repository-configured remote OpenAPI spec, downloaded after the bundle is built. */
|
|
259
|
+
interface RemoteApiSpec {
|
|
260
|
+
url: string;
|
|
261
|
+
tabLabel?: string;
|
|
262
|
+
parentTab?: string;
|
|
263
|
+
icon?: string;
|
|
264
|
+
hidden?: boolean;
|
|
265
|
+
/** Operation keys (`get /x`) only access-restricted pages document; marked `x-excluded` after download. */
|
|
266
|
+
withheldOperations?: Array<string>;
|
|
267
|
+
/** Operation keys published pages or docs.json also name; keeps a shared `$ref` path item public. */
|
|
268
|
+
keptOperations?: Array<string>;
|
|
269
|
+
}
|
|
258
270
|
interface MigrationBundle {
|
|
259
271
|
sourceUrl: string;
|
|
260
272
|
sourceKind: 'repository' | 'url';
|
|
@@ -262,13 +274,7 @@ interface MigrationBundle {
|
|
|
262
274
|
pages: Array<MigrationPage>;
|
|
263
275
|
assets: Array<MigrationAsset>;
|
|
264
276
|
/** Repository-configured remote specs awaiting a bounded network fetch by the host. */
|
|
265
|
-
remoteApiSpecs?: Array<
|
|
266
|
-
url: string;
|
|
267
|
-
tabLabel?: string;
|
|
268
|
-
parentTab?: string;
|
|
269
|
-
icon?: string;
|
|
270
|
-
hidden?: boolean;
|
|
271
|
-
}>;
|
|
277
|
+
remoteApiSpecs?: Array<RemoteApiSpec>;
|
|
272
278
|
/** Customer-owned component source and registry; paths are repository-relative. */
|
|
273
279
|
componentFiles?: Array<RenderedMigrationFile>;
|
|
274
280
|
/** Access-restricted source pages, written outside every published path. */
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "fs";
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
import { basename as basename3, dirname as dirname4, extname as extname6, isAbsolute as isAbsolute2, posix as posix3, relative as relative5, resolve as resolvePath, sep as sep2 } from "path";
|
|
19
|
-
import { parse as
|
|
19
|
+
import { parse as parseYaml5 } from "yaml";
|
|
20
20
|
import * as ts4 from "typescript";
|
|
21
21
|
|
|
22
22
|
// src/components.ts
|
|
@@ -321,7 +321,7 @@ function playgroundDisplay(value, where, warn) {
|
|
|
321
321
|
const mode = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
322
322
|
if (mode === "interactive" || mode === "simple" || mode === "none") return mode;
|
|
323
323
|
if (mode === "auth") {
|
|
324
|
-
warn?.(`${where} "auth"
|
|
324
|
+
warn?.(`${where} "auth" requires reader sign-in, which Thally does not support. Migrated as "simple" (no Try it). Set it to "interactive" to allow everyone.`);
|
|
325
325
|
return "simple";
|
|
326
326
|
}
|
|
327
327
|
warn?.(`${where} ${JSON.stringify(value)} is not one of interactive, simple, none, auth and was dropped.`);
|
|
@@ -451,7 +451,7 @@ function normalizeExplicitHeadingIds(raw, warn, options = {}) {
|
|
|
451
451
|
if (usable && options.headingMarkers) return `${heading} ${headingIdMarker(id)}`;
|
|
452
452
|
if (usable) return `<a id="${id}"></a>
|
|
453
453
|
${heading}`;
|
|
454
|
-
warn?.(`Heading anchor {#${id}} is not a valid HTML id and was removed from "${heading.replace(/^\s*#+\s*/, "")}".`);
|
|
454
|
+
warn?.(`Heading anchor {#${id}} ${options.headingMarkers ? 'contains whitespace or "*/"' : "is not a valid HTML id"} and was removed from "${heading.replace(/^\s*#+\s*/, "")}".`);
|
|
455
455
|
return heading;
|
|
456
456
|
}
|
|
457
457
|
}
|
|
@@ -485,7 +485,7 @@ function typesetQuotes(text2) {
|
|
|
485
485
|
}).join("");
|
|
486
486
|
}
|
|
487
487
|
function plainHeadingText(source) {
|
|
488
|
-
return source.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/`([^`]*)`/g, "$1").replace(/(\*\*|__|~~|\*)/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1");
|
|
488
|
+
return source.replace(/\{\/\*[\s\S]*?\*\/\}/g, " ").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/`([^`]*)`/g, "$1").replace(/(\*\*|__|~~|\*)/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1");
|
|
489
489
|
}
|
|
490
490
|
function markMintlifyHeadings(body) {
|
|
491
491
|
let fence;
|
|
@@ -4040,19 +4040,28 @@ function projectedTheme(value) {
|
|
|
4040
4040
|
if (["almond", "palm"].includes(String(value))) return "minimal";
|
|
4041
4041
|
return typeof value === "string" ? "default" : void 0;
|
|
4042
4042
|
}
|
|
4043
|
-
function projectedColors(value) {
|
|
4043
|
+
function projectedColors(value, warnings) {
|
|
4044
4044
|
const colors = objectValue2(value);
|
|
4045
|
-
if (!colors)
|
|
4046
|
-
|
|
4045
|
+
if (!colors) {
|
|
4046
|
+
if (value !== void 0) warnings.push({ code: "unsupported-config", message: "colors is not an object with a primary hex colour and was dropped.", source: "docs.json" });
|
|
4047
|
+
return void 0;
|
|
4048
|
+
}
|
|
4049
|
+
const hex = (entry, key) => {
|
|
4047
4050
|
const match = typeof entry === "string" ? /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(entry.trim()) : null;
|
|
4048
|
-
if (!match)
|
|
4051
|
+
if (!match) {
|
|
4052
|
+
if (entry !== void 0) warnings.push({ code: "unsupported-config", message: `colors.${key} ${JSON.stringify(entry)} is not a 3- or 6-digit hex colour (for example #16a34a) and was dropped.`, source: "docs.json" });
|
|
4053
|
+
return void 0;
|
|
4054
|
+
}
|
|
4049
4055
|
const digits = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join("") : match[1];
|
|
4050
4056
|
return `#${digits.toLowerCase()}`;
|
|
4051
4057
|
};
|
|
4052
|
-
const primary = hex(colors.primary);
|
|
4053
|
-
const light = hex(colors.light) ?? primary;
|
|
4054
|
-
const dark = hex(colors.dark) ?? primary;
|
|
4055
|
-
if (!primary)
|
|
4058
|
+
const primary = hex(colors.primary, "primary");
|
|
4059
|
+
const light = hex(colors.light, "light") ?? primary;
|
|
4060
|
+
const dark = hex(colors.dark, "dark") ?? primary;
|
|
4061
|
+
if (!primary) {
|
|
4062
|
+
if (colors.primary === void 0) warnings.push({ code: "unsupported-config", message: "colors has no primary hex colour, so none of its colours were migrated.", source: "docs.json" });
|
|
4063
|
+
return void 0;
|
|
4064
|
+
}
|
|
4056
4065
|
return { light: { accent: primary, primary: dark ?? primary }, dark: { accent: light ?? primary, primary: light ?? primary } };
|
|
4057
4066
|
}
|
|
4058
4067
|
function projectedNavbar(value) {
|
|
@@ -4157,8 +4166,16 @@ function projectedCompatibleConfig(config, warnings) {
|
|
|
4157
4166
|
if (droppedContextual.length > 0) {
|
|
4158
4167
|
warnings.push({ code: "unsupported-config", message: `contextual.options entries not supported by the page menu were dropped: ${droppedContextual.map((option) => typeof option === "string" ? option : "custom entry").join(", ")}.`, source: "docs.json" });
|
|
4159
4168
|
}
|
|
4160
|
-
const
|
|
4169
|
+
const rawMetatags = Object.entries(objectValue2(seo?.metatags) ?? {});
|
|
4170
|
+
const httpEquivNames = /* @__PURE__ */ new Set(["refresh", "set-cookie", "location", "content-security-policy", "content-security-policy-report-only", "content-type", "default-style", "x-ua-compatible"]);
|
|
4171
|
+
const validMetatag = (entry) => /^[\w:.-]+$/.test(entry[0]) && !entry[0].toLowerCase().startsWith("http-equiv") && !httpEquivNames.has(entry[0].toLowerCase()) && typeof entry[1] === "string" && entry[1].length <= 1e3 && ![...entry[1]].some((character) => character.charCodeAt(0) < 32);
|
|
4172
|
+
const metatagEntries = rawMetatags.filter(validMetatag);
|
|
4173
|
+
const droppedMetatags = rawMetatags.filter((entry) => !validMetatag(entry)).map(([key]) => key);
|
|
4174
|
+
if (droppedMetatags.length > 0) {
|
|
4175
|
+
warnings.push({ code: "unsupported-config", message: `seo.metatags entries were dropped because the name is not a plain meta name, is an http-equiv directive, or the value is not a string of at most 1000 characters: ${droppedMetatags.join(", ")}.`, source: "docs.json" });
|
|
4176
|
+
}
|
|
4161
4177
|
const projectedMetatags = metatagEntries.length > 0 ? Object.fromEntries(metatagEntries) : void 0;
|
|
4178
|
+
const projectedColorsValue = projectedColors(config.colors, warnings);
|
|
4162
4179
|
const iconLibrary = objectValue2(config.icons)?.library;
|
|
4163
4180
|
const projectedIconLibrary = iconLibrary === void 0 ? "fontawesome" : ["lucide", "fontawesome", "tabler"].includes(String(iconLibrary)) ? String(iconLibrary) : void 0;
|
|
4164
4181
|
const docsNavbar = projectedNavbar(config.navbar);
|
|
@@ -4189,7 +4206,7 @@ function projectedCompatibleConfig(config, warnings) {
|
|
|
4189
4206
|
return {
|
|
4190
4207
|
...projectedTheme(config.theme) ? { theme: projectedTheme(config.theme) } : {},
|
|
4191
4208
|
...projectedContextual.length > 0 ? { contextual: { options: projectedContextual } } : {},
|
|
4192
|
-
...
|
|
4209
|
+
...projectedColorsValue ? { colors: projectedColorsValue } : {},
|
|
4193
4210
|
...projectedIconLibrary ? { icons: { library: projectedIconLibrary } } : {},
|
|
4194
4211
|
...bannerContent ? {
|
|
4195
4212
|
banner: {
|
|
@@ -6075,6 +6092,68 @@ function withSpecRef(ref, specRef) {
|
|
|
6075
6092
|
return `${specRef} ${ref.operation}`;
|
|
6076
6093
|
}
|
|
6077
6094
|
|
|
6095
|
+
// src/spec-exclude.ts
|
|
6096
|
+
import { parse as parseYaml4, stringify as stringifyYaml } from "yaml";
|
|
6097
|
+
function specRefMatches(ref, specPath, specFilename, isDefault) {
|
|
6098
|
+
const prefix = /^(?:(\S+)\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|WEBHOOK)\s/i.exec(`${ref} `)?.[1];
|
|
6099
|
+
if (!prefix) return isDefault;
|
|
6100
|
+
const wanted = prefix.replace(/^\/+/, "").toLowerCase();
|
|
6101
|
+
const lowerPath = specPath.toLowerCase();
|
|
6102
|
+
return lowerPath === wanted || lowerPath.endsWith(`/${wanted}`) || specFilename.toLowerCase() === wanted;
|
|
6103
|
+
}
|
|
6104
|
+
function operationKey(operation) {
|
|
6105
|
+
const [first, ...rest] = operation.trim().split(/\s+/);
|
|
6106
|
+
return `${first.toLowerCase()} ${rest.join(" ")}`;
|
|
6107
|
+
}
|
|
6108
|
+
function keysFor(spec, refs) {
|
|
6109
|
+
return [...new Set(
|
|
6110
|
+
refs.filter((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true)).map((ref) => splitOpenApiRef(ref)?.operation).filter((op) => !!op).map(operationKey)
|
|
6111
|
+
)];
|
|
6112
|
+
}
|
|
6113
|
+
function withheldOperationKeys(spec, withheldRefs, keptRefs) {
|
|
6114
|
+
const kept = keysFor(spec, keptRefs);
|
|
6115
|
+
return { withheld: keysFor(spec, withheldRefs).filter((key) => !kept.includes(key)), kept };
|
|
6116
|
+
}
|
|
6117
|
+
function markExcluded(content, operationKeys, keptKeys = []) {
|
|
6118
|
+
const untouched = { content, excluded: [] };
|
|
6119
|
+
if (operationKeys.length === 0) return untouched;
|
|
6120
|
+
const text2 = content.toString("utf8");
|
|
6121
|
+
let doc;
|
|
6122
|
+
try {
|
|
6123
|
+
doc = parseYaml4(text2);
|
|
6124
|
+
} catch {
|
|
6125
|
+
return untouched;
|
|
6126
|
+
}
|
|
6127
|
+
if (!doc || typeof doc !== "object") return untouched;
|
|
6128
|
+
const excluded = [];
|
|
6129
|
+
for (const key of operationKeys) {
|
|
6130
|
+
const [method, ...rest] = key.split(" ");
|
|
6131
|
+
const name = rest.join(" ");
|
|
6132
|
+
if (method === "webhook") {
|
|
6133
|
+
const entry2 = doc.webhooks?.[name] ?? doc["x-webhooks"]?.[name];
|
|
6134
|
+
if (entry2 && typeof entry2 === "object") {
|
|
6135
|
+
entry2["x-excluded"] = true;
|
|
6136
|
+
excluded.push(`WEBHOOK ${name}`);
|
|
6137
|
+
}
|
|
6138
|
+
continue;
|
|
6139
|
+
}
|
|
6140
|
+
const entry = doc.paths?.[name];
|
|
6141
|
+
if (!entry || typeof entry !== "object") continue;
|
|
6142
|
+
const target = typeof entry.$ref === "string" ? keptKeys.some((k) => k.slice(k.indexOf(" ") + 1) === name) ? void 0 : entry : entry[method];
|
|
6143
|
+
if (target && typeof target === "object") {
|
|
6144
|
+
target["x-excluded"] = true;
|
|
6145
|
+
excluded.push(`${method.toUpperCase()} ${name}`);
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
if (excluded.length === 0) return untouched;
|
|
6149
|
+
const out = text2.trimStart().startsWith("{") ? `${JSON.stringify(doc, null, 2)}
|
|
6150
|
+
` : stringifyYaml(doc);
|
|
6151
|
+
return { content: Buffer.from(out), excluded };
|
|
6152
|
+
}
|
|
6153
|
+
function sharedSpecMessage(path, excluded = []) {
|
|
6154
|
+
return excluded.length ? `OpenAPI spec ${path} is shared with access-restricted pages and is published, but ${excluded.length} operation(s) documented only on those pages were withheld from it (${excluded.join(", ")}). Review the rest of the spec before publishing.` : `OpenAPI spec ${path} is shared with access-restricted pages and is published; it may describe restricted endpoints. Review it before publishing.`;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6078
6157
|
// src/analytics.ts
|
|
6079
6158
|
var GA4_ID = /^G-[A-Z0-9]{4,20}$/;
|
|
6080
6159
|
var GTM_ID = /^GTM-[A-Z0-9]{4,12}$/;
|
|
@@ -7056,12 +7135,23 @@ function normalizedReferenceKey(value) {
|
|
|
7056
7135
|
function exactReferenceKey(value) {
|
|
7057
7136
|
return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
|
|
7058
7137
|
}
|
|
7059
|
-
function
|
|
7060
|
-
const
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7138
|
+
function navigationOperationRefs(config) {
|
|
7139
|
+
const refs = [];
|
|
7140
|
+
const visit = (node) => {
|
|
7141
|
+
if (typeof node === "string") {
|
|
7142
|
+
if (splitOpenApiRef(node)) refs.push(node);
|
|
7143
|
+
} else if (Array.isArray(node)) node.forEach(visit);
|
|
7144
|
+
else if (node && typeof node === "object") {
|
|
7145
|
+
if (navigationGateReason(node)) return;
|
|
7146
|
+
Object.values(node).forEach(visit);
|
|
7147
|
+
}
|
|
7148
|
+
};
|
|
7149
|
+
visit(config?.navigation);
|
|
7150
|
+
return refs;
|
|
7151
|
+
}
|
|
7152
|
+
function excludeWithheldOperations(spec, withheldRefs, keptRefs) {
|
|
7153
|
+
const { withheld, kept } = withheldOperationKeys(spec, withheldRefs, keptRefs);
|
|
7154
|
+
return markExcluded(spec.content, withheld, kept);
|
|
7065
7155
|
}
|
|
7066
7156
|
var TOKEN_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\/`*!?#&]+/;
|
|
7067
7157
|
var PATH_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\`*!?#&]+/;
|
|
@@ -7221,7 +7311,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
7221
7311
|
}
|
|
7222
7312
|
return specs;
|
|
7223
7313
|
}
|
|
7224
|
-
function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings) {
|
|
7314
|
+
function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings, explainMissing = () => "no such file was found") {
|
|
7225
7315
|
for (const page of pages) {
|
|
7226
7316
|
const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
|
|
7227
7317
|
if (!ref?.specRef) continue;
|
|
@@ -7238,7 +7328,7 @@ function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings) {
|
|
|
7238
7328
|
if (match) {
|
|
7239
7329
|
page.openapi = withSpecRef(ref, specAssetPath(match.filename));
|
|
7240
7330
|
} else {
|
|
7241
|
-
warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the OpenAPI spec "${ref.specRef}", which
|
|
7331
|
+
warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the OpenAPI spec "${ref.specRef}", which was not migrated (${explainMissing(key)}); the page will not render an endpoint.`, source: page.source });
|
|
7242
7332
|
}
|
|
7243
7333
|
}
|
|
7244
7334
|
}
|
|
@@ -7263,7 +7353,7 @@ function slugifyApiSpecId(value) {
|
|
|
7263
7353
|
function parseOpenApiOperationsForLinking(content) {
|
|
7264
7354
|
let document;
|
|
7265
7355
|
try {
|
|
7266
|
-
document =
|
|
7356
|
+
document = parseYaml5(content.toString("utf8"));
|
|
7267
7357
|
} catch {
|
|
7268
7358
|
return [];
|
|
7269
7359
|
}
|
|
@@ -7395,7 +7485,7 @@ function fernGeneratorsUnsupportedSpecPaths(config) {
|
|
|
7395
7485
|
}
|
|
7396
7486
|
function readFernGeneratorsConfig(path) {
|
|
7397
7487
|
if (!existsSync5(path) || !lstatSync5(path).isFile() || lstatSync5(path).size > MAX_FERN_GENERATORS_BYTES) return null;
|
|
7398
|
-
const parsed =
|
|
7488
|
+
const parsed = parseYaml5(readFileSync5(path, "utf8"));
|
|
7399
7489
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
7400
7490
|
}
|
|
7401
7491
|
function fernOpenApiCandidateDirs(fernRoot, apiName, apiNameExplicit) {
|
|
@@ -7842,7 +7932,7 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
|
7842
7932
|
}
|
|
7843
7933
|
return aliases;
|
|
7844
7934
|
}
|
|
7845
|
-
function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
7935
|
+
function repositoryAssetHref(value, currentFile, siteRoot, onReferenced, onRootFallback) {
|
|
7846
7936
|
const isBracketed = value.startsWith("<") && value.endsWith(">");
|
|
7847
7937
|
const raw = isBracketed ? value.slice(1, -1) : value;
|
|
7848
7938
|
if (!raw || raw.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(raw)) return null;
|
|
@@ -7857,10 +7947,14 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
|
7857
7947
|
}
|
|
7858
7948
|
if (!ASSET_EXTENSIONS.has(extname6(decodedPath).toLowerCase())) return null;
|
|
7859
7949
|
try {
|
|
7950
|
+
let usedRootFallback = false;
|
|
7860
7951
|
let candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithinRoot(dirname4(currentFile), decodedPath, siteRoot);
|
|
7861
7952
|
if (!decodedPath.startsWith("/") && !existsSync5(candidate)) {
|
|
7862
7953
|
const fromRoot = resolveWithin(siteRoot, decodedPath.replace(/^(?:\.\/)+/, ""));
|
|
7863
|
-
if (existsSync5(fromRoot))
|
|
7954
|
+
if (existsSync5(fromRoot)) {
|
|
7955
|
+
candidate = fromRoot;
|
|
7956
|
+
usedRootFallback = true;
|
|
7957
|
+
}
|
|
7864
7958
|
}
|
|
7865
7959
|
const realRoot = realpathSync2(siteRoot);
|
|
7866
7960
|
const realCandidate = realpathSync2(candidate);
|
|
@@ -7874,6 +7968,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
|
|
|
7874
7968
|
const onDiskRelative = relative5(realRoot, realpathSync2.native(candidate)).replace(/\\/g, "/");
|
|
7875
7969
|
const onDisk = onDiskRelative.toLowerCase() === siteRelative.toLowerCase() ? normalizeAssetPath(onDiskRelative) : null;
|
|
7876
7970
|
onReferenced?.(normalized, onDisk ?? void 0);
|
|
7971
|
+
if (usedRootFallback) onRootFallback?.(decodedPath, normalized);
|
|
7877
7972
|
const rewritten = `/${normalized}${suffix}`;
|
|
7878
7973
|
return isBracketed ? `<${rewritten}>` : rewritten;
|
|
7879
7974
|
} catch {
|
|
@@ -7892,12 +7987,12 @@ function rewriteFernIdRenameLinks(body, renames) {
|
|
|
7892
7987
|
return text2.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
|
|
7893
7988
|
});
|
|
7894
7989
|
}
|
|
7895
|
-
function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced) {
|
|
7990
|
+
function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced, onRootFallback) {
|
|
7896
7991
|
return body.replace(/(!?\[[^\]]*\]\()(<[^>]+>|[^)\s]+)([^)]*\))/g, (original, opening, destination, closing) => {
|
|
7897
|
-
const rewritten = repositoryAssetHref(destination, currentFile, siteRoot, onReferenced);
|
|
7992
|
+
const rewritten = repositoryAssetHref(destination, currentFile, siteRoot, onReferenced, onRootFallback);
|
|
7898
7993
|
return rewritten ? `${opening}${rewritten}${closing}` : original;
|
|
7899
7994
|
}).replace(/\b(src|img|image|href)=(['"])([^'"]+)\2/g, (original, property, quote, destination) => {
|
|
7900
|
-
const rewritten = repositoryAssetHref(destination, currentFile, siteRoot, onReferenced);
|
|
7995
|
+
const rewritten = repositoryAssetHref(destination, currentFile, siteRoot, onReferenced, onRootFallback);
|
|
7901
7996
|
return rewritten ? `${property}=${quote}${rewritten}${quote}` : original;
|
|
7902
7997
|
});
|
|
7903
7998
|
}
|
|
@@ -8706,6 +8801,8 @@ function migrateRepository(options) {
|
|
|
8706
8801
|
if (platform === "mintlify" && mintlifyProjectRoot) {
|
|
8707
8802
|
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath, onDiskSpelling) => {
|
|
8708
8803
|
pageAssetReferences.push(publicAssetKey(onDiskSpelling ?? assetPath));
|
|
8804
|
+
}, (spelled, used) => {
|
|
8805
|
+
warnings.push({ code: "unsupported-config", message: `"${spelled}" was not found beside this page; the file "${used}" in the site root was used instead and will be published.`, source: file.relativePath });
|
|
8709
8806
|
});
|
|
8710
8807
|
}
|
|
8711
8808
|
if (platform === "fern" && fernProjectRoot) {
|
|
@@ -9158,21 +9255,31 @@ function migrateRepository(options) {
|
|
|
9158
9255
|
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
|
|
9159
9256
|
}
|
|
9160
9257
|
} else if (platform === "mintlify") {
|
|
9161
|
-
const warnSharedSpec = (path) => {
|
|
9162
|
-
warnings.push({
|
|
9163
|
-
code: "gated-page",
|
|
9164
|
-
message: `OpenAPI spec ${path} is shared with access-restricted pages and is published; it may describe restricted endpoints. Review it before publishing.`,
|
|
9165
|
-
source: path
|
|
9166
|
-
});
|
|
9258
|
+
const warnSharedSpec = (path, excluded = []) => {
|
|
9259
|
+
warnings.push({ code: "gated-page", message: sharedSpecMessage(path, excluded), source: path });
|
|
9167
9260
|
};
|
|
9261
|
+
const keptSpecRefs = [...publishedSpecRefs, ...navigationOperationRefs(mintlifyConfig)];
|
|
9168
9262
|
const allSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs, pages);
|
|
9263
|
+
for (const remote of remoteApiSpecs) {
|
|
9264
|
+
const { withheld, kept } = withheldOperationKeys({ sourcePath: remote.url, filename: remote.url }, withheldSpecRefs, keptSpecRefs);
|
|
9265
|
+
if (withheld.length > 0) {
|
|
9266
|
+
remote.withheldOperations = withheld;
|
|
9267
|
+
remote.keptOperations = kept;
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
const withheldOperations = /* @__PURE__ */ new Map();
|
|
9271
|
+
for (const spec of allSpecs) {
|
|
9272
|
+
const result = excludeWithheldOperations(spec, withheldSpecRefs, keptSpecRefs);
|
|
9273
|
+
spec.content = result.content;
|
|
9274
|
+
withheldOperations.set(spec, result.excluded);
|
|
9275
|
+
}
|
|
9169
9276
|
const resolvedSpecs = allSpecs.filter((spec) => !spec.pageOnly);
|
|
9170
9277
|
const pageOnlySpecs = allSpecs.filter((spec) => spec.pageOnly);
|
|
9171
9278
|
for (const spec of resolvedSpecs) {
|
|
9172
9279
|
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
|
|
9173
9280
|
assets.push(specAsset(spec.filename, spec.content));
|
|
9174
9281
|
}
|
|
9175
|
-
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath);
|
|
9282
|
+
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath, withheldOperations.get(spec));
|
|
9176
9283
|
}
|
|
9177
9284
|
let pageSpecs = resolvedSpecs;
|
|
9178
9285
|
if (resolvedSpecs.length > 0) {
|
|
@@ -9191,24 +9298,35 @@ function migrateRepository(options) {
|
|
|
9191
9298
|
quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${specAssetPath(filename)}`, content: readFileSync5(fallback.absolutePath) });
|
|
9192
9299
|
withheldAssetCount++;
|
|
9193
9300
|
} else {
|
|
9301
|
+
const pruned = excludeWithheldOperations({ sourcePath: fallback.relativePath, filename, content: readFileSync5(fallback.absolutePath) }, withheldSpecRefs, keptSpecRefs);
|
|
9194
9302
|
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
9195
|
-
assets.push(specAsset(filename,
|
|
9303
|
+
assets.push(specAsset(filename, pruned.content));
|
|
9196
9304
|
}
|
|
9197
9305
|
docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
|
|
9198
9306
|
pageSpecs = [{ filename, sourcePath: fallback.relativePath }];
|
|
9199
|
-
if (withheldSpecRefs.some(matches)) warnSharedSpec(fallback.relativePath);
|
|
9307
|
+
if (withheldSpecRefs.some(matches)) warnSharedSpec(fallback.relativePath, pruned.excluded);
|
|
9200
9308
|
}
|
|
9201
9309
|
}
|
|
9202
9310
|
}
|
|
9203
9311
|
for (const spec of pageOnlySpecs) {
|
|
9204
9312
|
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) assets.push(specAsset(spec.filename, spec.content));
|
|
9205
|
-
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath);
|
|
9313
|
+
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath, withheldOperations.get(spec));
|
|
9206
9314
|
}
|
|
9207
9315
|
if (pageOnlySpecs.length > 0) {
|
|
9208
9316
|
docsConfig = injectOpenApiSpecs(docsConfig, pageOnlySpecs, warnings);
|
|
9209
9317
|
pageSpecs = [...pageSpecs, ...pageOnlySpecs];
|
|
9210
9318
|
}
|
|
9211
|
-
|
|
9319
|
+
const explainMissingSpec = (key) => {
|
|
9320
|
+
const sameName = scannedFiles.find((file) => file.relativePath.toLowerCase() === key.toLowerCase());
|
|
9321
|
+
if (sameName && sameName.relativePath !== key) return `paths are case-sensitive; the file is at "${sameName.relativePath}"`;
|
|
9322
|
+
if (sameName && mintignoreMatcher?.ignores(sameName.relativePath)) return "the file is excluded by .mintignore";
|
|
9323
|
+
try {
|
|
9324
|
+
if (lstatSync5(resolvePath(repositoryDir, key)).isSymbolicLink()) return "the file is a symbolic link, which is never followed";
|
|
9325
|
+
} catch {
|
|
9326
|
+
}
|
|
9327
|
+
return sameName ? "the file could not be read as an OpenAPI document" : `no file exists at "${key}"`;
|
|
9328
|
+
};
|
|
9329
|
+
rewriteMintlifyPageSpecRefs(pages, pageSpecs, new Set(remoteApiSpecs.map((spec) => spec.url)), warnings, explainMissingSpec);
|
|
9212
9330
|
}
|
|
9213
9331
|
if (platform === "mintlify") {
|
|
9214
9332
|
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source));
|
|
@@ -9750,7 +9868,7 @@ import remarkMdx3 from "remark-mdx";
|
|
|
9750
9868
|
import remarkParse3 from "remark-parse";
|
|
9751
9869
|
import TurndownService from "turndown";
|
|
9752
9870
|
import { unified as unified3 } from "unified";
|
|
9753
|
-
import { parse as
|
|
9871
|
+
import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
|
|
9754
9872
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
9755
9873
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
9756
9874
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -10475,7 +10593,7 @@ function extractEmbeddedOpenApi(content) {
|
|
|
10475
10593
|
const match = fencePattern.exec(content);
|
|
10476
10594
|
if (!match) return { content };
|
|
10477
10595
|
try {
|
|
10478
|
-
const parsed =
|
|
10596
|
+
const parsed = parseYaml6(match[5], { maxAliasCount: 50 });
|
|
10479
10597
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
10480
10598
|
const document = parsed;
|
|
10481
10599
|
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|
|
@@ -10589,7 +10707,7 @@ function mergeEmbeddedOpenApi(fragments) {
|
|
|
10589
10707
|
if (Object.keys(combinedPaths).length === 0) return null;
|
|
10590
10708
|
if (Object.keys(combinedComponents).length === 0) delete combined.components;
|
|
10591
10709
|
if (tags.size > 0) combined.tags = [...tags.values()];
|
|
10592
|
-
return new TextEncoder().encode(
|
|
10710
|
+
return new TextEncoder().encode(stringifyYaml2(combined));
|
|
10593
10711
|
}
|
|
10594
10712
|
function htmlPage(document, id) {
|
|
10595
10713
|
const $ = load2(document.body);
|
|
@@ -11328,7 +11446,7 @@ import { createHash as createHash3 } from "crypto";
|
|
|
11328
11446
|
import { lookup } from "dns/promises";
|
|
11329
11447
|
import { request } from "https";
|
|
11330
11448
|
import { isIP } from "net";
|
|
11331
|
-
import { parse as
|
|
11449
|
+
import { parse as parseYaml7 } from "yaml";
|
|
11332
11450
|
var MAX_SPEC_BYTES = 25e6;
|
|
11333
11451
|
var MAX_REDIRECTS = 3;
|
|
11334
11452
|
function isPublicAddress(address, family) {
|
|
@@ -11424,7 +11542,7 @@ async function downloadSpec(value, fetcher) {
|
|
|
11424
11542
|
}
|
|
11425
11543
|
function parseOpenApi(content, extension) {
|
|
11426
11544
|
const text2 = content.toString("utf8");
|
|
11427
|
-
const parsed = extension === "yaml" || !text2.trimStart().startsWith("{") ?
|
|
11545
|
+
const parsed = extension === "yaml" || !text2.trimStart().startsWith("{") ? parseYaml7(text2) : JSON.parse(text2);
|
|
11428
11546
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
11429
11547
|
const document = parsed;
|
|
11430
11548
|
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
@@ -11482,7 +11600,10 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
11482
11600
|
try {
|
|
11483
11601
|
const url = validatedSpecUrl(reference.url);
|
|
11484
11602
|
const extension = /\.ya?ml$/i.test(url.pathname) ? "yaml" : "json";
|
|
11485
|
-
const
|
|
11603
|
+
const downloaded = await downloadSpec(reference.url, fetcher);
|
|
11604
|
+
const pruned = markExcluded(downloaded, reference.withheldOperations ?? [], reference.keptOperations);
|
|
11605
|
+
const body = pruned.content;
|
|
11606
|
+
if (reference.withheldOperations?.length) warnings.push({ code: "gated-page", message: sharedSpecMessage(reference.url, pruned.excluded), source: reference.url });
|
|
11486
11607
|
const operations = parseOpenApi(body, extension);
|
|
11487
11608
|
const filename = `openapi-${createHash3("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
11488
11609
|
assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|