sourcey 3.6.0 → 3.6.2

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 (86) hide show
  1. package/README.md +5 -2
  2. package/dist/adapters/index.d.ts +3 -2
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +35 -3
  5. package/dist/adapters/types.d.ts +8 -2
  6. package/dist/adapters/types.d.ts.map +1 -1
  7. package/dist/cli.js +28 -34
  8. package/dist/client/search.js +181 -119
  9. package/dist/client/tabs.js +23 -0
  10. package/dist/components/layout/Head.d.ts.map +1 -1
  11. package/dist/components/layout/Head.js +7 -6
  12. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  13. package/dist/components/openapi/Introduction.js +16 -1
  14. package/dist/components/openapi/Security.d.ts.map +1 -1
  15. package/dist/components/openapi/Security.js +18 -2
  16. package/dist/config.d.ts +70 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +23 -7
  19. package/dist/core/api-rendering.d.ts +47 -0
  20. package/dist/core/api-rendering.d.ts.map +1 -0
  21. package/dist/core/api-rendering.js +76 -0
  22. package/dist/core/doxygen-loader.d.ts +1 -1
  23. package/dist/core/doxygen-loader.d.ts.map +1 -1
  24. package/dist/core/doxygen-loader.js +37 -19
  25. package/dist/core/godoc-loader.d.ts.map +1 -1
  26. package/dist/core/godoc-loader.js +17 -6
  27. package/dist/core/markdown-loader.d.ts.map +1 -1
  28. package/dist/core/markdown-loader.js +8 -5
  29. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  30. package/dist/core/mcp-normalizer.js +48 -34
  31. package/dist/core/parser.d.ts.map +1 -1
  32. package/dist/core/parser.js +75 -4
  33. package/dist/core/rustdoc-introspector.d.ts +29 -0
  34. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  35. package/dist/core/rustdoc-introspector.js +236 -0
  36. package/dist/core/rustdoc-loader.d.ts +25 -0
  37. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  38. package/dist/core/rustdoc-loader.js +472 -0
  39. package/dist/core/rustdoc-render.d.ts +82 -0
  40. package/dist/core/rustdoc-render.d.ts.map +1 -0
  41. package/dist/core/rustdoc-render.js +808 -0
  42. package/dist/core/rustdoc-types.d.ts +314 -0
  43. package/dist/core/rustdoc-types.d.ts.map +1 -0
  44. package/dist/core/rustdoc-types.js +18 -0
  45. package/dist/core/search-indexer.js +1 -1
  46. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  47. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  48. package/dist/core/sourcey-rustdoc/README.md +59 -0
  49. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  50. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  51. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  52. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  53. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  54. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  55. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  56. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  57. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  58. package/dist/dev-server.d.ts.map +1 -1
  59. package/dist/dev-server.js +50 -1
  60. package/dist/index.d.ts +4 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +22 -13
  63. package/dist/init.js +2 -2
  64. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  65. package/dist/renderer/changelog-feed.js +27 -19
  66. package/dist/renderer/context.d.ts +1 -0
  67. package/dist/renderer/context.d.ts.map +1 -1
  68. package/dist/renderer/html-builder.d.ts.map +1 -1
  69. package/dist/renderer/html-builder.js +4 -3
  70. package/dist/renderer/llms.js +57 -2
  71. package/dist/site-assembly.d.ts +5 -0
  72. package/dist/site-assembly.d.ts.map +1 -1
  73. package/dist/site-assembly.js +57 -2
  74. package/dist/themes/default/sourcey.css +300 -0
  75. package/dist/utils/code-samples.d.ts +1 -0
  76. package/dist/utils/code-samples.d.ts.map +1 -1
  77. package/dist/utils/code-samples.js +30 -22
  78. package/dist/utils/html.d.ts +10 -0
  79. package/dist/utils/html.d.ts.map +1 -0
  80. package/dist/utils/html.js +152 -0
  81. package/dist/utils/http.d.ts +0 -2
  82. package/dist/utils/http.d.ts.map +1 -1
  83. package/dist/utils/http.js +19 -34
  84. package/dist/utils/markdown.d.ts.map +1 -1
  85. package/dist/utils/markdown.js +102 -37
  86. package/package.json +10 -5
