sourcey 3.5.10 → 3.6.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 (100) hide show
  1. package/README.md +24 -97
  2. package/dist/adapters/index.d.ts +12 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +130 -0
  5. package/dist/adapters/mkdocs.d.ts +5 -0
  6. package/dist/adapters/mkdocs.d.ts.map +1 -0
  7. package/dist/adapters/mkdocs.js +367 -0
  8. package/dist/adapters/shared.d.ts +12 -0
  9. package/dist/adapters/shared.d.ts.map +1 -0
  10. package/dist/adapters/shared.js +134 -0
  11. package/dist/adapters/types.d.ts +74 -0
  12. package/dist/adapters/types.d.ts.map +1 -0
  13. package/dist/adapters/types.js +1 -0
  14. package/dist/cli.js +28 -34
  15. package/dist/client/search.js +213 -82
  16. package/dist/client/tabs.js +23 -0
  17. package/dist/components/layout/Head.js +17 -4
  18. package/dist/components/layout/TableOfContents.d.ts.map +1 -1
  19. package/dist/components/layout/TableOfContents.js +4 -2
  20. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  21. package/dist/components/openapi/Introduction.js +16 -1
  22. package/dist/components/openapi/Security.d.ts.map +1 -1
  23. package/dist/components/openapi/Security.js +18 -2
  24. package/dist/config.d.ts +101 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +95 -143
  27. package/dist/core/api-rendering.d.ts +47 -0
  28. package/dist/core/api-rendering.d.ts.map +1 -0
  29. package/dist/core/api-rendering.js +76 -0
  30. package/dist/core/doxygen-loader.d.ts.map +1 -1
  31. package/dist/core/doxygen-loader.js +36 -17
  32. package/dist/core/godoc-loader.d.ts.map +1 -1
  33. package/dist/core/godoc-loader.js +17 -6
  34. package/dist/core/markdown-loader.d.ts +14 -0
  35. package/dist/core/markdown-loader.d.ts.map +1 -1
  36. package/dist/core/markdown-loader.js +70 -26
  37. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  38. package/dist/core/mcp-normalizer.js +48 -34
  39. package/dist/core/navigation.d.ts.map +1 -1
  40. package/dist/core/navigation.js +8 -7
  41. package/dist/core/parser.d.ts.map +1 -1
  42. package/dist/core/parser.js +75 -4
  43. package/dist/core/rustdoc-introspector.d.ts +29 -0
  44. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  45. package/dist/core/rustdoc-introspector.js +236 -0
  46. package/dist/core/rustdoc-loader.d.ts +25 -0
  47. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  48. package/dist/core/rustdoc-loader.js +469 -0
  49. package/dist/core/rustdoc-render.d.ts +76 -0
  50. package/dist/core/rustdoc-render.d.ts.map +1 -0
  51. package/dist/core/rustdoc-render.js +785 -0
  52. package/dist/core/rustdoc-types.d.ts +314 -0
  53. package/dist/core/rustdoc-types.d.ts.map +1 -0
  54. package/dist/core/rustdoc-types.js +18 -0
  55. package/dist/core/search-indexer.d.ts +10 -0
  56. package/dist/core/search-indexer.d.ts.map +1 -1
  57. package/dist/core/search-indexer.js +6 -1
  58. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  59. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  60. package/dist/core/sourcey-rustdoc/README.md +59 -0
  61. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  62. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  63. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  64. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  65. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  66. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  67. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  68. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  69. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  70. package/dist/dev-server.d.ts.map +1 -1
  71. package/dist/dev-server.js +109 -33
  72. package/dist/index.d.ts +4 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +26 -14
  75. package/dist/init.d.ts.map +1 -1
  76. package/dist/init.js +21 -16
  77. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  78. package/dist/renderer/changelog-feed.js +27 -19
  79. package/dist/renderer/html-builder.d.ts.map +1 -1
  80. package/dist/renderer/html-builder.js +4 -3
  81. package/dist/renderer/llms.js +57 -2
  82. package/dist/site-assembly.d.ts +5 -0
  83. package/dist/site-assembly.d.ts.map +1 -1
  84. package/dist/site-assembly.js +161 -25
  85. package/dist/themes/default/sourcey.css +300 -0
  86. package/dist/utils/code-samples.d.ts +1 -0
  87. package/dist/utils/code-samples.d.ts.map +1 -1
  88. package/dist/utils/code-samples.js +30 -22
  89. package/dist/utils/html.d.ts +10 -0
  90. package/dist/utils/html.d.ts.map +1 -0
  91. package/dist/utils/html.js +152 -0
  92. package/dist/utils/http.d.ts +0 -2
  93. package/dist/utils/http.d.ts.map +1 -1
  94. package/dist/utils/http.js +19 -34
  95. package/dist/utils/markdown.d.ts.map +1 -1
  96. package/dist/utils/markdown.js +115 -39
  97. package/dist/vite-plugin.d.ts +2 -0
  98. package/dist/vite-plugin.d.ts.map +1 -1
  99. package/dist/vite-plugin.js +71 -1
  100. package/package.json +11 -5
