@keenmate/web-components-devkit 1.0.0-rc05 → 1.0.0-rc07
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 +114 -0
- package/README.md +81 -23
- package/package.json +1 -1
- package/src/cli/args.js +32 -9
- package/src/cli/commands/check-pages.js +189 -0
- package/src/cli/commands/check-vars.js +24 -8
- package/src/cli/commands/doctor.js +129 -16
- package/src/cli/kmwc.js +80 -33
- package/src/cli/repo-checks.js +249 -0
- package/src/cli/sync-plan.js +3 -2
- package/src/config.d.ts +14 -4
- package/src/config.js +13 -2
- package/src/examples/css/base.css +28 -0
- package/src/examples/css/content.css +44 -0
- package/src/examples/css/form-controls.css +4 -0
- package/src/examples/css/index-page.css +4 -0
- package/src/examples/css/widgets.css +12 -4
- package/src/vite/index.js +10 -7
- package/src/vite/page-composer.js +30 -1
- package/templates/Makefile.tmpl +4 -1
- package/templates/claude-block.md +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,119 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-rc07] - 2026-11-03 [PUBLISHED]
|
|
4
|
+
|
|
5
|
+
Found migrating web-treeview — each item is something doctor missed or got wrong there.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`kmwc check-pages`** — loads every example page in Chromium at 1440 / 390 / 320px and
|
|
10
|
+
reports page and console errors, failed requests, custom elements that never get defined and a
|
|
11
|
+
page wider than the viewport (with the widest elements named). treeview's iOS-Files mockup
|
|
12
|
+
(`width: 420px`) and theme gallery (`minmax(500px, 1fr)`) pushed a phone sideways and only a
|
|
13
|
+
browser could tell. Starts the repo's dev server, or `--url` for one already running (the built
|
|
14
|
+
site on the preview port); `--widths`. It looks inside shadow roots and at text (a long word
|
|
15
|
+
sticking out of a box that fits), and skips what can't widen the page (code blocks and other
|
|
16
|
+
scrolling boxes, hidden or fixed layers such as a closed chapter-nav panel). `kmwc sync` adds a
|
|
17
|
+
`check:pages` script and a `make check-pages` target.
|
|
18
|
+
- **Doctor: accepted deviations** — a rulebook failure whose id heads a section of
|
|
19
|
+
`VALIDATION-NOTES.md` (`## C-CST-4 / C-CST-10 — …`) is an architectural decision recorded per the
|
|
20
|
+
rulebook and counts as a pass (`--verbose` lists them). treeview's documented C-CST-4 / C-CSS-1 /
|
|
21
|
+
C-NC-6 showed as errors.
|
|
22
|
+
- **Doctor: library outputs** — every `./dist/…` target in package.json (`main`, `module`, `types`,
|
|
23
|
+
`style`, `exports`) must be a file the library build emits, and before the switch the old
|
|
24
|
+
`lib.name` must equal `kmwc.library.globalName`. The preset's defaults would have renamed
|
|
25
|
+
treeview's UMD global (`WebTreeView` → `WebTreeview`) and its CSS (`web-treeview.css` →
|
|
26
|
+
`style.css`) without a word.
|
|
27
|
+
- **Doctor: ports** — a script that pins its own `--port`, a config / Makefile / doc port outside the
|
|
28
|
+
repo's registry block, and a sibling checkout listening inside it are reported. treeview ran on
|
|
29
|
+
21111 (docs too), and web-daterangepicker hardcoded treeview's 12300; the check also finds
|
|
30
|
+
web-multiselect and web-dropzone on 12200 and web-component-framework-test on 12300.
|
|
31
|
+
- **Doctor: analytics** — a Plausible site id another repo also uses is an error (treeview's
|
|
32
|
+
snippet borrowed web-multiselect's, so its traffic was counted as multiselect's).
|
|
33
|
+
- **Doctor: page CSS the kit covers** — page `<style>` / `examples-local.css` rules on kit-owned
|
|
34
|
+
selectors (`.kbd`, `kbd`, `mark`, `.pill` …) and one-declaration inline styles a kit class covers
|
|
35
|
+
(`width` on a field → `.w-*`, `margin-left: auto` → `.push-end`, `margin-top: 1rem` → `.mt-1`)
|
|
36
|
+
are warnings.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **check-vars: input variables** — in the default mode a component variable is present when it is
|
|
41
|
+
declared **or** read. A knob that is read but never declared is an input, unset by design:
|
|
42
|
+
treeview forwards `--wtv-context-menu-*` to core's private `--_km-menu-*` so a page-wide
|
|
43
|
+
`--km-menu-*` still applies, and declaring them would break that chain — check-vars called all 26
|
|
44
|
+
dead. A read-with-fallback that isn't in the manifest is now reported as undocumented.
|
|
45
|
+
`cssVariables.sources` also takes a package path (`@keenmate/web-components-core/dist/menu/styles.js`)
|
|
46
|
+
for the `--base-*` reads of a core module the component renders.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **The text "<head>" / "</body>" on a page no longer receives the injected snippets** —
|
|
51
|
+
the plugin put the theme / page-config / analytics block after the first `<head…>` match,
|
|
52
|
+
which also matched `<header>` (a page without `<head>`), and the kit's module script before
|
|
53
|
+
the first `</body>`, which can be text inside a page's inline script (the kit then landed
|
|
54
|
+
in that script and broke it). Now: the first real `<head>` tag outside HTML comments, and
|
|
55
|
+
the last `</body>`. Same failure as web-multiselect's live action-buttons page, where its
|
|
56
|
+
own Dockerfile's `sed '/<head>/r …'` put the Plausible snippet (an HTML comment) into the
|
|
57
|
+
page's module script, so no picker got its options.
|
|
58
|
+
- **Headings break instead of widening a phone** — a chapter heading naming an API outside
|
|
59
|
+
`<code>` (drp's `EV05 · getMonthDataCallback & beforeMonthChangeCallback`) and the examples index
|
|
60
|
+
title ("Web DateRangePicker" at 2.5rem) had no break opportunity at 320px. `h1`–`h4` get
|
|
61
|
+
`overflow-wrap: anywhere`; the index title is 2rem on a phone.
|
|
62
|
+
- **A 250px widget fits a 320px phone** — body + card padding left 240px, so an inline calendar
|
|
63
|
+
widened the page. Below 400px the body pads 0.75rem, cards and example sections 1rem, demo cards
|
|
64
|
+
and demo areas 0.75rem (264px inside a card).
|
|
65
|
+
- **`mark` no longer splits a word** — the 2px side padding cut a match inside a word apart
|
|
66
|
+
(core's Search page: "Town send"); `padding: 0`, and a match that wraps keeps its look on both
|
|
67
|
+
lines (`box-decoration-break: clone`).
|
|
68
|
+
|
|
69
|
+
## [1.0.0-rc06] - 2026-11-01 [PUBLISHED]
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **`<kbd>` and `<mark>`** — every component documents a keyboard map and most highlight
|
|
74
|
+
search matches, and pages either left them in the browser's look (core's Interaction page:
|
|
75
|
+
18 bare `<kbd>`s) or styled them locally (core's Search page). A `<kbd>` now looks like the
|
|
76
|
+
spotlight's key hints and a `<mark>` like its match highlight. Widgets page WG08.
|
|
77
|
+
- **Field widths** — a native field is ~20 characters wide, so pages set inline widths (core:
|
|
78
|
+
14). A number field is now 7rem (six digits and the spin buttons; a `.form-group` still
|
|
79
|
+
stretches it), and `.w-xs` / `.w-sm` / `.w-md` / `.w-lg` (4 / 6 / 9 / 12rem) size a short
|
|
80
|
+
field. `.push-end` moves an item to the end of a `.controls` row. Form controls page FC12.
|
|
81
|
+
- **`kmwc.library: false`** — for a repo that builds its package itself (core: `tsc`) or ships
|
|
82
|
+
none. Plain `vite build`, the devkit's library mode, refuses to run instead of emptying
|
|
83
|
+
`dist/`; `vite build --mode examples` still builds the site, and sync no longer proposes a
|
|
84
|
+
`build: vite build` script. `{ "shouldBuild": false, "entry": … }` keeps the pages' entry.
|
|
85
|
+
- **`kmwc.cssVariables: false`** (or `checkMode: "off"`) — sync stopped re-adding `check:vars`
|
|
86
|
+
only when there was no manifest; now it can be switched off (core builds its variable names
|
|
87
|
+
by interpolation, which the checker can't see). `kmwc check-vars` then skips.
|
|
88
|
+
- **Doctor knows libraries** — a repo whose registry entry (or `kmwc.kind`) is `library` runs
|
|
89
|
+
only the rulebook suites that apply to it: naming-conventions, readme-structure,
|
|
90
|
+
publish-command, example-pages. The CSS, Custom Elements Manifest, overlay and
|
|
91
|
+
component-structure suites check what a custom element ships; on core they gave 12 errors
|
|
92
|
+
and 4 "suite could not run" warnings. No prefix warning for a library.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- **`kmwc <command> --help` ran the command** — only a top-level `--help` was handled and
|
|
97
|
+
unknown flags were ignored, so `kmwc sync --help` did a full sync (it overwrote core's
|
|
98
|
+
Makefile). `--help` / `-h` after any command now prints that command's usage and exits 0,
|
|
99
|
+
and an unknown option is an error (exit 2). Every command declares its flags; a boolean
|
|
100
|
+
flag no longer swallows the next word as its value.
|
|
101
|
+
- **Inline `<code>` widened a phone page** — a long identifier has no break opportunity, so
|
|
102
|
+
`resolvePresentation(mode, env, map)` in a heading made core's page 350px wide at 320px.
|
|
103
|
+
Inline code now breaks anywhere; code in a `<pre>` keeps its lines.
|
|
104
|
+
- **Muted log text failed contrast on the light panel** — `--muted` lines, the seq / time /
|
|
105
|
+
source cells and the empty placeholder used `--kmwc-text-color-3`: 2.83:1 on the light
|
|
106
|
+
`.log-panel` in the light theme (WCAG AA wants 4.5:1). They now use `--kmwc-text-color-2`.
|
|
107
|
+
A spec checks every tone on both panels in both themes.
|
|
108
|
+
- **`.css-readout` sat on the demo above it** — it had no top margin (core carried 8 inline
|
|
109
|
+
margins); it now has 0.75rem.
|
|
110
|
+
|
|
111
|
+
- **Timeline log lines on a phone** — the cells (`.log-line__seq`, `__time`, `__source`,
|
|
112
|
+
`__name`) never shrink and the row never wrapped, so on a narrow panel, or with a long
|
|
113
|
+
source name, the detail got the few pixels left over: one letter per line. The row now
|
|
114
|
+
wraps, and the detail wants ~14rem beside the cells; with less room it drops under them at
|
|
115
|
+
full width. Wide panels are unchanged. A cell is capped at the panel width.
|
|
116
|
+
|
|
3
117
|
## [1.0.0-rc05] - 2026-11-01 [PUBLISHED]
|
|
4
118
|
|
|
5
119
|
### Changed
|
package/README.md
CHANGED
|
@@ -19,18 +19,20 @@ them in the same shape: `@keenmate/web-components-devkit`.
|
|
|
19
19
|
> positioning) lives in [`@keenmate/web-components-core`](../web-components-core).
|
|
20
20
|
> Nothing here ships inside a component's bundle.
|
|
21
21
|
|
|
22
|
-
## What's New in v1.0.0-
|
|
22
|
+
## What's New in v1.0.0-rc07
|
|
23
23
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
-
|
|
27
|
-
- **
|
|
24
|
+
- **CLI — `kmwc check-pages` finds what only a browser can see** — Migrating web-treeview turned up pages that pushed a phone sideways: an iOS-Files mockup 420px wide and a theme gallery built on `minmax(500px, 1fr)`. No static check could have caught that. `kmwc check-pages` loads every example page in Chromium at 1440, 390 and 320px. It reports page and console errors, failed requests, custom elements that never get defined, and pages wider than the viewport, naming the widest elements. It looks inside shadow roots and at long words that overflow their box. It ignores layers that can't widen a page: scrolling code blocks, hidden or fixed layers, a closed chapter-nav panel. It starts the repo's dev server, or `--url` points it at one already running (for example the built site on the preview port), and `--widths` changes the widths. `kmwc sync` adds a `check:pages` script and a `make check-pages` target.
|
|
25
|
+
- **Doctor — catches what the treeview and daterangepicker migrations got wrong** — Doctor now checks that every `./dist/…` target in package.json is a file the library build actually emits, and that the old UMD name matches `kmwc.library.globalName`. Without that, the preset's defaults would quietly have renamed treeview's global (`WebTreeView` → `WebTreeview`) and its CSS (`web-treeview.css` → `style.css`). Doctor also reports scripts that pin their own `--port`, config, Makefile and doc ports outside the repo's registry block, and sibling repos that listen inside it. This found web-daterangepicker on treeview's 12300 and web-multiselect / web-dropzone on daterangepicker's 12200. A Plausible site id that another repo also uses is an error, because treeview's traffic was being counted as web-multiselect's. Page CSS that duplicates kit classes (`.kbd`, `mark`, inline field widths, `margin-left: auto`) is a warning.
|
|
26
|
+
- **Doctor — `VALIDATION-NOTES.md` records accepted deviations** — The rulebook lets a component record an architectural decision instead of following a rule. Doctor still reported those as errors (treeview's C-CST-4, C-CSS-1 and C-NC-6). A rulebook check id used as a `##` heading in `VALIDATION-NOTES.md` (`## C-CST-4 / C-CST-10 — …`) now counts as a pass, and `--verbose` lists them. A mention in plain prose doesn't count.
|
|
27
|
+
- **check-vars — input variables are not dead** — Some component variables are public knobs that are read with a fallback and never declared. Treeview forwards `--wtv-context-menu-*` to core's private `--_km-menu-*` so a page-wide `--km-menu-*` still applies, and declaring them would break that chain. check-vars called all 26 of them dead. In the default mode a variable now counts as present when it is declared or read, and a read-with-fallback missing from the manifest is reported as undocumented. `cssVariables.sources` also takes a package path (`@keenmate/web-components-core/dist/menu/styles.js`) for the `--base-*` reads of a core module the component renders.
|
|
28
|
+
- **Example pages — fit a 320px phone, and injected scripts land in the right place** — A chapter heading that names an API outside `<code>` now breaks instead of widening the page. On a phone the index title is 2rem. Below 400px, body, card and demo padding shrink, so a 250px inline calendar fits inside a card. `mark` no longer splits a word. The Vite plugin now puts its head block after the real `<head>` tag (not `<header>` or the text "<head>"). It puts the kit script before the last `</body>`, so it can no longer land inside a page's inline script and break it. That's the failure web-multiselect's sed-based Dockerfile caused on its live action-buttons page.
|
|
28
29
|
|
|
29
|
-
## What's New in v1.0.0-
|
|
30
|
+
## What's New in v1.0.0-rc06
|
|
30
31
|
|
|
31
|
-
- **Example
|
|
32
|
-
-
|
|
33
|
-
- **
|
|
32
|
+
- **Example pages — `<kbd>`, `<mark>`, field widths and `.push-end`** — Every component documents a keyboard map and most highlight search matches. Pages either left `<kbd>` in the browser's look (core's Interaction page had 18 bare keys) or styled `<mark>` themselves. A `<kbd>` now looks like the spotlight's key hints and a `<mark>` like its match highlight. Native fields are about 20 characters wide, so pages set inline widths; core had 14. A number field is now 7rem by default. `.w-xs` / `.w-sm` / `.w-md` / `.w-lg` (4 / 6 / 9 / 12rem) size a short field, and a `.form-group` still stretches it to the row. `.push-end` moves an item to the end of a `.controls` row. See the playground's WG08 and FC12.
|
|
33
|
+
- **Example pages — phone and contrast fixes** — Inline `<code>` now breaks anywhere. Before, a long identifier such as `resolvePresentation(mode, env, map)` in a heading made a page 350px wide on a 320px screen; code inside a `<pre>` keeps its lines. Muted log text on the light `.log-panel` (`--muted` lines, the seq / time / source cells, the empty placeholder) measured 2.83:1 in the light theme. It now uses `--kmwc-text-color-2`, and a spec checks every tone on both panels in both themes against WCAG AA's 4.5:1. Timeline rows wrap, so on a phone or with a long source name the detail drops under the cells instead of showing one letter per line. `.css-readout` gets a top margin after the demo it measures.
|
|
34
|
+
- **CLI — `kmwc <command> --help` no longer runs the command** — Only a top-level `--help` was handled, and unknown flags were silently ignored, so `kmwc sync --help` did a full sync and overwrote core's hand-written Makefile. Every command now lists the flags it accepts. `--help` / `-h` after any command prints that command's usage and exits 0, and an unknown option is an error (exit 2). A boolean flag no longer takes the next word as its value.
|
|
35
|
+
- **Libraries — `library: false`, `cssVariables: false`, and a doctor that knows the kind** — A repo like web-components-core builds its package with `tsc`. A plain `vite build` (the devkit's library mode) would empty its `dist/`, so `"kmwc": { "library": false }` now makes it refuse to run, while `vite build --mode examples` still builds the site. `"cssVariables": false` turns off the variables-manifest check, so sync stops re-adding `check:vars`. Doctor reads the registry kind (or `kmwc.kind`): a `library` runs only naming-conventions, readme-structure, publish-command and example-pages. On core that drops 12 errors and 4 "suite could not run" warnings that didn't apply to a library.
|
|
34
36
|
|
|
35
37
|
## Catalogue
|
|
36
38
|
|
|
@@ -40,8 +42,8 @@ them in the same shape: `@keenmate/web-components-devkit`.
|
|
|
40
42
|
| Component | Package | Prefix | Dev port | Links | Devkit | About |
|
|
41
43
|
|---|---|---|---:|---|:---:|---|
|
|
42
44
|
| **web-multiselect** | `@keenmate/web-multiselect` | `ms` | 12100 | [repo](https://github.com/keenmate/web-multiselect) · [examples](https://examples.web-multiselect.keenmate.dev) · [docs](https://web-multiselect.keenmate.dev) · `web-multiselect-showcase` · `web-multiselect-docs` | — | Lightweight multiselect web component with typeahead search, rich content support, and excellent keyboard… |
|
|
43
|
-
| **web-daterangepicker** | `@keenmate/web-daterangepicker` | `drp` | 12200 | [repo](https://github.com/keenmate/web-daterangepicker) · [docs](https://web-daterangepicker.keenmate.dev) · `web-daterangepicker-showcase` |
|
|
44
|
-
| **web-treeview** | `@keenmate/web-treeview` | `wtv` | 12300 | [repo](https://github.com/keenmate/web-treeview) · `web-treeview-showcase` |
|
|
45
|
+
| **web-daterangepicker** | `@keenmate/web-daterangepicker` | `drp` | 12200 | [repo](https://github.com/keenmate/web-daterangepicker) · [docs](https://web-daterangepicker.keenmate.dev) · `web-daterangepicker-showcase` | ✅ | Lightweight date picker web component with excellent keyboard navigation and range selection |
|
|
46
|
+
| **web-treeview** | `@keenmate/web-treeview` | `wtv` | 12300 | [repo](https://github.com/keenmate/web-treeview) · `web-treeview-showcase` | ✅ | Lightweight treeview web component with hierarchical data display, search, expand/collapse, drag-drop, and… |
|
|
45
47
|
| **web-commandpalette** | `@keenmate/web-commandpalette` | `cp` | 12400 | [repo](https://github.com/keenmate/web-commandpalette) · [examples](https://examples.web-commandpalette.keenmate.dev) | ✅ | Framework-agnostic command-palette web component — modes, multi-step commands, scoped + global search,… |
|
|
46
48
|
| **web-dropzone** | `@keenmate/web-dropzone` | `dz` | 12500 | [repo](https://github.com/keenmate/web-dropzone) | — | Lightweight file dropzone web component with drag-drop, previews, validation, and multiple display modes. |
|
|
47
49
|
| **web-grid** | `@keenmate/web-grid` | `wg` | 12600 | [repo](https://github.com/keenmate/web-grid) · [docs](https://web-grid.keenmate.com) · [showcase](https://web-grid.keenmate.com) | — | Framework-agnostic data grid web component with sorting, filtering, inline editing, and keyboard navigation |
|
|
@@ -142,7 +144,8 @@ Every field is optional. Defaults are derived from the package.
|
|
|
142
144
|
"tagline": "Feature-rich multiselect …", // default: package.json description
|
|
143
145
|
"tag": "web-multiselect",
|
|
144
146
|
"prefix": "ms", // reserved CSS prefix (rulebook base-variables.md)
|
|
145
|
-
"
|
|
147
|
+
"kind": "component", // default: the components.json entry's kind
|
|
148
|
+
"library": { // false: no library build (see below)
|
|
146
149
|
"entry": "src/index.ts", // default
|
|
147
150
|
"globalName": "MultiSelect", // UMD global
|
|
148
151
|
"fileName": "multiselect", // → dist/multiselect.js + .umd.js
|
|
@@ -165,10 +168,12 @@ Every field is optional. Defaults are derived from the package.
|
|
|
165
168
|
"runner": "podman",
|
|
166
169
|
"packageJsonPaths": ["package.json"] // monorepos: list workspace package.jsons too
|
|
167
170
|
},
|
|
168
|
-
"cssVariables": {
|
|
169
|
-
"sources": ["src/css"], // dirs (stylesheets) or explicit files (any type)
|
|
171
|
+
"cssVariables": { // false: no variables manifest, no check:vars
|
|
172
|
+
"sources": ["src/css"], // dirs (stylesheets) or explicit files (any type); a package
|
|
173
|
+
// path (e.g. a core module's CSS) resolves from node_modules
|
|
170
174
|
"internalVariables": ["ms-input-current-width"], // runtime internals, not theming knobs
|
|
171
|
-
"checkMode": "declared" //
|
|
175
|
+
"checkMode": "declared" // declared OR read (an input knob may be read, never set);
|
|
176
|
+
// "consumed": var() reads only
|
|
172
177
|
},
|
|
173
178
|
"rulebook": {
|
|
174
179
|
"path": "../BlissFramework/guidelines/web-components", // relative to the component; default: the nearest
|
|
@@ -179,6 +184,16 @@ Every field is optional. Defaults are derived from the package.
|
|
|
179
184
|
}
|
|
180
185
|
```
|
|
181
186
|
|
|
187
|
+
**A library** (registry `kind: "library"`, e.g. web-components-core) builds its
|
|
188
|
+
package itself (`tsc`) or ships none. `"library": false` (or `{ "shouldBuild":
|
|
189
|
+
false, "entry": … }` to keep the pages' entry script) makes plain `vite build`
|
|
190
|
+
refuse to run — it is the devkit's library mode and would empty `dist/` — while
|
|
191
|
+
`vite build --mode examples` still builds the site, and `kmwc sync` no longer
|
|
192
|
+
proposes a `build: vite build` script. `"cssVariables": false` turns off the
|
|
193
|
+
manifest check (no `check:vars` script, `kmwc check-vars` skips), for variables
|
|
194
|
+
the checker can't see, such as names built by string interpolation. Doctor runs
|
|
195
|
+
only the rulebook suites that apply to a library (see below).
|
|
196
|
+
|
|
182
197
|
### Vite preset
|
|
183
198
|
|
|
184
199
|
```ts
|
|
@@ -265,7 +280,8 @@ doctor` lists the ones still repeating the head/header boilerplate.
|
|
|
265
280
|
`.demo-area`, `.demo-card`, `.code-block`, `.note(.warning|.info|.success|.error)`,
|
|
266
281
|
`.grid`/`.card-grid`, `.reference-table` (+ `.table-*` modifiers), `.log-panel`, `.pill`,
|
|
267
282
|
`.status-dot`, `.control-group`, buttons (`.btn-secondary`, `.btn-outline`,
|
|
268
|
-
`.btn-danger`)
|
|
283
|
+
`.btn-danger`), `<kbd>` keys and `<mark>` search matches (the spotlight's look),
|
|
284
|
+
`.push-end` (to the end of a flex row) — while everything the kit **generates** is prefixed BEM
|
|
269
285
|
(`kmwc-chapter-nav__item--active`, `kmwc-copy-link--copied`, …).
|
|
270
286
|
- **Form controls** look the same on every page without a class: text fields,
|
|
271
287
|
selects and list boxes (`multiple` / `size`), textareas, checkboxes, radios,
|
|
@@ -275,7 +291,9 @@ doctor` lists the ones still repeating the head/header boilerplate.
|
|
|
275
291
|
selects and buttons share `--kmwc-control-height`, so a row of them lines up
|
|
276
292
|
in any container, not only `.controls`. The rules are zero-specificity
|
|
277
293
|
(`:where()`); `kmwc-native` on an element or an ancestor keeps the browser's
|
|
278
|
-
own controls (a light-DOM component that draws its own inputs).
|
|
294
|
+
own controls (a light-DOM component that draws its own inputs). A number field
|
|
295
|
+
is 7rem wide; `.w-xs` / `.w-sm` / `.w-md` / `.w-lg` (4 / 6 / 9 / 12rem) size a
|
|
296
|
+
short field without an inline style. The
|
|
279
297
|
playground's `examples-forms.html` shows them all.
|
|
280
298
|
- **Tables:** any `<table>` gets a header fill, row lines and a hover tint;
|
|
281
299
|
`.reference-table` is the full-width, fully bordered API table. Modifiers
|
|
@@ -347,12 +365,20 @@ kmwc doctor [--json] devkit checks + package contents + the rulebook's
|
|
|
347
365
|
(exit 1 on errors). --verbose lists passes, --no-rulebook skips the
|
|
348
366
|
suites, --no-pack skips the package-contents check
|
|
349
367
|
kmwc check-vars [--strict] component-variables.manifest.json ⇔ CSS
|
|
368
|
+
kmwc check-pages [--url u] every example page in Chromium at 1440 / 390 / 320px: page and console
|
|
369
|
+
errors, failed requests, never-defined elements, a page wider than the
|
|
370
|
+
viewport. Starts the dev server unless --url (e.g. the built site);
|
|
371
|
+
--widths 1440,390,320
|
|
350
372
|
kmwc check-release --tag t latest ⇔ release version, rc ⇔ -rcN, version not yet on npm
|
|
351
373
|
kmwc new web-<name> scaffold a component (--prefix, --title, --tag, --dir, --port, --minimal, --register)
|
|
352
374
|
--adopt: scaffold an ALREADY registered component (its port/prefix) to migrate into
|
|
353
375
|
kmwc readme [--check] regenerate this README's catalogue (devkit repo only)
|
|
376
|
+
kmwc <command> --help that command's usage — never runs the command
|
|
354
377
|
```
|
|
355
378
|
|
|
379
|
+
An unknown option is an error (exit 2), never ignored — a typo can't run a
|
|
380
|
+
command with its defaults.
|
|
381
|
+
|
|
356
382
|
Generated files start with a `GENERATED by kmwc sync` header. Repo-specific
|
|
357
383
|
make targets go in `Makefile.local`, which the generated Makefile includes at
|
|
358
384
|
the end. Per-developer overrides (`DOCKER_RUNNER`, `IMAGE_PORT`) go in
|
|
@@ -364,9 +390,36 @@ and the e2e suite before building and publishing.
|
|
|
364
390
|
base-variables, color-scheme, readme-structure, custom-elements-manifest,
|
|
365
391
|
responsive-overlay, publish-command, example-pages) with `bash`, passing `kmwc.prefix`, and
|
|
366
392
|
reports each `FAIL` as an error pointing at the matching `.checks.md` entry.
|
|
393
|
+
A `library` runs only naming-conventions, readme-structure, publish-command
|
|
394
|
+
and example-pages; the others check what a custom element ships (its CSS,
|
|
395
|
+
manifest, overlay).
|
|
367
396
|
When the rulebook isn't checked out (or there's no bash) the suites are
|
|
368
397
|
skipped with a warning — the rulebook stays the single source of truth; the
|
|
369
|
-
devkit never copies its rules.
|
|
398
|
+
devkit never copies its rules. A failure whose check id heads a section of
|
|
399
|
+
`VALIDATION-NOTES.md` (`## C-CSS-1 — Lean strategy`, `## C-CST-4 / C-CST-10 — …`)
|
|
400
|
+
is an accepted deviation — an architectural decision recorded per the rulebook —
|
|
401
|
+
and counts as a pass (`--verbose` lists them).
|
|
402
|
+
|
|
403
|
+
**What else doctor checks.** Beyond the presets, legacy files, fragment pages and
|
|
404
|
+
generated files:
|
|
405
|
+
|
|
406
|
+
- **Library outputs** — every `./dist/…` target in package.json (`main`,
|
|
407
|
+
`module`, `types`, `style`, `exports`) is a file the library build emits. Before
|
|
408
|
+
the switch to the preset it also compares the old `lib.name` with
|
|
409
|
+
`kmwc.library.globalName` — a renamed UMD global or CSS file breaks consumers
|
|
410
|
+
silently.
|
|
411
|
+
- **Ports** — no script pins a `--port` of its own, the repo's configs, Makefile
|
|
412
|
+
and docs stay inside its registry block, and no sibling checkout (a registry
|
|
413
|
+
entry at `../<id>`) listens on a port inside it.
|
|
414
|
+
- **Analytics** — no other repo uses the same Plausible site id.
|
|
415
|
+
- **Page CSS** — a page `<style>` or `examples-local.css` rule on a kit-owned
|
|
416
|
+
selector (`.kbd`, `kbd`, `mark`, `.pill`, `.card` …) and single-declaration
|
|
417
|
+
inline styles a kit class covers (`width` on a field → `.w-*`,
|
|
418
|
+
`margin-left: auto` → `.push-end`, `margin-top: 1rem` → `.mt-1`) are warnings.
|
|
419
|
+
|
|
420
|
+
`kmwc check-pages` is the browser half: it loads every example page and reports
|
|
421
|
+
what doctor can't see in the source — a script error, a 404, a fixed width that
|
|
422
|
+
pushes a phone sideways.
|
|
370
423
|
|
|
371
424
|
**Package contents.** Doctor runs `npm pack --dry-run` and fails on anything
|
|
372
425
|
dev-only that would reach a consumer's `node_modules`: `CLAUDE.md` at any
|
|
@@ -429,7 +482,11 @@ registry edits), and move the component's own code into that layout with
|
|
|
429
482
|
runner from the old Makefile, the canonical origin from the old
|
|
430
483
|
`examples-copy-link.js`, and internals from the old
|
|
431
484
|
`check-variable-manifest.mjs` (`cssVariables.internalVariables`).
|
|
432
|
-
3.
|
|
485
|
+
3. Run `npx kmwc doctor` **before** replacing `vite.config.ts`: it compares the
|
|
486
|
+
old `lib.name` with `kmwc.library.globalName` and package.json's `dist/`
|
|
487
|
+
targets with the preset's outputs — set `library.globalName` / `fileName` /
|
|
488
|
+
`cssFileName` to the published names so nothing gets renamed. Then replace
|
|
489
|
+
`vite.config.ts` (and any `vite.examples.config.ts`) with
|
|
433
490
|
`defineComponentConfig()`. Move genuinely specific bits to `plugins` and
|
|
434
491
|
`overrides`. Do the same for `playwright.config.ts`.
|
|
435
492
|
4. Pages: delete the `<link href="examples-shared.css">` and the
|
|
@@ -446,10 +503,11 @@ registry edits), and move the component's own code into that layout with
|
|
|
446
503
|
7. Set `"check:vars": "kmwc check-vars"` and run it.
|
|
447
504
|
8. `npx kmwc sync`, then review the diff. Repo-specific Makefile targets move
|
|
448
505
|
to `Makefile.local`.
|
|
449
|
-
9. Check it runs: `npm run dev`, `npm run build
|
|
450
|
-
`npm run preview:examples`, the e2e tests,
|
|
451
|
-
|
|
452
|
-
|
|
506
|
+
9. Check it runs: `npm run dev`, `npm run build` (compare `dist/` with the old
|
|
507
|
+
build), `npm run build:examples`, `npm run preview:examples`, the e2e tests,
|
|
508
|
+
`npx kmwc check-pages` (and `--url` on the preview port for the built site),
|
|
509
|
+
and finally `npx kmwc doctor` (devkit + rulebook — rulebook failures are real
|
|
510
|
+
findings to fix, or architectural decisions to record in `VALIDATION-NOTES.md`).
|
|
453
511
|
|
|
454
512
|
### Developing the devkit
|
|
455
513
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keenmate/web-components-devkit",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-rc07",
|
|
4
4
|
"description": "Shared dev, example-site and build infrastructure for Keenmate web components: Vite preset, examples kit (chapter nav, copy-link, controls store, shared styles), Playwright preset and the `kmwc` CLI (sync, doctor, check-vars, new, readme).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/cli/args.js
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Flag parser: --flag, --key value, --key=value, positionals. `flagSpec` names
|
|
3
|
+
* every flag a command takes — `booleans` never take a value, `values` always
|
|
4
|
+
* do. Anything else is an error: an ignored typo (or `--help` before it was
|
|
5
|
+
* handled) used to run the command for real.
|
|
6
|
+
*
|
|
7
|
+
* @param {string[]} args
|
|
8
|
+
* @param {{ booleans?: string[], values?: string[] }} [flagSpec]
|
|
9
|
+
*/
|
|
10
|
+
export function parseArgs(args, flagSpec = {}) {
|
|
11
|
+
const booleanFlags = new Set(flagSpec.booleans || []);
|
|
12
|
+
const valueFlags = new Set(['root', ...(flagSpec.values || [])]);
|
|
3
13
|
const out = { _: [] };
|
|
4
14
|
for (let i = 0; i < args.length; i++) {
|
|
5
|
-
const
|
|
6
|
-
if (!
|
|
7
|
-
out._.push(
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
if (!arg.startsWith('-') || arg === '-') {
|
|
17
|
+
out._.push(arg);
|
|
8
18
|
continue;
|
|
9
19
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
if (!arg.startsWith('--')) throw new Error(`unknown option "${arg}"`);
|
|
21
|
+
const [key, inline] = arg.slice(2).split(/=(.*)/s);
|
|
22
|
+
if (booleanFlags.has(key)) {
|
|
23
|
+
if (inline !== undefined) throw new Error(`--${key} takes no value`);
|
|
24
|
+
out[key] = true;
|
|
25
|
+
} else if (valueFlags.has(key)) {
|
|
26
|
+
if (inline !== undefined) out[key] = inline;
|
|
27
|
+
else if (args[i + 1] !== undefined && !args[i + 1].startsWith('--')) out[key] = args[++i];
|
|
28
|
+
else throw new Error(`--${key} needs a value`);
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(`unknown option "--${key}"`);
|
|
31
|
+
}
|
|
14
32
|
}
|
|
15
33
|
return out;
|
|
16
34
|
}
|
|
35
|
+
|
|
36
|
+
/** True when the arguments ask for help (`--help` or `-h` anywhere). */
|
|
37
|
+
export function checkIsHelpRequested(args) {
|
|
38
|
+
return args.some((arg) => arg === '--help' || arg === '-h');
|
|
39
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kmwc check-pages — load every example page in a real browser and report what
|
|
3
|
+
* a reader would hit: page errors, console errors, failed requests, custom
|
|
4
|
+
* elements that never get defined, and a page wider than the viewport (a fixed
|
|
5
|
+
* width or a long word that pushes a phone sideways).
|
|
6
|
+
*
|
|
7
|
+
* Runs against the repo's own dev server (started here on kmwc.ports.dev), or a
|
|
8
|
+
* server you already have (--url, e.g. the built site on the preview port).
|
|
9
|
+
* Widths default to desktop + two phones. Uses the component's own
|
|
10
|
+
* @playwright/test (see getPeerModule) — `npx playwright install chromium` once.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { getKmwcConfig, getPeerModule, loadPeerModule } from '../../config.js';
|
|
15
|
+
import { getExamplePages } from '../../vite/index.js';
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_WIDTHS = [1440, 390, 320];
|
|
18
|
+
const VIEWPORT_HEIGHT = 900;
|
|
19
|
+
const SETTLE_MS = 600;
|
|
20
|
+
const VITE_CONFIG_FILES = ['vite.config.ts', 'vite.config.mts', 'vite.config.mjs', 'vite.config.js'];
|
|
21
|
+
const MAX_OFFENDERS = 3;
|
|
22
|
+
// Requests a page doesn't control (favicon probes, analytics in a built site).
|
|
23
|
+
const IGNORED_REQUEST_PATTERN = /favicon\.ico|stats\.keenmate\.services|plausible/;
|
|
24
|
+
|
|
25
|
+
/** `--widths 1440,390` → [1440, 390]. Pure. */
|
|
26
|
+
export function parseWidths(widthsText) {
|
|
27
|
+
if (!widthsText) return DEFAULT_WIDTHS;
|
|
28
|
+
const widths = String(widthsText).split(',').map((width) => Number(width.trim()));
|
|
29
|
+
if (!widths.length || widths.some((width) => !Number.isInteger(width) || width < 200)) throw new Error(`--widths: "${widthsText}" is not a list of pixel widths ≥ 200`);
|
|
30
|
+
return widths;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Problems in one page load's report → [string]. Pure — exported for tests.
|
|
35
|
+
* @param {{ width: number, errors: string[], undefinedElements: string[], scrollWidth: number, clientWidth: number, offenders: string[] }} pageReport
|
|
36
|
+
*/
|
|
37
|
+
export function getPageProblems(pageReport) {
|
|
38
|
+
const problems = [...pageReport.errors];
|
|
39
|
+
if (pageReport.undefinedElements.length) problems.push(`never defined: <${pageReport.undefinedElements.join('>, <')}>`);
|
|
40
|
+
if (pageReport.scrollWidth > pageReport.clientWidth) {
|
|
41
|
+
const offenderText = pageReport.offenders.length ? ` — widest: ${pageReport.offenders.join(', ')}` : '';
|
|
42
|
+
problems.push(`page is ${pageReport.scrollWidth}px wide in a ${pageReport.clientWidth}px viewport${offenderText}`);
|
|
43
|
+
}
|
|
44
|
+
return problems;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Runs in the page: overflow, undefined custom elements. A culprit is the deepest
|
|
49
|
+
* element — or the text — past the right edge, outside anything that scrolls or
|
|
50
|
+
* clips on purpose (a code block, a table wrapper) and outside hidden or fixed
|
|
51
|
+
* layers (a closed nav panel, a toast), which don't widen the page.
|
|
52
|
+
*/
|
|
53
|
+
function inspectPage(maxOffenders) {
|
|
54
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
55
|
+
// Shadow roots count: a component's own internals can widen the page too.
|
|
56
|
+
const getParent = (element) => element.parentElement || element.getRootNode().host || null;
|
|
57
|
+
const describeOne = (element) => `${element.tagName.toLowerCase()}${element.id ? `#${element.id}` : ''}${[...element.classList].slice(0, 2).map((name) => `.${name}`).join('')}`;
|
|
58
|
+
const describe = (element) => {
|
|
59
|
+
const host = element.getRootNode().host;
|
|
60
|
+
return host ? `${describe(host)} › ${describeOne(element)}` : describeOne(element);
|
|
61
|
+
};
|
|
62
|
+
const getAllElements = (root) => [...root.querySelectorAll('*')].flatMap((element) => (element.shadowRoot ? [element, ...getAllElements(element.shadowRoot)] : [element]));
|
|
63
|
+
const checkIsContained = (element) => {
|
|
64
|
+
for (let current = element; current && current !== document.body; current = getParent(current)) {
|
|
65
|
+
const style = getComputedStyle(current);
|
|
66
|
+
if (style.position === 'fixed' || style.visibility === 'hidden' || style.display === 'none') return true;
|
|
67
|
+
if (current !== element && ['auto', 'scroll', 'hidden', 'clip'].includes(style.overflowX)) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
71
|
+
const allElements = getAllElements(document.body);
|
|
72
|
+
const pastEdge = allElements.filter((element) => {
|
|
73
|
+
const box = element.getBoundingClientRect();
|
|
74
|
+
return box.width > 0 && box.right > viewportWidth + 1 && !checkIsContained(element);
|
|
75
|
+
});
|
|
76
|
+
const checkContainsDeep = (element, other) => {
|
|
77
|
+
for (let current = other; current; current = getParent(current)) if (current === element) return true;
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
const offenders = pastEdge.filter((element) => !pastEdge.some((other) => other !== element && checkContainsDeep(element, other))).map(describe);
|
|
81
|
+
// Text past the edge inside a box that fits: a long word with no break opportunity.
|
|
82
|
+
const textRoots = [document.body, ...allElements.filter((element) => element.shadowRoot).map((element) => element.shadowRoot)];
|
|
83
|
+
const textNodes = textRoots.flatMap((root) => {
|
|
84
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
85
|
+
const nodes = [];
|
|
86
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) nodes.push(node);
|
|
87
|
+
return nodes;
|
|
88
|
+
});
|
|
89
|
+
for (const textNode of textNodes) {
|
|
90
|
+
const parent = textNode.parentElement;
|
|
91
|
+
if (!textNode.textContent.trim() || !parent || pastEdge.includes(parent)) continue;
|
|
92
|
+
const range = document.createRange();
|
|
93
|
+
range.selectNodeContents(textNode);
|
|
94
|
+
const box = range.getBoundingClientRect();
|
|
95
|
+
const parentStyle = getComputedStyle(parent);
|
|
96
|
+
if (box.right > viewportWidth + 1 && !['hidden', 'clip', 'auto', 'scroll'].includes(parentStyle.overflowX) && !checkIsContained(parent)) {
|
|
97
|
+
offenders.push(`${describe(parent)} "${textNode.textContent.trim().slice(0, 40)}"`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const undefinedElements = [...new Set([...document.querySelectorAll(':not(:defined)')].map((element) => element.localName).filter((name) => name.includes('-')))];
|
|
101
|
+
return {
|
|
102
|
+
scrollWidth: document.documentElement.scrollWidth,
|
|
103
|
+
clientWidth: viewportWidth,
|
|
104
|
+
offenders: [...new Set(offenders)].slice(0, maxOffenders),
|
|
105
|
+
undefinedElements,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function loadPageReport(browser, pageUrl, width) {
|
|
110
|
+
const page = await browser.newPage({ viewport: { width, height: VIEWPORT_HEIGHT } });
|
|
111
|
+
const errors = [];
|
|
112
|
+
page.on('pageerror', (error) => errors.push(`page error: ${error.message.split('\n')[0]}`));
|
|
113
|
+
page.on('console', (message) => {
|
|
114
|
+
if (message.type() === 'error') errors.push(`console error: ${message.text().split('\n')[0]}`);
|
|
115
|
+
});
|
|
116
|
+
page.on('response', (response) => {
|
|
117
|
+
if (response.status() >= 400 && !IGNORED_REQUEST_PATTERN.test(response.url())) errors.push(`HTTP ${response.status()}: ${response.url()}`);
|
|
118
|
+
});
|
|
119
|
+
page.on('requestfailed', (request) => {
|
|
120
|
+
if (!IGNORED_REQUEST_PATTERN.test(request.url())) errors.push(`request failed: ${request.url()} (${request.failure()?.errorText})`);
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
await page.goto(pageUrl, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
124
|
+
await page.waitForTimeout(SETTLE_MS);
|
|
125
|
+
const inspection = await page.evaluate(inspectPage, MAX_OFFENDERS);
|
|
126
|
+
return { width, errors: [...new Set(errors)], ...inspection };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return { width, errors: [...new Set([...errors, `load failed: ${error.message.split('\n')[0]}`])], scrollWidth: 0, clientWidth: 0, offenders: [], undefinedElements: [] };
|
|
129
|
+
} finally {
|
|
130
|
+
await page.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function startDevServer(config) {
|
|
135
|
+
const viteModule = await loadPeerModule(config.root, 'vite');
|
|
136
|
+
// Vite < 6 resolves to its CJS build here: the API sits on `default`.
|
|
137
|
+
const createServer = viteModule.createServer || viteModule.default?.createServer;
|
|
138
|
+
// Run like `npm run dev` in the repo: the preset reads package.json#kmwc from the cwd,
|
|
139
|
+
// and Vite looks for vite.config there too.
|
|
140
|
+
process.chdir(config.root);
|
|
141
|
+
const configFile = VITE_CONFIG_FILES.map((fileName) => join(config.root, fileName)).find((filePath) => existsSync(filePath));
|
|
142
|
+
const server = await createServer({ root: config.root, configFile: configFile || false, logLevel: 'error', server: { port: config.ports.dev, strictPort: true } });
|
|
143
|
+
try {
|
|
144
|
+
await server.listen();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
await server.close();
|
|
147
|
+
throw new Error(
|
|
148
|
+
`port ${config.ports.dev} is busy (${error.code || error.message}). If that is this repo's dev server, run: kmwc check-pages --url http://localhost:${config.ports.dev}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return server;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function run(args) {
|
|
155
|
+
const config = getKmwcConfig(args.root);
|
|
156
|
+
const widths = parseWidths(args.widths);
|
|
157
|
+
const pageFiles = Object.values(getExamplePages(config)).map((pagePath) => pagePath.replace(/\\/g, '/').split('/').pop());
|
|
158
|
+
if (!pageFiles.length) {
|
|
159
|
+
console.log('kmwc check-pages: no example pages — nothing to check.');
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
let chromium;
|
|
163
|
+
try {
|
|
164
|
+
({ chromium } = getPeerModule(config.root, '@playwright/test'));
|
|
165
|
+
} catch {
|
|
166
|
+
throw new Error('@playwright/test is not installed in this repo — npm i -D @playwright/test && npx playwright install chromium');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const server = args.url ? null : await startDevServer(config);
|
|
170
|
+
const baseUrl = (args.url || `http://localhost:${config.ports.dev}`).replace(/\/$/, '');
|
|
171
|
+
const browser = await chromium.launch();
|
|
172
|
+
let problemCount = 0;
|
|
173
|
+
try {
|
|
174
|
+
console.log(`kmwc check-pages — ${pageFiles.length} page(s) × ${widths.join(' / ')}px on ${baseUrl}\n`);
|
|
175
|
+
for (const pageFile of pageFiles) {
|
|
176
|
+
const pageProblems = [];
|
|
177
|
+
for (const width of widths) {
|
|
178
|
+
for (const problem of getPageProblems(await loadPageReport(browser, `${baseUrl}/${pageFile}`, width))) pageProblems.push(`@${width}px ${problem}`);
|
|
179
|
+
}
|
|
180
|
+
problemCount += pageProblems.length;
|
|
181
|
+
console.log(`${pageProblems.length ? '✗' : '✓'} ${pageFile}${pageProblems.map((problem) => `\n ${problem}`).join('')}`);
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
await browser.close();
|
|
185
|
+
if (server) await server.close();
|
|
186
|
+
}
|
|
187
|
+
console.log(`\n${problemCount ? '✗' : '✓'} ${problemCount} problem(s)`);
|
|
188
|
+
return problemCount ? 1 : 0;
|
|
189
|
+
}
|