tkeron 5.3.0 → 6.0.1
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 +17 -16
- package/bun.lock +13 -196
- package/changelog.md +55 -0
- package/examples/init_sample/websrc/about.html +19 -0
- package/examples/init_sample/websrc/components/content/about-intro.com.md +7 -0
- package/examples/init_sample/websrc/components/layout/hero-section.com.html +88 -0
- package/examples/init_sample/websrc/components/layout/site-footer.com.html +20 -0
- package/examples/init_sample/websrc/components/layout/site-header.com.html +44 -0
- package/examples/init_sample/websrc/components/ui/counter-button.com.html +48 -0
- package/examples/init_sample/websrc/components/ui/crypto-prices-card.com.html +52 -0
- package/examples/init_sample/websrc/components/ui/html-components-card.com.html +10 -0
- package/examples/init_sample/websrc/{info-card.com.html → components/ui/info-card.com.html} +10 -6
- package/examples/init_sample/websrc/components/ui/markdown-card.com.html +21 -0
- package/examples/init_sample/websrc/{pre-render-card.com.html → components/ui/pre-render-card.com.html} +6 -3
- package/examples/init_sample/websrc/components/ui/quote-card.com.html +32 -0
- package/examples/init_sample/websrc/components/ui/styles-injector.com.ts +5 -0
- package/examples/init_sample/websrc/components/ui/ts-components-card.com.html +11 -0
- package/examples/init_sample/websrc/components/ui/user-badge.com.ts +30 -0
- package/examples/init_sample/websrc/docs.html +33 -0
- package/examples/init_sample/websrc/index.html +10 -209
- package/examples/init_sample/websrc/index.post.ts +70 -0
- package/examples/init_sample/websrc/index.pre.ts +10 -61
- package/examples/init_sample/websrc/index.ts +9 -7
- package/examples/init_sample/websrc/styles/global-styles.com.ts +5 -0
- package/examples/init_sample/websrc/styles/main.css +112 -0
- package/examples/init_sample/websrc/utils/api-service.ts +93 -0
- package/examples/with_global_styles/websrc/bad.html +23 -0
- package/examples/with_global_styles/websrc/global-styles.com.ts +5 -0
- package/examples/with_global_styles/websrc/good.html +22 -0
- package/examples/with_global_styles/websrc/index.html +25 -0
- package/examples/with_global_styles/websrc/styles.css +24 -0
- package/examples/with_style_dedup/websrc/tag-chip.com.html +15 -0
- package/examples/with_style_dedup/websrc/tag-chip.com.ts +2 -18
- package/index.ts +15 -0
- package/package.json +9 -13
- package/skills/tkeron/SKILL.md +287 -0
- package/skills/tkeron-components/SKILL.md +785 -0
- package/skills/tkeron-organization/SKILL.md +231 -0
- package/skills/tkeron-patterns/SKILL.md +587 -0
- package/skills/tkeron-testing/SKILL.md +194 -0
- package/skills/tkeron-troubleshooting/SKILL.md +263 -0
- package/src/build.ts +4 -6
- package/src/develop.ts +1 -2
- package/src/init.ts +1 -2
- package/src/skills.ts +139 -0
- package/src/skillsWrapper.ts +79 -0
- package/docs/mcp-server.md +0 -240
- package/examples/init_sample/websrc/api-service.ts +0 -98
- package/examples/init_sample/websrc/counter-card.com.html +0 -9
- package/examples/init_sample/websrc/favicon.ico +0 -0
- package/examples/init_sample/websrc/hero-section.com.html +0 -23
- package/examples/init_sample/websrc/html-components-card.com.html +0 -6
- package/examples/init_sample/websrc/ts-components-card.com.html +0 -6
- package/examples/init_sample/websrc/user-badge.com.ts +0 -17
- package/mcp-server.ts +0 -272
- package/src/cleanupOrphanedTempDirs.ts +0 -31
- package/src/promptUser.ts +0 -15
- package/src/setupSigintHandler.ts +0 -6
- /package/examples/init_sample/websrc/{profile.png → assets/profile.png} +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tkeron-testing
|
|
3
|
+
description: "Testing tkeron projects programmatically with getBuildResult() from the 'tkeron' package. Covers the full BuildResult API (dom, getContentAsString, type, size, fileHash), Bun test setup, recipes (assert components inlined, meta tags present, TS compiled), and the rules for fast, deterministic tests. Use this skill when writing or debugging tests for a tkeron project."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tkeron — Testing
|
|
7
|
+
|
|
8
|
+
Tkeron exposes `getBuildResult()` for programmatic testing of the build output. It runs a real build into a temporary directory, returns a structured map of every output file, and cleans up afterwards.
|
|
9
|
+
|
|
10
|
+
## Basic Setup (Bun test)
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { describe, it, expect, beforeAll } from "bun:test";
|
|
14
|
+
import { getBuildResult, type BuildResult } from "tkeron";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
|
|
17
|
+
describe("my project", () => {
|
|
18
|
+
const sourcePath = join(import.meta.dir, "websrc");
|
|
19
|
+
let result: BuildResult;
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
result = await getBuildResult(sourcePath);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("generates index.html", () => {
|
|
26
|
+
expect(result["index.html"]).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("has the correct title", () => {
|
|
30
|
+
const dom = result["index.html"]?.dom;
|
|
31
|
+
const title = dom?.querySelector("title");
|
|
32
|
+
expect(title).toBeDefined();
|
|
33
|
+
expect(title!.textContent).toBe("My Site");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### `getBuildResult(sourcePath, options?) => Promise<BuildResult>`
|
|
43
|
+
|
|
44
|
+
| Param | Type | Notes |
|
|
45
|
+
| ------------ | --------------------- | ---------------------------------------------------------- |
|
|
46
|
+
| `sourcePath` | `string` | Absolute path to the `websrc/` directory. |
|
|
47
|
+
| `options` | `{ logger?: Logger }` | Optional. Pass a custom logger; omit for silent (default). |
|
|
48
|
+
|
|
49
|
+
Returns a `BuildResult` — a record keyed by the relative output path (e.g. `"index.html"`, `"blog/post.html"`, `"styles/main.css"`).
|
|
50
|
+
|
|
51
|
+
### `FileInfo` — value of each entry
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface FileInfo {
|
|
55
|
+
fileName: string; // "index.html"
|
|
56
|
+
filePath: string; // "" for root, "blog" for blog/index.html
|
|
57
|
+
path: string; // "index.html" / "blog/post.html"
|
|
58
|
+
type: string; // MIME type (e.g. "text/html")
|
|
59
|
+
size: number; // bytes
|
|
60
|
+
fileHash: string; // sha256 of the output
|
|
61
|
+
getContentAsString?: () => string; // ONLY for text files
|
|
62
|
+
dom?: Document; // ONLY for .html (parsed by @tkeron/html-parser)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Text files (`html`, `css`, `js`, `json`, `txt`, `svg`, `xml`, `ts`, `md`, etc.) get `getContentAsString`. Only `.html` files get `dom`.
|
|
67
|
+
|
|
68
|
+
> **`dom` is from `@tkeron/html-parser`, not jsdom**. It supports `querySelector`, `querySelectorAll`, `textContent`, `getAttribute`, `innerHTML`, etc. Do NOT assume jsdom-only APIs (no `getComputedStyle`, no event dispatch, no `MutationObserver`).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Rules
|
|
73
|
+
|
|
74
|
+
1. **Build ONCE in `beforeAll()`**, NEVER inside each `it()` — a build can take seconds.
|
|
75
|
+
2. **Check existence BEFORE properties**: `expect(el).toBeDefined()` → then `el!.textContent`.
|
|
76
|
+
3. **Assert specific, bounded values**, not full output — whitespace/minification changes break brittle tests.
|
|
77
|
+
4. **Use `dom` for HTML assertions**, `getContentAsString()` for CSS/JS string searches.
|
|
78
|
+
5. **Use absolute paths** for `sourcePath` (`join(import.meta.dir, "websrc")`).
|
|
79
|
+
6. **Tests do not need to clean up** — `getBuildResult` removes its temp directory automatically.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Recipes
|
|
84
|
+
|
|
85
|
+
### Assert a component was inlined
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
it("inlines <site-header>", () => {
|
|
89
|
+
const html = result["index.html"]?.getContentAsString?.() ?? "";
|
|
90
|
+
expect(html).not.toContain("<site-header>");
|
|
91
|
+
expect(html).toContain('class="site-header"');
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Assert meta tags from `.pre.ts`
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
it("sets canonical from page-meta util", () => {
|
|
99
|
+
const dom = result["about.html"]?.dom;
|
|
100
|
+
const canonical = dom?.querySelector('link[rel="canonical"]');
|
|
101
|
+
expect(canonical?.getAttribute("href")).toBe("https://example.com/about");
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Assert TS was compiled and reference rewritten
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
it("rewrites .ts to .js in the page", () => {
|
|
109
|
+
const html = result["index.html"]?.getContentAsString?.() ?? "";
|
|
110
|
+
expect(html).toMatch(/src="\.\/index\.js"/);
|
|
111
|
+
expect(result["index.js"]).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Assert `.post.ts` ran on external links
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
it("post.ts adds rel/target to external links", () => {
|
|
119
|
+
const dom = result["index.html"]?.dom;
|
|
120
|
+
const link = dom?.querySelector('a[href^="http"]');
|
|
121
|
+
expect(link?.getAttribute("rel")).toBe("noopener noreferrer");
|
|
122
|
+
expect(link?.getAttribute("target")).toBe("_blank");
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Assert a `.com.ts` rendered each instance with its attributes
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
it("user-badge renders one card per instance", () => {
|
|
130
|
+
const dom = result["index.html"]?.dom;
|
|
131
|
+
const cards = dom?.querySelectorAll(".badge");
|
|
132
|
+
expect(cards?.length).toBe(3);
|
|
133
|
+
expect(cards?.[0].querySelector(".badge-name")?.textContent).toBe("Alice");
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Assert intermediate files do NOT appear in output
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
it("does not emit .com.* or .pre.ts files", () => {
|
|
141
|
+
const paths = Object.keys(result);
|
|
142
|
+
expect(paths.some((p) => p.endsWith(".com.html"))).toBe(false);
|
|
143
|
+
expect(paths.some((p) => p.endsWith(".com.ts"))).toBe(false);
|
|
144
|
+
expect(paths.some((p) => p.endsWith(".pre.ts"))).toBe(false);
|
|
145
|
+
expect(paths.some((p) => p.endsWith(".post.ts"))).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Assert sub-route page exists
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
it("emits blog/index.html as the /blog/ route", () => {
|
|
153
|
+
expect(result["blog/index.html"]).toBeDefined();
|
|
154
|
+
expect(result["blog/index.html"]?.filePath).toBe("blog");
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Assert hash/size constraints (output budget)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
it("CSS bundle stays under 50 KB", () => {
|
|
162
|
+
const css = result["styles/main.css"];
|
|
163
|
+
expect(css).toBeDefined();
|
|
164
|
+
expect(css!.size).toBeLessThan(50_000);
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Running Tests
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
bun test # all tests
|
|
174
|
+
bun test src/foo.test.ts # one file
|
|
175
|
+
bun test --watch # watch mode
|
|
176
|
+
NODE_ENV=production bun test # if your build depends on env
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`getBuildResult` honors the same `NODE_ENV` as a normal `tk build`, so you can test prod-only behavior (analytics scripts, dev banners) by setting it.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Common Test Failures
|
|
184
|
+
|
|
185
|
+
| Symptom | Likely cause |
|
|
186
|
+
| ------------------------------------- | ---------------------------------------------------------------------------- |
|
|
187
|
+
| `result["index.html"]` is `undefined` | Wrong `sourcePath`, or `index.html` was renamed/moved. |
|
|
188
|
+
| `dom.querySelector(...)` returns null | Selector doesn't match — remember the wrapper custom tag is gone. |
|
|
189
|
+
| `el.textContent` is empty | `.pre.ts`/`.post.ts` didn't run, or template wasn't loaded. |
|
|
190
|
+
| Test passes locally, fails in CI | Missing env var (`NODE_ENV`), or absolute paths assumed. |
|
|
191
|
+
| Slow test suite | Building inside `it()` — move to `beforeAll`. |
|
|
192
|
+
| Type error on `dom?.querySelector` | Import `Document` from your test setup or cast — the dom is parser-flavored. |
|
|
193
|
+
|
|
194
|
+
For build-time errors (the build itself fails), see `tkeron-troubleshooting`.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tkeron-troubleshooting
|
|
3
|
+
description: "Troubleshooting tkeron builds and the dev server: common errors with cause-and-fix (Bundle failed, Cannot resolve, missing hyphen, component not replaced, build hangs, websrc not found), dev server singleton recovery after a crash, debugging workflow (read web/, isolate the offending file, check iteration limits), and a quick diagnostic flowchart. Use this skill when a build fails, a component is not inlined, the dev server is down, or output looks wrong."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tkeron — Troubleshooting
|
|
7
|
+
|
|
8
|
+
## Diagnostic Flowchart
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Something is wrong
|
|
12
|
+
│
|
|
13
|
+
├─ Build fails with an error message? → "Common Build Errors" section
|
|
14
|
+
├─ Build succeeds but output is wrong? → "Output Looks Wrong" section
|
|
15
|
+
├─ Dev server is down / hot reload not working? → "Dev Server Recovery" section
|
|
16
|
+
├─ Test fails? → see tkeron-testing → "Common Test Failures"
|
|
17
|
+
└─ Type error in IDE only? → "IDE vs Build" section
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Common Build Errors
|
|
23
|
+
|
|
24
|
+
### `Bundle failed` / `Cannot resolve "./index.js"`
|
|
25
|
+
|
|
26
|
+
**Cause**: HTML references `.js` but the source is `.ts`.
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<!-- ❌ -->
|
|
30
|
+
<script src="index.js"></script>
|
|
31
|
+
|
|
32
|
+
<!-- ✅ -->
|
|
33
|
+
<script type="module" src="./index.ts"></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `Cannot resolve "/styles.css"`
|
|
37
|
+
|
|
38
|
+
**Cause**: absolute path in a `<link rel="stylesheet">`. Two problems: the path is absolute (Bun resolves from the source file, not the server root), and `<link rel="stylesheet">` is itself an anti-pattern in tkeron.
|
|
39
|
+
|
|
40
|
+
**Fix**: replace the `<link>` with a `<global-styles>` component that inlines the CSS at build time. See `tkeron-patterns` → "CSS via components".
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!-- ❌ BAD -->
|
|
44
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
45
|
+
<!-- ⚠️ Works but anti-pattern (extra request, no dedup) -->
|
|
46
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
47
|
+
<!-- ✅ GOOD — inlined, no extra request, dedup-friendly -->
|
|
48
|
+
<global-styles></global-styles>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `Component name must contain hyphen`
|
|
52
|
+
|
|
53
|
+
**Cause**: a `.com.html` / `.com.ts` / `.com.md` whose base name has no hyphen, or collides with a standard HTML tag.
|
|
54
|
+
|
|
55
|
+
Fix: rename to kebab-case with a hyphen: `card.com.html` → `info-card.com.html`. See `tkeron-organization` for naming rules.
|
|
56
|
+
|
|
57
|
+
### `Source directory websrc does not exist`
|
|
58
|
+
|
|
59
|
+
Fix: run `tk init .` to scaffold, or `mkdir websrc` and create at least an `index.html`.
|
|
60
|
+
|
|
61
|
+
### `Skill files already exist: ...` (from `tk skills`)
|
|
62
|
+
|
|
63
|
+
Fix: pass `--force` to overwrite, or delete the existing skill folder first.
|
|
64
|
+
|
|
65
|
+
### `Skills directory not found in tkeron installation`
|
|
66
|
+
|
|
67
|
+
**Cause**: corrupted or partial install of the `tkeron` package.
|
|
68
|
+
|
|
69
|
+
Fix: `bun add -g tkeron@latest` (or reinstall locally).
|
|
70
|
+
|
|
71
|
+
### Build hangs forever
|
|
72
|
+
|
|
73
|
+
**Cause**: `fetch()` without timeout in `.pre.ts` / `.post.ts` / `.com.ts`. The build event loop waits for the promise.
|
|
74
|
+
|
|
75
|
+
Fix: always wrap external fetches with `AbortController` + a 5 s timeout. See pattern in `tkeron-patterns`.
|
|
76
|
+
|
|
77
|
+
### `Maximum iterations reached` (or component loop never settles)
|
|
78
|
+
|
|
79
|
+
**Cause**: a `.com.ts` keeps emitting the same custom element it is processing, producing infinite expansion. Or two components reference each other circularly.
|
|
80
|
+
|
|
81
|
+
Fix:
|
|
82
|
+
|
|
83
|
+
1. Find the component whose `innerHTML` contains its own tag.
|
|
84
|
+
2. Break the cycle (emit raw HTML instead of the wrapper, or use a different tag).
|
|
85
|
+
3. Limit: 10 iterations, 50 nesting levels — exceeding either aborts the build.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Output Looks Wrong
|
|
90
|
+
|
|
91
|
+
### A component tag is still in `web/index.html`
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<!-- Final output still shows: -->
|
|
95
|
+
<user-card></user-card>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Causes (in order of frequency):
|
|
99
|
+
|
|
100
|
+
1. **File name mismatch**: tag is `<user-card>` but file is `userCard.com.html` or `usercard.com.html`. Tkeron is case-insensitive but expects matching kebab-case.
|
|
101
|
+
2. **Wrong extension**: file ends in `.html` instead of `.com.html`.
|
|
102
|
+
3. **File outside `websrc/`**: tkeron only scans `websrc/`.
|
|
103
|
+
4. **Typo**: `<user-cards>` vs `user-card.com.html`.
|
|
104
|
+
|
|
105
|
+
Verify:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
ls websrc/**/*.com.* | grep -i user-card
|
|
109
|
+
grep -rn "<user-card" websrc/
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### A `.com.ts` runs but `com.innerHTML` is empty in output
|
|
113
|
+
|
|
114
|
+
**Cause**: the `.com.ts` did not assign `com.innerHTML`. Only `com.innerHTML` survives — `textContent`, `appendChild`, `setAttribute` on `com` itself are all discarded.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// ❌ — discarded
|
|
118
|
+
com.textContent = "hi";
|
|
119
|
+
com.appendChild(document.createElement("span"));
|
|
120
|
+
|
|
121
|
+
// ✅ — preserved
|
|
122
|
+
com.innerHTML = `<span>hi</span>`;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### A `<script>` inside `com.innerHTML` is corrupted in output
|
|
126
|
+
|
|
127
|
+
**Cause**: the HTML parser re-serializes `innerHTML`, and `<`, `>`, `&` inside script JS get double-escaped.
|
|
128
|
+
|
|
129
|
+
Fix:
|
|
130
|
+
|
|
131
|
+
- Avoid comparison operators inside the inline script (`>=`, `<=`, `>`, `<`).
|
|
132
|
+
- Use `matchMedia('(min-width: Xpx)')` instead of `window.innerWidth >= X`.
|
|
133
|
+
- Move complex logic to a separate browser `.ts` file and only emit a `<script type="module" src="./x.ts"></script>` tag.
|
|
134
|
+
|
|
135
|
+
### Custom CSS selector targets the wrapper tag and matches nothing
|
|
136
|
+
|
|
137
|
+
**Cause**: the wrapper tag (`<my-section>`) disappears from the output — only the children of `innerHTML` remain.
|
|
138
|
+
|
|
139
|
+
Fix: write CSS targeting the class/element you emit **inside** `innerHTML`, never the custom tag. See `tkeron-components` → "wrapper tag disappears".
|
|
140
|
+
|
|
141
|
+
### `.pre.ts` ran but the page does not reflect it
|
|
142
|
+
|
|
143
|
+
Checklist:
|
|
144
|
+
|
|
145
|
+
1. Is the file named EXACTLY `<page>.pre.ts` next to `<page>.html`? Pairing is by file name.
|
|
146
|
+
2. Did the script throw silently? Run `tk build` and read the full stderr.
|
|
147
|
+
3. Are you querying nodes that don't exist yet because the component loop hasn't run? `.pre.ts` runs BEFORE components — query only what is in the source HTML.
|
|
148
|
+
|
|
149
|
+
### `.post.ts` ran but did not see component output
|
|
150
|
+
|
|
151
|
+
Same as above, but reversed: `.post.ts` runs AFTER the component loop, so you CAN query nodes emitted by components. If you can't, check the script name pairs the page, and that you didn't accidentally use `.pre.ts` instead.
|
|
152
|
+
|
|
153
|
+
### Assets disappeared from `web/`
|
|
154
|
+
|
|
155
|
+
Check:
|
|
156
|
+
|
|
157
|
+
1. File names contain spaces? Tkeron silently fails to copy them. Rename to kebab-case.
|
|
158
|
+
2. File is inside a `.com.*` or `.pre.ts` pair? Those are excluded from output by design.
|
|
159
|
+
3. File has the `.com.html` / `.com.ts` / `.com.md` / `.pre.ts` / `.post.ts` extension? Those are never copied.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Dev Server Recovery
|
|
164
|
+
|
|
165
|
+
`tk dev` is a **singleton process** per port. The first thing to check is whether it is even running.
|
|
166
|
+
|
|
167
|
+
### Check status
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
fuser 3000/tcp 2>/dev/null
|
|
171
|
+
# Empty output → not running
|
|
172
|
+
# Has output → running
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Recovery after a crash
|
|
176
|
+
|
|
177
|
+
`tk dev` can crash when an edit in `websrc/` introduces a compile or syntax error. Steps:
|
|
178
|
+
|
|
179
|
+
1. Confirm it's down: `fuser 3000/tcp 2>/dev/null` returns empty.
|
|
180
|
+
2. Read the terminal where `tk dev` was running — the last lines explain why it crashed.
|
|
181
|
+
3. Identify the offending file in `websrc/` (TS error, malformed component, broken import).
|
|
182
|
+
4. Fix the file.
|
|
183
|
+
5. Relaunch **in background**:
|
|
184
|
+
```bash
|
|
185
|
+
tk dev
|
|
186
|
+
```
|
|
187
|
+
(In agent sessions, this MUST be an async terminal — `tk dev` blocks indefinitely.)
|
|
188
|
+
6. Confirm it is up: `fuser 3000/tcp 2>/dev/null` returns output.
|
|
189
|
+
|
|
190
|
+
### Port already in use
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Error: listen EADDRINUSE :::3000
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Cause: another `tk dev` (or another process) holds port 3000.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
fuser -k 3000/tcp # kill the holder
|
|
200
|
+
# OR launch on a different port:
|
|
201
|
+
tk dev 3001
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Hot reload not firing
|
|
205
|
+
|
|
206
|
+
1. Browser tab too old (before the reload script was injected) → hard refresh once (`Ctrl+Shift+R`).
|
|
207
|
+
2. SSE connection blocked by a proxy/extension → check DevTools → Network → `/dev-reload` should be `text/event-stream`, status `200`, hanging open.
|
|
208
|
+
3. The watcher missed the file (rare on network volumes / Docker bind mounts) → restart `tk dev`.
|
|
209
|
+
|
|
210
|
+
### `tk dev` blocks the agent terminal
|
|
211
|
+
|
|
212
|
+
`tk dev` runs forever by design. In agent sessions:
|
|
213
|
+
|
|
214
|
+
- ALWAYS launch it as an async terminal (background mode).
|
|
215
|
+
- NEVER run it synchronously — it will time out and leave a zombie.
|
|
216
|
+
- Use `fuser 3000/tcp 2>/dev/null` as the readiness check, not a sleep.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Debugging Workflow
|
|
221
|
+
|
|
222
|
+
When something is wrong, follow this order:
|
|
223
|
+
|
|
224
|
+
1. **Reproduce with `tk build`** (not `tk dev`) — it's deterministic and gives full stderr.
|
|
225
|
+
2. **Read `web/<page>.html`** — does the component appear unprocessed? Are scripts referenced as `.js`?
|
|
226
|
+
3. **Isolate**: comment out custom elements one by one to find the offender.
|
|
227
|
+
4. **Strip to minimum**: replace the suspect `.com.ts` body with `com.innerHTML = "OK";` and rebuild — confirms whether the file is even loaded.
|
|
228
|
+
5. **Check naming**: file name vs tag name (kebab-case, hyphen, no collisions with standard HTML).
|
|
229
|
+
6. **Check pairing**: `.pre.ts` / `.post.ts` must share the base name with the `.html`.
|
|
230
|
+
7. **Check imports**: relative paths, no `.js` extensions for source TS files imported from build-time scripts.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## IDE vs Build
|
|
235
|
+
|
|
236
|
+
The IDE can show errors that the build does not (and vice versa):
|
|
237
|
+
|
|
238
|
+
| Symptom | What it means |
|
|
239
|
+
| --------------------------------------- | ---------------------------------------------------------------- |
|
|
240
|
+
| IDE: red squiggle on `com` | `tkeron.d.ts` missing or not in `tsconfig.json` `include`. |
|
|
241
|
+
| IDE: red squiggle on `document` in .pre | Same as above for the `*.pre.ts` declaration. |
|
|
242
|
+
| Build: passes but IDE shows type errors | Tkeron does NOT type-check. Run `bun x tsc --noEmit` to confirm. |
|
|
243
|
+
| Build: fails with a TS-looking error | It's a Bun **compile** error, not a type error — fix the syntax. |
|
|
244
|
+
|
|
245
|
+
Recommended pre-commit / CI step:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
bun x tsc --noEmit && tk build
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Last-Resort Reset
|
|
254
|
+
|
|
255
|
+
If the project is in a weird state (stale temp dirs, mismatched output):
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Remove the output and any leftover temp build dirs
|
|
259
|
+
rm -rf web/ .tktmp_*
|
|
260
|
+
tk build
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`getBuildResult` cleans its own temp dirs (`.tktmp_buildresult-*`), but a hard-killed build can leave them behind. They are always siblings of `websrc/`.
|
package/src/build.ts
CHANGED
|
@@ -8,11 +8,9 @@ import { processPost } from "./processPost";
|
|
|
8
8
|
import { deduplicateComStyles } from "./deduplicateComStyles";
|
|
9
9
|
import { rm, exists, mkdir, cp } from "fs/promises";
|
|
10
10
|
import type { Logger } from "@tkeron/tools";
|
|
11
|
-
import { silentLogger } from "@tkeron/tools";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
TEMP_DIR_PREFIX,
|
|
15
|
-
} from "./cleanupOrphanedTempDirs";
|
|
11
|
+
import { silentLogger, cleanupOrphanedTempDirs } from "@tkeron/tools";
|
|
12
|
+
|
|
13
|
+
export const TEMP_DIR_PREFIX = ".tktmp_build-";
|
|
16
14
|
|
|
17
15
|
export interface BuildOptions {
|
|
18
16
|
sourceDir?: string;
|
|
@@ -38,7 +36,7 @@ export const build = async (options: BuildOptions) => {
|
|
|
38
36
|
`${TEMP_DIR_PREFIX}${crypto.randomUUID()}`,
|
|
39
37
|
);
|
|
40
38
|
|
|
41
|
-
await cleanupOrphanedTempDirs(sourceParent, log);
|
|
39
|
+
await cleanupOrphanedTempDirs(sourceParent, TEMP_DIR_PREFIX, log);
|
|
42
40
|
|
|
43
41
|
try {
|
|
44
42
|
await mkdir(tempDir, { recursive: true });
|
package/src/develop.ts
CHANGED
|
@@ -2,8 +2,7 @@ import { resolve, dirname } from "path";
|
|
|
2
2
|
import { watch } from "fs";
|
|
3
3
|
import { build } from "./build";
|
|
4
4
|
import type { Logger } from "@tkeron/tools";
|
|
5
|
-
import { logger as defaultLogger } from "@tkeron/tools";
|
|
6
|
-
import { setupSigintHandler } from "./setupSigintHandler";
|
|
5
|
+
import { logger as defaultLogger, setupSigintHandler } from "@tkeron/tools";
|
|
7
6
|
|
|
8
7
|
export interface TkeronDevOptions {
|
|
9
8
|
outputDir?: string;
|
package/src/init.ts
CHANGED
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
} from "fs";
|
|
10
10
|
import { join, resolve, basename } from "path";
|
|
11
11
|
import type { Logger } from "@tkeron/tools";
|
|
12
|
-
import { silentLogger } from "@tkeron/tools";
|
|
13
|
-
import { promptUser } from "./promptUser";
|
|
12
|
+
import { silentLogger, promptUser } from "@tkeron/tools";
|
|
14
13
|
import { ensureTsconfig } from "./ensureTsconfig";
|
|
15
14
|
|
|
16
15
|
export interface InitOptions {
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
cpSync,
|
|
6
|
+
statSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "fs";
|
|
10
|
+
import { join, resolve } from "path";
|
|
11
|
+
import type { Logger } from "@tkeron/tools";
|
|
12
|
+
import {
|
|
13
|
+
silentLogger,
|
|
14
|
+
detectEnvironments,
|
|
15
|
+
addFrontmatterField,
|
|
16
|
+
} from "@tkeron/tools";
|
|
17
|
+
import type { Environment } from "@tkeron/tools";
|
|
18
|
+
|
|
19
|
+
export interface SkillsOptions {
|
|
20
|
+
target?: string;
|
|
21
|
+
force?: boolean;
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
logger?: Logger;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getTransform = (env: Environment) => {
|
|
27
|
+
if (env === "cursor") {
|
|
28
|
+
return (content: string) =>
|
|
29
|
+
addFrontmatterField(content, "alwaysApply", false);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const installToDir = (
|
|
35
|
+
skillNames: string[],
|
|
36
|
+
sourceDir: string,
|
|
37
|
+
targetDir: string,
|
|
38
|
+
transform: ((content: string) => string) | null,
|
|
39
|
+
force: boolean,
|
|
40
|
+
log: Logger,
|
|
41
|
+
) => {
|
|
42
|
+
const conflicts: string[] = [];
|
|
43
|
+
for (const name of skillNames) {
|
|
44
|
+
const dest = join(targetDir, name, "SKILL.md");
|
|
45
|
+
if (existsSync(dest)) {
|
|
46
|
+
conflicts.push(name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (conflicts.length > 0 && !force) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Skill files already exist: ${conflicts.join(", ")}. Use --force to overwrite.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
mkdirSync(targetDir, { recursive: true });
|
|
57
|
+
|
|
58
|
+
for (const name of skillNames) {
|
|
59
|
+
const src = join(sourceDir, name);
|
|
60
|
+
const dest = join(targetDir, name);
|
|
61
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
62
|
+
|
|
63
|
+
if (transform) {
|
|
64
|
+
const skillFile = join(dest, "SKILL.md");
|
|
65
|
+
if (existsSync(skillFile)) {
|
|
66
|
+
const content = readFileSync(skillFile, "utf-8");
|
|
67
|
+
writeFileSync(skillFile, transform(content), "utf-8");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
log.log(`✓ Installed skill: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const previewToDir = (skillNames: string[], targetDir: string, log: Logger) => {
|
|
76
|
+
for (const name of skillNames) {
|
|
77
|
+
const dest = join(targetDir, name, "SKILL.md");
|
|
78
|
+
log.log(`○ Would install: ${name} → ${dest}`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const skills = async (options: SkillsOptions = {}) => {
|
|
83
|
+
const {
|
|
84
|
+
target,
|
|
85
|
+
force = false,
|
|
86
|
+
dryRun = false,
|
|
87
|
+
logger: log = silentLogger,
|
|
88
|
+
} = options;
|
|
89
|
+
|
|
90
|
+
const sourceDir = join(import.meta.dir, "..", "skills");
|
|
91
|
+
|
|
92
|
+
if (!existsSync(sourceDir)) {
|
|
93
|
+
throw new Error("Skills directory not found in tkeron installation");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const skillNames = readdirSync(sourceDir).filter((name) =>
|
|
97
|
+
statSync(join(sourceDir, name)).isDirectory(),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (skillNames.length === 0) {
|
|
101
|
+
throw new Error("No skills available to install");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (target !== undefined) {
|
|
105
|
+
const targetDir = resolve(process.cwd(), target);
|
|
106
|
+
if (dryRun) {
|
|
107
|
+
previewToDir(skillNames, targetDir, log);
|
|
108
|
+
log.log(
|
|
109
|
+
`\n📋 ${skillNames.length} skill(s) would be installed in ${targetDir}`,
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
installToDir(skillNames, sourceDir, targetDir, null, force, log);
|
|
113
|
+
log.log(`\n✅ ${skillNames.length} skill(s) installed in ${targetDir}`);
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const detected = detectEnvironments(process.cwd());
|
|
119
|
+
|
|
120
|
+
if (detected.length === 0) {
|
|
121
|
+
throw new Error("NO_ENVIRONMENTS_DETECTED");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const { env, target: envTarget } of detected) {
|
|
125
|
+
const targetDir = resolve(process.cwd(), envTarget);
|
|
126
|
+
if (dryRun) {
|
|
127
|
+
previewToDir(skillNames, targetDir, log);
|
|
128
|
+
log.log(
|
|
129
|
+
`\n📋 ${skillNames.length} skill(s) would be installed for ${env} in ${targetDir}`,
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
const transform = getTransform(env);
|
|
133
|
+
installToDir(skillNames, sourceDir, targetDir, transform, force, log);
|
|
134
|
+
log.log(
|
|
135
|
+
`\n✅ ${skillNames.length} skill(s) installed for ${env} in ${targetDir}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|