@emberkit/core 0.1.2-alpha.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.
Files changed (62) hide show
  1. package/LICENSE +199 -0
  2. package/dist/boundaries/error-boundary.js +70 -0
  3. package/dist/boundaries/errors.js +72 -0
  4. package/dist/boundaries/index.js +3 -0
  5. package/dist/boundaries/loading-boundary.js +106 -0
  6. package/dist/cache/index.js +213 -0
  7. package/dist/compiler/compiler.js +44 -0
  8. package/dist/compiler/helpers/attributes.js +35 -0
  9. package/dist/compiler/helpers/utils.js +31 -0
  10. package/dist/compiler/index.js +4 -0
  11. package/dist/compiler/types.js +3 -0
  12. package/dist/context/index.js +51 -0
  13. package/dist/context/types.js +1 -0
  14. package/dist/dev-server/index.js +121 -0
  15. package/dist/forms/index.js +164 -0
  16. package/dist/forms/mutations.js +258 -0
  17. package/dist/hmr/client.js +84 -0
  18. package/dist/hmr/index.js +2 -0
  19. package/dist/hmr/types.js +133 -0
  20. package/dist/hydration/helpers/analyzer.js +94 -0
  21. package/dist/hydration/helpers/hydration.js +129 -0
  22. package/dist/hydration/index.js +3 -0
  23. package/dist/hydration/types.js +18 -0
  24. package/dist/image/index.js +34 -0
  25. package/dist/image/processor.js +143 -0
  26. package/dist/index.js +16 -0
  27. package/dist/jsx-dev-runtime.js +7 -0
  28. package/dist/jsx-runtime.js +7 -0
  29. package/dist/loader/helpers/loader.js +61 -0
  30. package/dist/loader/index.js +2 -0
  31. package/dist/loader/types.js +14 -0
  32. package/dist/markdown/index.js +365 -0
  33. package/dist/mdx/index.js +156 -0
  34. package/dist/mdx/loader.js +6 -0
  35. package/dist/meta/head-registry.js +15 -0
  36. package/dist/meta/head.js +100 -0
  37. package/dist/meta/index.js +210 -0
  38. package/dist/navigation/helpers/navigation.js +53 -0
  39. package/dist/navigation/helpers/useNavigate.js +10 -0
  40. package/dist/navigation/index.js +3 -0
  41. package/dist/navigation/types.js +2 -0
  42. package/dist/plugin/index.js +74 -0
  43. package/dist/router/helpers/path.js +74 -0
  44. package/dist/router/helpers/route.js +109 -0
  45. package/dist/router/index.js +110 -0
  46. package/dist/router/types.js +5 -0
  47. package/dist/runtime/helpers/element.js +52 -0
  48. package/dist/runtime/helpers/render.js +121 -0
  49. package/dist/runtime/index.js +132 -0
  50. package/dist/runtime/types.js +1 -0
  51. package/dist/signals/helpers/core.js +96 -0
  52. package/dist/signals/helpers/utils.js +22 -0
  53. package/dist/signals/index.js +3 -0
  54. package/dist/signals/types.js +1 -0
  55. package/dist/ssg/index.js +119 -0
  56. package/dist/ssr/helpers/render-html.js +55 -0
  57. package/dist/ssr/helpers/ssr.js +90 -0
  58. package/dist/ssr/index.js +3 -0
  59. package/dist/ssr/types.js +24 -0
  60. package/dist/vite-plugin/index.js +650 -0
  61. package/dist/vite-plugin/types.js +13 -0
  62. package/package.json +66 -0
