@stacksjs/stx 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js DELETED
@@ -1,3155 +0,0 @@
1
- // @bun
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,
16
- buildWebComponents,
17
- checkA11y,
18
- clearOnceStore,
19
- componentDirective,
20
- config,
21
- createDetailedErrorMessage,
22
- createEnhancedError,
23
- createTranslateFilter,
24
- defaultConfig,
25
- defaultFilters,
26
- defaultI18nConfig,
27
- defineStxConfig,
28
- devHelpers,
29
- errorLogger,
30
- errorRecovery,
31
- escapeHtml,
32
- evaluateAuthExpression,
33
- evaluateExpression,
34
- extractVariables,
35
- fileExists,
36
- getScreenReaderOnlyStyle,
37
- getSourceLineInfo,
38
- getTranslation,
39
- injectSeoTags,
40
- loadTranslation,
41
- markdownCache,
42
- markdownDirectiveHandler,
43
- metaDirective,
44
- motionDirective,
45
- onceStore,
46
- partialsCache,
47
- performanceMonitor,
48
- processA11yDirectives,
49
- processAnimationDirectives,
50
- processBasicFormDirectives,
51
- processCustomDirectives,
52
- processDirectives,
53
- processErrorDirective,
54
- processExpressions,
55
- processFormDirectives,
56
- processFormInputDirectives,
57
- processForms,
58
- processIncludes,
59
- processJsonDirective,
60
- processLoops,
61
- processMarkdownDirectives,
62
- processMarkdownFileDirectives,
63
- processMetaDirectives,
64
- processMiddleware,
65
- processOnceDirective,
66
- processSeoDirective,
67
- processStackPushDirectives,
68
- processStackReplacements,
69
- processStructuredData,
70
- processTranslateDirective,
71
- readMarkdownFile,
72
- registerA11yDirectives,
73
- registerAnimationDirectives,
74
- registerComponentDirectives,
75
- registerSeoDirectives,
76
- renderComponent,
77
- resolveTemplatePath,
78
- runPostProcessingMiddleware,
79
- runPreProcessingMiddleware,
80
- safeExecute,
81
- safeExecuteAsync,
82
- scanA11yIssues,
83
- screenReaderDirective,
84
- scrollAnimateDirective,
85
- setGlobalContext,
86
- structuredDataDirective,
87
- transitionDirective,
88
- unescapeHtml,
89
- validators,
90
- webComponentDirectiveHandler
91
- } from "../chunk-sjb2sfg6.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";
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";
454
- var plugin = {
455
- name: "bun-plugin-stx",
456
- async setup(build) {
457
- const options = {
458
- ...config,
459
- ...build.config?.stx
460
- };
461
- const allDependencies = new Set;
462
- const webComponentsPath = options.webComponents?.enabled ? `./${path2.relative(path2.dirname(build.config?.outdir || "dist"), options.webComponents.outputDir || "dist/web-components")}` : "/web-components";
463
- const builtComponents = [];
464
- if (options.webComponents?.enabled) {
465
- try {
466
- const components = await buildWebComponents(options, allDependencies);
467
- builtComponents.push(...components);
468
- if (options.debug && components.length > 0) {
469
- console.log(`Successfully built ${components.length} web components`);
470
- }
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";
2023
- }
2024
- }
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>`;
2163
- }
2164
- html += `</tbody></table>`;
2165
- }
2166
- if (doc.example) {
2167
- html += `<h4>Example</h4>`;
2168
- html += `<pre><code>${doc.example.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;")}</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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;")}</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;
2426
- }
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()} !!}`;
2533
- });
2534
- }
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 = {}) {
2596
- try {
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.`);
2602
- }
2603
- console.warn(`File ${fileName} already exists. Overwriting...`);
2604
- }
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;
2629
- }
2630
- }
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)
2732
- };
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
- }
2840
- });
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
- });
2889
- }
2890
- function createMiddleware(handler) {
2891
- return handler;
2892
- }
2893
- function createRoute(handler) {
2894
- return handler;
2895
- }
2896
- // src/streaming.ts
2897
- import path7 from "path";
2898
- var defaultStreamingConfig = {
2899
- enabled: true,
2900
- bufferSize: 1024 * 16,
2901
- strategy: "auto",
2902
- timeout: 30000
2903
- };
2904
- var SECTION_PATTERN = /<!-- @section:([\w-]+) -->([\s\S]*?)<!-- @endsection:\1 -->/g;
2905
- async function streamTemplate(templatePath, data = {}, options = {}) {
2906
- const fullOptions = {
2907
- ...defaultConfig,
2908
- ...options,
2909
- streaming: {
2910
- ...defaultStreamingConfig,
2911
- ...options.streaming
2912
- }
2913
- };
2914
- return new ReadableStream({
2915
- async start(controller) {
2916
- try {
2917
- const content = await Bun.file(templatePath).text();
2918
- const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
2919
- const scriptContent = scriptMatch ? scriptMatch[1] : "";
2920
- const templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
2921
- const context = {
2922
- ...data,
2923
- __filename: templatePath,
2924
- __dirname: path7.dirname(templatePath)
2925
- };
2926
- await extractVariables(scriptContent, context, templatePath);
2927
- const dependencies = new Set;
2928
- const output = await processDirectives(templateContent, context, templatePath, fullOptions, dependencies);
2929
- controller.enqueue(output);
2930
- controller.close();
2931
- } catch (error) {
2932
- controller.error(error);
2933
- }
2934
- }
2935
- });
2936
- }
2937
- async function createStreamRenderer(templatePath, options = {}) {
2938
- const fullOptions = {
2939
- ...defaultConfig,
2940
- ...options,
2941
- streaming: {
2942
- ...defaultStreamingConfig,
2943
- ...options.streaming
2944
- }
2945
- };
2946
- const content = await Bun.file(templatePath).text();
2947
- const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
2948
- const scriptContent = scriptMatch ? scriptMatch[1] : "";
2949
- const templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/i, "");
2950
- const originalTemplate = templateContent;
2951
- const sections = {};
2952
- let match;
2953
- SECTION_PATTERN.lastIndex = 0;
2954
- while ((match = SECTION_PATTERN.exec(templateContent)) !== null) {
2955
- const sectionName = match[1];
2956
- const sectionContent = match[2];
2957
- sections[sectionName] = sectionContent;
2958
- }
2959
- const shellTemplate = templateContent.replace(SECTION_PATTERN, "");
2960
- const renderer = {
2961
- renderShell: async (data = {}) => {
2962
- const context = {
2963
- ...data,
2964
- __filename: templatePath,
2965
- __dirname: path7.dirname(templatePath)
2966
- };
2967
- await extractVariables(scriptContent, context, templatePath);
2968
- const dependencies = new Set;
2969
- return processDirectives(shellTemplate, context, templatePath, fullOptions, dependencies);
2970
- },
2971
- renderSection: async (sectionName, data = {}) => {
2972
- try {
2973
- if (!sections[sectionName]) {
2974
- return `<div class="error-message">Section "${sectionName}" not found in template "${templatePath}"</div>`;
2975
- }
2976
- const context = {
2977
- ...data,
2978
- __filename: templatePath,
2979
- __dirname: path7.dirname(templatePath)
2980
- };
2981
- await extractVariables(scriptContent, context, templatePath);
2982
- const dependencies = new Set;
2983
- return await processDirectives(sections[sectionName], context, templatePath, fullOptions, dependencies);
2984
- } catch (error) {
2985
- const errorMessage = error instanceof Error ? error.message : String(error);
2986
- return createDetailedErrorMessage("Expression", errorMessage, templatePath, sections[sectionName] || "", 0, sections[sectionName] || "");
2987
- }
2988
- },
2989
- getSections: () => {
2990
- return Object.keys(sections);
2991
- },
2992
- getTemplate: () => {
2993
- return originalTemplate;
2994
- }
2995
- };
2996
- return renderer;
2997
- }
2998
- var islandDirective = {
2999
- name: "island",
3000
- hasEndTag: true,
3001
- handler: (content, params, _context, _filePath) => {
3002
- if (!params || params.length === 0) {
3003
- throw new Error("Island directive requires a name parameter");
3004
- }
3005
- const islandName = params[0].replace(/['"`]/g, "");
3006
- const priority = params[1] ? params[1].replace(/['"`]/g, "") : "lazy";
3007
- const id = `island-${islandName}-${Math.random().toString(36).substring(2, 9)}`;
3008
- const propsMatch = content.match(/<script\s+props\s*>([\s\S]*?)<\/script>/i);
3009
- const propsScript = propsMatch ? propsMatch[1].trim() : "";
3010
- const contentWithoutProps = propsMatch ? content.replace(propsMatch[0], "") : content;
3011
- return `<div data-island="${islandName}" data-island-id="${id}" data-priority="${priority}">
3012
- ${contentWithoutProps}
3013
- <script type="application/json" data-island-props="${id}">
3014
- ${propsScript ? `{${propsScript}}` : "{}"}
3015
- </script>
3016
- </div>`;
3017
- }
3018
- };
3019
- function registerStreamingDirectives(options = {}) {
3020
- const directives = [];
3021
- if (options.hydration?.enabled) {
3022
- directives.push(islandDirective);
3023
- }
3024
- return directives;
3025
- }
3026
- async function processSectionDirectives(content, context, filePath, _options = {}) {
3027
- return content;
3028
- }
3029
-
3030
- // src/index.ts
3031
- var src_default = {};
3032
- export {
3033
- webComponentDirectiveHandler,
3034
- validators,
3035
- unescapeHtml,
3036
- transitionDirective,
3037
- templateCache,
3038
- structuredDataDirective,
3039
- streamTemplate,
3040
- setGlobalContext,
3041
- serveStxFile,
3042
- serveMultipleStxFiles,
3043
- serveFile,
3044
- serve2 as serve,
3045
- scrollAnimateDirective,
3046
- screenReaderDirective,
3047
- scanA11yIssues,
3048
- safeExecuteAsync,
3049
- safeExecute,
3050
- runPreProcessingMiddleware,
3051
- runPostProcessingMiddleware,
3052
- resolveTemplatePath,
3053
- renderComponent,
3054
- registerStreamingDirectives,
3055
- registerSeoDirectives,
3056
- registerComponentDirectives,
3057
- registerAnimationDirectives,
3058
- registerA11yDirectives,
3059
- readMarkdownFile,
3060
- processTranslateDirective,
3061
- processStructuredData,
3062
- processStackReplacements,
3063
- processStackPushDirectives,
3064
- processSeoDirective,
3065
- processSectionDirectives,
3066
- processOnceDirective,
3067
- processMiddleware,
3068
- processMetaDirectives,
3069
- processMarkdownFileDirectives,
3070
- processMarkdownDirectives,
3071
- processLoops,
3072
- processJsonDirective,
3073
- processIncludes,
3074
- processForms,
3075
- processFormInputDirectives,
3076
- processFormDirectives,
3077
- processExpressions,
3078
- processErrorDirective,
3079
- processDirectives,
3080
- processCustomDirectives,
3081
- processBasicFormDirectives,
3082
- processAnimationDirectives,
3083
- processA11yDirectives,
3084
- partialsCache,
3085
- onceStore,
3086
- motionDirective,
3087
- metaDirective,
3088
- markdownDirectiveHandler,
3089
- markdownCache,
3090
- loadTranslation,
3091
- islandDirective,
3092
- injectSeoTags,
3093
- initFile,
3094
- hashFilePath,
3095
- gitHash,
3096
- getTranslation,
3097
- getSourceLineInfo,
3098
- getScreenReaderOnlyStyle,
3099
- generateTemplatesDocs,
3100
- generateDocs,
3101
- generateDirectivesDocs,
3102
- generateComponentsDocs,
3103
- generateComponentDoc,
3104
- formatStxContent,
3105
- formatDocsAsMarkdown,
3106
- formatDocsAsJson,
3107
- formatDocsAsHtml,
3108
- findComponentFiles,
3109
- fileExists,
3110
- extractVariables,
3111
- extractComponentProps,
3112
- extractComponentDescription,
3113
- evaluateExpression,
3114
- evaluateAuthExpression,
3115
- escapeHtml,
3116
- errorRecovery,
3117
- errorLogger,
3118
- docsCommand,
3119
- devHelpers,
3120
- defineStxConfig,
3121
- defaultI18nConfig,
3122
- defaultFilters,
3123
- defaultConfig,
3124
- src_default as default,
3125
- createTranslateFilter,
3126
- createStreamRenderer,
3127
- createRoute,
3128
- createMiddleware,
3129
- createEnhancedError,
3130
- createDetailedErrorMessage,
3131
- config,
3132
- componentDirective,
3133
- clearOnceStore,
3134
- checkCache,
3135
- checkA11y,
3136
- cacheTemplate,
3137
- buildWebComponents,
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
3153
- };
3154
-
3155
- export { analyzeProject, plugin, serveStxFile, serveMultipleStxFiles, docsCommand, formatStxContent, initFile, gitHash };