sourcey 3.4.11 → 3.5.1

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.
Files changed (102) hide show
  1. package/README.md +13 -12
  2. package/dist/cli.js +46 -4
  3. package/dist/components/changelog/ChangelogPage.d.ts +5 -0
  4. package/dist/components/changelog/ChangelogPage.d.ts.map +1 -0
  5. package/dist/components/changelog/ChangelogPage.js +8 -0
  6. package/dist/components/changelog/EntryList.d.ts +5 -0
  7. package/dist/components/changelog/EntryList.d.ts.map +1 -0
  8. package/dist/components/changelog/EntryList.js +6 -0
  9. package/dist/components/changelog/TypeBadge.d.ts +6 -0
  10. package/dist/components/changelog/TypeBadge.d.ts.map +1 -0
  11. package/dist/components/changelog/TypeBadge.js +14 -0
  12. package/dist/components/changelog/VersionCard.d.ts +6 -0
  13. package/dist/components/changelog/VersionCard.d.ts.map +1 -0
  14. package/dist/components/changelog/VersionCard.js +25 -0
  15. package/dist/components/layout/Head.d.ts.map +1 -1
  16. package/dist/components/layout/Head.js +15 -3
  17. package/dist/components/layout/Page.d.ts.map +1 -1
  18. package/dist/components/layout/Page.js +12 -6
  19. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  20. package/dist/components/openapi/Introduction.js +1 -1
  21. package/dist/components/openapi/MediaTypeDetails.d.ts +11 -0
  22. package/dist/components/openapi/MediaTypeDetails.d.ts.map +1 -0
  23. package/dist/components/openapi/MediaTypeDetails.js +54 -0
  24. package/dist/components/openapi/Parameters.d.ts.map +1 -1
  25. package/dist/components/openapi/Parameters.js +6 -1
  26. package/dist/components/openapi/RequestBody.d.ts.map +1 -1
  27. package/dist/components/openapi/RequestBody.js +5 -1
  28. package/dist/components/openapi/Responses.d.ts.map +1 -1
  29. package/dist/components/openapi/Responses.js +1 -1
  30. package/dist/components/openapi/Security.d.ts.map +1 -1
  31. package/dist/components/openapi/Security.js +3 -2
  32. package/dist/components/openapi/Tags.d.ts.map +1 -1
  33. package/dist/components/openapi/Tags.js +8 -1
  34. package/dist/config.d.ts +50 -1
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +50 -4
  37. package/dist/core/changelog-normalizer.d.ts +8 -0
  38. package/dist/core/changelog-normalizer.d.ts.map +1 -0
  39. package/dist/core/changelog-normalizer.js +531 -0
  40. package/dist/core/doxygen-loader.d.ts.map +1 -1
  41. package/dist/core/doxygen-loader.js +2 -0
  42. package/dist/core/loader.js +2 -0
  43. package/dist/core/markdown-loader.d.ts +21 -0
  44. package/dist/core/markdown-loader.d.ts.map +1 -1
  45. package/dist/core/markdown-loader.js +43 -25
  46. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  47. package/dist/core/navigation.d.ts +4 -3
  48. package/dist/core/navigation.d.ts.map +1 -1
  49. package/dist/core/navigation.js +20 -6
  50. package/dist/core/normalizer.d.ts.map +1 -1
  51. package/dist/core/normalizer.js +102 -4
  52. package/dist/core/parser.d.ts +2 -2
  53. package/dist/core/parser.d.ts.map +1 -1
  54. package/dist/core/parser.js +23 -16
  55. package/dist/core/search-indexer.d.ts +3 -2
  56. package/dist/core/search-indexer.d.ts.map +1 -1
  57. package/dist/core/search-indexer.js +50 -17
  58. package/dist/core/tag-utils.d.ts +5 -0
  59. package/dist/core/tag-utils.d.ts.map +1 -0
  60. package/dist/core/tag-utils.js +22 -0
  61. package/dist/core/types.d.ts +82 -4
  62. package/dist/core/types.d.ts.map +1 -1
  63. package/dist/core/types.js +1 -1
  64. package/dist/dev-server.d.ts +2 -0
  65. package/dist/dev-server.d.ts.map +1 -1
  66. package/dist/dev-server.js +83 -212
  67. package/dist/index.d.ts +6 -10
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +115 -247
  70. package/dist/init.d.ts.map +1 -1
  71. package/dist/init.js +7 -3
  72. package/dist/og/fonts/Inter-Bold.ttf +0 -0
  73. package/dist/og/fonts/Inter-Regular.ttf +0 -0
  74. package/dist/og/generate-og-image.d.ts +10 -0
  75. package/dist/og/generate-og-image.d.ts.map +1 -0
  76. package/dist/og/generate-og-image.js +198 -0
  77. package/dist/renderer/changelog-feed.d.ts +18 -0
  78. package/dist/renderer/changelog-feed.d.ts.map +1 -0
  79. package/dist/renderer/changelog-feed.js +91 -0
  80. package/dist/renderer/context.d.ts +27 -7
  81. package/dist/renderer/context.d.ts.map +1 -1
  82. package/dist/renderer/html-builder.d.ts +10 -1
  83. package/dist/renderer/html-builder.d.ts.map +1 -1
  84. package/dist/renderer/html-builder.js +58 -5
  85. package/dist/renderer/llms.d.ts.map +1 -1
  86. package/dist/renderer/llms.js +133 -24
  87. package/dist/site-assembly.d.ts +25 -0
  88. package/dist/site-assembly.d.ts.map +1 -0
  89. package/dist/site-assembly.js +467 -0
  90. package/dist/site-url.d.ts +12 -0
  91. package/dist/site-url.d.ts.map +1 -0
  92. package/dist/site-url.js +67 -0
  93. package/dist/themes/default/sourcey.css +236 -0
  94. package/dist/utils/code-samples.d.ts.map +1 -1
  95. package/dist/utils/code-samples.js +53 -12
  96. package/dist/utils/markdown.d.ts +6 -0
  97. package/dist/utils/markdown.d.ts.map +1 -1
  98. package/dist/utils/markdown.js +11 -2
  99. package/dist/vite-plugin.d.ts +2 -0
  100. package/dist/vite-plugin.d.ts.map +1 -1
  101. package/dist/vite-plugin.js +6 -2
  102. package/package.json +6 -3
