tkeron 4.2.0 → 4.3.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/README.md CHANGED
@@ -19,6 +19,7 @@ bun install -g tkeron
19
19
  - TypeScript → vanilla JavaScript
20
20
  - HTML components (`.com.html`) - inline at build time
21
21
  - TypeScript components (`.com.ts`) - dynamic generation
22
+ - Markdown components (`.com.md`) - write content in Markdown, rendered to HTML at build time
22
23
  - Pre-rendering (`.pre.ts`) - DOM manipulation at build time
23
24
  - Dev server with hot reload
24
25
  - Zero config
@@ -72,6 +73,7 @@ document.getElementById("quote").textContent = data.content;
72
73
  - **[Getting Started](./docs/getting-started.md)** - Installation and first steps
73
74
  - **[HTML Components](./docs/components-html.md)** - Reusable markup with `.com.html`
74
75
  - **[TypeScript Components](./docs/components-typescript.md)** - Dynamic components with `.com.ts`
76
+ - **[Markdown Components](./docs/components-markdown.md)** - Content in Markdown with `.com.md`
75
77
  - **[Pre-rendering](./docs/pre-rendering.md)** - Build-time HTML transformation
76
78
  - **[CLI Reference](./docs/cli-reference.md)** - All commands and options
77
79
  - **[Best Practices](./docs/best-practices.md)** - Patterns, limits, and anti-patterns
package/bun.lock CHANGED
@@ -30,11 +30,11 @@
30
30
 
31
31
  "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
32
32
 
33
- "@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
33
+ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
34
34
 
35
35
  "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
36
36
 
37
- "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
37
+ "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
38
38
 
39
39
  "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
40
40
 
@@ -110,7 +110,7 @@
110
110
 
111
111
  "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
112
112
 
113
- "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
113
+ "hono": ["hono@4.12.1", "", {}, "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw=="],
114
114
 
115
115
  "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
116
116
 
@@ -166,7 +166,7 @@
166
166
 
167
167
  "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
168
168
 
169
- "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
169
+ "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
170
170
 
171
171
  "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
172
172
 
@@ -204,7 +204,7 @@
204
204
 
205
205
  "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
206
206
 
207
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
207
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
208
208
 
209
209
  "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
210
210
 
package/changelog.md CHANGED
@@ -1,3 +1,36 @@
1
+ # v4.3.0
2
+
3
+ ## Markdown Components
4
+
5
+ ### New Features
6
+
7
+ - **Added**: `.com.md` component type — write components in Markdown, rendered to HTML at build time using Bun's native `Bun.markdown.html()` API with full GFM support (headings, bold, italic, strikethrough, lists, tables, blockquotes, links, images, code)
8
+ - **Added**: `src/processComMd.ts` — new processor following the same pattern as `processCom` and `processComTs`
9
+ - Circular dependency detection
10
+ - Max nesting depth (50 levels)
11
+ - Component resolution: local directory first, then root
12
+ - Returns `true` when changes were made (drives the build loop)
13
+
14
+ ### Breaking Changes (Build Behavior)
15
+
16
+ - **Changed**: Component resolution priority is now enforced: `.com.ts` > `.com.html` > `.com.md`
17
+ - Previously the loop ran `processCom` (HTML) before `processComTs` (TS), giving `.com.html` effective priority over `.com.ts` when both existed for the same tag
18
+ - Now `.com.ts` is resolved first as intended
19
+ - **Impact**: If you had both a `.com.ts` and `.com.html` for the same component name and were relying on the `.com.html` being used, the `.com.ts` will now win
20
+
21
+ ### Documentation
22
+
23
+ - **Added**: `docs/components-markdown.md` — full documentation for `.com.md` components including GFM features, naming rules, priority rules, and examples
24
+ - **Updated**: `README.md` — `.com.md` added to features list and documentation links
25
+ - **Updated**: `mcp-server.ts` — `tkeron://components-markdown` resource registered
26
+
27
+ ### Tests
28
+
29
+ - **Added**: `tests/processComMd.test.ts` — 23 tests covering all behaviors
30
+ - **Updated**: `tests/examples.test.ts` — updated `with_com_mixed_priority` tests to reflect correct `.com.ts` > `.com.html` priority
31
+
32
+ ---
33
+
1
34
  # v4.2.0
