sourcey 3.4.11 → 3.5.0

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 +2 -2
  48. package/dist/core/navigation.d.ts.map +1 -1
  49. package/dist/core/navigation.js +5 -1
  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 +81 -210
  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
@@ -1,17 +1,20 @@
1
1
  import { createServer as createViteServer } from "vite";
2
2
  import { resolve, dirname, extname, basename, relative } from "node:path";
3
- import { readFile, access } from "node:fs/promises";
3
+ import { access } from "node:fs/promises";
4
4
  import { fileURLToPath } from "node:url";
5
- import { loadConfig, resolveConfigFromRaw, tabPath } from "./config.js";
5
+ import { loadConfig, pageOutputPath, resolveConfigFromRaw, tabPath } from "./config.js";
6
6
  import { loadSpec } from "./core/loader.js";
7
7
  import { convertToOpenApi3 } from "./core/converter.js";
8
8
  import { parseSpec } from "./core/parser.js";
9
9
  import { normalizeSpec } from "./core/normalizer.js";
10
10
  import { normalizeMcpSpec } from "./core/mcp-normalizer.js";
11
- import { buildNavFromSpec, buildNavFromPages, buildSiteNavigation, withActivePage } from "./core/navigation.js";
12
- import { loadMarkdownPage, slugFromPath } from "./core/markdown-loader.js";
11
+ import { buildNavFromSpec, buildSiteNavigation, withActivePage } from "./core/navigation.js";
12
+ import { loadDocsPage, slugFromPath } from "./core/markdown-loader.js";
13
13
  import { loadDoxygenTab } from "./core/doxygen-loader.js";
14
14
  import { buildSearchIndex } from "./core/search-indexer.js";
15
+ import { createRenderOptions } from "./renderer/html-builder.js";
16
+ import { assembleSite, buildSiteConfig, collectDocsPagesByTab, enforceChangelogDiagnostics, formatChangelogDiagnostic, rebuildMarkdownTabNavigation, resolveInternalLinks, } from "./site-assembly.js";
17
+ import { stripBaseUrl } from "./site-url.js";
15
18
  import { sourceyPlugin } from "./vite-plugin.js";
16
19
  import tailwindcss from "@tailwindcss/vite";
17
20
  import preact from "@preact/preset-vite";
