skrypt-ai 0.4.2 → 0.6.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/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +20 -3
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +125 -7
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/plugins/index.js +7 -0
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +53 -26
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/python.js +17 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +149 -13
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +5 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Auto-detect documentation format from a directory by checking marker files.
|
|
5
|
+
* First match wins (priority order).
|
|
6
|
+
*/
|
|
7
|
+
export function detectFormat(dir) {
|
|
8
|
+
// 1. Mintlify: mint.json or docs.json with Mintlify structure
|
|
9
|
+
if (existsSync(join(dir, 'mint.json')))
|
|
10
|
+
return 'mintlify';
|
|
11
|
+
if (existsSync(join(dir, 'docs.json'))) {
|
|
12
|
+
try {
|
|
13
|
+
const content = JSON.parse(readFileSync(join(dir, 'docs.json'), 'utf-8'));
|
|
14
|
+
// Mintlify docs.json has navigation with group/pages AND one of: anchors, tabs, topbarCtaButton
|
|
15
|
+
if (content.navigation && Array.isArray(content.navigation) &&
|
|
16
|
+
(content.anchors || content.tabs || content.topbarCtaButton || content.topbarLinks)) {
|
|
17
|
+
return 'mintlify';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch { /* not mintlify */ }
|
|
21
|
+
}
|
|
22
|
+
// 2. Docusaurus: docusaurus.config.js or .ts
|
|
23
|
+
if (existsSync(join(dir, 'docusaurus.config.js')) || existsSync(join(dir, 'docusaurus.config.ts'))) {
|
|
24
|
+
return 'docusaurus';
|
|
25
|
+
}
|
|
26
|
+
// 3. GitBook: SUMMARY.md or .gitbook.yaml
|
|
27
|
+
if (existsSync(join(dir, 'SUMMARY.md')) || existsSync(join(dir, '.gitbook.yaml'))) {
|
|
28
|
+
return 'gitbook';
|
|
29
|
+
}
|
|
30
|
+
// 4. ReadMe: files with [block: syntax or _order.yaml
|
|
31
|
+
if (hasReadmeMarkers(dir))
|
|
32
|
+
return 'readme';
|
|
33
|
+
// 5. Notion: files with <aside> + 32-char hex UUID filenames
|
|
34
|
+
if (hasNotionMarkers(dir))
|
|
35
|
+
return 'notion';
|
|
36
|
+
// 6. Confluence: .html files with <ac:structured-macro
|
|
37
|
+
if (hasConfluenceMarkers(dir))
|
|
38
|
+
return 'confluence';
|
|
39
|
+
// 7. MkDocs (handled by markdown importer)
|
|
40
|
+
if (existsSync(join(dir, 'mkdocs.yml')))
|
|
41
|
+
return 'markdown';
|
|
42
|
+
// 8. Fallback: any .md/.mdx files present
|
|
43
|
+
return 'markdown';
|
|
44
|
+
}
|
|
45
|
+
function hasReadmeMarkers(dir) {
|
|
46
|
+
try {
|
|
47
|
+
const entries = readdirSync(dir, { recursive: true });
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const fullPath = join(dir, entry);
|
|
50
|
+
if (entry === '_order.yaml' || entry.endsWith('/_order.yaml'))
|
|
51
|
+
return true;
|
|
52
|
+
if (extname(entry) === '.md') {
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
55
|
+
if (content.includes('[block:'))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { /* skip */ }
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function hasNotionMarkers(dir) {
|
|
66
|
+
const UUID_PATTERN = /[0-9a-f]{32}/;
|
|
67
|
+
try {
|
|
68
|
+
const entries = readdirSync(dir);
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (extname(entry) === '.md' && UUID_PATTERN.test(entry)) {
|
|
71
|
+
try {
|
|
72
|
+
const content = readFileSync(join(dir, entry), 'utf-8');
|
|
73
|
+
if (content.includes('<aside>'))
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch { /* skip */ }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { /* skip */ }
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function hasConfluenceMarkers(dir) {
|
|
84
|
+
try {
|
|
85
|
+
const entries = readdirSync(dir);
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (extname(entry) === '.html') {
|
|
88
|
+
try {
|
|
89
|
+
const content = readFileSync(join(dir, entry), 'utf-8');
|
|
90
|
+
if (content.includes('<ac:structured-macro'))
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch { /* skip */ }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { /* skip */ }
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if a string is a GitHub URL
|
|
102
|
+
*/
|
|
103
|
+
export function isGitHubUrl(input) {
|
|
104
|
+
return /^https?:\/\/(www\.)?github\.com\//.test(input);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse a GitHub URL into components
|
|
108
|
+
* Supports: https://github.com/owner/repo/tree/branch/path
|
|
109
|
+
*/
|
|
110
|
+
export function parseGitHubUrl(url) {
|
|
111
|
+
const match = url.match(/^https?:\/\/(www\.)?github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.*))?)?/);
|
|
112
|
+
if (!match) {
|
|
113
|
+
throw new Error(`Invalid GitHub URL: ${url}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
owner: match[2],
|
|
117
|
+
repo: match[3],
|
|
118
|
+
ref: match[4] || 'main',
|
|
119
|
+
path: match[5] || '',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, relative, basename, extname } from 'path';
|
|
3
|
+
import { findMdxFiles } from '../utils/files.js';
|
|
4
|
+
import { transformDocusaurusAdmonitions, transformDocusaurusTabs, stripDocusaurusImports, normalizeFrontmatter, getSortWeight, rewriteImagePaths, } from './transform.js';
|
|
5
|
+
/**
|
|
6
|
+
* Import Docusaurus documentation.
|
|
7
|
+
*/
|
|
8
|
+
export function importDocusaurus(dir, name) {
|
|
9
|
+
const result = createEmptyResult(name);
|
|
10
|
+
const stats = { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 };
|
|
11
|
+
// Extract project info from docusaurus.config.js
|
|
12
|
+
const configPath = existsSync(join(dir, 'docusaurus.config.ts'))
|
|
13
|
+
? join(dir, 'docusaurus.config.ts')
|
|
14
|
+
: join(dir, 'docusaurus.config.js');
|
|
15
|
+
if (existsSync(configPath)) {
|
|
16
|
+
const configContent = readFileSync(configPath, 'utf-8');
|
|
17
|
+
const titleMatch = configContent.match(/title:\s*['"]([^'"]+)['"]/);
|
|
18
|
+
const taglineMatch = configContent.match(/tagline:\s*['"]([^'"]+)['"]/);
|
|
19
|
+
if (titleMatch)
|
|
20
|
+
result.name = titleMatch[1];
|
|
21
|
+
if (taglineMatch)
|
|
22
|
+
result.description = taglineMatch[1];
|
|
23
|
+
}
|
|
24
|
+
if (name)
|
|
25
|
+
result.name = name;
|
|
26
|
+
// Find docs directory
|
|
27
|
+
const docsDir = existsSync(join(dir, 'docs')) ? join(dir, 'docs') : dir;
|
|
28
|
+
// Try to parse sidebars.js for navigation
|
|
29
|
+
const sidebarNav = parseSidebars(dir, docsDir);
|
|
30
|
+
if (sidebarNav && sidebarNav.length > 0) {
|
|
31
|
+
for (const group of sidebarNav) {
|
|
32
|
+
const pages = [];
|
|
33
|
+
for (const pageId of group.pageIds) {
|
|
34
|
+
const page = processDocusaurusPage(docsDir, pageId, stats, result);
|
|
35
|
+
if (page)
|
|
36
|
+
pages.push(page);
|
|
37
|
+
}
|
|
38
|
+
if (pages.length > 0) {
|
|
39
|
+
result.navigation.push({ group: group.label, pages });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Fallback: walk docs/ with _category_.json
|
|
45
|
+
const groups = walkDocsDirectory(docsDir);
|
|
46
|
+
for (const group of groups) {
|
|
47
|
+
const pages = [];
|
|
48
|
+
for (const file of group.files) {
|
|
49
|
+
const pageId = relative(docsDir, file).replace(/\.(md|mdx)$/, '').replace(/\\/g, '/');
|
|
50
|
+
const page = processDocusaurusPage(docsDir, pageId, stats, result);
|
|
51
|
+
if (page)
|
|
52
|
+
pages.push(page);
|
|
53
|
+
}
|
|
54
|
+
if (pages.length > 0) {
|
|
55
|
+
result.navigation.push({ group: group.label, pages });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Copy static assets
|
|
60
|
+
const staticDir = join(dir, 'static');
|
|
61
|
+
if (existsSync(staticDir)) {
|
|
62
|
+
collectStaticAssets(staticDir, result, stats);
|
|
63
|
+
}
|
|
64
|
+
result.stats = {
|
|
65
|
+
pages: result.files.size,
|
|
66
|
+
groups: result.navigation.length,
|
|
67
|
+
transforms: stats,
|
|
68
|
+
};
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function processDocusaurusPage(docsDir, pageId, stats, result) {
|
|
72
|
+
let filePath = join(docsDir, `${pageId}.mdx`);
|
|
73
|
+
if (!existsSync(filePath))
|
|
74
|
+
filePath = join(docsDir, `${pageId}.md`);
|
|
75
|
+
if (!existsSync(filePath))
|
|
76
|
+
filePath = join(docsDir, pageId, 'index.mdx');
|
|
77
|
+
if (!existsSync(filePath))
|
|
78
|
+
filePath = join(docsDir, pageId, 'index.md');
|
|
79
|
+
if (!existsSync(filePath)) {
|
|
80
|
+
result.warnings.push(`Page not found: ${pageId}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
let content = readFileSync(filePath, 'utf-8');
|
|
84
|
+
const originalContent = content;
|
|
85
|
+
// Apply transforms
|
|
86
|
+
content = stripDocusaurusImports(content);
|
|
87
|
+
content = transformDocusaurusAdmonitions(content);
|
|
88
|
+
content = transformDocusaurusTabs(content);
|
|
89
|
+
content = normalizeFrontmatter(content);
|
|
90
|
+
// Strip Docusaurus-specific code fence metadata ({1,3-5} line highlighting)
|
|
91
|
+
content = content.replace(/^(```\w+)(?:\s+\{[\d,-]+\})/gm, '$1');
|
|
92
|
+
// Count transforms
|
|
93
|
+
stats.callouts += countNewOccurrences(originalContent, content, '<Callout');
|
|
94
|
+
stats.tabs += countNewOccurrences(originalContent, content, '<TabPanel');
|
|
95
|
+
const title = extractTitle(content, filePath);
|
|
96
|
+
const slug = pageId.replace(/\\/g, '/');
|
|
97
|
+
const outputPath = `content/docs/${slug}.mdx`;
|
|
98
|
+
result.files.set(outputPath, content);
|
|
99
|
+
return { title, slug, sourcePath: relative(docsDir, filePath), content };
|
|
100
|
+
}
|
|
101
|
+
function parseSidebars(rootDir, docsDir) {
|
|
102
|
+
const sidebarPath = join(rootDir, 'sidebars.js');
|
|
103
|
+
if (!existsSync(sidebarPath))
|
|
104
|
+
return null;
|
|
105
|
+
try {
|
|
106
|
+
const content = readFileSync(sidebarPath, 'utf-8');
|
|
107
|
+
const groups = [];
|
|
108
|
+
// Match category objects: { type: 'category', label: 'X', items: [...] }
|
|
109
|
+
const categoryRegex = /\{\s*type:\s*['"]category['"],\s*label:\s*['"]([^'"]+)['"]\s*,\s*items:\s*\[([\s\S]*?)\]\s*\}/g;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = categoryRegex.exec(content)) !== null) {
|
|
112
|
+
const label = match[1];
|
|
113
|
+
const itemsStr = match[2];
|
|
114
|
+
const pageIds = extractPageIds(itemsStr);
|
|
115
|
+
groups.push({ label, pageIds });
|
|
116
|
+
}
|
|
117
|
+
// Match autogenerated: { type: 'autogenerated', dirName: 'x' }
|
|
118
|
+
const autoRegex = /\{\s*type:\s*['"]autogenerated['"],\s*dirName:\s*['"]([^'"]+)['"]\s*\}/g;
|
|
119
|
+
while ((match = autoRegex.exec(content)) !== null) {
|
|
120
|
+
const dirName = match[1];
|
|
121
|
+
const subDir = join(docsDir, dirName);
|
|
122
|
+
if (existsSync(subDir)) {
|
|
123
|
+
const files = findMdxFiles(subDir);
|
|
124
|
+
const pageIds = files.map(f => relative(docsDir, f).replace(/\.(md|mdx)$/, '').replace(/\\/g, '/'));
|
|
125
|
+
const label = dirName.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
126
|
+
groups.push({ label, pageIds });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Match top-level string references: 'intro', 'getting-started'
|
|
130
|
+
if (groups.length === 0) {
|
|
131
|
+
const stringRefs = content.match(/['"]([a-zA-Z0-9/_-]+)['"]/g);
|
|
132
|
+
if (stringRefs) {
|
|
133
|
+
const pageIds = stringRefs.map(s => s.replace(/['"]/g, '')).filter(s => !['category', 'doc', 'autogenerated'].includes(s));
|
|
134
|
+
if (pageIds.length > 0) {
|
|
135
|
+
groups.push({ label: 'Documentation', pageIds });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return groups.length > 0 ? groups : null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function extractPageIds(itemsStr) {
|
|
146
|
+
const ids = [];
|
|
147
|
+
// String shorthand: 'page-id'
|
|
148
|
+
const stringRegex = /['"]([a-zA-Z0-9/_-]+)['"]/g;
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = stringRegex.exec(itemsStr)) !== null) {
|
|
151
|
+
const val = match[1];
|
|
152
|
+
if (!['doc', 'category', 'autogenerated'].includes(val)) {
|
|
153
|
+
ids.push(val);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return ids;
|
|
157
|
+
}
|
|
158
|
+
function walkDocsDirectory(docsDir) {
|
|
159
|
+
const groups = [];
|
|
160
|
+
// Root-level files
|
|
161
|
+
const rootFiles = getMdFilesInDir(docsDir);
|
|
162
|
+
if (rootFiles.length > 0) {
|
|
163
|
+
groups.push({ label: 'Documentation', files: rootFiles, position: 0 });
|
|
164
|
+
}
|
|
165
|
+
// Subdirectories
|
|
166
|
+
try {
|
|
167
|
+
const entries = readdirSync(docsDir);
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const fullPath = join(docsDir, entry);
|
|
170
|
+
if (!statSync(fullPath).isDirectory() || entry.startsWith('.') || entry === 'node_modules')
|
|
171
|
+
continue;
|
|
172
|
+
const files = findMdxFiles(fullPath);
|
|
173
|
+
if (files.length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
// Read _category_.json for label and position
|
|
176
|
+
let label = entry.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
177
|
+
let position = Infinity;
|
|
178
|
+
const categoryPath = join(fullPath, '_category_.json');
|
|
179
|
+
if (existsSync(categoryPath)) {
|
|
180
|
+
try {
|
|
181
|
+
const cat = JSON.parse(readFileSync(categoryPath, 'utf-8'));
|
|
182
|
+
if (cat.label)
|
|
183
|
+
label = cat.label;
|
|
184
|
+
if (typeof cat.position === 'number')
|
|
185
|
+
position = cat.position;
|
|
186
|
+
}
|
|
187
|
+
catch { /* skip */ }
|
|
188
|
+
}
|
|
189
|
+
groups.push({ label, files: sortByWeight(files), position });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch { /* skip */ }
|
|
193
|
+
return groups.sort((a, b) => a.position - b.position);
|
|
194
|
+
}
|
|
195
|
+
function getMdFilesInDir(dir) {
|
|
196
|
+
try {
|
|
197
|
+
return readdirSync(dir)
|
|
198
|
+
.filter(f => /\.(md|mdx)$/.test(f))
|
|
199
|
+
.map(f => join(dir, f));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function sortByWeight(files) {
|
|
206
|
+
return files.sort((a, b) => {
|
|
207
|
+
const aName = basename(a, extname(a)).toLowerCase();
|
|
208
|
+
const bName = basename(b, extname(b)).toLowerCase();
|
|
209
|
+
if (aName === 'index' || aName === 'readme')
|
|
210
|
+
return -1;
|
|
211
|
+
if (bName === 'index' || bName === 'readme')
|
|
212
|
+
return 1;
|
|
213
|
+
try {
|
|
214
|
+
const aWeight = getSortWeight(readFileSync(a, 'utf-8'));
|
|
215
|
+
const bWeight = getSortWeight(readFileSync(b, 'utf-8'));
|
|
216
|
+
if (aWeight !== bWeight)
|
|
217
|
+
return aWeight - bWeight;
|
|
218
|
+
}
|
|
219
|
+
catch { /* skip */ }
|
|
220
|
+
return aName.localeCompare(bName);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function collectStaticAssets(staticDir, result, stats) {
|
|
224
|
+
const imgExts = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico']);
|
|
225
|
+
const imageMapping = new Map();
|
|
226
|
+
function walk(dir) {
|
|
227
|
+
try {
|
|
228
|
+
for (const entry of readdirSync(dir)) {
|
|
229
|
+
const fullPath = join(dir, entry);
|
|
230
|
+
if (statSync(fullPath).isDirectory()) {
|
|
231
|
+
walk(fullPath);
|
|
232
|
+
}
|
|
233
|
+
else if (imgExts.has(extname(entry).toLowerCase())) {
|
|
234
|
+
const rel = relative(staticDir, fullPath);
|
|
235
|
+
const dest = `public/${rel}`;
|
|
236
|
+
result.assets.set(dest, fullPath);
|
|
237
|
+
imageMapping.set(`/static/${rel}`, `/${rel}`);
|
|
238
|
+
imageMapping.set(`static/${rel}`, `/${rel}`);
|
|
239
|
+
stats.images++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch { /* skip */ }
|
|
244
|
+
}
|
|
245
|
+
walk(staticDir);
|
|
246
|
+
// Rewrite image paths
|
|
247
|
+
if (imageMapping.size > 0) {
|
|
248
|
+
for (const [path, content] of result.files) {
|
|
249
|
+
result.files.set(path, rewriteImagePaths(content, imageMapping));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function extractTitle(content, filePath) {
|
|
254
|
+
const fmMatch = content.match(/^---\n[\s\S]*?title:\s*["']?([^"'\n]+)["']?\s*\n[\s\S]*?---/m);
|
|
255
|
+
if (fmMatch)
|
|
256
|
+
return fmMatch[1].trim();
|
|
257
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
258
|
+
if (h1Match)
|
|
259
|
+
return h1Match[1].trim();
|
|
260
|
+
return basename(filePath, extname(filePath)).replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
261
|
+
}
|
|
262
|
+
function countNewOccurrences(original, transformed, marker) {
|
|
263
|
+
const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
264
|
+
const origCount = (original.match(new RegExp(escaped, 'g')) || []).length;
|
|
265
|
+
const newCount = (transformed.match(new RegExp(escaped, 'g')) || []).length;
|
|
266
|
+
return Math.max(0, newCount - origCount);
|
|
267
|
+
}
|
|
268
|
+
function createEmptyResult(name) {
|
|
269
|
+
return {
|
|
270
|
+
navigation: [],
|
|
271
|
+
name: name || 'Documentation',
|
|
272
|
+
description: 'Imported from Docusaurus',
|
|
273
|
+
files: new Map(),
|
|
274
|
+
assets: new Map(),
|
|
275
|
+
warnings: [],
|
|
276
|
+
stats: { pages: 0, groups: 0, transforms: { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 } },
|
|
277
|
+
sourceFormat: 'docusaurus',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, relative, basename, extname } from 'path';
|
|
3
|
+
import { findMdxFiles } from '../utils/files.js';
|
|
4
|
+
import { transformGitBookHints, transformGitBookTabs, transformGitBookSteps, transformGitBookExpandable, transformGitBookContentRef, transformGitBookEmbed, normalizeFrontmatter, rewriteImagePaths, } from './transform.js';
|
|
5
|
+
/**
|
|
6
|
+
* Import GitBook documentation.
|
|
7
|
+
*/
|
|
8
|
+
export function importGitBook(dir, name) {
|
|
9
|
+
const result = createEmptyResult(name);
|
|
10
|
+
const stats = { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 };
|
|
11
|
+
// Check for .gitbook.yaml root config
|
|
12
|
+
let rootPath = '';
|
|
13
|
+
const gitbookYaml = join(dir, '.gitbook.yaml');
|
|
14
|
+
if (existsSync(gitbookYaml)) {
|
|
15
|
+
try {
|
|
16
|
+
const yamlContent = readFileSync(gitbookYaml, 'utf-8');
|
|
17
|
+
const rootMatch = yamlContent.match(/root:\s*(.+)/);
|
|
18
|
+
if (rootMatch)
|
|
19
|
+
rootPath = rootMatch[1].trim().replace(/^\.\//, '');
|
|
20
|
+
}
|
|
21
|
+
catch { /* skip */ }
|
|
22
|
+
}
|
|
23
|
+
const contentDir = rootPath ? join(dir, rootPath) : dir;
|
|
24
|
+
// Parse SUMMARY.md for navigation
|
|
25
|
+
const summaryPath = join(contentDir, 'SUMMARY.md');
|
|
26
|
+
if (existsSync(summaryPath)) {
|
|
27
|
+
const summaryContent = readFileSync(summaryPath, 'utf-8');
|
|
28
|
+
const groups = parseSummaryMd(summaryContent);
|
|
29
|
+
for (const group of groups) {
|
|
30
|
+
const pages = [];
|
|
31
|
+
for (const ref of group.pageRefs) {
|
|
32
|
+
const page = processGitBookPage(contentDir, ref.path, ref.title, stats, result);
|
|
33
|
+
if (page)
|
|
34
|
+
pages.push(page);
|
|
35
|
+
}
|
|
36
|
+
if (pages.length > 0) {
|
|
37
|
+
result.navigation.push({ group: group.label, pages });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// No SUMMARY.md — walk files
|
|
43
|
+
const files = findMdxFiles(contentDir);
|
|
44
|
+
const pages = [];
|
|
45
|
+
for (const filePath of files) {
|
|
46
|
+
if (basename(filePath).toUpperCase() === 'SUMMARY.MD')
|
|
47
|
+
continue;
|
|
48
|
+
const rel = relative(contentDir, filePath);
|
|
49
|
+
const page = processGitBookPage(contentDir, rel, undefined, stats, result);
|
|
50
|
+
if (page)
|
|
51
|
+
pages.push(page);
|
|
52
|
+
}
|
|
53
|
+
if (pages.length > 0) {
|
|
54
|
+
result.navigation.push({ group: 'Documentation', pages });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Copy .gitbook/assets/
|
|
58
|
+
const assetsDir = join(dir, '.gitbook', 'assets');
|
|
59
|
+
if (existsSync(assetsDir)) {
|
|
60
|
+
collectGitBookAssets(assetsDir, result, stats);
|
|
61
|
+
}
|
|
62
|
+
result.stats = {
|
|
63
|
+
pages: result.files.size,
|
|
64
|
+
groups: result.navigation.length,
|
|
65
|
+
transforms: stats,
|
|
66
|
+
};
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
function parseSummaryMd(content) {
|
|
70
|
+
const groups = [];
|
|
71
|
+
let currentGroup = null;
|
|
72
|
+
const lines = content.split('\n');
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
// Group headers: ## Group Name or non-indented text without link
|
|
75
|
+
const headerMatch = line.match(/^#{1,3}\s+(.+)/);
|
|
76
|
+
if (headerMatch) {
|
|
77
|
+
if (currentGroup && currentGroup.pageRefs.length > 0) {
|
|
78
|
+
groups.push(currentGroup);
|
|
79
|
+
}
|
|
80
|
+
currentGroup = { label: headerMatch[1].trim(), pageRefs: [] };
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Page references: * [Title](path.md) or - [Title](path.md)
|
|
84
|
+
const linkMatch = line.match(/^[\s]*[*-]\s+\[([^\]]+)\]\(([^)]+)\)/);
|
|
85
|
+
if (linkMatch) {
|
|
86
|
+
if (!currentGroup) {
|
|
87
|
+
currentGroup = { label: 'Documentation', pageRefs: [] };
|
|
88
|
+
}
|
|
89
|
+
currentGroup.pageRefs.push({ title: linkMatch[1], path: linkMatch[2] });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (currentGroup && currentGroup.pageRefs.length > 0) {
|
|
93
|
+
groups.push(currentGroup);
|
|
94
|
+
}
|
|
95
|
+
// If no groups found, treat all links as one group
|
|
96
|
+
if (groups.length === 0) {
|
|
97
|
+
const allLinks = [];
|
|
98
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
101
|
+
allLinks.push({ title: match[1], path: match[2] });
|
|
102
|
+
}
|
|
103
|
+
if (allLinks.length > 0) {
|
|
104
|
+
groups.push({ label: 'Documentation', pageRefs: allLinks });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return groups;
|
|
108
|
+
}
|
|
109
|
+
function processGitBookPage(contentDir, pagePath, title, stats, result) {
|
|
110
|
+
const filePath = join(contentDir, pagePath);
|
|
111
|
+
// Guard: prevent path traversal outside content directory
|
|
112
|
+
if (!filePath.startsWith(contentDir)) {
|
|
113
|
+
result.warnings.push(`Path traversal blocked: ${pagePath}`);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (!existsSync(filePath)) {
|
|
117
|
+
result.warnings.push(`Page not found: ${pagePath}`);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
let content = readFileSync(filePath, 'utf-8');
|
|
121
|
+
const originalContent = content;
|
|
122
|
+
// Apply all GitBook transforms
|
|
123
|
+
content = transformGitBookHints(content);
|
|
124
|
+
content = transformGitBookTabs(content);
|
|
125
|
+
content = transformGitBookSteps(content);
|
|
126
|
+
content = transformGitBookExpandable(content);
|
|
127
|
+
content = transformGitBookContentRef(content);
|
|
128
|
+
content = transformGitBookEmbed(content);
|
|
129
|
+
content = normalizeFrontmatter(content);
|
|
130
|
+
// Count transforms
|
|
131
|
+
stats.callouts += countNewOccurrences(originalContent, content, '<Callout');
|
|
132
|
+
stats.tabs += countNewOccurrences(originalContent, content, '<TabPanel');
|
|
133
|
+
stats.steps += countNewOccurrences(originalContent, content, '<Step>');
|
|
134
|
+
stats.accordions += countNewOccurrences(originalContent, content, '<Accordion');
|
|
135
|
+
const pageTitle = title || extractTitle(content, filePath);
|
|
136
|
+
const slug = pagePath.replace(/\.(md|mdx)$/, '').replace(/\\/g, '/');
|
|
137
|
+
const outputPath = `content/docs/${slug}.mdx`;
|
|
138
|
+
result.files.set(outputPath, content);
|
|
139
|
+
return { title: pageTitle, slug, sourcePath: pagePath, content };
|
|
140
|
+
}
|
|
141
|
+
function collectGitBookAssets(assetsDir, result, stats) {
|
|
142
|
+
const imageMapping = new Map();
|
|
143
|
+
try {
|
|
144
|
+
for (const entry of readdirSync(assetsDir)) {
|
|
145
|
+
const fullPath = join(assetsDir, entry);
|
|
146
|
+
if (statSync(fullPath).isFile()) {
|
|
147
|
+
const dest = `public/images/${entry}`;
|
|
148
|
+
result.assets.set(dest, fullPath);
|
|
149
|
+
imageMapping.set(`.gitbook/assets/${entry}`, `/images/${entry}`);
|
|
150
|
+
imageMapping.set(`../.gitbook/assets/${entry}`, `/images/${entry}`);
|
|
151
|
+
stats.images++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch { /* skip */ }
|
|
156
|
+
// Rewrite image paths
|
|
157
|
+
if (imageMapping.size > 0) {
|
|
158
|
+
for (const [path, content] of result.files) {
|
|
159
|
+
result.files.set(path, rewriteImagePaths(content, imageMapping));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function extractTitle(content, filePath) {
|
|
164
|
+
const fmMatch = content.match(/^---\n[\s\S]*?title:\s*["']?([^"'\n]+)["']?\s*\n[\s\S]*?---/m);
|
|
165
|
+
if (fmMatch)
|
|
166
|
+
return fmMatch[1].trim();
|
|
167
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
168
|
+
if (h1Match)
|
|
169
|
+
return h1Match[1].trim();
|
|
170
|
+
return basename(filePath, extname(filePath)).replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
171
|
+
}
|
|
172
|
+
function countNewOccurrences(original, transformed, marker) {
|
|
173
|
+
const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
174
|
+
const origCount = (original.match(new RegExp(escaped, 'g')) || []).length;
|
|
175
|
+
const newCount = (transformed.match(new RegExp(escaped, 'g')) || []).length;
|
|
176
|
+
return Math.max(0, newCount - origCount);
|
|
177
|
+
}
|
|
178
|
+
function createEmptyResult(name) {
|
|
179
|
+
return {
|
|
180
|
+
navigation: [],
|
|
181
|
+
name: name || 'Documentation',
|
|
182
|
+
description: 'Imported from GitBook',
|
|
183
|
+
files: new Map(),
|
|
184
|
+
assets: new Map(),
|
|
185
|
+
warnings: [],
|
|
186
|
+
stats: { pages: 0, groups: 0, transforms: { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 } },
|
|
187
|
+
sourceFormat: 'gitbook',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImportResult, ImportFormat } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch docs from a GitHub URL and import them.
|
|
4
|
+
*/
|
|
5
|
+
export declare function importFromGitHub(owner: string, repo: string, path: string, ref: string, options?: {
|
|
6
|
+
format?: ImportFormat;
|
|
7
|
+
name?: string;
|
|
8
|
+
}): Promise<ImportResult>;
|