@webjsdev/cli 0.10.57 → 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.
@@ -50,15 +50,21 @@ Read `AGENTS.md` first. Full hosted docs are at https://webjs.dev/docs.
50
50
  2. Browser tests in `test/<feature>/browser/*.test.js` for hydration, DOM, slots,
51
51
  and the client router.
52
52
  3. Documentation stays in sync on the SAME PR as the code, never a follow-up.
53
- 4. `npm run check` must pass (correctness), and so must `npm run doctor`
54
- (project health). CI runs both. Doctor fails on whatever your `package.json`
55
- `webjs.doctor.gate` marks `error`, which starts as the un-versioned
56
- stylesheet link check, plus the two hard toolchain checks that default to
57
- `error` with no gate entry at all: `NODE_VERSION` (the Node floor) and
58
- `TSCONFIG_ERASABLE` (`erasableSyntaxOnly` missing from an existing
59
- tsconfig), either of which would 500 the app at runtime. Everything else it
60
- reports is a warning that cannot fail the build. Widen or narrow the gate in
61
- `package.json` rather than in the workflow.
53
+ 4. `npm run ci` must pass before you push. It runs the step list declared in
54
+ `package.json` under `webjs.ci`, one result line per step: `webjs check`
55
+ (correctness), `webjs doctor` (project health), `webjs typecheck`, a
56
+ dependency audit, then the server, browser, and e2e test layers. The GitHub
57
+ workflow runs the same list on every PR and push, so the two cannot drift;
58
+ `npm run ci -- --only Tests` runs one layer while you iterate, and
59
+ `npm run ci -- --signoff` posts a green commit status (basecamp/gh-signoff)
60
+ a branch-protection rule can require. Doctor fails on whatever your
61
+ `package.json` `webjs.doctor.gate` marks `error`, which starts as the
62
+ un-versioned stylesheet link check, plus the two hard toolchain checks that
63
+ default to `error` with no gate entry at all: `NODE_VERSION` (the Node
64
+ floor) and `TSCONFIG_ERASABLE` (`erasableSyntaxOnly` missing from an
65
+ existing tsconfig), either of which would 500 the app at runtime. Everything
66
+ else it reports is a warning that cannot fail the build. Widen or narrow the
67
+ gate, and the step list, in `package.json` rather than in the workflow.
62
68
 
63
69
  How a PR gets REVIEWED is deliberately not specified here. Use whatever your