@@ -29,14 +32,15 @@ function log(msg) {
29
32
  const t = new Date().toLocaleTimeString([], { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
30
33
  console.log(` \x1b[2m${t}\x1b[0m ${msg}`);
31
34
  }
32
- function shortPath(p) {
33
- return relative(process.cwd(), p) || basename(p);
35
+ function shortPath(path) {
36
+ return relative(process.cwd(), path) || basename(path);
34
37
  }
35
38
  export async function startDevServer(options) {
36
39
  const { port } = options;
37
- const configPath = resolve(process.cwd(), "sourcey.config.ts");
38
- let config = await loadConfig();
39
- // Collect watch paths
40
+ let config = await loadConfig(options.config);
41
+ const configPath = options.config?.endsWith(".ts")
42
+ ? resolve(options.config)
43
+ : resolve(options.config ?? process.cwd(), "sourcey.config.ts");
40
44
  const watchPaths = [configPath];
41
45
  for (const tab of config.tabs) {
42
46
  if (tab.openapi)
@@ -47,8 +51,8 @@ export async function startDevServer(options) {
47
51
  watchPaths.push(tab.doxygen.xml);
48
52
  if (tab.groups) {
49
53
  for (const group of tab.groups) {
50
- for (const rp of group.pages) {
51
- watchPaths.push(rp.file);
54
+ for (const page of group.pages) {
55
+ watchPaths.push(page.file);
52
56
  }
53
57
  }
54
58
  }
@@ -68,8 +72,8 @@ export async function startDevServer(options) {
68
72
  }
69
73
  if (tab.groups) {
70
74
  for (const group of tab.groups) {
71
- for (const rp of group.pages) {
72
- map.set(resolve(rp.file), { kind: "markdown", tabSlug: tab.slug, pagePath: rp.file, pageSlug: rp.slug });
75
+ for (const page of group.pages) {
76
+ map.set(resolve(page.file), { kind: "markdown", tabSlug: tab.slug, pagePath: page.file, pageSlug: page.slug });
73
77
  }
74
78
  }
75
79
  }
@@ -77,23 +81,30 @@ export async function startDevServer(options) {
77
81
  return map;
78
82
  }
79
83
  let fileToContent = buildFileToContent(config);
80
- // Resolve source paths for Vite dev serving.
81
- // Prefer src/ (local dev) but fall back to dist/ (npm install).
82
84
  const projectRoot = resolve(__dirname, "..");
83
85
  const hasSrc = await exists(resolve(projectRoot, "src/client/index.ts"));
84
86
  const tailwindCssPath = resolve(projectRoot, hasSrc ? "src/themes/default/main.css" : "dist/themes/default/main.css");
85
87
  const sourceyCssPath = resolve(projectRoot, hasSrc ? "src/themes/default/sourcey.css" : "dist/themes/default/sourcey.css");
86
88
  const clientEntry = resolve(projectRoot, hasSrc ? "src/client/index.ts" : "dist/client/index.js");
87
89
  const ssrRendererPath = resolve(projectRoot, hasSrc ? "src/renderer/static-renderer.ts" : "dist/renderer/static-renderer.js");
88
- let vite;
89
90
  let cache = null;
90
91
  let cachePromise = null;
92
+ function reportChangelogDiagnostics(diagnostics) {
93
+ for (const diagnostic of diagnostics) {
94
+ const writer = diagnostic.severity === "error" ? console.error : console.warn;
95
+ writer(` ${formatChangelogDiagnostic(diagnostic)}`);
96
+ }
97
+ }
91
98
  function fullRebuild() {
92
99
  if (!cachePromise) {
93
100
  const start = performance.now();
94
101
  log("building site data...");
95
102
  cachePromise = (async () => {
96
- const data = await loadSiteData(config.tabs);
103
+ const data = await assembleSite(config);
104
+ if (data.changelogDiagnostics.length) {
105
+ reportChangelogDiagnostics(data.changelogDiagnostics);
106
+ }
107
+ enforceChangelogDiagnostics(data.changelogDiagnostics, options.strictChangelog ?? false);
97
108
  const siteConfig = await buildSiteConfig(config);
98
109
  const result = { data, siteConfig };
99
110
  cache = result;
@@ -113,12 +124,7 @@ export async function startDevServer(options) {
113
124
  return Promise.resolve(cache);
114
125
  return fullRebuild();
115
126
  }
116
- // Incremental patch: reload only the piece that changed, then reassemble
117
- // siteTabs + pageMap from the patched data. Falls back to full rebuild if
118
- // the cache isn't warm yet.
119
127
  async function incrementalUpdate(content) {
120
- // Capture current cache; if a full rebuild replaces it while we await,
121
- // we abandon this patch (the rebuild already produced fresh data).
122
128
  const snapshot = cache;
123
129
  if (!snapshot) {
124
130
  await fullRebuild();
@@ -129,37 +135,48 @@ export async function startDevServer(options) {
129
135
  if (content.kind === "markdown") {
130
136
  const slug = slugFromPath(content.pageSlug);
131
137
  log(`reloading ${shortPath(content.pagePath)}`);
132
- const page = await loadMarkdownPage(content.pagePath, slug);
138
+ const page = await loadDocsPage(content.pagePath, slug, {
139
+ changelog: config.changelog.enabled,
140
+ repoUrl: config.repo,
141
+ });
133
142
  if (cache !== snapshot)
134
143
  return;
135
- const pageKey = tabPath(content.tabSlug, `${slug}.html`);
144
+ const pageKey = pageOutputPath(content.tabSlug, slug, config.prettyUrls);
136
145
  const existing = data.pageMap.get(pageKey);
146
+ if (page.kind === "changelog" || existing?.currentPage.kind === "changelog") {
147
+ cache = null;
148
+ cachePromise = null;
149
+ await fullRebuild();
150
+ return;
151
+ }
137
152
  if (existing) {
138
153
  existing.currentPage = { kind: "markdown", markdown: page };
139
154
  }
140
- rebuildTabNav(data, config.tabs, content.tabSlug);
155
+ rebuildMarkdownTabNavigation(data.pageMap, data.siteTabs, config.tabs, content.tabSlug, config.prettyUrls);
141
156
  }
142
157
  else if (content.kind === "doxygen") {
143
- const tab = config.tabs.find((t) => t.slug === content.tabSlug);
158
+ const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
144
159
  if (!tab?.doxygen)
145
160
  return;
146
161
  log(`rebuilding doxygen tab "${tab.label}"`);
147
162
  const { pages, navTab } = await loadDoxygenTab(tab.doxygen, tab.slug, tab.label);
148
163
  if (cache !== snapshot)
149
164
  return;
150
- for (const [key, entry] of data.pageMap) {
151
- if (entry.tabSlug === content.tabSlug)
165
+ for (const [key, page] of data.pageMap) {
166
+ if (page.tabSlug === content.tabSlug)
152
167
  data.pageMap.delete(key);
153
168
  }
154
169
  for (const [slug, page] of pages) {
155
- data.pageMap.set(tabPath(tab.slug, `${slug}.html`), {
170
+ const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
171
+ data.pageMap.set(outputPath, {
172
+ outputPath,
156
173
  spec: data.primarySpec,
157
174
  currentPage: { kind: "markdown", markdown: page },
158
175
  tabSlug: tab.slug,
159
176
  pageSlug: slug,
160
177
  });
161
178
  }
162
- const idx = data.siteTabs.findIndex((t) => t.slug === content.tabSlug);
179
+ const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
163
180
  if (idx !== -1)
164
181
  data.siteTabs[idx] = navTab;
165
182
  else
@@ -180,11 +197,11 @@ export async function startDevServer(options) {
180
197
  existing.currentPage = { kind: "spec", spec };
181
198
  existing.spec = spec;
182
199
  }
183
- const tab = config.tabs.find((t) => t.slug === content.tabSlug);
200
+ const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
184
201
  if (tab) {
185
202
  const navTab = buildNavFromSpec(spec, tab.slug);
186
203
  navTab.label = tab.label;
187
- const idx = data.siteTabs.findIndex((t) => t.slug === content.tabSlug);
204
+ const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
188
205
  if (idx !== -1)
189
206
  data.siteTabs[idx] = navTab;
190
207
  }
@@ -203,54 +220,31 @@ export async function startDevServer(options) {
203
220
  existing.currentPage = { kind: "spec", spec };
204
221
  existing.spec = spec;
205
222
  }
206
- const tab = config.tabs.find((t) => t.slug === content.tabSlug);
223
+ const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
207
224
  if (tab) {
208
225
  const navTab = buildNavFromSpec(spec, tab.slug);
209
226
  navTab.label = tab.label;
210
- const idx = data.siteTabs.findIndex((t) => t.slug === content.tabSlug);
227
+ const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
211
228
  if (idx !== -1)
212
229
  data.siteTabs[idx] = navTab;
213
230
  }
214
231
  }
232
+ resolveInternalLinks(Array.from(data.pageMap.values()), config);
215
233
  const ms = Math.round(performance.now() - start);
216
234
  log(`updated \x1b[2m(${ms}ms)\x1b[0m`);
217
235
  }
218
- // Rebuild navigation for a single markdown tab from cached page data
219
- function rebuildTabNav(data, tabs, tabSlug) {
220
- const tab = tabs.find((t) => t.slug === tabSlug);
221
- if (!tab?.groups)
222
- return;
223
- const pagesByPath = new Map();
224
- for (const group of tab.groups) {
225
- for (const rp of group.pages) {
226
- const slug = slugFromPath(rp.slug);
227
- const pageKey = tabPath(tab.slug, `${slug}.html`);
228
- const entry = data.pageMap.get(pageKey);
229
- if (entry?.currentPage.kind === "markdown") {
230
- pagesByPath.set(rp.slug, entry.currentPage.markdown);
231
- }
232
- }
233
- }
234
- const navTab = buildNavFromPages(tab, pagesByPath);
235
- const idx = data.siteTabs.findIndex((t) => t.slug === tabSlug);
236
- if (idx !== -1)
237
- data.siteTabs[idx] = navTab;
238
- }
239
- // Reload config through Vite SSR (bypasses Node's module cache)
240
236
  async function reloadConfig() {
241
237
  const configMod = await vite.ssrLoadModule(configPath);
242
- return resolveConfigFromRaw(configMod.default, process.cwd());
238
+ return resolveConfigFromRaw(configMod.default, dirname(configPath));
243
239
  }
244
240
  async function render(url) {
245
- // Load renderer through Vite's SSR graph so invalidateAll() picks up
246
- // component changes. Site data uses static imports (file reads only).
247
241
  const { renderPage } = await vite.ssrLoadModule(ssrRendererPath);
248
242
  const { data, siteConfig: site } = await getCached();
249
- const { siteTabs, pageMap } = data;
250
- const navigation = buildSiteNavigation(siteTabs);
251
- let pagePath = url.replace(/^\//, "").replace(/\/$/, "");
243
+ const navigation = buildSiteNavigation(data.siteTabs);
244
+ const requestPath = stripBaseUrl(new URL(url, "http://sourcey.local").pathname, config.baseUrl);
245
+ let pagePath = requestPath.replace(/^\//, "").replace(/\/$/, "");
252
246
  if (!pagePath || pagePath === "index.html") {
253
- const firstKey = pageMap.keys().next().value;
247
+ const firstKey = data.pageMap.keys().next().value;
254
248
  if (!firstKey)
255
249
  return null;
256
250
  pagePath = firstKey;
@@ -258,35 +252,30 @@ export async function startDevServer(options) {
258
252
  if (!pagePath.endsWith(".html")) {
259
253
  pagePath += "/index.html";
260
254
  }
261
- const pageData = pageMap.get(pagePath);
255
+ let pageData = data.pageMap.get(pagePath);
256
+ if (!pageData && pagePath.endsWith(".html") && !pagePath.endsWith("/index.html")) {
257
+ // Fallback for stale `.html` links when prettyUrls is enabled.
258
+ const pretty = pagePath.replace(/\.html$/, "/index.html");
259
+ pageData = data.pageMap.get(pretty);
260
+ }
262
261
  if (!pageData)
263
262
  return null;
264
263
  const activeNav = withActivePage(navigation, pageData.tabSlug, pageData.pageSlug);
265
- const renderOptions = { embeddable: false, assetBase: "/" };
264
+ const renderOptions = createRenderOptions(pageData, site, {
265
+ embeddable: false,
266
+ assetBase: config.baseUrl || "/",
267
+ });
266
268
  let html = renderPage(pageData.spec, renderOptions, activeNav, pageData.currentPage, site);
267
- // Rewrite asset paths for Vite HMR
268
- // Preact outputs <link ... href="/sourcey.css"/> (no space before />)
269
269
  html = html.replace(/<link rel="stylesheet" href="[^"]*sourcey\.css"\s*\/?>/, `<link rel="stylesheet" href="/@fs${tailwindCssPath}" />\n<link rel="stylesheet" href="/@fs${sourceyCssPath}?direct" />`);
270
270
  html = html.replace(/<script src="[^"]*sourcey\.js"[^>]*><\/script>/, `<script type="module" src="/@fs${clientEntry}"></script>`);
271
271
  html = html.replace(/<script src="[^"]*sourcey\.js"\s*\/?>/, `<script type="module" src="/@fs${clientEntry}"></script>`);
272
272
  return html;
273
273
  }
274
274
  async function buildSearchIndexForDev() {
275
- const { data: { siteTabs, pageMap, specsBySlug } } = await getCached();
276
- const navigation = buildSiteNavigation(siteTabs);
277
- const markdownPagesByTab = new Map();
278
- for (const tab of config.tabs) {
279
- if (tab.groups || tab.doxygen) {
280
- const tabPages = [];
281
- for (const [, entry] of pageMap) {
282
- if (entry.tabSlug === tab.slug && entry.currentPage.kind === "markdown") {
283
- tabPages.push(entry.currentPage.markdown);
284
- }
285
- }
286
- markdownPagesByTab.set(tab.slug, tabPages);
287
- }
288
- }
289
- return buildSearchIndex(specsBySlug, markdownPagesByTab, navigation, "/", config.search.featured);
275
+ const { data } = await getCached();
276
+ const navigation = buildSiteNavigation(data.siteTabs);
277
+ const docsPagesByTab = collectDocsPagesByTab(data.pageMap, config.tabs);
278
+ return buildSearchIndex(data.specsBySlug, docsPagesByTab, navigation, config.baseUrl || "/", config.search.featured);
290
279
  }
291
280
  const viteConfig = {
292
281
  root: process.cwd(),
@@ -303,6 +292,7 @@ export async function startDevServer(options) {
303
292
  tailwindcss(),
304
293
  sourceyPlugin({
305
294
  watchPaths,
295
+ baseUrl: () => config.baseUrl,
306
296
  render,
307
297
  searchIndex: buildSearchIndexForDev,
308
298
  }),
@@ -313,16 +303,13 @@ export async function startDevServer(options) {
313
303
  exclude: ["sourcey"],
314
304
  },
315
305
  };
316
- vite = await createViteServer(viteConfig);
317
- // Incremental rebuild when content files change
306
+ const vite = await createViteServer(viteConfig);
318
307
  vite.watcher.on("change", async (file) => {
319
- // Check direct match first (markdown pages, spec files, config)
320
308
  let content = fileToContent.get(file);
321
- // For .xml files inside a doxygen directory, map to the parent tab
322
309
  if (!content && extname(file) === ".xml") {
323
- for (const [, c] of fileToContent) {
324
- if (c.kind === "doxygen" && file.startsWith(resolve(c.xmlDir))) {
325
- content = c;
310
+ for (const [, candidate] of fileToContent) {
311
+ if (candidate.kind === "doxygen" && file.startsWith(resolve(candidate.xmlDir))) {
312
+ content = candidate;
326
313
  break;
327
314
  }
328
315
  }
@@ -341,133 +328,17 @@ export async function startDevServer(options) {
341
328
  fullRebuild().catch((err) => {
342
329
  console.error(" Site data rebuild failed:", err.message);
343
330
  });
331
+ return;
344
332
  }
345
- else {
346
- incrementalUpdate(content).catch((err) => {
347
- console.error(" Incremental update failed:", err.message);
348
- });
349
- }
333
+ incrementalUpdate(content).catch((err) => {
334
+ console.error(" Incremental update failed:", err.message);
335
+ });
350
336
  });
351
337
  await vite.listen();
352
- console.log(`\n Sourcey dev server running at http://localhost:${port}`);
338
+ console.log(`\n Sourcey dev server running at http://localhost:${port}${config.baseUrl || "/"}`);
353
339
  console.log(` Watching: ${watchPaths.length} content file${watchPaths.length === 1 ? "" : "s"} + components + CSS (HMR)`);
354
340
  console.log(` Press Ctrl+C to stop\n`);
355
- // Build in background — server is listening, first request waits for this
356
341
  fullRebuild().catch((err) => {
357
342
  console.error(" Initial site data build failed:", err.message);
358
343
  });
359
344
  }
360
- async function loadSiteData(tabs) {
361
- const specsBySlug = new Map();
362
- const siteTabs = [];
363
- const pageMap = new Map();
364
- for (const tab of tabs) {
365
- if (tab.openapi) {
366
- const loaded = await loadSpec(tab.openapi);
367
- const parsed = await parseSpec(loaded);
368
- const openapi3 = await convertToOpenApi3(parsed);
369
- const spec = normalizeSpec(openapi3);
370
- specsBySlug.set(tab.slug, spec);
371
- }
372
- else if (tab.mcp) {
373
- const { parse } = await import("mcp-parser");
374
- const mcpSpec = await parse(tab.mcp);
375
- const spec = normalizeMcpSpec(mcpSpec);
376
- specsBySlug.set(tab.slug, spec);
377
- }
378
- }
379
- const primarySpec = specsBySlug.values().next().value ?? {
380
- info: { title: "", version: "", description: "" },
381
- servers: [], tags: [], operations: [], schemas: {}, securitySchemes: {}, webhooks: [],
382
- };
383
- for (const tab of tabs) {
384
- if (tab.openapi || tab.mcp) {
385
- const spec = specsBySlug.get(tab.slug);
386
- const navTab = buildNavFromSpec(spec, tab.slug);
387
- navTab.label = tab.label;
388
- siteTabs.push(navTab);
389
- pageMap.set(tabPath(tab.slug, "index.html"), {
390
- spec,
391
- currentPage: { kind: "spec", spec },
392
- tabSlug: tab.slug,
393
- pageSlug: "introduction",
394
- });
395
- }
396
- else if (tab.doxygen) {
397
- const { pages, navTab } = await loadDoxygenTab(tab.doxygen, tab.slug, tab.label);
398
- for (const [slug, page] of pages) {
399
- pageMap.set(tabPath(tab.slug, `${slug}.html`), {
400
- spec: primarySpec,
401
- currentPage: { kind: "markdown", markdown: page },
402
- tabSlug: tab.slug,
403
- pageSlug: slug,
404
- });
405
- }
406
- siteTabs.push(navTab);
407
- }
408
- else if (tab.groups) {
409
- const pagesByPath = new Map();
410
- for (const group of tab.groups) {
411
- for (const rp of group.pages) {
412
- const slug = slugFromPath(rp.slug);
413
- const page = await loadMarkdownPage(rp.file, slug);
414
- pagesByPath.set(rp.slug, page);
415
- pageMap.set(tabPath(tab.slug, `${slug}.html`), {
416
- spec: primarySpec,
417
- currentPage: { kind: "markdown", markdown: page },
418
- tabSlug: tab.slug,
419
- pageSlug: slug,
420
- });
421
- }
422
- }
423
- const navTab = buildNavFromPages(tab, pagesByPath);
424
- siteTabs.push(navTab);
425
- }
426
- }
427
- return { siteTabs, primarySpec, pageMap, specsBySlug };
428
- }
429
- async function buildSiteConfig(config) {
430
- const logo = config.logo
431
- ? {
432
- light: await resolveAssetUrl(config.logo.light ?? ""),
433
- dark: config.logo.dark ? await resolveAssetUrl(config.logo.dark) : undefined,
434
- href: config.logo.href,
435
- }
436
- : undefined;
437
- const customCSS = await loadCustomCSS(config.theme.css);
438
- return {
439
- name: config.name,
440
- theme: config.theme,
441
- logo: logo?.light ? logo : undefined,
442
- favicon: config.favicon ? await resolveAssetUrl(config.favicon) : undefined,
443
- repo: config.repo,
444
- editBranch: config.editBranch,
445
- editBasePath: config.editBasePath,
446
- codeSamples: config.codeSamples,
447
- navbar: config.navbar,
448
- footer: config.footer,
449
- customCSS: customCSS || undefined,
450
- };
451
- }
452
- async function loadCustomCSS(paths) {
453
- const parts = [];
454
- for (const p of paths) {
455
- parts.push(await readFile(p, "utf-8"));
456
- }
457
- return parts.join("\n");
458
- }
459
- const MIME_TYPES = {
460
- ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
461
- ".gif": "image/gif", ".svg": "image/svg+xml", ".webp": "image/webp",
462
- ".ico": "image/x-icon",
463
- };
464
- async function resolveAssetUrl(pathOrUrl) {
465
- if (!pathOrUrl || pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://") || pathOrUrl.startsWith("data:")) {
466
- return pathOrUrl;
467
- }
468
- const abs = resolve(pathOrUrl);
469
- const ext = extname(abs).toLowerCase();
470
- const mime = MIME_TYPES[ext] ?? "application/octet-stream";
471
- const data = await readFile(abs);
472
- return `data:${mime};base64,${data.toString("base64")}`;
473
- }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,17 @@
1
- import type { SitePage } from "./renderer/html-builder.js";
2
- import type { NormalizedSpec } from "./core/types.js";
1
+ import type { ChangelogDiagnostic, NormalizedSpec } from "./core/types.js";
3
2
  import type { ResolvedConfig } from "./config.js";
4
3
  export interface BuildOptions {
5
4
  specSource: string;
6
5
  outputDir?: string;
7
6
  embeddable?: boolean;
8
7
  skipWrite?: boolean;
8
+ strictChangelog?: boolean;
9
9
  }
10
10
  export interface BuildResult {
11
11
  spec: NormalizedSpec;
12
12
  outputDir: string;
13
13
  pageCount: number;
14
+ changelogDiagnostics: ChangelogDiagnostic[];
14
15
  }
15
16
  /**
16
17
  * Build API documentation from a single OpenAPI/Swagger spec.
@@ -23,22 +24,17 @@ export interface SiteBuildOptions {
23
24
  config?: ResolvedConfig;
24
25
  skipWrite?: boolean;
25
26
  embeddable?: boolean;
27
+ strictChangelog?: boolean;
26
28
  }
27
29
  export interface SiteBuildResult {
28
30
  outputDir: string;
29
31
  pageCount: number;
32
+ changelogDiagnostics: ChangelogDiagnostic[];
30
33
  /** @internal specs by tab slug, for buildDocs compat */
31
34
  _specs?: Map<string, NormalizedSpec>;
32
35
  }
33
36
  export declare function buildSiteDocs(options?: SiteBuildOptions): Promise<SiteBuildResult>;
34
- /**
35
- * Rewrite internal links in markdown page HTML to correct relative .html paths.
36
- *
37
- * Authors write links like [Page](/slug) or [Page](/tab/slug). This pass
38
- * rewrites matching href values so they resolve on static file servers
39
- * that don't support extensionless URLs.
40
- */
41
- export declare function resolveInternalLinks(pages: SitePage[], config: ResolvedConfig): void;
42
37
  export { defineConfig } from "./config.js";
38
+ export { resolveInternalLinks } from "./site-assembly.js";
43
39
  export type { NormalizedSpec, NormalizedOperation, NormalizedTag, NormalizedSchema, NormalizedParameter, NormalizedRequestBody, NormalizedResponse, } from "./core/types.js";
44
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,aAAa,CAAC;AAe/D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAY3E;AAMD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACtC;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAoH5F;AA6ED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAmGpF;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAA8B,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkBlD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAkB3E;AAMD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,wDAAwD;IACxD,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACtC;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwF5F;AAyED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}