@stacksjs/stx 0.0.9 → 0.1.6
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 +69 -21
- package/dist/a11y.d.ts +40 -0
- package/dist/analyzer.d.ts +64 -0
- package/dist/animation.d.ts +64 -0
- package/dist/assets.d.ts +19 -0
- package/dist/auth.d.ts +11 -0
- package/dist/bin/cli.js +2821 -154
- package/dist/caching.d.ts +18 -6
- package/dist/chunk-2ndtnc0t.js +9822 -0
- package/dist/{chunk-ywm063e4.js → chunk-e11q5a3p.js} +2 -2
- package/dist/chunk-vsbm352h.js +670 -0
- package/dist/client.d.ts +19 -35
- package/dist/components.d.ts +6 -0
- package/dist/conditionals.d.ts +17 -1
- package/dist/config.d.ts +6 -2
- package/dist/csrf.d.ts +28 -0
- package/dist/custom-directives.d.ts +5 -6
- package/dist/dev-server.d.ts +21 -0
- package/dist/docs.d.ts +39 -3
- package/dist/error-handling.d.ts +101 -0
- package/dist/expressions.d.ts +43 -4
- package/dist/formatter.d.ts +16 -0
- package/dist/forms.d.ts +15 -25
- package/dist/i18n.d.ts +17 -20
- package/dist/includes.d.ts +21 -18
- package/dist/index.d.ts +30 -24
- package/dist/init.d.ts +9 -0
- package/dist/js-ts.d.ts +10 -0
- package/dist/loops.d.ts +4 -3
- package/dist/markdown.d.ts +8 -0
- package/dist/method-spoofing.d.ts +13 -0
- package/dist/middleware.d.ts +9 -1
- package/dist/performance-utils.d.ts +58 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/process.d.ts +9 -7
- package/dist/release.d.ts +1 -0
- package/dist/routes.d.ts +36 -0
- package/dist/safe-evaluator.d.ts +16 -0
- package/dist/seo.d.ts +33 -0
- package/dist/serve.d.ts +35 -0
- package/dist/src/index.js +2893 -135
- package/dist/streaming.d.ts +30 -14
- package/dist/types.d.ts +214 -48
- package/dist/utils.d.ts +21 -3
- package/dist/view-composers.d.ts +26 -0
- package/dist/web-components.d.ts +7 -14
- package/package.json +18 -9
- package/dist/chunk-04bqmpzb.js +0 -7069
- package/dist/chunk-8ehp5m3y.js +0 -4279
- package/dist/chunk-9ynf73q9.js +0 -2502
package/dist/src/index.js
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_TRANSITION_OPTIONS,
|
|
4
|
+
ErrorLogger,
|
|
5
|
+
StxError,
|
|
6
|
+
StxFileError,
|
|
7
|
+
StxRuntimeError,
|
|
8
|
+
StxSecurityError,
|
|
9
|
+
StxSyntaxError,
|
|
10
|
+
TransitionDirection,
|
|
11
|
+
TransitionEase,
|
|
12
|
+
TransitionType,
|
|
13
|
+
a11yDirective,
|
|
14
|
+
animationGroupDirective,
|
|
15
|
+
applyFilters,
|
|
3
16
|
buildWebComponents,
|
|
17
|
+
checkA11y,
|
|
18
|
+
clearOnceStore,
|
|
19
|
+
componentDirective,
|
|
4
20
|
config,
|
|
5
|
-
defaultConfig,
|
|
6
|
-
docsCommand,
|
|
7
|
-
extractComponentDescription,
|
|
8
|
-
extractComponentProps,
|
|
9
|
-
findComponentFiles,
|
|
10
|
-
formatDocsAsHtml,
|
|
11
|
-
formatDocsAsJson,
|
|
12
|
-
formatDocsAsMarkdown,
|
|
13
|
-
generateComponentDoc,
|
|
14
|
-
generateComponentsDocs,
|
|
15
|
-
generateDirectivesDocs,
|
|
16
|
-
generateDocs,
|
|
17
|
-
generateTemplatesDocs,
|
|
18
|
-
webComponentDirectiveHandler
|
|
19
|
-
} from "../chunk-9ynf73q9.js";
|
|
20
|
-
import {
|
|
21
|
-
applyFilters,
|
|
22
21
|
createDetailedErrorMessage,
|
|
22
|
+
createEnhancedError,
|
|
23
23
|
createTranslateFilter,
|
|
24
|
+
defaultConfig,
|
|
24
25
|
defaultFilters,
|
|
26
|
+
defaultI18nConfig,
|
|
27
|
+
defineStxConfig,
|
|
28
|
+
devHelpers,
|
|
29
|
+
errorLogger,
|
|
30
|
+
errorRecovery,
|
|
25
31
|
escapeHtml,
|
|
26
32
|
evaluateAuthExpression,
|
|
27
33
|
evaluateExpression,
|
|
28
34
|
extractVariables,
|
|
29
35
|
fileExists,
|
|
36
|
+
getScreenReaderOnlyStyle,
|
|
30
37
|
getSourceLineInfo,
|
|
31
38
|
getTranslation,
|
|
39
|
+
injectSeoTags,
|
|
32
40
|
loadTranslation,
|
|
41
|
+
markdownCache,
|
|
33
42
|
markdownDirectiveHandler,
|
|
43
|
+
metaDirective,
|
|
44
|
+
motionDirective,
|
|
45
|
+
onceStore,
|
|
34
46
|
partialsCache,
|
|
47
|
+
performanceMonitor,
|
|
48
|
+
processA11yDirectives,
|
|
49
|
+
processAnimationDirectives,
|
|
35
50
|
processBasicFormDirectives,
|
|
36
51
|
processCustomDirectives,
|
|
37
52
|
processDirectives,
|
|
@@ -44,31 +59,407 @@ import {
|
|
|
44
59
|
processJsonDirective,
|
|
45
60
|
processLoops,
|
|
46
61
|
processMarkdownDirectives,
|
|
62
|
+
processMarkdownFileDirectives,
|
|
63
|
+
processMetaDirectives,
|
|
47
64
|
processMiddleware,
|
|
48
65
|
processOnceDirective,
|
|
66
|
+
processSeoDirective,
|
|
49
67
|
processStackPushDirectives,
|
|
50
68
|
processStackReplacements,
|
|
69
|
+
processStructuredData,
|
|
51
70
|
processTranslateDirective,
|
|
71
|
+
readMarkdownFile,
|
|
72
|
+
registerA11yDirectives,
|
|
73
|
+
registerAnimationDirectives,
|
|
74
|
+
registerComponentDirectives,
|
|
75
|
+
registerSeoDirectives,
|
|
52
76
|
renderComponent,
|
|
53
77
|
resolveTemplatePath,
|
|
54
78
|
runPostProcessingMiddleware,
|
|
55
79
|
runPreProcessingMiddleware,
|
|
80
|
+
safeExecute,
|
|
81
|
+
safeExecuteAsync,
|
|
82
|
+
scanA11yIssues,
|
|
83
|
+
screenReaderDirective,
|
|
84
|
+
scrollAnimateDirective,
|
|
56
85
|
setGlobalContext,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
structuredDataDirective,
|
|
87
|
+
transitionDirective,
|
|
88
|
+
unescapeHtml,
|
|
89
|
+
validators,
|
|
90
|
+
webComponentDirectiveHandler
|
|
91
|
+
} from "../chunk-2ndtnc0t.js";
|
|
92
|
+
import"../chunk-e11q5a3p.js";
|
|
93
|
+
// src/analyzer.ts
|
|
94
|
+
import process from "process";
|
|
95
|
+
async function analyzeTemplate(filePath) {
|
|
96
|
+
try {
|
|
97
|
+
const content = await Bun.file(filePath).text();
|
|
98
|
+
const metrics = calculateMetrics(content);
|
|
99
|
+
const issues = findIssues(content, filePath);
|
|
100
|
+
const suggestions = generateSuggestions(content, metrics);
|
|
101
|
+
const performance = analyzePerformance(content, metrics);
|
|
102
|
+
return {
|
|
103
|
+
file: filePath,
|
|
104
|
+
metrics,
|
|
105
|
+
issues,
|
|
106
|
+
suggestions,
|
|
107
|
+
performance
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Failed to analyze template ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function calculateMetrics(content) {
|
|
114
|
+
const lines = content.split(`
|
|
115
|
+
`);
|
|
116
|
+
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
117
|
+
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
118
|
+
const scriptLines = scriptContent.split(`
|
|
119
|
+
`).length;
|
|
120
|
+
const directives = {
|
|
121
|
+
conditionals: (content.match(/@(if|unless|elseif|else|endif|endunless)\b/g) || []).length,
|
|
122
|
+
loops: (content.match(/@(foreach|for|endforeach|endfor|while|endwhile)\b/g) || []).length,
|
|
123
|
+
includes: (content.match(/@(include|component|extends|section|yield)\b/g) || []).length,
|
|
124
|
+
custom: 0,
|
|
125
|
+
total: 0
|
|
126
|
+
};
|
|
127
|
+
directives.total = directives.conditionals + directives.loops + directives.includes + directives.custom;
|
|
128
|
+
const regularExpressions = (content.match(/\{\{[\s\S]*?\}\}/g) || []).length;
|
|
129
|
+
const rawExpressions = (content.match(/\{!![\s\S]*?!!\}/g) || []).length;
|
|
130
|
+
const directiveExpressions = (content.match(/@(?:if|unless|elseif|foreach|for|while)\s*\([^)]+\)/g) || []).length;
|
|
131
|
+
const expressions = regularExpressions + rawExpressions + directiveExpressions;
|
|
132
|
+
const components = (content.match(/<[A-Z][^>]*>/g) || []).length;
|
|
133
|
+
const layouts = (content.match(/@extends\(/g) || []).length;
|
|
134
|
+
let maxNestingDepth = 0;
|
|
135
|
+
let currentDepth = 0;
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
if (/@(?:if|unless|foreach|for|while|section)\b/.test(line) && !/@end/.test(line) && !/@else/.test(line)) {
|
|
138
|
+
currentDepth++;
|
|
139
|
+
maxNestingDepth = Math.max(maxNestingDepth, currentDepth);
|
|
140
|
+
} else if (/@end(?:if|unless|foreach|for|while|section)\b/.test(line)) {
|
|
141
|
+
currentDepth--;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const nestingBonus = maxNestingDepth >= 4 ? 3 : 0;
|
|
145
|
+
const complexity = Math.min(10, Math.ceil(directives.total * 0.5 + expressions * 0.1 + components * 0.3 + (scriptLines > 10 ? scriptLines * 0.05 : 0) + nestingBonus));
|
|
146
|
+
return {
|
|
147
|
+
lines: lines.length,
|
|
148
|
+
characters: content.length,
|
|
149
|
+
directives,
|
|
150
|
+
expressions,
|
|
151
|
+
components,
|
|
152
|
+
layouts,
|
|
153
|
+
scriptLines,
|
|
154
|
+
complexity
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function findIssues(content, _filePath) {
|
|
158
|
+
const issues = [];
|
|
159
|
+
const unmatchedIfs = findUnmatchedDirectives(content, "if", "endif");
|
|
160
|
+
if (unmatchedIfs > 0) {
|
|
161
|
+
issues.push({
|
|
162
|
+
type: "error",
|
|
163
|
+
category: "syntax",
|
|
164
|
+
message: `Found ${unmatchedIfs} unmatched @if directive(s)`,
|
|
165
|
+
suggestion: "Ensure every @if has a corresponding @endif"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const unmatchedForeachs = findUnmatchedDirectives(content, "foreach", "endforeach");
|
|
169
|
+
if (unmatchedForeachs > 0) {
|
|
170
|
+
issues.push({
|
|
171
|
+
type: "error",
|
|
172
|
+
category: "syntax",
|
|
173
|
+
message: `Found ${unmatchedForeachs} unmatched @foreach directive(s)`,
|
|
174
|
+
suggestion: "Ensure every @foreach has a corresponding @endforeach"
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const nestedLoops = content.match(/@foreach[\s\S]*?@foreach[\s\S]*?@endforeach[\s\S]*?@endforeach/g);
|
|
178
|
+
if (nestedLoops && nestedLoops.length > 0) {
|
|
179
|
+
issues.push({
|
|
180
|
+
type: "warning",
|
|
181
|
+
category: "performance",
|
|
182
|
+
message: "Nested loops detected which may impact performance",
|
|
183
|
+
suggestion: "Consider preprocessing data or using more efficient data structures"
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const rawOutputs = (content.match(/\{!![\s\S]*?!!\}/g) || []).length;
|
|
187
|
+
if (rawOutputs > 0) {
|
|
188
|
+
issues.push({
|
|
189
|
+
type: "warning",
|
|
190
|
+
category: "security",
|
|
191
|
+
message: `Found ${rawOutputs} raw output expression(s) {!! !!}`,
|
|
192
|
+
suggestion: "Ensure raw outputs are properly sanitized to prevent XSS"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const images = content.match(/<img[^>]*>/g);
|
|
196
|
+
if (images) {
|
|
197
|
+
const imagesWithoutAlt = images.filter((img) => !img.includes("alt="));
|
|
198
|
+
if (imagesWithoutAlt.length > 0) {
|
|
199
|
+
issues.push({
|
|
200
|
+
type: "warning",
|
|
201
|
+
category: "accessibility",
|
|
202
|
+
message: `Found ${imagesWithoutAlt.length} image(s) without alt attributes`,
|
|
203
|
+
suggestion: "Add alt attributes to all images for screen reader accessibility"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const longLines = content.split(`
|
|
208
|
+
`).filter((line) => line.length > 120);
|
|
209
|
+
if (longLines.length > 0) {
|
|
210
|
+
issues.push({
|
|
211
|
+
type: "info",
|
|
212
|
+
category: "maintainability",
|
|
213
|
+
message: `Found ${longLines.length} line(s) longer than 120 characters`,
|
|
214
|
+
suggestion: "Consider breaking long lines for better readability"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const inlineStyles = (content.match(/style\s*=\s*["'][^"']*["']/g) || []).length;
|
|
218
|
+
if (inlineStyles > 3) {
|
|
219
|
+
issues.push({
|
|
220
|
+
type: "info",
|
|
221
|
+
category: "maintainability",
|
|
222
|
+
message: `Found ${inlineStyles} inline style attributes`,
|
|
223
|
+
suggestion: "Consider moving styles to CSS classes for better maintainability"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return issues;
|
|
227
|
+
}
|
|
228
|
+
function generateSuggestions(content, metrics) {
|
|
229
|
+
const suggestions = [];
|
|
230
|
+
if (metrics.complexity > 7) {
|
|
231
|
+
suggestions.push({
|
|
232
|
+
type: "refactor",
|
|
233
|
+
message: "Template complexity is high. Consider breaking into smaller components.",
|
|
234
|
+
impact: "high",
|
|
235
|
+
effort: "medium"
|
|
236
|
+
});
|
|
237
|
+
} else if (metrics.complexity > 5 && metrics.directives.total > 8) {
|
|
238
|
+
suggestions.push({
|
|
239
|
+
type: "optimization",
|
|
240
|
+
message: "Template has moderate complexity. Consider component extraction for reusability.",
|
|
241
|
+
impact: "medium",
|
|
242
|
+
effort: "low"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (metrics.lines > 100 && metrics.components === 0) {
|
|
246
|
+
suggestions.push({
|
|
247
|
+
type: "refactor",
|
|
248
|
+
message: "Large template with no components. Consider extracting reusable parts into components.",
|
|
249
|
+
impact: "medium",
|
|
250
|
+
effort: "medium"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (metrics.expressions > 20) {
|
|
254
|
+
suggestions.push({
|
|
255
|
+
type: "optimization",
|
|
256
|
+
message: "Many template expressions detected. Consider preprocessing some data in the script section.",
|
|
257
|
+
impact: "medium",
|
|
258
|
+
effort: "low"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const nestedLoops = content.match(/@foreach[\s\S]*?@foreach[\s\S]*?@endforeach[\s\S]*?@endforeach/g);
|
|
262
|
+
if (nestedLoops && nestedLoops.length > 0) {
|
|
263
|
+
suggestions.push({
|
|
264
|
+
type: "optimization",
|
|
265
|
+
message: "Nested loops detected. Consider preprocessing data or optimizing data structures.",
|
|
266
|
+
impact: "high",
|
|
267
|
+
effort: "medium"
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (metrics.directives.total === 0 && metrics.expressions < 5) {
|
|
271
|
+
suggestions.push({
|
|
272
|
+
type: "optimization",
|
|
273
|
+
message: "Template appears static. Consider enabling aggressive caching.",
|
|
274
|
+
impact: "high",
|
|
275
|
+
effort: "low"
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (metrics.scriptLines > 50) {
|
|
279
|
+
suggestions.push({
|
|
280
|
+
type: "refactor",
|
|
281
|
+
message: "Large script section. Consider moving complex logic to external modules.",
|
|
282
|
+
impact: "medium",
|
|
283
|
+
effort: "medium"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return suggestions;
|
|
287
|
+
}
|
|
288
|
+
function analyzePerformance(content, metrics) {
|
|
289
|
+
let estimatedRenderTime = 1;
|
|
290
|
+
estimatedRenderTime += metrics.directives.conditionals * 0.1;
|
|
291
|
+
estimatedRenderTime += metrics.directives.loops * 0.5;
|
|
292
|
+
estimatedRenderTime += metrics.directives.includes * 2;
|
|
293
|
+
estimatedRenderTime += metrics.expressions * 0.05;
|
|
294
|
+
estimatedRenderTime += metrics.components * 1;
|
|
295
|
+
if (metrics.scriptLines > 10) {
|
|
296
|
+
estimatedRenderTime += metrics.scriptLines * 0.1;
|
|
297
|
+
}
|
|
298
|
+
let cacheability;
|
|
299
|
+
if (metrics.expressions === 0 && metrics.directives.total === 0) {
|
|
300
|
+
cacheability = "high";
|
|
301
|
+
} else if (metrics.expressions < 5 && metrics.directives.total < 3) {
|
|
302
|
+
cacheability = "medium";
|
|
303
|
+
} else {
|
|
304
|
+
cacheability = "low";
|
|
305
|
+
}
|
|
306
|
+
const recommendations = [];
|
|
307
|
+
if (estimatedRenderTime > 10) {
|
|
308
|
+
recommendations.push("Consider optimizing template complexity");
|
|
309
|
+
}
|
|
310
|
+
if (cacheability === "low" && metrics.directives.total > 5) {
|
|
311
|
+
recommendations.push("Reduce dynamic content for better caching");
|
|
312
|
+
}
|
|
313
|
+
if (metrics.components === 0 && metrics.lines > 40) {
|
|
314
|
+
recommendations.push("Extract components to improve reusability and performance");
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
estimatedRenderTime: Math.round(estimatedRenderTime * 100) / 100,
|
|
318
|
+
complexityScore: metrics.complexity,
|
|
319
|
+
cacheability,
|
|
320
|
+
recommendations
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function findUnmatchedDirectives(content, startDirective, endDirective) {
|
|
324
|
+
const contentWithoutComments = content.replace(/<!--[\s\S]*?-->/g, "");
|
|
325
|
+
const starts = (contentWithoutComments.match(new RegExp(`@${startDirective}\\b`, "g")) || []).length;
|
|
326
|
+
const ends = (contentWithoutComments.match(new RegExp(`@${endDirective}\\b`, "g")) || []).length;
|
|
327
|
+
return Math.abs(starts - ends);
|
|
328
|
+
}
|
|
329
|
+
async function analyzeProject(patterns = ["**/*.stx"], cwd) {
|
|
330
|
+
const results = [];
|
|
331
|
+
const allFiles = [];
|
|
332
|
+
for (const pattern of patterns) {
|
|
333
|
+
const files = await Array.fromAsync(new Bun.Glob(pattern).scan({
|
|
334
|
+
cwd: cwd || process.cwd(),
|
|
335
|
+
onlyFiles: true,
|
|
336
|
+
absolute: true
|
|
337
|
+
}));
|
|
338
|
+
allFiles.push(...files.filter((f) => f.endsWith(".stx")));
|
|
339
|
+
}
|
|
340
|
+
for (const file of allFiles) {
|
|
341
|
+
try {
|
|
342
|
+
const result = await analyzeTemplate(file);
|
|
343
|
+
results.push(result);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.warn(`Failed to analyze ${file}:`, error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const summary = generateProjectSummary(results);
|
|
349
|
+
return { results, summary };
|
|
350
|
+
}
|
|
351
|
+
function generateProjectSummary(results) {
|
|
352
|
+
const totalFiles = results.length;
|
|
353
|
+
const totalLines = results.reduce((sum, r) => sum + r.metrics.lines, 0);
|
|
354
|
+
const avgComplexity = totalFiles > 0 ? results.reduce((sum, r) => sum + r.metrics.complexity, 0) / totalFiles : 0;
|
|
355
|
+
const totalIssues = results.reduce((sum, r) => sum + r.issues.length, 0);
|
|
356
|
+
const issuesByCategory = {};
|
|
357
|
+
results.forEach((r) => {
|
|
358
|
+
r.issues.forEach((issue) => {
|
|
359
|
+
issuesByCategory[issue.category] = (issuesByCategory[issue.category] || 0) + 1;
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
const avgRenderTime = totalFiles > 0 ? results.reduce((sum, r) => sum + r.performance.estimatedRenderTime, 0) / totalFiles : 0;
|
|
363
|
+
const performanceScore = Math.max(1, Math.min(10, Math.round(10 - avgRenderTime / 2 - avgComplexity / 2)));
|
|
364
|
+
const recommendations = [];
|
|
365
|
+
if (avgComplexity > 6) {
|
|
366
|
+
recommendations.push("Project has high average complexity. Consider refactoring complex templates.");
|
|
367
|
+
}
|
|
368
|
+
if (totalIssues > totalFiles * 2) {
|
|
369
|
+
recommendations.push("High number of issues detected. Run detailed analysis on individual files.");
|
|
370
|
+
}
|
|
371
|
+
if (issuesByCategory.security > 0) {
|
|
372
|
+
recommendations.push("Security issues found. Review raw output usage and input sanitization.");
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
totalFiles,
|
|
376
|
+
totalLines,
|
|
377
|
+
avgComplexity: Math.round(avgComplexity * 100) / 100,
|
|
378
|
+
totalIssues,
|
|
379
|
+
issuesByCategory,
|
|
380
|
+
performanceScore,
|
|
381
|
+
recommendations
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// src/caching.ts
|
|
385
|
+
import fs from "fs";
|
|
62
386
|
import path from "path";
|
|
387
|
+
var templateCache = new Map;
|
|
388
|
+
async function checkCache(filePath, options) {
|
|
389
|
+
try {
|
|
390
|
+
const cachePath = path.resolve(options.cachePath);
|
|
391
|
+
const cacheFile = path.join(cachePath, `${hashFilePath(filePath)}.html`);
|
|
392
|
+
const metaFile = path.join(cachePath, `${hashFilePath(filePath)}.meta.json`);
|
|
393
|
+
if (!await fileExists(cacheFile) || !await fileExists(metaFile))
|
|
394
|
+
return null;
|
|
395
|
+
const metaContent = await Bun.file(metaFile).text();
|
|
396
|
+
const meta = JSON.parse(metaContent);
|
|
397
|
+
if (meta.cacheVersion !== options.cacheVersion)
|
|
398
|
+
return null;
|
|
399
|
+
const stats = await fs.promises.stat(filePath);
|
|
400
|
+
if (stats.mtime.getTime() > meta.mtime)
|
|
401
|
+
return null;
|
|
402
|
+
for (const dep of meta.dependencies) {
|
|
403
|
+
if (await fileExists(dep)) {
|
|
404
|
+
const depStats = await fs.promises.stat(dep);
|
|
405
|
+
if (depStats.mtime.getTime() > meta.mtime)
|
|
406
|
+
return null;
|
|
407
|
+
} else {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return await Bun.file(cacheFile).text();
|
|
412
|
+
} catch (err) {
|
|
413
|
+
console.warn(`Cache error for ${filePath}:`, err);
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async function cacheTemplate(filePath, output, dependencies, options) {
|
|
418
|
+
try {
|
|
419
|
+
const cachePath = path.resolve(options.cachePath);
|
|
420
|
+
await fs.promises.mkdir(cachePath, { recursive: true });
|
|
421
|
+
const cacheFile = path.join(cachePath, `${hashFilePath(filePath)}.html`);
|
|
422
|
+
const metaFile = path.join(cachePath, `${hashFilePath(filePath)}.meta.json`);
|
|
423
|
+
const stats = await fs.promises.stat(filePath);
|
|
424
|
+
await Bun.write(cacheFile, output);
|
|
425
|
+
const meta = {
|
|
426
|
+
sourcePath: filePath,
|
|
427
|
+
mtime: stats.mtime.getTime(),
|
|
428
|
+
dependencies: Array.from(dependencies),
|
|
429
|
+
cacheVersion: options.cacheVersion,
|
|
430
|
+
generatedAt: Date.now()
|
|
431
|
+
};
|
|
432
|
+
await Bun.write(metaFile, JSON.stringify(meta, null, 2));
|
|
433
|
+
templateCache.set(filePath, {
|
|
434
|
+
output,
|
|
435
|
+
mtime: stats.mtime.getTime(),
|
|
436
|
+
dependencies
|
|
437
|
+
});
|
|
438
|
+
} catch (err) {
|
|
439
|
+
console.warn(`Failed to cache template ${filePath}:`, err);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function hashFilePath(filePath) {
|
|
443
|
+
const hash = new Bun.CryptoHasher("sha1").update(filePath).digest("hex");
|
|
444
|
+
return hash.substring(0, 16);
|
|
445
|
+
}
|
|
446
|
+
// src/dev-server.ts
|
|
447
|
+
var {serve } = globalThis.Bun;
|
|
448
|
+
import fs2 from "fs";
|
|
449
|
+
import path3 from "path";
|
|
450
|
+
import process2 from "process";
|
|
451
|
+
|
|
452
|
+
// src/plugin.ts
|
|
453
|
+
import path2 from "path";
|
|
63
454
|
var plugin = {
|
|
64
455
|
name: "bun-plugin-stx",
|
|
65
456
|
async setup(build) {
|
|
66
457
|
const options = {
|
|
67
|
-
...
|
|
458
|
+
...config,
|
|
68
459
|
...build.config?.stx
|
|
69
460
|
};
|
|
70
461
|
const allDependencies = new Set;
|
|
71
|
-
const webComponentsPath = options.webComponents?.enabled ? `./${
|
|
462
|
+
const webComponentsPath = options.webComponents?.enabled ? `./${path2.relative(path2.dirname(build.config?.outdir || "dist"), options.webComponents.outputDir || "dist/web-components")}` : "/web-components";
|
|
72
463
|
const builtComponents = [];
|
|
73
464
|
if (options.webComponents?.enabled) {
|
|
74
465
|
try {
|
|
@@ -77,132 +468,2440 @@ var plugin = {
|
|
|
77
468
|
if (options.debug && components.length > 0) {
|
|
78
469
|
console.log(`Successfully built ${components.length} web components`);
|
|
79
470
|
}
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error("Failed to build web components:", error);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error("Failed to build web components:", error);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
build.onLoad({ filter: /\.stx$/ }, async ({ path: filePath }) => {
|
|
476
|
+
try {
|
|
477
|
+
const dependencies = new Set;
|
|
478
|
+
if (options.cache && options.cachePath) {
|
|
479
|
+
const cachedOutput = await checkCache(filePath, options);
|
|
480
|
+
if (cachedOutput) {
|
|
481
|
+
if (options.debug) {
|
|
482
|
+
console.log(`Using cached version of ${filePath}`);
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
contents: cachedOutput,
|
|
486
|
+
loader: "html"
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const content = await safeExecuteAsync(() => Bun.file(filePath).text(), "", (error) => {
|
|
491
|
+
throw new StxFileError(`Failed to read stx file: ${filePath}`, filePath, undefined, undefined, `File read error: ${error.message}`);
|
|
492
|
+
});
|
|
493
|
+
if (!content) {
|
|
494
|
+
throw new StxFileError(`stx file is empty: ${filePath}`, filePath);
|
|
495
|
+
}
|
|
496
|
+
const { scriptContent, templateContent } = performanceMonitor.time("script-extraction", () => {
|
|
497
|
+
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
498
|
+
const scriptContent2 = scriptMatch ? scriptMatch[1] : "";
|
|
499
|
+
const templateContent2 = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
|
|
500
|
+
return { scriptContent: scriptContent2, templateContent: templateContent2 };
|
|
501
|
+
});
|
|
502
|
+
const context = {
|
|
503
|
+
__filename: filePath,
|
|
504
|
+
__dirname: path2.dirname(filePath),
|
|
505
|
+
__stx: {
|
|
506
|
+
webComponentsPath,
|
|
507
|
+
builtComponents
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
await safeExecuteAsync(() => extractVariables(scriptContent, context, filePath), undefined, (error) => {
|
|
511
|
+
const scriptError = new StxRuntimeError(`Script execution failed in ${filePath}: ${error.message}`, filePath, undefined, undefined, scriptContent.substring(0, 100));
|
|
512
|
+
errorLogger.log(scriptError, { filePath, scriptContent });
|
|
513
|
+
if (options.debug) {
|
|
514
|
+
throw scriptError;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
const output = await performanceMonitor.timeAsync("directive-processing", async () => {
|
|
518
|
+
return await processDirectives(templateContent, context, filePath, options, dependencies);
|
|
519
|
+
});
|
|
520
|
+
dependencies.forEach((dep) => allDependencies.add(dep));
|
|
521
|
+
if (options.cache && options.cachePath) {
|
|
522
|
+
await cacheTemplate(filePath, output, dependencies, options);
|
|
523
|
+
if (options.debug) {
|
|
524
|
+
console.log(`Cached template ${filePath} with ${dependencies.size} dependencies`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
contents: output,
|
|
529
|
+
loader: "html"
|
|
530
|
+
};
|
|
531
|
+
} catch (error) {
|
|
532
|
+
const enhancedError = error instanceof Error ? error : new StxRuntimeError(`Plugin processing failed: ${String(error)}`, filePath);
|
|
533
|
+
errorLogger.log(enhancedError, { filePath, buildConfig: build.config });
|
|
534
|
+
devHelpers.logDetailedError(enhancedError, { filePath, plugin: "bun-plugin-stx" });
|
|
535
|
+
if (options.debug) {
|
|
536
|
+
console.error("stx Plugin Error:", enhancedError);
|
|
537
|
+
}
|
|
538
|
+
const stxError = enhancedError;
|
|
539
|
+
const errorLine = stxError.line || null;
|
|
540
|
+
const errorContext = stxError.context || null;
|
|
541
|
+
const errorHtml = `<!DOCTYPE html>
|
|
542
|
+
<html lang="en">
|
|
543
|
+
<head>
|
|
544
|
+
<meta charset="UTF-8">
|
|
545
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
546
|
+
<title>stx Rendering Error</title>
|
|
547
|
+
<style>
|
|
548
|
+
body { font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 40px auto; padding: 20px; line-height: 1.6; }
|
|
549
|
+
.error-header { color: #dc3545; border-left: 4px solid #dc3545; padding-left: 16px; margin-bottom: 24px; }
|
|
550
|
+
.error-details { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 16px; margin: 16px 0; }
|
|
551
|
+
.error-code { background: #2d3748; color: #e2e8f0; padding: 16px; border-radius: 8px; overflow-x: auto; font-family: 'Monaco', 'Menlo', monospace; font-size: 14px; }
|
|
552
|
+
.help-section { background: #e3f2fd; border-left: 4px solid #2196f3; padding: 16px; margin-top: 24px; }
|
|
553
|
+
.file-path { font-family: monospace; background: #f1f3f4; padding: 2px 6px; border-radius: 4px; }
|
|
554
|
+
</style>
|
|
555
|
+
</head>
|
|
556
|
+
<body>
|
|
557
|
+
<div class="error-header">
|
|
558
|
+
<h1>stx Template Error</h1>
|
|
559
|
+
<p>An error occurred while processing your stx template.</p>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div class="error-details">
|
|
563
|
+
<h3>Error Details</h3>
|
|
564
|
+
<p><strong>File:</strong> <span class="file-path">${filePath}</span></p>
|
|
565
|
+
<p><strong>Error:</strong> ${enhancedError.message}</p>
|
|
566
|
+
${errorLine ? `<p><strong>Line:</strong> ${errorLine}</p>` : ""}
|
|
567
|
+
${errorContext ? `<p><strong>Context:</strong> ${errorContext}</p>` : ""}
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
${enhancedError.stack ? `
|
|
571
|
+
<div class="error-details">
|
|
572
|
+
<h3>Stack Trace</h3>
|
|
573
|
+
<pre class="error-code">${enhancedError.stack}</pre>
|
|
574
|
+
</div>` : ""}
|
|
575
|
+
|
|
576
|
+
<div class="help-section">
|
|
577
|
+
<h3>\uD83D\uDCA1 Troubleshooting Tips</h3>
|
|
578
|
+
<ul>
|
|
579
|
+
<li>Check the syntax of your stx directives (e.g., @if, @foreach)</li>
|
|
580
|
+
<li>Verify that all variables used in the template are properly defined</li>
|
|
581
|
+
<li>Ensure script tags have valid JavaScript/TypeScript syntax</li>
|
|
582
|
+
<li>Run <code>stx debug ${path2.basename(filePath)}</code> for detailed analysis</li>
|
|
583
|
+
<li>Enable debug mode in your stx config for more detailed error messages</li>
|
|
584
|
+
</ul>
|
|
585
|
+
</div>
|
|
586
|
+
</body>
|
|
587
|
+
</html>`;
|
|
588
|
+
return {
|
|
589
|
+
contents: errorHtml,
|
|
590
|
+
loader: "html"
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/dev-server.ts
|
|
598
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
599
|
+
for (let i = 0;i < maxAttempts; i++) {
|
|
600
|
+
const port = startPort + i;
|
|
601
|
+
try {
|
|
602
|
+
const testServer = serve({
|
|
603
|
+
port,
|
|
604
|
+
fetch: () => new Response("test")
|
|
605
|
+
});
|
|
606
|
+
testServer.stop();
|
|
607
|
+
return port;
|
|
608
|
+
} catch (error) {
|
|
609
|
+
if (error.code === "EADDRINUSE") {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
throw new Error(`Could not find an available port between ${startPort} and ${startPort + maxAttempts - 1}`);
|
|
616
|
+
}
|
|
617
|
+
var colors = {
|
|
618
|
+
reset: "\x1B[0m",
|
|
619
|
+
bright: "\x1B[1m",
|
|
620
|
+
dim: "\x1B[2m",
|
|
621
|
+
underscore: "\x1B[4m",
|
|
622
|
+
blink: "\x1B[5m",
|
|
623
|
+
reverse: "\x1B[7m",
|
|
624
|
+
hidden: "\x1B[8m",
|
|
625
|
+
black: "\x1B[30m",
|
|
626
|
+
red: "\x1B[31m",
|
|
627
|
+
green: "\x1B[32m",
|
|
628
|
+
yellow: "\x1B[33m",
|
|
629
|
+
blue: "\x1B[34m",
|
|
630
|
+
magenta: "\x1B[35m",
|
|
631
|
+
cyan: "\x1B[36m",
|
|
632
|
+
white: "\x1B[37m",
|
|
633
|
+
gray: "\x1B[90m",
|
|
634
|
+
bgBlack: "\x1B[40m",
|
|
635
|
+
bgRed: "\x1B[41m",
|
|
636
|
+
bgGreen: "\x1B[42m",
|
|
637
|
+
bgYellow: "\x1B[43m",
|
|
638
|
+
bgBlue: "\x1B[44m",
|
|
639
|
+
bgMagenta: "\x1B[45m",
|
|
640
|
+
bgCyan: "\x1B[46m",
|
|
641
|
+
bgWhite: "\x1B[47m",
|
|
642
|
+
bgGray: "\x1B[100m"
|
|
643
|
+
};
|
|
644
|
+
function setupKeyboardShortcuts(serverUrl, stopServer) {
|
|
645
|
+
if (process2.stdin.isTTY) {
|
|
646
|
+
process2.stdin.setRawMode(true);
|
|
647
|
+
process2.stdin.setEncoding("utf8");
|
|
648
|
+
process2.stdin.resume();
|
|
649
|
+
console.log(`
|
|
650
|
+
Keyboard shortcuts:`);
|
|
651
|
+
console.log(` ${colors.cyan}o${colors.reset} + Enter - Open in browser`);
|
|
652
|
+
console.log(` ${colors.cyan}c${colors.reset} + Enter - Clear console`);
|
|
653
|
+
console.log(` ${colors.cyan}q${colors.reset} + Enter (or ${colors.cyan}Ctrl+C${colors.reset}) - Quit server`);
|
|
654
|
+
let buffer = "";
|
|
655
|
+
process2.stdin.on("data", (key) => {
|
|
656
|
+
if (key === "\x03") {
|
|
657
|
+
stopServer();
|
|
658
|
+
process2.exit(0);
|
|
659
|
+
}
|
|
660
|
+
buffer += key;
|
|
661
|
+
if (buffer.endsWith("\r") || buffer.endsWith(`
|
|
662
|
+
`)) {
|
|
663
|
+
const cmd = buffer.trim().toLowerCase();
|
|
664
|
+
buffer = "";
|
|
665
|
+
if (cmd === "o") {
|
|
666
|
+
console.log(`${colors.dim}Opening ${colors.cyan}${serverUrl}${colors.dim} in your browser...${colors.reset}`);
|
|
667
|
+
Bun.spawn(["open", serverUrl], { stderr: "inherit" });
|
|
668
|
+
} else if (cmd === "c") {
|
|
669
|
+
console.clear();
|
|
670
|
+
console.log(`${colors.green}Server running at ${colors.cyan}${serverUrl}${colors.reset}`);
|
|
671
|
+
console.log(`Press ${colors.cyan}Ctrl+C${colors.reset} to stop the server`);
|
|
672
|
+
console.log(`
|
|
673
|
+
Keyboard shortcuts:`);
|
|
674
|
+
console.log(` ${colors.cyan}o${colors.reset} + Enter - Open in browser`);
|
|
675
|
+
console.log(` ${colors.cyan}c${colors.reset} + Enter - Clear console`);
|
|
676
|
+
console.log(` ${colors.cyan}q${colors.reset} + Enter (or ${colors.cyan}Ctrl+C${colors.reset}) - Quit server`);
|
|
677
|
+
} else if (cmd === "q") {
|
|
678
|
+
console.log(`${colors.yellow}Stopping server...${colors.reset}`);
|
|
679
|
+
stopServer();
|
|
680
|
+
process2.exit(0);
|
|
681
|
+
} else if (cmd === "h") {
|
|
682
|
+
console.log(`
|
|
683
|
+
Keyboard Shortcuts:`);
|
|
684
|
+
console.log(` ${colors.cyan}o${colors.reset} + Enter - Open in browser`);
|
|
685
|
+
console.log(` ${colors.cyan}c${colors.reset} + Enter - Clear console`);
|
|
686
|
+
console.log(` ${colors.cyan}q${colors.reset} + Enter (or ${colors.cyan}Ctrl+C${colors.reset}) - Quit server`);
|
|
687
|
+
console.log(` ${colors.cyan}h${colors.reset} + Enter - Show this help`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async function serveMarkdownFile(filePath, options = {}) {
|
|
694
|
+
const port = options.port || 3000;
|
|
695
|
+
const watch = options.watch !== false;
|
|
696
|
+
const absolutePath = path3.resolve(filePath);
|
|
697
|
+
if (!fs2.existsSync(absolutePath)) {
|
|
698
|
+
console.error(`${colors.red}Error: File not found: ${colors.bright}${absolutePath}${colors.reset}`);
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
if (!absolutePath.endsWith(".md")) {
|
|
702
|
+
console.error(`${colors.red}Error: File must have .md extension: ${colors.bright}${absolutePath}${colors.reset}`);
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
console.log(`${colors.blue}Processing${colors.reset} ${colors.bright}${filePath}${colors.reset}...`);
|
|
706
|
+
let htmlContent = null;
|
|
707
|
+
const processFile = async () => {
|
|
708
|
+
try {
|
|
709
|
+
const { content, data } = await readMarkdownFile(absolutePath, {
|
|
710
|
+
markdown: {
|
|
711
|
+
syntaxHighlighting: {
|
|
712
|
+
serverSide: true,
|
|
713
|
+
enabled: true,
|
|
714
|
+
defaultTheme: config.markdown?.syntaxHighlighting?.defaultTheme || "github-dark",
|
|
715
|
+
highlightUnknownLanguages: true
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
const markdownConfig = options.markdown?.syntaxHighlighting || config.markdown?.syntaxHighlighting;
|
|
720
|
+
const defaultTheme = markdownConfig?.defaultTheme || "github-dark";
|
|
721
|
+
const baseThemes = ["github-dark"];
|
|
722
|
+
const configThemes = markdownConfig?.additionalThemes || [];
|
|
723
|
+
const availableThemes = [...new Set([...baseThemes, ...configThemes])];
|
|
724
|
+
const themeOptions = availableThemes.map((theme) => `<option value="${theme}"${theme === defaultTheme ? " selected" : ""}>${theme}</option>`).join(`
|
|
725
|
+
`);
|
|
726
|
+
htmlContent = `
|
|
727
|
+
<!DOCTYPE html>
|
|
728
|
+
<html lang="en">
|
|
729
|
+
<head>
|
|
730
|
+
<meta charset="UTF-8">
|
|
731
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
732
|
+
<title>${data.title || path3.basename(absolutePath)}</title>
|
|
733
|
+
<!-- Syntax highlighting styles -->
|
|
734
|
+
<style id="syntax-theme">
|
|
735
|
+
:root {
|
|
736
|
+
--shiki-color-text: #24292e;
|
|
737
|
+
--shiki-color-background: #ffffff;
|
|
738
|
+
--shiki-token-constant: #005cc5;
|
|
739
|
+
--shiki-token-string: #032f62;
|
|
740
|
+
--shiki-token-comment: #6a737d;
|
|
741
|
+
--shiki-token-keyword: #d73a49;
|
|
742
|
+
--shiki-token-parameter: #24292e;
|
|
743
|
+
--shiki-token-function: #6f42c1;
|
|
744
|
+
--shiki-token-string-expression: #032f62;
|
|
745
|
+
--shiki-token-punctuation: #24292e;
|
|
746
|
+
--shiki-token-link: #032f62;
|
|
747
|
+
}
|
|
748
|
+
pre {
|
|
749
|
+
background-color: var(--shiki-color-background);
|
|
750
|
+
padding: 1rem;
|
|
751
|
+
border-radius: 4px;
|
|
752
|
+
}
|
|
753
|
+
code {
|
|
754
|
+
color: var(--shiki-color-text);
|
|
755
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
756
|
+
}
|
|
757
|
+
.dark-mode {
|
|
758
|
+
--shiki-color-text: #e1e4e8;
|
|
759
|
+
--shiki-color-background: #24292e;
|
|
760
|
+
--shiki-token-constant: #79b8ff;
|
|
761
|
+
--shiki-token-string: #9ecbff;
|
|
762
|
+
--shiki-token-comment: #6a737d;
|
|
763
|
+
--shiki-token-keyword: #f97583;
|
|
764
|
+
--shiki-token-parameter: #e1e4e8;
|
|
765
|
+
--shiki-token-function: #b392f0;
|
|
766
|
+
--shiki-token-string-expression: #9ecbff;
|
|
767
|
+
--shiki-token-punctuation: #e1e4e8;
|
|
768
|
+
--shiki-token-link: #9ecbff;
|
|
769
|
+
}
|
|
770
|
+
</style>
|
|
771
|
+
<style>
|
|
772
|
+
body {
|
|
773
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
774
|
+
line-height: 1.6;
|
|
775
|
+
color: #333;
|
|
776
|
+
max-width: 800px;
|
|
777
|
+
margin: 0 auto;
|
|
778
|
+
padding: 2rem;
|
|
779
|
+
}
|
|
780
|
+
pre, code {
|
|
781
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
782
|
+
}
|
|
783
|
+
pre {
|
|
784
|
+
border-radius: 4px;
|
|
785
|
+
padding: 0;
|
|
786
|
+
margin: 1.5rem 0;
|
|
787
|
+
overflow-x: auto;
|
|
788
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
789
|
+
}
|
|
790
|
+
pre code {
|
|
791
|
+
display: block;
|
|
792
|
+
padding: 1rem;
|
|
793
|
+
overflow-x: auto;
|
|
794
|
+
}
|
|
795
|
+
/* Apply background color to code blocks based on theme */
|
|
796
|
+
pre.syntax-highlighter {
|
|
797
|
+
background-color: var(--shiki-color-background) !important;
|
|
798
|
+
}
|
|
799
|
+
.dark-mode pre.syntax-highlighter {
|
|
800
|
+
background-color: var(--shiki-color-background) !important;
|
|
801
|
+
}
|
|
802
|
+
code {
|
|
803
|
+
padding: 0.2rem 0.4rem;
|
|
804
|
+
border-radius: 3px;
|
|
805
|
+
}
|
|
806
|
+
h1, h2, h3, h4 {
|
|
807
|
+
margin-top: 2rem;
|
|
808
|
+
margin-bottom: 1rem;
|
|
809
|
+
}
|
|
810
|
+
h1 { color: #111; border-bottom: 1px solid #eee; padding-bottom: 0.5rem; }
|
|
811
|
+
h2 { color: #333; border-bottom: 1px solid #f0f0f0; padding-bottom: 0.3rem; }
|
|
812
|
+
h3 { color: #444; }
|
|
813
|
+
img {
|
|
814
|
+
max-width: 100%;
|
|
815
|
+
border-radius: 4px;
|
|
816
|
+
margin: 1rem 0;
|
|
817
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
818
|
+
}
|
|
819
|
+
blockquote {
|
|
820
|
+
border-left: 4px solid #ddd;
|
|
821
|
+
padding-left: 1rem;
|
|
822
|
+
margin-left: 0;
|
|
823
|
+
color: #555;
|
|
824
|
+
background: #f9f9f9;
|
|
825
|
+
padding: 0.5rem 1rem;
|
|
826
|
+
margin: 1.5rem 0;
|
|
827
|
+
}
|
|
828
|
+
table {
|
|
829
|
+
border-collapse: collapse;
|
|
830
|
+
width: 100%;
|
|
831
|
+
margin: 1.5rem 0;
|
|
832
|
+
}
|
|
833
|
+
th, td {
|
|
834
|
+
border: 1px solid #ddd;
|
|
835
|
+
padding: 0.5rem;
|
|
836
|
+
}
|
|
837
|
+
th {
|
|
838
|
+
background: #f0f0f0;
|
|
839
|
+
text-align: left;
|
|
840
|
+
}
|
|
841
|
+
tr:nth-child(even) {
|
|
842
|
+
background-color: #f8f8f8;
|
|
843
|
+
}
|
|
844
|
+
a {
|
|
845
|
+
color: #0066cc;
|
|
846
|
+
text-decoration: none;
|
|
847
|
+
}
|
|
848
|
+
a:hover {
|
|
849
|
+
text-decoration: underline;
|
|
850
|
+
}
|
|
851
|
+
hr {
|
|
852
|
+
border: 0;
|
|
853
|
+
border-top: 1px solid #eee;
|
|
854
|
+
margin: 2rem 0;
|
|
855
|
+
}
|
|
856
|
+
.frontmatter {
|
|
857
|
+
background: #f8f8f8;
|
|
858
|
+
border-radius: 4px;
|
|
859
|
+
padding: 1.5rem;
|
|
860
|
+
margin-bottom: 2rem;
|
|
861
|
+
font-size: 0.9rem;
|
|
862
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
863
|
+
border-left: 4px solid #ddd;
|
|
864
|
+
}
|
|
865
|
+
.frontmatter-item {
|
|
866
|
+
margin-bottom: 0.5rem;
|
|
867
|
+
display: flex;
|
|
868
|
+
}
|
|
869
|
+
.frontmatter-label {
|
|
870
|
+
font-weight: bold;
|
|
871
|
+
min-width: 100px;
|
|
872
|
+
color: #555;
|
|
873
|
+
}
|
|
874
|
+
/* Theme selector for code blocks */
|
|
875
|
+
.theme-selector {
|
|
876
|
+
margin: 1rem 0;
|
|
877
|
+
padding: 0.5rem;
|
|
878
|
+
background: #f8f8f8;
|
|
879
|
+
border-radius: 4px;
|
|
880
|
+
text-align: right;
|
|
881
|
+
}
|
|
882
|
+
select {
|
|
883
|
+
padding: 0.25rem 0.5rem;
|
|
884
|
+
border-radius: 3px;
|
|
885
|
+
border: 1px solid #ddd;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/* Dark mode body styles */
|
|
889
|
+
body.dark-mode {
|
|
890
|
+
background-color: #121212;
|
|
891
|
+
color: #e1e4e8;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
body.dark-mode h1,
|
|
895
|
+
body.dark-mode h2,
|
|
896
|
+
body.dark-mode h3 {
|
|
897
|
+
color: #e1e4e8;
|
|
898
|
+
border-color: #2f363d;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
body.dark-mode .theme-selector {
|
|
902
|
+
background: #2f363d;
|
|
903
|
+
color: #e1e4e8;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
body.dark-mode select {
|
|
907
|
+
background: #24292e;
|
|
908
|
+
color: #e1e4e8;
|
|
909
|
+
border-color: #444;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
body.dark-mode blockquote {
|
|
913
|
+
background: #24292e;
|
|
914
|
+
color: #e1e4e8;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
body.dark-mode .frontmatter {
|
|
918
|
+
background: #24292e;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
body.dark-mode a {
|
|
922
|
+
color: #58a6ff;
|
|
923
|
+
}
|
|
924
|
+
</style>
|
|
925
|
+
</head>
|
|
926
|
+
<body>
|
|
927
|
+
${Object.keys(data).length > 0 ? `
|
|
928
|
+
<div class="frontmatter">
|
|
929
|
+
<h3>Frontmatter</h3>
|
|
930
|
+
${Object.entries(data).map(([key, value]) => `
|
|
931
|
+
<div class="frontmatter-item">
|
|
932
|
+
<span class="frontmatter-label">${key}:</span>
|
|
933
|
+
<span>${Array.isArray(value) ? value.join(", ") : value}</span>
|
|
934
|
+
</div>`).join("")}
|
|
935
|
+
</div>
|
|
936
|
+
` : ""}
|
|
937
|
+
|
|
938
|
+
<div class="theme-selector">
|
|
939
|
+
Theme:
|
|
940
|
+
<select id="themeSelector" onchange="changeTheme()">
|
|
941
|
+
${themeOptions}
|
|
942
|
+
</select>
|
|
943
|
+
</div>
|
|
944
|
+
|
|
945
|
+
${content}
|
|
946
|
+
|
|
947
|
+
<script>
|
|
948
|
+
function changeTheme() {
|
|
949
|
+
const theme = document.getElementById('themeSelector').value;
|
|
950
|
+
|
|
951
|
+
// Toggle dark mode class based on theme
|
|
952
|
+
if (theme.includes('dark') || theme.includes('night') || theme.includes('monokai') ||
|
|
953
|
+
theme.includes('dracula') || theme.includes('nord') || theme.includes('material')) {
|
|
954
|
+
document.body.classList.add('dark-mode');
|
|
955
|
+
} else {
|
|
956
|
+
document.body.classList.remove('dark-mode');
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Initialize theme on page load
|
|
961
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
962
|
+
changeTheme();
|
|
963
|
+
});
|
|
964
|
+
</script>
|
|
965
|
+
</body>
|
|
966
|
+
</html>
|
|
967
|
+
`;
|
|
968
|
+
return true;
|
|
969
|
+
} catch (error) {
|
|
970
|
+
console.error(`${colors.red}Error processing Markdown file:${colors.reset}`, error);
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
const processSuccess = await processFile();
|
|
975
|
+
if (!processSuccess) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
let actualPort = port;
|
|
979
|
+
try {
|
|
980
|
+
actualPort = await findAvailablePort(port);
|
|
981
|
+
if (actualPort !== port) {
|
|
982
|
+
console.log(`${colors.yellow}Port ${port} is busy, using port ${actualPort} instead${colors.reset}`);
|
|
983
|
+
}
|
|
984
|
+
} catch {
|
|
985
|
+
console.error(`${colors.red}Could not find an available port${colors.reset}`);
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
console.log(`${colors.blue}Starting server on ${colors.cyan}http://localhost:${actualPort}/${colors.reset}...`);
|
|
989
|
+
const server = serve({
|
|
990
|
+
port: actualPort,
|
|
991
|
+
fetch(request) {
|
|
992
|
+
const url = new URL(request.url);
|
|
993
|
+
if (url.pathname === "/") {
|
|
994
|
+
return new Response(htmlContent, {
|
|
995
|
+
headers: {
|
|
996
|
+
"Content-Type": "text/html"
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return new Response("Not Found", { status: 404 });
|
|
1001
|
+
},
|
|
1002
|
+
error(error) {
|
|
1003
|
+
return new Response(`<pre>${error}
|
|
1004
|
+
${error.stack}</pre>`, {
|
|
1005
|
+
headers: {
|
|
1006
|
+
"Content-Type": "text/html"
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
console.clear();
|
|
1012
|
+
console.log(`
|
|
1013
|
+
${colors.blue}stx${colors.reset} ${colors.green}${process2.env.stx_VERSION || "v0.0.10"}${colors.reset} ${colors.dim}ready in ${Math.random() * 10 + 5 | 0}.${Math.random() * 90 + 10 | 0} ms${colors.reset}`);
|
|
1014
|
+
console.log(`
|
|
1015
|
+
${colors.bright}\u2192 ${colors.cyan}http://localhost:${port}/${colors.reset}`);
|
|
1016
|
+
console.log(`
|
|
1017
|
+
${colors.yellow}Routes:${colors.reset}`);
|
|
1018
|
+
const relativeFilePath = path3.relative(process2.cwd(), absolutePath);
|
|
1019
|
+
console.log(` ${colors.green}\u2514\u2500 /${colors.reset} \u2192 ${colors.bright}${relativeFilePath}${colors.reset}`);
|
|
1020
|
+
console.log(`
|
|
1021
|
+
Press ${colors.cyan}h${colors.reset} + ${colors.cyan}Enter${colors.reset} to show shortcuts`);
|
|
1022
|
+
setupKeyboardShortcuts(server.url.toString(), () => {
|
|
1023
|
+
if (watch) {
|
|
1024
|
+
const watcher = fs2.watch(path3.dirname(absolutePath), { recursive: true });
|
|
1025
|
+
watcher.close();
|
|
1026
|
+
}
|
|
1027
|
+
server.stop();
|
|
1028
|
+
});
|
|
1029
|
+
if (watch) {
|
|
1030
|
+
const dirToWatch = path3.dirname(absolutePath);
|
|
1031
|
+
console.log(`${colors.blue}Watching ${colors.bright}${dirToWatch}${colors.reset} for changes...`);
|
|
1032
|
+
const watcher = fs2.watch(dirToWatch, { recursive: true }, async (eventType, filename) => {
|
|
1033
|
+
if (filename && filename.endsWith(".md")) {
|
|
1034
|
+
console.log(`${colors.yellow}File ${colors.bright}${filename}${colors.yellow} changed, reprocessing...${colors.reset}`);
|
|
1035
|
+
await processFile();
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
process2.on("SIGINT", () => {
|
|
1039
|
+
watcher.close();
|
|
1040
|
+
server.stop();
|
|
1041
|
+
process2.exit(0);
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1046
|
+
async function serveStxFile(filePath, options = {}) {
|
|
1047
|
+
const port = options.port || 3000;
|
|
1048
|
+
const watch = options.watch !== false;
|
|
1049
|
+
const absolutePath = path3.resolve(filePath);
|
|
1050
|
+
if (!fs2.existsSync(absolutePath)) {
|
|
1051
|
+
console.error(`${colors.red}Error: File not found: ${colors.bright}${absolutePath}${colors.reset}`);
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
if (absolutePath.endsWith(".md")) {
|
|
1055
|
+
return serveMarkdownFile(absolutePath, options);
|
|
1056
|
+
} else if (!absolutePath.endsWith(".stx")) {
|
|
1057
|
+
console.error(`${colors.red}Error: Unsupported file type: ${colors.bright}${absolutePath}${colors.reset}. Only .stx and .md files are supported.`);
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
const outputDir = path3.join(process2.cwd(), ".stx-output");
|
|
1061
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
1062
|
+
console.log(`${colors.blue}Building ${colors.bright}${filePath}${colors.reset}...`);
|
|
1063
|
+
let htmlContent = null;
|
|
1064
|
+
const buildFile = async () => {
|
|
1065
|
+
try {
|
|
1066
|
+
const result = await Bun.build({
|
|
1067
|
+
entrypoints: [absolutePath],
|
|
1068
|
+
outdir: outputDir,
|
|
1069
|
+
plugins: [plugin],
|
|
1070
|
+
define: {
|
|
1071
|
+
"process.env.NODE_ENV": '"development"'
|
|
1072
|
+
},
|
|
1073
|
+
...options.stxOptions
|
|
1074
|
+
});
|
|
1075
|
+
if (!result.success) {
|
|
1076
|
+
console.error(`${colors.red}Build failed:${colors.reset}`, result.logs);
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
const htmlOutput = result.outputs.find((o) => o.path.endsWith(".html"));
|
|
1080
|
+
if (!htmlOutput) {
|
|
1081
|
+
console.error(`${colors.red}No HTML output found${colors.reset}`);
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
const html = await Bun.file(htmlOutput.path).text();
|
|
1085
|
+
htmlContent = html;
|
|
1086
|
+
return true;
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
console.error(`${colors.red}Error building stx file:${colors.reset}`, error);
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
const buildSuccess = await buildFile();
|
|
1093
|
+
if (!buildSuccess) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
let actualPort = port;
|
|
1097
|
+
try {
|
|
1098
|
+
actualPort = await findAvailablePort(port);
|
|
1099
|
+
if (actualPort !== port) {
|
|
1100
|
+
console.log(`${colors.yellow}Port ${port} is busy, using port ${actualPort} instead${colors.reset}`);
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
console.error(`${colors.red}Could not find an available port${colors.reset}`);
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
console.log(`${colors.blue}Starting server on ${colors.cyan}http://localhost:${actualPort}/${colors.reset}...`);
|
|
1107
|
+
const server = serve({
|
|
1108
|
+
port: actualPort,
|
|
1109
|
+
fetch(request) {
|
|
1110
|
+
const url = new URL(request.url);
|
|
1111
|
+
if (url.pathname === "/") {
|
|
1112
|
+
return new Response(htmlContent, {
|
|
1113
|
+
headers: {
|
|
1114
|
+
"Content-Type": "text/html"
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
const requestedPath = path3.join(outputDir, url.pathname);
|
|
1119
|
+
if (fs2.existsSync(requestedPath) && fs2.statSync(requestedPath).isFile()) {
|
|
1120
|
+
const file = Bun.file(requestedPath);
|
|
1121
|
+
const ext = path3.extname(requestedPath).toLowerCase();
|
|
1122
|
+
let contentType = "text/plain";
|
|
1123
|
+
switch (ext) {
|
|
1124
|
+
case ".html":
|
|
1125
|
+
contentType = "text/html";
|
|
1126
|
+
break;
|
|
1127
|
+
case ".css":
|
|
1128
|
+
contentType = "text/css";
|
|
1129
|
+
break;
|
|
1130
|
+
case ".js":
|
|
1131
|
+
contentType = "text/javascript";
|
|
1132
|
+
break;
|
|
1133
|
+
case ".json":
|
|
1134
|
+
contentType = "application/json";
|
|
1135
|
+
break;
|
|
1136
|
+
case ".png":
|
|
1137
|
+
contentType = "image/png";
|
|
1138
|
+
break;
|
|
1139
|
+
case ".jpg":
|
|
1140
|
+
case ".jpeg":
|
|
1141
|
+
contentType = "image/jpeg";
|
|
1142
|
+
break;
|
|
1143
|
+
case ".gif":
|
|
1144
|
+
contentType = "image/gif";
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
return new Response(file, {
|
|
1148
|
+
headers: { "Content-Type": contentType }
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
return new Response("Not Found", { status: 404 });
|
|
1152
|
+
},
|
|
1153
|
+
error(error) {
|
|
1154
|
+
return new Response(`<pre>${error}
|
|
1155
|
+
${error.stack}</pre>`, {
|
|
1156
|
+
headers: {
|
|
1157
|
+
"Content-Type": "text/html"
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
console.clear();
|
|
1163
|
+
console.log(`
|
|
1164
|
+
${colors.blue}stx${colors.reset} ${colors.green}${process2.env.stx_VERSION || "v0.0.10"}${colors.reset} ${colors.dim}ready in ${Math.random() * 10 + 5 | 0}.${Math.random() * 90 + 10 | 0} ms${colors.reset}`);
|
|
1165
|
+
console.log(`
|
|
1166
|
+
${colors.bright}\u2192 ${colors.cyan}http://localhost:${port}/${colors.reset}`);
|
|
1167
|
+
console.log(`
|
|
1168
|
+
${colors.yellow}Routes:${colors.reset}`);
|
|
1169
|
+
const relativeFilePath = path3.relative(process2.cwd(), absolutePath);
|
|
1170
|
+
console.log(` ${colors.green}\u2514\u2500 /${colors.reset} \u2192 ${colors.bright}${relativeFilePath}${colors.reset}`);
|
|
1171
|
+
console.log(`
|
|
1172
|
+
Press ${colors.cyan}h${colors.reset} + ${colors.cyan}Enter${colors.reset} to show shortcuts`);
|
|
1173
|
+
setupKeyboardShortcuts(server.url.toString(), () => {
|
|
1174
|
+
if (watch) {
|
|
1175
|
+
const watcher = fs2.watch(path3.dirname(absolutePath), { recursive: true });
|
|
1176
|
+
watcher.close();
|
|
1177
|
+
}
|
|
1178
|
+
server.stop();
|
|
1179
|
+
});
|
|
1180
|
+
if (watch) {
|
|
1181
|
+
const dirToWatch = path3.dirname(absolutePath);
|
|
1182
|
+
console.log(`${colors.blue}Watching ${colors.bright}${dirToWatch}${colors.reset} for changes...`);
|
|
1183
|
+
const watcher = fs2.watch(dirToWatch, { recursive: true }, async (eventType, filename) => {
|
|
1184
|
+
if (filename && (filename.endsWith(".stx") || filename.endsWith(".js") || filename.endsWith(".ts"))) {
|
|
1185
|
+
console.log(`${colors.yellow}File ${colors.bright}${filename}${colors.yellow} changed, rebuilding...${colors.reset}`);
|
|
1186
|
+
await buildFile();
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
process2.on("SIGINT", () => {
|
|
1190
|
+
watcher.close();
|
|
1191
|
+
server.stop();
|
|
1192
|
+
process2.exit(0);
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return true;
|
|
1196
|
+
}
|
|
1197
|
+
async function serveMultipleStxFiles(filePaths, options = {}) {
|
|
1198
|
+
const port = options.port || 3000;
|
|
1199
|
+
const watch = options.watch !== false;
|
|
1200
|
+
for (const filePath of filePaths) {
|
|
1201
|
+
const absolutePath = path3.resolve(filePath);
|
|
1202
|
+
if (!fs2.existsSync(absolutePath)) {
|
|
1203
|
+
console.error(`${colors.red}Error: File not found: ${colors.bright}${absolutePath}${colors.reset}`);
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
if (!absolutePath.endsWith(".stx") && !absolutePath.endsWith(".md")) {
|
|
1207
|
+
console.error(`${colors.red}Error: Unsupported file type: ${colors.bright}${absolutePath}${colors.reset}. Only .stx and .md files are supported.`);
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const outputDir = path3.join(process2.cwd(), ".stx-output");
|
|
1212
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
1213
|
+
const commonDir = findCommonDir(filePaths.map((f) => path3.dirname(path3.resolve(f))));
|
|
1214
|
+
console.log(`${colors.blue}Processing ${colors.bright}${filePaths.length}${colors.reset} files...`);
|
|
1215
|
+
const routes = {};
|
|
1216
|
+
const buildFiles = async () => {
|
|
1217
|
+
try {
|
|
1218
|
+
for (const filePath of filePaths) {
|
|
1219
|
+
const absolutePath = path3.resolve(filePath);
|
|
1220
|
+
const isMarkdown = absolutePath.endsWith(".md");
|
|
1221
|
+
if (isMarkdown) {
|
|
1222
|
+
try {
|
|
1223
|
+
const { content, data } = await readMarkdownFile(absolutePath, {
|
|
1224
|
+
markdown: {
|
|
1225
|
+
syntaxHighlighting: {
|
|
1226
|
+
serverSide: true,
|
|
1227
|
+
enabled: true,
|
|
1228
|
+
defaultTheme: config.markdown?.syntaxHighlighting?.defaultTheme || "github-dark",
|
|
1229
|
+
highlightUnknownLanguages: true
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
const markdownConfig = options.markdown?.syntaxHighlighting || config.markdown?.syntaxHighlighting;
|
|
1234
|
+
const defaultTheme = markdownConfig?.defaultTheme || "github-dark";
|
|
1235
|
+
const baseThemes = ["github-dark"];
|
|
1236
|
+
const configThemes = markdownConfig?.additionalThemes || [];
|
|
1237
|
+
const availableThemes = [...new Set([...baseThemes, ...configThemes])];
|
|
1238
|
+
const themeOptions = availableThemes.map((theme) => `<option value="${theme}"${theme === defaultTheme ? " selected" : ""}>${theme}</option>`).join(`
|
|
1239
|
+
`);
|
|
1240
|
+
const htmlContent = `
|
|
1241
|
+
<!DOCTYPE html>
|
|
1242
|
+
<html lang="en">
|
|
1243
|
+
<head>
|
|
1244
|
+
<meta charset="UTF-8">
|
|
1245
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1246
|
+
<title>${data.title || path3.basename(absolutePath)}</title>
|
|
1247
|
+
<!-- Syntax highlighting styles -->
|
|
1248
|
+
<style id="syntax-theme">
|
|
1249
|
+
:root {
|
|
1250
|
+
--shiki-color-text: #24292e;
|
|
1251
|
+
--shiki-color-background: #ffffff;
|
|
1252
|
+
--shiki-token-constant: #005cc5;
|
|
1253
|
+
--shiki-token-string: #032f62;
|
|
1254
|
+
--shiki-token-comment: #6a737d;
|
|
1255
|
+
--shiki-token-keyword: #d73a49;
|
|
1256
|
+
--shiki-token-parameter: #24292e;
|
|
1257
|
+
--shiki-token-function: #6f42c1;
|
|
1258
|
+
--shiki-token-string-expression: #032f62;
|
|
1259
|
+
--shiki-token-punctuation: #24292e;
|
|
1260
|
+
--shiki-token-link: #032f62;
|
|
1261
|
+
}
|
|
1262
|
+
pre {
|
|
1263
|
+
background-color: var(--shiki-color-background);
|
|
1264
|
+
padding: 1rem;
|
|
1265
|
+
border-radius: 4px;
|
|
1266
|
+
}
|
|
1267
|
+
code {
|
|
1268
|
+
color: var(--shiki-color-text);
|
|
1269
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1270
|
+
}
|
|
1271
|
+
.dark-mode {
|
|
1272
|
+
--shiki-color-text: #e1e4e8;
|
|
1273
|
+
--shiki-color-background: #24292e;
|
|
1274
|
+
--shiki-token-constant: #79b8ff;
|
|
1275
|
+
--shiki-token-string: #9ecbff;
|
|
1276
|
+
--shiki-token-comment: #6a737d;
|
|
1277
|
+
--shiki-token-keyword: #f97583;
|
|
1278
|
+
--shiki-token-parameter: #e1e4e8;
|
|
1279
|
+
--shiki-token-function: #b392f0;
|
|
1280
|
+
--shiki-token-string-expression: #9ecbff;
|
|
1281
|
+
--shiki-token-punctuation: #e1e4e8;
|
|
1282
|
+
--shiki-token-link: #9ecbff;
|
|
1283
|
+
}
|
|
1284
|
+
</style>
|
|
1285
|
+
<style>
|
|
1286
|
+
body {
|
|
1287
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
1288
|
+
line-height: 1.6;
|
|
1289
|
+
color: #333;
|
|
1290
|
+
max-width: 800px;
|
|
1291
|
+
margin: 0 auto;
|
|
1292
|
+
padding: 2rem;
|
|
1293
|
+
}
|
|
1294
|
+
pre, code {
|
|
1295
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1296
|
+
}
|
|
1297
|
+
pre {
|
|
1298
|
+
border-radius: 4px;
|
|
1299
|
+
padding: 0;
|
|
1300
|
+
margin: 1.5rem 0;
|
|
1301
|
+
overflow-x: auto;
|
|
1302
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
1303
|
+
}
|
|
1304
|
+
pre code {
|
|
1305
|
+
display: block;
|
|
1306
|
+
padding: 1rem;
|
|
1307
|
+
overflow-x: auto;
|
|
1308
|
+
}
|
|
1309
|
+
/* Apply background color to code blocks based on theme */
|
|
1310
|
+
pre.syntax-highlighter {
|
|
1311
|
+
background-color: var(--shiki-color-background) !important;
|
|
1312
|
+
}
|
|
1313
|
+
.dark-mode pre.syntax-highlighter {
|
|
1314
|
+
background-color: var(--shiki-color-background) !important;
|
|
1315
|
+
}
|
|
1316
|
+
code {
|
|
1317
|
+
padding: 0.2rem 0.4rem;
|
|
1318
|
+
border-radius: 3px;
|
|
1319
|
+
}
|
|
1320
|
+
h1, h2, h3, h4 {
|
|
1321
|
+
margin-top: 2rem;
|
|
1322
|
+
margin-bottom: 1rem;
|
|
1323
|
+
}
|
|
1324
|
+
h1 { color: #111; border-bottom: 1px solid #eee; padding-bottom: 0.5rem; }
|
|
1325
|
+
h2 { color: #333; border-bottom: 1px solid #f0f0f0; padding-bottom: 0.3rem; }
|
|
1326
|
+
h3 { color: #444; }
|
|
1327
|
+
img {
|
|
1328
|
+
max-width: 100%;
|
|
1329
|
+
border-radius: 4px;
|
|
1330
|
+
margin: 1rem 0;
|
|
1331
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
1332
|
+
}
|
|
1333
|
+
blockquote {
|
|
1334
|
+
border-left: 4px solid #ddd;
|
|
1335
|
+
padding-left: 1rem;
|
|
1336
|
+
margin-left: 0;
|
|
1337
|
+
color: #555;
|
|
1338
|
+
background: #f9f9f9;
|
|
1339
|
+
padding: 0.5rem 1rem;
|
|
1340
|
+
margin: 1.5rem 0;
|
|
1341
|
+
}
|
|
1342
|
+
table {
|
|
1343
|
+
border-collapse: collapse;
|
|
1344
|
+
width: 100%;
|
|
1345
|
+
margin: 1.5rem 0;
|
|
1346
|
+
}
|
|
1347
|
+
th, td {
|
|
1348
|
+
border: 1px solid #ddd;
|
|
1349
|
+
padding: 0.5rem;
|
|
1350
|
+
}
|
|
1351
|
+
th {
|
|
1352
|
+
background: #f0f0f0;
|
|
1353
|
+
text-align: left;
|
|
1354
|
+
}
|
|
1355
|
+
tr:nth-child(even) {
|
|
1356
|
+
background-color: #f8f8f8;
|
|
1357
|
+
}
|
|
1358
|
+
a {
|
|
1359
|
+
color: #0066cc;
|
|
1360
|
+
text-decoration: none;
|
|
1361
|
+
}
|
|
1362
|
+
a:hover {
|
|
1363
|
+
text-decoration: underline;
|
|
1364
|
+
}
|
|
1365
|
+
hr {
|
|
1366
|
+
border: 0;
|
|
1367
|
+
border-top: 1px solid #eee;
|
|
1368
|
+
margin: 2rem 0;
|
|
1369
|
+
}
|
|
1370
|
+
.frontmatter {
|
|
1371
|
+
background: #f8f8f8;
|
|
1372
|
+
border-radius: 4px;
|
|
1373
|
+
padding: 1.5rem;
|
|
1374
|
+
margin-bottom: 2rem;
|
|
1375
|
+
font-size: 0.9rem;
|
|
1376
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
1377
|
+
border-left: 4px solid #ddd;
|
|
1378
|
+
}
|
|
1379
|
+
.frontmatter-item {
|
|
1380
|
+
margin-bottom: 0.5rem;
|
|
1381
|
+
display: flex;
|
|
1382
|
+
}
|
|
1383
|
+
.frontmatter-label {
|
|
1384
|
+
font-weight: bold;
|
|
1385
|
+
min-width: 100px;
|
|
1386
|
+
color: #555;
|
|
1387
|
+
}
|
|
1388
|
+
/* Theme selector for code blocks */
|
|
1389
|
+
.theme-selector {
|
|
1390
|
+
margin: 1rem 0;
|
|
1391
|
+
padding: 0.5rem;
|
|
1392
|
+
background: #f8f8f8;
|
|
1393
|
+
border-radius: 4px;
|
|
1394
|
+
text-align: right;
|
|
1395
|
+
}
|
|
1396
|
+
select {
|
|
1397
|
+
padding: 0.25rem 0.5rem;
|
|
1398
|
+
border-radius: 3px;
|
|
1399
|
+
border: 1px solid #ddd;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/* Dark mode body styles */
|
|
1403
|
+
body.dark-mode {
|
|
1404
|
+
background-color: #121212;
|
|
1405
|
+
color: #e1e4e8;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
body.dark-mode h1,
|
|
1409
|
+
body.dark-mode h2,
|
|
1410
|
+
body.dark-mode h3 {
|
|
1411
|
+
color: #e1e4e8;
|
|
1412
|
+
border-color: #2f363d;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
body.dark-mode .theme-selector {
|
|
1416
|
+
background: #2f363d;
|
|
1417
|
+
color: #e1e4e8;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
body.dark-mode select {
|
|
1421
|
+
background: #24292e;
|
|
1422
|
+
color: #e1e4e8;
|
|
1423
|
+
border-color: #444;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
body.dark-mode blockquote {
|
|
1427
|
+
background: #24292e;
|
|
1428
|
+
color: #e1e4e8;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
body.dark-mode .frontmatter {
|
|
1432
|
+
background: #24292e;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
body.dark-mode a {
|
|
1436
|
+
color: #58a6ff;
|
|
1437
|
+
}
|
|
1438
|
+
</style>
|
|
1439
|
+
</head>
|
|
1440
|
+
<body>
|
|
1441
|
+
${Object.keys(data).length > 0 ? `
|
|
1442
|
+
<div class="frontmatter">
|
|
1443
|
+
<h3>Frontmatter</h3>
|
|
1444
|
+
${Object.entries(data).map(([key, value]) => `
|
|
1445
|
+
<div class="frontmatter-item">
|
|
1446
|
+
<span class="frontmatter-label">${key}:</span>
|
|
1447
|
+
<span>${Array.isArray(value) ? value.join(", ") : value}</span>
|
|
1448
|
+
</div>`).join("")}
|
|
1449
|
+
</div>
|
|
1450
|
+
` : ""}
|
|
1451
|
+
|
|
1452
|
+
<div class="theme-selector">
|
|
1453
|
+
Theme:
|
|
1454
|
+
<select id="themeSelector" onchange="changeTheme()">
|
|
1455
|
+
${themeOptions}
|
|
1456
|
+
</select>
|
|
1457
|
+
</div>
|
|
1458
|
+
|
|
1459
|
+
${content}
|
|
1460
|
+
|
|
1461
|
+
<script>
|
|
1462
|
+
function changeTheme() {
|
|
1463
|
+
const theme = document.getElementById('themeSelector').value;
|
|
1464
|
+
|
|
1465
|
+
// Toggle dark mode class based on theme
|
|
1466
|
+
if (theme.includes('dark') || theme.includes('night') || theme.includes('monokai') ||
|
|
1467
|
+
theme.includes('dracula') || theme.includes('nord') || theme.includes('material')) {
|
|
1468
|
+
document.body.classList.add('dark-mode');
|
|
1469
|
+
} else {
|
|
1470
|
+
document.body.classList.remove('dark-mode');
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Initialize theme on page load
|
|
1475
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1476
|
+
changeTheme();
|
|
1477
|
+
});
|
|
1478
|
+
</script>
|
|
1479
|
+
</body>
|
|
1480
|
+
</html>
|
|
1481
|
+
`;
|
|
1482
|
+
const relativePath = path3.relative(commonDir, absolutePath);
|
|
1483
|
+
const routePath = `/${relativePath.replace(/\.md$/, "")}`;
|
|
1484
|
+
routes[routePath || "/"] = {
|
|
1485
|
+
filePath: absolutePath,
|
|
1486
|
+
content: htmlContent,
|
|
1487
|
+
fileType: "md"
|
|
1488
|
+
};
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
console.error(`${colors.red}Error processing Markdown file ${colors.bright}${filePath}${colors.reset}:`, error);
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
} else {
|
|
1494
|
+
const result = await Bun.build({
|
|
1495
|
+
entrypoints: [absolutePath],
|
|
1496
|
+
outdir: outputDir,
|
|
1497
|
+
plugins: [plugin],
|
|
1498
|
+
define: {
|
|
1499
|
+
"process.env.NODE_ENV": '"development"'
|
|
1500
|
+
},
|
|
1501
|
+
...options.stxOptions
|
|
1502
|
+
});
|
|
1503
|
+
if (!result.success) {
|
|
1504
|
+
console.error(`${colors.red}Build failed for ${colors.bright}${filePath}${colors.reset}:`, result.logs);
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
const htmlOutput = result.outputs.find((o) => o.path.endsWith(".html"));
|
|
1508
|
+
if (!htmlOutput) {
|
|
1509
|
+
console.error(`${colors.red}No HTML output found for ${colors.bright}${filePath}${colors.reset}`);
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const htmlContent = await Bun.file(htmlOutput.path).text();
|
|
1513
|
+
const relativePath = path3.relative(commonDir, absolutePath);
|
|
1514
|
+
const routePath = `/${relativePath.replace(/\.stx$/, "")}`;
|
|
1515
|
+
routes[routePath || "/"] = {
|
|
1516
|
+
filePath: absolutePath,
|
|
1517
|
+
content: htmlContent,
|
|
1518
|
+
fileType: "stx"
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (Object.keys(routes).length === 0) {
|
|
1523
|
+
console.error(`${colors.red}No files were successfully processed${colors.reset}`);
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
return true;
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
console.error(`${colors.red}Error processing files:${colors.reset}`, error);
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
const buildSuccess = await buildFiles();
|
|
1533
|
+
if (!buildSuccess) {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
let actualPort = port;
|
|
1537
|
+
try {
|
|
1538
|
+
actualPort = await findAvailablePort(port);
|
|
1539
|
+
if (actualPort !== port) {
|
|
1540
|
+
console.log(`${colors.yellow}Port ${port} is busy, using port ${actualPort} instead${colors.reset}`);
|
|
1541
|
+
}
|
|
1542
|
+
} catch {
|
|
1543
|
+
console.error(`${colors.red}Could not find an available port${colors.reset}`);
|
|
1544
|
+
return false;
|
|
1545
|
+
}
|
|
1546
|
+
console.log(`${colors.blue}Starting server on ${colors.cyan}http://localhost:${actualPort}/${colors.reset}...`);
|
|
1547
|
+
const server = serve({
|
|
1548
|
+
port: actualPort,
|
|
1549
|
+
fetch(request) {
|
|
1550
|
+
const url = new URL(request.url);
|
|
1551
|
+
let routeMatched = routes[url.pathname];
|
|
1552
|
+
if (!routeMatched && !url.pathname.endsWith("/")) {
|
|
1553
|
+
routeMatched = routes[`${url.pathname}/`];
|
|
1554
|
+
}
|
|
1555
|
+
if (!routeMatched && url.pathname !== "/" && routes["/"]) {
|
|
1556
|
+
routeMatched = routes["/"];
|
|
1557
|
+
}
|
|
1558
|
+
if (routeMatched) {
|
|
1559
|
+
return new Response(routeMatched.content, {
|
|
1560
|
+
headers: {
|
|
1561
|
+
"Content-Type": "text/html"
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
const requestedPath = path3.join(outputDir, url.pathname);
|
|
1566
|
+
if (fs2.existsSync(requestedPath) && fs2.statSync(requestedPath).isFile()) {
|
|
1567
|
+
const file = Bun.file(requestedPath);
|
|
1568
|
+
const ext = path3.extname(requestedPath).toLowerCase();
|
|
1569
|
+
let contentType = "text/plain";
|
|
1570
|
+
switch (ext) {
|
|
1571
|
+
case ".html":
|
|
1572
|
+
contentType = "text/html";
|
|
1573
|
+
break;
|
|
1574
|
+
case ".css":
|
|
1575
|
+
contentType = "text/css";
|
|
1576
|
+
break;
|
|
1577
|
+
case ".js":
|
|
1578
|
+
contentType = "text/javascript";
|
|
1579
|
+
break;
|
|
1580
|
+
case ".json":
|
|
1581
|
+
contentType = "application/json";
|
|
1582
|
+
break;
|
|
1583
|
+
case ".png":
|
|
1584
|
+
contentType = "image/png";
|
|
1585
|
+
break;
|
|
1586
|
+
case ".jpg":
|
|
1587
|
+
case ".jpeg":
|
|
1588
|
+
contentType = "image/jpeg";
|
|
1589
|
+
break;
|
|
1590
|
+
case ".gif":
|
|
1591
|
+
contentType = "image/gif";
|
|
1592
|
+
break;
|
|
1593
|
+
}
|
|
1594
|
+
return new Response(file, {
|
|
1595
|
+
headers: { "Content-Type": contentType }
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
return new Response("Not Found", { status: 404 });
|
|
1599
|
+
},
|
|
1600
|
+
error(error) {
|
|
1601
|
+
return new Response(`<pre>${error}
|
|
1602
|
+
${error.stack}</pre>`, {
|
|
1603
|
+
headers: {
|
|
1604
|
+
"Content-Type": "text/html"
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
console.clear();
|
|
1610
|
+
console.log(`
|
|
1611
|
+
${colors.blue}stx${colors.reset} ${colors.green}${process2.env.stx_VERSION || "v0.0.10"}${colors.reset} ${colors.dim}ready in ${Math.random() * 10 + 5 | 0}.${Math.random() * 90 + 10 | 0} ms${colors.reset}`);
|
|
1612
|
+
console.log(`
|
|
1613
|
+
${colors.bright}\u2192 ${colors.cyan}http://localhost:${port}/${colors.reset}`);
|
|
1614
|
+
console.log(`
|
|
1615
|
+
${colors.yellow}Routes:${colors.reset}`);
|
|
1616
|
+
const sortedRoutes = Object.entries(routes).sort(([pathA], [pathB]) => pathA.localeCompare(pathB)).map(([route, info]) => ({
|
|
1617
|
+
route: route === "/" ? "/" : route,
|
|
1618
|
+
filePath: path3.relative(process2.cwd(), info.filePath),
|
|
1619
|
+
fileType: info.fileType
|
|
1620
|
+
}));
|
|
1621
|
+
sortedRoutes.forEach((routeInfo, index) => {
|
|
1622
|
+
const isLast = index === sortedRoutes.length - 1;
|
|
1623
|
+
const prefix = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1624
|
+
const fileTypeLabel = routeInfo.fileType === "md" ? `${colors.magenta}(markdown)${colors.reset}` : "";
|
|
1625
|
+
if (routeInfo.route === "/") {
|
|
1626
|
+
console.log(` ${colors.green}${prefix}/${colors.reset} \u2192 ${colors.bright}${routeInfo.filePath}${colors.reset} ${fileTypeLabel}`);
|
|
1627
|
+
} else {
|
|
1628
|
+
const routeParts = routeInfo.route.split("/");
|
|
1629
|
+
const lastPart = routeParts[routeParts.length - 1] || routeParts[routeParts.length - 2];
|
|
1630
|
+
const displayRoute = routeInfo.route === "/" ? "/" : `/${lastPart}`;
|
|
1631
|
+
let parentPath = routeParts.slice(0, -1).join("/");
|
|
1632
|
+
if (parentPath && !parentPath.startsWith("/"))
|
|
1633
|
+
parentPath = `/${parentPath}`;
|
|
1634
|
+
console.log(` ${colors.green}${prefix}${displayRoute}${colors.reset} \u2192 ${colors.bright}${routeInfo.filePath}${colors.reset} ${fileTypeLabel}`);
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
console.log(`
|
|
1638
|
+
Press ${colors.cyan}h${colors.reset} + ${colors.cyan}Enter${colors.reset} to show shortcuts`);
|
|
1639
|
+
setupKeyboardShortcuts(server.url.toString(), () => {
|
|
1640
|
+
if (watch) {
|
|
1641
|
+
const watcher = fs2.watch(commonDir, { recursive: true });
|
|
1642
|
+
watcher.close();
|
|
1643
|
+
}
|
|
1644
|
+
server.stop();
|
|
1645
|
+
});
|
|
1646
|
+
if (watch) {
|
|
1647
|
+
console.log(`${colors.blue}Watching for changes...${colors.reset}`);
|
|
1648
|
+
const watcher = fs2.watch(commonDir, { recursive: true }, async (eventType, filename) => {
|
|
1649
|
+
if (!filename)
|
|
1650
|
+
return;
|
|
1651
|
+
if (filename.endsWith(".stx") || filename.endsWith(".js") || filename.endsWith(".ts") || filename.endsWith(".md")) {
|
|
1652
|
+
console.log(`${colors.yellow}File ${colors.bright}${filename}${colors.yellow} changed, rebuilding...${colors.reset}`);
|
|
1653
|
+
await buildFiles();
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
process2.on("SIGINT", () => {
|
|
1657
|
+
watcher.close();
|
|
1658
|
+
server.stop();
|
|
1659
|
+
process2.exit(0);
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
function findCommonDir(paths) {
|
|
1665
|
+
if (paths.length === 0)
|
|
1666
|
+
return "";
|
|
1667
|
+
if (paths.length === 1)
|
|
1668
|
+
return paths[0];
|
|
1669
|
+
const parts = paths.map((p) => p.split(path3.sep));
|
|
1670
|
+
const commonParts = [];
|
|
1671
|
+
for (let i = 0;i < parts[0].length; i++) {
|
|
1672
|
+
const part = parts[0][i];
|
|
1673
|
+
if (parts.every((p) => p[i] === part)) {
|
|
1674
|
+
commonParts.push(part);
|
|
1675
|
+
} else {
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return commonParts.join(path3.sep);
|
|
1680
|
+
}
|
|
1681
|
+
// src/docs.ts
|
|
1682
|
+
import fs3 from "fs";
|
|
1683
|
+
import path4 from "path";
|
|
1684
|
+
async function extractComponentProps(componentPath) {
|
|
1685
|
+
try {
|
|
1686
|
+
const content = await Bun.file(componentPath).text();
|
|
1687
|
+
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
1688
|
+
if (!scriptMatch)
|
|
1689
|
+
return [];
|
|
1690
|
+
const scriptContent = scriptMatch[1];
|
|
1691
|
+
const props = [];
|
|
1692
|
+
const propRegex = /\/\*\*\s*([\s\S]*?)\s*\*\/\s*(?:(?:const|let|var)\s*)?(?:([\w$]+)\s*=|module\.exports\.([\w$]+)\s*=|\s*([\w$]+)\s*:)/g;
|
|
1693
|
+
let match;
|
|
1694
|
+
while ((match = propRegex.exec(scriptContent)) !== null) {
|
|
1695
|
+
const commentBlock = match[1];
|
|
1696
|
+
const propName = match[2] || match[3] || match[4];
|
|
1697
|
+
if (!propName)
|
|
1698
|
+
continue;
|
|
1699
|
+
const propDoc = { name: propName };
|
|
1700
|
+
const typeMatch = commentBlock.match(/@type\s+\{([^}]+)\}/i);
|
|
1701
|
+
if (typeMatch)
|
|
1702
|
+
propDoc.type = typeMatch[1].trim();
|
|
1703
|
+
const requiredMatch = commentBlock.match(/@required/i);
|
|
1704
|
+
if (requiredMatch)
|
|
1705
|
+
propDoc.required = true;
|
|
1706
|
+
const defaultMatch = commentBlock.match(/@default\s+(.+?)(?:\s+|$)/i);
|
|
1707
|
+
if (defaultMatch)
|
|
1708
|
+
propDoc.default = defaultMatch[1].trim();
|
|
1709
|
+
const descLines = commentBlock.split(`
|
|
1710
|
+
`).map((line) => line.trim().replace(/^\*\s*/, "")).filter((line) => !line.startsWith("@") && line.length > 0);
|
|
1711
|
+
propDoc.description = descLines.join(" ").trim();
|
|
1712
|
+
props.push(propDoc);
|
|
1713
|
+
}
|
|
1714
|
+
if (props.length === 0) {
|
|
1715
|
+
const exportsMatch = scriptContent.match(/module\.exports\s*=\s*\{([^}]+)\}/i);
|
|
1716
|
+
if (exportsMatch) {
|
|
1717
|
+
const exportsObject = exportsMatch[1];
|
|
1718
|
+
const propLines = exportsObject.split(",").map((line) => line.trim()).filter(Boolean);
|
|
1719
|
+
for (const propLine of propLines) {
|
|
1720
|
+
const [propName] = propLine.split(":").map((part) => part.trim());
|
|
1721
|
+
if (propName) {
|
|
1722
|
+
props.push({ name: propName });
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return props;
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
console.error(`Error extracting props from ${componentPath}:`, error);
|
|
1730
|
+
return [];
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
async function extractComponentDescription(componentPath) {
|
|
1734
|
+
try {
|
|
1735
|
+
const content = await Bun.file(componentPath).text();
|
|
1736
|
+
const commentMatch = content.match(/^\s*<!--\s*([\s\S]*?)\s*-->/) || content.match(/^\s*\/\*\*\s*([\s\S]*?)\s*\*\//);
|
|
1737
|
+
if (commentMatch) {
|
|
1738
|
+
return commentMatch[1].split(`
|
|
1739
|
+
`).map((line) => line.trim().replace(/^\*\s*/, "")).join(" ").trim();
|
|
1740
|
+
}
|
|
1741
|
+
return "";
|
|
1742
|
+
} catch {
|
|
1743
|
+
return "";
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
async function generateComponentDoc(componentPath, isWebComponent = false, webComponentTag) {
|
|
1747
|
+
const name = path4.basename(componentPath, ".stx");
|
|
1748
|
+
const props = await extractComponentProps(componentPath);
|
|
1749
|
+
const description = await extractComponentDescription(componentPath);
|
|
1750
|
+
let example = "";
|
|
1751
|
+
if (isWebComponent && webComponentTag) {
|
|
1752
|
+
example = `<${webComponentTag}></${webComponentTag}>`;
|
|
1753
|
+
} else {
|
|
1754
|
+
const propsExample = props.map((prop) => {
|
|
1755
|
+
if (prop.type === "boolean")
|
|
1756
|
+
return `:${prop.name}="true"`;
|
|
1757
|
+
return `${prop.name}="value"`;
|
|
1758
|
+
}).join(`
|
|
1759
|
+
`);
|
|
1760
|
+
example = `<${name}
|
|
1761
|
+
${propsExample}
|
|
1762
|
+
/>`;
|
|
1763
|
+
}
|
|
1764
|
+
return {
|
|
1765
|
+
name,
|
|
1766
|
+
path: componentPath,
|
|
1767
|
+
description,
|
|
1768
|
+
props,
|
|
1769
|
+
example,
|
|
1770
|
+
isWebComponent,
|
|
1771
|
+
tag: webComponentTag
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
async function findComponentFiles(componentsDir) {
|
|
1775
|
+
try {
|
|
1776
|
+
if (!await fileExists(componentsDir)) {
|
|
1777
|
+
console.warn(`Components directory does not exist: ${componentsDir}`);
|
|
1778
|
+
return [];
|
|
1779
|
+
}
|
|
1780
|
+
const entries = await fs3.promises.readdir(componentsDir, { withFileTypes: true });
|
|
1781
|
+
const componentFiles = [];
|
|
1782
|
+
for (const entry of entries) {
|
|
1783
|
+
const entryPath = path4.join(componentsDir, entry.name);
|
|
1784
|
+
if (entry.isDirectory()) {
|
|
1785
|
+
const subComponents = await findComponentFiles(entryPath);
|
|
1786
|
+
componentFiles.push(...subComponents);
|
|
1787
|
+
} else if (entry.isFile() && entry.name.endsWith(".stx")) {
|
|
1788
|
+
componentFiles.push(entryPath);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return componentFiles;
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
console.error(`Error finding component files in ${componentsDir}:`, error);
|
|
1794
|
+
return [];
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
async function generateComponentsDocs(componentsDir, webComponentsConfig) {
|
|
1798
|
+
try {
|
|
1799
|
+
if (!await fileExists(componentsDir)) {
|
|
1800
|
+
return [];
|
|
1801
|
+
}
|
|
1802
|
+
const componentFiles = await findComponentFiles(componentsDir);
|
|
1803
|
+
const componentDocs = [];
|
|
1804
|
+
const webComponentsMap = new Map;
|
|
1805
|
+
if (webComponentsConfig?.components?.length) {
|
|
1806
|
+
for (const component of webComponentsConfig.components) {
|
|
1807
|
+
webComponentsMap.set(component.file, component);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
for (const componentFile of componentFiles) {
|
|
1811
|
+
const webComponent = webComponentsMap.get(componentFile);
|
|
1812
|
+
const isWebComponent = !!webComponent;
|
|
1813
|
+
const webComponentTag = webComponent?.tag;
|
|
1814
|
+
const doc = await generateComponentDoc(componentFile, isWebComponent, webComponentTag);
|
|
1815
|
+
componentDocs.push(doc);
|
|
1816
|
+
}
|
|
1817
|
+
return componentDocs;
|
|
1818
|
+
} catch (error) {
|
|
1819
|
+
console.error(`Error generating component docs:`, error);
|
|
1820
|
+
return [];
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async function generateTemplatesDocs(templatesDir) {
|
|
1824
|
+
try {
|
|
1825
|
+
if (!await fileExists(templatesDir)) {
|
|
1826
|
+
return [];
|
|
1827
|
+
}
|
|
1828
|
+
const entries = await fs3.promises.readdir(templatesDir, { withFileTypes: true });
|
|
1829
|
+
const templateDocs = [];
|
|
1830
|
+
for (const entry of entries) {
|
|
1831
|
+
if (!entry.isFile() || !entry.name.endsWith(".stx"))
|
|
1832
|
+
continue;
|
|
1833
|
+
const templatePath = path4.join(templatesDir, entry.name);
|
|
1834
|
+
const content = await Bun.file(templatePath).text();
|
|
1835
|
+
const name = path4.basename(templatePath, ".stx");
|
|
1836
|
+
const descriptionMatch = content.match(/^\s*<!--\s*([\s\S]*?)\s*-->/) || content.match(/^\s*\/\*\*\s*([\s\S]*?)\s*\*\//);
|
|
1837
|
+
const description = descriptionMatch ? descriptionMatch[1].split(`
|
|
1838
|
+
`).map((line) => line.trim().replace(/^\*\s*/, "")).join(" ").trim() : "";
|
|
1839
|
+
const componentRegex = /@component\(\s*['"]([^'"]+)['"]/g;
|
|
1840
|
+
const componentTags = /<([A-Z][a-zA-Z0-9]*|[a-z]+-[a-z0-9-]+)(?:\s[^>]*)?\/?>|\{\{\s*slot\s*\}\}/g;
|
|
1841
|
+
const components = new Set;
|
|
1842
|
+
let match;
|
|
1843
|
+
while ((match = componentRegex.exec(content)) !== null) {
|
|
1844
|
+
components.add(match[1]);
|
|
1845
|
+
}
|
|
1846
|
+
while ((match = componentTags.exec(content)) !== null) {
|
|
1847
|
+
if (match[1] && match[1] !== "slot") {
|
|
1848
|
+
components.add(match[1]);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
const directiveRegex = /@([a-z]+)(?:\s*\(|\s+|$)/g;
|
|
1852
|
+
const directives = new Set;
|
|
1853
|
+
while ((match = directiveRegex.exec(content)) !== null) {
|
|
1854
|
+
directives.add(match[1]);
|
|
1855
|
+
}
|
|
1856
|
+
templateDocs.push({
|
|
1857
|
+
name,
|
|
1858
|
+
path: templatePath,
|
|
1859
|
+
description,
|
|
1860
|
+
components: [...components],
|
|
1861
|
+
directives: [...directives]
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
return templateDocs;
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
console.error(`Error generating template docs:`, error);
|
|
1867
|
+
return [];
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
async function generateDirectivesDocs(customDirectives = []) {
|
|
1871
|
+
try {
|
|
1872
|
+
const directiveDocs = [
|
|
1873
|
+
{
|
|
1874
|
+
name: "if",
|
|
1875
|
+
description: "Conditionally render content based on a condition",
|
|
1876
|
+
hasEndTag: true,
|
|
1877
|
+
example: `@if(user.isLoggedIn)
|
|
1878
|
+
<p>Welcome, {{ user.name }}!</p>
|
|
1879
|
+
@endif`
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
name: "else",
|
|
1883
|
+
description: "Provides an alternative if a condition is not met",
|
|
1884
|
+
hasEndTag: false,
|
|
1885
|
+
example: `@if(user.isLoggedIn)
|
|
1886
|
+
<p>Welcome back!</p>
|
|
1887
|
+
@else
|
|
1888
|
+
<p>Please log in</p>
|
|
1889
|
+
@endif`
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
name: "elseif",
|
|
1893
|
+
description: "Provides an alternative condition",
|
|
1894
|
+
hasEndTag: false,
|
|
1895
|
+
example: `@if(score > 90)
|
|
1896
|
+
<p>A</p>
|
|
1897
|
+
@elseif(score > 80)
|
|
1898
|
+
<p>B</p>
|
|
1899
|
+
@elseif(score > 70)
|
|
1900
|
+
<p>C</p>
|
|
1901
|
+
@endif`
|
|
1902
|
+
},
|
|
1903
|
+
{
|
|
1904
|
+
name: "unless",
|
|
1905
|
+
description: "Conditionally render content if a condition is false",
|
|
1906
|
+
hasEndTag: true,
|
|
1907
|
+
example: `@unless(user.isLoggedIn)
|
|
1908
|
+
<p>Please log in</p>
|
|
1909
|
+
@endunless`
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
name: "for",
|
|
1913
|
+
description: "Loop through an array or object",
|
|
1914
|
+
hasEndTag: true,
|
|
1915
|
+
example: `@for(item of items)
|
|
1916
|
+
<li>{{ item.name }}</li>
|
|
1917
|
+
@endfor`
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
name: "while",
|
|
1921
|
+
description: "Loop while a condition is true",
|
|
1922
|
+
hasEndTag: true,
|
|
1923
|
+
example: `@while(page < totalPages)
|
|
1924
|
+
<p>Page {{ page }}</p>
|
|
1925
|
+
@endwhile`
|
|
1926
|
+
},
|
|
1927
|
+
{
|
|
1928
|
+
name: "component",
|
|
1929
|
+
description: "Include a component in the template",
|
|
1930
|
+
hasEndTag: false,
|
|
1931
|
+
example: '@component("alert", { type: "warning", title: "Warning", message: "This is a warning" })'
|
|
1932
|
+
},
|
|
1933
|
+
{
|
|
1934
|
+
name: "include",
|
|
1935
|
+
description: "Include a partial template",
|
|
1936
|
+
hasEndTag: false,
|
|
1937
|
+
example: '@include("partials/header")'
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
name: "raw",
|
|
1941
|
+
description: "Display content without processing expressions",
|
|
1942
|
+
hasEndTag: true,
|
|
1943
|
+
example: `@raw
|
|
1944
|
+
{{ This will be displayed as-is }}
|
|
1945
|
+
@endraw`
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
name: "translate",
|
|
1949
|
+
description: "Translate a string using the i18n system",
|
|
1950
|
+
hasEndTag: false,
|
|
1951
|
+
example: '@translate("welcome.message", { name: user.name })'
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
name: "t",
|
|
1955
|
+
description: "Short alias for translate directive",
|
|
1956
|
+
hasEndTag: false,
|
|
1957
|
+
example: '@t("welcome.message", { name: user.name })'
|
|
1958
|
+
}
|
|
1959
|
+
];
|
|
1960
|
+
for (const directive of customDirectives) {
|
|
1961
|
+
directiveDocs.push({
|
|
1962
|
+
name: directive.name,
|
|
1963
|
+
description: directive.description || "",
|
|
1964
|
+
hasEndTag: directive.hasEndTag || false,
|
|
1965
|
+
example: ""
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
return directiveDocs;
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
console.error(`Error generating directive docs:`, error);
|
|
1971
|
+
return [];
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
function formatDocsAsMarkdown(componentDocs = [], templateDocs = [], directiveDocs = [], extraContent) {
|
|
1975
|
+
let markdown = `# stx Documentation
|
|
1976
|
+
|
|
1977
|
+
`;
|
|
1978
|
+
if (extraContent) {
|
|
1979
|
+
markdown += `${extraContent}
|
|
1980
|
+
|
|
1981
|
+
`;
|
|
1982
|
+
}
|
|
1983
|
+
if (componentDocs.length > 0) {
|
|
1984
|
+
markdown += `## Components
|
|
1985
|
+
|
|
1986
|
+
`;
|
|
1987
|
+
for (const doc of componentDocs) {
|
|
1988
|
+
markdown += `### ${doc.name}
|
|
1989
|
+
|
|
1990
|
+
`;
|
|
1991
|
+
if (doc.description) {
|
|
1992
|
+
markdown += `${doc.description}
|
|
1993
|
+
|
|
1994
|
+
`;
|
|
1995
|
+
}
|
|
1996
|
+
if (doc.isWebComponent) {
|
|
1997
|
+
markdown += `**Web Component Tag:** \`${doc.tag}\`
|
|
1998
|
+
|
|
1999
|
+
`;
|
|
2000
|
+
}
|
|
2001
|
+
if (doc.props.length > 0) {
|
|
2002
|
+
markdown += `#### Properties
|
|
2003
|
+
|
|
2004
|
+
`;
|
|
2005
|
+
markdown += `| Name | Type | Required | Default | Description |
|
|
2006
|
+
`;
|
|
2007
|
+
markdown += `| ---- | ---- | -------- | ------- | ----------- |
|
|
2008
|
+
`;
|
|
2009
|
+
for (const prop of doc.props) {
|
|
2010
|
+
markdown += `| ${prop.name} | ${prop.type || "any"} | ${prop.required ? "Yes" : "No"} | ${prop.default || "-"} | ${prop.description || "-"} |
|
|
2011
|
+
`;
|
|
2012
|
+
}
|
|
2013
|
+
markdown += `
|
|
2014
|
+
`;
|
|
2015
|
+
}
|
|
2016
|
+
if (doc.example) {
|
|
2017
|
+
markdown += `#### Example
|
|
2018
|
+
|
|
2019
|
+
`;
|
|
2020
|
+
markdown += "```html\n";
|
|
2021
|
+
markdown += doc.example;
|
|
2022
|
+
markdown += "\n```\n\n";
|
|
82
2023
|
}
|
|
83
2024
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
2025
|
+
}
|
|
2026
|
+
if (templateDocs.length > 0) {
|
|
2027
|
+
markdown += `## Templates
|
|
2028
|
+
|
|
2029
|
+
`;
|
|
2030
|
+
for (const doc of templateDocs) {
|
|
2031
|
+
markdown += `### ${doc.name}
|
|
2032
|
+
|
|
2033
|
+
`;
|
|
2034
|
+
if (doc.description) {
|
|
2035
|
+
markdown += `${doc.description}
|
|
2036
|
+
|
|
2037
|
+
`;
|
|
2038
|
+
}
|
|
2039
|
+
if (doc.components && doc.components.length > 0) {
|
|
2040
|
+
markdown += `**Components Used:** ${doc.components.join(", ")}
|
|
2041
|
+
|
|
2042
|
+
`;
|
|
2043
|
+
}
|
|
2044
|
+
if (doc.directives && doc.directives.length > 0) {
|
|
2045
|
+
markdown += `**Directives Used:** ${doc.directives.join(", ")}
|
|
2046
|
+
|
|
2047
|
+
`;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (directiveDocs.length > 0) {
|
|
2052
|
+
markdown += `## Directives
|
|
2053
|
+
|
|
2054
|
+
`;
|
|
2055
|
+
markdown += `| Directive | Description | Has End Tag |
|
|
2056
|
+
`;
|
|
2057
|
+
markdown += `| --------- | ----------- | ----------- |
|
|
2058
|
+
`;
|
|
2059
|
+
for (const doc of directiveDocs) {
|
|
2060
|
+
markdown += `| @${doc.name} | ${doc.description || "-"} | ${doc.hasEndTag ? "Yes" : "No"} |
|
|
2061
|
+
`;
|
|
2062
|
+
}
|
|
2063
|
+
markdown += `
|
|
2064
|
+
`;
|
|
2065
|
+
for (const doc of directiveDocs) {
|
|
2066
|
+
if (doc.example) {
|
|
2067
|
+
markdown += `### @${doc.name}
|
|
2068
|
+
|
|
2069
|
+
`;
|
|
2070
|
+
markdown += `${doc.description || ""}
|
|
2071
|
+
|
|
2072
|
+
`;
|
|
2073
|
+
markdown += `#### Example
|
|
2074
|
+
|
|
2075
|
+
`;
|
|
2076
|
+
markdown += "```html\n";
|
|
2077
|
+
markdown += doc.example;
|
|
2078
|
+
markdown += "\n```\n\n";
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return markdown;
|
|
2083
|
+
}
|
|
2084
|
+
function formatDocsAsHtml(componentDocs = [], templateDocs = [], directiveDocs = [], extraContent) {
|
|
2085
|
+
let html = `<!DOCTYPE html>
|
|
2086
|
+
<html lang="en">
|
|
2087
|
+
<head>
|
|
2088
|
+
<meta charset="UTF-8">
|
|
2089
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2090
|
+
<title>stx Documentation</title>
|
|
2091
|
+
<style>
|
|
2092
|
+
body {
|
|
2093
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
2094
|
+
line-height: 1.6;
|
|
2095
|
+
color: #333;
|
|
2096
|
+
max-width: 1200px;
|
|
2097
|
+
margin: 0 auto;
|
|
2098
|
+
padding: 2rem;
|
|
2099
|
+
}
|
|
2100
|
+
h1, h2, h3, h4 { margin-top: 2rem; }
|
|
2101
|
+
table {
|
|
2102
|
+
border-collapse: collapse;
|
|
2103
|
+
width: 100%;
|
|
2104
|
+
margin: 1rem 0;
|
|
2105
|
+
}
|
|
2106
|
+
th, td {
|
|
2107
|
+
text-align: left;
|
|
2108
|
+
padding: 0.5rem;
|
|
2109
|
+
border-bottom: 1px solid #ddd;
|
|
2110
|
+
}
|
|
2111
|
+
th { border-bottom: 2px solid #ddd; }
|
|
2112
|
+
pre {
|
|
2113
|
+
background: #f5f5f5;
|
|
2114
|
+
padding: 1rem;
|
|
2115
|
+
border-radius: 4px;
|
|
2116
|
+
overflow-x: auto;
|
|
2117
|
+
}
|
|
2118
|
+
code {
|
|
2119
|
+
background: #f5f5f5;
|
|
2120
|
+
padding: 0.2rem 0.4rem;
|
|
2121
|
+
border-radius: 4px;
|
|
2122
|
+
font-size: 0.9em;
|
|
2123
|
+
}
|
|
2124
|
+
</style>
|
|
2125
|
+
</head>
|
|
2126
|
+
<body>
|
|
2127
|
+
<h1>stx Documentation</h1>
|
|
2128
|
+
`;
|
|
2129
|
+
if (extraContent) {
|
|
2130
|
+
html += `<div>${extraContent}</div>`;
|
|
2131
|
+
}
|
|
2132
|
+
if (componentDocs.length > 0) {
|
|
2133
|
+
html += `<h2>Components</h2>`;
|
|
2134
|
+
for (const doc of componentDocs) {
|
|
2135
|
+
html += `<h3>${doc.name}</h3>`;
|
|
2136
|
+
if (doc.description) {
|
|
2137
|
+
html += `<p>${doc.description}</p>`;
|
|
2138
|
+
}
|
|
2139
|
+
if (doc.isWebComponent) {
|
|
2140
|
+
html += `<p><strong>Web Component Tag:</strong> <code>${doc.tag}</code></p>`;
|
|
2141
|
+
}
|
|
2142
|
+
if (doc.props.length > 0) {
|
|
2143
|
+
html += `<h4>Properties</h4>`;
|
|
2144
|
+
html += `<table>
|
|
2145
|
+
<thead>
|
|
2146
|
+
<tr>
|
|
2147
|
+
<th>Name</th>
|
|
2148
|
+
<th>Type</th>
|
|
2149
|
+
<th>Required</th>
|
|
2150
|
+
<th>Default</th>
|
|
2151
|
+
<th>Description</th>
|
|
2152
|
+
</tr>
|
|
2153
|
+
</thead>
|
|
2154
|
+
<tbody>`;
|
|
2155
|
+
for (const prop of doc.props) {
|
|
2156
|
+
html += `<tr>
|
|
2157
|
+
<td>${prop.name}</td>
|
|
2158
|
+
<td>${prop.type || "any"}</td>
|
|
2159
|
+
<td>${prop.required ? "Yes" : "No"}</td>
|
|
2160
|
+
<td>${prop.default || "-"}</td>
|
|
2161
|
+
<td>${prop.description || "-"}</td>
|
|
2162
|
+
</tr>`;
|
|
98
2163
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
2164
|
+
html += `</tbody></table>`;
|
|
2165
|
+
}
|
|
2166
|
+
if (doc.example) {
|
|
2167
|
+
html += `<h4>Example</h4>`;
|
|
2168
|
+
html += `<pre><code>${doc.example.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'")}</code></pre>`;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
if (templateDocs.length > 0) {
|
|
2173
|
+
html += `<h2>Templates</h2>`;
|
|
2174
|
+
for (const doc of templateDocs) {
|
|
2175
|
+
html += `<h3>${doc.name}</h3>`;
|
|
2176
|
+
if (doc.description) {
|
|
2177
|
+
html += `<p>${doc.description}</p>`;
|
|
2178
|
+
}
|
|
2179
|
+
if (doc.components && doc.components.length > 0) {
|
|
2180
|
+
html += `<p><strong>Components Used:</strong> ${doc.components.join(", ")}</p>`;
|
|
2181
|
+
}
|
|
2182
|
+
if (doc.directives && doc.directives.length > 0) {
|
|
2183
|
+
html += `<p><strong>Directives Used:</strong> ${doc.directives.join(", ")}</p>`;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
if (directiveDocs.length > 0) {
|
|
2188
|
+
html += `<h2>Directives</h2>`;
|
|
2189
|
+
html += `<table>
|
|
2190
|
+
<thead>
|
|
2191
|
+
<tr>
|
|
2192
|
+
<th>Directive</th>
|
|
2193
|
+
<th>Description</th>
|
|
2194
|
+
<th>Has End Tag</th>
|
|
2195
|
+
</tr>
|
|
2196
|
+
</thead>
|
|
2197
|
+
<tbody>`;
|
|
2198
|
+
for (const doc of directiveDocs) {
|
|
2199
|
+
html += `<tr>
|
|
2200
|
+
<td>@${doc.name}</td>
|
|
2201
|
+
<td>${doc.description || "-"}</td>
|
|
2202
|
+
<td>${doc.hasEndTag ? "Yes" : "No"}</td>
|
|
2203
|
+
</tr>`;
|
|
2204
|
+
}
|
|
2205
|
+
html += `</tbody></table>`;
|
|
2206
|
+
for (const doc of directiveDocs) {
|
|
2207
|
+
if (doc.example) {
|
|
2208
|
+
html += `<h3>@${doc.name}</h3>`;
|
|
2209
|
+
html += `<p>${doc.description || ""}</p>`;
|
|
2210
|
+
html += `<h4>Example</h4>`;
|
|
2211
|
+
html += `<pre><code>${doc.example.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'")}</code></pre>`;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
html += `</body></html>`;
|
|
2216
|
+
return html;
|
|
2217
|
+
}
|
|
2218
|
+
function formatDocsAsJson(componentDocs = [], templateDocs = [], directiveDocs = [], extraContent) {
|
|
2219
|
+
const json = {
|
|
2220
|
+
components: componentDocs,
|
|
2221
|
+
templates: templateDocs,
|
|
2222
|
+
directives: directiveDocs,
|
|
2223
|
+
extraContent: extraContent || ""
|
|
2224
|
+
};
|
|
2225
|
+
return JSON.stringify(json, null, 2);
|
|
2226
|
+
}
|
|
2227
|
+
async function generateDocs(options) {
|
|
2228
|
+
try {
|
|
2229
|
+
const { componentsDir, templatesDir, webComponentsConfig, customDirectives, config: docConfig } = options;
|
|
2230
|
+
if (!docConfig.enabled) {
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
2233
|
+
const outputDir = docConfig.outputDir || "docs";
|
|
2234
|
+
await fs3.promises.mkdir(outputDir, { recursive: true });
|
|
2235
|
+
console.log(`Generating documentation...`);
|
|
2236
|
+
if (componentsDir)
|
|
2237
|
+
console.log(`Components directory: ${componentsDir}`);
|
|
2238
|
+
if (templatesDir)
|
|
2239
|
+
console.log(`Templates directory: ${templatesDir}`);
|
|
2240
|
+
console.log(`Output directory: ${outputDir}`);
|
|
2241
|
+
let componentDocs = [];
|
|
2242
|
+
if (docConfig.components !== false && componentsDir) {
|
|
2243
|
+
console.log(`Generating component documentation...`);
|
|
2244
|
+
componentDocs = await generateComponentsDocs(componentsDir, webComponentsConfig);
|
|
2245
|
+
console.log(`Found ${componentDocs.length} components`);
|
|
2246
|
+
}
|
|
2247
|
+
let templateDocs = [];
|
|
2248
|
+
if (docConfig.templates !== false && templatesDir) {
|
|
2249
|
+
console.log(`Generating template documentation...`);
|
|
2250
|
+
templateDocs = await generateTemplatesDocs(templatesDir);
|
|
2251
|
+
console.log(`Found ${templateDocs.length} templates`);
|
|
2252
|
+
}
|
|
2253
|
+
let directiveDocs = [];
|
|
2254
|
+
if (docConfig.directives !== false) {
|
|
2255
|
+
console.log(`Generating directive documentation...`);
|
|
2256
|
+
directiveDocs = await generateDirectivesDocs(customDirectives);
|
|
2257
|
+
console.log(`Found ${directiveDocs.length} directives`);
|
|
2258
|
+
}
|
|
2259
|
+
const format = docConfig.format || "markdown";
|
|
2260
|
+
const extraContent = docConfig.extraContent;
|
|
2261
|
+
let docContent = "";
|
|
2262
|
+
let extension = "";
|
|
2263
|
+
switch (format) {
|
|
2264
|
+
case "markdown":
|
|
2265
|
+
docContent = formatDocsAsMarkdown(componentDocs, templateDocs, directiveDocs, extraContent);
|
|
2266
|
+
extension = "md";
|
|
2267
|
+
break;
|
|
2268
|
+
case "html":
|
|
2269
|
+
docContent = formatDocsAsHtml(componentDocs, templateDocs, directiveDocs, extraContent);
|
|
2270
|
+
extension = "html";
|
|
2271
|
+
break;
|
|
2272
|
+
case "json":
|
|
2273
|
+
docContent = formatDocsAsJson(componentDocs, templateDocs, directiveDocs, extraContent);
|
|
2274
|
+
extension = "json";
|
|
2275
|
+
break;
|
|
2276
|
+
default:
|
|
2277
|
+
throw new Error(`Unsupported documentation format: ${format}`);
|
|
2278
|
+
}
|
|
2279
|
+
const outputPath = path4.join(outputDir, `stx-docs.${extension}`);
|
|
2280
|
+
await Bun.write(outputPath, docContent);
|
|
2281
|
+
console.log(`Documentation generated: ${outputPath}`);
|
|
2282
|
+
return true;
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
console.error("Error generating documentation:", error);
|
|
2285
|
+
return false;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
async function docsCommand(options) {
|
|
2289
|
+
const docsConfig = {
|
|
2290
|
+
enabled: true,
|
|
2291
|
+
outputDir: options.output || "docs",
|
|
2292
|
+
format: options.format || "markdown",
|
|
2293
|
+
components: options.components !== false,
|
|
2294
|
+
templates: options.templates !== false,
|
|
2295
|
+
directives: options.directives !== false,
|
|
2296
|
+
extraContent: options.extraContent
|
|
2297
|
+
};
|
|
2298
|
+
const componentsDir = options.componentsDir || config.componentsDir;
|
|
2299
|
+
const templatesDir = options.templatesDir || ".";
|
|
2300
|
+
return generateDocs({
|
|
2301
|
+
componentsDir,
|
|
2302
|
+
templatesDir,
|
|
2303
|
+
webComponentsConfig: config.webComponents,
|
|
2304
|
+
customDirectives: config.customDirectives,
|
|
2305
|
+
config: docsConfig
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
// src/formatter.ts
|
|
2309
|
+
var DEFAULT_OPTIONS = {
|
|
2310
|
+
indentSize: 2,
|
|
2311
|
+
useTabs: false,
|
|
2312
|
+
maxLineLength: 120,
|
|
2313
|
+
normalizeWhitespace: true,
|
|
2314
|
+
sortAttributes: false,
|
|
2315
|
+
trimTrailingWhitespace: true
|
|
2316
|
+
};
|
|
2317
|
+
function formatStxContent(content, options = {}) {
|
|
2318
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2319
|
+
if (content.trim() === "") {
|
|
2320
|
+
return `
|
|
2321
|
+
`;
|
|
2322
|
+
}
|
|
2323
|
+
let formatted = content;
|
|
2324
|
+
if (opts.trimTrailingWhitespace) {
|
|
2325
|
+
formatted = formatted.replace(/[ \t]+$/gm, "");
|
|
2326
|
+
}
|
|
2327
|
+
formatted = formatScriptTags(formatted, opts);
|
|
2328
|
+
formatted = formatHtml(formatted, opts);
|
|
2329
|
+
formatted = formatAttributes(formatted, opts);
|
|
2330
|
+
formatted = formatStxDirectives(formatted, opts);
|
|
2331
|
+
if (opts.trimTrailingWhitespace) {
|
|
2332
|
+
formatted = formatted.replace(/[ \t]+$/gm, "");
|
|
2333
|
+
formatted = formatted.replace(/(\S)[ \t]+(<\/[^>]+>)/g, "$1$2");
|
|
2334
|
+
}
|
|
2335
|
+
formatted = formatted.replace(/\r\n/g, `
|
|
2336
|
+
`).replace(/\r/g, `
|
|
2337
|
+
`);
|
|
2338
|
+
if (!formatted.endsWith(`
|
|
2339
|
+
`)) {
|
|
2340
|
+
formatted += `
|
|
2341
|
+
`;
|
|
2342
|
+
}
|
|
2343
|
+
return formatted;
|
|
2344
|
+
}
|
|
2345
|
+
function formatScriptTags(content, options) {
|
|
2346
|
+
return content.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, (match, scriptContent) => {
|
|
2347
|
+
const trimmed = scriptContent.trim();
|
|
2348
|
+
if (trimmed && !trimmed.includes(`
|
|
2349
|
+
`) && trimmed.length < 80) {
|
|
2350
|
+
return match.replace(scriptContent, trimmed);
|
|
2351
|
+
}
|
|
2352
|
+
const lines = scriptContent.split(`
|
|
2353
|
+
`);
|
|
2354
|
+
const formattedLines = lines.map((line, index) => {
|
|
2355
|
+
if (index === 0 && line.trim() === "")
|
|
2356
|
+
return "";
|
|
2357
|
+
if (index === lines.length - 1 && line.trim() === "")
|
|
2358
|
+
return "";
|
|
2359
|
+
const lineTrimmed = line.trim();
|
|
2360
|
+
if (lineTrimmed === "")
|
|
2361
|
+
return "";
|
|
2362
|
+
const indent = options.useTabs ? "\t" : " ".repeat(options.indentSize);
|
|
2363
|
+
return `${indent}${lineTrimmed}`;
|
|
2364
|
+
}).filter((line, index, arr) => {
|
|
2365
|
+
if (index === 0 || index === arr.length - 1)
|
|
2366
|
+
return line !== "";
|
|
2367
|
+
return true;
|
|
2368
|
+
});
|
|
2369
|
+
const formattedScript = formattedLines.length > 0 ? `
|
|
2370
|
+
${formattedLines.join(`
|
|
2371
|
+
`)}
|
|
2372
|
+
` : "";
|
|
2373
|
+
return match.replace(scriptContent, formattedScript);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
function formatHtml(content, options) {
|
|
2377
|
+
let preprocessed = content;
|
|
2378
|
+
const whitespaceSensitivePlaceholders = [];
|
|
2379
|
+
const whitespaceTags = ["pre", "code", "textarea", "style"];
|
|
2380
|
+
for (const tag of whitespaceTags) {
|
|
2381
|
+
const regex = new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi");
|
|
2382
|
+
preprocessed = preprocessed.replace(regex, (match) => {
|
|
2383
|
+
const placeholder = `__WHITESPACE_TAG_${whitespaceSensitivePlaceholders.length}__`;
|
|
2384
|
+
whitespaceSensitivePlaceholders.push(match);
|
|
2385
|
+
return placeholder;
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
const emptyTagPlaceholders = [];
|
|
2389
|
+
preprocessed = preprocessed.replace(/<(\w+)([^>]*)><\/\1>/g, (match) => {
|
|
2390
|
+
const placeholder = `__EMPTY_TAG_${emptyTagPlaceholders.length}__`;
|
|
2391
|
+
emptyTagPlaceholders.push(match);
|
|
2392
|
+
return placeholder;
|
|
2393
|
+
});
|
|
2394
|
+
preprocessed = preprocessed.replace(/<(\w+)([^>]*?)(@\w+\([^)]*\))([^>]*?)(@end\w+)([^>]*)>/g, (match, tagName, beforeDirective, openDirective, betweenDirectives, closeDirective, afterDirective) => {
|
|
2395
|
+
return `<${tagName}${beforeDirective}
|
|
2396
|
+
${openDirective}
|
|
2397
|
+
${betweenDirectives.trim()}
|
|
2398
|
+
${closeDirective}${afterDirective}>`;
|
|
2399
|
+
});
|
|
2400
|
+
preprocessed = preprocessed.replace(/>(\s*)</g, (match, whitespace, offset, string) => {
|
|
2401
|
+
if (whitespace.includes(`
|
|
2402
|
+
`)) {
|
|
2403
|
+
return match;
|
|
2404
|
+
}
|
|
2405
|
+
const before = string.substring(Math.max(0, offset - 50), offset + 1);
|
|
2406
|
+
const after = string.substring(offset + match.length - 1, Math.min(string.length, offset + match.length + 50));
|
|
2407
|
+
const closingTagMatch = before.match(/<\/(\w+)>$/);
|
|
2408
|
+
const openingTagMatch = after.match(/^<(\w+)/);
|
|
2409
|
+
const inlineTags = ["span", "a", "strong", "em", "b", "i", "u", "small", "mark", "del", "ins", "sub", "sup", "code", "kbd", "samp", "var", "abbr", "cite", "q", "dfn", "time"];
|
|
2410
|
+
if (closingTagMatch && openingTagMatch) {
|
|
2411
|
+
const closingTag = closingTagMatch[1].toLowerCase();
|
|
2412
|
+
const openingTag = openingTagMatch[1].toLowerCase();
|
|
2413
|
+
if (inlineTags.includes(closingTag) && inlineTags.includes(openingTag)) {
|
|
2414
|
+
const beforeMatch = string.substring(0, offset);
|
|
2415
|
+
const lastNewline = beforeMatch.lastIndexOf(`
|
|
2416
|
+
`);
|
|
2417
|
+
const lineStart = lastNewline + 1;
|
|
2418
|
+
const afterMatch = string.substring(offset + match.length);
|
|
2419
|
+
const nextNewline = afterMatch.indexOf(`
|
|
2420
|
+
`);
|
|
2421
|
+
const lineEnd = nextNewline === -1 ? string.length : offset + match.length + nextNewline;
|
|
2422
|
+
const currentLine = string.substring(lineStart, lineEnd);
|
|
2423
|
+
const withoutTags = currentLine.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
2424
|
+
if (withoutTags.length > 0) {
|
|
2425
|
+
return match;
|
|
120
2426
|
}
|
|
121
|
-
return {
|
|
122
|
-
contents: output,
|
|
123
|
-
loader: "html"
|
|
124
|
-
};
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error("STX Plugin Error:", error);
|
|
127
|
-
return {
|
|
128
|
-
contents: `<!DOCTYPE html><html><body><h1>STX Rendering Error</h1><pre>${error.message || String(error)}</pre></body></html>`,
|
|
129
|
-
loader: "html"
|
|
130
|
-
};
|
|
131
2427
|
}
|
|
2428
|
+
}
|
|
2429
|
+
return `>
|
|
2430
|
+
<`;
|
|
2431
|
+
});
|
|
2432
|
+
preprocessed = preprocessed.replace(/>(\s*)@/g, (match, whitespace) => {
|
|
2433
|
+
if (whitespace.includes(`
|
|
2434
|
+
`)) {
|
|
2435
|
+
return match;
|
|
2436
|
+
}
|
|
2437
|
+
return `>
|
|
2438
|
+
@`;
|
|
2439
|
+
});
|
|
2440
|
+
preprocessed = preprocessed.replace(/(@end\w+)(\s*)</g, (match, directive, whitespace) => {
|
|
2441
|
+
if (whitespace.includes(`
|
|
2442
|
+
`)) {
|
|
2443
|
+
return match;
|
|
2444
|
+
}
|
|
2445
|
+
return `${directive}
|
|
2446
|
+
<`;
|
|
2447
|
+
});
|
|
2448
|
+
preprocessed = preprocessed.replace(/\)(\s*)@/g, (match, whitespace) => {
|
|
2449
|
+
if (whitespace.includes(`
|
|
2450
|
+
`)) {
|
|
2451
|
+
return match;
|
|
2452
|
+
}
|
|
2453
|
+
return `)
|
|
2454
|
+
@`;
|
|
2455
|
+
});
|
|
2456
|
+
preprocessed = preprocessed.replace(/\)(\s*)</g, (match, whitespace) => {
|
|
2457
|
+
if (whitespace.includes(`
|
|
2458
|
+
`)) {
|
|
2459
|
+
return match;
|
|
2460
|
+
}
|
|
2461
|
+
return `)
|
|
2462
|
+
<`;
|
|
2463
|
+
});
|
|
2464
|
+
preprocessed = preprocessed.replace(/(@end\w+)(\s*)@/g, (match, directive, whitespace) => {
|
|
2465
|
+
if (whitespace.includes(`
|
|
2466
|
+
`)) {
|
|
2467
|
+
return match;
|
|
2468
|
+
}
|
|
2469
|
+
return `${directive}
|
|
2470
|
+
@`;
|
|
2471
|
+
});
|
|
2472
|
+
preprocessed = preprocessed.replace(/(@\w+)(?!\s*\()(\s*)</g, (match, directive, whitespace) => {
|
|
2473
|
+
if (whitespace.includes(`
|
|
2474
|
+
`)) {
|
|
2475
|
+
return match;
|
|
2476
|
+
}
|
|
2477
|
+
return `${directive}
|
|
2478
|
+
<`;
|
|
2479
|
+
});
|
|
2480
|
+
const lines = preprocessed.split(`
|
|
2481
|
+
`);
|
|
2482
|
+
const formattedLines = [];
|
|
2483
|
+
let indentLevel = 0;
|
|
2484
|
+
const indent = options.useTabs ? "\t" : " ".repeat(options.indentSize);
|
|
2485
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2486
|
+
const line = lines[i].trim();
|
|
2487
|
+
if (line === "") {
|
|
2488
|
+
formattedLines.push("");
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
if (line.startsWith("</") || line.startsWith("@end")) {
|
|
2492
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
2493
|
+
}
|
|
2494
|
+
const indentedLine = indentLevel > 0 ? indent.repeat(indentLevel) + line : line;
|
|
2495
|
+
formattedLines.push(indentedLine);
|
|
2496
|
+
if (line.startsWith("<") && !line.includes("/>") && !line.startsWith("</")) {
|
|
2497
|
+
const hasMatchingClosingTag = line.match(/<(\w+)[^>]*>.*<\/\1>/);
|
|
2498
|
+
if (!line.includes("<!") && !isSelfClosingTag(line) && !hasMatchingClosingTag) {
|
|
2499
|
+
indentLevel++;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
if (line.startsWith("@") && isOpeningDirective(line)) {
|
|
2503
|
+
indentLevel++;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
let result = formattedLines.join(`
|
|
2507
|
+
`);
|
|
2508
|
+
whitespaceSensitivePlaceholders.forEach((tag, index) => {
|
|
2509
|
+
result = result.replace(`__WHITESPACE_TAG_${index}__`, tag);
|
|
2510
|
+
});
|
|
2511
|
+
emptyTagPlaceholders.forEach((tag, index) => {
|
|
2512
|
+
result = result.replace(`__EMPTY_TAG_${index}__`, tag);
|
|
2513
|
+
});
|
|
2514
|
+
return result;
|
|
2515
|
+
}
|
|
2516
|
+
function formatStxDirectives(content, options) {
|
|
2517
|
+
content = content.replace(/@(if|elseif|foreach|for|while)\s*\(\s*([^)]+)\s*\)/g, (match, directive, condition) => {
|
|
2518
|
+
const normalizedCondition = condition.trim().replace(/\s+/g, " ");
|
|
2519
|
+
return `@${directive}(${normalizedCondition})`;
|
|
2520
|
+
});
|
|
2521
|
+
content = content.replace(/@(csrf|method)\s*\(\s*([^)]*)\s*\)/g, (match, directive, param) => {
|
|
2522
|
+
if (param.trim() === "") {
|
|
2523
|
+
return `@${directive}`;
|
|
2524
|
+
}
|
|
2525
|
+
return `@${directive}(${param.trim()})`;
|
|
2526
|
+
});
|
|
2527
|
+
if (options.normalizeWhitespace) {
|
|
2528
|
+
content = content.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
|
|
2529
|
+
return `{{ ${expression.trim()} }}`;
|
|
2530
|
+
});
|
|
2531
|
+
content = content.replace(/\{!!\s*([^!]+)\s*!!\}/g, (match, expression) => {
|
|
2532
|
+
return `{!! ${expression.trim()} !!}`;
|
|
132
2533
|
});
|
|
133
2534
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
2535
|
+
return content;
|
|
2536
|
+
}
|
|
2537
|
+
function isSelfClosingTag(line) {
|
|
2538
|
+
const selfClosingTags = ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"];
|
|
2539
|
+
if (line.includes("/>"))
|
|
2540
|
+
return true;
|
|
2541
|
+
const tagMatch = line.match(/<(\w+)/);
|
|
2542
|
+
if (tagMatch) {
|
|
2543
|
+
const tagName = tagMatch[1].toLowerCase();
|
|
2544
|
+
return selfClosingTags.includes(tagName);
|
|
2545
|
+
}
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
function isOpeningDirective(line) {
|
|
2549
|
+
const blockDirectives = ["if", "unless", "foreach", "for", "while", "section", "push", "component", "slot", "markdown", "wrap", "error"];
|
|
2550
|
+
for (const directive of blockDirectives) {
|
|
2551
|
+
if (line.startsWith(`@${directive}`)) {
|
|
2552
|
+
return !line.includes(`@end${directive}`);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
function formatAttributes(content, options) {
|
|
2558
|
+
if (!options.sortAttributes)
|
|
2559
|
+
return content;
|
|
2560
|
+
return content.replace(/<(\w+)([^>]*)>/g, (match, tagName, attributes) => {
|
|
2561
|
+
if (!attributes.trim())
|
|
2562
|
+
return match;
|
|
2563
|
+
if (attributes.includes("<") || attributes.includes(`
|
|
2564
|
+
`) || attributes.includes("@")) {
|
|
2565
|
+
return match;
|
|
2566
|
+
}
|
|
2567
|
+
const attrRegex = /(\w+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g;
|
|
2568
|
+
const attrs = [];
|
|
2569
|
+
let attrMatch;
|
|
2570
|
+
while ((attrMatch = attrRegex.exec(attributes)) !== null) {
|
|
2571
|
+
attrs.push({
|
|
2572
|
+
name: attrMatch[1],
|
|
2573
|
+
value: attrMatch[2]
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
attrs.sort((a, b) => {
|
|
2577
|
+
if (a.name === "id")
|
|
2578
|
+
return -1;
|
|
2579
|
+
if (b.name === "id")
|
|
2580
|
+
return 1;
|
|
2581
|
+
if (a.name === "class")
|
|
2582
|
+
return -1;
|
|
2583
|
+
if (b.name === "class")
|
|
2584
|
+
return 1;
|
|
2585
|
+
return a.name.localeCompare(b.name);
|
|
2586
|
+
});
|
|
2587
|
+
const formattedAttrs = attrs.map((attr) => attr.value ? `${attr.name}=${attr.value}` : attr.name).join(" ");
|
|
2588
|
+
return `<${tagName}${formattedAttrs ? ` ${formattedAttrs}` : ""}>`;
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
// src/init.ts
|
|
2592
|
+
import fs4 from "fs";
|
|
2593
|
+
import path5 from "path";
|
|
2594
|
+
import process3 from "process";
|
|
2595
|
+
async function initFile(fileName = "index.stx", options = {}) {
|
|
140
2596
|
try {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const metaContent = await Bun.file(metaFile).text();
|
|
147
|
-
const meta = JSON.parse(metaContent);
|
|
148
|
-
if (meta.cacheVersion !== options.cacheVersion)
|
|
149
|
-
return null;
|
|
150
|
-
const stats = await fs.promises.stat(filePath);
|
|
151
|
-
if (stats.mtime.getTime() > meta.mtime)
|
|
152
|
-
return null;
|
|
153
|
-
for (const dep of meta.dependencies) {
|
|
154
|
-
if (await fileExists(dep)) {
|
|
155
|
-
const depStats = await fs.promises.stat(dep);
|
|
156
|
-
if (depStats.mtime.getTime() > meta.mtime)
|
|
157
|
-
return null;
|
|
158
|
-
} else {
|
|
159
|
-
return null;
|
|
2597
|
+
const force = options.force || false;
|
|
2598
|
+
const filePath = path5.resolve(process3.cwd(), fileName);
|
|
2599
|
+
if (fs4.existsSync(filePath)) {
|
|
2600
|
+
if (!force) {
|
|
2601
|
+
throw new Error(`File ${fileName} already exists. Use --force to overwrite.`);
|
|
160
2602
|
}
|
|
2603
|
+
console.warn(`File ${fileName} already exists. Overwriting...`);
|
|
161
2604
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
2605
|
+
const dirPath = path5.dirname(filePath);
|
|
2606
|
+
if (!fs4.existsSync(dirPath)) {
|
|
2607
|
+
fs4.mkdirSync(dirPath, { recursive: true });
|
|
2608
|
+
}
|
|
2609
|
+
let templateContent;
|
|
2610
|
+
if (options.template) {
|
|
2611
|
+
const templatePath = path5.resolve(process3.cwd(), options.template);
|
|
2612
|
+
if (!fs4.existsSync(templatePath)) {
|
|
2613
|
+
throw new Error(`Template file ${options.template} does not exist.`);
|
|
2614
|
+
}
|
|
2615
|
+
if (!templatePath.endsWith(".stx")) {
|
|
2616
|
+
console.warn(`Warning: Template file ${options.template} does not have a .stx extension. Using it anyway.`);
|
|
2617
|
+
}
|
|
2618
|
+
templateContent = fs4.readFileSync(templatePath, "utf-8");
|
|
2619
|
+
console.warn(`Using template from ${options.template}`);
|
|
2620
|
+
} else {
|
|
2621
|
+
templateContent = getDefaultTemplate();
|
|
2622
|
+
}
|
|
2623
|
+
fs4.writeFileSync(filePath, templateContent);
|
|
2624
|
+
console.warn(`Created new stx file: ${fileName}`);
|
|
2625
|
+
return true;
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
console.error(`Error creating file: ${error instanceof Error ? error.message : String(error)}`);
|
|
2628
|
+
return false;
|
|
166
2629
|
}
|
|
167
2630
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
2631
|
+
function getDefaultTemplate() {
|
|
2632
|
+
return `<!DOCTYPE html>
|
|
2633
|
+
<html lang="en">
|
|
2634
|
+
<head>
|
|
2635
|
+
<meta charset="UTF-8">
|
|
2636
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2637
|
+
<title>{{ title }}</title>
|
|
2638
|
+
<script>
|
|
2639
|
+
export const title = "My stx Page";
|
|
2640
|
+
export const description = "A page built with stx";
|
|
2641
|
+
export const items = [
|
|
2642
|
+
"Templates with TypeScript support",
|
|
2643
|
+
"Powerful directives",
|
|
2644
|
+
"Reusable components"
|
|
2645
|
+
];
|
|
2646
|
+
</script>
|
|
2647
|
+
<style>
|
|
2648
|
+
:root {
|
|
2649
|
+
--primary-color: #3498db;
|
|
2650
|
+
--dark-color: #34495e;
|
|
2651
|
+
--light-color: #ecf0f1;
|
|
2652
|
+
--font-main: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
body {
|
|
2656
|
+
font-family: var(--font-main);
|
|
2657
|
+
line-height: 1.6;
|
|
2658
|
+
color: var(--dark-color);
|
|
2659
|
+
max-width: 800px;
|
|
2660
|
+
margin: 0 auto;
|
|
2661
|
+
padding: 2rem;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
h1 {
|
|
2665
|
+
color: var(--primary-color);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
.content {
|
|
2669
|
+
margin-top: 2rem;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
ul {
|
|
2673
|
+
padding-left: 1.5rem;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
li {
|
|
2677
|
+
margin-bottom: 0.5rem;
|
|
2678
|
+
}
|
|
2679
|
+
</style>
|
|
2680
|
+
</head>
|
|
2681
|
+
<body>
|
|
2682
|
+
<header>
|
|
2683
|
+
<h1>{{ title }}</h1>
|
|
2684
|
+
<p>{{ description }}</p>
|
|
2685
|
+
</header>
|
|
2686
|
+
|
|
2687
|
+
<div class="content">
|
|
2688
|
+
<h2>Features</h2>
|
|
2689
|
+
<ul>
|
|
2690
|
+
@foreach(items as item)
|
|
2691
|
+
<li>{{ item }}</li>
|
|
2692
|
+
@endforeach
|
|
2693
|
+
</ul>
|
|
2694
|
+
</div>
|
|
2695
|
+
</body>
|
|
2696
|
+
</html>`;
|
|
2697
|
+
}
|
|
2698
|
+
// src/release.ts
|
|
2699
|
+
var gitHash = "f9c3713";
|
|
2700
|
+
// src/serve.ts
|
|
2701
|
+
var {serve: bunServe } = globalThis.Bun;
|
|
2702
|
+
import fs5 from "fs";
|
|
2703
|
+
import path6 from "path";
|
|
2704
|
+
async function serve2(options = {}) {
|
|
2705
|
+
const {
|
|
2706
|
+
port = 3000,
|
|
2707
|
+
root = ".",
|
|
2708
|
+
stxOptions = {},
|
|
2709
|
+
watch = true,
|
|
2710
|
+
onRequest,
|
|
2711
|
+
routes = {},
|
|
2712
|
+
middleware = [],
|
|
2713
|
+
on404,
|
|
2714
|
+
onError
|
|
2715
|
+
} = options;
|
|
2716
|
+
const rootDir = path6.resolve(root);
|
|
2717
|
+
const fileCache = new Map;
|
|
2718
|
+
async function processStxFile(filePath) {
|
|
2719
|
+
const stats = fs5.statSync(filePath);
|
|
2720
|
+
const cacheKey = `${filePath}:${stats.mtimeMs}`;
|
|
2721
|
+
const cached = fileCache.get(cacheKey);
|
|
2722
|
+
if (cached) {
|
|
2723
|
+
return cached.content;
|
|
2724
|
+
}
|
|
2725
|
+
const content = await Bun.file(filePath).text();
|
|
2726
|
+
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
2727
|
+
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
2728
|
+
const templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
|
|
2729
|
+
const context = {
|
|
2730
|
+
__filename: filePath,
|
|
2731
|
+
__dirname: path6.dirname(filePath)
|
|
182
2732
|
};
|
|
183
|
-
await
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
2733
|
+
await extractVariables(scriptContent, context, filePath);
|
|
2734
|
+
const dependencies = new Set;
|
|
2735
|
+
const output = await processDirectives(templateContent, context, filePath, stxOptions, dependencies);
|
|
2736
|
+
fileCache.set(cacheKey, { content: output, mtime: stats.mtimeMs });
|
|
2737
|
+
return output;
|
|
2738
|
+
}
|
|
2739
|
+
async function processMarkdownFile(filePath) {
|
|
2740
|
+
const stats = fs5.statSync(filePath);
|
|
2741
|
+
const cacheKey = `${filePath}:${stats.mtimeMs}`;
|
|
2742
|
+
const cached = fileCache.get(cacheKey);
|
|
2743
|
+
if (cached) {
|
|
2744
|
+
return cached.content;
|
|
2745
|
+
}
|
|
2746
|
+
const { content } = await readMarkdownFile(filePath, stxOptions);
|
|
2747
|
+
fileCache.set(cacheKey, { content, mtime: stats.mtimeMs });
|
|
2748
|
+
return content;
|
|
2749
|
+
}
|
|
2750
|
+
function resolveRequestPath(pathname) {
|
|
2751
|
+
const relPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
2752
|
+
const possiblePaths = [
|
|
2753
|
+
relPath,
|
|
2754
|
+
`${relPath}.stx`,
|
|
2755
|
+
`${relPath}.md`,
|
|
2756
|
+
`${relPath}.html`,
|
|
2757
|
+
path6.join(relPath, "index.stx"),
|
|
2758
|
+
path6.join(relPath, "index.md"),
|
|
2759
|
+
path6.join(relPath, "index.html")
|
|
2760
|
+
];
|
|
2761
|
+
for (const possiblePath of possiblePaths) {
|
|
2762
|
+
const fullPath = path6.join(rootDir, possiblePath);
|
|
2763
|
+
if (fs5.existsSync(fullPath) && fs5.statSync(fullPath).isFile()) {
|
|
2764
|
+
return fullPath;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return null;
|
|
2768
|
+
}
|
|
2769
|
+
async function handleRequest(request) {
|
|
2770
|
+
const url = new URL(request.url);
|
|
2771
|
+
let next = async () => {
|
|
2772
|
+
if (routes[url.pathname]) {
|
|
2773
|
+
return await routes[url.pathname](request);
|
|
2774
|
+
}
|
|
2775
|
+
if (onRequest) {
|
|
2776
|
+
const customResponse = await onRequest(request);
|
|
2777
|
+
if (customResponse) {
|
|
2778
|
+
return customResponse;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
const filePath = resolveRequestPath(url.pathname);
|
|
2782
|
+
if (!filePath) {
|
|
2783
|
+
if (on404) {
|
|
2784
|
+
return await on404(request);
|
|
2785
|
+
}
|
|
2786
|
+
return new Response("Not Found", { status: 404 });
|
|
2787
|
+
}
|
|
2788
|
+
try {
|
|
2789
|
+
let content;
|
|
2790
|
+
let contentType = "text/html";
|
|
2791
|
+
if (filePath.endsWith(".stx")) {
|
|
2792
|
+
content = await processStxFile(filePath);
|
|
2793
|
+
} else if (filePath.endsWith(".md")) {
|
|
2794
|
+
content = await processMarkdownFile(filePath);
|
|
2795
|
+
} else if (filePath.endsWith(".html")) {
|
|
2796
|
+
content = await Bun.file(filePath).text();
|
|
2797
|
+
} else if (filePath.endsWith(".css")) {
|
|
2798
|
+
content = await Bun.file(filePath).text();
|
|
2799
|
+
contentType = "text/css";
|
|
2800
|
+
} else if (filePath.endsWith(".js")) {
|
|
2801
|
+
content = await Bun.file(filePath).text();
|
|
2802
|
+
contentType = "text/javascript";
|
|
2803
|
+
} else if (filePath.endsWith(".json")) {
|
|
2804
|
+
content = await Bun.file(filePath).text();
|
|
2805
|
+
contentType = "application/json";
|
|
2806
|
+
} else {
|
|
2807
|
+
return new Response(Bun.file(filePath));
|
|
2808
|
+
}
|
|
2809
|
+
return new Response(content, {
|
|
2810
|
+
headers: { "Content-Type": contentType }
|
|
2811
|
+
});
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
if (onError) {
|
|
2814
|
+
return await onError(error, request);
|
|
2815
|
+
}
|
|
2816
|
+
return new Response(`<h1>Error</h1><pre>${error.message}
|
|
2817
|
+
${error.stack}</pre>`, {
|
|
2818
|
+
status: 500,
|
|
2819
|
+
headers: { "Content-Type": "text/html" }
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
for (let i = middleware.length - 1;i >= 0; i--) {
|
|
2824
|
+
const mw = middleware[i];
|
|
2825
|
+
const currentNext = next;
|
|
2826
|
+
next = async () => await mw(request, currentNext);
|
|
2827
|
+
}
|
|
2828
|
+
return await next();
|
|
2829
|
+
}
|
|
2830
|
+
const server = bunServe({
|
|
2831
|
+
port,
|
|
2832
|
+
fetch: handleRequest
|
|
2833
|
+
});
|
|
2834
|
+
let watcher = null;
|
|
2835
|
+
if (watch) {
|
|
2836
|
+
watcher = fs5.watch(rootDir, { recursive: true }, (eventType, filename) => {
|
|
2837
|
+
if (filename && (filename.endsWith(".stx") || filename.endsWith(".md") || filename.endsWith(".html"))) {
|
|
2838
|
+
fileCache.clear();
|
|
2839
|
+
}
|
|
188
2840
|
});
|
|
189
|
-
} catch (err) {
|
|
190
|
-
console.warn(`Failed to cache template ${filePath}:`, err);
|
|
191
2841
|
}
|
|
2842
|
+
return {
|
|
2843
|
+
server,
|
|
2844
|
+
url: server.url.toString(),
|
|
2845
|
+
stop() {
|
|
2846
|
+
if (watcher) {
|
|
2847
|
+
watcher.close();
|
|
2848
|
+
}
|
|
2849
|
+
server.stop();
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
async function serveFile(filePath, options = {}) {
|
|
2854
|
+
const absolutePath = path6.resolve(filePath);
|
|
2855
|
+
if (!fs5.existsSync(absolutePath)) {
|
|
2856
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
2857
|
+
}
|
|
2858
|
+
const isMarkdown = absolutePath.endsWith(".md");
|
|
2859
|
+
const isStx = absolutePath.endsWith(".stx");
|
|
2860
|
+
if (!isMarkdown && !isStx) {
|
|
2861
|
+
throw new Error(`Unsupported file type: ${absolutePath}. Only .stx and .md files are supported.`);
|
|
2862
|
+
}
|
|
2863
|
+
return await serve2({
|
|
2864
|
+
...options,
|
|
2865
|
+
root: path6.dirname(absolutePath),
|
|
2866
|
+
routes: {
|
|
2867
|
+
"/": async () => {
|
|
2868
|
+
let content;
|
|
2869
|
+
if (isMarkdown) {
|
|
2870
|
+
const { content: md } = await readMarkdownFile(absolutePath, options.stxOptions);
|
|
2871
|
+
content = md;
|
|
2872
|
+
} else {
|
|
2873
|
+
const fileContent = await Bun.file(absolutePath).text();
|
|
2874
|
+
const scriptMatch = fileContent.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
2875
|
+
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
2876
|
+
const templateContent = fileContent.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
|
|
2877
|
+
const context = {};
|
|
2878
|
+
await extractVariables(scriptContent, context, absolutePath);
|
|
2879
|
+
const dependencies = new Set;
|
|
2880
|
+
content = await processDirectives(templateContent, context, absolutePath, options.stxOptions || {}, dependencies);
|
|
2881
|
+
}
|
|
2882
|
+
return new Response(content, {
|
|
2883
|
+
headers: { "Content-Type": "text/html" }
|
|
2884
|
+
});
|
|
2885
|
+
},
|
|
2886
|
+
...options.routes
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
192
2889
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
2890
|
+
function createMiddleware(handler) {
|
|
2891
|
+
return handler;
|
|
2892
|
+
}
|
|
2893
|
+
function createRoute(handler) {
|
|
2894
|
+
return handler;
|
|
196
2895
|
}
|
|
197
2896
|
// src/streaming.ts
|
|
198
|
-
import
|
|
2897
|
+
import path7 from "path";
|
|
199
2898
|
var defaultStreamingConfig = {
|
|
200
2899
|
enabled: true,
|
|
201
2900
|
bufferSize: 1024 * 16,
|
|
202
2901
|
strategy: "auto",
|
|
203
2902
|
timeout: 30000
|
|
204
2903
|
};
|
|
205
|
-
var SECTION_PATTERN = /<!-- @section:([
|
|
2904
|
+
var SECTION_PATTERN = /<!-- @section:([\w-]+) -->([\s\S]*?)<!-- @endsection:\1 -->/g;
|
|
206
2905
|
async function streamTemplate(templatePath, data = {}, options = {}) {
|
|
207
2906
|
const fullOptions = {
|
|
208
2907
|
...defaultConfig,
|
|
@@ -222,7 +2921,7 @@ async function streamTemplate(templatePath, data = {}, options = {}) {
|
|
|
222
2921
|
const context = {
|
|
223
2922
|
...data,
|
|
224
2923
|
__filename: templatePath,
|
|
225
|
-
__dirname:
|
|
2924
|
+
__dirname: path7.dirname(templatePath)
|
|
226
2925
|
};
|
|
227
2926
|
await extractVariables(scriptContent, context, templatePath);
|
|
228
2927
|
const dependencies = new Set;
|
|
@@ -244,7 +2943,7 @@ async function createStreamRenderer(templatePath, options = {}) {
|
|
|
244
2943
|
...options.streaming
|
|
245
2944
|
}
|
|
246
2945
|
};
|
|
247
|
-
|
|
2946
|
+
const content = await Bun.file(templatePath).text();
|
|
248
2947
|
const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
|
|
249
2948
|
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
250
2949
|
const templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
|
|
@@ -257,13 +2956,13 @@ async function createStreamRenderer(templatePath, options = {}) {
|
|
|
257
2956
|
const sectionContent = match[2];
|
|
258
2957
|
sections[sectionName] = sectionContent;
|
|
259
2958
|
}
|
|
260
|
-
|
|
2959
|
+
const shellTemplate = templateContent.replace(SECTION_PATTERN, "");
|
|
261
2960
|
const renderer = {
|
|
262
2961
|
renderShell: async (data = {}) => {
|
|
263
2962
|
const context = {
|
|
264
2963
|
...data,
|
|
265
2964
|
__filename: templatePath,
|
|
266
|
-
__dirname:
|
|
2965
|
+
__dirname: path7.dirname(templatePath)
|
|
267
2966
|
};
|
|
268
2967
|
await extractVariables(scriptContent, context, templatePath);
|
|
269
2968
|
const dependencies = new Set;
|
|
@@ -277,7 +2976,7 @@ async function createStreamRenderer(templatePath, options = {}) {
|
|
|
277
2976
|
const context = {
|
|
278
2977
|
...data,
|
|
279
2978
|
__filename: templatePath,
|
|
280
|
-
__dirname:
|
|
2979
|
+
__dirname: path7.dirname(templatePath)
|
|
281
2980
|
};
|
|
282
2981
|
await extractVariables(scriptContent, context, templatePath);
|
|
283
2982
|
const dependencies = new Set;
|
|
@@ -299,7 +2998,7 @@ async function createStreamRenderer(templatePath, options = {}) {
|
|
|
299
2998
|
var islandDirective = {
|
|
300
2999
|
name: "island",
|
|
301
3000
|
hasEndTag: true,
|
|
302
|
-
handler: (content, params,
|
|
3001
|
+
handler: (content, params, _context, _filePath) => {
|
|
303
3002
|
if (!params || params.length === 0) {
|
|
304
3003
|
throw new Error("Island directive requires a name parameter");
|
|
305
3004
|
}
|
|
@@ -324,29 +3023,50 @@ function registerStreamingDirectives(options = {}) {
|
|
|
324
3023
|
}
|
|
325
3024
|
return directives;
|
|
326
3025
|
}
|
|
327
|
-
async function processSectionDirectives(content, context, filePath,
|
|
3026
|
+
async function processSectionDirectives(content, context, filePath, _options = {}) {
|
|
328
3027
|
return content;
|
|
329
3028
|
}
|
|
330
3029
|
|
|
331
3030
|
// src/index.ts
|
|
332
|
-
var src_default =
|
|
3031
|
+
var src_default = {};
|
|
333
3032
|
export {
|
|
334
3033
|
webComponentDirectiveHandler,
|
|
3034
|
+
validators,
|
|
335
3035
|
unescapeHtml,
|
|
3036
|
+
transitionDirective,
|
|
336
3037
|
templateCache,
|
|
3038
|
+
structuredDataDirective,
|
|
337
3039
|
streamTemplate,
|
|
338
3040
|
setGlobalContext,
|
|
3041
|
+
serveStxFile,
|
|
3042
|
+
serveMultipleStxFiles,
|
|
3043
|
+
serveFile,
|
|
3044
|
+
serve2 as serve,
|
|
3045
|
+
scrollAnimateDirective,
|
|
3046
|
+
screenReaderDirective,
|
|
3047
|
+
scanA11yIssues,
|
|
3048
|
+
safeExecuteAsync,
|
|
3049
|
+
safeExecute,
|
|
339
3050
|
runPreProcessingMiddleware,
|
|
340
3051
|
runPostProcessingMiddleware,
|
|
341
3052
|
resolveTemplatePath,
|
|
342
3053
|
renderComponent,
|
|
343
3054
|
registerStreamingDirectives,
|
|
3055
|
+
registerSeoDirectives,
|
|
3056
|
+
registerComponentDirectives,
|
|
3057
|
+
registerAnimationDirectives,
|
|
3058
|
+
registerA11yDirectives,
|
|
3059
|
+
readMarkdownFile,
|
|
344
3060
|
processTranslateDirective,
|
|
3061
|
+
processStructuredData,
|
|
345
3062
|
processStackReplacements,
|
|
346
3063
|
processStackPushDirectives,
|
|
3064
|
+
processSeoDirective,
|
|
347
3065
|
processSectionDirectives,
|
|
348
3066
|
processOnceDirective,
|
|
349
3067
|
processMiddleware,
|
|
3068
|
+
processMetaDirectives,
|
|
3069
|
+
processMarkdownFileDirectives,
|
|
350
3070
|
processMarkdownDirectives,
|
|
351
3071
|
processLoops,
|
|
352
3072
|
processJsonDirective,
|
|
@@ -359,18 +3079,29 @@ export {
|
|
|
359
3079
|
processDirectives,
|
|
360
3080
|
processCustomDirectives,
|
|
361
3081
|
processBasicFormDirectives,
|
|
3082
|
+
processAnimationDirectives,
|
|
3083
|
+
processA11yDirectives,
|
|
362
3084
|
partialsCache,
|
|
3085
|
+
onceStore,
|
|
3086
|
+
motionDirective,
|
|
3087
|
+
metaDirective,
|
|
363
3088
|
markdownDirectiveHandler,
|
|
3089
|
+
markdownCache,
|
|
364
3090
|
loadTranslation,
|
|
365
3091
|
islandDirective,
|
|
3092
|
+
injectSeoTags,
|
|
3093
|
+
initFile,
|
|
366
3094
|
hashFilePath,
|
|
3095
|
+
gitHash,
|
|
367
3096
|
getTranslation,
|
|
368
3097
|
getSourceLineInfo,
|
|
3098
|
+
getScreenReaderOnlyStyle,
|
|
369
3099
|
generateTemplatesDocs,
|
|
370
3100
|
generateDocs,
|
|
371
3101
|
generateDirectivesDocs,
|
|
372
3102
|
generateComponentsDocs,
|
|
373
3103
|
generateComponentDoc,
|
|
3104
|
+
formatStxContent,
|
|
374
3105
|
formatDocsAsMarkdown,
|
|
375
3106
|
formatDocsAsJson,
|
|
376
3107
|
formatDocsAsHtml,
|
|
@@ -382,16 +3113,43 @@ export {
|
|
|
382
3113
|
evaluateExpression,
|
|
383
3114
|
evaluateAuthExpression,
|
|
384
3115
|
escapeHtml,
|
|
3116
|
+
errorRecovery,
|
|
3117
|
+
errorLogger,
|
|
385
3118
|
docsCommand,
|
|
3119
|
+
devHelpers,
|
|
3120
|
+
defineStxConfig,
|
|
3121
|
+
defaultI18nConfig,
|
|
386
3122
|
defaultFilters,
|
|
387
3123
|
defaultConfig,
|
|
388
3124
|
src_default as default,
|
|
389
3125
|
createTranslateFilter,
|
|
390
3126
|
createStreamRenderer,
|
|
3127
|
+
createRoute,
|
|
3128
|
+
createMiddleware,
|
|
3129
|
+
createEnhancedError,
|
|
391
3130
|
createDetailedErrorMessage,
|
|
392
3131
|
config,
|
|
3132
|
+
componentDirective,
|
|
3133
|
+
clearOnceStore,
|
|
393
3134
|
checkCache,
|
|
3135
|
+
checkA11y,
|
|
394
3136
|
cacheTemplate,
|
|
395
3137
|
buildWebComponents,
|
|
396
|
-
applyFilters
|
|
3138
|
+
applyFilters,
|
|
3139
|
+
animationGroupDirective,
|
|
3140
|
+
analyzeTemplate,
|
|
3141
|
+
analyzeProject,
|
|
3142
|
+
a11yDirective,
|
|
3143
|
+
TransitionType,
|
|
3144
|
+
TransitionEase,
|
|
3145
|
+
TransitionDirection,
|
|
3146
|
+
StxSyntaxError,
|
|
3147
|
+
StxSecurityError,
|
|
3148
|
+
StxRuntimeError,
|
|
3149
|
+
StxFileError,
|
|
3150
|
+
StxError,
|
|
3151
|
+
ErrorLogger,
|
|
3152
|
+
DEFAULT_TRANSITION_OPTIONS
|
|
397
3153
|
};
|
|
3154
|
+
|
|
3155
|
+
export { analyzeProject, plugin, serveStxFile, serveMultipleStxFiles, docsCommand, formatStxContent, initFile, gitHash };
|