@rettangoli/sites 0.2.7 → 1.0.0-rc10
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/README.md +201 -93
- package/package.json +8 -8
- package/sites/README.md +67 -0
- package/sites/partials/docs-mobile-nav.yaml +7 -0
- package/sites/partials/docs-sidebar.yaml +1 -0
- package/sites/partials/mobile-nav.yaml +10 -0
- package/sites/partials/navbar.yaml +7 -0
- package/sites/partials/seo.yaml +11 -0
- package/sites/templates/base.yaml +25 -0
- package/sites/templates/docs.yaml +37 -0
- package/src/builtinTemplateFunctions.js +163 -0
- package/src/cli/build.js +17 -9
- package/src/cli/init.js +5 -5
- package/src/cli/watch.js +154 -180
- package/src/createSiteBuilder.js +398 -105
- package/src/markdownItAsync.js +104 -0
- package/src/rtglMarkdown.js +162 -13
- package/src/screenshotRunner.js +5 -169
- package/src/utils/loadSiteConfig.js +333 -36
- package/templates/default/README.md +57 -25
- package/templates/default/data/site.yaml +3 -0
- package/templates/default/pages/blog.yaml +1 -1
- package/templates/default/pages/index.yaml +1 -1
- package/templates/default/partials/footer.yaml +1 -1
- package/templates/default/partials/header.yaml +1 -1
- package/templates/default/sites.config.yaml +24 -0
- package/templates/default/templates/base.yaml +5 -3
- package/templates/default/templates/post.yaml +6 -4
- package/src/screenshot.js +0 -250
- package/templates/default/package.json +0 -14
- package/templates/default/sites.config.js +0 -9
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from https://github.com/antfu/markdown-it-async
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import MarkdownIt from 'markdown-it';
|
|
6
|
+
|
|
7
|
+
const placeholder = (id, code) => `<pre><!--::markdown-it-async::${id}::--><code>${code}</code></pre>`;
|
|
8
|
+
const placeholderRe = /<pre><!--::markdown-it-async::(\w+)::--><code>[\s\S]*?<\/code><\/pre>/g;
|
|
9
|
+
const wrappedSet = new WeakSet();
|
|
10
|
+
|
|
11
|
+
function randStr() {
|
|
12
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function escapeHtml(unsafe) {
|
|
16
|
+
return unsafe
|
|
17
|
+
.replace(/&/g, '&')
|
|
18
|
+
.replace(/</g, '<')
|
|
19
|
+
.replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"')
|
|
21
|
+
.replace(/'/g, ''');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function wrapHighlight(highlight, map) {
|
|
25
|
+
if (!highlight) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (wrappedSet.has(highlight)) {
|
|
30
|
+
return highlight;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const wrapped = (str, lang, attrs) => {
|
|
34
|
+
const result = highlight(str, lang, attrs);
|
|
35
|
+
if (typeof result === 'string') {
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const id = randStr();
|
|
40
|
+
map.set(id, [result, str, lang, attrs]);
|
|
41
|
+
const escapedCode = escapeHtml(str.endsWith('\n') ? str.slice(0, -1) : str);
|
|
42
|
+
return placeholder(id, escapedCode);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
wrappedSet.add(wrapped);
|
|
46
|
+
return wrapped;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function replaceAsync(string, searchValue, replacer) {
|
|
50
|
+
const values = [];
|
|
51
|
+
String.prototype.replace.call(string, searchValue, (...args) => {
|
|
52
|
+
values.push(replacer(...args));
|
|
53
|
+
return '';
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const resolvedValues = await Promise.all(values);
|
|
57
|
+
return String.prototype.replace.call(string, searchValue, () => resolvedValues.shift() || '');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class MarkdownItAsync extends MarkdownIt {
|
|
61
|
+
constructor(...args) {
|
|
62
|
+
const options = args.length === 2 ? args[1] : args[0];
|
|
63
|
+
const map = new Map();
|
|
64
|
+
|
|
65
|
+
if (options && 'highlight' in options) {
|
|
66
|
+
options.highlight = wrapHighlight(options.highlight, map);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
super(...args);
|
|
70
|
+
this.placeholderMap = map;
|
|
71
|
+
this.disableWarn = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
render(src, env) {
|
|
75
|
+
if (this.options.warnOnSyncRender && !this.disableWarn) {
|
|
76
|
+
console.warn('[markdown-it-async] Please use `md.renderAsync` instead of `md.render`');
|
|
77
|
+
}
|
|
78
|
+
return super.render(src, env);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async renderAsync(src, env) {
|
|
82
|
+
this.options.highlight = wrapHighlight(this.options.highlight, this.placeholderMap);
|
|
83
|
+
this.disableWarn = true;
|
|
84
|
+
const result = this.render(src, env);
|
|
85
|
+
this.disableWarn = false;
|
|
86
|
+
|
|
87
|
+
return replaceAsync(result, placeholderRe, async (_match, id) => {
|
|
88
|
+
const item = this.placeholderMap.get(id);
|
|
89
|
+
if (!item) {
|
|
90
|
+
throw new Error(`Unknown highlight placeholder id: ${id}`);
|
|
91
|
+
}
|
|
92
|
+
const [promise] = item;
|
|
93
|
+
const highlighted = (await promise) || '';
|
|
94
|
+
this.placeholderMap.delete(id);
|
|
95
|
+
return highlighted;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createMarkdownItAsync(...args) {
|
|
101
|
+
return new MarkdownItAsync(...args);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default createMarkdownItAsync;
|
package/src/rtglMarkdown.js
CHANGED
|
@@ -1,22 +1,161 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { codeToHtml } from 'shiki';
|
|
2
|
+
import { createMarkdownItAsync } from './markdownItAsync.js';
|
|
3
|
+
|
|
4
|
+
function resolveHeadingAnchorOptions(input) {
|
|
5
|
+
if (input === false) {
|
|
6
|
+
return { enabled: false, slugMode: 'unicode', wrap: true, fallback: 'section' };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (input === true || input == null) {
|
|
10
|
+
return { enabled: true, slugMode: 'unicode', wrap: true, fallback: 'section' };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const candidate = typeof input === 'object' && !Array.isArray(input) ? input : {};
|
|
14
|
+
const fallback = typeof candidate.fallback === 'string' && candidate.fallback.trim()
|
|
15
|
+
? candidate.fallback.trim()
|
|
16
|
+
: 'section';
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
enabled: candidate.enabled !== undefined ? !!candidate.enabled : true,
|
|
20
|
+
slugMode: candidate.slugMode === 'ascii' ? 'ascii' : 'unicode',
|
|
21
|
+
wrap: candidate.wrap !== undefined ? !!candidate.wrap : true,
|
|
22
|
+
fallback
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveCodePreviewOptions(input) {
|
|
27
|
+
if (input === false || input == null) {
|
|
28
|
+
return { enabled: false, showSource: true, theme: undefined };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (input === true) {
|
|
32
|
+
return { enabled: true, showSource: true, theme: undefined };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
36
|
+
const theme = typeof input.theme === 'string' && input.theme.trim()
|
|
37
|
+
? input.theme.trim()
|
|
38
|
+
: undefined;
|
|
39
|
+
return {
|
|
40
|
+
enabled: input.enabled !== undefined ? !!input.enabled : true,
|
|
41
|
+
showSource: input.showSource !== undefined ? !!input.showSource : true,
|
|
42
|
+
theme
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { enabled: false, showSource: true, theme: undefined };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasCodePreviewAttribute(attrs) {
|
|
50
|
+
if (typeof attrs !== 'string') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return attrs
|
|
54
|
+
.split(/\s+/)
|
|
55
|
+
.map((item) => item.trim())
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.includes('codePreview');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderCodePreviewLayout(code, highlightedCode, showSource = true) {
|
|
61
|
+
if (!showSource) {
|
|
62
|
+
return `
|
|
63
|
+
<rtgl-view class="rtgl-code-preview" w="f" bw="xs" br="md">
|
|
64
|
+
<rtgl-view w="f" d="h">
|
|
65
|
+
${highlightedCode}
|
|
66
|
+
</rtgl-view>
|
|
67
|
+
</rtgl-view>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return `
|
|
71
|
+
<rtgl-view class="rtgl-code-preview" w="f" bw="xs" br="md">
|
|
72
|
+
<rtgl-view w="f" p="lg">
|
|
73
|
+
${code}
|
|
74
|
+
</rtgl-view>
|
|
75
|
+
<rtgl-view h="1" w="f" bgc="bo"></rtgl-view>
|
|
76
|
+
<rtgl-view w="f" d="h">
|
|
77
|
+
${highlightedCode}
|
|
78
|
+
</rtgl-view>
|
|
79
|
+
</rtgl-view>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function slugify(text, { slugMode = 'unicode', fallback = 'section' } = {}) {
|
|
83
|
+
const normalized = String(text || '')
|
|
4
84
|
.toLowerCase()
|
|
85
|
+
.normalize('NFKD')
|
|
86
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5
87
|
.trim()
|
|
6
|
-
.replace(
|
|
88
|
+
.replace(
|
|
89
|
+
slugMode === 'ascii' ? /[^a-z0-9\s-]/g : /[^\p{Letter}\p{Number}\s-]/gu,
|
|
90
|
+
''
|
|
91
|
+
)
|
|
7
92
|
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
8
93
|
.replace(/--+/g, '-') // Replace multiple hyphens with single hyphen
|
|
9
94
|
.replace(/^-+/, '') // Remove leading hyphens
|
|
10
95
|
.replace(/-+$/, '') // Remove trailing hyphens
|
|
96
|
+
|
|
97
|
+
return normalized || fallback;
|
|
11
98
|
}
|
|
12
99
|
|
|
13
|
-
export
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
100
|
+
export function createRtglMarkdown(_markdownit, options = {}) {
|
|
101
|
+
const {
|
|
102
|
+
preset = 'default',
|
|
103
|
+
html = true,
|
|
104
|
+
xhtmlOut = false,
|
|
105
|
+
linkify = true,
|
|
106
|
+
typographer = false,
|
|
107
|
+
breaks = false,
|
|
108
|
+
langPrefix = 'language-',
|
|
109
|
+
quotes = '\u201c\u201d\u2018\u2019',
|
|
110
|
+
maxNesting = 100,
|
|
111
|
+
shiki = {},
|
|
112
|
+
headingAnchors = true,
|
|
113
|
+
codePreview = false
|
|
114
|
+
} = options;
|
|
115
|
+
const headingAnchorOptions = resolveHeadingAnchorOptions(headingAnchors);
|
|
116
|
+
const codePreviewOptions = resolveCodePreviewOptions(codePreview);
|
|
117
|
+
|
|
118
|
+
const shikiEnabled = typeof shiki.enabled === 'boolean' ? shiki.enabled : true;
|
|
119
|
+
const shikiTheme = typeof shiki.theme === 'string' ? shiki.theme : 'slack-dark';
|
|
120
|
+
|
|
121
|
+
const md = createMarkdownItAsync(preset, {
|
|
122
|
+
html,
|
|
123
|
+
xhtmlOut,
|
|
124
|
+
linkify,
|
|
125
|
+
typographer,
|
|
126
|
+
breaks,
|
|
127
|
+
langPrefix,
|
|
128
|
+
quotes,
|
|
129
|
+
maxNesting,
|
|
130
|
+
...(shikiEnabled
|
|
131
|
+
? {
|
|
132
|
+
async highlight(code, lang, attrs) {
|
|
133
|
+
try {
|
|
134
|
+
const isCodePreview = codePreviewOptions.enabled && hasCodePreviewAttribute(attrs);
|
|
135
|
+
const highlightTheme = isCodePreview
|
|
136
|
+
? (codePreviewOptions.theme || shikiTheme)
|
|
137
|
+
: shikiTheme;
|
|
138
|
+
const highlightedCode = await codeToHtml(code, {
|
|
139
|
+
lang: lang || 'text',
|
|
140
|
+
theme: highlightTheme
|
|
141
|
+
});
|
|
142
|
+
if (isCodePreview) {
|
|
143
|
+
return renderCodePreviewLayout(code, highlightedCode, codePreviewOptions.showSource);
|
|
144
|
+
}
|
|
145
|
+
return highlightedCode;
|
|
146
|
+
} catch {
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
warnOnSyncRender: false
|
|
18
153
|
})
|
|
19
154
|
|
|
155
|
+
if (!headingAnchorOptions.enabled) {
|
|
156
|
+
return md;
|
|
157
|
+
}
|
|
158
|
+
|
|
20
159
|
// Override heading renderer to add IDs and wrap with anchor links
|
|
21
160
|
const defaultHeadingRender = md.renderer.rules.heading_open || function (tokens, idx, options, env, renderer) {
|
|
22
161
|
return renderer.renderToken(tokens, idx, options)
|
|
@@ -27,17 +166,25 @@ export default function configureMarkdown(markdownit) {
|
|
|
27
166
|
}
|
|
28
167
|
|
|
29
168
|
md.renderer.rules.heading_open = function (tokens, idx, options, env, renderer) {
|
|
169
|
+
env = env || {};
|
|
30
170
|
const token = tokens[idx]
|
|
31
171
|
const nextToken = tokens[idx + 1]
|
|
32
|
-
let slug =
|
|
172
|
+
let slug = headingAnchorOptions.fallback
|
|
33
173
|
|
|
34
174
|
if (nextToken && nextToken.type === 'inline') {
|
|
35
175
|
const headingText = nextToken.content
|
|
36
|
-
|
|
37
|
-
|
|
176
|
+
const baseSlug = slugify(headingText, headingAnchorOptions)
|
|
177
|
+
const headingCounts = env.__rtglHeadingSlugCounts || (env.__rtglHeadingSlugCounts = new Map())
|
|
178
|
+
const nextCount = (headingCounts.get(baseSlug) || 0) + 1
|
|
179
|
+
headingCounts.set(baseSlug, nextCount)
|
|
180
|
+
slug = nextCount === 1 ? baseSlug : `${baseSlug}-${nextCount}`
|
|
38
181
|
}
|
|
39
182
|
|
|
183
|
+
token.attrSet('id', slug)
|
|
40
184
|
const headingHtml = defaultHeadingRender(tokens, idx, options, env, renderer)
|
|
185
|
+
if (!headingAnchorOptions.wrap) {
|
|
186
|
+
return headingHtml
|
|
187
|
+
}
|
|
41
188
|
return `<a href="#${slug}" style="display: contents; text-decoration: none; color: inherit;">` + headingHtml
|
|
42
189
|
}
|
|
43
190
|
|
|
@@ -46,4 +193,6 @@ export default function configureMarkdown(markdownit) {
|
|
|
46
193
|
}
|
|
47
194
|
|
|
48
195
|
return md
|
|
49
|
-
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default createRtglMarkdown;
|
package/src/screenshotRunner.js
CHANGED
|
@@ -1,171 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { buildSite } from './cli/build.js';
|
|
6
|
-
import { createScreenshotCapture } from './screenshot.js';
|
|
7
|
-
import { loadSiteConfig } from './utils/loadSiteConfig.js';
|
|
8
|
-
|
|
9
|
-
function getContentType(ext) {
|
|
10
|
-
const types = {
|
|
11
|
-
'.html': 'text/html',
|
|
12
|
-
'.css': 'text/css',
|
|
13
|
-
'.js': 'application/javascript',
|
|
14
|
-
'.json': 'application/json',
|
|
15
|
-
'.png': 'image/png',
|
|
16
|
-
'.jpg': 'image/jpeg',
|
|
17
|
-
'.gif': 'image/gif',
|
|
18
|
-
'.svg': 'image/svg+xml',
|
|
19
|
-
'.ico': 'image/x-icon'
|
|
20
|
-
};
|
|
21
|
-
return types[ext] || 'application/octet-stream';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function serveFile(filePath, res, skipWebSocket = false) {
|
|
25
|
-
const ext = path.extname(filePath);
|
|
26
|
-
const contentType = getContentType(ext);
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const content = fs.readFileSync(filePath);
|
|
30
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
31
|
-
res.end(content);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error('Error serving file:', err);
|
|
34
|
-
res.writeHead(500);
|
|
35
|
-
res.end('Internal Server Error');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function handleRequest(req, res, siteDir = '_site') {
|
|
40
|
-
const urlParts = req.url.split('?');
|
|
41
|
-
let urlPath = urlParts[0];
|
|
42
|
-
const queryString = urlParts[1] || '';
|
|
43
|
-
|
|
44
|
-
// Check if this is a screenshot request
|
|
45
|
-
const isScreenshotRequest = queryString.includes('screenshot=true');
|
|
46
|
-
|
|
47
|
-
// Default to index.html for root
|
|
48
|
-
if (urlPath === '/') {
|
|
49
|
-
urlPath = '/index.html';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Handle trailing slash - remove it for processing
|
|
53
|
-
const hasTrailingSlash = urlPath.endsWith('/') && urlPath !== '/';
|
|
54
|
-
if (hasTrailingSlash) {
|
|
55
|
-
urlPath = urlPath.slice(0, -1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Handle paths without extensions
|
|
59
|
-
if (!path.extname(urlPath)) {
|
|
60
|
-
// First try as .html file
|
|
61
|
-
const htmlPath = path.join(siteDir, urlPath + '.html');
|
|
62
|
-
if (existsSync(htmlPath)) {
|
|
63
|
-
urlPath = urlPath + '.html';
|
|
64
|
-
} else {
|
|
65
|
-
// Try as directory with index.html
|
|
66
|
-
const indexPath = path.join(siteDir, urlPath, 'index.html');
|
|
67
|
-
if (existsSync(indexPath)) {
|
|
68
|
-
urlPath = path.join(urlPath, 'index.html');
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const filePath = path.join(siteDir, urlPath);
|
|
74
|
-
|
|
75
|
-
// Check if file exists
|
|
76
|
-
if (!existsSync(filePath)) {
|
|
77
|
-
res.writeHead(404);
|
|
78
|
-
res.end('404 Not Found');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check if it's a directory
|
|
83
|
-
const stats = fs.statSync(filePath);
|
|
84
|
-
if (stats.isDirectory()) {
|
|
85
|
-
// Try to serve index.html from the directory
|
|
86
|
-
const indexPath = path.join(filePath, 'index.html');
|
|
87
|
-
if (existsSync(indexPath)) {
|
|
88
|
-
return serveFile(indexPath, res, isScreenshotRequest);
|
|
89
|
-
} else {
|
|
90
|
-
res.writeHead(404);
|
|
91
|
-
res.end('404 Not Found');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Serve the file
|
|
97
|
-
serveFile(filePath, res, isScreenshotRequest);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function createTempServer(port = 3001, siteDir = '_site') {
|
|
101
|
-
const httpServer = http.createServer((req, res) => {
|
|
102
|
-
handleRequest(req, res, siteDir);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const start = () => {
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
107
|
-
httpServer.listen(port, '0.0.0.0', () => {
|
|
108
|
-
console.log(`📡 Temp server running at http://localhost:${port}/`);
|
|
109
|
-
resolve();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
httpServer.on('error', reject);
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const close = () => {
|
|
117
|
-
return new Promise((resolve) => {
|
|
118
|
-
httpServer.close(() => {
|
|
119
|
-
console.log('📡 Temp server closed');
|
|
120
|
-
resolve();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return { start, close };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const screenshotCommand = async (options = {}) => {
|
|
129
|
-
const {
|
|
130
|
-
port = 3001,
|
|
131
|
-
rootDir = '.'
|
|
132
|
-
} = options;
|
|
133
|
-
|
|
134
|
-
console.log('📸 Starting screenshot capture for all pages...');
|
|
135
|
-
|
|
136
|
-
// Load config to get ignore patterns
|
|
137
|
-
const config = await loadSiteConfig(rootDir, false);
|
|
138
|
-
const ignorePatterns = config?.screenshots?.ignore || [];
|
|
139
|
-
|
|
140
|
-
if (ignorePatterns.length > 0) {
|
|
141
|
-
console.log('📋 Ignore patterns:', ignorePatterns);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Build the site first (with screenshot mode enabled)
|
|
145
|
-
console.log('Building site...');
|
|
146
|
-
await buildSite({ rootDir, isScreenshotMode: true });
|
|
147
|
-
console.log('Build complete');
|
|
148
|
-
|
|
149
|
-
// Start temporary server
|
|
150
|
-
const server = createTempServer(port);
|
|
151
|
-
await server.start();
|
|
152
|
-
|
|
153
|
-
// Initialize screenshot capture
|
|
154
|
-
const screenshotCapture = await createScreenshotCapture(port);
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
// Capture screenshots for all pages
|
|
158
|
-
const pagesDir = path.join(rootDir, 'pages');
|
|
159
|
-
await screenshotCapture.captureAllPages(pagesDir, { ignorePatterns });
|
|
160
|
-
|
|
161
|
-
console.log('✅ All screenshots captured successfully!');
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('❌ Error capturing screenshots:', error);
|
|
164
|
-
} finally {
|
|
165
|
-
// Clean up
|
|
166
|
-
await screenshotCapture.close();
|
|
167
|
-
await server.close();
|
|
168
|
-
}
|
|
1
|
+
const screenshotCommand = async () => {
|
|
2
|
+
throw new Error(
|
|
3
|
+
'Screenshot CLI for @rettangoli/sites was removed. Use "rtgl vt generate" with "vt.url" and optional "vt.service.start".',
|
|
4
|
+
);
|
|
169
5
|
};
|
|
170
6
|
|
|
171
|
-
export default screenshotCommand;
|
|
7
|
+
export default screenshotCommand;
|