sourcey 3.6.4 → 3.6.6

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 (64) hide show
  1. package/README.md +38 -5
  2. package/dist/adapters/shared.js +1 -1
  3. package/dist/astro/index.d.ts +89 -0
  4. package/dist/astro/index.d.ts.map +1 -0
  5. package/dist/astro/index.js +299 -0
  6. package/dist/cli.js +8 -0
  7. package/dist/client/reader-controls.js +81 -0
  8. package/dist/client/reader.d.ts +5 -0
  9. package/dist/client/reader.d.ts.map +1 -0
  10. package/dist/client/reader.js +5 -0
  11. package/dist/client/tabs.js +144 -95
  12. package/dist/components/App.d.ts.map +1 -1
  13. package/dist/components/App.js +1 -1
  14. package/dist/components/layout/Head.d.ts.map +1 -1
  15. package/dist/components/layout/Head.js +6 -4
  16. package/dist/components/layout/Page.d.ts.map +1 -1
  17. package/dist/components/layout/Page.js +20 -14
  18. package/dist/components/layout/Reader.d.ts +5 -0
  19. package/dist/components/layout/Reader.d.ts.map +1 -0
  20. package/dist/components/layout/Reader.js +64 -0
  21. package/dist/components/layout/SearchDialog.d.ts +2 -0
  22. package/dist/components/layout/SearchDialog.d.ts.map +1 -0
  23. package/dist/components/layout/SearchDialog.js +4 -0
  24. package/dist/components/openapi/Operation.d.ts.map +1 -1
  25. package/dist/components/openapi/Operation.js +2 -2
  26. package/dist/components/openapi/Tags.js +1 -1
  27. package/dist/config.d.ts +56 -4
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +18 -10
  30. package/dist/core/doxygen-loader.js +1 -1
  31. package/dist/core/loader.js +2 -2
  32. package/dist/core/markdown-loader.d.ts +2 -0
  33. package/dist/core/markdown-loader.d.ts.map +1 -1
  34. package/dist/core/markdown-loader.js +1 -0
  35. package/dist/core/navigation.js +1 -1
  36. package/dist/core/parser.js +1 -1
  37. package/dist/dev-server.d.ts.map +1 -1
  38. package/dist/dev-server.js +3 -4
  39. package/dist/index.d.ts +4 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +16 -112
  42. package/dist/init.js +4 -4
  43. package/dist/renderer/html-builder.d.ts.map +1 -1
  44. package/dist/renderer/html-builder.js +6 -27
  45. package/dist/renderer/static-files.d.ts +6 -0
  46. package/dist/renderer/static-files.d.ts.map +1 -0
  47. package/dist/renderer/static-files.js +78 -0
  48. package/dist/site.d.ts +38 -0
  49. package/dist/site.d.ts.map +1 -0
  50. package/dist/site.js +159 -0
  51. package/dist/themes/assets.d.ts +8 -0
  52. package/dist/themes/assets.d.ts.map +1 -0
  53. package/dist/themes/assets.js +16 -0
  54. package/dist/themes/default/sourcey.css +305 -60
  55. package/dist/themes/reader/sourcey.css +1024 -0
  56. package/dist/themes/registry.d.ts +20 -0
  57. package/dist/themes/registry.d.ts.map +1 -0
  58. package/dist/themes/registry.js +8 -0
  59. package/dist/utils/server-warnings.d.ts +4 -0
  60. package/dist/utils/server-warnings.d.ts.map +1 -0
  61. package/dist/utils/server-warnings.js +41 -0
  62. package/dist/vite-plugin.d.ts.map +1 -1
  63. package/dist/vite-plugin.js +1 -53
  64. package/package.json +9 -4
package/dist/index.js CHANGED
@@ -1,11 +1,7 @@
1
- import { readFile } from "node:fs/promises";
2
- import { extname, resolve } from "node:path";
3
- import { buildSite as buildSiteHtml } from "./renderer/html-builder.js";
1
+ import { resolve } from "node:path";
4
2
  import { loadConfig, configFromSpec } from "./config.js";