64
70
  team already does. WebJs has opinions about the code (the conventions above,
@@ -264,6 +264,7 @@ Success is a 303 (PRG); failure re-renders the page at 422 with the result on `a
264
264
 
265
265
  ## Testing Defaults
266
266
 
267
+ - `npm run ci` before every push: it runs the `webjs.ci` step list in `package.json` (correctness, project health, types, a dependency audit, then the server, browser, and e2e test layers) with a result line per step, and CI runs the same list, so a green local run predicts the pipeline. `npm run ci -- --only Tests` runs one layer while iterating. See `references/testing.md` and `references/built-ins.md`.
267
268
  - Prefer server/handler tests first: drive the app with `handle()` from `@webjsdev/server/testing` and assert on the `Response`.
268
269
  - Add a browser test (`npm run test:browser`) for anything touching hydration, the client router, slots, or custom-element upgrade. A unit test is necessary but NOT sufficient for a browser-facing change.
269
270
  - Render the app and LOOK for any UI change: `npm run check` and `npm run typecheck` pass even when a layout collapses. Static tools give no signal for a visual defect.
@@ -216,6 +216,48 @@ An over-limit body responds `413` without buffering the whole payload.
216
216
 
217
217
  `before` runs to completion first (a non-zero exit aborts the boot). `parallel` (dev only) runs long-lived watchers alongside the server and tears them down on exit. `watch` (dev only) adds extra live-reload directories outside the app tree.
218
218
 
219
+ ### Local CI (`webjs.ci`)
220
+
221
+ `webjs ci` runs the step list the block declares, the Rails 8.1 `bin/ci` posture: your machine is the first CI runner, and a cloud pipeline runs the SAME list by calling `npm run ci`, so the two cannot drift. Each step prints a heading, then `✅ <title> passed in 2.11s` or `❌ <title> failed in 0.01s`; the run ends with every failure listed and one total line, and exits 1 on any failure.
222
+
223
+ ```jsonc
224
+ { "webjs": { "ci": { "steps": [
225
+ { "title": "Setup", "run": "webjs db migrate" },
226
+ { "title": "Checks", "parallel": 2, "steps": [ // two at a time
227
+ "webjs check", // a string is a command titled by itself
228
+ { "title": "Types", "run": "webjs typecheck" },
229
+ { "title": "Tests", "steps": [ // a nested group takes ONE slot, runs in order
230
+ { "title": "Tests: server", "run": "webjs test --server" },
231
+ { "title": "Tests: e2e", "run": "webjs test --server", "env": { "WEBJS_E2E": "1" } }
232
+ ] }
233
+ ] }
234
+ ] } } }
235
+ ```
236
+
237
+ A step is a string, a `{ title, run, env? }` command, or a `{ title, steps, parallel? }` group. `parallel` is a slot count (default 1); a parallel group captures each step's output and replays it whole when the step finishes, so two steps never interleave, and a group nested inside it takes one slot and runs sequentially (it cannot declare `parallel`, which the reader reports rather than honours). `env` is per-step, so the e2e opt-in does not depend on a shell prefix. Every child runs with `CI=true`, `node_modules/.bin` on PATH, and `.env` loaded first (a real env var wins), so a `webjs db migrate` step sees `DATABASE_URL`.
238
+
239
+ Flags: `-f` / `--fail-fast` stops after the first failure (the default runs everything and lists every failure), `--only <title>` runs one step or group by title (repeatable, an unknown title is an error rather than an empty green run), `--json` emits one document on stdout (`{ ok, seconds, steps: [{ title, run, group, ok, code, seconds, output? }] }`, failed steps carrying their captured output, the human report on stderr) for an agent loop, and `--signoff` runs `gh signoff` after a green run. To hold a merge until a LOCAL run is green, install `basecamp/gh-signoff`, run `gh signoff install` once (a branch-protection rule requiring the `signoff` status), and run `npm run ci -- --signoff`; a red run posts nothing.
240
+
241
+ Under GitHub Actions each step is a `::group::` in the log, a failed step is an `::error::` annotation, and a step table is appended to the job summary, so a single job running the whole list still names the layer that broke. The scaffold's workflow is exactly that one job; `webjs create --skip-ci` omits it and the local list always ships. A malformed block (a group with no title, a nested `parallel`, an unknown key) refuses to run and names every problem by JSON path, because a silently dropped step is a check that never ran. Nothing declared is exit 1 too, naming any workspace member that declares one, since "ran zero steps" would read as green.
242
+
243
+ ### Bring your own ORM (`webjs.db`)
244
+
245
+ Drizzle is the scaffold DEFAULT, not lock-in. The runtime never imports it, `db/connection.server.ts` is the app's own file, and `webjs db` is adapter-driven: a `db` block maps each verb to the shell command `webjs db <verb>` runs instead of the drizzle-kit default (node_modules/.bin on PATH like a `before` step, extra CLI args appended).
246
+
247
+ ```jsonc
248
+ { "webjs": {
249
+ "db": {
250
+ "generate": "prisma migrate dev --create-only",
251
+ "migrate": "prisma migrate deploy",
252
+ "push": "prisma db push",
253
+ "studio": "prisma studio",
254
+ "reset": "prisma migrate reset --force"
255
+ }
256
+ } }
257
+ ```
258
+
259
+ Any key is a verb (`reset` above adds `webjs db reset`). A verb the block does not name keeps its default (drizzle-kit for `generate` / `migrate` / `push` / `studio`, `db/seed.server.ts` for `seed`), so an app with no block is unchanged and the scaffold emits none. The payoff is that `webjs db migrate` stays one spelling across ORMs, so the scaffolded `dev.before` / `start.before`, the Dockerfile, CI, and the deploy docs all keep working after a swap. Write the bare binary (`prisma migrate deploy`), not `npx prisma ...`, since a pure Bun image has no `npx`. The swap itself is the app's own files: replace `db/connection.server.ts` with the new client, drop `drizzle.config.ts` / `db/columns.server.ts`, and keep server-only imports behind `.server.ts` as before.
260
+
219
261
  ### Doctor severity gate
220
262
 
221
263
  `webjs doctor` reports project health, and by default only a broken toolchain fails the exit. `--strict` makes EVERY warning fatal, which is unusable in CI, because four checks are environment-shaped: `GIT_HOOK` wants a local pre-commit hook a runner has no reason to have, `ENV_DRIFT` compares against a `.env` CI does not carry, `VENDOR_PIN` fetches the network, and `FRAMEWORK_RESOLVE` plus `FRAMEWORK_LINKS` depend on the environment. So per-check severity is CONFIG, keyed by the stable code every result carries.
@@ -115,7 +115,7 @@ So near an island the fragment is the smaller and more predictable choice, not a
115
115
 
116
116
  ### A design system for repeated PRIMITIVES: class helpers built on `@webjsdev/ui`
117
117
 
118
- An `html`-fragment helper is right for a repeated CHUNK of markup (the rubric above). For a repeated UI PRIMITIVE (button, input, card, badge) that needs variants and sizes, use a class helper instead: a function that returns a Tailwind class STRING you spread onto a native element. That is exactly what `@webjsdev/ui` ships (`buttonClass({ variant, size })`, `cardClass()`, `inputClass()`, `badgeClass({ variant })`), and it is what the scaffold gallery uses in `components/ui/`. To style a ONE-OFF that a variant does not cover (a circular icon button, a pill), compose the helper and override the bespoke bits with `cn()`: `cn(buttonClass({ variant: 'secondary', size: 'none' }), 'w-9 h-9 rounded-full')`. `cn` resolves Tailwind conflicts so a later class wins, including a shorthand over the axis it subsumes (`p-0` beats an earlier `px-4 py-2`), so an override just works. Conflicts are keyed on the CSS PROPERTY wherever `cn` can tell the properties apart, rather than on the shared class prefix, so the common prefix collisions do NOT evict: `cn('border-2', 'border-primary')` keeps both (a width and a colour), `cn('flex', 'flex-1')` keeps both (a `display` and a `flex-grow`, the shape an element that is both a flex container and a flex child needs), `cn('shadow-lg', 'shadow-red-500')` keeps both (a box-shadow and its colour), `cn('bg-clip-text', 'bg-primary')` keeps both (a clip and a colour, so the gradient-text idiom survives a later background), and an arbitrary value carrying a type hint is read as the property the hint names (`cn('shadow-lg', 'shadow-[color:red]')` keeps both). It is a small hand-rolled merger, not `tailwind-merge`, so it is still coarse in two ways. A prefix outside the families it knows is not grouped at all, so both classes are emitted and the winner is left to compiled stylesheet order (`inset-shadow-sm` against `inset-shadow-red-500`, `ring-2` against `ring-red-500`). And where one prefix carries two properties it reads the value against Tailwind's DEFAULT scales, so a `@theme`-extended name it cannot know about can still be misread and evict the wrong class: a custom `--shadow-card` makes `shadow-card` a box-shadow, but `cn` sees an unfamiliar name under a prefix whose bare names are usually colours and treats it as one. When an override has to win and you are unsure, pass the one class rather than layering, or install `clsx` + `tailwind-merge` and replace the helper (its header comment shows the swap). For an icon button prefer `size: 'none'` (it states "I supply my own box" by dropping the helper's padding + radius) over layering a `p-0` on top of the default size.
118
+ An `html`-fragment helper is right for a repeated CHUNK of markup (the rubric above). For a repeated UI PRIMITIVE (button, input, card, badge) that needs variants and sizes, use a class helper instead: a function that returns a Tailwind class STRING you spread onto a native element. That is exactly what `@webjsdev/ui` ships (`buttonClass({ variant, size })`, `cardClass()`, `inputClass()`, `badgeClass({ variant })`), and it is what the scaffold gallery uses in `components/ui/`. To style a ONE-OFF that a variant does not cover (a circular icon button, a pill), compose the helper and override the bespoke bits with `cn()`: `cn(buttonClass({ variant: 'secondary', size: 'none' }), 'w-9 h-9 rounded-full')`. `cn` resolves Tailwind conflicts so a later class wins, including a shorthand over the axis it subsumes (`p-0` beats an earlier `px-4 py-2`), so an override just works. Conflicts are keyed on the CSS PROPERTY wherever `cn` can tell the properties apart, rather than on the shared class prefix, so the common prefix collisions do NOT evict: `cn('border-2', 'border-primary')` keeps both (a width and a colour), `cn('flex', 'flex-1')` keeps both (a `display` and a `flex-grow`, the shape an element that is both a flex container and a flex child needs), `cn('shadow-lg', 'shadow-red-500')` keeps both (a box-shadow and its colour), `cn('bg-clip-text', 'bg-primary')` keeps both (a clip and a colour, so the gradient-text idiom survives a later background), and an arbitrary value carrying a type hint is read as the property the hint names (`cn('shadow-lg', 'shadow-[color:red]')` keeps both). It is a small hand-rolled merger, not `tailwind-merge`, so it is still coarse in two ways. A prefix outside the families it knows is not grouped at all, so both classes are emitted and the winner is left to compiled stylesheet order (`inset-shadow-sm` against `inset-shadow-red-500`, `ring-2` against `ring-red-500`). And where one prefix carries two properties it reads the value against Tailwind's DEFAULT scales, so a `@theme`-extended name it cannot know about can still be misread and evict the wrong class: a custom `--shadow-card` makes `shadow-card` a box-shadow, but `cn` sees an unfamiliar name under a prefix whose bare names are usually colours and treats it as one. When an override has to win and you are unsure, pass the one class rather than layering, or install `clsx` + `tailwind-merge` and replace the helper (its header comment shows the swap). For an icon button prefer `size: 'none'` (it states "I supply my own box" by dropping the helper's padding + radius) over layering a `p-0` on top of the default size. Under the opt-in `webjsui lint` (configured by a `lint` block in `components.json`, see `references/ui-kit.md`), `w-9 h-9` is layout and `rounded-full` is shape in the shadcn category taxonomy, so an app running it sets `no-restyle` to `allow: ["layout", "rounded"]` for this idiom to pass: the plain radius group is granted by name without opening the whole `shape` category, and `border-2` beside a helper still fires.
119
119
 
120
120
  ```ts
121
121
  // components/ui/button.ts (npx webjsdev ui add button, themed to your app)
@@ -189,6 +189,17 @@ WEBJS_ELIDE=0 npm run test:e2e
189
189
 
190
190
  A test that passes under one and fails under the other is a wrong verdict, and `webjs elision` tells you which module and on what evidence. If the component's interactivity is genuinely invisible to static analysis, the fix is `static interactive = true` on it; see `components.md` for what that override does and does not rescue.
191
191
 
192
+ ## One command for every layer (`webjs ci`)
193
+
194
+ ```sh
195
+ npm run ci # the webjs.ci list: check, doctor, typecheck, audit, then every test layer
196
+ npm run ci -- --only Tests # one step or group by title
197
+ npm run ci -- --fail-fast # stop at the first failure
198
+ npm run ci -- --json # one JSON document for an agent loop
199
+ ```
200
+
201
+ The scaffold declares its gate once, in `package.json` under `webjs.ci`, and `npm run ci` runs it with a timed result line per step (the Rails `bin/ci` model). The generated GitHub workflow runs the same list, so a green local run predicts CI. Run it before every push; the pre-commit hook deliberately runs none of it so a commit stays fast. Browser and e2e steps need a Chromium on the machine (`npx playwright install chromium`, plus `puppeteer-core` for the e2e layer), which the workflow installs for itself. The list, the flags, the `--signoff` merge gate, and the JSON shape are in `references/built-ins.md` under "Local CI".
202
+
192
203
  ## Type-checking your tests (`webjs typecheck`)
193
204
 
194
205
  Your tests are inside the tsconfig `include`, so `npm run typecheck` reads them (#1299). Treat a type error in a test as a failed gate, not a review catch: the checker sees a wrong argument shape or an unannotated parameter in a test the same way it sees one in `app/`.
@@ -52,6 +52,31 @@ So the loop is: `add` the component, then query `ui <name>` (MCP) or
52
52
  that ships inside the installed `@webjsdev/ui`, with no network. This pins you
53
53
  to the installed version; run `npx webjsdev ui diff` to see where your local copies
54
54
  drift from the upstream (that command alone compares against the live registry).
55
+ - `npx webjsdev ui lint` is an OPT-IN design-system linter over the app's own
56
+ source. It reads the Tailwind classes in `html` templates, `cn()` calls and
57
+ `class=${...}` holes and reports, at the line, a raw palette colour where the
58
+ theme declares a role token (`no-raw-colors`, with a message naming only the
59
+ `--color-*` tokens the configured `tailwind.css` actually declares), an
60
+ arbitrary value such as `p-[13px]` (`no-arbitrary-values`; an arbitrary
61
+ VARIANT like `[&_svg]:size-4` never fires), and a class composed over a kit
62
+ helper (`no-restyle`, naming the helper's real variants and sizes read from
63
+ the app's copied `components/ui/*.ts`). It is off until `components.json`
64
+ carries a `lint` block, and with no block it reports nothing and exits 0.
65
+ `components/ui/**` is skipped by default (a copied primitive owns structural
66
+ values no variant expresses), and `allow` uses shadcn's category taxonomy
67
+ (`layout`, `color`, `typography`, `spacing`, `shape`, `effects`, `motion`) or
68
+ a class-group id such as `rounded`. `--json` emits `{ violations, summary }`
69
+ for an agent loop; `--max-warnings <n>` pins a count.
70
+
71
+ ```json
72
+ "lint": {
73
+ "rules": {
74
+ "no-raw-colors": "warn",
75
+ "no-arbitrary-values": { "severity": "warn", "allow": ["layout"] },
76
+ "no-restyle": { "severity": "error", "allow": ["layout", "rounded"] }
77
+ }
78
+ }
79
+ ```
55
80
 
56
81
  ## Inventory (run `npx webjsdev ui list` or the MCP `ui` tool for the authoritative, current set)
57
82
 
@@ -4,10 +4,9 @@
4
4
 
5
5
  ## Test plan
6
6
 
7
- - [ ] Unit tests added/updated (`webjs test` passes)
8
- - [ ] E2E tests added/updated for user-facing changes (`webjs test --e2e` passes)
9
- - [ ] `webjs check` passes (no convention violations)
10
- - [ ] `webjs doctor` passes (project health; it fails on whatever `webjs.doctor.gate` marks `error`, plus the hard `NODE_VERSION` / `TSCONFIG_ERASABLE` checks)
7
+ - [ ] `webjs ci` passes locally (the `webjs.ci` step list in package.json: `webjs check`, `webjs doctor`, `webjs typecheck`, the dependency audit, and the server / browser / e2e test layers; CI runs the same list)
8
+ - [ ] Unit tests added/updated
9
+ - [ ] E2E tests added/updated for user-facing changes (`WEBJS_E2E=1 webjs test`)
11
10
 
12
11
  ## Definition of done
13
12
 
@@ -1,15 +1,25 @@
1
1
  name: CI
2
2
 
3
- # The test gate for {{APP_NAME}}. Runs the full test pyramid on every PR
4
- # into main and on every push to main. This is the gate the local
5
- # pre-commit hook deliberately leaves out, so `git commit` stays fast and
6
- # the test gate runs in one authoritative place a local --no-verify cannot
7
- # skip. Same posture as the webjs framework's own CI.
3
+ # The cloud half of CI for {{APP_NAME}}. The step list itself lives in
4
+ # package.json under "webjs": { "ci": { "steps": [...] } }, and `npm run ci`
5
+ # runs it: on a developer machine before a push, and here on every PR into
6
+ # main and every push to main, so the two can never drift. This job only
7
+ # prepares the runner (Node, dependencies, a browser, the database) and then
8
+ # runs that one command.
8
9
  #
9
- # The four layers run as separate jobs so a failure names the layer that
10
- # broke. Mark all four as required status checks in the branch-protection
11
- # rule for main so a PR can only merge when every layer is green. Free on
12
- # public repos (ubuntu-latest has unlimited Actions minutes).
10
+ # One job on purpose. Each step is still a collapsible log group with its own
11
+ # result line, a failing step is annotated, and the run's step table lands in
12
+ # the job summary, so a failure names the layer that broke. Mark this job as a
13
+ # required status check in the branch-protection rule for main. A team that
14
+ # wants one required check PER LAYER can add a matrix job that runs
15
+ # `npx webjs ci --only "<group title>"` per entry.
16
+ #
17
+ # The local pre-commit hook deliberately runs none of this, so a commit stays
18
+ # fast; `npm run ci` before pushing is the local gate. To hold a merge until a
19
+ # LOCAL run is green, `npm run ci -- --signoff` posts a green commit status
20
+ # through basecamp/gh-signoff, which branch protection can require (`gh signoff
21
+ # install`), the Rails posture. Free on public repos (ubuntu-latest has
22
+ # unlimited Actions minutes).
13
23
 
14
24
  on:
15
25
  pull_request:
@@ -17,40 +27,21 @@ on:
17
27
  push:
18
28
  branches: [main]
19
29
 
30
+ permissions:
31
+ contents: read
32
+
20
33
  # A newer push to the same branch cancels the older in-flight run.
21
34
  concurrency:
22
35
  group: ci-${{ github.ref }}
23
36
  cancel-in-progress: true
24
37
 
25
38
  jobs:
26
- conventions:
27
- name: Conventions (webjs check)
28
- runs-on: ubuntu-latest
29
- steps:
30
- - uses: actions/checkout@v6
31
- - uses: actions/setup-node@v6
32
- with:
33
- node-version: '24'
34
- cache: npm
35
- - run: npm ci
36
- - run: npm run check
37
- # Project health, on top of the correctness checks. WHICH findings are
38
- # fatal is your call, declared in package.json under
39
- # "webjs": { "doctor": { "gate": { "<CODE>": "off" | "warn" | "error" } } },
40
- # so this step and a local `npm run doctor` always agree. The scaffold
41
- # starts with UNMARKED_ASSET_LINKS at error (an un-versioned /public url
42
- # is a real deploy-staleness bug). Two checks fail with no gate entry at
43
- # all, NODE_VERSION and TSCONFIG_ERASABLE, because either would 500 the
44
- # app at runtime; everything else stays a warn and cannot fail this job.
45
- # Widen or narrow the gate in package.json, not
46
- # here. Deliberately not --strict: the git-hook, env-drift, vendor-pin,
47
- # and framework-resolve checks are environment-shaped and would fail a
48
- # perfectly healthy runner.
49
- - run: npm run doctor
50
-
51
- unit:
52
- name: Unit + integration (node --test)
39
+ ci:
40
+ name: CI (npm run ci)
53
41
  runs-on: ubuntu-latest
42
+ timeout-minutes: 20
43
+ env:
44
+ DATABASE_URL: file:./ci.db
54
45
  steps:
55
46
  - uses: actions/checkout@v6
56
47
  - uses: actions/setup-node@v6
@@ -58,58 +49,17 @@ jobs:
58
49
  node-version: '24'
59
50
  cache: npm
60
51
  - run: npm ci
61
- - name: Set up the database (generate + apply migrations)
62
- run: npm run db:generate && npm run db:migrate
63
- env:
64
- DATABASE_URL: file:./ci.db
65
- # --server keeps this job to node:test (the browser layer is its own
66
- # job below). Without WEBJS_E2E the e2e folders are skipped too.
67
- - run: npm run test:server
68
- env:
69
- DATABASE_URL: file:./ci.db
70
-
71
- browser:
72
- name: Browser (web-test-runner / Playwright)
73
- runs-on: ubuntu-latest
74
- steps:
75
- - uses: actions/checkout@v6
76
- - uses: actions/setup-node@v6
77
- with:
78
- node-version: '24'
79
- cache: npm
80
- - run: npm ci
81
- - name: Install Playwright Chromium
52
+ - name: Install Playwright Chromium (browser + e2e layers)
82
53
  run: npx playwright install --with-deps chromium
83
- - run: npm run test:browser
84
-
85
- e2e:
86
- name: E2E (full app boot)
87
- runs-on: ubuntu-latest
88
- steps:
89
- - uses: actions/checkout@v6
90
- - uses: actions/setup-node@v6
91
- with:
92
- node-version: '24'
93
- cache: npm
94
- - run: npm ci
95
- - name: Set up the database (generate + apply migrations)
96
- run: npm run db:generate && npm run db:migrate
97
- env:
98
- DATABASE_URL: file:./ci.db
99
- # The scaffold's e2e test (test/hello/e2e/) drives a real browser
100
- # via puppeteer-core, which is not a default dependency (the test
101
- # skips when it is absent). Install it and Chromium so the e2e
102
- # layer actually runs in CI rather than skipping silently.
103
- - name: Install puppeteer-core + Chromium
104
- run: |
105
- npm install --no-save puppeteer-core
106
- npx playwright install --with-deps chromium
54
+ # The scaffold's e2e test (test/hello/e2e/) drives a real browser via
55
+ # puppeteer-core, which is not a default dependency (the test skips when
56
+ # it is absent). Install it so the e2e layer runs here rather than
57
+ # skipping silently.
58
+ - name: Install puppeteer-core
59
+ run: npm install --no-save puppeteer-core
107
60
  - name: Resolve the Chromium binary path
108
61
  run: echo "CHROMIUM_PATH=$(node -e "console.log(require('playwright-core').chromium.executablePath())")" >> "$GITHUB_ENV"
109
- # --server with WEBJS_E2E=1 runs node:test including the e2e folders
110
- # (the runner gates them on that env var) and skips the browser layer.
111
- - name: Run e2e
112
- env:
113
- WEBJS_E2E: '1'
114
- DATABASE_URL: file:./ci.db
115
- run: npm run test:server
62
+ - name: Set up the database (generate + apply migrations)
63
+ run: npm run db:generate && npm run db:migrate
64
+ # Everything above prepares the runner. This is the whole gate.
65
+ - run: npm run ci
@@ -7,10 +7,11 @@
7
7
  #
8
8
  # To bypass in emergencies: git commit --no-verify
9
9
  #
10
- # Tests and convention checks run in CI (.github/workflows/ci.yml), not
11
- # here, so a commit stays fast and the test gate cannot be skipped by a
12
- # local --no-verify. The CI workflow runs `webjs check` + `webjs test`
13
- # on every push and pull request.
10
+ # Tests and convention checks do not run here, so a commit stays fast. The
11
+ # step list lives in package.json under "webjs": { "ci" }. Run it locally
12
+ # with `npm run ci` before pushing (the local gate), and the CI workflow
13
+ # (.github/workflows/ci.yml) runs the same list on every push and pull
14
+ # request, where a local --no-verify cannot skip it.
14
15
  #
15
16
  # Running more than one AI agent on this repo at once? Give each task its own
16
17
  # git worktree, not a shared checkout. Two agents in one working directory
@@ -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/