2
35
 
3
36
  ## Developer Experience
@@ -0,0 +1,322 @@
1
+ # Markdown Components
2
+
3
+ Markdown components (`.com.md` files) let you write reusable content in Markdown that gets converted to HTML and inlined at build time. Powered by Bun's built-in Markdown parser with full GitHub Flavored Markdown (GFM) support.
4
+
5
+ ## Quick Start
6
+
7
+ Create a file named with `.com.md` extension:
8
+
9
+ ```markdown
10
+ <!-- hero-text.com.md -->
11
+
12
+ # Welcome to My Site
13
+
14
+ This is a **great** place to start.
15
+
16
+ - Fast
17
+ - Simple
18
+ - Powerful
19
+ ```
20
+
21
+ Use it with a matching custom element:
22
+
23
+ ```html
24
+ <!-- index.html -->
25
+ <hero-text></hero-text>
26
+ ```
27
+
28
+ Build, and the custom element is replaced with the rendered HTML.
29
+
30
+ ## How They Work
31
+
32
+ ### 1. Component Naming
33
+
34
+ Component filenames must:
35
+
36
+ - End with `.com.md`
37
+ - Match the custom element name (without `.com.md`)
38
+ - Use lowercase with hyphens
39
+
40
+ **Examples:**
41
+
42
+ | Filename | Custom Element | Valid? |
43
+ | ------------------- | -------------- | ----------------- |
44
+ | `hero-text.com.md` | `<hero-text>` | ✅ Yes |
45
+ | `about-info.com.md` | `<about-info>` | ✅ Yes |
46
+ | `intro.com.md` | `<intro>` | ❌ No (no hyphen) |
47
+
48
+ ### 2. Component Resolution
49
+
50
+ Tkeron looks for components in this order:
51
+
52
+ 1. **Same directory** as the file using it
53
+ 2. **Root directory** (`websrc/`)
54
+
55
+ **Example structure:**
56
+
57
+ ```
58
+ websrc/
59
+ ├── index.html
60
+ ├── site-intro.com.md # Available to all files
61
+ ├── blog/
62
+ │ ├── post.html
63
+ │ └── post-footer.com.md # Available only to blog/post.html
64
+ ```
65
+
66
+ In `blog/post.html`:
67
+
68
+ - `<post-footer>` → Finds `blog/post-footer.com.md` first
69
+ - `<site-intro>` → Falls back to `websrc/site-intro.com.md`
70
+
71
+ ### 3. Component Priority
72
+
73
+ When multiple component types exist for the same name, Tkeron uses this priority:
74
+
75
+ 1. **`.com.ts`** (TypeScript components — highest priority)
76
+ 2. **`.com.html`** (HTML components)
77
+ 3. **`.com.md`** (Markdown components — lowest priority)
78
+
79
+ If both `hero-text.com.html` and `hero-text.com.md` exist, the `.com.html` version is used and `.com.md` is ignored.
80
+
81
+ ### 4. Build Process
82
+
83
+ ```
84
+ Before Build (websrc/index.html):
85
+ <hero-text></hero-text>
86
+
87
+ After Build (web/index.html):
88
+ <h1>Welcome</h1>
89
+ <p>Some <strong>bold</strong> text</p>
90
+ ```
91
+
92
+ The `.com.md` file is NOT copied to output — only its rendered HTML is inlined.
93
+
94
+ ## GFM Features
95
+
96
+ Markdown components support all GitHub Flavored Markdown extensions out of the box:
97
+
98
+ ### Headings
99
+
100
+ ```markdown
101
+ # Heading 1
102
+
103
+ ## Heading 2
104
+
105
+ ### Heading 3
106
+ ```
107
+
108
+ ### Bold, Italic, Strikethrough
109
+
110
+ ```markdown
111
+ **bold text**
112
+ _italic text_
113
+ ~~strikethrough~~
114
+ ```
115
+
116
+ ### Lists
117
+
118
+ ```markdown
119
+ - Unordered item 1
120
+ - Unordered item 2
121
+
122
+ 1. Ordered item 1
123
+ 2. Ordered item 2
124
+ ```
125
+
126
+ ### Links and Images
127
+
128
+ ```markdown
129
+ [Link text](https://example.com)
130
+ ![Alt text](image.jpg)
131
+ ```
132
+
133
+ ### Tables
134
+
135
+ ```markdown
136
+ | Feature | Status |
137
+ | -------- | ------ |
138
+ | Tables | ✅ |
139
+ | Lists | ✅ |
140
+ | Headings | ✅ |
141
+ ```
142
+
143
+ ### Blockquotes
144
+
145
+ ```markdown
146
+ > This is a blockquote
147
+ > with multiple lines
148
+ ```
149
+
150
+ ### Code
151
+
152
+ ````markdown
153
+ Inline `code` and fenced blocks:
154
+
155
+ ```js
156
+ console.log("hello");
157
+ ```
158
+ ````
159
+
160
+ ## Basic Examples
161
+
162
+ ### About Section
163
+
164
+ ```markdown
165
+ <!-- about-section.com.md -->
166
+
167
+ ## About Us
168
+
169
+ We are a **small team** building great tools.
170
+
171
+ - Open source
172
+ - Community driven
173
+ - Built with love
174
+ ```
175
+
176
+ **Usage:**
177
+
178
+ ```html
179
+ <about-section></about-section>
180
+ ```
181
+
182
+ ### Feature List
183
+
184
+ ```markdown
185
+ <!-- feature-list.com.md -->
186
+
187
+ ## Features
188
+
189
+ | Feature | Description |
190
+ | ----------- | ------------------------- |
191
+ | Fast builds | Sub-second build times |
192
+ | Zero config | Works out of the box |
193
+ | Components | Reusable HTML, TS, and MD |
194
+ ```
195
+
196
+ ### FAQ Section
197
+
198
+ ```markdown
199
+ <!-- faq-section.com.md -->
200
+
201
+ ## Frequently Asked Questions
202
+
203
+ ### How do I install?
204
+
205
+ Run `bun add -g tkeron` to install globally.
206
+
207
+ ### How do I build?
208
+
209
+ Run `tk build` in your project directory.
210
+
211
+ ### Where is the output?
212
+
213
+ Built files go to the `web/` directory.
214
+ ```
215
+
216
+ ## Nested Components
217
+
218
+ Markdown components can include other components using HTML custom elements inside the Markdown:
219
+
220
+ ```markdown
221
+ <!-- page-content.com.md -->
222
+
223
+ # Main Content
224
+
225
+ Here is some introductory text.
226
+
227
+ <feature-list></feature-list>
228
+
229
+ And here is the conclusion.
230
+ ```
231
+
232
+ The nested `<feature-list>` will be resolved — whether it's a `.com.ts`, `.com.html`, or `.com.md` component.
233
+
234
+ ## When to Use Markdown Components
235
+
236
+ ### ✅ Perfect For
237
+
238
+ - **Content-heavy sections**: About pages, documentation, FAQs
239
+ - **Blog content**: Static blog posts or content blocks
240
+ - **Feature descriptions**: Lists of features with formatting
241
+ - **Text-heavy UI**: Any section where the content is primarily text
242
+
243
+ ### ❌ Not Ideal For
244
+
245
+ - **Dynamic content**: Use [TypeScript Components](./components-typescript.md) instead
246
+ - **Complex layouts**: Use [HTML Components](./components-html.md) for precise HTML structure
247
+ - **Interactive elements**: Markdown produces static HTML only
248
+
249
+ ## Limitations
250
+
251
+ ### ❌ No Dynamic Content
252
+
253
+ Markdown components are **static**. They can't read attributes or use logic. For dynamic content, use [TypeScript Components](./components-typescript.md).
254
+
255
+ ### ❌ No Circular Dependencies
256
+
257
+ Components can't include themselves directly or indirectly. Build will fail with a clear error message.
258
+
259
+ ### Maximum Nesting Depth
260
+
261
+ Components can be nested up to **50 levels** deep.
262
+
263
+ ## Best Practices
264
+
265
+ ### ✅ Use for Content Sections
266
+
267
+ ```markdown
268
+ <!-- team-info.com.md -->
269
+
270
+ ## Our Team
271
+
272
+ We have **10+ engineers** working on this project.
273
+
274
+ - Backend: 5 engineers
275
+ - Frontend: 3 engineers
276
+ - DevOps: 2 engineers
277
+ ```
278
+
279
+ ### ✅ Name Descriptively
280
+
281
+ ```
282
+ Good:
283
+ - about-section.com.md
284
+ - faq-content.com.md
285
+ - feature-overview.com.md
286
+
287
+ Bad:
288
+ - text1.com.md
289
+ - stuff.com.md
290
+ ```
291
+
292
+ ### ✅ Combine with Other Component Types
293
+
294
+ Use Markdown for content, HTML for layout, and TypeScript for dynamic parts:
295
+
296
+ ```
297
+ websrc/
298
+ ├── page-layout.com.html # Layout structure
299
+ ├── hero-content.com.md # Content in Markdown
300
+ ├── user-greeting.com.ts # Dynamic greeting
301
+ ├── index.html
302
+ ```
303
+
304
+ ```html
305
+ <!-- index.html -->
306
+ <page-layout></page-layout>
307
+ ```
308
+
309
+ ```html
310
+ <!-- page-layout.com.html -->
311
+ <div class="page">
312
+ <user-greeting></user-greeting>
313
+ <hero-content></hero-content>
314
+ </div>
315
+ ```
316
+
317
+ ## Next Steps
318
+
319
+ - For precise HTML structure, see [**HTML Components**](./components-html.md)
320
+ - For dynamic components with logic, see [**TypeScript Components**](./components-typescript.md)
321
+ - For build-time transformations, see [**Pre-rendering**](./pre-rendering.md)
322
+ - For patterns and tips, see [**Best Practices**](./best-practices.md)
package/mcp-server.ts CHANGED
@@ -53,6 +53,12 @@ const DOCS = [
53
53
  description: "Build dynamic components with .com.ts files",
54
54
  file: "components-typescript.md",
55
55
  },
