skrypt-ai 0.4.2 → 0.5.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 +94 -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.js +10 -3
- package/dist/cli.js +16 -3
- package/dist/commands/generate.js +37 -1
- 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/login.js +15 -4
- package/dist/commands/review-pr.js +10 -0
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +103 -0
- package/dist/generator/writer.js +12 -3
- 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/plugins/index.js +7 -0
- package/dist/scanner/index.js +37 -24
- package/dist/scanner/python.js +17 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +67 -9
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/package.json +4 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mintlify: <Note>content</Note>, <Warning>, <Tip>, <Info>, <Check>
|
|
3
|
+
*/
|
|
4
|
+
export declare function transformMintlifyCallouts(content: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Docusaurus: :::note[Title]\ncontent\n:::
|
|
7
|
+
*/
|
|
8
|
+
export declare function transformDocusaurusAdmonitions(content: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* GitBook: {% hint style="info" %}\ncontent\n{% endhint %}
|
|
11
|
+
*/
|
|
12
|
+
export declare function transformGitBookHints(content: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* ReadMe RDMD: > 📘 Title\n> content lines
|
|
15
|
+
*/
|
|
16
|
+
export declare function transformReadmeCallouts(content: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Notion: <aside>content</aside> and :::callout
|
|
19
|
+
*/
|
|
20
|
+
export declare function transformNotionCallouts(content: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Confluence: <ac:structured-macro ac:name="info">...<ac:rich-text-body>content</ac:rich-text-body>
|
|
23
|
+
*/
|
|
24
|
+
export declare function transformConfluenceCallouts(content: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Mintlify: <Tabs><Tab title="X">content</Tab></Tabs>
|
|
27
|
+
*/
|
|
28
|
+
export declare function transformMintlifyTabs(content: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Docusaurus: <Tabs><TabItem value="x" label="X">content</TabItem></Tabs>
|
|
31
|
+
*/
|
|
32
|
+
export declare function transformDocusaurusTabs(content: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* GitBook: {% tabs %}{% tab title="X" %}content{% endtab %}{% endtabs %}
|
|
35
|
+
*/
|
|
36
|
+
export declare function transformGitBookTabs(content: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* ReadMe [block:code] JSON → <CodeGroup>
|
|
39
|
+
*/
|
|
40
|
+
export declare function transformReadmeCodeBlocks(content: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* GitBook: {% stepper %}{% step %}...{% endstep %}{% endstepper %}
|
|
43
|
+
*/
|
|
44
|
+
export declare function transformGitBookSteps(content: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* GitBook: {% expandable title="X" %}content{% endexpandable %}
|
|
47
|
+
*/
|
|
48
|
+
export declare function transformGitBookExpandable(content: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* GitBook: {% content-ref url="path" %} → markdown link
|
|
51
|
+
*/
|
|
52
|
+
export declare function transformGitBookContentRef(content: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* GitBook: {% embed url="..." %} → plain URL
|
|
55
|
+
*/
|
|
56
|
+
export declare function transformGitBookEmbed(content: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Notion: <details><summary>Title</summary>content</details> → <Accordion>
|
|
59
|
+
*/
|
|
60
|
+
export declare function transformNotionToggles(content: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Confluence: Convert common HTML and AC macros to markdown
|
|
63
|
+
*/
|
|
64
|
+
export declare function transformConfluenceHtml(content: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Docusaurus: Strip import ... from '@theme/...' statements
|
|
67
|
+
*/
|
|
68
|
+
export declare function stripDocusaurusImports(content: string): string;
|
|
69
|
+
interface FrontmatterDefaults {
|
|
70
|
+
title?: string;
|
|
71
|
+
description?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Normalize frontmatter fields to Skrypt format
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeFrontmatter(content: string, defaults?: FrontmatterDefaults): string;
|
|
77
|
+
/**
|
|
78
|
+
* Rewrite image paths based on asset copy mapping
|
|
79
|
+
*/
|
|
80
|
+
export declare function rewriteImagePaths(content: string, mapping: Map<string, string>): string;
|
|
81
|
+
/**
|
|
82
|
+
* Strip 32-char hex UUID suffixes from Notion filenames
|
|
83
|
+
* e.g., "Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md" → "Getting Started.md"
|
|
84
|
+
*/
|
|
85
|
+
export declare function stripNotionUUIDs(filename: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Get sorting weight from frontmatter
|
|
88
|
+
*/
|
|
89
|
+
export declare function getSortWeight(content: string): number;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Content Transformation Engine
|
|
3
|
+
// Composable string→string transforms for all platform syntaxes
|
|
4
|
+
// ============================================================
|
|
5
|
+
// --- Callout Transforms ---
|
|
6
|
+
const MINTLIFY_CALLOUT_MAP = {
|
|
7
|
+
Note: 'note',
|
|
8
|
+
Warning: 'warning',
|
|
9
|
+
Tip: 'tip',
|
|
10
|
+
Info: 'info',
|
|
11
|
+
Check: 'success',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Mintlify: <Note>content</Note>, <Warning>, <Tip>, <Info>, <Check>
|
|
15
|
+
*/
|
|
16
|
+
export function transformMintlifyCallouts(content) {
|
|
17
|
+
for (const [tag, type] of Object.entries(MINTLIFY_CALLOUT_MAP)) {
|
|
18
|
+
const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'g');
|
|
19
|
+
content = content.replace(regex, `<Callout type="${type}">$1</Callout>`);
|
|
20
|
+
}
|
|
21
|
+
return content;
|
|
22
|
+
}
|
|
23
|
+
const DOCUSAURUS_ADMONITION_MAP = {
|
|
24
|
+
note: 'note',
|
|
25
|
+
tip: 'tip',
|
|
26
|
+
info: 'info',
|
|
27
|
+
caution: 'warning',
|
|
28
|
+
danger: 'error',
|
|
29
|
+
warning: 'warning',
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Docusaurus: :::note[Title]\ncontent\n:::
|
|
33
|
+
*/
|
|
34
|
+
export function transformDocusaurusAdmonitions(content) {
|
|
35
|
+
return content.replace(/:::(note|tip|info|caution|danger|warning)(?:\[(.+?)\])?\n([\s\S]*?):::/g, (_match, type, title, body) => {
|
|
36
|
+
const calloutType = DOCUSAURUS_ADMONITION_MAP[type] || 'info';
|
|
37
|
+
const titleAttr = title ? ` title="${title}"` : '';
|
|
38
|
+
return `<Callout type="${calloutType}"${titleAttr}>${body}</Callout>`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const GITBOOK_HINT_MAP = {
|
|
42
|
+
info: 'info',
|
|
43
|
+
warning: 'warning',
|
|
44
|
+
danger: 'error',
|
|
45
|
+
success: 'success',
|
|
46
|
+
tip: 'tip',
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* GitBook: {% hint style="info" %}\ncontent\n{% endhint %}
|
|
50
|
+
*/
|
|
51
|
+
export function transformGitBookHints(content) {
|
|
52
|
+
return content.replace(/\{%\s*hint\s+style="(\w+)"\s*%\}([\s\S]*?)\{%\s*endhint\s*%\}/g, (_match, style, body) => {
|
|
53
|
+
const calloutType = GITBOOK_HINT_MAP[style] || 'info';
|
|
54
|
+
return `<Callout type="${calloutType}">${body.trim()}</Callout>`;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const README_EMOJI_MAP = {
|
|
58
|
+
'\u{1F4D8}': 'info', // 📘
|
|
59
|
+
'\u{2139}\uFE0F': 'info', // ℹ️
|
|
60
|
+
'\u{1F44D}': 'success', // 👍
|
|
61
|
+
'\u{2705}': 'success', // ✅
|
|
62
|
+
'\u{1F6A7}': 'warning', // 🚧
|
|
63
|
+
'\u{26A0}\uFE0F': 'warning', // ⚠️
|
|
64
|
+
'\u{2757}': 'error', // ❗
|
|
65
|
+
'\u{1F6D1}': 'error', // 🛑
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* ReadMe RDMD: > 📘 Title\n> content lines
|
|
69
|
+
*/
|
|
70
|
+
export function transformReadmeCallouts(content) {
|
|
71
|
+
// Modern RDMD emoji format
|
|
72
|
+
const emojiPattern = Object.keys(README_EMOJI_MAP).join('|');
|
|
73
|
+
const rdmdRegex = new RegExp(`> (${emojiPattern}) (.+)\\n((?:> .+\\n?)*)`, 'g');
|
|
74
|
+
content = content.replace(rdmdRegex, (_match, emoji, title, bodyLines) => {
|
|
75
|
+
const type = README_EMOJI_MAP[emoji] || 'info';
|
|
76
|
+
const body = bodyLines.replace(/^> /gm, '').trim();
|
|
77
|
+
return `<Callout type="${type}" title="${title}">\n${body}\n</Callout>`;
|
|
78
|
+
});
|
|
79
|
+
// Legacy [block:callout] format
|
|
80
|
+
content = content.replace(/\[block:callout\]\n?([\s\S]*?)\n?\[\/block\]/g, (_match, jsonStr) => {
|
|
81
|
+
try {
|
|
82
|
+
const data = JSON.parse(jsonStr);
|
|
83
|
+
const type = data.type === 'warning' ? 'warning' :
|
|
84
|
+
data.type === 'danger' ? 'error' :
|
|
85
|
+
data.type === 'success' ? 'success' : 'info';
|
|
86
|
+
const titleAttr = data.title ? ` title="${data.title}"` : '';
|
|
87
|
+
return `<Callout type="${type}"${titleAttr}>\n${data.body || ''}\n</Callout>`;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return _match; // leave as-is if JSON parse fails
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return content;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Notion: <aside>content</aside> and :::callout
|
|
97
|
+
*/
|
|
98
|
+
export function transformNotionCallouts(content) {
|
|
99
|
+
// <aside> blocks
|
|
100
|
+
content = content.replace(/<aside>([\s\S]*?)<\/aside>/g, (_match, body) => {
|
|
101
|
+
// Strip common Notion emoji prefixes (single codepoint emojis + variation selectors)
|
|
102
|
+
const cleaned = body.replace(/^[\s\n]*(?:☝️|💡|⚠️|❗|📌|🔥|✨|🎯|📝)\s*/u, '').trim();
|
|
103
|
+
return `<Callout type="info">${cleaned}</Callout>`;
|
|
104
|
+
});
|
|
105
|
+
// :::callout blocks (Notion API export format)
|
|
106
|
+
content = content.replace(/:::callout([\s\S]*?):::/g, (_match, body) => `<Callout type="info">${body.trim()}</Callout>`);
|
|
107
|
+
return content;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Confluence: <ac:structured-macro ac:name="info">...<ac:rich-text-body>content</ac:rich-text-body>
|
|
111
|
+
*/
|
|
112
|
+
export function transformConfluenceCallouts(content) {
|
|
113
|
+
return content.replace(/<ac:structured-macro[^>]*ac:name="(info|note|warning|tip)"[^>]*>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, type, body) => {
|
|
114
|
+
const cleaned = stripHtmlTags(body).trim();
|
|
115
|
+
return `<Callout type="${type}">${cleaned}</Callout>`;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// --- Tabs Transforms ---
|
|
119
|
+
/**
|
|
120
|
+
* Mintlify: <Tabs><Tab title="X">content</Tab></Tabs>
|
|
121
|
+
*/
|
|
122
|
+
export function transformMintlifyTabs(content) {
|
|
123
|
+
return content.replace(/<Tabs>([\s\S]*?)<\/Tabs>/g, (_match, inner) => {
|
|
124
|
+
const tabs = [];
|
|
125
|
+
const tabRegex = /<Tab\s+title="([^"]+)">([\s\S]*?)<\/Tab>/g;
|
|
126
|
+
let tabMatch;
|
|
127
|
+
while ((tabMatch = tabRegex.exec(inner)) !== null) {
|
|
128
|
+
tabs.push({ title: tabMatch[1], content: tabMatch[2] });
|
|
129
|
+
}
|
|
130
|
+
if (tabs.length === 0)
|
|
131
|
+
return _match;
|
|
132
|
+
const defaultValue = tabs[0].title;
|
|
133
|
+
const tabList = tabs.map(t => ` <Tab value="${t.title}">${t.title}</Tab>`).join('\n');
|
|
134
|
+
const panels = tabs.map(t => `<TabPanel value="${t.title}">${t.content}</TabPanel>`).join('\n');
|
|
135
|
+
return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Docusaurus: <Tabs><TabItem value="x" label="X">content</TabItem></Tabs>
|
|
140
|
+
*/
|
|
141
|
+
export function transformDocusaurusTabs(content) {
|
|
142
|
+
return content.replace(/<Tabs[^>]*>([\s\S]*?)<\/Tabs>/g, (_match, inner) => {
|
|
143
|
+
const tabs = [];
|
|
144
|
+
const tabRegex = /<TabItem\s+value="([^"]+)"(?:\s+label="([^"]*)")?>([\s\S]*?)<\/TabItem>/g;
|
|
145
|
+
let tabMatch;
|
|
146
|
+
while ((tabMatch = tabRegex.exec(inner)) !== null) {
|
|
147
|
+
tabs.push({ value: tabMatch[1], label: tabMatch[2] || tabMatch[1], content: tabMatch[3] });
|
|
148
|
+
}
|
|
149
|
+
if (tabs.length === 0)
|
|
150
|
+
return _match;
|
|
151
|
+
const defaultValue = tabs[0].value;
|
|
152
|
+
const tabList = tabs.map(t => ` <Tab value="${t.value}">${t.label}</Tab>`).join('\n');
|
|
153
|
+
const panels = tabs.map(t => `<TabPanel value="${t.value}">${t.content}</TabPanel>`).join('\n');
|
|
154
|
+
return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* GitBook: {% tabs %}{% tab title="X" %}content{% endtab %}{% endtabs %}
|
|
159
|
+
*/
|
|
160
|
+
export function transformGitBookTabs(content) {
|
|
161
|
+
return content.replace(/\{%\s*tabs\s*%\}([\s\S]*?)\{%\s*endtabs\s*%\}/g, (_match, inner) => {
|
|
162
|
+
const tabs = [];
|
|
163
|
+
const tabRegex = /\{%\s*tab\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endtab\s*%\}/g;
|
|
164
|
+
let tabMatch;
|
|
165
|
+
while ((tabMatch = tabRegex.exec(inner)) !== null) {
|
|
166
|
+
tabs.push({ title: tabMatch[1], content: tabMatch[2].trim() });
|
|
167
|
+
}
|
|
168
|
+
if (tabs.length === 0)
|
|
169
|
+
return _match;
|
|
170
|
+
const defaultValue = tabs[0].title;
|
|
171
|
+
const tabList = tabs.map(t => ` <Tab value="${t.title}">${t.title}</Tab>`).join('\n');
|
|
172
|
+
const panels = tabs.map(t => `<TabPanel value="${t.title}">\n${t.content}\n</TabPanel>`).join('\n');
|
|
173
|
+
return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* ReadMe [block:code] JSON → <CodeGroup>
|
|
178
|
+
*/
|
|
179
|
+
export function transformReadmeCodeBlocks(content) {
|
|
180
|
+
return content.replace(/\[block:code\]\n?([\s\S]*?)\n?\[\/block\]/g, (_match, jsonStr) => {
|
|
181
|
+
try {
|
|
182
|
+
const data = JSON.parse(jsonStr);
|
|
183
|
+
if (!data.codes || !Array.isArray(data.codes))
|
|
184
|
+
return _match;
|
|
185
|
+
if (data.codes.length === 1) {
|
|
186
|
+
const c = data.codes[0];
|
|
187
|
+
const lang = c.language || '';
|
|
188
|
+
const name = c.name ? ` ${c.name}` : '';
|
|
189
|
+
return `\`\`\`${lang}${name}\n${c.code}\n\`\`\``;
|
|
190
|
+
}
|
|
191
|
+
const blocks = data.codes.map((c) => {
|
|
192
|
+
const lang = c.language || '';
|
|
193
|
+
const name = c.name ? ` ${c.name}` : '';
|
|
194
|
+
return `\`\`\`${lang}${name}\n${c.code || ''}\n\`\`\``;
|
|
195
|
+
}).join('\n\n');
|
|
196
|
+
return `<CodeGroup>\n\n${blocks}\n\n</CodeGroup>`;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return _match;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// --- Other Component Transforms ---
|
|
204
|
+
/**
|
|
205
|
+
* GitBook: {% stepper %}{% step %}...{% endstep %}{% endstepper %}
|
|
206
|
+
*/
|
|
207
|
+
export function transformGitBookSteps(content) {
|
|
208
|
+
return content.replace(/\{%\s*stepper\s*%\}([\s\S]*?)\{%\s*endstepper\s*%\}/g, (_match, inner) => {
|
|
209
|
+
const steps = inner.split(/\{%\s*step\s*%\}/).filter(s => s.trim());
|
|
210
|
+
const mapped = steps.map(step => {
|
|
211
|
+
const body = step.replace(/\{%\s*endstep\s*%\}/g, '').trim();
|
|
212
|
+
return `<Step>\n${body}\n</Step>`;
|
|
213
|
+
}).join('\n');
|
|
214
|
+
return `<Steps>\n${mapped}\n</Steps>`;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* GitBook: {% expandable title="X" %}content{% endexpandable %}
|
|
219
|
+
*/
|
|
220
|
+
export function transformGitBookExpandable(content) {
|
|
221
|
+
return content.replace(/\{%\s*expandable\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endexpandable\s*%\}/g, (_match, title, body) => `<Accordion title="${title}">\n${body.trim()}\n</Accordion>`);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* GitBook: {% content-ref url="path" %} → markdown link
|
|
225
|
+
*/
|
|
226
|
+
export function transformGitBookContentRef(content) {
|
|
227
|
+
return content.replace(/\{%\s*content-ref\s+url="([^"]+)"\s*%\}[\s\S]*?\{%\s*endcontent-ref\s*%\}/g, (_match, url) => {
|
|
228
|
+
const label = url.replace(/\.md$/, '').split('/').pop() || url;
|
|
229
|
+
return `[${label}](${url})`;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* GitBook: {% embed url="..." %} → plain URL
|
|
234
|
+
*/
|
|
235
|
+
export function transformGitBookEmbed(content) {
|
|
236
|
+
return content.replace(/\{%\s*embed\s+url="([^"]+)"\s*%\}/g, (_match, url) => url);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Notion: <details><summary>Title</summary>content</details> → <Accordion>
|
|
240
|
+
*/
|
|
241
|
+
export function transformNotionToggles(content) {
|
|
242
|
+
return content.replace(/<details>\s*<summary>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/g, (_match, title, body) => `<Accordion title="${title.trim()}">\n${body.trim()}\n</Accordion>`);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Confluence: Convert common HTML and AC macros to markdown
|
|
246
|
+
*/
|
|
247
|
+
export function transformConfluenceHtml(content) {
|
|
248
|
+
// Code macros
|
|
249
|
+
content = content.replace(/<ac:structured-macro[^>]*ac:name="code"[^>]*>[\s\S]*?(?:<ac:parameter ac:name="language">([^<]*)<\/ac:parameter>)?[\s\S]*?<ac:plain-text-body><!\[CDATA\[([\s\S]*?)\]\]><\/ac:plain-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, lang, code) => `\`\`\`${lang || ''}\n${code}\n\`\`\``);
|
|
250
|
+
// Expand macros → Accordion
|
|
251
|
+
content = content.replace(/<ac:structured-macro[^>]*ac:name="expand"[^>]*>[\s\S]*?<ac:parameter ac:name="title">([^<]*)<\/ac:parameter>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, title, body) => `<Accordion title="${title}">\n${stripHtmlTags(body).trim()}\n</Accordion>`);
|
|
252
|
+
// Strip remaining AC macros
|
|
253
|
+
content = content.replace(/<ac:[^>]+>|<\/ac:[^>]+>/g, '');
|
|
254
|
+
// Convert common HTML
|
|
255
|
+
content = convertBasicHtml(content);
|
|
256
|
+
return content;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Docusaurus: Strip import ... from '@theme/...' statements
|
|
260
|
+
*/
|
|
261
|
+
export function stripDocusaurusImports(content) {
|
|
262
|
+
return content.replace(/^import\s+.*from\s+['"]@theme\/.*['"];?\s*\n?/gm, '');
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Normalize frontmatter fields to Skrypt format
|
|
266
|
+
*/
|
|
267
|
+
export function normalizeFrontmatter(content, defaults) {
|
|
268
|
+
const { data, body } = parseFrontmatterRaw(content);
|
|
269
|
+
if (!data && !defaults)
|
|
270
|
+
return content;
|
|
271
|
+
const fm = data || {};
|
|
272
|
+
// Map known field aliases
|
|
273
|
+
if (!fm.title && fm.sidebarTitle)
|
|
274
|
+
fm.title = fm.sidebarTitle;
|
|
275
|
+
if (!fm.description && fm.excerpt)
|
|
276
|
+
fm.description = fm.excerpt;
|
|
277
|
+
// Apply defaults
|
|
278
|
+
if (defaults?.title && !fm.title)
|
|
279
|
+
fm.title = defaults.title;
|
|
280
|
+
if (defaults?.description && !fm.description)
|
|
281
|
+
fm.description = defaults.description;
|
|
282
|
+
// Map icon field
|
|
283
|
+
if (fm.sidebar_icon && !fm.icon)
|
|
284
|
+
fm.icon = fm.sidebar_icon;
|
|
285
|
+
// Remove non-Skrypt fields
|
|
286
|
+
const removeFields = ['sidebar_position', 'order', 'weight', 'slug', 'sidebarTitle',
|
|
287
|
+
'excerpt', 'sidebar_icon', 'sidebar_label', 'sidebar_class_name',
|
|
288
|
+
'pagination_next', 'pagination_prev', 'custom_edit_url',
|
|
289
|
+
'displayed_sidebar', 'hide_table_of_contents', 'hide_title',
|
|
290
|
+
'keywords', 'tags', 'image', 'last_update', 'category'];
|
|
291
|
+
for (const field of removeFields) {
|
|
292
|
+
delete fm[field];
|
|
293
|
+
}
|
|
294
|
+
// Build normalized frontmatter
|
|
295
|
+
const skryptFields = {};
|
|
296
|
+
if (fm.title)
|
|
297
|
+
skryptFields.title = fm.title;
|
|
298
|
+
if (fm.description)
|
|
299
|
+
skryptFields.description = fm.description;
|
|
300
|
+
if (fm.icon)
|
|
301
|
+
skryptFields.icon = fm.icon;
|
|
302
|
+
// Include any remaining unknown fields
|
|
303
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
304
|
+
if (!(key in skryptFields) && !removeFields.includes(key)) {
|
|
305
|
+
skryptFields[key] = value;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (Object.keys(skryptFields).length === 0)
|
|
309
|
+
return body;
|
|
310
|
+
const fmLines = Object.entries(skryptFields)
|
|
311
|
+
.map(([k, v]) => {
|
|
312
|
+
if (typeof v === 'string') {
|
|
313
|
+
// Escape double quotes and backslashes in YAML string values
|
|
314
|
+
const escaped = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
315
|
+
return `${k}: "${escaped}"`;
|
|
316
|
+
}
|
|
317
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
318
|
+
})
|
|
319
|
+
.join('\n');
|
|
320
|
+
return `---\n${fmLines}\n---\n\n${body}`;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Rewrite image paths based on asset copy mapping
|
|
324
|
+
*/
|
|
325
|
+
export function rewriteImagePaths(content, mapping) {
|
|
326
|
+
for (const [oldPath, newPath] of mapping) {
|
|
327
|
+
content = content.replaceAll(oldPath, newPath);
|
|
328
|
+
}
|
|
329
|
+
return content;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Strip 32-char hex UUID suffixes from Notion filenames
|
|
333
|
+
* e.g., "Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md" → "Getting Started.md"
|
|
334
|
+
*/
|
|
335
|
+
export function stripNotionUUIDs(filename) {
|
|
336
|
+
return filename.replace(/\s+[0-9a-f]{32}/g, '');
|
|
337
|
+
}
|
|
338
|
+
// --- Helpers ---
|
|
339
|
+
function stripHtmlTags(html) {
|
|
340
|
+
return html
|
|
341
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
342
|
+
.replace(/<\/p>/gi, '\n\n')
|
|
343
|
+
.replace(/<[^>]+>/g, '')
|
|
344
|
+
.replace(/&/g, '&')
|
|
345
|
+
.replace(/</g, '<')
|
|
346
|
+
.replace(/>/g, '>')
|
|
347
|
+
.replace(/"/g, '"')
|
|
348
|
+
.replace(/'/g, "'")
|
|
349
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
350
|
+
}
|
|
351
|
+
function convertBasicHtml(html) {
|
|
352
|
+
let md = html;
|
|
353
|
+
// Headings
|
|
354
|
+
md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, '# $1\n\n');
|
|
355
|
+
md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, '## $1\n\n');
|
|
356
|
+
md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, '### $1\n\n');
|
|
357
|
+
md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, '#### $1\n\n');
|
|
358
|
+
// Bold / italic
|
|
359
|
+
md = md.replace(/<strong>([\s\S]*?)<\/strong>/gi, '**$1**');
|
|
360
|
+
md = md.replace(/<b>([\s\S]*?)<\/b>/gi, '**$1**');
|
|
361
|
+
md = md.replace(/<em>([\s\S]*?)<\/em>/gi, '*$1*');
|
|
362
|
+
md = md.replace(/<i>([\s\S]*?)<\/i>/gi, '*$1*');
|
|
363
|
+
// Links
|
|
364
|
+
md = md.replace(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, '[$2]($1)');
|
|
365
|
+
// Images
|
|
366
|
+
md = md.replace(/<img[^>]+src="([^"]*)"(?:[^>]*alt="([^"]*)")?[^>]*\/?>/gi, '');
|
|
367
|
+
// Lists
|
|
368
|
+
md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n');
|
|
369
|
+
md = md.replace(/<\/?[uo]l[^>]*>/gi, '\n');
|
|
370
|
+
// Paragraphs & breaks
|
|
371
|
+
md = md.replace(/<\/p>/gi, '\n\n');
|
|
372
|
+
md = md.replace(/<p[^>]*>/gi, '');
|
|
373
|
+
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
374
|
+
// Code
|
|
375
|
+
md = md.replace(/<code>([\s\S]*?)<\/code>/gi, '`$1`');
|
|
376
|
+
md = md.replace(/<pre>([\s\S]*?)<\/pre>/gi, '```\n$1\n```');
|
|
377
|
+
// Tables
|
|
378
|
+
md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
|
|
379
|
+
return convertHtmlTable(tableContent);
|
|
380
|
+
});
|
|
381
|
+
// Strip remaining HTML tags (preserve Skrypt components like Accordion, Callout, Steps, etc.)
|
|
382
|
+
md = md.replace(/<\/?(?!Accordion|Callout|Steps|Step|Tabs|Tab|TabList|TabPanel|CodeGroup|Card|CardGroup)[a-z][^>]*>/gi, '');
|
|
383
|
+
// Decode entities
|
|
384
|
+
md = md.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
|
|
385
|
+
return md;
|
|
386
|
+
}
|
|
387
|
+
function convertHtmlTable(tableHtml) {
|
|
388
|
+
const rows = [];
|
|
389
|
+
const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
|
|
390
|
+
let rowMatch;
|
|
391
|
+
while ((rowMatch = rowRegex.exec(tableHtml)) !== null) {
|
|
392
|
+
const cells = [];
|
|
393
|
+
const cellRegex = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
|
|
394
|
+
let cellMatch;
|
|
395
|
+
while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
|
|
396
|
+
cells.push(cellMatch[1].replace(/<[^>]+>/g, '').trim());
|
|
397
|
+
}
|
|
398
|
+
if (cells.length > 0)
|
|
399
|
+
rows.push(cells);
|
|
400
|
+
}
|
|
401
|
+
if (rows.length === 0)
|
|
402
|
+
return '';
|
|
403
|
+
const colCount = Math.max(...rows.map(r => r.length));
|
|
404
|
+
const lines = [];
|
|
405
|
+
for (let i = 0; i < rows.length; i++) {
|
|
406
|
+
const paddedCells = Array.from({ length: colCount }, (_, j) => rows[i][j] || '');
|
|
407
|
+
lines.push('| ' + paddedCells.join(' | ') + ' |');
|
|
408
|
+
if (i === 0) {
|
|
409
|
+
lines.push('| ' + paddedCells.map(() => '---').join(' | ') + ' |');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return '\n' + lines.join('\n') + '\n';
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Simple frontmatter parser: splits on --- markers
|
|
416
|
+
*/
|
|
417
|
+
function parseFrontmatterRaw(content) {
|
|
418
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
419
|
+
if (!match)
|
|
420
|
+
return { data: null, body: content };
|
|
421
|
+
const yamlStr = match[1];
|
|
422
|
+
const body = match[2];
|
|
423
|
+
// Simple YAML key: value parser (handles strings, numbers, booleans)
|
|
424
|
+
const data = {};
|
|
425
|
+
for (const line of yamlStr.split('\n')) {
|
|
426
|
+
const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
427
|
+
if (!kvMatch)
|
|
428
|
+
continue;
|
|
429
|
+
const key = kvMatch[1];
|
|
430
|
+
let value = kvMatch[2].trim();
|
|
431
|
+
// Strip quotes
|
|
432
|
+
if (typeof value === 'string') {
|
|
433
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
434
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
435
|
+
value = value.slice(1, -1);
|
|
436
|
+
}
|
|
437
|
+
else if (value === 'true')
|
|
438
|
+
value = true;
|
|
439
|
+
else if (value === 'false')
|
|
440
|
+
value = false;
|
|
441
|
+
else if (/^\d+$/.test(value))
|
|
442
|
+
value = parseInt(value, 10);
|
|
443
|
+
}
|
|
444
|
+
data[key] = value;
|
|
445
|
+
}
|
|
446
|
+
return { data, body };
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get sorting weight from frontmatter
|
|
450
|
+
*/
|
|
451
|
+
export function getSortWeight(content) {
|
|
452
|
+
const { data } = parseFrontmatterRaw(content);
|
|
453
|
+
if (!data)
|
|
454
|
+
return Infinity;
|
|
455
|
+
const weight = data.sidebar_position ?? data.order ?? data.weight ?? data.position;
|
|
456
|
+
return typeof weight === 'number' ? weight : Infinity;
|
|
457
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ImportFormat = 'mintlify' | 'docusaurus' | 'gitbook' | 'readme' | 'notion' | 'confluence' | 'markdown';
|
|
2
|
+
export interface ImportedPage {
|
|
3
|
+
title: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
content: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
frontmatter?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ImportedGroup {
|
|
11
|
+
group: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
pages: ImportedPage[];
|
|
14
|
+
}
|
|
15
|
+
export interface TransformStats {
|
|
16
|
+
callouts: number;
|
|
17
|
+
tabs: number;
|
|
18
|
+
codeGroups: number;
|
|
19
|
+
steps: number;
|
|
20
|
+
accordions: number;
|
|
21
|
+
images: number;
|
|
22
|
+
other: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ImportResult {
|
|
25
|
+
navigation: ImportedGroup[];
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
files: Map<string, string>;
|
|
29
|
+
assets: Map<string, string>;
|
|
30
|
+
warnings: string[];
|
|
31
|
+
stats: {
|
|
32
|
+
pages: number;
|
|
33
|
+
groups: number;
|
|
34
|
+
transforms: TransformStats;
|
|
35
|
+
};
|
|
36
|
+
sourceFormat: ImportFormat;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/plugins/index.js
CHANGED
|
@@ -23,6 +23,8 @@ export class PluginManager {
|
|
|
23
23
|
if (!configFile)
|
|
24
24
|
return;
|
|
25
25
|
try {
|
|
26
|
+
console.log(` Loading config: ${configFile}`);
|
|
27
|
+
console.warn(' ⚠ Plugin configs execute code — only load trusted files');
|
|
26
28
|
const configUrl = pathToFileURL(configFile).href;
|
|
27
29
|
const module = await import(configUrl);
|
|
28
30
|
const config = module.default || module;
|
|
@@ -57,6 +59,11 @@ export class PluginManager {
|
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
59
61
|
async loadPluginByName(name) {
|
|
62
|
+
// Validate plugin name — must be a valid npm package name, no path traversal
|
|
63
|
+
if (!/^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/.test(name)) {
|
|
64
|
+
console.warn(`Skipping plugin with invalid name: ${name}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
60
67
|
// Try to load from node_modules
|
|
61
68
|
try {
|
|
62
69
|
const module = await import(name);
|