@webjsdev/cli 0.10.56 → 0.10.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +6 -1
  2. package/bin/webjs.js +219 -9
  3. package/lib/app-tasks.js +70 -10
  4. package/lib/check-target.js +1 -1
  5. package/lib/ci-config.js +250 -0
  6. package/lib/ci-runner.js +499 -0
  7. package/lib/create.js +59 -2
  8. package/lib/doctor/codes.js +1 -0
  9. package/lib/doctor/probes/framework-resolves.js +182 -6
  10. package/lib/doctor/runner.js +2 -1
  11. package/lib/doctor.js +1 -1
  12. package/lib/run-tasks.js +23 -3
  13. package/package.json +3 -3
  14. package/templates/.agents/rules/workflow.md +19 -12
  15. package/templates/.agents/skills/webjs/SKILL.md +6 -2
  16. package/templates/.agents/skills/webjs/references/built-ins.md +45 -1
  17. package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +32 -3
  18. package/templates/.agents/skills/webjs/references/components.md +9 -1
  19. package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +26 -2
  20. package/templates/.agents/skills/webjs/references/optimistic-ui.md +23 -11
  21. package/templates/.agents/skills/webjs/references/runtime.md +1 -1
  22. package/templates/.agents/skills/webjs/references/styling.md +48 -3
  23. package/templates/.agents/skills/webjs/references/testing.md +11 -0
  24. package/templates/.agents/skills/webjs/references/ui-kit.md +25 -0
  25. package/templates/.github/pull_request_template.md +3 -8
  26. package/templates/.github/workflows/ci.yml +38 -88
  27. package/templates/.hooks/pre-commit +5 -4
  28. package/templates/gallery/app/features/client-router/page.ts +5 -1
  29. package/templates/gallery/app/features/metadata/page.ts +7 -1
  30. package/templates/gallery/modules/client-router/components/router-controls.ts +7 -0
  31. package/templates/gallery/modules/stream/components/browser/stream-demo.test.js +64 -0
  32. package/templates/gallery/modules/stream/components/stream-demo.ts +14 -9
  33. package/templates/gallery/modules/stream/utils/ui/row.ts +41 -0
  34. package/templates/gallery/test/rate-limit/rate-limit.test.ts +2 -1
  35. package/templates/partials/agents-playbook-api.md +14 -8
  36. package/templates/partials/agents-playbook-fullstack.md +16 -10
@@ -13,6 +13,15 @@
13
13
  // region-swap would clobber.
14
14
  import { WebComponent, html, renderStream } from '@webjsdev/core';
15
15
  import { buttonClass } from '#components/ui/button.ts';
16
+ // The row markup lives in ONE place, a feature-local view fragment under
17
+ // `utils/ui/` (see references/styling.md for that folder). A fragment rather
18
+ // than a display-only <stream-row> element because BOTH of this demo's uses
19
+ // rule an element out, which is the test to apply: the seeded list needs a
20
+ // direct `<ul> > <li>` child (a wrapper tag would sit between them and break
21
+ // the selector and the list semantics), and the streamed payload is an HTML
22
+ // STRING, which a component cannot produce at all. Where a wrapper IS fine,
23
+ // prefer the component.
24
+ import { streamRow, streamRowHTML } from '../utils/ui/row.ts';
16
25
 
17
26
  // Build a <webjs-stream> payload string. It is a plain string (NOT an html``
18
27
  // template), so interpolating the row markup here is fine. `remove` needs no
@@ -22,9 +31,6 @@ function streamPayload(action: string, target: string, inner = '') {
22
31
  return `<webjs-stream action="${action}" target="${target}">${body}</webjs-stream>`;
23
32
  }
24
33
 