@@ -0,0 +1,650 @@
1
+ import { DEFAULT_CONFIG } from './types.js';
2
+ import { readdirSync, statSync } from 'node:fs';
3
+ import { join, relative, dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const VIRTUAL_EMBERKIT_CONFIG = 'virtual:emberkit-config';
7
+ const VIRTUAL_EMBERKIT_ROUTES = 'virtual:emberkit-routes';
8
+ function resolveConfig(userOptions = {}) {
9
+ return {
10
+ ...DEFAULT_CONFIG,
11
+ ...userOptions,
12
+ markdown: { ...DEFAULT_CONFIG.markdown, ...userOptions.markdown },
13
+ };
14
+ }
15
+ export function emberkitVitePlugin(userOptions = {}) {
16
+ const options = resolveConfig(userOptions);
17
+ let routesCode = `export const routes = [];`;
18
+ return {
19
+ name: 'emberkit:vite-plugin',
20
+ enforce: 'pre',
21
+ config() {
22
+ const pkgRoot = resolve(__dirname, '..', '..');
23
+ const srcDir = join(pkgRoot, 'src');
24
+ return {
25
+ resolve: {
26
+ alias: {
27
+ '@emberkit/core': srcDir,
28
+ },
29
+ },
30
+ esbuild: {
31
+ jsxImportSource: '@emberkit/core',
32
+ },
33
+ optimizeDeps: {
34
+ exclude: ['@emberkit/core'],
35
+ },
36
+ };
37
+ },
38
+ configResolved(config) {
39
+ const root = config.root;
40
+ const routeDir = join(root, options.routeDir ?? 'src/routes');
41
+ const files = scanRouteFiles(routeDir);
42
+ routesCode = generateRoutesCode(files, routeDir);
43
+ },
44
+ resolveId(id) {
45
+ if (id === VIRTUAL_EMBERKIT_CONFIG) {
46
+ return VIRTUAL_EMBERKIT_CONFIG;
47
+ }
48
+ if (id === VIRTUAL_EMBERKIT_ROUTES) {
49
+ return VIRTUAL_EMBERKIT_ROUTES;
50
+ }
51
+ return null;
52
+ },
53
+ load(id) {
54
+ if (id === VIRTUAL_EMBERKIT_CONFIG) {
55
+ return `export const config = ${JSON.stringify(options)};`;
56
+ }
57
+ if (id === VIRTUAL_EMBERKIT_ROUTES) {
58
+ return routesCode;
59
+ }
60
+ return null;
61
+ },
62
+ transform(code, id) {
63
+ if (id.includes('\u0000'))
64
+ return null;
65
+ const ext = id.split('.').pop() ?? '';
66
+ const isMD = ext === 'md';
67
+ const isMDX = ext === 'mdx';
68
+ if (!isMD && !isMDX) {
69
+ if (ext !== 'tsx' && ext !== 'ts' && ext !== 'jsx' && ext !== 'js') {
70
+ return null;
71
+ }
72
+ return null;
73
+ }
74
+ if (isMD || isMDX) {
75
+ return transformMarkdownToJSX(code, id, options);
76
+ }
77
+ return code;
78
+ },
79
+ };
80
+ }
81
+ function transformMarkdownToJSX(code, id, options) {
82
+ const frontmatterMatch = code.match(/^---\n([\s\S]*?)\n---\n?/);
83
+ let frontmatter = {};
84
+ let content = code;
85
+ if (frontmatterMatch) {
86
+ const fmContent = frontmatterMatch[1];
87
+ frontmatter = parseFrontmatter(fmContent);
88
+ content = code.slice(frontmatterMatch[0].length);
89
+ }
90
+ const jsxContent = markdownToJSX(content, options.markdown);
91
+ const exportLines = [];
92
+ if (frontmatter.title) {
93
+ exportLines.push(`export const title = ${JSON.stringify(frontmatter.title)};`);
94
+ }
95
+ if (frontmatter.description) {
96
+ exportLines.push(`export const description = ${JSON.stringify(frontmatter.description)};`);
97
+ }
98
+ if (frontmatter.author) {
99
+ exportLines.push(`export const author = ${JSON.stringify(frontmatter.author)};`);
100
+ }
101
+ if (frontmatter.date) {
102
+ exportLines.push(`export const date = ${JSON.stringify(frontmatter.date)};`);
103
+ }
104
+ exportLines.push(`export const metadata = ${JSON.stringify(frontmatter)};`);
105
+ const componentCode = `
106
+ import { createElement } from '@emberkit/core';
107
+
108
+ ${exportLines.join('\n')}
109
+
110
+ const defaultContent = ${JSON.stringify(jsxContent)};
111
+
112
+ function MDContent(props) {
113
+ return createElement('div', {
114
+ className: 'md-content',
115
+ dangerouslySetInnerHTML: { __html: defaultContent }
116
+ });
117
+ }
118
+
119
+ export default function MDComponent(props) {
120
+ return createElement('article', {
121
+ className: 'md-doc',
122
+ 'data-file': ${JSON.stringify(id)},
123
+ children: [
124
+ createElement(MDContent, props)
125
+ ]
126
+ });
127
+ }
128
+ `;
129
+ return { code: componentCode };
130
+ }
131
+ function parseFrontmatter(content) {
132
+ const result = {};
133
+ const lines = content.split('\n');
134
+ for (const line of lines) {
135
+ const colonIndex = line.indexOf(':');
136
+ if (colonIndex === -1)
137
+ continue;
138
+ const key = line.slice(0, colonIndex).trim();
139
+ let value = line.slice(colonIndex + 1).trim();
140
+ if (value === 'true')
141
+ value = true;
142
+ else if (value === 'false')
143
+ value = false;
144
+ else if (!isNaN(Number(value)))
145
+ value = Number(value);
146
+ else if (typeof value === 'string' && value.startsWith('[')) {
147
+ value = value.replace(/[\[\]]/g, '').split(',').map((s) => s.trim());
148
+ }
149
+ result[key] = value;
150
+ }
151
+ return result;
152
+ }
153
+ function markdownToJSX(content, options) {
154
+ let html = content;
155
+ html = processCodeBlocks(html);
156
+ html = processHeadings(html);
157
+ html = processHorizontalRules(html);
158
+ html = processTables(html);
159
+ html = processImages(html);
160
+ html = processLinks(html);
161
+ html = processLists(html);
162
+ html = processBlockquotes(html);
163
+ html = processEmphasis(html);
164
+ html = processParagraphs(html, options.breaks);
165
+ return html;
166
+ }
167
+ function processHeadings(html) {
168
+ return html.replace(/^(#{1,6})\s+(.+)$/gm, (_match, hashes, text) => {
169
+ const id = text.toLowerCase().replace(/[^\w]+/g, '-');
170
+ return `<h${hashes.length} id="${id}">${text}</h${hashes.length}>`;
171
+ });
172
+ }
173
+ function processCodeBlocks(html) {
174
+ return html.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
175
+ let highlighted = code.trim();
176
+ if (lang === 'ts' || lang === 'tsx' || lang === 'js' || lang === 'jsx' || lang === 'typescript' || lang === 'javascript') {
177
+ highlighted = highlightTS(highlighted);
178
+ }
179
+ else if (lang === 'bash' || lang === 'sh' || lang === 'shell') {
180
+ highlighted = highlightBash(highlighted);
181
+ }
182
+ else if (lang === 'json') {
183
+ highlighted = highlightJSON(highlighted);
184
+ }
185
+ else {
186
+ highlighted = escapeHtml(highlighted);
187
+ }
188
+ const langAttr = lang ? ` data-lang="${lang}"` : '';
189
+ return `<pre${langAttr}><button class="copy-btn" onclick="(async()=>{await navigator.clipboard.writeText(this.closest('pre').querySelector('code').textContent);this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500)})()"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg> Copy</button><code class="language-${lang}">${highlighted}</code></pre>`;
190
+ });
191
+ }
192
+ function escapeHtml(text) {
193
+ return text
194
+ .replace(/&/g, '&amp;')
195
+ .replace(/</g, '&lt;')
196
+ .replace(/>/g, '&gt;');
197
+ }
198
+ function highlightTS(code) {
199
+ const tokens = [];
200
+ let remaining = code;
201
+ const controlFlow = new Set(['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue', 'return', 'throw', 'try', 'catch', 'finally']);
202
+ const declarations = new Set(['import', 'export', 'from', 'const', 'let', 'var', 'function', 'class', 'extends', 'super', 'enum', 'type', 'interface', 'module', 'namespace', 'declare', 'new', 'delete', 'typeof', 'instanceof', 'in', 'of', 'default', 'as', 'satisfies', 'keyof', 'infer', 'is', 'asserts', 'abstract', 'implements']);
203
+ const modifiers = new Set(['readonly', 'public', 'private', 'protected', 'static', 'abstract', 'async', 'override']);
204
+ const literals = new Set(['true', 'false', 'null', 'undefined', 'this']);
205
+ const builtins = new Set(['console', 'document', 'window', 'Math', 'JSON', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Promise', 'Map', 'Set', 'RegExp', 'Date', 'Error', 'Symbol', 'Record', 'Partial', 'Required', 'Pick', 'Omit', 'Exclude', 'Extract', 'ReturnType', 'Parameters', 'JSX', 'FC', 'Props', 'State', 'Effect', 'Memo', 'Signal', 'Ref', 'Context', 'React']);
206
+ while (remaining.length > 0) {
207
+ let m;
208
+ // Multi-line comment
209
+ m = remaining.match(/^\/\*[\s\S]*?\*\//);
210
+ if (m) {
211
+ tokens.push(`<span class="cm">${escapeHtml(m[0])}</span>`);
212
+ remaining = remaining.slice(m[0].length);
213
+ continue;
214
+ }
215
+ // Single-line comment
216
+ m = remaining.match(/^\/\/.*/);
217
+ if (m) {
218
+ tokens.push(`<span class="cm">${escapeHtml(m[0])}</span>`);
219
+ remaining = remaining.slice(m[0].length);
220
+ continue;
221
+ }
222
+ // Template literal with interpolations
223
+ m = remaining.match(/^`/);
224
+ if (m) {
225
+ let tmpl = '`';
226
+ remaining = remaining.slice(1);
227
+ while (remaining.length > 0) {
228
+ if (remaining[0] === '`') {
229
+ tmpl += '`';
230
+ remaining = remaining.slice(1);
231
+ break;
232
+ }
233
+ if (remaining.startsWith('\\')) {
234
+ tmpl += remaining.slice(0, 2);
235
+ remaining = remaining.slice(2);
236
+ continue;
237
+ }
238
+ if (remaining.startsWith('${')) {
239
+ tmpl += '${';
240
+ remaining = remaining.slice(2);
241
+ // Parse interpolation until matching }
242
+ let depth = 1;
243
+ let expr = '';
244
+ while (remaining.length > 0 && depth > 0) {
245
+ if (remaining[0] === '{')
246
+ depth++;
247
+ if (remaining[0] === '}') {
248
+ depth--;
249
+ if (depth === 0) {
250
+ remaining = remaining.slice(1);
251
+ break;
252
+ }
253
+ }
254
+ expr += remaining[0];
255
+ remaining = remaining.slice(1);
256
+ }
257
+ tmpl += highlightInlineExpr(expr) + '}';
258
+ continue;
259
+ }
260
+ tmpl += remaining[0];
261
+ remaining = remaining.slice(1);
262
+ }
263
+ tokens.push(`<span class="str">${tmpl}</span>`);
264
+ continue;
265
+ }
266
+ // String (single, double)
267
+ m = remaining.match(/^('[^']*'|"[^"]*")/);
268
+ if (m) {
269
+ tokens.push(`<span class="str">${escapeHtml(m[0])}</span>`);
270
+ remaining = remaining.slice(m[0].length);
271
+ continue;
272
+ }
273
+ // Arrow function =>
274
+ m = remaining.match(/^=>/);
275
+ if (m) {
276
+ tokens.push(`<span class="op">=&gt;</span>`);
277
+ remaining = remaining.slice(2);
278
+ continue;
279
+ }
280
+ // JSX closing tag </Component>
281
+ m = remaining.match(/^(<\/)([A-Za-z][\w.]*)(>)/);
282
+ if (m) {
283
+ tokens.push(`${escapeHtml(m[1])}<span class="tag">${m[2]}</span>${escapeHtml(m[3])}`);
284
+ remaining = remaining.slice(m[0].length);
285
+ continue;
286
+ }
287
+ // JSX self-closing or opening tag <Component or <div
288
+ m = remaining.match(/^(<)([A-Za-z][\w.]*)/);
289
+ if (m) {
290
+ tokens.push(`${escapeHtml(m[1])}<span class="tag">${m[2]}</span>`);
291
+ remaining = remaining.slice(m[0].length);
292
+ continue;
293
+ }
294
+ // JSX prop name (word followed by =)
295
+ m = remaining.match(/^([a-zA-Z_][\w.]*)\s*(?==)/);
296
+ if (m && !['if', 'else', 'for', 'while', 'switch', 'case', 'return', 'import', 'export', 'from', 'const', 'let', 'var', 'function', 'class', 'new', 'typeof', 'instanceof', 'void', 'null', 'undefined', 'true', 'false', 'this'].includes(m[1])) {
297
+ tokens.push(`<span class="attr">${escapeHtml(m[1])}</span>`);
298
+ remaining = remaining.slice(m[1].length);
299
+ continue;
300
+ }
301
+ // Decorator @Decorator
302
+ m = remaining.match(/^@([A-Za-z_]\w*)/);
303
+ if (m) {
304
+ tokens.push(`<span class="dec">${m[0]}</span>`);
305
+ remaining = remaining.slice(m[0].length);
306
+ continue;
307
+ }
308
+ // Number (with underscores and scientific notation)
309
+ m = remaining.match(/^(\d[\d_]*\.?[\d_]*([eE][+-]?\d+)?)/);
310
+ if (m) {
311
+ tokens.push(`<span class="num">${m[0]}</span>`);
312
+ remaining = remaining.slice(m[0].length);
313
+ continue;
314
+ }
315
+ // Multi-char operators (check before single-char)
316
+ m = remaining.match(/^(&&|\|\||===|!==|==|!=|<=|>=|\+\+|--|\*\*|=>|\.\.\.)/);
317
+ if (m) {
318
+ tokens.push(`<span class="op">${escapeHtml(m[0])}</span>`);
319
+ remaining = remaining.slice(m[0].length);
320
+ continue;
321
+ }
322
+ // Word (keyword, type, builtin, function, identifier)
323
+ m = remaining.match(/^([A-Za-z_$][\w$]*)/);
324
+ if (m) {
325
+ const word = m[1];
326
+ let cls = '';
327
+ if (controlFlow.has(word))
328
+ cls = 'kw';
329
+ else if (declarations.has(word))
330
+ cls = 'kw';
331
+ else if (modifiers.has(word))
332
+ cls = 'kw';
333
+ else if (literals.has(word))
334
+ cls = 'val';
335
+ else if (builtins.has(word))
336
+ cls = 'type';
337
+ else if (/^[A-Z]/.test(word) && word.length > 1)
338
+ cls = 'type';
339
+ else {
340
+ // Check if followed by ( → function call
341
+ const ahead = remaining.slice(word.length);
342
+ if (/^\s*\(/.test(ahead))
343
+ cls = 'fn';
344
+ }
345
+ tokens.push(cls ? `<span class="${cls}">${word}</span>` : word);
346
+ remaining = remaining.slice(word.length);
347
+ continue;
348
+ }
349
+ // Single-char operators and punctuation
350
+ m = remaining.match(/^([{}()\[\];:,.=<>\-*/|!?~^%])/);
351
+ if (m) {
352
+ tokens.push(`<span class="op">${escapeHtml(m[0])}</span>`);
353
+ remaining = remaining.slice(m[0].length);
354
+ continue;
355
+ }
356
+ // Whitespace
357
+ m = remaining.match(/^(\s+)/);
358
+ if (m) {
359
+ tokens.push(m[0]);
360
+ remaining = remaining.slice(m[0].length);
361
+ continue;
362
+ }
363
+ // Anything else
364
+ tokens.push(escapeHtml(remaining[0]));
365
+ remaining = remaining.slice(1);
366
+ }
367
+ return tokens.join('');
368
+ }
369
+ // Highlight a short inline expression (for template literal interpolations)
370
+ function highlightInlineExpr(code) {
371
+ return escapeHtml(code)
372
+ .replace(/\b(const|let|var|return|if|else|new|typeof|instanceof|async|await|function|import|from|export)\b/g, '<span class="kw">$1</span>')
373
+ .replace(/\b(true|false|null|undefined|this)\b/g, '<span class="val">$1</span>')
374
+ .replace(/(\d+)/g, '<span class="num">$1</span>')
375
+ .replace(/([A-Z][\w]+)/g, '<span class="type">$1</span>');
376
+ }
377
+ function highlightBash(code) {
378
+ const lines = code.split('\n');
379
+ return lines.map(line => {
380
+ const trimmed = line.trimStart();
381
+ // Comment
382
+ if (trimmed.startsWith('#')) {
383
+ return `<span class="cm">${line}</span>`;
384
+ }
385
+ // Content is already HTML-escaped by processCodeBlocks
386
+ // Tokenize directly without double-escaping
387
+ const tokens = [];
388
+ let remaining = line;
389
+ while (remaining.length > 0) {
390
+ // HTML entity — pass through
391
+ let m = remaining.match(/^(&\w+;)/);
392
+ if (m) {
393
+ tokens.push(m[1]);
394
+ remaining = remaining.slice(m[1].length);
395
+ continue;
396
+ }
397
+ // Quoted string
398
+ m = remaining.match(/^(&quot;[^&]*?&quot;|&apos;[^&]*?&apos;)/);
399
+ if (m) {
400
+ tokens.push(`<span class="str">${m[1]}</span>`);
401
+ remaining = remaining.slice(m[1].length);
402
+ continue;
403
+ }
404
+ // Flag
405
+ m = remaining.match(/^(\s+)(--?[\w-]+)/);
406
+ if (m) {
407
+ tokens.push(`${m[1]}<span class="attr">${m[2]}</span>`);
408
+ remaining = remaining.slice(m[0].length);
409
+ continue;
410
+ }
411
+ // Word (potential command)
412
+ m = remaining.match(/^([a-zA-Z][\w-]*)/);
413
+ if (m) {
414
+ const cmds = new Set(['sudo', 'cd', 'mkdir', 'rm', 'cp', 'mv', 'ls', 'cat', 'echo', 'npm', 'pnpm', 'yarn', 'git', 'curl', 'chmod', 'export', 'source', 'node', 'npx', 'bun', 'deno', 'grep', 'awk', 'sed', 'find', 'docker', 'kubectl']);
415
+ tokens.push(cmds.has(m[1]) ? `<span class="kw">${m[1]}</span>` : m[1]);
416
+ remaining = remaining.slice(m[1].length);
417
+ continue;
418
+ }
419
+ // Anything else
420
+ tokens.push(remaining[0]);
421
+ remaining = remaining.slice(1);
422
+ }
423
+ return tokens.join('');
424
+ }).join('\n');
425
+ }
426
+ function highlightJSON(code) {
427
+ let result = escapeHtml(code);
428
+ // Keys
429
+ result = result.replace(/(&quot;[^&]*&quot;|"[^"]*")\s*:/g, '<span class="attr">$1</span>:');
430
+ // String values
431
+ result = result.replace(/:\s*(&quot;[^&]*&quot;|"[^"]*")/g, ': <span class="val">$1</span>');
432
+ // Numbers
433
+ result = result.replace(/:\s*(\d+\.?\d*)/g, ': <span class="num">$1</span>');
434
+ // Booleans and null
435
+ result = result.replace(/:\s*(true|false|null)/g, ': <span class="kw">$1</span>');
436
+ return result;
437
+ }
438
+ function processTables(html) {
439
+ const lines = html.split('\n');
440
+ const result = [];
441
+ let i = 0;
442
+ while (i < lines.length) {
443
+ // Check if this line and the next look like a table
444
+ if (i + 1 < lines.length &&
445
+ lines[i].trim().startsWith('|') && lines[i].trim().endsWith('|') &&
446
+ lines[i + 1].trim().match(/^\|[\s\-:|]+\|$/)) {
447
+ // Parse header row
448
+ const headerCells = lines[i].trim().split('|').filter(c => c.trim() !== '');
449
+ result.push('<table>');
450
+ result.push('<thead><tr>');
451
+ for (const cell of headerCells) {
452
+ result.push(`<th>${cell.trim()}</th>`);
453
+ }
454
+ result.push('</tr></thead>');
455
+ result.push('<tbody>');
456
+ // Skip separator row
457
+ i += 2;
458
+ // Parse data rows
459
+ while (i < lines.length && lines[i].trim().startsWith('|') && lines[i].trim().endsWith('|')) {
460
+ const cells = lines[i].trim().split('|').filter(c => c.trim() !== '');
461
+ result.push('<tr>');
462
+ for (const cell of cells) {
463
+ result.push(`<td>${cell.trim()}</td>`);
464
+ }
465
+ result.push('</tr>');
466
+ i++;
467
+ }
468
+ result.push('</tbody>');
469
+ result.push('</table>');
470
+ }
471
+ else {
472
+ result.push(lines[i]);
473
+ i++;
474
+ }
475
+ }
476
+ return result.join('\n');
477
+ }
478
+ function processLinks(html) {
479
+ return html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, href) => {
480
+ return `<a href="${href}">${text}</a>`;
481
+ });
482
+ }
483
+ function processImages(html) {
484
+ return html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, src) => {
485
+ return `<img src="${src}" alt="${alt}" loading="lazy">`;
486
+ });
487
+ }
488
+ function processLists(html) {
489
+ return html
490
+ .replace(/^- \[([ x])\]\s+(.+)/gm, (_match, checked, text) => {
491
+ const isChecked = checked === 'x';
492
+ return `<li class="task"><input type="checkbox" ${isChecked ? 'checked' : ''} disabled>${text}</li>`;
493
+ })
494
+ .replace(/^[-*+]\s+(.+)/gm, '<li>$1</li>')
495
+ .replace(/^\d+\.\s+(.+)/gm, '<li>$1</li>')
496
+ .replace(/(<li>.*<\/li>\n?)+/g, (match) => {
497
+ if (match.includes('class="task"')) {
498
+ return `<ul class="task-list">${match}</ul>`;
499
+ }
500
+ return `<ul>${match}</ul>`;
501
+ });
502
+ }
503
+ function processBlockquotes(html) {
504
+ const lines = html.split('\n');
505
+ const result = [];
506
+ let inBlockquote = false;
507
+ let depth = 0;
508
+ for (const line of lines) {
509
+ const match = line.match(/^(\s*)>\s?(.*)/);
510
+ if (match) {
511
+ const indent = match[1].length;
512
+ const content = match[2];
513
+ const newDepth = Math.floor(indent / 2) + 1;
514
+ if (!inBlockquote) {
515
+ for (let i = 0; i < newDepth; i++) {
516
+ result.push('<blockquote>');
517
+ }
518
+ depth = newDepth;
519
+ inBlockquote = true;
520
+ }
521
+ else if (newDepth > depth) {
522
+ for (let i = depth; i < newDepth; i++) {
523
+ result.push('<blockquote>');
524
+ }
525
+ depth = newDepth;
526
+ }
527
+ else if (newDepth < depth) {
528
+ for (let i = depth; i > newDepth; i--) {
529
+ result.push('</blockquote>');
530
+ }
531
+ depth = newDepth;
532
+ }
533
+ result.push(content || '<br>');
534
+ }
535
+ else {
536
+ if (inBlockquote) {
537
+ for (let i = depth; i > 0; i--) {
538
+ result.push('</blockquote>');
539
+ }
540
+ inBlockquote = false;
541
+ depth = 0;
542
+ }
543
+ result.push(line);
544
+ }
545
+ }
546
+ if (inBlockquote) {
547
+ for (let i = depth; i > 0; i--) {
548
+ result.push('</blockquote>');
549
+ }
550
+ }
551
+ return result.join('\n');
552
+ }
553
+ function processHorizontalRules(html) {
554
+ return html.replace(/^([-*_])\s*\1\s*\1[\s-]*$/gm, '<hr>');
555
+ }
556
+ function processEmphasis(html) {
557
+ return html
558
+ .replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
559
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
560
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
561
+ .replace(/~~(.+?)~~/g, '<del>$1</del>')
562
+ .replace(/`([^`]+)`/g, (_match, code) => `<code>${escapeHtml(code)}</code>`);
563
+ }
564
+ function processParagraphs(html, breaks) {
565
+ // Split on pre blocks to avoid processing code content
566
+ const parts = html.split(/(<pre[\s\S]*?<\/pre>)/);
567
+ return parts.map((part) => {
568
+ // Don't process content inside pre tags
569
+ if (part.startsWith('<pre'))
570
+ return part;
571
+ const paragraphs = part.split('\n\n');
572
+ return paragraphs
573
+ .map((p) => {
574
+ p = p.trim();
575
+ if (!p)
576
+ return '';
577
+ if (p.startsWith('<h') || p.startsWith('<ul') || p.startsWith('<ol') ||
578
+ p.startsWith('<pre') || p.startsWith('<blockquote') || p.startsWith('<table') ||
579
+ p.startsWith('<hr')) {
580
+ return p;
581
+ }
582
+ p = p.replace(/\n/g, breaks ? '<br>' : ' ');
583
+ return `<p>${p}</p>`;
584
+ })
585
+ .join('\n');
586
+ }).join('');
587
+ }
588
+ function scanRouteFiles(dir) {
589
+ const files = [];
590
+ const extensions = new Set(['tsx', 'ts', 'jsx', 'js', 'md', 'mdx']);
591
+ function walk(currentDir) {
592
+ let entries;
593
+ try {
594
+ entries = readdirSync(currentDir);
595
+ }
596
+ catch {
597
+ return;
598
+ }
599
+ for (const entry of entries) {
600
+ const fullPath = join(currentDir, entry);
601
+ const stat = statSync(fullPath);
602
+ if (stat.isDirectory()) {
603
+ walk(fullPath);
604
+ }
605
+ else {
606
+ const ext = entry.split('.').pop() ?? '';
607
+ if (extensions.has(ext)) {
608
+ files.push(fullPath);
609
+ }
610
+ }
611
+ }
612
+ }
613
+ walk(dir);
614
+ return files;
615
+ }
616
+ function generateRoutesCode(files, routeDir) {
617
+ const routeEntries = [];
618
+ for (const file of files) {
619
+ const relativePath = relative(routeDir, file).replace(/\\/g, '/');
620
+ const ext = file.split('.').pop() ?? '';
621
+ const isMarkdown = ext === 'md' || ext === 'mdx';
622
+ // Skip special files
623
+ if (relativePath.includes('_layout') || relativePath.includes('_error') || relativePath.includes('_loading')) {
624
+ continue;
625
+ }
626
+ // Skip API routes
627
+ if (relativePath.startsWith('_api/') || relativePath.includes('/_api/')) {
628
+ continue;
629
+ }
630
+ let routePath = relativePath
631
+ .replace(/\.(tsx|ts|jsx|js|md|mdx)$/, '')
632
+ .replace(/(^|\/)index$/, '$1')
633
+ .replace(/\[\.\.\.(\w+)\]/g, ':$1*')
634
+ .replace(/\[([^\]]+)\]/g, ':$1');
635
+ if (routePath === '' || routePath === '/') {
636
+ routePath = '/';
637
+ }
638
+ else {
639
+ routePath = '/' + routePath;
640
+ }
641
+ const importPath = file.replace(/\\/g, '/');
642
+ if (isMarkdown) {
643
+ routeEntries.push(` { path: ${JSON.stringify(routePath)}, component: () => import(${JSON.stringify(importPath)}), isMarkdown: true }`);
644
+ }
645
+ else {
646
+ routeEntries.push(` { path: ${JSON.stringify(routePath)}, component: () => import(${JSON.stringify(importPath)}) }`);
647
+ }
648
+ }
649
+ return `export const routes = [\n${routeEntries.join(',\n')}\n];`;
650
+ }
@@ -0,0 +1,13 @@
1
+ export const DEFAULT_CONFIG = {
2
+ mode: 'hybrid',
3
+ routeDir: 'src/routes',
4
+ outputDir: 'dist',
5
+ jsx: 'automatic',
6
+ markdown: {
7
+ gfm: true,
8
+ breaks: false,
9
+ html: true,
10
+ tables: true,
11
+ },
12
+ mdx: {},
13
+ };