@@ -9,12 +9,14 @@ import { generateExample } from "../utils/example-generator.js";
9
9
  // ── Public API ────────────────────────────────────────────────────────
10
10
  export function normalizeMcpSpec(spec) {
11
11
  const connectionInfo = {
12
- transport: spec.transport ? {
13
- type: spec.transport.type,
14
- command: spec.transport.command,
15
- args: spec.transport.args,
16
- url: spec.transport.url,
17
- } : undefined,
12
+ transport: spec.transport
13
+ ? {
14
+ type: spec.transport.type,
15
+ command: spec.transport.command,
16
+ args: spec.transport.args,
17
+ url: spec.transport.url,
18
+ }
19
+ : undefined,
18
20
  serverName: spec.server.name,
19
21
  mcpVersion: spec.mcpVersion,
20
22
  capabilities: spec.capabilities,
@@ -58,13 +60,14 @@ function toolToOperation(tool, connection) {
58
60
  const group = tool["x-sourcey-group"];
59
61
  const inputSchema = convertSchema(tool.inputSchema);
60
62
  const outputSchema = tool.outputSchema ? convertSchema(tool.outputSchema) : undefined;
61
- // Flat parameters from top-level inputSchema properties
62
- const parameters = flattenInputSchema(tool.inputSchema);
63
- // Only include requestBody for nested/complex schemas
64
- const requestBody = hasNestedProperties(tool.inputSchema) ? {
65
- required: true,
66
- content: { "application/json": { schema: inputSchema } },
67
- } : undefined;
63
+ const emitsRequestBody = hasNestedProperties(tool.inputSchema);
64
+ const parameters = emitsRequestBody ? [] : flattenInputSchema(tool.inputSchema);
65
+ const requestBody = emitsRequestBody
66
+ ? {
67
+ required: true,
68
+ content: { "application/json": { schema: inputSchema } },
69
+ }
70
+ : undefined;
68
71
  return {
69
72
  operationId: tool.name,
70
73
  summary: tool.title ?? tool.name,
@@ -80,12 +83,14 @@ function toolToOperation(tool, connection) {
80
83
  codeSamples: generateToolSamples(tool, inputSchema),
81
84
  mcpExtras: {
82
85
  type: "tool",
83
- annotations: tool.annotations ? {
84
- readOnlyHint: tool.annotations.readOnlyHint,
85
- destructiveHint: tool.annotations.destructiveHint,
86
- idempotentHint: tool.annotations.idempotentHint,
87
- openWorldHint: tool.annotations.openWorldHint,
88
- } : undefined,
86
+ annotations: tool.annotations
87
+ ? {
88
+ readOnlyHint: tool.annotations.readOnlyHint,
89
+ destructiveHint: tool.annotations.destructiveHint,
90
+ idempotentHint: tool.annotations.idempotentHint,
91
+ openWorldHint: tool.annotations.openWorldHint,
92
+ }
93
+ : undefined,
89
94
  outputSchema,
90
95
  connection,
91
96
  },
@@ -136,7 +141,7 @@ function resourceTemplateToOperation(template, connection) {
136
141
  // ── Prompt → Operation ────────────────────────────────────────────────
137
142
  function promptToOperation(prompt, connection) {
138
143
  const group = prompt["x-sourcey-group"];
139
- const parameters = (prompt.arguments ?? []).map(arg => ({
144
+ const parameters = (prompt.arguments ?? []).map((arg) => ({
140
145
  name: arg.name,
141
146
  in: "argument",
142
147
  description: arg.description,
@@ -189,18 +194,20 @@ function convertSchema(schema, name) {
189
194
  }
190
195
  }
191
196
  if (schema.additionalProperties !== undefined) {
192
- result.additionalProperties = typeof schema.additionalProperties === "boolean"
193
- ? schema.additionalProperties
194
- : convertSchema(schema.additionalProperties);
197
+ result.additionalProperties =
198
+ typeof schema.additionalProperties === "boolean"
199
+ ? schema.additionalProperties
200
+ : convertSchema(schema.additionalProperties);
195
201
  }
196
202
  if (schema.required)
197
203
  result.required = schema.required;
198
204
  // Array — handle JsonSchema.items being JsonSchema | JsonSchema[]
199
205
  if (schema.items) {
200
206
  if (Array.isArray(schema.items)) {
201
- result.items = schema.items.length === 1
202
- ? convertSchema(schema.items[0])
203
- : { oneOf: schema.items.map(s => convertSchema(s)) };
207
+ result.items =
208
+ schema.items.length === 1
209
+ ? convertSchema(schema.items[0])
210
+ : { oneOf: schema.items.map((s) => convertSchema(s)) };
204
211
  }
205
212
  else {
206
213
  result.items = convertSchema(schema.items);
@@ -265,7 +272,10 @@ function hasNestedProperties(schema) {
265
272
  return Object.values(schema.properties).some((prop) => {
266
273
  if (prop.type === "object" && prop.properties)
267
274
  return true;
268
- if (prop.type === "array" && prop.items && !Array.isArray(prop.items) && prop.items.type === "object")
275
+ if (prop.type === "array" &&
276
+ prop.items &&
277
+ !Array.isArray(prop.items) &&
278
+ prop.items.type === "object")
269
279
  return true;
270
280
  if (prop.allOf || prop.anyOf || prop.oneOf)
271
281
  return true;
@@ -276,7 +286,7 @@ function extractUriTemplateParams(uriTemplate) {
276
286
  const matches = uriTemplate.match(/\{([^}]+)\}/g);
277
287
  if (!matches)
278
288
  return [];
279
- return matches.map(m => ({
289
+ return matches.map((m) => ({
280
290
  name: m.slice(1, -1),
281
291
  in: "path",
282
292
  required: true,
@@ -289,11 +299,12 @@ function slugify(name) {
289
299
  }
290
300
  // ── Code sample generation ────────────────────────────────────────────
291
301
  function generateToolSamples(tool, inputSchema) {
292
- const example = inputSchema.properties
293
- ? generateExample(inputSchema)
294
- : {};
302
+ const example = inputSchema.properties ? generateExample(inputSchema) : {};
295
303
  const exampleJson = JSON.stringify(example, null, 2);
296
- const indented = exampleJson.split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
304
+ const indented = exampleJson
305
+ .split("\n")
306
+ .map((line, i) => (i === 0 ? line : " " + line))
307
+ .join("\n");
297
308
  return [
298
309
  {
299
310
  lang: "json",
@@ -345,10 +356,13 @@ function generateResourceSamples(resource) {
345
356
  ];
346
357
  }
347
358
  function generatePromptSamples(prompt) {
348
- const args = (prompt.arguments ?? []);
359
+ const args = prompt.arguments ?? [];
349
360
  const exampleArgs = Object.fromEntries(args.map((a) => [a.name, `<${a.name}>`]));
350
361
  const argsJson = JSON.stringify(exampleArgs, null, 2);
351
- const indented = argsJson.split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
362
+ const indented = argsJson
363
+ .split("\n")
364
+ .map((line, i) => (i === 0 ? line : " " + line))
365
+ .join("\n");
352
366
  const tsArgs = args.map((a) => ` ${a.name}: "<${a.name}>",`).join("\n");
353
367
  const pyArgs = args.map((a) => ` "${a.name}": "<${a.name}>",`).join("\n");
354
368
  return [
@@ -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;AA8ZD,eAAO,MAAM,sBAAsB,wYAuBzB,CAAC"}