@@ -0,0 +1,467 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { extname, posix, resolve } from "node:path";
3
+ import { loadSpec } from "./core/loader.js";
4
+ import { convertToOpenApi3 } from "./core/converter.js";
5
+ import { parseSpec } from "./core/parser.js";
6
+ import { normalizeSpec } from "./core/normalizer.js";
7
+ import { normalizeMcpSpec } from "./core/mcp-normalizer.js";
8
+ import { loadDocsPage, slugFromPath } from "./core/markdown-loader.js";
9
+ import { loadDoxygenTab } from "./core/doxygen-loader.js";
10
+ import { buildNavFromSpec, buildNavFromPages } from "./core/navigation.js";
11
+ import { generateChangelogFeeds } from "./renderer/changelog-feed.js";
12
+ import { pageOutputPath, tabPath } from "./config.js";
13
+ import { toPublicUrl } from "./site-url.js";
14
+ export async function assembleSite(config) {
15
+ const specsBySlug = await loadSpecs(config.tabs);
16
+ const primarySpec = specsBySlug.values().next().value ?? createMinimalSpec();
17
+ const pageMap = new Map();
18
+ const siteTabs = [];
19
+ const changelogDiagnostics = [];
20
+ for (const tab of config.tabs) {
21
+ if (tab.openapi || tab.mcp) {
22
+ const spec = specsBySlug.get(tab.slug);
23
+ const navTab = buildNavFromSpec(spec, tab.slug, config.prettyUrls);
24
+ navTab.label = tab.label;
25
+ siteTabs.push(navTab);
26
+ pageMap.set(tabPath(tab.slug, "index.html"), {
27
+ outputPath: tabPath(tab.slug, "index.html"),
28
+ currentPage: { kind: "spec", spec },
29
+ spec,
30
+ tabSlug: tab.slug,
31
+ pageSlug: "introduction",
32
+ });
33
+ continue;
34
+ }
35
+ if (tab.doxygen) {
36
+ const { pages, navTab } = await loadDoxygenTab(tab.doxygen, tab.slug, tab.label);
37
+ for (const [slug, page] of pages) {
38
+ const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
39
+ pageMap.set(outputPath, {
40
+ outputPath,
41
+ currentPage: { kind: "markdown", markdown: page },
42
+ spec: primarySpec,
43
+ tabSlug: tab.slug,
44
+ pageSlug: slug,
45
+ });
46
+ }
47
+ siteTabs.push(navTab);
48
+ continue;
49
+ }
50
+ if (!tab.groups)
51
+ continue;
52
+ const pagesByPath = new Map();
53
+ for (const group of tab.groups) {
54
+ for (const rp of group.pages) {
55
+ const slug = slugFromPath(rp.slug);
56
+ const page = await loadDocsPage(rp.file, slug, {
57
+ changelog: config.changelog.enabled,
58
+ repoUrl: config.repo,
59
+ });
60
+ pagesByPath.set(rp.slug, page);
61
+ const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
62
+ if (page.kind === "changelog") {
63
+ changelogDiagnostics.push(...page.changelog.diagnostics);
64
+ pageMap.set(outputPath, {
65
+ outputPath,
66
+ currentPage: { kind: "changelog", changelog: page },
67
+ spec: primarySpec,
68
+ tabSlug: tab.slug,
69
+ pageSlug: slug,
70
+ });
71
+ continue;
72
+ }
73
+ pageMap.set(outputPath, {
74
+ outputPath,
75
+ currentPage: { kind: "markdown", markdown: page },
76
+ spec: primarySpec,
77
+ tabSlug: tab.slug,
78
+ pageSlug: slug,
79
+ });
80
+ }
81
+ }
82
+ siteTabs.push(buildNavFromPages(tab, pagesByPath, config.prettyUrls));
83
+ }
84
+ if (config.changelog.permalinks) {
85
+ addPermalinkPages(pageMap);
86
+ }
87
+ const sitePages = Array.from(pageMap.values());
88
+ resolveInternalLinks(sitePages, config);
89
+ const extraFiles = attachChangelogFeeds(sitePages, config);
90
+ return {
91
+ siteTabs,
92
+ primarySpec,
93
+ specsBySlug,
94
+ pageMap,
95
+ changelogDiagnostics,
96
+ extraFiles,
97
+ };
98
+ }
99
+ export function collectDocsPagesByTab(pageMap, tabs) {
100
+ const docsPagesByTab = new Map();
101
+ for (const tab of tabs) {
102
+ if (!tab.groups && !tab.doxygen)
103
+ continue;
104
+ const tabPages = [];
105
+ for (const [, page] of pageMap) {
106
+ if (page.tabSlug !== tab.slug || !isSearchableDocsSitePage(page))
107
+ continue;
108
+ tabPages.push(page.currentPage.kind === "markdown" ? page.currentPage.markdown : page.currentPage.changelog);
109
+ }
110
+ docsPagesByTab.set(tab.slug, tabPages);
111
+ }
112
+ return docsPagesByTab;
113
+ }
114
+ export function rebuildMarkdownTabNavigation(pageMap, siteTabs, tabs, tabSlug, prettyUrls) {
115
+ const tab = tabs.find((candidate) => candidate.slug === tabSlug);
116
+ if (!tab?.groups)
117
+ return;
118
+ const pagesByPath = new Map();
119
+ for (const group of tab.groups) {
120
+ for (const rp of group.pages) {
121
+ const slug = slugFromPath(rp.slug);
122
+ const entry = pageMap.get(pageOutputPath(tab.slug, slug, prettyUrls));
123
+ if (!entry)
124
+ continue;
125
+ if (entry.currentPage.kind === "markdown") {
126
+ pagesByPath.set(rp.slug, entry.currentPage.markdown);
127
+ }
128
+ else if (entry.currentPage.kind === "changelog") {
129
+ pagesByPath.set(rp.slug, entry.currentPage.changelog);
130
+ }
131
+ }
132
+ }
133
+ const navTab = buildNavFromPages(tab, pagesByPath, prettyUrls);
134
+ const idx = siteTabs.findIndex((candidate) => candidate.slug === tabSlug);
135
+ if (idx !== -1)
136
+ siteTabs[idx] = navTab;
137
+ }
138
+ export function enforceChangelogDiagnostics(diagnostics, strictChangelog = false) {
139
+ const error = diagnostics.find((diagnostic) => diagnostic.severity === "error");
140
+ if (error) {
141
+ throw new Error(formatChangelogFailure(error));
142
+ }
143
+ if (!strictChangelog)
144
+ return;
145
+ const warning = diagnostics.find((diagnostic) => diagnostic.severity === "warning");
146
+ if (warning) {
147
+ throw new Error(formatChangelogFailure(warning));
148
+ }
149
+ }
150
+ export function formatChangelogDiagnostic(diagnostic) {
151
+ const location = diagnostic.version ? ` (${diagnostic.version})` : "";
152
+ const line = diagnostic.line ? ` line ${diagnostic.line}` : "";
153
+ return `${diagnostic.severity.toUpperCase()} ${diagnostic.code}${location}${line}: ${diagnostic.message}`;
154
+ }
155
+ export function formatChangelogFailure(diagnostic) {
156
+ const location = diagnostic.version ? ` (${diagnostic.version})` : "";
157
+ const line = diagnostic.line ? ` line ${diagnostic.line}` : "";
158
+ return `[${diagnostic.code}]${location}${line}: ${diagnostic.message}`;
159
+ }
160
+ export function formatChangelogDiagnostics(diagnostics) {
161
+ return diagnostics.map(formatChangelogDiagnostic);
162
+ }
163
+ export async function buildSiteConfig(config) {
164
+ const logo = config.logo
165
+ ? {
166
+ light: await resolveAssetUrl(config.logo.light ?? ""),
167
+ dark: config.logo.dark ? await resolveAssetUrl(config.logo.dark) : undefined,
168
+ href: config.logo.href,
169
+ }
170
+ : undefined;
171
+ const customCSS = await loadCustomCSS(config.theme.css);
172
+ return {
173
+ name: config.name,
174
+ siteUrl: config.siteUrl,
175
+ baseUrl: config.baseUrl,
176
+ prettyUrls: config.prettyUrls,
177
+ theme: config.theme,
178
+ logo: logo?.light ? logo : undefined,
179
+ favicon: config.favicon ? await resolveAssetUrl(config.favicon) : undefined,
180
+ repo: config.repo,
181
+ editBranch: config.editBranch,
182
+ editBasePath: config.editBasePath,
183
+ codeSamples: config.codeSamples,
184
+ navbar: config.navbar,
185
+ footer: config.footer,
186
+ customCSS: customCSS || undefined,
187
+ changelog: config.changelog,
188
+ };
189
+ }
190
+ export function createMinimalSpec() {
191
+ return {
192
+ info: { title: "", version: "", description: "" },
193
+ servers: [],
194
+ tags: [],
195
+ operations: [],
196
+ schemas: {},
197
+ securitySchemes: {},
198
+ webhooks: [],
199
+ };
200
+ }
201
+ export function resolveInternalLinks(pages, config) {
202
+ const pathMap = new Map();
203
+ for (const page of pages) {
204
+ const out = page.outputPath;
205
+ const clean = out.replace(/\/index\.html$/, "").replace(/\.html$/, "");
206
+ pathMap.set(clean, out);
207
+ const docsSourcePath = getCanonicalSourcePath(page);
208
+ if (!docsSourcePath)
209
+ continue;
210
+ const sourceClean = docsSourcePath
211
+ .replace(/\\/g, "/")
212
+ .replace(/^\/+/, "")
213
+ .replace(/\.(md|mdx)$/, "");
214
+ pathMap.set(sourceClean, out);
215
+ }
216
+ const repoBase = config.repo?.replace(/\/$/, "");
217
+ const branch = config.editBranch;
218
+ const sourceBase = repoBase && branch ? `${repoBase}/tree/${branch}` : undefined;
219
+ const prettyUrls = config.prettyUrls;
220
+ for (const page of pages) {
221
+ const docsSourcePath = getDocsSourcePath(page);
222
+ if (!docsSourcePath)
223
+ continue;
224
+ if (page.currentPage.kind === "markdown") {
225
+ page.currentPage.markdown.html = rewriteHtmlLinks(page.currentPage.markdown.html, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
226
+ continue;
227
+ }
228
+ if (page.currentPage.kind !== "changelog")
229
+ continue;
230
+ const changelog = page.currentPage.changelog;
231
+ for (const version of changelog.changelog.versions) {
232
+ if (version.summary) {
233
+ version.summary = rewriteMarkdownLinks(version.summary, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
234
+ }
235
+ for (const section of version.sections) {
236
+ for (const entry of section.entries) {
237
+ entry.html = rewriteHtmlLinks(entry.html, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
238
+ entry.links = entry.links.map((link) => ({
239
+ ...link,
240
+ href: resolveInternalHref(link.href, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) ?? link.href,
241
+ }));
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ function attachChangelogFeeds(pages, config) {
248
+ const extraFiles = new Map();
249
+ if (!config.changelog.feed)
250
+ return extraFiles;
251
+ const changelogPages = pages.filter(isMainChangelogSitePage);
252
+ const globalFeedLinks = changelogPages.length === 1
253
+ ? createFeedLinks("feed.xml", "feed.rss", changelogPages[0].currentPage.changelog.title)
254
+ : null;
255
+ for (const page of changelogPages) {
256
+ const changelog = page.currentPage.changelog;
257
+ const versionHref = (version) => {
258
+ if (config.changelog.permalinks) {
259
+ return toPublicUrl(tabPath(page.tabSlug, `${changelog.slug}/${version.id}/index.html`), config.siteUrl, config.baseUrl, config.prettyUrls);
260
+ }
261
+ return `${toPublicUrl(page.outputPath, config.siteUrl, config.baseUrl, config.prettyUrls)}#${version.id}`;
262
+ };
263
+ const atomPath = globalFeedLinks && changelogPages.length === 1
264
+ ? "feed.xml"
265
+ : tabPath(page.tabSlug, `${changelog.slug}/feed.xml`);
266
+ const rssPath = globalFeedLinks && changelogPages.length === 1
267
+ ? "feed.rss"
268
+ : tabPath(page.tabSlug, `${changelog.slug}/feed.rss`);
269
+ const feedConfig = config.changelog.feed || undefined;
270
+ const feeds = generateChangelogFeeds(changelog, {
271
+ atomPath: toPublicUrl(atomPath, config.siteUrl, config.baseUrl, config.prettyUrls),
272
+ rssPath: toPublicUrl(rssPath, config.siteUrl, config.baseUrl, config.prettyUrls),
273
+ pagePath: toPublicUrl(page.outputPath, config.siteUrl, config.baseUrl, config.prettyUrls),
274
+ siteName: config.name,
275
+ title: feedConfig?.title,
276
+ description: feedConfig?.description,
277
+ versionHref,
278
+ });
279
+ extraFiles.set(atomPath, feeds.atom);
280
+ extraFiles.set(rssPath, feeds.rss);
281
+ const feedLinks = createFeedLinks(atomPath, rssPath, changelog.title);
282
+ page.alternateLinks = feedLinks;
283
+ for (const candidate of pages) {
284
+ if (candidate.currentPage.kind !== "changelog")
285
+ continue;
286
+ if (candidate.currentPage.changelog.sourcePath !== changelog.sourcePath)
287
+ continue;
288
+ candidate.alternateLinks = feedLinks;
289
+ }
290
+ }
291
+ if (globalFeedLinks) {
292
+ for (const page of pages) {
293
+ if (!page.alternateLinks?.length) {
294
+ page.alternateLinks = globalFeedLinks;
295
+ }
296
+ }
297
+ }
298
+ return extraFiles;
299
+ }
300
+ function addPermalinkPages(pageMap) {
301
+ const changelogPages = Array.from(pageMap.values()).filter(isMainChangelogSitePage);
302
+ for (const page of changelogPages) {
303
+ const changelog = page.currentPage.changelog;
304
+ for (const version of changelog.changelog.versions) {
305
+ const permalinkPage = {
306
+ ...changelog,
307
+ description: version.summary ?? changelog.description,
308
+ slug: `${changelog.slug}/${version.id}`,
309
+ headings: [],
310
+ permalinkVersionId: version.id,
311
+ };
312
+ const outputPath = tabPath(page.tabSlug, `${changelog.slug}/${version.id}/index.html`);
313
+ pageMap.set(outputPath, {
314
+ outputPath,
315
+ currentPage: { kind: "changelog", changelog: permalinkPage },
316
+ spec: page.spec,
317
+ tabSlug: page.tabSlug,
318
+ pageSlug: `${page.pageSlug}--${version.id}`,
319
+ });
320
+ }
321
+ }
322
+ }
323
+ async function loadSpecs(tabs) {
324
+ const specsBySlug = new Map();
325
+ for (const tab of tabs) {
326
+ if (tab.openapi) {
327
+ const loaded = await loadSpec(tab.openapi);
328
+ const parsed = await parseSpec(loaded);
329
+ const openapi3 = await convertToOpenApi3(parsed);
330
+ specsBySlug.set(tab.slug, normalizeSpec(openapi3));
331
+ }
332
+ else if (tab.mcp) {
333
+ const { parse } = await import("mcp-parser");
334
+ const mcpSpec = await parse(tab.mcp);
335
+ specsBySlug.set(tab.slug, normalizeMcpSpec(mcpSpec));
336
+ }
337
+ }
338
+ return specsBySlug;
339
+ }
340
+ async function loadCustomCSS(paths) {
341
+ const parts = [];
342
+ for (const path of paths) {
343
+ parts.push(await readFile(path, "utf-8"));
344
+ }
345
+ return parts.join("\n");
346
+ }
347
+ const MIME_TYPES = {
348
+ ".png": "image/png",
349
+ ".jpg": "image/jpeg",
350
+ ".jpeg": "image/jpeg",
351
+ ".gif": "image/gif",
352
+ ".svg": "image/svg+xml",
353
+ ".webp": "image/webp",
354
+ ".ico": "image/x-icon",
355
+ };
356
+ async function resolveAssetUrl(pathOrUrl) {
357
+ if (!pathOrUrl || pathOrUrl.startsWith("data:") || pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
358
+ return pathOrUrl;
359
+ }
360
+ const abs = resolve(pathOrUrl);
361
+ const mime = MIME_TYPES[extname(abs).toLowerCase()] ?? "application/octet-stream";
362
+ const data = await readFile(abs);
363
+ return `data:${mime};base64,${data.toString("base64")}`;
364
+ }
365
+ function isMainChangelogSitePage(page) {
366
+ return page.currentPage.kind === "changelog" && !page.currentPage.changelog.permalinkVersionId;
367
+ }
368
+ function isSearchableDocsSitePage(page) {
369
+ return page.currentPage.kind === "markdown" || isMainChangelogSitePage(page);
370
+ }
371
+ function createFeedLinks(atomPath, rssPath, title) {
372
+ return [
373
+ { href: atomPath, type: "application/atom+xml", title: `${title} Atom Feed` },
374
+ { href: rssPath, type: "application/rss+xml", title: `${title} RSS Feed` },
375
+ ];
376
+ }
377
+ function getCanonicalSourcePath(page) {
378
+ if (page.currentPage.kind === "markdown")
379
+ return page.currentPage.markdown.sourcePath;
380
+ if (isMainChangelogSitePage(page))
381
+ return page.currentPage.changelog.sourcePath;
382
+ return undefined;
383
+ }
384
+ function getDocsSourcePath(page) {
385
+ if (page.currentPage.kind === "markdown")
386
+ return page.currentPage.markdown.sourcePath;
387
+ if (page.currentPage.kind === "changelog")
388
+ return page.currentPage.changelog.sourcePath;
389
+ return undefined;
390
+ }
391
+ function rewriteHtmlLinks(html, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
392
+ return html.replace(/href="([^"]+)"/g, (match, href) => {
393
+ const resolved = resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
394
+ return resolved ? `href="${resolved}"` : match;
395
+ });
396
+ }
397
+ function rewriteMarkdownLinks(markdown, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
398
+ return markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, href) => {
399
+ const resolved = resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
400
+ return resolved ? `[${label}](${resolved})` : match;
401
+ });
402
+ }
403
+ function resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
404
+ if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("#") || href.startsWith("mailto:")) {
405
+ return null;
406
+ }
407
+ const [path, hash] = href.split("#", 2);
408
+ const hashSuffix = hash ? `#${hash}` : "";
409
+ const sourcePath = path.replace(/\\/g, "/");
410
+ const clean = sourcePath.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.(md|mdx)$/, "");
411
+ if (clean.endsWith(".html"))
412
+ return null;
413
+ const pageDir = outputPath.includes("/")
414
+ ? outputPath.substring(0, outputPath.lastIndexOf("/"))
415
+ : "";
416
+ const depth = pageDir ? pageDir.split("/").length : 0;
417
+ const toRoot = depth > 0 ? "../".repeat(depth) : "";
418
+ const candidates = new Set();
419
+ if (!sourcePath.startsWith("/") && docsSourcePath) {
420
+ const sourceDir = posix.dirname(docsSourcePath.replace(/\\/g, "/"));
421
+ candidates.add(posix.normalize(posix.join(sourceDir, clean)));
422
+ }
423
+ candidates.add(clean);
424
+ let target;
425
+ for (const initialCandidate of candidates) {
426
+ let candidate = initialCandidate;
427
+ while (!target && candidate && candidate !== ".") {
428
+ target = pathMap.get(candidate);
429
+ if (target)
430
+ break;
431
+ const slash = candidate.indexOf("/");
432
+ if (slash === -1)
433
+ break;
434
+ candidate = candidate.substring(slash + 1);
435
+ }
436
+ if (target)
437
+ break;
438
+ }
439
+ if (target) {
440
+ return `${toRoot}${toPrettyLink(target, prettyUrls)}${hashSuffix}`;
441
+ }
442
+ if (sourceBase && href.includes("../") && docsSourcePath) {
443
+ const sourceDir = posix.dirname(docsSourcePath);
444
+ const resolved = posix.normalize(posix.join(sourceDir, path));
445
+ if (!resolved.startsWith("..")) {
446
+ return `${sourceBase}/${resolved}${hashSuffix}`;
447
+ }
448
+ }
449
+ return null;
450
+ }
451
+ /**
452
+ * Rewrite an on-disk output path into the relative href that a user visits.
453
+ * - `"slash"`: trims trailing `index.html`, leaves a directory-style `foo/`.
454
+ * - `"strip"`: trims both `index.html` and the trailing slash, so the browser sees `foo`.
455
+ * - `false`: returns the path unchanged.
456
+ */
457
+ function toPrettyLink(target, prettyUrls) {
458
+ if (!prettyUrls)
459
+ return target;
460
+ if (target === "index.html")
461
+ return "";
462
+ if (target.endsWith("/index.html")) {
463
+ const withSlash = target.slice(0, -"index.html".length);
464
+ return prettyUrls === "strip" ? withSlash.slice(0, -1) : withSlash;
465
+ }
466
+ return target;
467
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pretty-URL emission style. See `ResolvedConfig.prettyUrls`.
3
+ */
4
+ export type PrettyUrls = false | "slash" | "strip";
5
+ export declare function isAbsoluteHttpUrl(value: string): boolean;
6
+ export declare function normalizeBaseUrl(baseUrl?: string): string;
7
+ export declare function normalizeSiteUrl(siteUrl?: string): string | undefined;
8
+ export declare function toPublicPath(outputPath: string, baseUrl?: string, prettyUrls?: PrettyUrls): string;
9
+ export declare function toAbsoluteUrl(publicPath: string, siteUrl?: string): string;
10
+ export declare function toPublicUrl(outputPath: string, siteUrl?: string, baseUrl?: string, prettyUrls?: PrettyUrls): string;
11
+ export declare function stripBaseUrl(pathname: string, baseUrl?: string): string;
12
+ //# sourceMappingURL=site-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-url.d.ts","sourceRoot":"","sources":["../src/site-url.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoBrE;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,SAAK,EAAE,UAAU,GAAE,UAAkB,GAAG,MAAM,CAkBrG;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,SAAK,EACZ,UAAU,GAAE,UAAkB,GAC7B,MAAM,CAER;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAcnE"}
@@ -0,0 +1,67 @@
1
+ export function isAbsoluteHttpUrl(value) {
2
+ return value.startsWith("http://") || value.startsWith("https://");
3
+ }
4
+ export function normalizeBaseUrl(baseUrl) {
5
+ const raw = baseUrl?.trim();
6
+ if (!raw || raw === "/")
7
+ return "";
8
+ const cleaned = raw.replace(/^\/+|\/+$/g, "");
9
+ return cleaned ? `/${cleaned}/` : "";
10
+ }
11
+ export function normalizeSiteUrl(siteUrl) {
12
+ const raw = siteUrl?.trim();
13
+ if (!raw)
14
+ return undefined;
15
+ let url;
16
+ try {
17
+ url = new URL(raw);
18
+ }
19
+ catch {
20
+ throw new Error(`Invalid siteUrl "${siteUrl}". Expected an absolute http(s) URL.`);
21
+ }
22
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
23
+ throw new Error(`Invalid siteUrl "${siteUrl}". Expected an absolute http(s) URL.`);
24
+ }
25
+ if (url.pathname !== "/" || url.search || url.hash) {
26
+ throw new Error(`Invalid siteUrl "${siteUrl}". Use siteUrl for the origin and baseUrl for any path prefix.`);
27
+ }
28
+ return url.origin;
29
+ }
30
+ export function toPublicPath(outputPath, baseUrl = "", prettyUrls = false) {
31
+ const normalizedBase = normalizeBaseUrl(baseUrl);
32
+ const prefix = normalizedBase || "/";
33
+ const cleanOutputPath = outputPath.replace(/^\/+/, "");
34
+ if (!cleanOutputPath || cleanOutputPath === "index.html") {
35
+ return prefix;
36
+ }
37
+ if (cleanOutputPath.endsWith("/index.html")) {
38
+ const withSlash = cleanOutputPath.slice(0, -("index.html".length));
39
+ if (prettyUrls === "strip") {
40
+ return `${prefix}${withSlash.slice(0, -1)}`;
41
+ }
42
+ return `${prefix}${withSlash}`;
43
+ }
44
+ return `${prefix}${cleanOutputPath}`;
45
+ }
46
+ export function toAbsoluteUrl(publicPath, siteUrl) {
47
+ if (!siteUrl)
48
+ return publicPath;
49
+ const base = siteUrl.endsWith("/") ? siteUrl : `${siteUrl}/`;
50
+ return new URL(publicPath.replace(/^\/+/, ""), base).toString();
51
+ }
52
+ export function toPublicUrl(outputPath, siteUrl, baseUrl = "", prettyUrls = false) {
53
+ return toAbsoluteUrl(toPublicPath(outputPath, baseUrl, prettyUrls), siteUrl);
54
+ }
55
+ export function stripBaseUrl(pathname, baseUrl = "") {
56
+ const normalizedBase = normalizeBaseUrl(baseUrl);
57
+ if (!normalizedBase)
58
+ return pathname;
59
+ const bareBase = normalizedBase.slice(0, -1);
60
+ if (pathname === bareBase || pathname === normalizedBase) {
61
+ return "/";
62
+ }
63
+ if (pathname.startsWith(normalizedBase)) {
64
+ return `/${pathname.slice(normalizedBase.length)}`;
65
+ }
66
+ return pathname;
67
+ }