25
- const rowCls = 'flex items-center gap-2 px-3 py-2 rounded-xl bg-card border border-border text-[15px] text-foreground';
26
- const row = (id: string, label: string) => `<li id="${id}" class="${rowCls}">${label}</li>`;
27
-
28
34
  export class StreamDemo extends WebComponent {
29
35
  // A plain instance field, NOT a signal: incremented to mint unique row ids.
30
36
  // It is never read inside render(), so appending a row does not re-render the
@@ -37,16 +43,16 @@ export class StreamDemo extends WebComponent {
37
43
  // never runs. Name your handlers something else (see muscle-memory-gotchas).
38
44
  appendRow() {
39
45
  this.#n++;
40
- renderStream(streamPayload('append', 'stream-list', row(`row-${this.#n}`, `Row ${this.#n} (appended)`)));
46
+ renderStream(streamPayload('append', 'stream-list', streamRowHTML(`row-${this.#n}`, `Row ${this.#n} (appended)`)));
41
47
  }
42
48
  prependRow() {
43
49
  this.#n++;
44
- renderStream(streamPayload('prepend', 'stream-list', row(`row-${this.#n}`, `Row ${this.#n} (prepended)`)));
50
+ renderStream(streamPayload('prepend', 'stream-list', streamRowHTML(`row-${this.#n}`, `Row ${this.#n} (prepended)`)));
45
51
  }
46
52
  replaceFirst() {
47
53
  // `replace` swaps the target element itself. The replacement keeps id row-1,
48
54
  // so the button stays repeatable.
49
- renderStream(streamPayload('replace', 'row-1', row('row-1', 'Row 1 (replaced)')));
55
+ renderStream(streamPayload('replace', 'row-1', streamRowHTML('row-1', 'Row 1 (replaced)')));
50
56
  }
51
57
  removeSecond() {
52
58
  // `remove` deletes the target and needs no <template>.
@@ -54,7 +60,7 @@ export class StreamDemo extends WebComponent {
54
60
  }
55
61
  reset() {
56
62
  // `update` replaces the target's children, restoring the seed list.
57
- renderStream(streamPayload('update', 'stream-list', row('row-1', 'Row 1') + row('row-2', 'Row 2')));
63
+ renderStream(streamPayload('update', 'stream-list', streamRowHTML('row-1', 'Row 1') + streamRowHTML('row-2', 'Row 2')));
58
64
  }
59
65
 
60
66
  render() {
@@ -71,8 +77,7 @@ export class StreamDemo extends WebComponent {
71
77
  <!-- The target list. renderStream() mutates it by id; this markup renders
72
78
  once and is never re-rendered by the component. -->
73
79
  <ul id="stream-list" class="grid gap-2 m-0 p-0 list-none">
74
- <li id="row-1" class="flex items-center gap-2 px-3 py-2 rounded-xl bg-card border border-border text-[15px] text-foreground">Row 1</li>
75
- <li id="row-2" class="flex items-center gap-2 px-3 py-2 rounded-xl bg-card border border-border text-[15px] text-foreground">Row 2</li>
80
+ ${streamRow('row-1', 'Row 1')}${streamRow('row-2', 'Row 2')}
76
81
  </ul>
77
82
  </div>
78
83
  `;
@@ -0,0 +1,41 @@
1
+ // A feature-local VIEW FRAGMENT: pure, returns markup, used only by the stream
2
+ // demo. It lives in `modules/<feature>/utils/ui/` rather than `utils/` (which
3
+ // holds helpers returning DATA) or `components/` (which holds custom elements).
4
+ // See .agents/skills/webjs/references/styling.md.
5
+ //
6
+ // WHY a fragment and not a display-only <stream-row> element: an element is a
7
+ // tag in the DOM, and neither caller can carry one. The list wants a direct
8
+ // `<ul> > <li>` child, so a wrapper would break that selector and the list
9
+ // semantics; and <webjs-stream> takes its payload as an HTML STRING, which no
10
+ // component can return. That is the test, not bytes: where a wrapper element is
11
+ // harmless, reach for the component instead.
12
+ //
13
+ // Hence the two shapes below, off one class list, so the streamed row and the
14
+ // seeded rows cannot drift apart. That drift was live: the class list used to
15
+ // exist three times in this feature, once in a `rowCls` const and twice inlined
16
+ // in the component's own template, which did not use the const.
17
+ import { html, escapeAttr, escapeText } from '@webjsdev/core';
18
+
19
+ const ROW =
20
+ 'flex items-center gap-2 px-3 py-2 rounded-xl bg-card border border-border text-[15px] text-foreground';
21
+
22
+ /**
23
+ * The row as a template, for the component's own render(). `unknown` on the
24
+ * label because it forwards straight into an `html` hole, which renders a
25
+ * string, a number, a TemplateResult, or an array of those.
26
+ */
27
+ export function streamRow(id: string, label: unknown) {
28
+ return html`<li id=${id} class=${ROW}>${label}</li>`;
29
+ }
30
+
31
+ /**
32
+ * The same row as an HTML string, for a <webjs-stream> template payload.
33
+ *
34
+ * ESCAPE EVERY HOLE HERE. The `html` tag above escapes its own holes; this
35
+ * plain template literal does NOT, so an unescaped value becomes markup the
36
+ * moment renderStream() puts it in the document. The demo only ever passes its
37
+ * own literals, but this is the shape people copy, so it does the safe thing.
38
+ */
39
+ export function streamRowHTML(id: string, label: string): string {
40
+ return `<li id="${escapeAttr(id)}" class="${ROW}">${escapeText(label)}</li>`;
41
+ }
@@ -5,6 +5,7 @@ import { dirname, resolve } from 'node:path';
5
5
 
6
6
  import { createRequestHandler } from '@webjsdev/server';
7
7
  import { testRequest } from '@webjsdev/server/testing';
8
+ import type { Handle } from '@webjsdev/server/testing';
8
9
 
9
10
  const appDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
10
11
 
@@ -22,7 +23,7 @@ const MAX = 5;
22
23
  // X-Forwarded-For that DISAGREES, standing in for the CDN egress address the
23
24
  // real deploy puts there, so a test that passes only because the two agree
24
25
  // cannot exist.
25
- function ping(handle: (req: Request) => Promise<Response>, visitor: string, cdnEgress = '172.68.1.9') {
26
+ function ping(handle: Handle, visitor: string, cdnEgress = '172.68.1.9') {
26
27
  return testRequest(handle, PING, {
27
28
  headers: { 'cf-connecting-ip': visitor, 'x-forwarded-for': cdnEgress },
28
29
  });
@@ -44,15 +44,20 @@ cross-origin access use the `cors()` middleware from `@webjsdev/server`; with
44
44
 
45
45
  ### 5. Verify before you call it done
46
46
 
47
- Run each of these and fix what it reports, in order:
47
+ Run `npm run ci` and fix what it reports. It is one command for every gate,
48
+ the step list declared in `package.json` under `webjs.ci`, with a result line
49
+ per step:
48
50
 
49
- - `npm run check` (correctness: no browser-import or boundary violation).
50
- - `npm run doctor` (project health; CI runs it too). It fails on whatever
51
- `package.json` `webjs.doctor.gate` marks `error`, plus the two hard toolchain
52
- checks that are fatal with no gate entry, `NODE_VERSION` and
53
- `TSCONFIG_ERASABLE`.
54
- - `npm run typecheck` (zero type errors).
55
- - `npm test` (unit tests for the endpoints and modules you built).
51
+ - `webjs check` (correctness: no browser-import or boundary violation).
52
+ - `webjs doctor` (project health). It fails on whatever `package.json`
53
+ `webjs.doctor.gate` marks `error`, plus the two hard toolchain checks that
54
+ are fatal with no gate entry, `NODE_VERSION` and `TSCONFIG_ERASABLE`.
55
+ - `webjs typecheck` (zero type errors).
56
+ - A dependency audit.
57
+ - The test layers for the endpoints and modules you built.
58
+
59
+ The GitHub workflow runs the same list, so a green local run predicts CI.
60
+ While iterating, `npm run ci -- --only Tests` runs one layer.
56
61
 
57
62
  Then boot `npm run dev` and probe each endpoint for the expected status and JSON
58
63
  shape.
@@ -66,6 +71,7 @@ npm run dev # dev server at http://localhost:8080
66
71
  npm run start # production server
67
72
  npm test # unit + browser tests
68
73
  npm run typecheck
74
+ npm run ci # every gate, one command (the webjs.ci steps in package.json)
69
75
  npm run check # correctness checks
70
76
  npm run doctor # project health (severity per check: webjs.doctor.gate)
71
77
  npm run db:generate && npm run db:migrate
@@ -95,16 +95,21 @@ accessor). Use the shorthand for primitives
95
95
 
96
96
  ### 7. Verify before you call it done
97
97
 
98
- Run each of these and fix what it reports, in order:
99
-
100
- - `npm run check` (correctness: no browser-import or boundary violation).
101
- - `npm run doctor` (project health; CI runs it too). It fails on whatever
102
- `package.json` `webjs.doctor.gate` marks `error`, plus the two hard toolchain
103
- checks that are fatal with no gate entry, `NODE_VERSION` and
104
- `TSCONFIG_ERASABLE`.
105
- - `npm run typecheck` (zero type errors).
106
- - `npm test` (unit and browser tests for the features you built).
107
- - `npm run css:build` (compile Tailwind).
98
+ Run `npm run ci` and fix what it reports. It is one command for every gate,
99
+ the step list declared in `package.json` under `webjs.ci`, with a result line
100
+ per step:
101
+
102
+ - `webjs check` (correctness: no browser-import or boundary violation).
103
+ - `webjs doctor` (project health). It fails on whatever `package.json`
104
+ `webjs.doctor.gate` marks `error`, plus the two hard toolchain checks that
105
+ are fatal with no gate entry, `NODE_VERSION` and `TSCONFIG_ERASABLE`.
106
+ - `webjs typecheck` (zero type errors).
107
+ - A dependency audit.
108
+ - The server, browser, and e2e test layers for the features you built.
109
+
110
+ The GitHub workflow runs the same list, so a green local run predicts CI.
111
+ While iterating, `npm run ci -- --only Tests` runs one layer. Then
112
+ `npm run css:build` (compile Tailwind).
108
113
 
109
114
  Then boot `npm run dev`, confirm every page route returns HTTP 200, and open
110
115
  every route you changed in a real browser and play through its states: `check`
@@ -121,6 +126,7 @@ npm run start # production server
121
126
  npm test # unit + browser tests
122
127
  npm run typecheck
123
128
  npm run css:build # compile Tailwind
129
+ npm run ci # every gate, one command (the webjs.ci steps in package.json)
124
130
  npm run check # correctness checks
125
131
  npm run doctor # project health (severity per check: webjs.doctor.gate)
126
132
  npx webjsdev ui add <name> # copy a ui primitive into components/ui/