@@ -73,31 +73,32 @@ export function buildNavFromSpec(spec, tabSlug, prettyUrls = false) {
73
73
  * Groups come from the config; items come from loaded page data.
74
74
  */
75
75
  export function buildNavFromPages(tab, pagesByPath, prettyUrls = false) {
76
- const groups = [];
77
- if (tab.groups) {
78
- for (const group of tab.groups) {
76
+ const navGroups = [];
77
+ const sourceGroups = tab.source.kind === "markdown" ? tab.source.groups : tab.groups;
78
+ if (sourceGroups) {
79
+ for (const group of sourceGroups) {
79
80
  const items = [];
80
81
  for (const rp of group.pages) {
81
82
  const page = pagesByPath.get(rp.slug);
82
83
  if (!page)
83
84
  continue;
84
85
  items.push({
85
- label: page.title,
86
+ label: rp.label ?? page.title,
86
87
  href: navHref(pageOutputPath(tab.slug, page.slug, prettyUrls), prettyUrls),
87
88
  id: page.slug,
88
89
  });
89
90
  }
90
- groups.push({ label: group.label, items });
91
+ navGroups.push({ label: group.label, items });
91
92
  }
92
93
  }
93
- const firstItem = groups[0]?.items[0];
94
+ const firstItem = navGroups[0]?.items[0];
94
95
  const href = firstItem?.href ?? (tab.slug ? `${tab.slug}/` : "");
95
96
  return {
96
97
  label: tab.label,
97
98
  slug: tab.slug,
98
99
  href,
99
100
  kind: "docs",
100
- groups,
101
+ groups: navGroups,
101
102
  };
102
103
  }
103
104
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBvE"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BvE"}
@@ -1,4 +1,6 @@
1
1
  import { $RefParser } from "@apidevtools/json-schema-ref-parser";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
2
4
  import { fileURLToPath, pathToFileURL } from "node:url";
3
5
  /**
4
6
  * Dereference a loaded spec document.
@@ -10,7 +12,21 @@ import { fileURLToPath, pathToFileURL } from "node:url";
10
12
  export async function parseSpec(loaded) {
11
13
  try {
12
14
  const parser = new $RefParser();
13
- const dereferenced = await parser.dereference(resolveDocumentUri(loaded), structuredClone(loaded.raw), { dereference: { circular: "ignore" } });
15
+ const dereferenced = await parser.dereference(resolveDocumentUri(loaded), structuredClone(loaded.raw), {
16
+ dereference: {
17
+ circular: "ignore",
18
+ onDereference(ref, value) {
19
+ const refName = refNameFromRef(ref);
20
+ if (refName && isRecord(value) && typeof value["x-ref-name"] !== "string") {
21
+ value["x-ref-name"] = refName;
22
+ }
23
+ },
24
+ },
25
+ resolve: {
26
+ http: false,
27
+ file: createConfinedFileResolver(loaded),
28
+ },
29
+ });
14
30
  return {
15
31
  document: dereferenced,
16
32
  source: loaded.source,
@@ -21,6 +37,63 @@ export async function parseSpec(loaded) {
21
37
  throw new Error(`Failed to parse OpenAPI spec: ${message}`);
22
38
  }
23
39
  }
40
+ function createConfinedFileResolver(loaded) {
41
+ const root = localSpecRoot(loaded.source);
42
+ if (!root)
43
+ return false;
44
+ return {
45
+ order: 1,
46
+ canRead(file) {
47
+ return filePathFromRef(file.url) !== null;
48
+ },
49
+ async read(file) {
50
+ const filePath = filePathFromRef(file.url);
51
+ if (!filePath)
52
+ throw new Error(`Unsupported external $ref URI: ${file.url}`);
53
+ const resolved = resolve(filePath);
54
+ if (!isWithinRoot(root, resolved)) {
55
+ throw new Error(`External $ref resolves outside the spec directory: ${file.url}`);
56
+ }
57
+ return readFile(resolved);
58
+ },
59
+ };
60
+ }
61
+ function localSpecRoot(source) {
62
+ if (source.startsWith("http://") || source.startsWith("https://"))
63
+ return null;
64
+ return dirname(resolve(source));
65
+ }
66
+ function filePathFromRef(refUrl) {
67
+ const withoutHash = refUrl.split("#", 1)[0];
68
+ if (!withoutHash)
69
+ return null;
70
+ if (/^file:/i.test(withoutHash)) {
71
+ try {
72
+ return fileURLToPath(withoutHash);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ if (/^[a-z][a-z\d+.-]*:/i.test(withoutHash))
79
+ return null;
80
+ return resolve(withoutHash);
81
+ }
82
+ function isWithinRoot(root, target) {
83
+ const rel = relative(root, target);
84
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
85
+ }
86
+ function refNameFromRef(ref) {
87
+ const pointer = ref.includes("#") ? ref.slice(ref.indexOf("#") + 1) : ref;
88
+ const segments = pointer.split("/").filter(Boolean);
89
+ const raw = segments.at(-1);
90
+ if (!raw)
91
+ return null;
92
+ return raw.replace(/~1/g, "/").replace(/~0/g, "~");
93
+ }
94
+ function isRecord(value) {
95
+ return typeof value === "object" && value !== null && !Array.isArray(value);
96
+ }
24
97
  function resolveDocumentUri(loaded) {
25
98
  const self = typeof loaded.raw["$self"] === "string" ? loaded.raw["$self"] : undefined;
26
99
  if (!self)
@@ -30,9 +103,7 @@ function resolveDocumentUri(loaded) {
30
103
  return new URL(self, loaded.source).toString();
31
104
  }
32
105
  const resolved = new URL(self, pathToFileURL(loaded.source));
33
- return resolved.protocol === "file:"
34
- ? fileURLToPath(resolved)
35
- : resolved.toString();
106
+ return resolved.protocol === "file:" ? fileURLToPath(resolved) : resolved.toString();
36
107
  }
37
108
  catch {
38
109
  return loaded.source;
@@ -0,0 +1,29 @@
1
+ import type { ResolvedRustdocConfig } from "../config.js";
2
+ import type { RustdocDiagnostic, RustdocSpec } from "./rustdoc-types.js";
3
+ export declare class RustdocIntrospectorError extends Error {
4
+ readonly code: string;
5
+ readonly cause?: Error | undefined;
6
+ constructor(code: string, message: string, cause?: Error);
7
+ }
8
+ export interface IntrospectOptions {
9
+ config: ResolvedRustdocConfig;
10
+ /** Override cargo binary; defaults to "cargo" on PATH. */
11
+ cargoBinary?: string;
12
+ /** Override rustup binary; defaults to "rustup" on PATH. */
13
+ rustupBinary?: string;
14
+ }
15
+ export interface IntrospectResult {
16
+ spec: RustdocSpec;
17
+ diagnostics: RustdocDiagnostic[];
18
+ /** Mode actually used (after auto resolution). */
19
+ modeUsed: "live" | "snapshot";
20
+ }
21
+ /**
22
+ * Run the bundled Rust helper or load a committed snapshot, return a
23
+ * parsed RustdocSpec plus any diagnostics. Mode is resolved per the
24
+ * spec's auto fallback policy.
25
+ */
26
+ export declare function runIntrospector(opts: IntrospectOptions): Promise<IntrospectResult>;
27
+ /** Resolved helper entry paths, exported for tests that pin runtime. */
28
+ export declare const __rustdocHelperCandidatesForTests: string[];
29
+ //# sourceMappingURL=rustdoc-introspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rustdoc-introspector.d.ts","sourceRoot":"","sources":["../../src/core/rustdoc-introspector.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAQzE,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;gBACvB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAMzD;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAexF;AAwPD,wEAAwE;AACxE,eAAO,MAAM,iCAAiC,UAG7C,CAAC"}
@@ -0,0 +1,236 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { gunzipSync } from "node:zlib";
6
+ import { RUSTDOC_DIAGNOSTIC_CODES, SPEC_VERSION } from "./rustdoc-types.js";
7
+ const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
8
+ const PACKAGED_HELPER_DIR = join(MODULE_DIR, "sourcey-rustdoc");
9
+ const DEV_HELPER_DIR = resolve(MODULE_DIR, "../../rust/sourcey-rustdoc");
10
+ const HELPER_MANIFEST = "Cargo.toml";
11
+ const HELPER_ENTRY = "src/main.rs";
12
+ export class RustdocIntrospectorError extends Error {
13
+ code;
14
+ cause;
15
+ constructor(code, message, cause) {
16
+ super(message);
17
+ this.name = "RustdocIntrospectorError";
18
+ this.code = code;
19
+ this.cause = cause;
20
+ }
21
+ }
22
+ /**
23
+ * Run the bundled Rust helper or load a committed snapshot, return a
24
+ * parsed RustdocSpec plus any diagnostics. Mode is resolved per the
25
+ * spec's auto fallback policy.
26
+ */
27
+ export async function runIntrospector(opts) {
28
+ const cfg = opts.config;
29
+ const mode = await resolveMode(cfg, opts);
30
+ if (mode === "snapshot") {
31
+ const result = await loadSnapshot(cfg);
32
+ if (cfg.mode === "auto") {
33
+ result.diagnostics.unshift({
34
+ severity: "info",
35
+ code: RUSTDOC_DIAGNOSTIC_CODES.AUTO_FELL_BACK_TO_SNAPSHOT,
36
+ message: `Rustdoc auto mode did not find "${cfg.toolchain}" and used the configured snapshot.`,
37
+ });
38
+ }
39
+ return result;
40
+ }
41
+ return runLive(cfg, opts);
42
+ }
43
+ async function resolveMode(cfg, opts) {
44
+ if (cfg.mode === "live")
45
+ return "live";
46
+ if (cfg.mode === "snapshot") {
47
+ if (!cfg.snapshot) {
48
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.NO_VIABLE_MODE, "Rustdoc adapter mode is 'snapshot' but no snapshot path was configured.");
49
+ }
50
+ return "snapshot";
51
+ }
52
+ // auto
53
+ const nightlyAvailable = await probeNightly(opts.rustupBinary ?? "rustup", cfg.toolchain);
54
+ if (nightlyAvailable)
55
+ return "live";
56
+ if (cfg.snapshot)
57
+ return "snapshot";
58
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.NO_VIABLE_MODE, `Rustdoc adapter "auto" mode found neither the "${cfg.toolchain}" toolchain nor a configured snapshot. ` +
59
+ `Either run \`rustup toolchain install ${cfg.toolchain}\` or set { mode: "snapshot", snapshot: "./snapshots/rustdoc.json" }.`);
60
+ }
61
+ async function probeNightly(rustupBinary, toolchain) {
62
+ try {
63
+ const result = spawnSync(rustupBinary, ["toolchain", "list"], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ });
67
+ if (result.status !== 0 || !result.stdout)
68
+ return false;
69
+ return result.stdout.split("\n").some((line) => line.includes(toolchain));
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ async function loadSnapshot(cfg) {
76
+ const snapshotPath = cfg.snapshot;
77
+ if (!snapshotPath) {
78
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.NO_VIABLE_MODE, "Snapshot mode requires `snapshot` to point at a committed rustdoc.json.");
79
+ }
80
+ let raw;
81
+ try {
82
+ if (/\.gz$/i.test(snapshotPath)) {
83
+ const buf = await readFile(snapshotPath);
84
+ raw = gunzipSync(buf).toString("utf8");
85
+ }
86
+ else {
87
+ raw = await readFile(snapshotPath, "utf8");
88
+ }
89
+ }
90
+ catch (err) {
91
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.INVALID_SNAPSHOT_SCHEMA, `Could not read snapshot at ${snapshotPath}: ${err.message}`, err);
92
+ }
93
+ let spec;
94
+ try {
95
+ spec = JSON.parse(raw);
96
+ }
97
+ catch (err) {
98
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.INVALID_SNAPSHOT_SCHEMA, `Snapshot at ${snapshotPath} is not valid JSON: ${err.message}`, err);
99
+ }
100
+ validateSpec(spec, snapshotPath);
101
+ return {
102
+ spec,
103
+ diagnostics: collectDiagnostics(spec),
104
+ modeUsed: "snapshot",
105
+ };
106
+ }
107
+ async function runLive(cfg, opts) {
108
+ const helperDir = await resolveHelperDir();
109
+ const cargoBinary = opts.cargoBinary ?? "cargo";
110
+ const args = buildArgs(cfg, helperDir);
111
+ const { stdout, stderr, code } = await runCargo(cargoBinary, args, helperDir);
112
+ if (code !== 0) {
113
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_FAILED, `sourcey-rustdoc helper exited with code ${code}:\n${stderr.trim()}`);
114
+ }
115
+ let spec;
116
+ try {
117
+ spec = JSON.parse(stdout);
118
+ }
119
+ catch (err) {
120
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_FAILED, `Could not parse helper output as JSON: ${err.message}\n` +
121
+ `stdout (first 200 chars): ${stdout.slice(0, 200)}`, err);
122
+ }
123
+ validateSpec(spec, "live invocation");
124
+ return {
125
+ spec,
126
+ diagnostics: collectDiagnostics(spec),
127
+ modeUsed: "live",
128
+ };
129
+ }
130
+ function buildArgs(cfg, helperDir) {
131
+ void helperDir;
132
+ const args = [
133
+ "run",
134
+ "--release",
135
+ "--quiet",
136
+ "--",
137
+ "--manifest",
138
+ cfg.manifest,
139
+ "--toolchain",
140
+ cfg.toolchain,
141
+ "--output",
142
+ "-",
143
+ ];
144
+ for (const crateName of cfg.crates) {
145
+ args.push("--crate", crateName);
146
+ }
147
+ if (cfg.features.all) {
148
+ args.push("--all-features");
149
+ }
150
+ else {
151
+ if (cfg.features.default === false) {
152
+ args.push("--no-default-features");
153
+ }
154
+ for (const feature of cfg.features.list) {
155
+ args.push("--features", feature);
156
+ }
157
+ }
158
+ if (cfg.includePrivate)
159
+ args.push("--include-private");
160
+ if (cfg.includeHidden)
161
+ args.push("--include-hidden");
162
+ if (cfg.target)
163
+ args.push("--target", cfg.target);
164
+ return args;
165
+ }
166
+ async function resolveHelperDir() {
167
+ const candidates = [PACKAGED_HELPER_DIR, DEV_HELPER_DIR];
168
+ for (const candidate of candidates) {
169
+ try {
170
+ await access(join(candidate, HELPER_MANIFEST));
171
+ await access(join(candidate, HELPER_ENTRY));
172
+ return candidate;
173
+ }
174
+ catch {
175
+ // try next
176
+ }
177
+ }
178
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_MISSING, "Sourcey's Rust documentation helper is missing. Checked:\n" +
179
+ candidates.map((c) => `- ${join(c, HELPER_ENTRY)}`).join("\n") +
180
+ "\nReinstall sourcey or run `npm run build`.");
181
+ }
182
+ function runCargo(binary, args, cwd) {
183
+ return new Promise((resolvePromise, rejectPromise) => {
184
+ let child;
185
+ try {
186
+ child = spawn(binary, args, { cwd, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
187
+ }
188
+ catch (err) {
189
+ rejectPromise(new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_FAILED, `Could not launch cargo (${binary}). Install Rust from https://rustup.rs, or use mode: "snapshot" with a committed rustdoc.json.`, err));
190
+ return;
191
+ }
192
+ const stdoutChunks = [];
193
+ const stderrChunks = [];
194
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
195
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
196
+ child.on("error", (err) => {
197
+ if (err.code === "ENOENT") {
198
+ rejectPromise(new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_FAILED, `Could not launch cargo (${binary}). Install Rust from https://rustup.rs, or use mode: "snapshot" with a committed rustdoc.json.`, err));
199
+ return;
200
+ }
201
+ rejectPromise(new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.HELPER_FAILED, err.message, err));
202
+ });
203
+ child.on("close", (code) => {
204
+ resolvePromise({
205
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
206
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
207
+ code: code ?? -1,
208
+ });
209
+ });
210
+ });
211
+ }
212
+ function validateSpec(spec, source) {
213
+ if (spec.version !== SPEC_VERSION) {
214
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.INVALID_SNAPSHOT_SCHEMA, `Rustdoc snapshot from ${source} reports version ${spec.version}; this sourcey expects ${SPEC_VERSION}.`);
215
+ }
216
+ if (!Array.isArray(spec.crates)) {
217
+ throw new RustdocIntrospectorError(RUSTDOC_DIAGNOSTIC_CODES.INVALID_SNAPSHOT_SCHEMA, `Rustdoc snapshot from ${source} is missing the \`crates\` array.`);
218
+ }
219
+ }
220
+ function collectDiagnostics(spec) {
221
+ const out = [];
222
+ for (const d of spec.diagnostics ?? []) {
223
+ out.push(d);
224
+ }
225
+ for (const c of spec.crates) {
226
+ for (const d of c.diagnostics) {
227
+ out.push({ ...d, crate_name: d.crate_name ?? c.name });
228
+ }
229
+ }
230
+ return out;
231
+ }
232
+ /** Resolved helper entry paths, exported for tests that pin runtime. */
233
+ export const __rustdocHelperCandidatesForTests = [
234
+ join(PACKAGED_HELPER_DIR, HELPER_ENTRY),
235
+ join(DEV_HELPER_DIR, HELPER_ENTRY),
236
+ ];
@@ -0,0 +1,25 @@
1
+ import type { ResolvedRustdocConfig } from "../config.js";
2
+ import type { MarkdownPage } from "./markdown-loader.js";
3
+ import type { SiteTab } from "./navigation.js";
4
+ export interface RustdocLoaderDiagnostic {
5
+ severity: "error" | "warning" | "info";
6
+ code: string;
7
+ message: string;
8
+ crate?: string;
9
+ file?: string;
10
+ line?: number;
11
+ }
12
+ export interface RustdocLoaderResult {
13
+ pages: Map<string, MarkdownPage>;
14
+ navTab: SiteTab;
15
+ diagnostics: RustdocLoaderDiagnostic[];
16
+ }
17
+ export interface RustdocSourceLinkOptions {
18
+ repo?: string;
19
+ editBranch?: string;
20
+ editBasePath?: string;
21
+ }
22
+ /** Load a rustdoc tab from the configured manifest or snapshot. */
23
+ export declare function loadRustdocTab(config: ResolvedRustdocConfig, tabSlug: string, tabLabel: string, sourceLinks?: RustdocSourceLinkOptions): Promise<RustdocLoaderResult>;
24
+ export declare const RUST_SEARCH_CATEGORIES: readonly ["rust function", "rust method", "rust struct", "rust enum", "rust trait", "rust trait alias", "rust type alias", "rust constant", "rust static", "rust macro", "rust proc-macro", "rust associated type", "rust associated constant", "rust module", "rust doctest", "rust re-export", "rust variant", "rust struct field", "rust union", "rust impl", "rust primitive", "rust extern type"];
25
+ //# sourceMappingURL=rustdoc-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rustdoc-loader.d.ts","sourceRoot":"","sources":["../../src/core/rustdoc-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAgC,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAE,OAAO,EAA6B,MAAM,iBAAiB,CAAC;AAa1E,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,uBAAuB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mEAAmE;AACnE,wBAAsB,cAAc,CAClC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,wBAA6B,GACzC,OAAO,CAAC,mBAAmB,CAAC,CA0F9B;AA2ZD,eAAO,MAAM,sBAAsB,wYAuBzB,CAAC"}