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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot capture orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates MDX parsing, browser capture, diff detection, and file output.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join, resolve } from 'path';
|
|
8
|
+
import { findMdxFiles } from '../utils/files.js';
|
|
9
|
+
import { parseScreenshotDirectives } from './parser.js';
|
|
10
|
+
import { captureScreenshots } from './browser.js';
|
|
11
|
+
import { loadHashStore, saveHashStore, computeHash, hasChanged } from './diff.js';
|
|
12
|
+
/**
|
|
13
|
+
* Run the screenshot capture pipeline.
|
|
14
|
+
*
|
|
15
|
+
* 1. Find all MDX files in docsDir
|
|
16
|
+
* 2. Parse <Screenshot> directives from each
|
|
17
|
+
* 3. Validate base URL reachability
|
|
18
|
+
* 4. Capture screenshots via headless browser
|
|
19
|
+
* 5. Compare hashes for --diff mode
|
|
20
|
+
* 6. Write changed files to outputDir
|
|
21
|
+
* 7. Write manifest and hash store
|
|
22
|
+
*/
|
|
23
|
+
export async function runCapture(docsDir, options) {
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
const resolvedDocsDir = resolve(docsDir);
|
|
26
|
+
const resolvedOutputDir = resolve(options.outputDir);
|
|
27
|
+
// 1. Find MDX files
|
|
28
|
+
const mdxFiles = findMdxFiles(resolvedDocsDir);
|
|
29
|
+
// 2. Parse directives
|
|
30
|
+
const allDirectives = mdxFiles.flatMap(filePath => {
|
|
31
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
32
|
+
return parseScreenshotDirectives(content, filePath, options.baseUrl);
|
|
33
|
+
});
|
|
34
|
+
if (allDirectives.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
directives: 0,
|
|
37
|
+
captured: 0,
|
|
38
|
+
unchanged: 0,
|
|
39
|
+
failed: 0,
|
|
40
|
+
duration: Date.now() - startTime,
|
|
41
|
+
results: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// 3. Dry run
|
|
45
|
+
if (options.dryRun) {
|
|
46
|
+
console.log(`\n Found ${allDirectives.length} screenshot directive(s):`);
|
|
47
|
+
for (const d of allDirectives) {
|
|
48
|
+
const sel = d.selector ? ` (${d.selector})` : ' (full page)';
|
|
49
|
+
console.log(` ${d.url}${sel} → ${d.alt}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('\n [dry run — no files written]\n');
|
|
52
|
+
return {
|
|
53
|
+
directives: allDirectives.length,
|
|
54
|
+
captured: 0,
|
|
55
|
+
unchanged: 0,
|
|
56
|
+
failed: 0,
|
|
57
|
+
duration: Date.now() - startTime,
|
|
58
|
+
results: [],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// 4. Validate base URL reachability
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(options.baseUrl, {
|
|
64
|
+
method: 'HEAD',
|
|
65
|
+
signal: AbortSignal.timeout(5000),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
console.error(`\n Base URL ${options.baseUrl} returned ${response.status}`);
|
|
69
|
+
console.error(' Make sure your dev server is running.\n');
|
|
70
|
+
return {
|
|
71
|
+
directives: allDirectives.length,
|
|
72
|
+
captured: 0,
|
|
73
|
+
unchanged: 0,
|
|
74
|
+
failed: allDirectives.length,
|
|
75
|
+
duration: Date.now() - startTime,
|
|
76
|
+
results: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
console.error(`\n Cannot reach ${options.baseUrl}`);
|
|
82
|
+
console.error(' Start your dev server first, then run heal again.\n');
|
|
83
|
+
return {
|
|
84
|
+
directives: allDirectives.length,
|
|
85
|
+
captured: 0,
|
|
86
|
+
unchanged: 0,
|
|
87
|
+
failed: allDirectives.length,
|
|
88
|
+
duration: Date.now() - startTime,
|
|
89
|
+
results: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// 5. Load hash store for diff mode
|
|
93
|
+
const projectDir = resolvedDocsDir;
|
|
94
|
+
const hashStore = options.diff ? loadHashStore(projectDir) : {};
|
|
95
|
+
// 6. Capture all screenshots
|
|
96
|
+
console.log(`\n Capturing ${allDirectives.length} screenshot(s)...`);
|
|
97
|
+
const results = await captureScreenshots(allDirectives, options);
|
|
98
|
+
// 7. Process results — write files, check diffs
|
|
99
|
+
mkdirSync(resolvedOutputDir, { recursive: true });
|
|
100
|
+
let captured = 0;
|
|
101
|
+
let unchanged = 0;
|
|
102
|
+
let failed = 0;
|
|
103
|
+
const newHashStore = { ...hashStore };
|
|
104
|
+
const manifestEntries = {};
|
|
105
|
+
for (const result of results) {
|
|
106
|
+
if (result.status === 'failed') {
|
|
107
|
+
failed++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const lightBuffer = getBuffer(result, 'light');
|
|
111
|
+
if (!lightBuffer) {
|
|
112
|
+
failed++;
|
|
113
|
+
result.status = 'failed';
|
|
114
|
+
result.error = 'No screenshot data captured';
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const hash = computeHash(lightBuffer);
|
|
118
|
+
// Diff check
|
|
119
|
+
if (options.diff && !hasChanged(result.filename, hash, hashStore)) {
|
|
120
|
+
unchanged++;
|
|
121
|
+
result.status = 'unchanged';
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// Write light screenshot
|
|
125
|
+
const outputPath = join(resolvedOutputDir, result.filename);
|
|
126
|
+
writeFileSync(outputPath, lightBuffer);
|
|
127
|
+
newHashStore[result.filename] = hash;
|
|
128
|
+
captured++;
|
|
129
|
+
// Write dark variant if present
|
|
130
|
+
if (result.darkFilename) {
|
|
131
|
+
const darkBuffer = getBuffer(result, 'dark');
|
|
132
|
+
if (darkBuffer) {
|
|
133
|
+
const darkPath = join(resolvedOutputDir, result.darkFilename);
|
|
134
|
+
writeFileSync(darkPath, darkBuffer);
|
|
135
|
+
newHashStore[result.darkFilename] = computeHash(darkBuffer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Build manifest entry
|
|
139
|
+
manifestEntries[result.filename] = {
|
|
140
|
+
url: result.directive.url,
|
|
141
|
+
selector: result.directive.selector,
|
|
142
|
+
filename: result.filename,
|
|
143
|
+
darkFilename: result.darkFilename,
|
|
144
|
+
capturedAt: new Date().toISOString(),
|
|
145
|
+
hash,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// 8. Write manifest
|
|
149
|
+
const manifest = {
|
|
150
|
+
version: 1,
|
|
151
|
+
capturedAt: new Date().toISOString(),
|
|
152
|
+
baseUrl: options.baseUrl,
|
|
153
|
+
viewport: options.viewport,
|
|
154
|
+
screenshots: manifestEntries,
|
|
155
|
+
};
|
|
156
|
+
writeFileSync(join(resolvedOutputDir, '_manifest.json'), JSON.stringify(manifest, null, 2), 'utf-8');
|
|
157
|
+
// 9. Save hash store
|
|
158
|
+
if (options.diff) {
|
|
159
|
+
saveHashStore(projectDir, newHashStore);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
directives: allDirectives.length,
|
|
163
|
+
captured,
|
|
164
|
+
unchanged,
|
|
165
|
+
failed,
|
|
166
|
+
duration: Date.now() - startTime,
|
|
167
|
+
results,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extract buffer from result (stored as internal _buffer or _buffers property).
|
|
172
|
+
*/
|
|
173
|
+
function getBuffer(result, variant) {
|
|
174
|
+
const r = result;
|
|
175
|
+
if (r._buffers) {
|
|
176
|
+
return variant === 'light' ? r._buffers.light : r._buffers.dark;
|
|
177
|
+
}
|
|
178
|
+
if (variant === 'light' && r._buffer) {
|
|
179
|
+
return r._buffer;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Print a capture report to stdout.
|
|
185
|
+
*/
|
|
186
|
+
export function printCaptureReport(report) {
|
|
187
|
+
if (report.directives === 0) {
|
|
188
|
+
console.log('\n No <Screenshot> directives found in docs.\n');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const parts = [];
|
|
192
|
+
if (report.captured > 0)
|
|
193
|
+
parts.push(`${report.captured} captured`);
|
|
194
|
+
if (report.unchanged > 0)
|
|
195
|
+
parts.push(`${report.unchanged} unchanged`);
|
|
196
|
+
if (report.failed > 0)
|
|
197
|
+
parts.push(`${report.failed} failed`);
|
|
198
|
+
console.log(`\n Screenshots: ${parts.join(', ')} (${report.duration}ms)`);
|
|
199
|
+
// Show failures
|
|
200
|
+
const failures = report.results.filter(r => r.status === 'failed');
|
|
201
|
+
for (const f of failures) {
|
|
202
|
+
const loc = f.directive.selector
|
|
203
|
+
? `${f.directive.url} (${f.directive.selector})`
|
|
204
|
+
: f.directive.url;
|
|
205
|
+
console.log(` ✗ ${loc}: ${f.error}`);
|
|
206
|
+
}
|
|
207
|
+
if (report.captured > 0) {
|
|
208
|
+
console.log('');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic screenshot filename generation.
|
|
3
|
+
*
|
|
4
|
+
* Derives stable, human-readable filenames from URL + optional CSS selector.
|
|
5
|
+
*
|
|
6
|
+
* Examples:
|
|
7
|
+
* /dashboard + .billing-panel → dashboard--billing-panel.png
|
|
8
|
+
* /settings/account + #profile → settings-account--profile.png
|
|
9
|
+
* /dashboard (no selector) → dashboard.png
|
|
10
|
+
*/
|
|
11
|
+
export declare function screenshotFilename(url: string, selector?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Dark mode variant filename.
|
|
14
|
+
* dashboard.png → dashboard-dark.png
|
|
15
|
+
* dashboard--panel.png → dashboard--panel-dark.png
|
|
16
|
+
*/
|
|
17
|
+
export declare function darkVariantFilename(filename: string): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic screenshot filename generation.
|
|
3
|
+
*
|
|
4
|
+
* Derives stable, human-readable filenames from URL + optional CSS selector.
|
|
5
|
+
*
|
|
6
|
+
* Examples:
|
|
7
|
+
* /dashboard + .billing-panel → dashboard--billing-panel.png
|
|
8
|
+
* /settings/account + #profile → settings-account--profile.png
|
|
9
|
+
* /dashboard (no selector) → dashboard.png
|
|
10
|
+
*/
|
|
11
|
+
export function screenshotFilename(url, selector) {
|
|
12
|
+
// Extract pathname from full URL or use as-is if just a path
|
|
13
|
+
let pathname;
|
|
14
|
+
try {
|
|
15
|
+
pathname = new URL(url).pathname;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
pathname = url;
|
|
19
|
+
}
|
|
20
|
+
// Strip leading /
|
|
21
|
+
let slug = pathname.replace(/^\/+/, '');
|
|
22
|
+
// Replace / with -
|
|
23
|
+
slug = slug.replace(/\//g, '-');
|
|
24
|
+
// If empty (root path), use "index"
|
|
25
|
+
if (!slug)
|
|
26
|
+
slug = 'index';
|
|
27
|
+
// Sanitize: only keep alphanumeric and hyphens
|
|
28
|
+
slug = slug.replace(/[^a-zA-Z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
29
|
+
if (selector) {
|
|
30
|
+
// Strip CSS selector prefix (. or #)
|
|
31
|
+
let selectorSlug = selector.replace(/^[.#]/, '');
|
|
32
|
+
// Sanitize selector the same way
|
|
33
|
+
selectorSlug = selectorSlug.replace(/[^a-zA-Z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
34
|
+
slug = `${slug}--${selectorSlug}`;
|
|
35
|
+
}
|
|
36
|
+
return `${slug}.png`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Dark mode variant filename.
|
|
40
|
+
* dashboard.png → dashboard-dark.png
|
|
41
|
+
* dashboard--panel.png → dashboard--panel-dark.png
|
|
42
|
+
*/
|
|
43
|
+
export function darkVariantFilename(filename) {
|
|
44
|
+
return filename.replace(/\.png$/, '-dark.png');
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract <Screenshot> directives from MDX files.
|
|
3
|
+
*
|
|
4
|
+
* Scans MDX content for <Screenshot> tags outside code blocks.
|
|
5
|
+
* Uses the same code-block-aware scanning pattern as src/qa/checks.ts.
|
|
6
|
+
*/
|
|
7
|
+
import type { ScreenshotDirective } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse all <Screenshot> directives from MDX content.
|
|
10
|
+
*
|
|
11
|
+
* Supports both self-closing (<Screenshot ... />) and open/close tags.
|
|
12
|
+
* Extracts url, selector, alt, caption props via regex.
|
|
13
|
+
* Resolves relative URLs against baseUrl.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseScreenshotDirectives(content: string, filePath: string, baseUrl: string): ScreenshotDirective[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract <Screenshot> directives from MDX files.
|
|
3
|
+
*
|
|
4
|
+
* Scans MDX content for <Screenshot> tags outside code blocks.
|
|
5
|
+
* Uses the same code-block-aware scanning pattern as src/qa/checks.ts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse all <Screenshot> directives from MDX content.
|
|
9
|
+
*
|
|
10
|
+
* Supports both self-closing (<Screenshot ... />) and open/close tags.
|
|
11
|
+
* Extracts url, selector, alt, caption props via regex.
|
|
12
|
+
* Resolves relative URLs against baseUrl.
|
|
13
|
+
*/
|
|
14
|
+
export function parseScreenshotDirectives(content, filePath, baseUrl) {
|
|
15
|
+
const directives = [];
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
let inCodeBlock = false;
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
const line = lines[i];
|
|
20
|
+
if (line === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
if (line.startsWith('```')) {
|
|
23
|
+
inCodeBlock = !inCodeBlock;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (inCodeBlock)
|
|
27
|
+
continue;
|
|
28
|
+
// Match <Screenshot ...> or <Screenshot ... />
|
|
29
|
+
const tagMatches = line.matchAll(/<Screenshot\s+([^>]*?)\/?>/g);
|
|
30
|
+
for (const match of tagMatches) {
|
|
31
|
+
const propsStr = match[1];
|
|
32
|
+
if (!propsStr)
|
|
33
|
+
continue;
|
|
34
|
+
const url = extractProp(propsStr, 'url');
|
|
35
|
+
const alt = extractProp(propsStr, 'alt');
|
|
36
|
+
if (!url || !alt)
|
|
37
|
+
continue;
|
|
38
|
+
const selector = extractProp(propsStr, 'selector');
|
|
39
|
+
const caption = extractProp(propsStr, 'caption');
|
|
40
|
+
// Resolve relative URLs against baseUrl
|
|
41
|
+
let resolvedUrl;
|
|
42
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
43
|
+
resolvedUrl = url;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Ensure baseUrl doesn't have trailing slash for clean join
|
|
47
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
48
|
+
const path = url.startsWith('/') ? url : `/${url}`;
|
|
49
|
+
resolvedUrl = `${base}${path}`;
|
|
50
|
+
}
|
|
51
|
+
directives.push({
|
|
52
|
+
url: resolvedUrl,
|
|
53
|
+
selector: selector || undefined,
|
|
54
|
+
alt,
|
|
55
|
+
caption: caption || undefined,
|
|
56
|
+
sourceFile: filePath,
|
|
57
|
+
sourceLine: i + 1,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return directives;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract a prop value from a JSX props string.
|
|
65
|
+
* Handles both quoted values: prop="value" and prop='value'
|
|
66
|
+
* Also handles JSX expressions: prop={"value"} and prop={'value'}
|
|
67
|
+
*/
|
|
68
|
+
function extractProp(propsStr, propName) {
|
|
69
|
+
// Match prop="value" or prop='value'
|
|
70
|
+
const quoteRegex = new RegExp(`${propName}\\s*=\\s*["']([^"']*?)["']`);
|
|
71
|
+
const quoteMatch = propsStr.match(quoteRegex);
|
|
72
|
+
if (quoteMatch?.[1] !== undefined)
|
|
73
|
+
return quoteMatch[1];
|
|
74
|
+
// Match prop={"value"} or prop={'value'}
|
|
75
|
+
const jsxRegex = new RegExp(`${propName}\\s*=\\s*\\{\\s*["']([^"']*?)["']\\s*\\}`);
|
|
76
|
+
const jsxMatch = propsStr.match(jsxRegex);
|
|
77
|
+
if (jsxMatch?.[1] !== undefined)
|
|
78
|
+
return jsxMatch[1];
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface ScreenshotDirective {
|
|
2
|
+
url: string;
|
|
3
|
+
selector?: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
sourceFile: string;
|
|
7
|
+
sourceLine: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ScreenshotResult {
|
|
10
|
+
directive: ScreenshotDirective;
|
|
11
|
+
filename: string;
|
|
12
|
+
darkFilename?: string;
|
|
13
|
+
status: 'captured' | 'unchanged' | 'failed';
|
|
14
|
+
error?: string;
|
|
15
|
+
hash?: string;
|
|
16
|
+
duration: number;
|
|
17
|
+
}
|
|
18
|
+
export interface CaptureReport {
|
|
19
|
+
directives: number;
|
|
20
|
+
captured: number;
|
|
21
|
+
unchanged: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
results: ScreenshotResult[];
|
|
25
|
+
}
|
|
26
|
+
export interface ScreenshotManifest {
|
|
27
|
+
version: 1;
|
|
28
|
+
capturedAt: string;
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
viewport: {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
};
|
|
34
|
+
screenshots: Record<string, {
|
|
35
|
+
url: string;
|
|
36
|
+
selector?: string;
|
|
37
|
+
filename: string;
|
|
38
|
+
darkFilename?: string;
|
|
39
|
+
capturedAt: string;
|
|
40
|
+
hash: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export interface CaptureOptions {
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
outputDir: string;
|
|
46
|
+
viewport: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
};
|
|
50
|
+
dark: boolean;
|
|
51
|
+
diff: boolean;
|
|
52
|
+
dryRun: boolean;
|
|
53
|
+
timeout: number;
|
|
54
|
+
wait: number;
|
|
55
|
+
device?: string;
|
|
56
|
+
concurrency: number;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { watchCommand } from './commands/watch.js';
|
|
|
6
6
|
import { autofixCommand } from './commands/autofix.js';
|
|
7
7
|
import { reviewPRCommand } from './commands/review-pr.js';
|
|
8
8
|
import { lintCommand } from './commands/lint.js';
|
|
9
|
+
import { qaCommand } from './commands/qa.js';
|
|
9
10
|
import { checkLinksCommand } from './commands/check-links.js';
|
|
10
11
|
import { mcpCommand } from './commands/mcp.js';
|
|
11
12
|
import { versionCommand } from './commands/version.js';
|
|
@@ -18,6 +19,9 @@ import { loginCommand, logoutCommand, whoamiCommand } from './commands/login.js'
|
|
|
18
19
|
import { cronCommand } from './commands/cron.js';
|
|
19
20
|
import { deployCommand } from './commands/deploy.js';
|
|
20
21
|
import { testCommand } from './commands/test.js';
|
|
22
|
+
import { securityCommand } from './commands/security.js';
|
|
23
|
+
import { importCommand } from './commands/import.js';
|
|
24
|
+
import { healCommand } from './commands/heal.js';
|
|
21
25
|
import { createRequire } from 'module';
|
|
22
26
|
process.on('uncaughtException', (err) => {
|
|
23
27
|
console.error('\x1b[31mFatal error:\x1b[0m', err.message);
|
|
@@ -33,6 +37,16 @@ process.on('unhandledRejection', (reason) => {
|
|
|
33
37
|
});
|
|
34
38
|
const require = createRequire(import.meta.url);
|
|
35
39
|
const { version: VERSION } = require('../package.json');
|
|
40
|
+
function semverNewer(latest, current) {
|
|
41
|
+
const parse = (v) => v.split('.').map(Number);
|
|
42
|
+
const [lMaj, lMin, lPatch] = parse(latest);
|
|
43
|
+
const [cMaj, cMin, cPatch] = parse(current);
|
|
44
|
+
if (lMaj !== cMaj)
|
|
45
|
+
return lMaj > cMaj;
|
|
46
|
+
if (lMin !== cMin)
|
|
47
|
+
return lMin > cMin;
|
|
48
|
+
return lPatch > cPatch;
|
|
49
|
+
}
|
|
36
50
|
async function checkForUpdates() {
|
|
37
51
|
try {
|
|
38
52
|
const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
|
|
@@ -41,9 +55,8 @@ async function checkForUpdates() {
|
|
|
41
55
|
if (res.ok) {
|
|
42
56
|
const data = await res.json();
|
|
43
57
|
const version = data.version;
|
|
44
|
-
if (typeof version === 'string' && /^\d+\.\d+\.\d
|
|
45
|
-
|
|
46
|
-
console.log(`\n Update available: ${VERSION} → ${latest}`);
|
|
58
|
+
if (typeof version === 'string' && /^\d+\.\d+\.\d+$/.test(version) && semverNewer(version, VERSION)) {
|
|
59
|
+
console.log(`\n Update available: ${VERSION} → ${version}`);
|
|
47
60
|
console.log(` Run: npm install -g skrypt-ai@latest\n`);
|
|
48
61
|
}
|
|
49
62
|
}
|
|
@@ -66,6 +79,7 @@ program.addCommand(watchCommand);
|
|
|
66
79
|
program.addCommand(autofixCommand);
|
|
67
80
|
program.addCommand(reviewPRCommand);
|
|
68
81
|
program.addCommand(lintCommand);
|
|
82
|
+
program.addCommand(qaCommand);
|
|
69
83
|
program.addCommand(checkLinksCommand);
|
|
70
84
|
program.addCommand(mcpCommand);
|
|
71
85
|
program.addCommand(versionCommand);
|
|
@@ -80,4 +94,7 @@ program.addCommand(whoamiCommand);
|
|
|
80
94
|
program.addCommand(cronCommand);
|
|
81
95
|
program.addCommand(deployCommand);
|
|
82
96
|
program.addCommand(testCommand);
|
|
97
|
+
program.addCommand(securityCommand);
|
|
98
|
+
program.addCommand(importCommand);
|
|
99
|
+
program.addCommand(healCommand);
|
|
83
100
|
program.parse();
|