56
+ {
57
+ uri: "tkeron://components-markdown",
58
+ name: "Markdown Components",
59
+ description: "Create components with Markdown using .com.md files",
60
+ file: "components-markdown.md",
61
+ },
56
62
  {
57
63
  uri: "tkeron://pre-rendering",
58
64
  name: "Pre-rendering",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tkeron",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "CLI tool for backend-driven frontend development with TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/build.ts CHANGED
@@ -3,6 +3,7 @@ import { buildDir } from "./buildDir";
3
3
  import { processPre } from "./processPre";
4
4
  import { processCom } from "./processCom";
5
5
  import { processComTs } from "./processComTs";
6
+ import { processComMd } from "./processComMd";
6
7
  import { rm, exists, mkdir, cp } from "fs/promises";
7
8
  import type { Logger } from "@tkeron/tools";
8
9
  import { silentLogger } from "@tkeron/tools";
@@ -46,10 +47,11 @@ export const build = async (options: BuildOptions) => {
46
47
 
47
48
  const MAX_ITERATIONS = 10;
48
49
  for (let i = 0; i < MAX_ITERATIONS; i++) {
49
- const htmlChanged = await processCom(tempDir, { logger: log });
50
50
  const tsChanged = await processComTs(tempDir, { logger: log });
51
+ const htmlChanged = await processCom(tempDir, { logger: log });
52
+ const mdChanged = await processComMd(tempDir, { logger: log });
51
53
 
52
- if (!htmlChanged && !tsChanged) {
54
+ if (!tsChanged && !htmlChanged && !mdChanged) {
53
55
  break;
54
56
  }
55
57
  }
@@ -0,0 +1,197 @@
1
+ import { parseHTML } from "@tkeron/html-parser";
2
+ import { getFilePaths } from "@tkeron/tools";
3
+ import { join, dirname } from "path";
4
+ import type { Logger } from "@tkeron/tools";
5
+ import { silentLogger } from "@tkeron/tools";
6
+
7
+ const DOCTYPE = "<!doctype html>\n";
8
+
9
+ const ensureHtmlDocument = (html: string): string => {
10
+ const hasHtmlTag = /<\s*html[\s>]/i.test(html);
11
+ if (hasHtmlTag) return html;
12
+ return `${DOCTYPE}<html><head></head><body>${html}</body></html>`;
13
+ };
14
+
15
+ export interface ProcessComMdOptions {
16
+ logger?: Logger;
17
+ }
18
+
19
+ export const processComMd = async (
20
+ tempDir: string,
21
+ options: ProcessComMdOptions = {},
22
+ ): Promise<boolean> => {
23
+ const log = options.logger || silentLogger;
24
+
25
+ if (!tempDir || typeof tempDir !== "string") {
26
+ log.error(`\n❌ Error: Invalid tempDir provided for processComMd.`);
27
+ return false;
28
+ }
29
+
30
+ const htmlFiles = getFilePaths(tempDir, "**/*.html", true).filter(
31
+ (p) => !p.endsWith(".com.html"),
32
+ );
33
+
34
+ let hasChanges = false;
35
+
36
+ for (const htmlFile of htmlFiles) {
37
+ const htmlContent = await Bun.file(htmlFile).text();
38
+ const document = parseHTML(ensureHtmlDocument(htmlContent));
39
+
40
+ const htmlElement =
41
+ document.querySelector("html") || document.documentElement;
42
+ if (htmlElement) {
43
+ const changed = await processComponents(
44
+ htmlElement,
45
+ dirname(htmlFile),
46
+ tempDir,
47
+ [],
48
+ 0,
49
+ log,
50
+ );
51
+ hasChanges = hasChanges || changed;
52
+ }
53
+
54
+ const output =
55
+ DOCTYPE +
56
+ (htmlElement?.outerHTML || document.documentElement?.outerHTML || "");
57
+ await Bun.write(htmlFile, output);
58
+ }
59
+
60
+ return hasChanges;
61
+ };
62
+
63
+ const processComponents = async (
64
+ element: any,
65
+ currentDir: string,
66
+ rootDir: string,
67
+ componentStack: string[],
68
+ depth: number = 0,
69
+ log: Logger = silentLogger,
70
+ ): Promise<boolean> => {
71
+ let hasChanges = false;
72
+ const MAX_DEPTH = 50;
73
+ if (depth > MAX_DEPTH) {
74
+ log.warn(`Maximum component nesting depth (${MAX_DEPTH}) reached`);
75
+ return hasChanges;
76
+ }
77
+
78
+ const children = Array.from(element.children || []);
79
+
80
+ for (const child of children) {
81
+ const tagName = (child as any).tagName?.toLowerCase();
82
+
83
+ if (tagName) {
84
+ if (!tagName.includes("-")) {
85
+ const localPath = join(currentDir, `${tagName}.com.md`);
86
+ const rootPath = join(rootDir, `${tagName}.com.md`);
87
+ if (
88
+ (await Bun.file(localPath).exists()) ||
89
+ (await Bun.file(rootPath).exists())
90
+ ) {
91
+ log.error(
92
+ `\n❌ Error: Component name '${tagName}' must contain at least one hyphen.`,
93
+ );
94
+ log.error(`\n💡 Custom elements require hyphens to be valid HTML.`);
95
+ log.error(
96
+ ` Rename '${tagName}.com.md' to 'tk-${tagName}.com.md' or similar.`,
97
+ );
98
+ log.error(
99
+ ` File: ${(await Bun.file(localPath).exists()) ? localPath : rootPath}\n`,
100
+ );
101
+ throw new Error(
102
+ `Component name '${tagName}' must contain at least one hyphen`,
103
+ );
104
+ }
105
+ } else {
106
+ const localPath = join(currentDir, `${tagName}.com.md`);
107
+ const rootPath = join(rootDir, `${tagName}.com.md`);
108
+
109
+ let componentPath: string | null = null;
110
+
111
+ if (await Bun.file(localPath).exists()) {
112
+ componentPath = localPath;
113
+ } else if (await Bun.file(rootPath).exists()) {
114
+ componentPath = rootPath;
115
+ }
116
+
117
+ if (componentPath) {
118
+ hasChanges = true;
119
+
120
+ if (componentStack.includes(tagName)) {
121
+ const chain = [...componentStack, tagName].join(" -> ");
122
+ log.error(`\n❌ Error: Circular component dependency detected.`);
123
+ log.error(`\n💡 Component chain: ${chain}`);
124
+ log.error(
125
+ `\n Components cannot include themselves directly or indirectly.`,
126
+ );
127
+ log.error(` Check your .com.md files for circular references.\n`);
128
+ throw new Error(`Circular dependency: ${chain}`);
129
+ }
130
+
131
+ const mdContent = await Bun.file(componentPath).text();
132
+ const componentHtml = Bun.markdown.html(mdContent);
133
+
134
+ const tempDoc = parseHTML(
135
+ `<html><body><div id="__tkeron_component_root__">${componentHtml}</div></body></html>`,
136
+ );
137
+ const body = tempDoc.querySelector("body");
138
+ const tempContainer =
139
+ body?.querySelector("#__tkeron_component_root__") ||
140
+ body?.firstElementChild;
141
+
142
+ if (!tempContainer) continue;
143
+
144
+ const nodesToInsert = Array.from(
145
+ (tempContainer as any).childNodes || [],
146
+ ).map((node: any) => (node?.cloneNode ? node.cloneNode(true) : node));
147
+
148
+ const parent = (child as any).parentNode;
149
+
150
+ if (parent) {
151
+ if (nodesToInsert.length === 0) {
152
+ parent.removeChild(child);
153
+ } else if (nodesToInsert.length === 1) {
154
+ parent.replaceChild(nodesToInsert[0], child);
155
+ } else {
156
+ parent.replaceChild(nodesToInsert[0], child);
157
+ let refNode = nodesToInsert[0];
158
+ for (let i = 1; i < nodesToInsert.length; i++) {
159
+ parent.insertBefore(nodesToInsert[i], refNode.nextSibling);
160
+ refNode = nodesToInsert[i];
161
+ }
162
+ }
163
+ }
164
+
165
+ const nextCurrentDir = dirname(componentPath);
166
+ const nextStack = [...componentStack, tagName];
167
+
168
+ for (const node of nodesToInsert) {
169
+ if ((node as any).nodeType === 1) {
170
+ const nestedChanged = await processComponents(
171
+ node,
172
+ nextCurrentDir,
173
+ rootDir,
174
+ nextStack,
175
+ depth + 1,
176
+ log,
177
+ );
178
+ hasChanges = hasChanges || nestedChanged;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ const childChanged = await processComponents(
186
+ child,
187
+ currentDir,
188
+ rootDir,
189
+ componentStack,
190
+ depth,
191
+ log,
192
+ );
193
+ hasChanges = hasChanges || childChanged;
194
+ }
195
+
196
+ return hasChanges;
197
+ };