@nativele/ts-dom 0.5.3 → 0.5.5
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/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/README.zh-TW.md +3 -3
- package/dist/{a11y-Bw-8ZIAK.mjs → a11y-CiEJj_e6.mjs} +16 -14
- package/dist/cli.mjs +163 -25
- package/dist/{lint-qEPgmmMa.mjs → lint-Did4w-NL.mjs} +2 -2
- package/dist/oxlint-plugin.mjs +2 -2
- package/dist/vite-plugin.mjs +5 -2
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/skills/README.md +33 -13
- package/skills/nativele-angular/SKILL.md +2 -4
- package/skills/nativele-architecture/SKILL.md +19 -9
- package/skills/nativele-core/SKILL.md +2 -2
- package/skills/nativele-integration/SKILL.md +7 -9
- package/skills/nativele-react/SKILL.md +11 -15
- package/skills/nativele-state-management/SKILL.md +4 -4
- package/skills/nativele-svelte/SKILL.md +9 -13
- package/skills/nativele-tailwind/SKILL.md +1 -1
- package/skills/nativele-tanstack-query/SKILL.md +1 -1
- package/skills/nativele-testing/SKILL.md +7 -7
- package/skills/nativele-vite/SKILL.md +6 -6
- package/skills/nativele-vue/SKILL.md +5 -5
- package/skills/nativele-zod/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,52 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
|
|
|
10
10
|
- Adding a lint rule is a minor change; lint rules are warnings or opt-in and do not count as breaking.
|
|
11
11
|
- Before every release run `npm run verify:pack` (`publint` and `attw`), `tsc`, the unit tests, the browser tests, `npm run smoke` and `npm run smoke:no-bundler`; all of them run in `.github/workflows/ci.yml`.
|
|
12
12
|
|
|
13
|
+
## 0.5.5
|
|
14
|
+
|
|
15
|
+
Skills are verified by agent trials and stand on their own: each Skill can be followed and is accurate without its example project, which stays in the repository only as a reference.
|
|
16
|
+
|
|
17
|
+
- A Skill is verified by its agent trials. An `example/` is an optional reference project from the trial and is no longer run in CI; Official means accepted through an agent trial and maintained by the maintainers. Each Skill's Versions table names the version of its last agent trial, and `test/skills.test.ts` keeps that row in line with the `nativele` range.
|
|
18
|
+
- "SKILL.md format" (in `skills.yml`) is a required check for `main`: it reports on every pull request, skips its steps when nothing it checks changed, and also covers the links in the READMEs, `llms.txt` and the guides.
|
|
19
|
+
- `skills/README.md` gives a fixed form for the agent trial record added to a Skill pull request.
|
|
20
|
+
- Every Skill is self-contained. The architecture Skill names the interfaces of the small components its example code uses. Across the Skills, an example project is mentioned only as a reference implementation, never as the source of a step or a fact.
|
|
21
|
+
|
|
22
|
+
## 0.5.4
|
|
23
|
+
|
|
24
|
+
The guides, the Skills and the tools agree with each other: wrong statements in the guides are fixed, facts the Skills repeated now live in the guides, and the tools report the traps the Skill trials hit instead of staying silent.
|
|
25
|
+
|
|
26
|
+
### New checks
|
|
27
|
+
|
|
28
|
+
- `ts-dom doctor` reports `typecheck-before-gen` (warning): a package.json script, with its `pre` script and the scripts it runs through `npm run` (or pnpm, yarn, bun), that runs `tsc`, `vue-tsc`, `svelte-check` or `ng build`/`serve`/`test` before any `ts-dom gen` or `ts-dom check` while `.gitignore` leaves out `*.html.ts`; on a clean checkout it fails with TS2307. A `tsc -p` whose config covers none of the scanned folders is skipped.
|
|
29
|
+
- `ts-dom doctor` reports `framework-template` (error): an `.html` in a scanned folder that a TypeScript file names as an Angular `templateUrl`. The `invalid-hole` and `no-importer` hints now also say that the folder may not be meant to be scanned.
|
|
30
|
+
- `formatter-whitespace` also reports generated files written with `preserve` while a package.json script or the Vite config asks for `--whitespace=collapse`, with the command to regenerate them.
|
|
31
|
+
- `vite-plugin-missing` also reports a `vitest.config.*` that replaces the Vite config for Vitest without the `tsDom()` plugin and without importing `./vite.config`.
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
|
|
35
|
+
- The Vite plugin warns (`dir-not-found`) when a `tsDom({ dirs })` entry does not exist under Vite's `root`, instead of skipping it silently.
|
|
36
|
+
- A generated `ts-dom-tags.ts` in a subfolder that is not itself scanned (left over after components moved) is an orphan: `gen` deletes it, and `gen --check`, `check --no-write` and `doctor` report it as `stale-generated`.
|
|
37
|
+
- A `tsc` error located in a generated `x.html.ts` or `ts-dom-tags.ts` now says to run `ts-dom gen` or delete the leftover file, not to change the TypeScript there.
|
|
38
|
+
- `ts-dom check` leaves out `tsc` errors inside the body of a class it already reported for a contract error (for example an implicit `any` on a `bind` callback's parameter next to a `{{ hole }}` that names nothing).
|
|
39
|
+
- `ts-dom --help` lists `--include`, `--exclude` and `--whitespace` under `check` and `doctor`, which accept them.
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
- `docs/components.md` gains:
|
|
44
|
+
- Light DOM slots and children managed by a framework;
|
|
45
|
+
- when a Light or Shadow component builds its content;
|
|
46
|
+
- the `props` and `"~emits"` members that framework declarations use;
|
|
47
|
+
- `EmitEvent` with an undeclared name;
|
|
48
|
+
- `ReadableStore`.
|
|
49
|
+
- Five framework Skills link to the slot section instead of repeating it.
|
|
50
|
+
- Corrected:
|
|
51
|
+
- the import map in `docs/design.md`;
|
|
52
|
+
- the generated files being "already in `.gitignore`" (a project adds them itself);
|
|
53
|
+
- duplicate and misplaced reordering notes;
|
|
54
|
+
- `llms.txt` lists every guide.
|
|
55
|
+
- The Skills agree on one build-script rule: a project whose type check is plain `tsc` runs `ts-dom check` before the bundler, and a framework type checker gets `ts-dom gen` in front. The README Skill tables show the right tiers.
|
|
56
|
+
- `test/skills.test.ts` checks the README Skill tables against each Skill's tier, and every link and anchor in the READMEs, `llms.txt`, the guides and the Skills.
|
|
57
|
+
- `docs/tooling.md`: the new checks; the bare specifier to map is `@nativele/ts-dom`; the Vite plugin generates when the build, the dev server or Vitest starts, never type-checks, prints rejected templates only in the terminal, and sends a full reload to every client; a run without `--whitespace` keeps each file's recorded mode; `check` runs `tsc` once per referenced project of a solution-style tsconfig.
|
|
58
|
+
|
|
13
59
|
## 0.5.3
|
|
14
60
|
|
|
15
61
|
How to lay out an app with several pages: a new architecture guide, an Official Skill that applies it, and a medium example app checked in CI. Building the example found a codegen bug with several scanned folders, fixed here.
|
package/README.md
CHANGED
|
@@ -63,18 +63,18 @@ npx ts-dom skills add nativele-core # copy it into .claude/skills/ (when the
|
|
|
63
63
|
| `nativele-core` | building components: the `ts-dom check` loop, templates, state, lists, data flow |
|
|
64
64
|
| `nativele-react` | Nativele components in React 19: props, events, children and slots, lifecycle, TSX types |
|
|
65
65
|
| `nativele-vue` | Nativele components in Vue 3: props, events, `v-model`, slots, `<KeepAlive>`, template types |
|
|
66
|
-
| `nativele-svelte` | Nativele components in Svelte 5: props, events, slots, template types |
|
|
66
|
+
| `nativele-svelte` | Nativele components in Svelte 5: props, events, slots, template types (Experimental) |
|
|
67
67
|
| `nativele-angular` | Nativele components in Angular: bindings, change detection, the build, content projection (Experimental) |
|
|
68
68
|
| `nativele-architecture` | app layout: feature folders, shared components and stores, pages mounted by a router, lazy pages, the test split |
|
|
69
69
|
| `nativele-vite` | Nativele in a Vite app: the plugin and `root`, multi-page builds, hot updates |
|
|
70
|
-
| `nativele-integration` | Nativele components in a framework without its own Skill (Preact, Solid, ...): properties, events, slots, lifecycle, template types
|
|
70
|
+
| `nativele-integration` | Nativele components in a framework without its own Skill (Preact, Solid, ...): properties, events, slots, lifecycle, template types |
|
|
71
71
|
| `nativele-state-management` | where state lives; `createStore`, and Zustand, Redux Toolkit or nanostores through `bind` |
|
|
72
72
|
| `nativele-testing` | component tests with Vitest, real-browser tests |
|
|
73
73
|
| `nativele-tanstack-query` | data fetching with `@tanstack/query-core` (Experimental) |
|
|
74
74
|
| `nativele-zod` | form validation with Zod or Valibot (Experimental) |
|
|
75
75
|
| `nativele-tailwind` | Tailwind CSS in templates and Shadow DOM (Experimental) |
|
|
76
76
|
|
|
77
|
-
Tiers: Official Skills are verified
|
|
77
|
+
Tiers: Official Skills are verified through agent trials and maintained by the maintainers; Community Skills are contributed and show their author and last verified versions; Experimental Skills are not fully verified yet. `ts-dom skills` marks a Skill as stale when its verification is more than six months old or does not cover the installed version.
|
|
78
78
|
|
|
79
79
|
Nativele stays a light UI layer and grows its integrations through Skills: a new Skill changes no runtime code and adds no dependency. The planned Skills and how to contribute one are in [the Skills guide](https://github.com/Pixmi/nativele/blob/main/skills/README.md).
|
|
80
80
|
|
package/README.zh-TW.md
CHANGED
|
@@ -63,18 +63,18 @@ npx ts-dom skills add nativele-core # 複製到 .claude/skills/(專案有 .
|
|
|
63
63
|
| `nativele-core` | 開發元件:`ts-dom check` 循環、範本、state、清單、資料流 |
|
|
64
64
|
| `nativele-react` | 在 React 19 中使用 Nativele 元件:props、事件、子元素與 slot、生命週期、TSX 型別 |
|
|
65
65
|
| `nativele-vue` | 在 Vue 3 中使用 Nativele 元件:props、事件、`v-model`、slot、`<KeepAlive>`、範本型別 |
|
|
66
|
-
| `nativele-svelte` | 在 Svelte 5 中使用 Nativele 元件:props、事件、slot
|
|
66
|
+
| `nativele-svelte` | 在 Svelte 5 中使用 Nativele 元件:props、事件、slot、範本型別(Experimental) |
|
|
67
67
|
| `nativele-angular` | 在 Angular 中使用 Nativele 元件:綁定、變更偵測、建置流程、內容投影(Experimental) |
|
|
68
68
|
| `nativele-architecture` | 應用的結構:功能資料夾、共用元件與 store、由 router 掛載的頁面、延遲載入、測試分層 |
|
|
69
69
|
| `nativele-vite` | 在 Vite 應用中使用 Nativele:外掛與 `root`、多頁建置、熱更新 |
|
|
70
|
-
| `nativele-integration` | 在沒有專屬 Skill 的框架(Preact、Solid 等)中使用 Nativele 元件:property、事件、slot
|
|
70
|
+
| `nativele-integration` | 在沒有專屬 Skill 的框架(Preact、Solid 等)中使用 Nativele 元件:property、事件、slot、生命週期、範本型別 |
|
|
71
71
|
| `nativele-state-management` | 狀態放在哪裡;`createStore`,以及透過 `bind` 接上 Zustand、Redux Toolkit、nanostores |
|
|
72
72
|
| `nativele-testing` | 用 Vitest 做元件測試、真實瀏覽器測試 |
|
|
73
73
|
| `nativele-tanstack-query` | 用 `@tanstack/query-core` 取資料(Experimental) |
|
|
74
74
|
| `nativele-zod` | 用 Zod 或 Valibot 驗證表單(Experimental) |
|
|
75
75
|
| `nativele-tailwind` | 在範本與 Shadow DOM 中使用 Tailwind CSS(Experimental) |
|
|
76
76
|
|
|
77
|
-
等級:Official
|
|
77
|
+
等級:Official 經 agent 實際驗收、由維護者維護;Community 由社群貢獻,標示作者與最後驗證版本;Experimental 尚未充分驗證。驗證超過六個月,或不涵蓋已安裝的版本時,`ts-dom skills` 會標示為可能過期。
|
|
78
78
|
|
|
79
79
|
Nativele 維持輕量的 UI 層,整合能力透過 Skill 擴展:新增 Skill 不改動執行期程式碼,也不增加依賴。規劃中的 Skill 與貢獻方式見 [Skills 指南](https://github.com/Pixmi/nativele/blob/main/skills/README.md)。
|
|
80
80
|
|
|
@@ -104,7 +104,7 @@ function analyze(html) {
|
|
|
104
104
|
mark(listName ? `text:${listName}/${name}` : `text:${name}`, at);
|
|
105
105
|
} else {
|
|
106
106
|
const inner = (m[1] ?? "").trim();
|
|
107
|
-
report("invalid-hole", "error", `{{ ${inner} }} is not a valid hole: a hole must be a single identifier, paths and expressions are not supported. Fix: compute the value in TypeScript (state, derive or this.text) and use a named hole such as {{ name }}.`, at);
|
|
107
|
+
report("invalid-hole", "error", `{{ ${inner} }} is not a valid hole: a hole must be a single identifier, paths and expressions are not supported. Fix: compute the value in TypeScript (state, derive or this.text) and use a named hole such as {{ name }}. If the file is another framework's template, this folder is not meant to be scanned: scan only the folder with the Nativele templates, or leave the file out with --exclude.`, at);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -517,8 +517,9 @@ function renderTags(dir) {
|
|
|
517
517
|
/**
|
|
518
518
|
* Everything `generateAll` would write for a directory, in memory: generated path to content, or null for a file that should not exist.
|
|
519
519
|
* Templates with error diagnostics are left out and their messages returned in `errors`.
|
|
520
|
+
* `scanned` are all the directories of the run: a ts-dom-tags.ts in a subfolder is an orphan unless that subfolder is one of them.
|
|
520
521
|
*/
|
|
521
|
-
function planGenerated(dir, options = {}, defs = scanDefines(dir)) {
|
|
522
|
+
function planGenerated(dir, options = {}, defs = scanDefines(dir), scanned = [dir]) {
|
|
522
523
|
const files = /* @__PURE__ */ new Map();
|
|
523
524
|
const errors = [];
|
|
524
525
|
for (const f of listHtml(dir)) {
|
|
@@ -538,26 +539,26 @@ function planGenerated(dir, options = {}, defs = scanDefines(dir)) {
|
|
|
538
539
|
if (e instanceof CodegenError) errors.push(e.message);
|
|
539
540
|
else throw e;
|
|
540
541
|
}
|
|
541
|
-
for (const f of listGenerated(dir)) if (!files.has(f) && !existsSync(f.slice(0, -3))) files.set(f, null);
|
|
542
|
+
for (const f of listGenerated(dir, scanned)) if (!files.has(f) && !existsSync(f.slice(0, -3))) files.set(f, null);
|
|
542
543
|
return {
|
|
543
544
|
files,
|
|
544
545
|
errors
|
|
545
546
|
};
|
|
546
547
|
}
|
|
547
548
|
/** Generated files under `dir` that differ from what `ts-dom gen` would write now. */
|
|
548
|
-
function staleGenerated(dir, options = {}, defs) {
|
|
549
|
-
return checkGenerated(dir, options, defs).stale;
|
|
549
|
+
function staleGenerated(dir, options = {}, defs, scanned) {
|
|
550
|
+
return checkGenerated(dir, options, defs, scanned).stale;
|
|
550
551
|
}
|
|
551
552
|
/** `staleGenerated`, plus the errors that would make `ts-dom gen` fail (template diagnostics, a tag defined twice). */
|
|
552
|
-
function checkGenerated(dir, options = {}, defs) {
|
|
553
|
-
const plan = planGenerated(dir, options, defs);
|
|
553
|
+
function checkGenerated(dir, options = {}, defs, scanned) {
|
|
554
|
+
const plan = planGenerated(dir, options, defs, scanned);
|
|
554
555
|
const out = [];
|
|
555
556
|
for (const [file, next] of plan.files) {
|
|
556
557
|
const prev = existsSync(file) ? readFileSync(file, "utf8") : null;
|
|
557
558
|
if (next === null) {
|
|
558
559
|
if (prev !== null && prev.startsWith(TAGS_HEADER)) out.push({
|
|
559
560
|
file,
|
|
560
|
-
reason: file
|
|
561
|
+
reason: file === join(dir, TAGS_FILE) ? "outdated" : "orphan"
|
|
561
562
|
});
|
|
562
563
|
} else if (prev === null) out.push({
|
|
563
564
|
file,
|
|
@@ -573,13 +574,14 @@ function checkGenerated(dir, options = {}, defs) {
|
|
|
573
574
|
errors: plan.errors
|
|
574
575
|
};
|
|
575
576
|
}
|
|
576
|
-
function listGenerated(dir) {
|
|
577
|
+
function listGenerated(dir, scanned, top = true) {
|
|
577
578
|
const out = [];
|
|
579
|
+
const ownTags = top || scanned.some((d) => resolve(d) === resolve(dir));
|
|
578
580
|
for (const name of readdirSync(dir)) {
|
|
579
581
|
if (name === "node_modules") continue;
|
|
580
582
|
const full = join(dir, name);
|
|
581
|
-
if (statSync(full).isDirectory()) out.push(...listGenerated(full));
|
|
582
|
-
else if (name.endsWith(".html.ts") && readFileSync(full, "utf8").startsWith(TAGS_HEADER)) out.push(full);
|
|
583
|
+
if (statSync(full).isDirectory()) out.push(...listGenerated(full, scanned, false));
|
|
584
|
+
else if ((name.endsWith(".html.ts") || name === TAGS_FILE && !ownTags) && readFileSync(full, "utf8").startsWith(TAGS_HEADER)) out.push(full);
|
|
583
585
|
}
|
|
584
586
|
return out;
|
|
585
587
|
}
|
|
@@ -587,7 +589,7 @@ function listGenerated(dir) {
|
|
|
587
589
|
* Generate every `x.html.ts` (and the tag map) under a directory; collects all errors and throws them together.
|
|
588
590
|
* `defs` are the define() calls templates import child components from (default: those under `dir`).
|
|
589
591
|
*/
|
|
590
|
-
function generateAll(dir, options = {}, defs = scanDefines(dir)) {
|
|
592
|
+
function generateAll(dir, options = {}, defs = scanDefines(dir), scanned = [dir]) {
|
|
591
593
|
const written = [];
|
|
592
594
|
const errors = [];
|
|
593
595
|
for (const f of listHtml(dir)) {
|
|
@@ -606,7 +608,7 @@ function generateAll(dir, options = {}, defs = scanDefines(dir)) {
|
|
|
606
608
|
if (e instanceof CodegenError) errors.push(e.message);
|
|
607
609
|
else throw e;
|
|
608
610
|
}
|
|
609
|
-
for (const f of listGenerated(dir)) if (!existsSync(f.slice(0, -3))) unlinkSync(f);
|
|
611
|
+
for (const f of listGenerated(dir, scanned)) if (!existsSync(f.slice(0, -3))) unlinkSync(f);
|
|
610
612
|
if (errors.length) throw new CodegenError(errors.join("\n"));
|
|
611
613
|
return written;
|
|
612
614
|
}
|
|
@@ -616,7 +618,7 @@ function generateDirs(dirs, options = {}) {
|
|
|
616
618
|
const written = [];
|
|
617
619
|
const errors = [];
|
|
618
620
|
for (const dir of dirs) try {
|
|
619
|
-
written.push(...generateAll(dir, options, defs));
|
|
621
|
+
written.push(...generateAll(dir, options, defs, dirs));
|
|
620
622
|
} catch (e) {
|
|
621
623
|
if (e instanceof CodegenError) errors.push(e.message);
|
|
622
624
|
else throw e;
|
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as staleGenerated, a as analyze, c as formatDiagnostics, f as isSelected, g as scanDefines, h as renderTags, i as CodegenError, l as generateDirs, m as listTs, n as checkA11y, o as checkGenerated, p as listHtml, r as CODEGEN_RULES, s as crossDirDuplicates, t as A11Y_RULES, v as watchAll } from "./a11y-
|
|
3
|
-
import { c as lintUsage, n as UNUSED_MARKUP_RULES, o as formatIssue, s as lintDataFlow, t as DATA_FLOW_RULES } from "./lint-
|
|
2
|
+
import { _ as staleGenerated, a as analyze, c as formatDiagnostics, f as isSelected, g as scanDefines, h as renderTags, i as CodegenError, l as generateDirs, m as listTs, n as checkA11y, o as checkGenerated, p as listHtml, r as CODEGEN_RULES, s as crossDirDuplicates, t as A11Y_RULES, v as watchAll } from "./a11y-CiEJj_e6.mjs";
|
|
3
|
+
import { c as lintUsage, n as UNUSED_MARKUP_RULES, o as formatIssue, s as lintDataFlow, t as DATA_FLOW_RULES } from "./lint-Did4w-NL.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { cpSync, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
// src/lib/tsconfig.ts
|
|
@@ -164,13 +164,22 @@ function mapTscErrors(errors, cwd) {
|
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
166
|
const brokenClasses = /* @__PURE__ */ new Set();
|
|
167
|
+
const brokenBodies = [];
|
|
167
168
|
for (const e of errors) {
|
|
168
169
|
if (!/missingStateKeys|conflictingKeys|reservedPropNames|nativeEventNames/.test(e.message)) continue;
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
170
|
+
const text = readFileSync(resolve(cwd, e.file), "utf8");
|
|
171
|
+
const lines = text.split("\n");
|
|
172
|
+
const m = /class\s+([A-Za-z_$][\w$]*)\s+extends\s+Component\s*\(/.exec(lines[e.line - 1] ?? "");
|
|
173
|
+
if (!m?.[1]) continue;
|
|
174
|
+
brokenClasses.add(m[1]);
|
|
175
|
+
const end = classBodyEnd(text, lines.slice(0, e.line - 1).join("\n").length + (e.line > 1 ? 1 : 0) + m.index + m[0].length - 1);
|
|
176
|
+
if (end !== -1) brokenBodies.push({
|
|
177
|
+
file: rel(resolve(cwd, e.file)),
|
|
178
|
+
from: e.line,
|
|
179
|
+
to: text.slice(0, end).split("\n").length
|
|
180
|
+
});
|
|
172
181
|
}
|
|
173
|
-
const followsBroken = (
|
|
182
|
+
const followsBroken = (i) => [...brokenClasses].some((c) => i.message.includes(`'${c}'`) || i.message.includes(`'typeof ${c}'`)) || brokenBodies.some((b) => b.file === i.file && i.line > b.from && i.line <= b.to);
|
|
174
183
|
for (const e of errors) {
|
|
175
184
|
const file = resolve(cwd, e.file);
|
|
176
185
|
const where = `${rel(file)}:${e.line}:${e.col}`;
|
|
@@ -208,9 +217,31 @@ function mapTscErrors(errors, cwd) {
|
|
|
208
217
|
named = true;
|
|
209
218
|
issue(rule, `${e.message.split("\n")[0]} Fix: ${fix}.`);
|
|
210
219
|
}
|
|
211
|
-
if (
|
|
220
|
+
if (named) continue;
|
|
221
|
+
if (file.endsWith(".html.ts") || file.endsWith(`${sep}ts-dom-tags.ts`)) issue("tsc", `${e.code}: ${e.message} This file is generated, so an edit to it is overwritten. Fix: run ts-dom gen for the folder that holds it; if it is left over from a template or folder that is no longer scanned, delete it.`);
|
|
222
|
+
else issue("tsc", `${e.code}: ${e.message} Fix: change the TypeScript at this location so the types agree; ts-dom rules --explain=tsc.`);
|
|
212
223
|
}
|
|
213
|
-
return out.filter((i) => i.rule !== "tsc" || !followsBroken(i
|
|
224
|
+
return out.filter((i) => i.rule !== "tsc" || !followsBroken(i));
|
|
225
|
+
}
|
|
226
|
+
/** The offset of the `}` that closes a class body, given the offset of the `(` of its `extends Component(`; -1 when it cannot be found. Strings and comments are skipped. */
|
|
227
|
+
function classBodyEnd(text, open) {
|
|
228
|
+
let depth = 0;
|
|
229
|
+
let inBody = false;
|
|
230
|
+
for (let i = open; i < text.length; i++) {
|
|
231
|
+
const c = text[i];
|
|
232
|
+
if (c === "\"" || c === "'" || c === "`") {
|
|
233
|
+
for (i++; i < text.length && text[i] !== c; i++) if (text[i] === "\\") i++;
|
|
234
|
+
} else if (c === "/" && text[i + 1] === "/") i = text.indexOf("\n", i) === -1 ? text.length : text.indexOf("\n", i);
|
|
235
|
+
else if (c === "/" && text[i + 1] === "*") i = text.indexOf("*/", i) === -1 ? text.length : text.indexOf("*/", i) + 1;
|
|
236
|
+
else if ("([{".includes(c)) {
|
|
237
|
+
if (c === "{" && depth === 0) inBody = true;
|
|
238
|
+
depth++;
|
|
239
|
+
} else if (")]}".includes(c)) {
|
|
240
|
+
depth--;
|
|
241
|
+
if (depth === 0 && inBody) return i;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return -1;
|
|
214
245
|
}
|
|
215
246
|
function findTsc(cwd) {
|
|
216
247
|
const req = createRequire(join(cwd, "package.json"));
|
|
@@ -278,7 +309,7 @@ function runCheck(options) {
|
|
|
278
309
|
});
|
|
279
310
|
if (options.write === false) {
|
|
280
311
|
const defs = scanDefines(dirs);
|
|
281
|
-
for (const dir of dirs) for (const s of staleGenerated(dir, generate, defs)) issues.push({
|
|
312
|
+
for (const dir of dirs) for (const s of staleGenerated(dir, generate, defs, dirs)) issues.push({
|
|
282
313
|
file: rel(s.file),
|
|
283
314
|
line: 1,
|
|
284
315
|
col: 1,
|
|
@@ -451,9 +482,11 @@ const DOCTOR_RULES = {
|
|
|
451
482
|
"runtime-not-installed": "the runtime package the generated files import cannot be resolved",
|
|
452
483
|
"template-import-without-js": "a template is imported as ./x.html instead of ./x.html.js",
|
|
453
484
|
"stale-generated": "a generated file is missing, outdated or orphaned",
|
|
454
|
-
"
|
|
485
|
+
"framework-template": "a scanned .html is an Angular component's templateUrl, not a Nativele template",
|
|
486
|
+
"typecheck-before-gen": "a package.json script type-checks before ts-dom gen while *.html.ts is git-ignored, so it fails on a clean checkout",
|
|
487
|
+
"vite-plugin-missing": "the Vite config, or a vitest.config that replaces it for Vitest, does not use the ts-dom plugin, so templates are not generated by the dev server or the tests",
|
|
455
488
|
"oxlint-plugin-missing": "the lint config does not load the ts-dom oxlint plugin, so the template rules do not run in the editor",
|
|
456
|
-
"formatter-whitespace": "a formatter is set up while templates keep their whitespace, so reformatting can change the rendered spacing",
|
|
489
|
+
"formatter-whitespace": "a formatter is set up while templates keep their whitespace, so reformatting can change the rendered spacing; or the scripts or Vite config ask for collapse while the generated files were written with preserve",
|
|
457
490
|
"skill-available": "the project uses a tool that a Nativele Skill covers, and the project has no copy of that Skill",
|
|
458
491
|
"skill-outdated": "the project's copy of a Nativele Skill differs from the one shipped with the installed version"
|
|
459
492
|
};
|
|
@@ -467,6 +500,24 @@ function covers(pattern, dir) {
|
|
|
467
500
|
if (pattern.slice(star).startsWith("**")) return d === prefix || d.startsWith(`${prefix}/`);
|
|
468
501
|
return d === prefix;
|
|
469
502
|
}
|
|
503
|
+
const TYPECHECK = /(?:^|\s)(?:npx\s+)?(tsc|vue-tsc|svelte-check|ng\s+(?:build|serve|test))(?=\s|$)/;
|
|
504
|
+
const TSC_PROJECT = /(?:^|\s)(?:npx\s+)?(?:vue-)?tsc(?:\s.*?)?\s(?:-p|--project)[\s=]+(\S+)/;
|
|
505
|
+
const GENERATES = /(?:^|\s)ts-dom\s+(?:gen|check)(?=\s|$)/;
|
|
506
|
+
const RUN_SCRIPT = /^(?:npm\s+run(?:-script)?|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun\s+run)\s+([\w:.-]+)/;
|
|
507
|
+
/** The commands a script runs in order, with its pre and post scripts and the scripts it runs through npm run (or pnpm, yarn, bun) spelled out. */
|
|
508
|
+
function scriptSteps(scripts, name, depth = 0) {
|
|
509
|
+
const command = scripts[name];
|
|
510
|
+
if (depth > 5 || command === void 0) return [];
|
|
511
|
+
const own = command.split(/&&|;/).flatMap((step) => {
|
|
512
|
+
const ref = RUN_SCRIPT.exec(step.trim())?.[1];
|
|
513
|
+
return ref && scripts[ref] !== void 0 ? scriptSteps(scripts, ref, depth + 1) : [step.trim()];
|
|
514
|
+
});
|
|
515
|
+
return [
|
|
516
|
+
...scriptSteps(scripts, `pre${name}`, depth + 1),
|
|
517
|
+
...own,
|
|
518
|
+
...scriptSteps(scripts, `post${name}`, depth + 1)
|
|
519
|
+
];
|
|
520
|
+
}
|
|
470
521
|
const templateFolders = (dir) => [...new Set(listHtml(dir).map((f) => dirname(f)))];
|
|
471
522
|
const hasTemplates = (dir) => listHtml(dir).length > 0;
|
|
472
523
|
/** Check the project set-up an agent or a developer could get wrong; every issue names its rule and ends with a Fix hint. */
|
|
@@ -537,7 +588,14 @@ function runDoctor(options) {
|
|
|
537
588
|
line: i + 1
|
|
538
589
|
});
|
|
539
590
|
});
|
|
540
|
-
for (const s of staleGenerated(d, generate, defs)) add("stale-generated", "warning", `The generated file is ${s.reason}. Fix: run ts-dom gen (or ts-dom check, which regenerates).`, { file: rel(s.file) });
|
|
591
|
+
for (const s of staleGenerated(d, generate, defs, dirs)) add("stale-generated", "warning", `The generated file is ${s.reason}. Fix: run ts-dom gen (or ts-dom check, which regenerates).`, { file: rel(s.file) });
|
|
592
|
+
}
|
|
593
|
+
const scannedHtml = new Set(dirs.flatMap(listHtml));
|
|
594
|
+
if (scannedHtml.size) for (const f of listTs(cwd)) for (const m of readFileSync(f, "utf8").matchAll(/templateUrl\s*:\s*["'`]([^"'`]+\.html)["'`]/g)) {
|
|
595
|
+
const html = resolve(dirname(f), m[1]);
|
|
596
|
+
if (!scannedHtml.has(html)) continue;
|
|
597
|
+
scannedHtml.delete(html);
|
|
598
|
+
add("framework-template", "error", `${rel(html)} is the Angular template of ${rel(f)} (templateUrl), not a Nativele template, so ts-dom gen reports its Angular syntax as errors. Fix: scan only the folder with the Nativele templates (for example ts-dom gen src/nv, and the same folder for check, doctor and tsDom({ dirs })).`, { file: rel(html) });
|
|
541
599
|
}
|
|
542
600
|
const viteConfig = [
|
|
543
601
|
"vite.config.ts",
|
|
@@ -547,6 +605,16 @@ function runDoctor(options) {
|
|
|
547
605
|
].map((n) => join(cwd, n)).find((f) => existsSync(f));
|
|
548
606
|
const vite = viteConfig ? readText(viteConfig) : null;
|
|
549
607
|
if (viteConfig && vite !== null && !/@nativele\/ts-dom\/vite|vite-plugin(\.ts)?["']/.test(vite)) add("vite-plugin-missing", "warning", "The Vite config does not use the ts-dom plugin, so templates are not regenerated while the dev server runs. Fix: import { tsDom } from \"@nativele/ts-dom/vite\" and add tsDom() to plugins.", { file: rel(viteConfig) });
|
|
608
|
+
const vitestConfig = [
|
|
609
|
+
"ts",
|
|
610
|
+
"mts",
|
|
611
|
+
"cts",
|
|
612
|
+
"js",
|
|
613
|
+
"mjs",
|
|
614
|
+
"cjs"
|
|
615
|
+
].map((ext) => join(cwd, `vitest.config.${ext}`)).find((f) => existsSync(f));
|
|
616
|
+
const vitest = vitestConfig ? readText(vitestConfig) : null;
|
|
617
|
+
if (vitestConfig && vitest !== null && !/@nativele\/ts-dom\/vite|vite-plugin(\.ts)?["']/.test(vitest) && !/["']\.\/vite\.config(\.[cm]?[jt]s)?["']/.test(vitest)) add("vite-plugin-missing", "warning", `${rel(vitestConfig)} replaces the Vite config for Vitest and does not use the ts-dom plugin, so the templates are not generated when the tests start and a clean checkout fails to resolve ./x.html.js. Fix: add tsDom() to its plugins, or merge the Vite config into it: mergeConfig(viteConfig, defineConfig({ test: { ... } })) with viteConfig imported from "./vite.config".`, { file: rel(vitestConfig) });
|
|
550
618
|
const oxlintrc = [".oxlintrc.json", "oxlintrc.json"].map((n) => join(cwd, n)).find((f) => existsSync(f));
|
|
551
619
|
const lintConfigs = [oxlintrc ? readText(oxlintrc) : null, vite && /\blint\s*:/.test(vite) ? vite : null].filter((t) => t !== null);
|
|
552
620
|
if (lintConfigs.length && !lintConfigs.some((t) => /@nativele\/ts-dom\/oxlint|oxlint-plugin(\.ts)?["']/.test(t))) add("oxlint-plugin-missing", "warning", "The lint config does not load the ts-dom oxlint plugin, so unused markup, data flow and accessibility rules do not run in the editor. Fix: add { name: \"ts-dom\", specifier: \"@nativele/ts-dom/oxlint\" } to jsPlugins and enable \"ts-dom/unused-markup\".", { file: rel(oxlintrc ?? viteConfig) });
|
|
@@ -557,6 +625,31 @@ function runDoctor(options) {
|
|
|
557
625
|
...pkg.devDependencies
|
|
558
626
|
};
|
|
559
627
|
const scripts = Object.values(pkg.scripts ?? {});
|
|
628
|
+
if ((readText(join(cwd, ".gitignore")) ?? "").split("\n").some((l) => !l.trim().startsWith("!") && /\*\.html\.ts\s*$/.test(l)) && dirs.some(hasTemplates)) {
|
|
629
|
+
const all = pkg.scripts ?? {};
|
|
630
|
+
const checksTemplates = (step) => {
|
|
631
|
+
const project = TSC_PROJECT.exec(step)?.[1];
|
|
632
|
+
if (!project && !/(?:^|\s)(?:npx\s+)?(?:vue-)?tsc(?=\s|$)/.test(step)) return true;
|
|
633
|
+
const config = resolve(cwd, project ?? "tsconfig.json");
|
|
634
|
+
const configs = projectConfigs(cwd, existsSync(config) && statSync(config).isDirectory() ? join(config, "tsconfig.json") : config);
|
|
635
|
+
return !configs.length || configs.some((c) => {
|
|
636
|
+
const include = readTsConfig(c).include;
|
|
637
|
+
return !include || targets.some((t) => include.some((p) => covers(p, t)));
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
const dirArgs = options.dirs.join(" ");
|
|
641
|
+
for (const name of Object.keys(all)) {
|
|
642
|
+
if (/^(pre|post)./.test(name) && all[name.replace(/^(pre|post)/, "")] !== void 0) continue;
|
|
643
|
+
const step = scriptSteps(all, name).find((s) => GENERATES.test(s) || TYPECHECK.test(s) && checksTemplates(s));
|
|
644
|
+
if (step === void 0 || GENERATES.test(step)) continue;
|
|
645
|
+
const tool = TYPECHECK.exec(step)[1]?.replace(/\s+/g, " ");
|
|
646
|
+
const line = pkgText.split("\n").findIndex((l) => l.includes(`${JSON.stringify(name)}:`)) + 1;
|
|
647
|
+
add("typecheck-before-gen", "warning", `The script "${name}" runs ${tool} before any ts-dom gen, and .gitignore leaves out *.html.ts, so on a clean checkout ${tool} fails with TS2307 (cannot find module './x.html.js'). Fix: put "ts-dom gen ${dirArgs} && " in front of it, or use "ts-dom check ${dirArgs}" as the type check when the project uses plain tsc.`, {
|
|
648
|
+
file: "package.json",
|
|
649
|
+
...line ? { line } : {}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
560
653
|
const ignoredHtml = /(^|\n)\s*(\*\*\/)?\*\.html\s*($|\n)/.test(readText(join(cwd, ".prettierignore")) ?? "");
|
|
561
654
|
const formatter = [".oxfmtrc.json", ".oxfmtrc.jsonc"].some((n) => existsSync(join(cwd, n))) || "oxfmt" in deps || scripts.some((c) => /(^|[\s;&|])(oxfmt|vp fmt)(\s|$)/.test(c)) || vite !== null && /\bfmt\s*:/.test(vite) || !ignoredHtml && ([
|
|
562
655
|
".prettierrc",
|
|
@@ -565,7 +658,22 @@ function runDoctor(options) {
|
|
|
565
658
|
"prettier.config.js",
|
|
566
659
|
"prettier.config.mjs"
|
|
567
660
|
].some((n) => existsSync(join(cwd, n))) || "prettier" in deps || scripts.some((c) => /(^|[\s;&|])prettier(\s|$)/.test(c)));
|
|
568
|
-
const
|
|
661
|
+
const collapseScript = Object.entries(pkg.scripts ?? {}).find(([, c]) => c.includes("--whitespace=collapse"))?.[0];
|
|
662
|
+
const collapseVite = vite !== null && /whitespace\s*:\s*["']collapse["']/.test(vite);
|
|
663
|
+
const generatedModes = dirs.flatMap(listHtml).flatMap((f) => {
|
|
664
|
+
const head = (readText(`${f}.ts`) ?? "").split("\n", 3);
|
|
665
|
+
return head[0]?.startsWith("// Generated by ts-dom.") ? [{
|
|
666
|
+
file: `${f}.ts`,
|
|
667
|
+
collapse: head.includes("// whitespace: collapse")
|
|
668
|
+
}] : [];
|
|
669
|
+
});
|
|
670
|
+
const preserved = generatedModes.filter((g) => !g.collapse);
|
|
671
|
+
if ((collapseScript || collapseVite) && preserved.length) {
|
|
672
|
+
const asker = collapseScript ? `The script "${collapseScript}"` : "The Vite config";
|
|
673
|
+
const rerun = collapseScript ? `npm run ${collapseScript}` : `ts-dom gen ${options.dirs.join(" ")} --whitespace=collapse`;
|
|
674
|
+
add("formatter-whitespace", "warning", `${asker} asks for whitespace "collapse", but ${preserved.length === 1 ? "this generated file was" : `${preserved.length} generated files (this one first) were`} written with "preserve" (by a gen or check run without the flag, for example), and a run without --whitespace keeps the mode a file was last written with, so the page gets the whitespace as written instead of collapsed. Fix: regenerate with the flag once: ${rerun}.`, { file: rel(preserved[0].file) });
|
|
675
|
+
}
|
|
676
|
+
const collapsed = collapseScript !== void 0 || collapseVite || generatedModes.some((g) => g.collapse);
|
|
569
677
|
if (formatter && !collapsed && dirs.some(hasTemplates)) add("formatter-whitespace", "warning", "A formatter (oxfmt or Prettier) is set up, and templates keep their whitespace as written, so reformatting a template can change the spacing on the page. Fix: generate with whitespace \"collapse\" (ts-dom gen --whitespace=collapse, or tsDom({ whitespace: \"collapse\" })).");
|
|
570
678
|
for (const skill of listSkills(options.skillsDir ?? skillsRoot())) {
|
|
571
679
|
const copies = installedCopies(cwd, skill.name);
|
|
@@ -1030,7 +1138,7 @@ this.refs.list.items = ["a", "b"];`
|
|
|
1030
1138
|
good: `<button data-on-click="open">open</button>`
|
|
1031
1139
|
},
|
|
1032
1140
|
"stale-generated": {
|
|
1033
|
-
why: "Types come from the generated *.html.ts files. If one is missing, outdated or orphaned, tsc checks against old templates and its errors may not match the HTML. ts-dom doctor reports the same condition as a warning.",
|
|
1141
|
+
why: "Types come from the generated *.html.ts files. If one is missing, outdated or orphaned (its template was deleted, or a ts-dom-tags.ts was left in a subfolder that is no longer scanned and declares its tags a second time), tsc checks against old templates and its errors may not match the HTML. ts-dom doctor reports the same condition as a warning.",
|
|
1034
1142
|
bad: `# card.html was edited, card.html.ts was not regenerated
|
|
1035
1143
|
npx ts-dom check --no-write`,
|
|
1036
1144
|
good: `npx ts-dom gen
|
|
@@ -1100,9 +1208,11 @@ const n: number = 1;`
|
|
|
1100
1208
|
good: `npx ts-dom check --project=tsconfig.json`
|
|
1101
1209
|
},
|
|
1102
1210
|
"dir-not-found": {
|
|
1103
|
-
why: "A folder given to ts-dom check or doctor that does not exist would otherwise be skipped silently, and a clean result would only mean that nothing was looked at.",
|
|
1104
|
-
bad: `npx ts-dom check ap # the templates live in app
|
|
1105
|
-
|
|
1211
|
+
why: "A folder given to ts-dom check or doctor, or to tsDom({ dirs }) in the Vite plugin, that does not exist would otherwise be skipped silently, and a clean result would only mean that nothing was looked at. The Vite plugin resolves dirs against Vite's root, not the working directory.",
|
|
1212
|
+
bad: `npx ts-dom check ap # the templates live in app/
|
|
1213
|
+
// vite.config.ts with root: "web": tsDom({ dirs: ["web/src"] }) looks for web/web/src`,
|
|
1214
|
+
good: `npx ts-dom check app
|
|
1215
|
+
// vite.config.ts with root: "web": tsDom({ dirs: ["src"] })`
|
|
1106
1216
|
},
|
|
1107
1217
|
"tsc-not-found": {
|
|
1108
1218
|
why: "ts-dom check runs the TypeScript compiler of the project. Without it installed, nothing can be type-checked.",
|
|
@@ -1154,13 +1264,35 @@ npx ts-dom check`
|
|
|
1154
1264
|
bad: `import tpl from "./card.html";`,
|
|
1155
1265
|
good: `import tpl from "./card.html.js";`
|
|
1156
1266
|
},
|
|
1267
|
+
"framework-template": {
|
|
1268
|
+
why: "Every .html in a scanned folder is read as a Nativele template. An Angular component's templateUrl file uses Angular syntax ({{ user.name }}, *ngIf, (click)), so gen reports errors whose hints ask to rewrite a file that belongs to another framework.",
|
|
1269
|
+
bad: `// src/app/app.ts
|
|
1270
|
+
@Component({ selector: "app-root", templateUrl: "./app.html" })
|
|
1271
|
+
# package.json
|
|
1272
|
+
"build": "ts-dom gen src && ng build"`,
|
|
1273
|
+
good: `# keep the Nativele components in their own folder and scan only it
|
|
1274
|
+
"build": "ts-dom gen src/nv && ng build"`
|
|
1275
|
+
},
|
|
1276
|
+
"typecheck-before-gen": {
|
|
1277
|
+
why: "The generated *.html.ts files are not committed. A script that runs tsc (or vue-tsc, svelte-check, ng build) before ts-dom gen works on a machine that has generated them once, and fails on a clean checkout or in CI with TS2307 for every ./x.html.js import.",
|
|
1278
|
+
bad: `// package.json, with *.html.ts in .gitignore
|
|
1279
|
+
"build": "tsc && vite build"`,
|
|
1280
|
+
good: `"build": "ts-dom gen src && tsc && vite build"
|
|
1281
|
+
// or, with plain tsc, let check generate and type-check in one step:
|
|
1282
|
+
"typecheck": "ts-dom check src"`
|
|
1283
|
+
},
|
|
1157
1284
|
"vite-plugin-missing": {
|
|
1158
|
-
why: "The Vite plugin regenerates the .html.ts files while the dev server runs. Without it, edits to a template are not reflected until gen is run by hand.",
|
|
1285
|
+
why: "The Vite plugin regenerates the .html.ts files while the dev server runs. Without it, edits to a template are not reflected until gen is run by hand. A vitest.config file replaces vite.config for Vitest, so unless it has the plugin too or merges vite.config, the tests start without generated files and fail on a clean checkout.",
|
|
1159
1286
|
bad: `// vite.config.ts
|
|
1160
|
-
export default defineConfig({ plugins: [] })
|
|
1287
|
+
export default defineConfig({ plugins: [] });
|
|
1288
|
+
// vitest.config.ts next to a vite.config.ts that has tsDom()
|
|
1289
|
+
export default defineConfig({ test: { environment: "happy-dom" } });`,
|
|
1161
1290
|
good: `// vite.config.ts
|
|
1162
1291
|
import { tsDom } from "@nativele/ts-dom/vite";
|
|
1163
|
-
export default defineConfig({ plugins: [tsDom()] })
|
|
1292
|
+
export default defineConfig({ plugins: [tsDom()] });
|
|
1293
|
+
// vitest.config.ts
|
|
1294
|
+
import viteConfig from "./vite.config";
|
|
1295
|
+
export default mergeConfig(viteConfig, defineConfig({ test: { environment: "happy-dom" } }));`
|
|
1164
1296
|
},
|
|
1165
1297
|
"oxlint-plugin-missing": {
|
|
1166
1298
|
why: "The oxlint plugin runs the template rules (unused markup, data flow, accessibility) in the editor. Without it they only run from the CLI.",
|
|
@@ -1173,11 +1305,14 @@ export default defineConfig({ plugins: [tsDom()] });`
|
|
|
1173
1305
|
}`
|
|
1174
1306
|
},
|
|
1175
1307
|
"formatter-whitespace": {
|
|
1176
|
-
why: "Text whitespace in a template is kept as written. A formatter that re-indents the HTML can then change the spaces rendered on the page.",
|
|
1308
|
+
why: "Text whitespace in a template is kept as written. A formatter that re-indents the HTML can then change the spaces rendered on the page. The same warning is given when the scripts or the Vite config ask for collapse but the generated files were written with preserve: a run without --whitespace keeps the mode a file was last written with, so one run without the flag leaves the project on preserve.",
|
|
1177
1309
|
bad: `# formatter is configured, templates keep their whitespace
|
|
1178
|
-
npx ts-dom gen
|
|
1310
|
+
npx ts-dom gen
|
|
1311
|
+
# or: package.json has "gen": "ts-dom gen src --whitespace=collapse", but the files came from a plain run
|
|
1312
|
+
npx ts-dom check src`,
|
|
1179
1313
|
good: `npx ts-dom gen --whitespace=collapse
|
|
1180
|
-
// or in vite.config.ts: tsDom({ whitespace: "collapse" })
|
|
1314
|
+
// or in vite.config.ts: tsDom({ whitespace: "collapse" })
|
|
1315
|
+
# and after a plain run: npm run gen (the script with the flag) once`
|
|
1181
1316
|
},
|
|
1182
1317
|
"skill-available": {
|
|
1183
1318
|
why: "A Nativele Skill gives an agent the verified steps, limitations and checks for a tool the project already uses. This is a suggestion and never changes the exit code.",
|
|
@@ -1212,11 +1347,14 @@ Commands
|
|
|
1212
1347
|
--no-write write nothing: report stale generated files instead (for a CI job that must not
|
|
1213
1348
|
change the working tree; gen --check only checks the generated files)
|
|
1214
1349
|
--project=<path> tsconfig for tsc (default: tsc's own lookup)
|
|
1350
|
+
--include=<glob>, --exclude=<glob>, --whitespace=collapse|preserve as for gen
|
|
1215
1351
|
--a11y also check ARIA / accessibility (warnings)
|
|
1216
1352
|
--strict exit with code 1 on warnings too (errors always do)
|
|
1217
1353
|
--json print the issues as JSON instead of text
|
|
1218
1354
|
doctor [dirs...] Check the project set-up: tsconfig, template imports, generated files, the runtime package,
|
|
1219
1355
|
and the Vite, oxlint and formatter configuration (default dir: src)
|
|
1356
|
+
--include=<glob>, --exclude=<glob>, --whitespace=collapse|preserve as for gen (they decide which
|
|
1357
|
+
generated files are stale)
|
|
1220
1358
|
--json print the issues as JSON instead of text
|
|
1221
1359
|
skills List the Nativele Skills shipped with this version: task procedures for coding agents
|
|
1222
1360
|
(set-up, a complete example, known limitations, verification), with the versions verified
|
|
@@ -1363,7 +1501,7 @@ if (!dirs.length) dirs.push("src");
|
|
|
1363
1501
|
try {
|
|
1364
1502
|
if (cmd === "gen" && flags.has("--check")) {
|
|
1365
1503
|
const defs = scanDefines(dirs);
|
|
1366
|
-
const results = dirs.map((d) => checkGenerated(d, generate, defs));
|
|
1504
|
+
const results = dirs.map((d) => checkGenerated(d, generate, defs, dirs));
|
|
1367
1505
|
const stale = results.flatMap((r) => r.stale);
|
|
1368
1506
|
const errors = [...results.flatMap((r) => r.errors), ...crossDirDuplicates(dirs).map((d) => formatDiagnostics(d.file, [d]))];
|
|
1369
1507
|
if (flags.has("--json")) console.log(JSON.stringify({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as analyze, g as scanDefines, p as listHtml } from "./a11y-
|
|
1
|
+
import { a as analyze, g as scanDefines, p as listHtml } from "./a11y-CiEJj_e6.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, relative } from "node:path";
|
|
4
4
|
// src/lib/lint.ts
|
|
@@ -126,7 +126,7 @@ function lintUsage(dirs, tsFiles) {
|
|
|
126
126
|
severity: "error",
|
|
127
127
|
line: 1,
|
|
128
128
|
col: 1,
|
|
129
|
-
message: `No TypeScript file imports this template. Fix: add \`import tpl from "./${name}.js"\` and \`class X extends Component(tpl)\` next to it, or delete the file.`
|
|
129
|
+
message: `No TypeScript file imports this template. Fix: add \`import tpl from "./${name}.js"\` and \`class X extends Component(tpl)\` next to it, or delete the file; if it is not a Nativele template, this folder is not meant to be scanned: scan only the folder with the Nativele templates.`
|
|
130
130
|
});
|
|
131
131
|
continue;
|
|
132
132
|
}
|
package/dist/oxlint-plugin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as checkA11y, t as A11Y_RULES } from "./a11y-
|
|
2
|
-
import { a as dataFlow, i as contractsFor, l as templateCode, n as UNUSED_MARKUP_RULES, r as componentClasses, t as DATA_FLOW_RULES, u as unusedMarkup } from "./lint-
|
|
1
|
+
import { n as checkA11y, t as A11Y_RULES } from "./a11y-CiEJj_e6.mjs";
|
|
2
|
+
import { a as dataFlow, i as contractsFor, l as templateCode, n as UNUSED_MARKUP_RULES, r as componentClasses, t as DATA_FLOW_RULES, u as unusedMarkup } from "./lint-Did4w-NL.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
// src/lib/oxlint-plugin.ts
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as formatDiagnostics, d as isFullPage, f as isSelected, g as scanDefines, i as CodegenError, l as generateDirs, n as checkA11y, p as listHtml, u as generateFile } from "./a11y-
|
|
1
|
+
import { c as formatDiagnostics, d as isFullPage, f as isSelected, g as scanDefines, i as CodegenError, l as generateDirs, n as checkA11y, p as listHtml, u as generateFile } from "./a11y-CiEJj_e6.mjs";
|
|
2
2
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
|
-
import { resolve, sep } from "node:path";
|
|
3
|
+
import { relative, resolve, sep } from "node:path";
|
|
4
4
|
// src/lib/vite-plugin.ts
|
|
5
5
|
const GENERATED_HEADER = "// Generated by ts-dom.";
|
|
6
6
|
const slashes = (file) => file.split(sep).join("/");
|
|
@@ -17,6 +17,7 @@ function tsDom(opts = {}) {
|
|
|
17
17
|
exclude: opts.exclude
|
|
18
18
|
};
|
|
19
19
|
const a11y = opts.a11y === true ? {} : opts.a11y || null;
|
|
20
|
+
let root = process.cwd();
|
|
20
21
|
let dirs = (opts.dirs ?? ["src"]).map((d) => resolve(d));
|
|
21
22
|
const shown = /* @__PURE__ */ new Map();
|
|
22
23
|
let defs;
|
|
@@ -83,10 +84,12 @@ function tsDom(opts = {}) {
|
|
|
83
84
|
name: "ts-dom",
|
|
84
85
|
enforce: "pre",
|
|
85
86
|
configResolved(config) {
|
|
87
|
+
root = config.root;
|
|
86
88
|
dirs = (opts.dirs ?? ["src"]).map((d) => resolve(config.root, d));
|
|
87
89
|
defs = void 0;
|
|
88
90
|
},
|
|
89
91
|
buildStart() {
|
|
92
|
+
for (const d of dirs) if (!existsSync(d)) this.warn(`${relative(process.cwd(), d) || "."}:1:1 warning [dir-not-found] The folder ${d} given in tsDom({ dirs }) does not exist, so no templates in it are generated. Fix: dirs are resolved against Vite's root (${root}), not the working directory; give the folder relative to that root, or an absolute path.`);
|
|
90
93
|
try {
|
|
91
94
|
generateDirs(existing(), generate);
|
|
92
95
|
} catch (e) {
|
package/llms.txt
CHANGED
|
@@ -101,8 +101,10 @@ In a browser without a bundler map the bare name once: `<script type="importmap"
|
|
|
101
101
|
- `data-if` content is hidden until the name is truthy (it stays in memory, so its `refs` still work); `{{ hole }}` stays empty until filled.
|
|
102
102
|
- With Vite, `package.json` needs `"type": "module"`.
|
|
103
103
|
- `slotchange()` is Light-DOM only; with `shadow: true` use the native `slotchange` event.
|
|
104
|
+
- A Light DOM component builds its content when connected (a `shadow: true` one in the constructor), so a new Light element has no children, `refs` or `text` until it is appended.
|
|
105
|
+
- Inside a framework (React, Vue, Svelte, ...), children the framework later inserts, removes or reorders in a Light DOM host with slots break or land in the wrong slot: use `shadow: true`, or one stable element per slot.
|
|
104
106
|
|
|
105
107
|
## Docs
|
|
106
108
|
- [README](README.md): introduction, the short version, rules for agents, quick start. [README.zh-TW.md](README.zh-TW.md): the same in Traditional Chinese.
|
|
107
|
-
- Guides (English): [templates](docs/templates.md), [components and data flow](docs/components.md), [lists](docs/lists.md), [architecture](docs/architecture.md), [setup, checks and lint](docs/tooling.md), [performance](docs/performance.md), [design](docs/design.md), [developing Nativele](docs/contributing.md).
|
|
109
|
+
- Guides (English): [templates](docs/templates.md), [components and data flow](docs/components.md), [recipes](docs/recipes.md), [lists](docs/lists.md), [architecture](docs/architecture.md), [setup, checks and lint](docs/tooling.md), [performance](docs/performance.md), [design](docs/design.md), [developing Nativele](docs/contributing.md).
|
|
108
110
|
- `npx ts-dom rules`: every rule id with a description.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativele/ts-dom",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Designed for AI agents. Driven by TypeScript. Defined in vanilla HTML. A lightweight library for building web components: typed templates, native custom elements, no virtual DOM, no template expressions, no bundler required.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"html",
|