5
- import { buildSiteNavigation } from "./core/navigation.js";
6
- import { buildSearchIndex } from "./core/search-indexer.js";
7
- import { generateLlmsTxt, generateLlmsFullTxt } from "./renderer/llms.js";
8
- import { assembleSite, buildSiteConfig, collectDocsPagesByTab, createMinimalSpec, enforceChangelogDiagnostics, enforceGodocDiagnostics, enforceRustdocDiagnostics, } from "./site-assembly.js";
3
+ import { buildSourceySite, writeSourceySite } from "./site.js";
4
+ import { createMinimalSpec } from "./site-assembly.js";
9
5
  /**
10
6
  * Build API documentation from a single OpenAPI/Swagger spec.
11
7
  * Wraps the spec in a single-tab site and renders through the modern layout.
@@ -30,117 +26,25 @@ export async function buildDocs(options) {
30
26
  export async function buildSiteDocs(options = {}) {
31
27
  const outputDir = resolve(options.outputDir ?? "dist");
32
28
  const config = options.config ?? (await loadConfig(options.configDir));
33
- const assembled = await assembleSite(config);
34
- const site = await buildSiteConfig(config);
35
- const sitePages = Array.from(assembled.pageMap.values());
36
- const navigation = buildSiteNavigation(assembled.siteTabs);
37
- enforceChangelogDiagnostics(assembled.changelogDiagnostics, options.strictChangelog);
38
- enforceGodocDiagnostics(assembled.godocDiagnostics);
39
- enforceRustdocDiagnostics(assembled.rustdocDiagnostics);
40
- const docsPagesByTab = collectDocsPagesByTab(assembled.pageMap, config.tabs);
41
- const searchIndex = buildSearchIndex(assembled.specsBySlug, docsPagesByTab, navigation, config.baseUrl || "/", config.search.featured, config.prettyUrls);
42
- const llmsTxt = generateLlmsTxt(sitePages, navigation, site);
43
- const llmsFullTxt = generateLlmsFullTxt(sitePages, navigation, site);
44
- const extraFiles = new Map(assembled.extraFiles);
45
- const ogImages = new Map();
46
- if (!config.ogImage) {
47
- const { generateOgImage } = await import("./og/generate-og-image.js");
48
- const CONCURRENCY = 8;
49
- for (let i = 0; i < sitePages.length; i += CONCURRENCY) {
50
- const batch = sitePages.slice(i, i + CONCURRENCY);
51
- await Promise.all(batch.map(async (page) => {
52
- const ogMeta = describePageForOg(page, config.name || "API", config.changelog.ogImages);
53
- if (!ogMeta)
54
- return;
55
- try {
56
- const ogPath = `_og/${page.outputPath.replace(/\.html$/, ".png")}`;
57
- const png = await generateOgImage({
58
- title: ogMeta.title,
59
- description: ogMeta.description,
60
- siteName: config.name,
61
- theme: config.theme,
62
- logo: site.logo?.light,
63
- });
64
- page.ogImagePath = ogPath;
65
- ogImages.set(ogPath, png);
66
- }
67
- catch (error) {
68
- const message = error instanceof Error ? error.message : String(error);
69
- console.warn(`Sourcey: skipping OG image for ${page.outputPath}: ${message}`);
70
- }
71
- }));
72
- }
73
- }
74
- else {
75
- const staticOg = config.ogImage;
76
- if (staticOg.startsWith("http://") ||
77
- staticOg.startsWith("https://") ||
78
- staticOg.startsWith("data:")) {
79
- for (const page of sitePages) {
80
- page.ogImagePath = staticOg;
81
- }
82
- }
83
- else {
84
- const ogPath = `_og/static${extname(staticOg) || ".png"}`;
85
- extraFiles.set(ogPath, await readFile(staticOg));
86
- for (const page of sitePages) {
87
- page.ogImagePath = ogPath;
88
- }
89
- }
90
- }
29
+ const sourceySite = await buildSourceySite({
30
+ config,
31
+ outputDir,
32
+ strictChangelog: options.strictChangelog,
33
+ generateOgImages: options.generateOgImages,
34
+ });
91
35
  if (!options.skipWrite) {
92
- await buildSiteHtml(sitePages, navigation, outputDir, site, {
93
- searchIndex,
94
- llmsTxt,
95
- llmsFullTxt,
96
- embeddable: options.embeddable,
97
- ogImages,
98
- extraFiles,
99
- });
36
+ await writeSourceySite(sourceySite, { embeddable: options.embeddable });
100
37
  }
101
38
  return {
102
39
  outputDir,
103
- pageCount: sitePages.length,
104
- changelogDiagnostics: assembled.changelogDiagnostics,
105
- godocDiagnostics: assembled.godocDiagnostics,
106
- rustdocDiagnostics: assembled.rustdocDiagnostics,
107
- _specs: assembled.specsBySlug,
40
+ pageCount: sourceySite.pageCount,
41
+ changelogDiagnostics: sourceySite.changelogDiagnostics,
42
+ godocDiagnostics: sourceySite.godocDiagnostics,
43
+ rustdocDiagnostics: sourceySite.rustdocDiagnostics,
44
+ _specs: sourceySite.specsBySlug,
108
45
  };
109
46
  }
110
- function describePageForOg(page, defaultSiteName, changelogPermalinkOg) {
111
- if (page.currentPage.kind === "markdown") {
112
- return {
113
- title: page.currentPage.markdown.title,
114
- description: page.currentPage.markdown.description || undefined,
115
- };
116
- }
117
- if (page.currentPage.kind === "changelog") {
118
- const changelog = page.currentPage.changelog;
119
- if (changelog.permalinkVersionId) {
120
- if (!changelogPermalinkOg)
121
- return null;
122
- const version = changelog.changelog.versions.find((candidate) => candidate.id === changelog.permalinkVersionId);
123
- if (!version)
124
- return null;
125
- return {
126
- title: `${version.version ?? "Unreleased"} - ${changelog.title}`,
127
- description: version.summary || summarizeVersion(version),
128
- };
129
- }
130
- return {
131
- title: changelog.title,
132
- description: changelog.description || changelog.changelog.description,
133
- };
134
- }
135
- return { title: `${defaultSiteName} Reference` };
136
- }
137
- function summarizeVersion(version) {
138
- if (version.summary)
139
- return version.summary;
140
- const texts = version.sections.flatMap((section) => section.entries.map((entry) => entry.text));
141
- const summary = texts.slice(0, 3).join(" ");
142
- return summary || undefined;
143
- }
144
47
  export { defineConfig } from "./config.js";
145
48
  export { doxygen, godoc, markdown, mcp, mkdocs, openapi, rustdoc } from "./adapters/index.js";
146
49
  export { resolveInternalLinks } from "./site-assembly.js";
50
+ export { buildSourceySite, collectSourceyWatchPaths, writeSourceySite } from "./site.js";
package/dist/init.js CHANGED
@@ -97,12 +97,12 @@ export const init = defineCommand({
97
97
  }));
98
98
  if (typeof name === "symbol")
99
99
  process.exit(0);
100
- // -- Theme preset --
101
- const preset = (await consola.prompt("Theme preset:", {
100
+ // -- Theme --
101
+ const theme = (await consola.prompt("Theme:", {
102
102
  type: "select",
103
103
  options: ["default", "minimal", "api-first"],
104
104
  }));
105
- if (typeof preset === "symbol")
105
+ if (typeof theme === "symbol")
106
106
  process.exit(0);
107
107
  // -- Detected sources --
108
108
  let addOpenAPI = null;
@@ -200,7 +200,7 @@ export const init = defineCommand({
200
200
  export default defineConfig({
201
201
  name: "${name}",
202
202
  theme: {
203
- preset: "${preset}",
203
+ name: "${theme}",
204
204
  },
205
205
  navigation: {
206
206
  tabs: [
@@ -1 +1 @@
1
- {"version":3,"file":"html-builder.d.ts","sourceRoot":"","sources":["../../src/renderer/html-builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiC5D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,aAAa,CAcf;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CAC3C,GACA,OAAO,CAAC,WAAW,CAAC,CA8EtB"}
1
+ {"version":3,"file":"html-builder.d.ts","sourceRoot":"","sources":["../../src/renderer/html-builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAU5D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,aAAa,CAcf;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CAC3C,GACA,OAAO,CAAC,WAAW,CAAC,CA8EtB"}
@@ -1,4 +1,4 @@
1
- import { mkdir, writeFile, readFile, access, rm } from "node:fs/promises";
1
+ import { mkdir, writeFile, readFile, rm } from "node:fs/promises";
2
2
  import { resolve, dirname } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { build as viteBuild } from "vite";
@@ -8,30 +8,9 @@ import { renderPage } from "./static-renderer.js";
8
8
  import { withActivePage } from "../core/navigation.js";
9
9
  import { isAbsoluteHttpUrl, toAbsoluteUrl, toPublicPath } from "../site-url.js";
10
10
  import { escapeHtml as escapeXml } from "../utils/html.js";
11
+ import { resolveThemeAssets } from "../themes/assets.js";
11
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
13
  const projectRoot = resolve(__dirname, "../..");
13
- async function exists(path) {
14
- try {
15
- await access(path);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- /**
23
- * Resolve client entry and CSS paths. Works from both source tree (dev) and
24
- * published npm package (dist-only) by checking which paths exist.
25
- */
26
- async function resolveAssetPaths() {
27
- const srcClient = resolve(projectRoot, "src/client/index.ts");
28
- const distClient = resolve(projectRoot, "dist/client/index.js");
29
- const srcCss = resolve(projectRoot, "src/themes/default/sourcey.css");
30
- const distCss = resolve(projectRoot, "dist/themes/default/sourcey.css");
31
- const clientEntry = (await exists(srcClient)) ? srcClient : distClient;
32
- const sourceyCssPath = (await exists(srcCss)) ? srcCss : distCss;
33
- return { clientEntry, sourceyCssPath };
34
- }
35
14
  export function createRenderOptions(page, site, options) {
36
15
  const assetBase = options?.assetBase ?? "";
37
16
  const pagePublicPath = toPublicPath(page.outputPath, site.baseUrl, site.prettyUrls);
@@ -76,7 +55,7 @@ export async function buildSite(pages, navigation, outputDir, site, options) {
76
55
  const html = renderPage(first.spec, renderOptions, activeNav, first.currentPage, site);
77
56
  await writeFile(resolve(resolvedDir, "index.html"), html, "utf-8");
78
57
  }
79
- await buildAssets(resolvedDir);
58
+ await buildAssets(resolvedDir, site.theme.name);
80
59
  if (options?.searchIndex) {
81
60
  await writeFile(resolve(resolvedDir, "search-index.json"), options.searchIndex, "utf-8");
82
61
  }
@@ -136,8 +115,8 @@ function resolveRenderHref(pathOrUrl, assetBase, site) {
136
115
  * Build client JS + Tailwind CSS via Vite.
137
116
  * Same plugins as the dev server — preact() + tailwindcss().
138
117
  */
139
- async function buildAssets(outputDir) {
140
- const { clientEntry, sourceyCssPath } = await resolveAssetPaths();
118
+ async function buildAssets(outputDir, theme) {
119
+ const { clientEntry, sourceyCssPaths } = resolveThemeAssets(theme);
141
120
  await viteBuild({
142
121
  root: projectRoot,
143
122
  logLevel: "silent",
@@ -162,7 +141,7 @@ async function buildAssets(outputDir) {
162
141
  },
163
142
  });
164
143
  // Append component CSS
165
- const componentCSS = await readFile(sourceyCssPath, "utf-8");
144
+ const componentCSS = (await Promise.all(sourceyCssPaths.map((path) => readFile(path, "utf-8")))).join("\n");
166
145
  const builtCSS = await readFile(resolve(outputDir, "sourcey.css"), "utf-8").catch(() => "");
167
146
  await writeFile(resolve(outputDir, "sourcey.css"), builtCSS + "\n" + componentCSS, "utf-8");
168
147
  }
@@ -0,0 +1,6 @@
1
+ import type { PrettyUrls } from "../site-url.js";
2
+ export declare function outputPathFromRequestPath(pathname: string, baseUrl: string): string;
3
+ export declare function outputPathCandidatesForRequest(pathname: string, baseUrl: string, prettyUrls: PrettyUrls): string[];
4
+ export declare function requestPathMatchesBase(pathname: string, baseUrl: string): boolean;
5
+ export declare function contentTypeForPath(path: string): string;
6
+ //# sourceMappingURL=static-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-files.d.ts","sourceRoot":"","sources":["../../src/renderer/static-files.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAcnF;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,UAAU,GACrB,MAAM,EAAE,CAYV;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0CvD"}
@@ -0,0 +1,78 @@
1
+ import { extname } from "node:path";
2
+ import { normalizeBaseUrl, stripBaseUrl } from "../site-url.js";
3
+ export function outputPathFromRequestPath(pathname, baseUrl) {
4
+ let path = pathname;
5
+ const normalizedBase = normalizeBaseUrl(baseUrl);
6
+ if (normalizedBase && path.startsWith(normalizedBase)) {
7
+ path = path.slice(normalizedBase.length);
8
+ }
9
+ try {
10
+ path = decodeURIComponent(path);
11
+ }
12
+ catch {
13
+ // Keep the raw path; a malformed escape cannot match generated output.
14
+ }
15
+ return path.replace(/^\/+/, "");
16
+ }
17
+ export function outputPathCandidatesForRequest(pathname, baseUrl, prettyUrls) {
18
+ const requestPath = stripBaseUrl(pathname, baseUrl);
19
+ const clean = outputPathFromRequestPath(requestPath, "");
20
+ if (!clean)
21
+ return ["index.html"];
22
+ if (extname(clean))
23
+ return [clean];
24
+ const candidates = prettyUrls === "strip"
25
+ ? [`${clean}.html`, `${clean}/index.html`]
26
+ : [`${clean}/index.html`, `${clean}.html`];
27
+ return [...candidates, clean];
28
+ }
29
+ export function requestPathMatchesBase(pathname, baseUrl) {
30
+ const normalizedBase = normalizeBaseUrl(baseUrl);
31
+ if (!normalizedBase)
32
+ return true;
33
+ const bareBase = normalizedBase.slice(0, -1);
34
+ return (pathname === bareBase || pathname === normalizedBase || pathname.startsWith(normalizedBase));
35
+ }
36
+ export function contentTypeForPath(path) {
37
+ switch (extname(path).toLowerCase()) {
38
+ case ".html":
39
+ return "text/html; charset=utf-8";
40
+ case ".css":
41
+ return "text/css; charset=utf-8";
42
+ case ".js":
43
+ case ".mjs":
44
+ return "text/javascript; charset=utf-8";
45
+ case ".json":
46
+ return "application/json; charset=utf-8";
47
+ case ".svg":
48
+ return "image/svg+xml";
49
+ case ".png":
50
+ return "image/png";
51
+ case ".jpg":
52
+ case ".jpeg":
53
+ return "image/jpeg";
54
+ case ".gif":
55
+ return "image/gif";
56
+ case ".webp":
57
+ return "image/webp";
58
+ case ".avif":
59
+ return "image/avif";
60
+ case ".ico":
61
+ return "image/x-icon";
62
+ case ".txt":
63
+ case ".xml":
64
+ return "text/plain; charset=utf-8";
65
+ case ".pdf":
66
+ return "application/pdf";
67
+ case ".woff":
68
+ return "font/woff";
69
+ case ".woff2":
70
+ return "font/woff2";
71
+ case ".mp4":
72
+ return "video/mp4";
73
+ case ".webm":
74
+ return "video/webm";
75
+ default:
76
+ return "application/octet-stream";
77
+ }
78
+ }
package/dist/site.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { ResolvedConfig } from "./config.js";
2
+ import type { ChangelogDiagnostic } from "./core/types.js";
3
+ import type { GodocLoaderDiagnostic } from "./core/godoc-loader.js";
4
+ import type { RustdocLoaderDiagnostic } from "./core/rustdoc-loader.js";
5
+ import type { SiteNavigation } from "./core/navigation.js";
6
+ import type { SiteConfig } from "./renderer/context.js";
7
+ import type { SitePage } from "./renderer/html-builder.js";
8
+ export interface SourceySiteOptions {
9
+ configDir?: string;
10
+ config?: ResolvedConfig;
11
+ outputDir?: string;
12
+ strictChangelog?: boolean;
13
+ generateOgImages?: boolean;
14
+ }
15
+ export interface SourceySiteArtifacts {
16
+ config: ResolvedConfig;
17
+ outputDir: string;
18
+ site: SiteConfig;
19
+ navigation: SiteNavigation;
20
+ pages: SitePage[];
21
+ pageCount: number;
22
+ searchIndex: string;
23
+ llmsTxt: string;
24
+ llmsFullTxt: string;
25
+ extraFiles: Map<string, string | Buffer>;
26
+ ogImages: Map<string, Buffer>;
27
+ changelogDiagnostics: ChangelogDiagnostic[];
28
+ godocDiagnostics: GodocLoaderDiagnostic[];
29
+ rustdocDiagnostics: RustdocLoaderDiagnostic[];
30
+ specsBySlug: Map<string, import("./core/types.js").NormalizedSpec>;
31
+ }
32
+ export interface SourceySiteWriteOptions {
33
+ embeddable?: boolean;
34
+ }
35
+ export declare function buildSourceySite(options?: SourceySiteOptions): Promise<SourceySiteArtifacts>;
36
+ export declare function writeSourceySite(site: SourceySiteArtifacts, options?: SourceySiteWriteOptions): Promise<void>;
37
+ export declare function collectSourceyWatchPaths(config: ResolvedConfig, configPath?: string): string[];
38
+ //# sourceMappingURL=site.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,KAAK,EAAE,mBAAmB,EAA8B,MAAM,iBAAiB,CAAC;AACvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAW3D,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,cAAc,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACzC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;IAC1C,kBAAkB,EAAE,uBAAuB,EAAE,CAAC;IAC9C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,iBAAiB,EAAE,cAAc,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAiD/B;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,cAAc,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,EAAE,CA0BV"}
package/dist/site.js ADDED
@@ -0,0 +1,159 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { extname, resolve } from "node:path";
3
+ import { loadConfig } from "./config.js";
4
+ import { buildSiteNavigation } from "./core/navigation.js";
5
+ import { buildSearchIndex } from "./core/search-indexer.js";
6
+ import { buildSite as buildSiteHtml } from "./renderer/html-builder.js";
7
+ import { generateLlmsFullTxt, generateLlmsTxt } from "./renderer/llms.js";
8
+ import { assembleSite, buildSiteConfig, collectDocsPagesByTab, enforceChangelogDiagnostics, enforceGodocDiagnostics, enforceRustdocDiagnostics, } from "./site-assembly.js";
9
+ export async function buildSourceySite(options = {}) {
10
+ const outputDir = resolve(options.outputDir ?? "dist");
11
+ const config = options.config ?? (await loadConfig(options.configDir));
12
+ const assembled = await assembleSite(config);
13
+ const site = await buildSiteConfig(config);
14
+ const pages = Array.from(assembled.pageMap.values());
15
+ const navigation = buildSiteNavigation(assembled.siteTabs);
16
+ enforceChangelogDiagnostics(assembled.changelogDiagnostics, options.strictChangelog);
17
+ enforceGodocDiagnostics(assembled.godocDiagnostics);
18
+ enforceRustdocDiagnostics(assembled.rustdocDiagnostics);
19
+ const docsPagesByTab = collectDocsPagesByTab(assembled.pageMap, config.tabs);
20
+ const searchIndex = buildSearchIndex(assembled.specsBySlug, docsPagesByTab, navigation, config.baseUrl || "/", config.search.featured, config.prettyUrls);
21
+ const llmsTxt = generateLlmsTxt(pages, navigation, site);
22
+ const llmsFullTxt = generateLlmsFullTxt(pages, navigation, site);
23
+ const extraFiles = new Map(assembled.extraFiles);
24
+ const ogImages = new Map();
25
+ if (options.generateOgImages !== false) {
26
+ await attachOgImages(pages, config, site, extraFiles, ogImages);
27
+ }
28
+ return {
29
+ config,
30
+ outputDir,
31
+ site,
32
+ navigation,
33
+ pages,
34
+ pageCount: pages.length,
35
+ searchIndex,
36
+ llmsTxt,
37
+ llmsFullTxt,
38
+ extraFiles,
39
+ ogImages,
40
+ changelogDiagnostics: assembled.changelogDiagnostics,
41
+ godocDiagnostics: assembled.godocDiagnostics,
42
+ rustdocDiagnostics: assembled.rustdocDiagnostics,
43
+ specsBySlug: assembled.specsBySlug,
44
+ };
45
+ }
46
+ export async function writeSourceySite(site, options = {}) {
47
+ await buildSiteHtml(site.pages, site.navigation, site.outputDir, site.site, {
48
+ searchIndex: site.searchIndex,
49
+ llmsTxt: site.llmsTxt,
50
+ llmsFullTxt: site.llmsFullTxt,
51
+ embeddable: options.embeddable,
52
+ ogImages: site.ogImages,
53
+ extraFiles: site.extraFiles,
54
+ });
55
+ }
56
+ export function collectSourceyWatchPaths(config, configPath) {
57
+ const paths = new Set();
58
+ if (configPath)
59
+ paths.add(resolve(configPath));
60
+ for (const tab of config.tabs) {
61
+ for (const watchPath of tab.source.watchPaths ?? []) {
62
+ paths.add(resolve(watchPath));
63
+ }
64
+ if (tab.source.kind === "rustdoc") {
65
+ paths.add(resolve(tab.source.config.manifest));
66
+ if (tab.source.config.snapshot) {
67
+ paths.add(resolve(tab.source.config.snapshot));
68
+ }
69
+ }
70
+ if (tab.source.kind === "markdown") {
71
+ for (const group of tab.source.groups) {
72
+ for (const page of group.pages) {
73
+ paths.add(resolve(page.file));
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return Array.from(paths);
79
+ }
80
+ async function attachOgImages(pages, config, site, extraFiles, ogImages) {
81
+ if (config.ogImage) {
82
+ const staticOg = config.ogImage;
83
+ if (staticOg.startsWith("http://") ||
84
+ staticOg.startsWith("https://") ||
85
+ staticOg.startsWith("data:")) {
86
+ for (const page of pages) {
87
+ page.ogImagePath = staticOg;
88
+ }
89
+ }
90
+ else {
91
+ const ogPath = `_og/static${extname(staticOg) || ".png"}`;
92
+ extraFiles.set(ogPath, await readFile(staticOg));
93
+ for (const page of pages) {
94
+ page.ogImagePath = ogPath;
95
+ }
96
+ }
97
+ return;
98
+ }
99
+ const { generateOgImage } = await import("./og/generate-og-image.js");
100
+ const concurrency = 8;
101
+ for (let i = 0; i < pages.length; i += concurrency) {
102
+ const batch = pages.slice(i, i + concurrency);
103
+ await Promise.all(batch.map(async (page) => {
104
+ const ogMeta = describePageForOg(page, config.name || "API", config.changelog.ogImages);
105
+ if (!ogMeta)
106
+ return;
107
+ try {
108
+ const ogPath = `_og/${page.outputPath.replace(/\.html$/, ".png")}`;
109
+ const png = await generateOgImage({
110
+ title: ogMeta.title,
111
+ description: ogMeta.description,
112
+ siteName: config.name,
113
+ theme: config.theme,
114
+ logo: site.logo?.light,
115
+ });
116
+ page.ogImagePath = ogPath;
117
+ ogImages.set(ogPath, png);
118
+ }
119
+ catch (error) {
120
+ const message = error instanceof Error ? error.message : String(error);
121
+ console.warn(`Sourcey: skipping OG image for ${page.outputPath}: ${message}`);
122
+ }
123
+ }));
124
+ }
125
+ }
126
+ function describePageForOg(page, defaultSiteName, changelogPermalinkOg) {
127
+ if (page.currentPage.kind === "markdown") {
128
+ return {
129
+ title: page.currentPage.markdown.title,
130
+ description: page.currentPage.markdown.description || undefined,
131
+ };
132
+ }
133
+ if (page.currentPage.kind === "changelog") {
134
+ const changelog = page.currentPage.changelog;
135
+ if (changelog.permalinkVersionId) {
136
+ if (!changelogPermalinkOg)
137
+ return null;
138
+ const version = changelog.changelog.versions.find((candidate) => candidate.id === changelog.permalinkVersionId);
139
+ if (!version)
140
+ return null;
141
+ return {
142
+ title: `${version.version ?? "Unreleased"} - ${changelog.title}`,
143
+ description: version.summary || summarizeVersion(version),
144
+ };
145
+ }
146
+ return {
147
+ title: changelog.title,
148
+ description: changelog.description || changelog.changelog.description,
149
+ };
150
+ }
151
+ return { title: `${defaultSiteName} Reference` };
152
+ }
153
+ function summarizeVersion(version) {
154
+ if (version.summary)
155
+ return version.summary;
156
+ const texts = version.sections.flatMap((section) => section.entries.map((entry) => entry.text));
157
+ const summary = texts.slice(0, 3).join(" ");
158
+ return summary || undefined;
159
+ }
@@ -0,0 +1,8 @@
1
+ import { type ThemeName } from "./registry.js";
2
+ /** The same asset selection is used by development and packaged static builds. */
3
+ export declare function resolveThemeAssets(name: ThemeName): {
4
+ clientEntry: string;
5
+ foundationCss: string;
6
+ sourceyCssPaths: string[];
7
+ };
8
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/themes/assets.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEjE,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS;;;;EAUjD"}
@@ -0,0 +1,16 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { themeDefinitions } from "./registry.js";
5
+ /** The same asset selection is used by development and packaged static builds. */
6
+ export function resolveThemeAssets(name) {
7
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
8
+ const source = existsSync(resolve(root, "src/client/index.ts"));
9
+ const tree = source ? "src" : "dist";
10
+ const theme = themeDefinitions[name];
11
+ return {
12
+ clientEntry: resolve(root, tree, `client/${theme.client}.${source ? "ts" : "js"}`),
13
+ foundationCss: resolve(root, tree, "themes/default/main.css"),
14
+ sourceyCssPaths: theme.css.map((css) => resolve(root, tree, "themes", css)),
15
+ };
16
+ }