skrypt-ai 0.6.1 → 0.8.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.
- package/dist/audit/doc-parser.d.ts +5 -0
- package/dist/audit/doc-parser.js +106 -0
- package/dist/audit/index.d.ts +4 -0
- package/dist/audit/index.js +4 -0
- package/dist/audit/matcher.d.ts +6 -0
- package/dist/audit/matcher.js +94 -0
- package/dist/audit/reporter.d.ts +9 -0
- package/dist/audit/reporter.js +106 -0
- package/dist/audit/types.d.ts +37 -0
- package/dist/auth/index.js +6 -4
- package/dist/cli.js +12 -2
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +59 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
- package/dist/commands/generate/index.js +393 -0
- package/dist/commands/generate/scan.d.ts +41 -0
- package/dist/commands/generate/scan.js +256 -0
- package/dist/commands/generate/verify.d.ts +14 -0
- package/dist/commands/generate/verify.js +122 -0
- package/dist/commands/generate/write.d.ts +25 -0
- package/dist/commands/generate/write.js +120 -0
- package/dist/commands/import.js +4 -1
- package/dist/commands/llms-txt.js +6 -4
- package/dist/commands/refresh.d.ts +2 -0
- package/dist/commands/refresh.js +158 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +110 -0
- package/dist/commands/test.js +177 -236
- package/dist/commands/watch.js +29 -20
- package/dist/config/loader.d.ts +6 -2
- package/dist/config/loader.js +39 -3
- package/dist/config/types.d.ts +7 -0
- package/dist/generator/agents-md.d.ts +25 -0
- package/dist/generator/agents-md.js +122 -0
- package/dist/generator/generator.js +2 -1
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/mdx-serializer.d.ts +11 -0
- package/dist/generator/mdx-serializer.js +135 -0
- package/dist/generator/organizer.d.ts +1 -16
- package/dist/generator/organizer.js +0 -38
- package/dist/generator/types.d.ts +3 -0
- package/dist/generator/writer.js +65 -32
- package/dist/github/org-discovery.d.ts +17 -0
- package/dist/github/org-discovery.js +93 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +8 -2
- package/dist/llm/proxy-client.d.ts +32 -0
- package/dist/llm/proxy-client.js +103 -0
- package/dist/next-actions/actions.d.ts +2 -0
- package/dist/next-actions/actions.js +190 -0
- package/dist/next-actions/index.d.ts +6 -0
- package/dist/next-actions/index.js +39 -0
- package/dist/next-actions/setup.d.ts +2 -0
- package/dist/next-actions/setup.js +72 -0
- package/dist/next-actions/state.d.ts +7 -0
- package/dist/next-actions/state.js +68 -0
- package/dist/next-actions/suggest.d.ts +3 -0
- package/dist/next-actions/suggest.js +47 -0
- package/dist/next-actions/types.d.ts +26 -0
- package/dist/refresh/differ.d.ts +9 -0
- package/dist/refresh/differ.js +67 -0
- package/dist/refresh/index.d.ts +4 -0
- package/dist/refresh/index.js +4 -0
- package/dist/refresh/manifest.d.ts +18 -0
- package/dist/refresh/manifest.js +71 -0
- package/dist/refresh/splicer.d.ts +9 -0
- package/dist/refresh/splicer.js +50 -0
- package/dist/refresh/types.d.ts +37 -0
- package/dist/review/index.d.ts +8 -0
- package/dist/review/index.js +94 -0
- package/dist/review/parser.d.ts +16 -0
- package/dist/review/parser.js +95 -0
- package/dist/review/types.d.ts +18 -0
- package/dist/scanner/csharp.d.ts +0 -4
- package/dist/scanner/csharp.js +9 -49
- package/dist/scanner/go.d.ts +0 -3
- package/dist/scanner/go.js +8 -35
- package/dist/scanner/java.d.ts +0 -4
- package/dist/scanner/java.js +9 -49
- package/dist/scanner/kotlin.d.ts +0 -3
- package/dist/scanner/kotlin.js +6 -33
- package/dist/scanner/php.d.ts +0 -10
- package/dist/scanner/php.js +11 -55
- package/dist/scanner/ruby.d.ts +0 -3
- package/dist/scanner/ruby.js +8 -38
- package/dist/scanner/rust.d.ts +0 -3
- package/dist/scanner/rust.js +10 -37
- package/dist/scanner/swift.d.ts +0 -3
- package/dist/scanner/swift.js +8 -35
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/utils.d.ts +41 -0
- package/dist/scanner/utils.js +97 -0
- package/dist/structure/index.d.ts +19 -0
- package/dist/structure/index.js +92 -0
- package/dist/structure/planner.d.ts +8 -0
- package/dist/structure/planner.js +180 -0
- package/dist/structure/topology.d.ts +16 -0
- package/dist/structure/topology.js +49 -0
- package/dist/structure/types.d.ts +26 -0
- package/dist/template/docs.json +5 -2
- package/dist/template/next.config.mjs +31 -0
- package/dist/template/package.json +5 -3
- package/dist/template/src/app/layout.tsx +13 -13
- package/dist/template/src/app/llms-full.md/route.ts +29 -0
- package/dist/template/src/app/llms.txt/route.ts +29 -0
- package/dist/template/src/app/md/[...slug]/route.ts +174 -0
- package/dist/template/src/app/reference/route.ts +22 -18
- package/dist/template/src/app/sitemap.ts +1 -1
- package/dist/template/src/components/ai-chat-impl.tsx +206 -0
- package/dist/template/src/components/ai-chat.tsx +20 -193
- package/dist/template/src/components/mdx/index.tsx +27 -4
- package/dist/template/src/lib/fonts.ts +135 -0
- package/dist/template/src/middleware.ts +101 -0
- package/dist/template/src/styles/globals.css +28 -20
- package/dist/testing/comparator.d.ts +7 -0
- package/dist/testing/comparator.js +77 -0
- package/dist/testing/docker.d.ts +21 -0
- package/dist/testing/docker.js +234 -0
- package/dist/testing/env.d.ts +16 -0
- package/dist/testing/env.js +58 -0
- package/dist/testing/extractor.d.ts +9 -0
- package/dist/testing/extractor.js +195 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/runner.d.ts +5 -0
- package/dist/testing/runner.js +225 -0
- package/dist/testing/types.d.ts +58 -0
- package/dist/utils/files.d.ts +0 -8
- package/dist/utils/files.js +0 -33
- package/package.json +1 -1
- package/dist/autofix/autofix.test.js +0 -487
- package/dist/commands/generate.js +0 -445
- package/dist/generator/generator.test.js +0 -259
- package/dist/generator/writer.test.js +0 -411
- package/dist/llm/llm.manual-test.js +0 -112
- package/dist/llm/llm.mock-test.d.ts +0 -4
- package/dist/llm/llm.mock-test.js +0 -79
- package/dist/plugins/index.d.ts +0 -47
- package/dist/plugins/index.js +0 -181
- package/dist/scanner/content-type.test.js +0 -231
- package/dist/scanner/integration.test.d.ts +0 -4
- package/dist/scanner/integration.test.js +0 -180
- package/dist/scanner/scanner.test.js +0 -210
- package/dist/scanner/typescript.manual-test.d.ts +0 -1
- package/dist/scanner/typescript.manual-test.js +0 -112
- package/dist/template/src/app/docs/auth/page.mdx +0 -589
- package/dist/template/src/app/docs/autofix/page.mdx +0 -624
- package/dist/template/src/app/docs/cli/page.mdx +0 -217
- package/dist/template/src/app/docs/config/page.mdx +0 -428
- package/dist/template/src/app/docs/configuration/page.mdx +0 -86
- package/dist/template/src/app/docs/deployment/page.mdx +0 -112
- package/dist/template/src/app/docs/generator/generator.md +0 -504
- package/dist/template/src/app/docs/generator/organizer.md +0 -779
- package/dist/template/src/app/docs/generator/page.mdx +0 -613
- package/dist/template/src/app/docs/github/page.mdx +0 -502
- package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
- package/dist/template/src/app/docs/llm/index.md +0 -471
- package/dist/template/src/app/docs/llm/page.mdx +0 -428
- package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
- package/dist/template/src/app/docs/pro/page.mdx +0 -121
- package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
- package/dist/template/src/app/docs/scanner/content-type.md +0 -599
- package/dist/template/src/app/docs/scanner/index.md +0 -212
- package/dist/template/src/app/docs/scanner/page.mdx +0 -307
- package/dist/template/src/app/docs/scanner/python.md +0 -469
- package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
- package/dist/template/src/app/docs/scanner/rust.md +0 -325
- package/dist/template/src/app/docs/scanner/typescript.md +0 -201
- package/dist/template/src/app/icon.tsx +0 -29
- package/dist/utils/validation.d.ts +0 -1
- package/dist/utils/validation.js +0 -12
- /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
- /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
- /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
- /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
- /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
- /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
package/dist/config/loader.js
CHANGED
|
@@ -4,7 +4,7 @@ import yaml from 'js-yaml';
|
|
|
4
4
|
import { DEFAULT_CONFIG, PROVIDER_ENV_KEYS } from './types.js';
|
|
5
5
|
const VALID_PROVIDERS = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
|
|
6
6
|
const CONFIG_FILENAMES = ['.skrypt.yaml', '.skrypt.yml', 'skrypt.yaml', 'skrypt.yml'];
|
|
7
|
-
|
|
7
|
+
function findConfigFile(dir) {
|
|
8
8
|
for (const filename of CONFIG_FILENAMES) {
|
|
9
9
|
const filepath = join(dir, filename);
|
|
10
10
|
if (existsSync(filepath)) {
|
|
@@ -61,6 +61,7 @@ function mergeConfig(defaults, overrides) {
|
|
|
61
61
|
...defaults.source,
|
|
62
62
|
...overrides.source
|
|
63
63
|
},
|
|
64
|
+
sources: overrides.sources,
|
|
64
65
|
output: {
|
|
65
66
|
...defaults.output,
|
|
66
67
|
...overrides.output
|
|
@@ -74,10 +75,25 @@ function mergeConfig(defaults, overrides) {
|
|
|
74
75
|
}
|
|
75
76
|
export function validateConfig(config) {
|
|
76
77
|
const errors = [];
|
|
77
|
-
if (config.version !== 1) {
|
|
78
|
+
if (config.version !== 1 && config.version !== 2) {
|
|
78
79
|
errors.push(`Unsupported config version: ${config.version}`);
|
|
79
80
|
}
|
|
80
|
-
if (
|
|
81
|
+
// Validate sources array if present (v2)
|
|
82
|
+
if (config.sources && config.sources.length > 0) {
|
|
83
|
+
const labels = new Set();
|
|
84
|
+
for (const entry of config.sources) {
|
|
85
|
+
if (!entry.path) {
|
|
86
|
+
errors.push('Each source entry must have a path');
|
|
87
|
+
}
|
|
88
|
+
if (entry.label) {
|
|
89
|
+
if (labels.has(entry.label)) {
|
|
90
|
+
errors.push(`Duplicate source label: "${entry.label}"`);
|
|
91
|
+
}
|
|
92
|
+
labels.add(entry.label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (!config.source.path) {
|
|
81
97
|
errors.push('source.path is required');
|
|
82
98
|
}
|
|
83
99
|
if (!config.output.path) {
|
|
@@ -88,6 +104,26 @@ export function validateConfig(config) {
|
|
|
88
104
|
}
|
|
89
105
|
return errors;
|
|
90
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve v1 `source` or v2 `sources` into a normalized array of SourceEntry.
|
|
109
|
+
* Falls back to the single source config if no sources array is defined.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveSourceEntries(config) {
|
|
112
|
+
if (config.sources && config.sources.length > 0) {
|
|
113
|
+
return config.sources.map(s => ({
|
|
114
|
+
path: s.path,
|
|
115
|
+
label: s.label,
|
|
116
|
+
include: s.include ?? config.source.include,
|
|
117
|
+
exclude: s.exclude ?? config.source.exclude,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
// Backwards compat: single source
|
|
121
|
+
return [{
|
|
122
|
+
path: config.source.path,
|
|
123
|
+
include: config.source.include,
|
|
124
|
+
exclude: config.source.exclude,
|
|
125
|
+
}];
|
|
126
|
+
}
|
|
91
127
|
export function checkApiKey(provider) {
|
|
92
128
|
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
93
129
|
// Ollama doesn't need an API key
|
package/dist/config/types.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ export interface SourceConfig {
|
|
|
3
3
|
include?: string[];
|
|
4
4
|
exclude?: string[];
|
|
5
5
|
}
|
|
6
|
+
export interface SourceEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
include?: string[];
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
}
|
|
6
12
|
export type LLMProvider = 'deepseek' | 'openai' | 'anthropic' | 'google' | 'ollama' | 'openrouter';
|
|
7
13
|
export interface LLMConfig {
|
|
8
14
|
provider: LLMProvider;
|
|
@@ -14,6 +20,7 @@ export declare const DEFAULT_MODELS: Record<LLMProvider, string>;
|
|
|
14
20
|
export interface Config {
|
|
15
21
|
version: number;
|
|
16
22
|
source: SourceConfig;
|
|
23
|
+
sources?: SourceEntry[];
|
|
17
24
|
output: {
|
|
18
25
|
path: string;
|
|
19
26
|
format: 'markdown' | 'mdx';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md / CLAUDE.md generator
|
|
3
|
+
*
|
|
4
|
+
* Generates context files that coding agents (Claude Code, Cursor, GitHub Copilot,
|
|
5
|
+
* OpenCode) load on every turn. Vercel research: 8KB AGENTS.md = 100% pass rate
|
|
6
|
+
* vs 79% for skills-based approaches.
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentsMdPage {
|
|
9
|
+
title: string;
|
|
10
|
+
path: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
category?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentsMdOptions {
|
|
15
|
+
projectName: string;
|
|
16
|
+
docsPath: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
pages: AgentsMdPage[];
|
|
19
|
+
hasLlmsTxt: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate AGENTS.md content for a project.
|
|
23
|
+
* Designed to be loaded by coding agents on every turn for instant docs access.
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateAgentsMd(options: AgentsMdOptions): string;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md / CLAUDE.md generator
|
|
3
|
+
*
|
|
4
|
+
* Generates context files that coding agents (Claude Code, Cursor, GitHub Copilot,
|
|
5
|
+
* OpenCode) load on every turn. Vercel research: 8KB AGENTS.md = 100% pass rate
|
|
6
|
+
* vs 79% for skills-based approaches.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_SIZE_BYTES = 8 * 1024; // 8KB — Vercel's optimal size
|
|
9
|
+
/**
|
|
10
|
+
* Generate AGENTS.md content for a project.
|
|
11
|
+
* Designed to be loaded by coding agents on every turn for instant docs access.
|
|
12
|
+
*/
|
|
13
|
+
export function generateAgentsMd(options) {
|
|
14
|
+
const { projectName, docsPath, baseUrl, pages, hasLlmsTxt } = options;
|
|
15
|
+
let out = `# ${projectName} — Documentation Guide\n\n`;
|
|
16
|
+
out += `When answering questions about **${projectName}**, consult these docs first.\n\n`;
|
|
17
|
+
// Access instructions
|
|
18
|
+
out += `## How to Access Docs\n\n`;
|
|
19
|
+
if (baseUrl) {
|
|
20
|
+
out += `- **Browse:** ${baseUrl}\n`;
|
|
21
|
+
if (hasLlmsTxt) {
|
|
22
|
+
out += `- **Agent index:** ${baseUrl}/llms.txt (structured summary)\n`;
|
|
23
|
+
out += `- **Full content:** ${baseUrl}/llms-full.md (all docs in one file)\n`;
|
|
24
|
+
}
|
|
25
|
+
out += `- **Markdown variant:** Append \`.md\` to any docs URL, or use \`Accept: text/markdown\` header\n`;
|
|
26
|
+
}
|
|
27
|
+
out += `- **Local files:** \`${docsPath}/\` (MDX/Markdown source)\n`;
|
|
28
|
+
out += `\n`;
|
|
29
|
+
// Identify entry points
|
|
30
|
+
const quickstarts = pages.filter(p => isEntryPoint(p, 'quickstart'));
|
|
31
|
+
const apiRefs = pages.filter(p => isEntryPoint(p, 'api'));
|
|
32
|
+
const guides = pages.filter(p => isEntryPoint(p, 'guide'));
|
|
33
|
+
if (quickstarts.length > 0 || apiRefs.length > 0) {
|
|
34
|
+
out += `## Key Entry Points\n\n`;
|
|
35
|
+
if (quickstarts.length > 0) {
|
|
36
|
+
out += `**Getting Started:**\n`;
|
|
37
|
+
for (const p of quickstarts.slice(0, 3)) {
|
|
38
|
+
out += formatPageLine(p, baseUrl);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (apiRefs.length > 0) {
|
|
42
|
+
out += `**API Reference:**\n`;
|
|
43
|
+
for (const p of apiRefs.slice(0, 5)) {
|
|
44
|
+
out += formatPageLine(p, baseUrl);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (guides.length > 0) {
|
|
48
|
+
out += `**Guides:**\n`;
|
|
49
|
+
for (const p of guides.slice(0, 5)) {
|
|
50
|
+
out += formatPageLine(p, baseUrl);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
out += `\n`;
|
|
54
|
+
}
|
|
55
|
+
// Page index — compact, one line per page
|
|
56
|
+
out += `## Documentation Index\n\n`;
|
|
57
|
+
// Group by category if available
|
|
58
|
+
const byCategory = groupByCategory(pages);
|
|
59
|
+
for (const [category, categoryPages] of byCategory) {
|
|
60
|
+
if (category !== '_default') {
|
|
61
|
+
out += `### ${category}\n\n`;
|
|
62
|
+
}
|
|
63
|
+
for (const page of categoryPages) {
|
|
64
|
+
out += formatPageLine(page, baseUrl);
|
|
65
|
+
// Check size — stop adding pages if we're near the limit
|
|
66
|
+
if (Buffer.byteLength(out, 'utf-8') > MAX_SIZE_BYTES - 200) {
|
|
67
|
+
const remaining = pages.length - categoryPages.indexOf(page) - 1;
|
|
68
|
+
if (remaining > 0) {
|
|
69
|
+
out += `\n*...and ${remaining} more pages. See ${hasLlmsTxt ? 'llms.txt' : docsPath} for the complete index.*\n`;
|
|
70
|
+
}
|
|
71
|
+
return finalize(out);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
out += `\n`;
|
|
75
|
+
}
|
|
76
|
+
return finalize(out);
|
|
77
|
+
}
|
|
78
|
+
function finalize(content) {
|
|
79
|
+
const out = content.trimEnd() + '\n';
|
|
80
|
+
// Final size check — truncate page index if over limit
|
|
81
|
+
if (Buffer.byteLength(out, 'utf-8') > MAX_SIZE_BYTES) {
|
|
82
|
+
// Find the last complete line that fits
|
|
83
|
+
const lines = out.split('\n');
|
|
84
|
+
let truncated = '';
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const next = truncated + line + '\n';
|
|
87
|
+
if (Buffer.byteLength(next, 'utf-8') > MAX_SIZE_BYTES - 100) {
|
|
88
|
+
truncated += '\n*Index truncated to fit 8KB limit. See llms.txt for complete listing.*\n';
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
truncated = next;
|
|
92
|
+
}
|
|
93
|
+
return truncated;
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function formatPageLine(page, baseUrl) {
|
|
98
|
+
const ref = baseUrl ? `${baseUrl}/docs/${page.path}` : page.path;
|
|
99
|
+
const desc = page.description ? ` — ${page.description}` : '';
|
|
100
|
+
return `- [${page.title}](${ref})${desc}\n`;
|
|
101
|
+
}
|
|
102
|
+
function isEntryPoint(page, type) {
|
|
103
|
+
const lower = `${page.title} ${page.path} ${page.category || ''}`.toLowerCase();
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'quickstart':
|
|
106
|
+
return /quickstart|getting.started|introduction|overview|setup|install/i.test(lower);
|
|
107
|
+
case 'api':
|
|
108
|
+
return /api.ref|reference|endpoint|api$/i.test(lower);
|
|
109
|
+
case 'guide':
|
|
110
|
+
return /guide|tutorial|how.to|walkthrough/i.test(lower);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function groupByCategory(pages) {
|
|
114
|
+
const groups = new Map();
|
|
115
|
+
for (const page of pages) {
|
|
116
|
+
const cat = page.category || '_default';
|
|
117
|
+
if (!groups.has(cat))
|
|
118
|
+
groups.set(cat, []);
|
|
119
|
+
groups.get(cat).push(page);
|
|
120
|
+
}
|
|
121
|
+
return groups;
|
|
122
|
+
}
|
|
@@ -68,7 +68,8 @@ export async function generateForElement(element, client, options, onProgress) {
|
|
|
68
68
|
report('generating');
|
|
69
69
|
try {
|
|
70
70
|
const elementContext = buildElementContext(element, options.externalContext, options.projectContext);
|
|
71
|
-
const
|
|
71
|
+
const errorContext = options.previousErrors?.get(element.name);
|
|
72
|
+
const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang, verify: options.verify, previousError: errorContext });
|
|
72
73
|
report('done');
|
|
73
74
|
return {
|
|
74
75
|
element,
|
package/dist/generator/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MDX-to-Markdown serializer
|
|
3
|
+
*
|
|
4
|
+
* Converts MDX content with JSX components into clean markdown
|
|
5
|
+
* for agent consumption (llms-full.md, content negotiation, AGENTS.md).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Strip MDX/JSX components and convert to clean markdown.
|
|
9
|
+
* Handles CodeGroup, Callout, Card, Tabs, Steps, Accordion, Screenshot, etc.
|
|
10
|
+
*/
|
|
11
|
+
export declare function serializeMdxToMarkdown(mdx: string): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MDX-to-Markdown serializer
|
|
3
|
+
*
|
|
4
|
+
* Converts MDX content with JSX components into clean markdown
|
|
5
|
+
* for agent consumption (llms-full.md, content negotiation, AGENTS.md).
|
|
6
|
+
*/
|
|
7
|
+
/** Extract a named attribute value from a JSX attribute string. */
|
|
8
|
+
function attr(attrs, name) {
|
|
9
|
+
const re = new RegExp(`${name}=["']([^"']+)["']`);
|
|
10
|
+
const m = attrs.match(re);
|
|
11
|
+
return m ? m[1] : undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Strip MDX/JSX components and convert to clean markdown.
|
|
15
|
+
* Handles CodeGroup, Callout, Card, Tabs, Steps, Accordion, Screenshot, etc.
|
|
16
|
+
*/
|
|
17
|
+
export function serializeMdxToMarkdown(mdx) {
|
|
18
|
+
let out = mdx;
|
|
19
|
+
// Remove import statements
|
|
20
|
+
out = out.replace(/^import\s+.*$/gm, '');
|
|
21
|
+
// Remove export statements (but not export default with content)
|
|
22
|
+
out = out.replace(/^export\s+(?!default\s).*$/gm, '');
|
|
23
|
+
// --- Component conversions (order matters) ---
|
|
24
|
+
// CodeGroup → flatten to labeled code blocks
|
|
25
|
+
out = out.replace(/<CodeGroup>\s*([\s\S]*?)\s*<\/CodeGroup>/g, (_match, inner) => {
|
|
26
|
+
return inner.trim();
|
|
27
|
+
});
|
|
28
|
+
// Callout variants: <Callout type="warning">, <Warning>, <Info>, <Note>, <Tip>, <Success>, <Error>
|
|
29
|
+
const calloutTypes = ['Callout', 'Info', 'Warning', 'Success', 'Error', 'Tip', 'Note'];
|
|
30
|
+
for (const tag of calloutTypes) {
|
|
31
|
+
// With type attribute: <Callout type="warning">
|
|
32
|
+
const typedRegex = new RegExp(`<${tag}\\s+type=["']([^"']+)["'][^>]*>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
|
|
33
|
+
out = out.replace(typedRegex, (_m, type, content) => {
|
|
34
|
+
const label = type.charAt(0).toUpperCase() + type.slice(1);
|
|
35
|
+
return formatBlockquote(label, content.trim());
|
|
36
|
+
});
|
|
37
|
+
// Without type attribute: <Warning>content</Warning>
|
|
38
|
+
const simpleRegex = new RegExp(`<${tag}(?:\\s[^>]*)?>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
|
|
39
|
+
out = out.replace(simpleRegex, (_m, content) => {
|
|
40
|
+
const label = tag === 'Callout' ? 'Note' : tag;
|
|
41
|
+
return formatBlockquote(label, content.trim());
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// CardGroup → unwrap (just keep children)
|
|
45
|
+
out = out.replace(/<CardGroup[^>]*>\s*/g, '');
|
|
46
|
+
out = out.replace(/\s*<\/CardGroup>/g, '');
|
|
47
|
+
// Card with attributes + children (attribute-order-independent)
|
|
48
|
+
out = out.replace(/<Card\s+([^>]+?)>\s*([\s\S]*?)\s*<\/Card>/g, (_m, attrs, desc) => {
|
|
49
|
+
const title = attr(attrs, 'title');
|
|
50
|
+
const href = attr(attrs, 'href');
|
|
51
|
+
const description = desc.trim().replace(/<[^>]+>/g, '');
|
|
52
|
+
if (title && href) {
|
|
53
|
+
return description
|
|
54
|
+
? `- **[${title}](${href})** — ${description}`
|
|
55
|
+
: `- **[${title}](${href})**`;
|
|
56
|
+
}
|
|
57
|
+
if (title) {
|
|
58
|
+
return description ? `- **${title}** — ${description}` : `- **${title}**`;
|
|
59
|
+
}
|
|
60
|
+
return description || '';
|
|
61
|
+
});
|
|
62
|
+
// Tabs/TabList/TabPanel → headings + content
|
|
63
|
+
out = out.replace(/<TabList>\s*([\s\S]*?)\s*<\/TabList>/g, '');
|
|
64
|
+
out = out.replace(/<Tab\s+label=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Tab>/g, (_m, label, content) => `#### ${label}\n\n${content.trim()}\n`);
|
|
65
|
+
out = out.replace(/<TabPanel[^>]*>\s*([\s\S]*?)\s*<\/TabPanel>/g, (_m, content) => content.trim());
|
|
66
|
+
out = out.replace(/<Tabs[^>]*>\s*/g, '');
|
|
67
|
+
out = out.replace(/\s*<\/Tabs>/g, '');
|
|
68
|
+
// Steps/Step → numbered list
|
|
69
|
+
out = out.replace(/<Steps>\s*([\s\S]*?)\s*<\/Steps>/g, (_match, inner) => {
|
|
70
|
+
let stepNum = 0;
|
|
71
|
+
return inner.replace(/<Step\s*(?:title=["']([^"']+)["'][^>]*)?>?\s*([\s\S]*?)\s*<\/Step>/g, (_m, title, content) => {
|
|
72
|
+
stepNum++;
|
|
73
|
+
const heading = title ? `**${title}**` : '';
|
|
74
|
+
return `${stepNum}. ${heading}\n ${content.trim()}\n`;
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// Accordion → bold title + content
|
|
78
|
+
out = out.replace(/<AccordionGroup[^>]*>\s*/g, '');
|
|
79
|
+
out = out.replace(/\s*<\/AccordionGroup>/g, '');
|
|
80
|
+
out = out.replace(/<Accordion\s+title=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Accordion>/g, (_m, title, content) => `**${title}**\n\n${content.trim()}\n`);
|
|
81
|
+
// Screenshot → image (attribute-order-independent)
|
|
82
|
+
out = out.replace(/<Screenshot\s+([^>]*?)\s*\/?>/g, (_m, attrs) => {
|
|
83
|
+
const src = attr(attrs, 'src');
|
|
84
|
+
const alt = attr(attrs, 'alt');
|
|
85
|
+
return src ? `` : '';
|
|
86
|
+
});
|
|
87
|
+
// Also handle <Screenshot>...</Screenshot> (non-self-closing)
|
|
88
|
+
out = out.replace(/<Screenshot\s+([^>]*?)>[\s\S]*?<\/Screenshot>/g, (_m, attrs) => {
|
|
89
|
+
const src = attr(attrs, 'src');
|
|
90
|
+
return src ? `` : '';
|
|
91
|
+
});
|
|
92
|
+
// Frame → unwrap
|
|
93
|
+
out = out.replace(/<Frame[^>]*>\s*/g, '');
|
|
94
|
+
out = out.replace(/\s*<\/Frame>/g, '');
|
|
95
|
+
// DarkImage → image (use light image src)
|
|
96
|
+
out = out.replace(/<DarkImage\s+(?:[^>]*?src=["']([^"']+)["'][^>]*?)\s*\/?>/g, (_m, src) => ``);
|
|
97
|
+
// CodePlayground / PythonPlayground / GoPlayground → extract code
|
|
98
|
+
for (const tag of ['CodePlayground', 'PythonPlayground', 'GoPlayground']) {
|
|
99
|
+
const regex = new RegExp(`<${tag}[^>]*>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
|
|
100
|
+
out = out.replace(regex, (_m, content) => content.trim());
|
|
101
|
+
}
|
|
102
|
+
// ParamTable / Schema → pass through content
|
|
103
|
+
out = out.replace(/<ParamTable[^>]*>\s*([\s\S]*?)\s*<\/ParamTable>/g, (_m, c) => c.trim());
|
|
104
|
+
out = out.replace(/<Schema[^>]*>\s*([\s\S]*?)\s*<\/Schema>/g, (_m, c) => c.trim());
|
|
105
|
+
// API badges → inline text
|
|
106
|
+
out = out.replace(/<MethodBadge\s+method=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1`');
|
|
107
|
+
out = out.replace(/<StatusBadge\s+code=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1`');
|
|
108
|
+
out = out.replace(/<Endpoint\s+method=["']([^"']+)["']\s+path=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1 $2`');
|
|
109
|
+
// LinkPreview → markdown link
|
|
110
|
+
out = out.replace(/<LinkPreview\s+(?:[^>]*?url=["']([^"']+)["'][^>]*?)(?:\s+title=["']([^"']+)["'])?[^>]*\s*\/?>/g, (_m, url, title) => `[${title || url}](${url})`);
|
|
111
|
+
// Tooltip → just the text
|
|
112
|
+
out = out.replace(/<Tooltip\s+[^>]*>\s*([\s\S]*?)\s*<\/Tooltip>/g, (_m, c) => c.trim());
|
|
113
|
+
// Changelog components → headings
|
|
114
|
+
out = out.replace(/<Changelog>\s*/g, '');
|
|
115
|
+
out = out.replace(/\s*<\/Changelog>/g, '');
|
|
116
|
+
out = out.replace(/<ChangelogEntry\s+(?:[^>]*?date=["']([^"']+)["'][^>]*?)>\s*([\s\S]*?)\s*<\/ChangelogEntry>/g, (_m, date, content) => `### ${date}\n\n${content.trim()}\n`);
|
|
117
|
+
out = out.replace(/<Change\s+type=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Change>/g, (_m, type, content) => `- **${type}:** ${content.trim()}`);
|
|
118
|
+
// Mermaid → keep as code block
|
|
119
|
+
out = out.replace(/<Mermaid\s+chart=["'`]([^"'`]+)["'`][^>]*\s*\/?>/g, (_m, chart) => '```mermaid\n' + chart.trim() + '\n```');
|
|
120
|
+
// Catch-all: remove any remaining self-closing JSX tags
|
|
121
|
+
out = out.replace(/<[A-Z][A-Za-z]*\s[^>]*\/>/g, '');
|
|
122
|
+
// Catch-all: unwrap any remaining JSX component tags (keep children)
|
|
123
|
+
out = out.replace(/<[A-Z][A-Za-z]*(?:\s[^>]*)?>/g, '');
|
|
124
|
+
out = out.replace(/<\/[A-Z][A-Za-z]*>/g, '');
|
|
125
|
+
// Clean up excessive blank lines
|
|
126
|
+
out = out.replace(/\n{4,}/g, '\n\n\n');
|
|
127
|
+
// Trim leading/trailing whitespace
|
|
128
|
+
out = out.trim() + '\n';
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function formatBlockquote(label, content) {
|
|
132
|
+
const lines = content.split('\n');
|
|
133
|
+
const quoted = lines.map(line => `> ${line}`).join('\n');
|
|
134
|
+
return `> **${label}:** ${quoted.replace(/^> /, '')}\n`;
|
|
135
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GeneratedDoc, Topic, TopicConfig, CrossReference
|
|
1
|
+
import { GeneratedDoc, Topic, TopicConfig, CrossReference } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Default topic configuration with common patterns
|
|
4
4
|
*/
|
|
@@ -15,18 +15,3 @@ export declare function detectCrossReferences(docs: GeneratedDoc[]): CrossRefere
|
|
|
15
15
|
* Get cross-references for a specific element
|
|
16
16
|
*/
|
|
17
17
|
export declare function getCrossRefsForElement(elementName: string, allRefs: CrossReference[]): CrossReference[];
|
|
18
|
-
/**
|
|
19
|
-
* Build navigation structure from topics
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
export declare function buildNavigation(topics: Topic[]): NavigationItem[];
|
|
23
|
-
/**
|
|
24
|
-
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export declare function generateSidebarConfig(topics: Topic[]): object;
|
|
28
|
-
/**
|
|
29
|
-
* Merge user config with defaults
|
|
30
|
-
* @internal
|
|
31
|
-
*/
|
|
32
|
-
export declare function mergeTopicConfig(userConfig: Partial<TopicConfig>, defaults?: TopicConfig): TopicConfig;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { slugify } from '../utils/files.js';
|
|
2
1
|
/**
|
|
3
2
|
* Default topic configuration with common patterns
|
|
4
3
|
*/
|
|
@@ -172,32 +171,6 @@ export function detectCrossReferences(docs) {
|
|
|
172
171
|
export function getCrossRefsForElement(elementName, allRefs) {
|
|
173
172
|
return allRefs.filter(r => r.fromElement === elementName);
|
|
174
173
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Build navigation structure from topics
|
|
177
|
-
* @internal
|
|
178
|
-
*/
|
|
179
|
-
export function buildNavigation(topics) {
|
|
180
|
-
return topics.map(topic => ({
|
|
181
|
-
title: topic.name,
|
|
182
|
-
path: `/${topic.id}`,
|
|
183
|
-
children: topic.docs.map(doc => ({
|
|
184
|
-
title: doc.element.name,
|
|
185
|
-
path: `/${topic.id}#${slugify(doc.element.name)}`
|
|
186
|
-
}))
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
191
|
-
* @internal
|
|
192
|
-
*/
|
|
193
|
-
export function generateSidebarConfig(topics) {
|
|
194
|
-
return {
|
|
195
|
-
navigation: topics.map(topic => ({
|
|
196
|
-
group: topic.name,
|
|
197
|
-
pages: topic.docs.map(doc => `${topic.id}/${slugify(doc.element.name)}`)
|
|
198
|
-
}))
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
174
|
/**
|
|
202
175
|
* Convert string to title case
|
|
203
176
|
*/
|
|
@@ -206,14 +179,3 @@ function titleCase(str) {
|
|
|
206
179
|
.replace(/-/g, ' ')
|
|
207
180
|
.replace(/\b\w/g, c => c.toUpperCase());
|
|
208
181
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Merge user config with defaults
|
|
211
|
-
* @internal
|
|
212
|
-
*/
|
|
213
|
-
export function mergeTopicConfig(userConfig, defaults = DEFAULT_TOPIC_CONFIG) {
|
|
214
|
-
return {
|
|
215
|
-
topics: { ...defaults.topics, ...userConfig.topics },
|
|
216
|
-
rules: [...defaults.rules, ...(userConfig.rules || [])],
|
|
217
|
-
defaultTopic: userConfig.defaultTopic ?? defaults.defaultTopic
|
|
218
|
-
};
|
|
219
|
-
}
|
|
@@ -33,6 +33,9 @@ export interface GenerationOptions {
|
|
|
33
33
|
externalContext?: Map<string, string>;
|
|
34
34
|
maxTokens?: number;
|
|
35
35
|
projectContext?: string;
|
|
36
|
+
verify?: boolean;
|
|
37
|
+
/** Error context from previous verification failures, keyed by element name */
|
|
38
|
+
previousErrors?: Map<string, string>;
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
38
41
|
* Result of generating docs for a file
|
package/dist/generator/writer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync } from 'fs';
|
|
|
4
4
|
import { formatAsMarkdown } from './generator.js';
|
|
5
5
|
import { organizeByTopic, detectCrossReferences, getCrossRefsForElement } from './organizer.js';
|
|
6
6
|
import { slugify } from '../utils/files.js';
|
|
7
|
+
import { serializeMdxToMarkdown } from './mdx-serializer.js';
|
|
7
8
|
/**
|
|
8
9
|
* Generate llms.txt file (Answer Engine Optimization)
|
|
9
10
|
* Format follows https://llmstxt.org convention
|
|
@@ -13,36 +14,45 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
13
14
|
const description = options.description || 'API documentation generated by skrypt';
|
|
14
15
|
let content = `# ${projectName}\n\n`;
|
|
15
16
|
content += `> ${description}\n\n`;
|
|
16
|
-
//
|
|
17
|
-
const
|
|
17
|
+
// Check if docs have source labels (multi-source mode)
|
|
18
|
+
const hasSourceLabels = docs.some(d => d.element.sourceLabel);
|
|
19
|
+
// Group by source label then by file
|
|
20
|
+
const bySource = new Map();
|
|
18
21
|
for (const doc of docs) {
|
|
22
|
+
const sourceKey = doc.element.sourceLabel || '_default';
|
|
23
|
+
if (!bySource.has(sourceKey))
|
|
24
|
+
bySource.set(sourceKey, new Map());
|
|
25
|
+
const sourceMap = bySource.get(sourceKey);
|
|
19
26
|
const file = basename(doc.element.filePath).replace(/\.[^.]+$/, '');
|
|
20
|
-
if (!
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const fileDocs = byFile.get(file);
|
|
24
|
-
if (fileDocs)
|
|
25
|
-
fileDocs.push(doc);
|
|
27
|
+
if (!sourceMap.has(file))
|
|
28
|
+
sourceMap.set(file, []);
|
|
29
|
+
sourceMap.get(file).push(doc);
|
|
26
30
|
}
|
|
31
|
+
const totalModules = Array.from(bySource.values()).reduce((sum, m) => sum + m.size, 0);
|
|
27
32
|
// Summary section
|
|
28
33
|
content += `## Overview\n\n`;
|
|
29
|
-
content += `This project contains ${docs.length} documented API elements across ${
|
|
34
|
+
content += `This project contains ${docs.length} documented API elements across ${totalModules} modules.\n\n`;
|
|
30
35
|
// Quick reference
|
|
31
36
|
content += `## Quick Reference\n\n`;
|
|
32
|
-
for (const [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
for (const [sourceLabel, byFile] of bySource) {
|
|
38
|
+
if (hasSourceLabels && sourceLabel !== '_default') {
|
|
39
|
+
content += `### ${sourceLabel}\n\n`;
|
|
40
|
+
}
|
|
41
|
+
for (const [file, fileDocs] of byFile) {
|
|
42
|
+
content += `${hasSourceLabels ? '####' : '###'} ${file}\n\n`;
|
|
43
|
+
for (const doc of fileDocs) {
|
|
44
|
+
content += `- \`${doc.element.name}\`: ${doc.element.kind}`;
|
|
45
|
+
if (doc.markdown) {
|
|
46
|
+
// Extract first sentence as summary
|
|
47
|
+
const firstSentence = doc.markdown.split(/\.\s/)[0]?.slice(0, 100);
|
|
48
|
+
if (firstSentence) {
|
|
49
|
+
content += ` - ${firstSentence}`;
|
|
50
|
+
}
|
|
41
51
|
}
|
|
52
|
+
content += '\n';
|
|
42
53
|
}
|
|
43
54
|
content += '\n';
|
|
44
55
|
}
|
|
45
|
-
content += '\n';
|
|
46
56
|
}
|
|
47
57
|
// Detailed API section
|
|
48
58
|
content += `## API Details\n\n`;
|
|
@@ -52,14 +62,14 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
52
62
|
content += `**File:** ${doc.element.filePath}\n\n`;
|
|
53
63
|
content += `\`\`\`\n${doc.element.signature}\n\`\`\`\n\n`;
|
|
54
64
|
if (doc.markdown) {
|
|
55
|
-
content += doc.markdown.slice(0, 500) + '\n\n';
|
|
65
|
+
content += serializeMdxToMarkdown(doc.markdown.slice(0, 500)) + '\n\n';
|
|
56
66
|
}
|
|
57
67
|
if (doc.codeExample) {
|
|
58
68
|
content += `**Example:**\n\`\`\`${doc.codeLanguage}\n${doc.codeExample.slice(0, 300)}\n\`\`\`\n\n`;
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
|
-
// Write both llms.txt and llms-full.md
|
|
62
|
-
await writeFile(join(outputDir, 'llms.txt'), content, 'utf-8');
|
|
71
|
+
// Write both llms.txt and llms-full.md — serialize any JSX for clean agent consumption
|
|
72
|
+
await writeFile(join(outputDir, 'llms.txt'), serializeMdxToMarkdown(content), 'utf-8');
|
|
63
73
|
// Also write a condensed version (just signatures)
|
|
64
74
|
let condensed = `# ${projectName} API\n\n`;
|
|
65
75
|
condensed += `> ${description}\n\n`;
|
|
@@ -67,7 +77,7 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
67
77
|
condensed += `## ${doc.element.name}\n`;
|
|
68
78
|
condensed += `${doc.element.signature}\n\n`;
|
|
69
79
|
}
|
|
70
|
-
await writeFile(join(outputDir, 'llms-full.md'), condensed, 'utf-8');
|
|
80
|
+
await writeFile(join(outputDir, 'llms-full.md'), serializeMdxToMarkdown(condensed), 'utf-8');
|
|
71
81
|
}
|
|
72
82
|
/**
|
|
73
83
|
* Write generated docs to output directory
|
|
@@ -134,15 +144,38 @@ async function writeIndexFile(results, outputDir, sourceDir) {
|
|
|
134
144
|
const totalElements = results.reduce((sum, r) => sum + r.docs.length, 0);
|
|
135
145
|
content += '## Summary\n\n';
|
|
136
146
|
content += `- **Total elements:** ${totalElements}\n\n`;
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
// Check if any docs have source labels
|
|
148
|
+
const hasLabels = results.some(r => r.docs.some(d => d.element.sourceLabel));
|
|
149
|
+
if (hasLabels) {
|
|
150
|
+
// Group by source label
|
|
151
|
+
const byLabel = new Map();
|
|
152
|
+
for (const result of results) {
|
|
153
|
+
if (result.docs.length === 0)
|
|
154
|
+
continue;
|
|
155
|
+
const label = result.docs[0]?.element.sourceLabel || 'Other';
|
|
156
|
+
if (!byLabel.has(label))
|
|
157
|
+
byLabel.set(label, []);
|
|
158
|
+
byLabel.get(label).push(result);
|
|
159
|
+
}
|
|
160
|
+
for (const [label, labelResults] of byLabel) {
|
|
161
|
+
content += `## ${label}\n\n`;
|
|
162
|
+
for (const result of labelResults) {
|
|
163
|
+
const relPath = relative(sourceDir, result.filePath);
|
|
164
|
+
const docFileName = relPath.replace(/\.[^.]+$/, '.md');
|
|
165
|
+
content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
|
|
166
|
+
}
|
|
167
|
+
content += '\n';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
content += '## Files\n\n';
|
|
172
|
+
for (const result of results) {
|
|
173
|
+
if (result.docs.length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
const relPath = relative(sourceDir, result.filePath);
|
|
176
|
+
const docFileName = relPath.replace(/\.[^.]+$/, '.md');
|
|
177
|
+
content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
|
|
178
|
+
}
|
|
146
179
|
}
|
|
147
180
|
await writeFile(join(outputDir, 'README.md'), content, 'utf-8');
|
|
148
181
|
}
|