@webjsdev/cli 0.10.31 → 0.10.33
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/lib/create.js +52 -13
- package/package.json +1 -1
- package/templates/.agents/rules/workflow.md +17 -6
- package/templates/.claude/hooks/cleanup-merged-worktree.sh +129 -0
- package/templates/.claude/hooks/commit-before-stop.sh +52 -0
- package/templates/.claude/settings.json +19 -0
- package/templates/.cursorrules +17 -7
- package/templates/.github/copilot-instructions.md +17 -7
- package/templates/AGENTS.md +32 -10
- package/templates/CLAUDE.md +22 -0
- package/templates/CONVENTIONS.md +24 -10
- package/templates/gallery/app/features/forms/page.ts +6 -0
- package/templates/gallery/app/features/service-worker/page.ts +2 -1
- package/templates/gallery/modules/components/components/browser/counter-card.test.js +36 -0
- package/templates/gallery/modules/todo/queries/list-todos.server.ts +6 -0
- package/templates/test/hello/browser/hello.test.js +6 -0
- package/templates/web-test-runner.config.js +9 -1
package/lib/create.js
CHANGED
|
@@ -49,6 +49,27 @@ function runInstall(appDir, pm) {
|
|
|
49
49
|
return r.status === 0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Author the INITIAL Drizzle migration for the shipped schema, so the app boots
|
|
54
|
+
* with its tables and the very first `run dev` works with no manual step. The
|
|
55
|
+
* scaffold's schema is TypeScript (`db/schema.server.ts`); `db migrate` (run in
|
|
56
|
+
* `webjs.dev.before` / `webjs.start.before`) only applies migration SQL FILES, so
|
|
57
|
+
* with no file the shipped example hits "no such table". `db generate` turns the
|
|
58
|
+
* schema into that first `db/migrations/*.sql`. It runs OFFLINE (a schema-to-SQL
|
|
59
|
+
* diff, no database connection), so it is safe for sqlite AND postgres here, well
|
|
60
|
+
* before any `DATABASE_URL` exists. Needs `drizzle-kit`, so it only runs after a
|
|
61
|
+
* successful install; on `--no-install` the printed next-steps still show
|
|
62
|
+
* `db:generate`. Returns whether a migration was authored.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} appDir
|
|
65
|
+
* @param {string} pm
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function runDbGenerate(appDir, pm) {
|
|
69
|
+
const r = spawnSync(pm, ['run', 'db:generate'], { cwd: appDir, stdio: 'inherit' });
|
|
70
|
+
return r.status === 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
53
74
|
const TEMPLATES = resolve(__dirname, '..', 'templates');
|
|
54
75
|
|
|
@@ -401,14 +422,18 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
401
422
|
// for relations v2. SQLite needs NO driver dependency: the connection
|
|
402
423
|
// uses the built-in node:sqlite (Node) / bun:sqlite (Bun) via Drizzle's
|
|
403
424
|
// node-sqlite / bun-sqlite adapters. Postgres still needs the pg driver.
|
|
404
|
-
|
|
425
|
+
// Pinned EXACTLY (no caret): a caret on a prerelease still admits later
|
|
426
|
+
// rc.N of 1.0.0, and the relations-v2 query API the scaffold is written and
|
|
427
|
+
// tested against is rc.3 (#562). An exact pin keeps generated apps
|
|
428
|
+
// deterministic instead of silently drifting to a newer rc.
|
|
429
|
+
'drizzle-orm': '1.0.0-rc.3',
|
|
405
430
|
...(dialect === 'postgres' ? { pg: '^8.13.0' } : {}),
|
|
406
431
|
'@webjsdev/cli': 'latest',
|
|
407
432
|
'@webjsdev/core': 'latest',
|
|
408
433
|
'@webjsdev/server': 'latest',
|
|
409
434
|
},
|
|
410
435
|
devDependencies: {
|
|
411
|
-
'drizzle-kit': '
|
|
436
|
+
'drizzle-kit': '1.0.0-rc.3',
|
|
412
437
|
...(dialect === 'postgres' ? { '@types/pg': '^8.11.0' } : {}),
|
|
413
438
|
// The TypeScript compiler, for `npm run typecheck` (webjs typecheck runs
|
|
414
439
|
// tsc --noEmit). Not needed at runtime (Node strips types in place), only
|
|
@@ -528,6 +553,8 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
528
553
|
'.claude/hooks/block-prose-punctuation.sh',
|
|
529
554
|
'.claude/hooks/guard-branch-context.sh',
|
|
530
555
|
'.claude/hooks/nudge-uncommitted.sh',
|
|
556
|
+
'.claude/hooks/commit-before-stop.sh',
|
|
557
|
+
'.claude/hooks/cleanup-merged-worktree.sh',
|
|
531
558
|
'.claude/hooks/require-tests-with-src.sh',
|
|
532
559
|
'.claude/hooks/check-server-imports.sh',
|
|
533
560
|
'.claude/hooks/check-server-imports.mjs',
|
|
@@ -603,7 +630,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
603
630
|
|
|
604
631
|
// Make hook scripts executable
|
|
605
632
|
const { chmod } = await import('node:fs/promises');
|
|
606
|
-
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh', 'require-tests-with-src.sh']) {
|
|
633
|
+
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh', 'commit-before-stop.sh', 'cleanup-merged-worktree.sh', 'require-tests-with-src.sh']) {
|
|
607
634
|
const hookPath = join(appDir, '.claude', 'hooks', hook);
|
|
608
635
|
if (existsSync(hookPath)) await chmod(hookPath, 0o755);
|
|
609
636
|
}
|
|
@@ -1274,8 +1301,10 @@ ${UI_THEME}
|
|
|
1274
1301
|
<main class="flex-1 w-full max-w-[760px] mx-auto px-4 sm:px-6 pt-[72px] pb-12">
|
|
1275
1302
|
\${children}
|
|
1276
1303
|
</main>
|
|
1277
|
-
<!-- "Built with webjs"
|
|
1278
|
-
|
|
1304
|
+
<!-- webjs-scaffold-placeholder. This "Built with webjs" footer is SCAFFOLD
|
|
1305
|
+
branding, not your app's. REMOVE it, or replace it with your own
|
|
1306
|
+
footer, before shipping a delivered app. Delete this line once done.
|
|
1307
|
+
webjs check fails while the marker remains. -->
|
|
1279
1308
|
<footer class="border-t border-border">
|
|
1280
1309
|
<div class="max-w-[760px] mx-auto px-4 sm:px-6 py-6 flex items-center justify-center">
|
|
1281
1310
|
<a href="https://webjs.dev" class="inline-flex items-center gap-2 no-underline text-sm text-muted-foreground hover:text-foreground transition-colors">
|
|
@@ -1668,11 +1697,21 @@ For AI agents, read this before editing scaffolded files:
|
|
|
1668
1697
|
// (#541). Otherwise honour the invoking PM (npm / pnpm / yarn / bun).
|
|
1669
1698
|
const pm = isBun ? 'bun' : detectPackageManager();
|
|
1670
1699
|
let installed = false;
|
|
1700
|
+
let generatedMigration = false;
|
|
1671
1701
|
if (shouldInstall) {
|
|
1672
1702
|
console.log(`Running '${pm} install' in ${name}/ ...\n`);
|
|
1673
1703
|
installed = runInstall(appDir, pm);
|
|
1674
1704
|
if (!installed) {
|
|
1675
1705
|
console.log(`\n[warn] ${pm} install failed. Run '${pm} install' manually in ${name}/ to finish setup.\n`);
|
|
1706
|
+
} else {
|
|
1707
|
+
// Author the initial migration NOW (drizzle-kit is installed), so the
|
|
1708
|
+
// shipped schema's tables exist and the very first `run dev` works with no
|
|
1709
|
+
// manual step (webjs.*.before applies the migration on boot). See runDbGenerate.
|
|
1710
|
+
console.log(`Authoring the initial database migration ('${pm} run db:generate') ...\n`);
|
|
1711
|
+
generatedMigration = runDbGenerate(appDir, pm);
|
|
1712
|
+
if (!generatedMigration) {
|
|
1713
|
+
console.log(`\n[warn] '${pm} run db:generate' failed. Run it manually in ${name}/ before '${pm} run dev'.\n`);
|
|
1714
|
+
}
|
|
1676
1715
|
}
|
|
1677
1716
|
}
|
|
1678
1717
|
|
|
@@ -1683,14 +1722,14 @@ For AI agents, read this before editing scaffolded files:
|
|
|
1683
1722
|
// templates ship with @webjsdev/ui already initialised; the api
|
|
1684
1723
|
// template has no UI but may add one later.
|
|
1685
1724
|
const installSegment = installed ? '' : `${pm} install && `;
|
|
1686
|
-
//
|
|
1687
|
-
//
|
|
1688
|
-
//
|
|
1689
|
-
//
|
|
1690
|
-
//
|
|
1691
|
-
//
|
|
1692
|
-
//
|
|
1693
|
-
const dbSegment =
|
|
1725
|
+
// The shipped schema is applied on the first `run dev` (webjs.*.before runs
|
|
1726
|
+
// `db migrate`), but only if a migration FILE exists. When we installed, we
|
|
1727
|
+
// already authored it above (runDbGenerate), so the run command is just
|
|
1728
|
+
// `run dev`. Otherwise (--no-install, or generate failed) the user authors it
|
|
1729
|
+
// first: `db:generate` writes the migration from db/schema.server.ts, then
|
|
1730
|
+
// `run dev` applies it (Drizzle splits Prisma's `migrate dev` into
|
|
1731
|
+
// generate-then-migrate).
|
|
1732
|
+
const dbSegment = generatedMigration ? '' : `${pm} run db:generate && `;
|
|
1694
1733
|
const runCommand = `cd ${name} && ${installSegment}${dbSegment}${pm} run dev`;
|
|
1695
1734
|
// Postgres needs a reachable DATABASE_URL before any migrate (sqlite uses a
|
|
1696
1735
|
// local file with no .env). Point it at a running database; `dev` / `start`
|
package/package.json
CHANGED
|
@@ -46,12 +46,23 @@ cover what you need, the full hosted docs are at **https://docs.webjs.dev**.
|
|
|
46
46
|
route, middleware, metadata routes). CSS, helpers, and constants do NOT:
|
|
47
47
|
`globals.css` is at `styles/`, browser-safe helpers at `lib/utils/`, feature
|
|
48
48
|
logic in `modules/`.
|
|
49
|
-
- **Use a unique design
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
- **Use a unique design, and redesign means more than recolor (UI apps).** Give
|
|
50
|
+
the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
|
|
51
|
+
from what the app IS. Recoloring the scaffold and swapping the logo while
|
|
52
|
+
keeping its skeleton (a fixed top header with a Home link and a theme toggle,
|
|
53
|
+
the centered ~760px reading column, the "Built with webjs" footer) is NOT a
|
|
54
|
+
unique design. Decide from scratch whether this app even needs a header or
|
|
55
|
+
footer, what nav (if any), and what layout fits (a centered board, a full-bleed
|
|
56
|
+
dashboard, a split, a single card). The scaffold ships a
|
|
57
|
+
`webjs-scaffold-placeholder` marker on its footer, so `webjs check` fails until
|
|
58
|
+
you remove or replace the "Built with webjs" branding. Self-audit before
|
|
59
|
+
finishing: nothing should read as the scaffold example (no "Built with webjs"
|
|
60
|
+
footer, no leftover example nav, no default reading column unless it truly
|
|
61
|
+
fits). Keep only the design TOKENS and theme wiring in `app/layout.ts`
|
|
62
|
+
(infrastructure the ui kit reads) and restyle on top. Style with Tailwind
|
|
63
|
+
utilities wherever they reach, and use custom CSS only for what utilities
|
|
64
|
+
cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix or
|
|
65
|
+
gradients). The `api` template has no UI, so this does not apply there.
|
|
55
66
|
- **Only three templates exist:** `webjs create <name>` (default full-stack),
|
|
56
67
|
`--template api`, `--template saas`. The CLI rejects any other `--template`
|
|
57
68
|
value. Pick:
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Claude Code PostToolUse hook (matcher: Bash).
|
|
4
|
+
#
|
|
5
|
+
# After a `gh pr merge`, sweep the repo's git worktrees and REMOVE the ones
|
|
6
|
+
# whose work has already landed, so a merged branch's worktree does not leak.
|
|
7
|
+
# Accumulated stale worktrees (a session that merged but never cleaned up, or
|
|
8
|
+
# crashed mid-task) are exactly what this closes: the webjs-start-work skill
|
|
9
|
+
# already says "after the PR merges, git worktree remove", but as guidance it
|
|
10
|
+
# gets skipped, so this makes the cleanup deterministic.
|
|
11
|
+
#
|
|
12
|
+
# CONSERVATIVE BY DESIGN. A worktree is removed ONLY when ALL hold:
|
|
13
|
+
# * it is a LINKED worktree, not the primary checkout;
|
|
14
|
+
# * it is NOT the current directory (you cannot remove the one you are in);
|
|
15
|
+
# * its branch is not main/master;
|
|
16
|
+
# * its branch is MERGED (an ancestor of the base ref, OR a merged GitHub PR
|
|
17
|
+
# for that head branch, which is how squash-merges are detected);
|
|
18
|
+
# * its working tree is CLEAN apart from untracked node_modules / .webjs.
|
|
19
|
+
# Anything with uncommitted or unpushed-looking work is KEPT and reported, so
|
|
20
|
+
# the hook can never destroy in-flight work.
|
|
21
|
+
#
|
|
22
|
+
# It never blocks the tool (always exits 0) and reports what it did back to the
|
|
23
|
+
# model via hookSpecificOutput.additionalContext. Disable with
|
|
24
|
+
# WEBJS_NO_WORKTREE_CLEANUP=1.
|
|
25
|
+
#
|
|
26
|
+
# Rule: AGENTS.md "One task per git worktree" + the webjs-start-work skill.
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
# Read the whole payload first so we always honour the hook contract.
|
|
31
|
+
payload=$(cat 2>/dev/null || true)
|
|
32
|
+
|
|
33
|
+
if [ "${WEBJS_NO_WORKTREE_CLEANUP:-}" = "1" ]; then exit 0; fi
|
|
34
|
+
|
|
35
|
+
cmd=$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
|
|
36
|
+
if [ -z "$cmd" ]; then exit 0; fi
|
|
37
|
+
|
|
38
|
+
# Only act after a `gh pr merge` (whole word, not `gh pr merge-queue` typos etc.).
|
|
39
|
+
if ! printf '%s' "$cmd" | grep -Eq '(^|[^[:alnum:]-])gh pr merge([^[:alnum:]-]|$)'; then
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then exit 0; fi
|
|
44
|
+
|
|
45
|
+
# The base ref merged branches land on. Prefer origin/main; fall back to a
|
|
46
|
+
# local main/master (the test harness has no remote).
|
|
47
|
+
base=""
|
|
48
|
+
for ref in origin/main origin/master main master; do
|
|
49
|
+
if git rev-parse --verify --quiet "$ref" >/dev/null 2>&1; then base="$ref"; break; fi
|
|
50
|
+
done
|
|
51
|
+
[ -z "$base" ] && exit 0
|
|
52
|
+
|
|
53
|
+
here=$(git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$PWD")
|
|
54
|
+
# The primary worktree is the first entry of `git worktree list`.
|
|
55
|
+
primary=$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')
|
|
56
|
+
|
|
57
|
+
is_merged() {
|
|
58
|
+
local br="$1"
|
|
59
|
+
# Ancestor of the base ref (fast-forward / rebase merges, and the real
|
|
60
|
+
# merges the test harness makes).
|
|
61
|
+
if git merge-base --is-ancestor "refs/heads/$br" "$base" 2>/dev/null; then return 0; fi
|
|
62
|
+
# A merged GitHub PR for this head branch (squash merges, which are NOT an
|
|
63
|
+
# ancestor of base). Network; skipped when gh is absent or unauthenticated.
|
|
64
|
+
if command -v gh >/dev/null 2>&1; then
|
|
65
|
+
local n
|
|
66
|
+
n=$(gh pr list --state merged --head "$br" --json number --jq '.[0].number' 2>/dev/null || true)
|
|
67
|
+
[ -n "$n" ] && return 0
|
|
68
|
+
fi
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Clean = nothing in `git status` except untracked node_modules / .webjs caches.
|
|
73
|
+
is_clean() {
|
|
74
|
+
local wt="$1" dirty
|
|
75
|
+
dirty=$(git -C "$wt" status --porcelain 2>/dev/null \
|
|
76
|
+
| grep -vE '(^|/)(node_modules|\.webjs)(/|$)' || true)
|
|
77
|
+
[ -z "$dirty" ]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
removed=()
|
|
81
|
+
kept=()
|
|
82
|
+
|
|
83
|
+
# Parse worktree path + branch pairs.
|
|
84
|
+
wt=""
|
|
85
|
+
while IFS= read -r line; do
|
|
86
|
+
case "$line" in
|
|
87
|
+
worktree\ *) wt="${line#worktree }" ;;
|
|
88
|
+
branch\ *)
|
|
89
|
+
br="${line#branch refs/heads/}"
|
|
90
|
+
# Skip the primary checkout and main/master lines.
|
|
91
|
+
if [ "$wt" = "$primary" ] || [ "$br" = "main" ] || [ "$br" = "master" ]; then wt=""; continue; fi
|
|
92
|
+
# Never remove the worktree we are currently in.
|
|
93
|
+
if [ "$wt" = "$here" ]; then
|
|
94
|
+
kept+=("$wt (current directory; cd out then \`git worktree remove\`)")
|
|
95
|
+
wt=""; continue
|
|
96
|
+
fi
|
|
97
|
+
if ! is_clean "$wt"; then
|
|
98
|
+
kept+=("$wt (uncommitted changes)"); wt=""; continue
|
|
99
|
+
fi
|
|
100
|
+
if ! is_merged "$br"; then
|
|
101
|
+
kept+=("$wt (branch $br not merged yet)"); wt=""; continue
|
|
102
|
+
fi
|
|
103
|
+
if git worktree remove --force "$wt" >/dev/null 2>&1; then
|
|
104
|
+
removed+=("$wt ($br)")
|
|
105
|
+
else
|
|
106
|
+
kept+=("$wt (git worktree remove failed)")
|
|
107
|
+
fi
|
|
108
|
+
wt="" ;;
|
|
109
|
+
"") wt="" ;;
|
|
110
|
+
esac
|
|
111
|
+
done < <(git worktree list --porcelain 2>/dev/null)
|
|
112
|
+
|
|
113
|
+
git worktree prune >/dev/null 2>&1 || true
|
|
114
|
+
|
|
115
|
+
# Report nothing if there was nothing to do.
|
|
116
|
+
if [ "${#removed[@]}" -eq 0 ] && [ "${#kept[@]}" -eq 0 ]; then exit 0; fi
|
|
117
|
+
|
|
118
|
+
msg="Worktree cleanup after \`gh pr merge\`:"
|
|
119
|
+
for r in "${removed[@]:-}"; do [ -n "$r" ] && msg="$msg"$'\n'" removed $r (merged, clean)"; done
|
|
120
|
+
for k in "${kept[@]:-}"; do [ -n "$k" ] && msg="$msg"$'\n'" kept $k"; done
|
|
121
|
+
|
|
122
|
+
jq -n --arg ctx "$msg" '{
|
|
123
|
+
hookSpecificOutput: {
|
|
124
|
+
hookEventName: "PostToolUse",
|
|
125
|
+
additionalContext: $ctx
|
|
126
|
+
}
|
|
127
|
+
}' 2>/dev/null || true
|
|
128
|
+
|
|
129
|
+
exit 0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Claude Code Stop hook.
|
|
4
|
+
#
|
|
5
|
+
# The commit-per-logical-unit rule (CLAUDE.md + AGENTS.md "Git workflow") is
|
|
6
|
+
# easy for an agent to defer to "the end", and then the end arrives with the
|
|
7
|
+
# whole feature done and ZERO commits, which is the worst outcome: git history,
|
|
8
|
+
# the user's revert and cherry-pick safety net, is empty. The PostToolUse
|
|
9
|
+
# `nudge-uncommitted.sh` reminds DURING work but is only a soft context nudge an
|
|
10
|
+
# agent can ignore. This Stop hook is the backstop at the END of a turn: if you
|
|
11
|
+
# try to finish with a pile of uncommitted work on a feature branch, it blocks
|
|
12
|
+
# the stop once and tells you to commit the completed unit first.
|
|
13
|
+
#
|
|
14
|
+
# Loop-safe: when `stop_hook_active` is already true (this hook fired and the
|
|
15
|
+
# agent is continuing because of it), it does NOT block again, so it nags at
|
|
16
|
+
# most once per stop and can never trap the agent in a loop.
|
|
17
|
+
#
|
|
18
|
+
# Skipped on main/master (you must not commit there anyway) and outside a git
|
|
19
|
+
# work tree. Threshold via WEBJS_COMMIT_STOP_THRESHOLD (default 2). Disable
|
|
20
|
+
# entirely with WEBJS_NO_COMMIT_STOP=1.
|
|
21
|
+
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
|
|
24
|
+
payload=$(cat 2>/dev/null || true)
|
|
25
|
+
|
|
26
|
+
if [ "${WEBJS_NO_COMMIT_STOP:-}" = "1" ]; then exit 0; fi
|
|
27
|
+
|
|
28
|
+
# Loop guard: if we already blocked once this stop-cycle, let the agent stop.
|
|
29
|
+
active=$(printf '%s' "$payload" | jq -r '.stop_hook_active // false' 2>/dev/null || echo false)
|
|
30
|
+
if [ "$active" = "true" ]; then exit 0; fi
|
|
31
|
+
|
|
32
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then exit 0; fi
|
|
33
|
+
|
|
34
|
+
branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
35
|
+
if [ -z "$branch" ] || [ "$branch" = "main" ] || [ "$branch" = "master" ]; then exit 0; fi
|
|
36
|
+
|
|
37
|
+
threshold="${WEBJS_COMMIT_STOP_THRESHOLD:-2}"
|
|
38
|
+
|
|
39
|
+
# Count real changes: tracked modifications + staged + untracked, minus the
|
|
40
|
+
# noise the agent should never commit (node_modules, the sqlite db, caches).
|
|
41
|
+
changed=$(git status --porcelain 2>/dev/null \
|
|
42
|
+
| grep -vE '(^|/)(node_modules|\.webjs)(/|$)|dev\.db($|-journal)' \
|
|
43
|
+
| grep -c . || true)
|
|
44
|
+
|
|
45
|
+
if [ -z "$changed" ] || [ "$changed" -lt "$threshold" ]; then exit 0; fi
|
|
46
|
+
|
|
47
|
+
reason="You are ending the turn with ${changed} uncommitted changes on '${branch}'. This project OVERRIDES Claude Code's never-commit default: commit per logical unit (see CLAUDE.md and AGENTS.md \"Git workflow\"). Before you stop, group the completed work into a meaningful commit ('git add' the related files, 'git commit' with an imperative subject under 72 chars) and push. If the work is genuinely mid-change and not yet a coherent unit, commit what IS complete, or explain in your final message why it cannot be committed yet. To relax this backstop set WEBJS_COMMIT_STOP_THRESHOLD, or disable it with WEBJS_NO_COMMIT_STOP=1."
|
|
48
|
+
|
|
49
|
+
jq -n --arg r "$reason" '{decision: "block", reason: $r}' 2>/dev/null \
|
|
50
|
+
|| printf '{"decision":"block","reason":%s}\n' "$(printf '%s' "$reason" | jq -Rs . 2>/dev/null || echo '""')"
|
|
51
|
+
|
|
52
|
+
exit 0
|
|
@@ -56,6 +56,25 @@
|
|
|
56
56
|
"command": ".claude/hooks/nudge-uncommitted.sh"
|
|
57
57
|
}
|
|
58
58
|
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"matcher": "Bash",
|
|
62
|
+
"hooks": [
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": ".claude/hooks/cleanup-merged-worktree.sh"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"Stop": [
|
|
71
|
+
{
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": ".claude/hooks/commit-before-stop.sh"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
59
78
|
}
|
|
60
79
|
]
|
|
61
80
|
}
|
package/templates/.cursorrules
CHANGED
|
@@ -46,13 +46,23 @@ cover what you need, the full hosted docs are at **https://docs.webjs.dev**.
|
|
|
46
46
|
layout, route, middleware, metadata routes). CSS, helpers, and constants
|
|
47
47
|
do NOT: `globals.css` is at `styles/`, browser-safe helpers at
|
|
48
48
|
`lib/utils/`, feature logic in `modules/`.
|
|
49
|
-
- **Use a unique design
|
|
50
|
-
design of
|
|
51
|
-
the
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
- **Use a unique design, and redesign means more than recolor (UI apps).** Give
|
|
50
|
+
the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
|
|
51
|
+
from what the app IS. Recoloring the scaffold and swapping the logo while
|
|
52
|
+
keeping its skeleton (a fixed top header with a Home link and a theme toggle,
|
|
53
|
+
the centered ~760px reading column, the "Built with webjs" footer) is NOT a
|
|
54
|
+
unique design. Decide from scratch whether this app even needs a header or
|
|
55
|
+
footer, what nav (if any), and what layout fits (a centered board, a
|
|
56
|
+
full-bleed dashboard, a split, a single card). The scaffold ships a
|
|
57
|
+
`webjs-scaffold-placeholder` marker on its footer, so `webjs check` fails until
|
|
58
|
+
you remove or replace the "Built with webjs" branding. Self-audit before
|
|
59
|
+
finishing: nothing should read as the scaffold example (no "Built with webjs"
|
|
60
|
+
footer, no leftover example nav, no default reading column unless it truly
|
|
61
|
+
fits). Keep only the design TOKENS and theme wiring in `app/layout.ts`
|
|
62
|
+
(infrastructure the ui kit reads) and restyle on top. Style with Tailwind
|
|
63
|
+
utilities wherever they reach, and use custom CSS only for what utilities
|
|
64
|
+
cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix or
|
|
65
|
+
gradients). The `api` template has no UI, so this does not apply there.
|
|
56
66
|
- **Only three templates exist:** `webjs create <name>` (default
|
|
57
67
|
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
58
68
|
other `--template` value. Pick:
|
|
@@ -46,13 +46,23 @@ the full hosted docs are at **https://docs.webjs.dev**.
|
|
|
46
46
|
layout, route, middleware, metadata routes). CSS, helpers, and constants
|
|
47
47
|
do NOT: `globals.css` is at `styles/`, browser-safe helpers at
|
|
48
48
|
`lib/utils/`, feature logic in `modules/`.
|
|
49
|
-
- **Use a unique design
|
|
50
|
-
design of
|
|
51
|
-
the
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
- **Use a unique design, and redesign means more than recolor (UI apps).** Give
|
|
50
|
+
the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
|
|
51
|
+
from what the app IS. Recoloring the scaffold and swapping the logo while
|
|
52
|
+
keeping its skeleton (a fixed top header with a Home link and a theme toggle,
|
|
53
|
+
the centered ~760px reading column, the "Built with webjs" footer) is NOT a
|
|
54
|
+
unique design. Decide from scratch whether this app even needs a header or
|
|
55
|
+
footer, what nav (if any), and what layout fits (a centered board, a
|
|
56
|
+
full-bleed dashboard, a split, a single card). The scaffold ships a
|
|
57
|
+
`webjs-scaffold-placeholder` marker on its footer, so `webjs check` fails until
|
|
58
|
+
you remove or replace the "Built with webjs" branding. Self-audit before
|
|
59
|
+
finishing: nothing should read as the scaffold example (no "Built with webjs"
|
|
60
|
+
footer, no leftover example nav, no default reading column unless it truly
|
|
61
|
+
fits). Keep only the design TOKENS and theme wiring in `app/layout.ts`
|
|
62
|
+
(infrastructure the ui kit reads) and restyle on top. Style with Tailwind
|
|
63
|
+
utilities wherever they reach, and use custom CSS only for what utilities
|
|
64
|
+
cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix or
|
|
65
|
+
gradients). The `api` template has no UI, so this does not apply there.
|
|
56
66
|
- **Only three templates exist:** `webjs create <name>` (default
|
|
57
67
|
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
58
68
|
other `--template` value. Pick:
|
package/templates/AGENTS.md
CHANGED
|
@@ -21,12 +21,21 @@ default `<main class="max-w-[760px]">` is a reading column for prose and
|
|
|
21
21
|
forms, so for a full-bleed app, dashboard, or board, widen the cap or
|
|
22
22
|
remove it (keep the theme tokens). A wide layout left in the 760px
|
|
23
23
|
reading column overflows into a horizontal scrollbar. **Give the app a
|
|
24
|
-
unique design.** When it has a UI,
|
|
25
|
-
|
|
26
|
-
scaffold
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
unique design, and redesign means more than recolor.** When it has a UI,
|
|
25
|
+
choose its palette, typography, LAYOUT, and chrome from what the app IS.
|
|
26
|
+
Recoloring the scaffold and swapping the logo while keeping its skeleton (a
|
|
27
|
+
fixed top header with a Home link and a theme toggle, the centered ~760px
|
|
28
|
+
reading column, the "Built with webjs" footer) is NOT a unique design.
|
|
29
|
+
Decide from scratch whether this app even needs a header or footer, what nav
|
|
30
|
+
(if any), and what layout fits (a centered board, a full-bleed dashboard, a
|
|
31
|
+
split, a single card). Before finishing, self-audit that nothing still reads
|
|
32
|
+
as the scaffold example (no "Built with webjs" footer, no leftover example
|
|
33
|
+
nav, no default reading column unless it truly fits). The `api` template has
|
|
34
|
+
no UI, so this does not apply there. The design tokens and theme wiring are
|
|
35
|
+
infrastructure to keep and restyle on top of. Style with Tailwind utilities
|
|
36
|
+
wherever they reach, and use custom CSS only for what utilities cannot
|
|
37
|
+
express (@theme tokens, @keyframes, scrollbar, complex color-mix or
|
|
38
|
+
gradients). This is ENFORCED:
|
|
30
39
|
the example `app/page.ts` and `app/layout.ts` carry a
|
|
31
40
|
`webjs-scaffold-placeholder` marker comment, and `webjs check` fails
|
|
32
41
|
while any marker remains, so this freshly scaffolded app fails the check
|
|
@@ -372,10 +381,14 @@ db/
|
|
|
372
381
|
dev.db SQLite file (gitignored); created when migrations apply (\`dev\`/\`start\` run \`webjs db migrate\`)
|
|
373
382
|
migrations/ generated migration SQL (committed)
|
|
374
383
|
drizzle.config.ts drizzle-kit config (root; SQLite by default, --db postgres to switch)
|
|
375
|
-
public/ static assets
|
|
384
|
+
public/ static assets at /public/* (favicon, sw.js, offline.html serve at root)
|
|
376
385
|
test/<feature>/ feature-scoped tests, one folder per concern
|
|
377
386
|
<name>.test.ts node unit / integration test (node --test)
|
|
378
|
-
browser/<name>.test.js real-browser test (web-test-runner)
|
|
387
|
+
browser/<name>.test.js real-browser test (web-test-runner); may ALSO be
|
|
388
|
+
co-located next to a component, e.g.
|
|
389
|
+
modules/<feature>/components/browser/<name>.test.js
|
|
390
|
+
(see the gallery counter-card test for the idioms:
|
|
391
|
+
suite/test, ssrFixture, inline assert, no chai)
|
|
379
392
|
e2e/<name>.test.ts end-to-end test (full app boot, opt in via WEBJS_E2E=1)
|
|
380
393
|
smoke/<name>.test.ts fast post-deploy sanity check
|
|
381
394
|
middleware.ts root middleware (optional, outermost)
|
|
@@ -536,6 +549,7 @@ Scripts (all wrap `drizzle-kit`):
|
|
|
536
549
|
- `npm run db:studio`: `webjs db studio` (visual DB browser)
|
|
537
550
|
- `npm run db:seed`: `webjs db seed` (run `db/seed.server.ts`)
|
|
538
551
|
- `webjs.dev.before` and `webjs.start.before` both run `webjs db migrate` inside `webjs dev` / `webjs start` (idempotent; replaces the old `prestart` hook), so after you `db:generate` a migration it is applied on the next boot with no manual `db:migrate` step.
|
|
552
|
+
- The INITIAL migration for the shipped schema is authored by `webjs create` at setup time (right after install), so `db/migrations/` is populated and the first `run dev` works with no manual database step. You run `db:generate` yourself only when you CHANGE `db/schema.server.ts` (a new table or column), then the next `run dev` applies it.
|
|
539
553
|
|
|
540
554
|
Always import `db` from `db/connection.server.ts` (the globalThis-cached
|
|
541
555
|
singleton avoids opening a new connection on every dev-server reload), and
|
|
@@ -1404,8 +1418,10 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
1404
1418
|
3. Commit and push **per logical unit**, not at the end. A logical unit is one
|
|
1405
1419
|
feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
|
|
1406
1420
|
spanning different concerns, commit the current group before continuing.
|
|
1407
|
-
|
|
1408
|
-
|
|
1421
|
+
For Claude Code, its `CLAUDE.md` explicitly OVERRIDES Claude Code's built-in
|
|
1422
|
+
never-commit default, so it commits per unit without waiting to be asked. The
|
|
1423
|
+
framework also ships a `nudge-uncommitted` hook for several agents that fires
|
|
1424
|
+
at threshold 4:
|
|
1409
1425
|
|
|
1410
1426
|
| Agent | Hook path | Doc |
|
|
1411
1427
|
|---|---|---|
|
|
@@ -1416,6 +1432,12 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
1416
1432
|
| Antigravity (Google) | text rule only (post-write hooks not yet exposed) | `.agents/rules/workflow.md` |
|
|
1417
1433
|
| GitHub Copilot | text rule only (no hooks API) | `.github/copilot-instructions.md` |
|
|
1418
1434
|
|
|
1435
|
+
Claude Code adds two more backstops of its own. A `commit-before-stop.sh`
|
|
1436
|
+
Stop hook refuses to end a turn with a pile of uncommitted work on a feature
|
|
1437
|
+
branch (loop-safe, disable with `WEBJS_NO_COMMIT_STOP=1`), and a
|
|
1438
|
+
`cleanup-merged-worktree.sh` PostToolUse hook removes a merged branch's
|
|
1439
|
+
worktree after a `gh pr merge`.
|
|
1440
|
+
|
|
1419
1441
|
The `.hooks/pre-commit` hook blocks commits to main and nothing else;
|
|
1420
1442
|
`webjs test` + `webjs check` run in CI (`.github/workflows/ci.yml`) on
|
|
1421
1443
|
every PR and push to main, regardless of which agent (or human) made
|
package/templates/CLAUDE.md
CHANGED
|
@@ -1,2 +1,24 @@
|
|
|
1
1
|
@AGENTS.md
|
|
2
2
|
@CONVENTIONS.md
|
|
3
|
+
|
|
4
|
+
# Committing per logical unit (this OVERRIDES Claude Code's default)
|
|
5
|
+
|
|
6
|
+
Claude Code's built-in default is to NEVER commit unless the user explicitly
|
|
7
|
+
asks. **For this project that default does NOT apply.** Commit and push per
|
|
8
|
+
logical unit (one feature, one fix, one rename, one doc rewrite) as soon as it
|
|
9
|
+
is complete, WITHOUT being asked. Do not save all the work for one commit at the
|
|
10
|
+
end. A finished implementation with zero commits is a mistake here, because git
|
|
11
|
+
history is the user's revert and cherry-pick safety net.
|
|
12
|
+
|
|
13
|
+
- After each completed unit whose tests pass, `git add` the related files and
|
|
14
|
+
`git commit` with an imperative subject under 72 chars, then push. If 5+ files
|
|
15
|
+
span more than one concern, you already waited too long.
|
|
16
|
+
- Never commit to `main`. Work on a feature branch (the
|
|
17
|
+
`.claude/hooks/guard-branch-context.sh` hook enforces this).
|
|
18
|
+
- No AI-attribution trailers (`Co-Authored-By`, `Generated by`).
|
|
19
|
+
|
|
20
|
+
See AGENTS.md "Git workflow" for the full contract. Two hooks back this up: the
|
|
21
|
+
`.claude/hooks/nudge-uncommitted.sh` PostToolUse hook reminds you while
|
|
22
|
+
uncommitted changes pile up during work, and the
|
|
23
|
+
`.claude/hooks/commit-before-stop.sh` Stop hook stops you from ending a turn
|
|
24
|
+
with a pile of uncommitted work still on a feature branch.
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -373,14 +373,24 @@ When the user asks the agent to build their actual app:
|
|
|
373
373
|
dashboard, or board, or a wide layout overflows into an unnecessary
|
|
374
374
|
horizontal scrollbar. Keep the design tokens and theme setup, those
|
|
375
375
|
are infrastructure.
|
|
376
|
-
6. **Use a unique design
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
376
|
+
6. **Use a unique design, and redesign means more than recolor (UI apps).**
|
|
377
|
+
Give the app a design of its own (palette, typography, LAYOUT, spacing,
|
|
378
|
+
and chrome) chosen from what the app IS. Recoloring the scaffold and
|
|
379
|
+
swapping the logo while keeping its skeleton (a fixed top header with a
|
|
380
|
+
Home link and a theme toggle, the centered ~760px reading column, the
|
|
381
|
+
"Built with webjs" footer) is NOT a unique design. Decide from scratch
|
|
382
|
+
whether this app even needs a header or footer, what nav (if any), and
|
|
383
|
+
what layout fits (a centered board, a full-bleed dashboard, a split, a
|
|
384
|
+
single card). The scaffold ships a `webjs-scaffold-placeholder` marker on
|
|
385
|
+
its footer, so `webjs check` fails until you remove or replace the
|
|
386
|
+
"Built with webjs" branding. Self-audit before finishing: nothing should
|
|
387
|
+
read as the scaffold example (no "Built with webjs" footer, no leftover
|
|
388
|
+
example nav, no default reading column unless it truly fits). The design
|
|
389
|
+
tokens and theme wiring in `app/layout.ts` are infrastructure to keep and
|
|
390
|
+
restyle on top of, not the example look to preserve. Style with Tailwind
|
|
391
|
+
utilities wherever they reach, and use custom CSS only for what utilities
|
|
392
|
+
cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix
|
|
393
|
+
or gradients). The `api` template has no UI, so this does not apply there.
|
|
384
394
|
7. **Keep:** the Drizzle setup, the test config, the agent config files
|
|
385
395
|
(`AGENTS.md`, `CONVENTIONS.md`, `CLAUDE.md`, `.cursorrules`, etc.),
|
|
386
396
|
`db/connection.server.ts` + `db/columns.server.ts`, the directory
|
|
@@ -1255,8 +1265,12 @@ This project enforces a git workflow via agent-specific config files
|
|
|
1255
1265
|
(`Add contact form with email validation`, not `update files`).
|
|
1256
1266
|
- **NEVER add AI attribution.** No `Co-Authored-By: Claude`, no
|
|
1257
1267
|
`Generated by AI`, no `AI-assisted` trailers or prefixes.
|
|
1258
|
-
- **Committing is automatic
|
|
1259
|
-
"please commit". Commit after completing each
|
|
1268
|
+
- **Committing is automatic (this overrides Claude Code's default).** The
|
|
1269
|
+
user should never have to ask "please commit". Commit after completing each
|
|
1270
|
+
logical unit. Claude Code's built-in never-commit default is overridden by
|
|
1271
|
+
`CLAUDE.md`, and the `.claude/hooks/commit-before-stop.sh` Stop hook refuses
|
|
1272
|
+
to end a turn with a pile of uncommitted work on a feature branch (disable
|
|
1273
|
+
with `WEBJS_NO_COMMIT_STOP=1`).
|
|
1260
1274
|
|
|
1261
1275
|
**Branch rules:**
|
|
1262
1276
|
- **Feature branches.** Never commit directly to main
|
|
@@ -58,6 +58,12 @@ export default function FormsFeature({ searchParams, actionData }: { searchParam
|
|
|
58
58
|
// The page action runs on a non-GET submission to this URL (the no-JS write
|
|
59
59
|
// path). Validate, then return a failure (re-renders at 422 with fieldErrors +
|
|
60
60
|
// values) or a success with a same-site `redirect` (a 303 PRG to the confirmation).
|
|
61
|
+
//
|
|
62
|
+
// FOOTGUN: to redirect on success, RETURN `{ success: true, redirect: '/path' }`
|
|
63
|
+
// (a 303 See Other, so the browser follows with a GET). Do NOT THROW `redirect()`
|
|
64
|
+
// from a page action, that is a 307 which PRESERVES the POST method and body, so
|
|
65
|
+
// the browser re-POSTs to the target and re-runs the mutation (a duplicate write).
|
|
66
|
+
// Throw `redirect()` only from a page render / GET context, never a page action.
|
|
61
67
|
export async function action({ formData }: { formData: FormData }): Promise<Result> {
|
|
62
68
|
const name = String(formData.get('name') ?? '').trim();
|
|
63
69
|
const email = String(formData.get('email') ?? '').trim();
|
|
@@ -22,7 +22,8 @@ export default function ServiceWorkerExample() {
|
|
|
22
22
|
<pre class="bg-card border border-border rounded-xl p-4 overflow-x-auto text-sm font-mono mb-4"><code>connectedCallback() {
|
|
23
23
|
super.connectedCallback();
|
|
24
24
|
if ('serviceWorker' in navigator) {
|
|
25
|
-
//
|
|
25
|
+
// The framework serves your public/sw.js at the site root /sw.js (with a
|
|
26
|
+
// Service-Worker-Allowed: / header), so the worker's scope is the whole origin.
|
|
26
27
|
navigator.serviceWorker.register('/sw.js');
|
|
27
28
|
}
|
|
28
29
|
}</code></pre>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Co-located browser test for the <counter-card> component. This is the webjs
|
|
2
|
+
// component-test SHAPE, so copy it for your own components:
|
|
3
|
+
// - It runs in REAL Chromium (webjs test --browser, or npx wtr), not jsdom. If
|
|
4
|
+
// the browser binary is missing, install it once: npx playwright install chromium.
|
|
5
|
+
// - The runner's mocha UI is `tdd`, so use suite() / test(), NOT describe / it().
|
|
6
|
+
// - There is NO assertion library in the importmap (no chai, no expect). Throw
|
|
7
|
+
// to fail. A tiny inline `assert` is plenty.
|
|
8
|
+
// - `ssrFixture` server-renders AND hydrates the component, so you exercise the
|
|
9
|
+
// REAL SSR output and the client interactivity, not a jsdom approximation.
|
|
10
|
+
// It lives NEXT TO the component (a `browser/` dir inside the module), the
|
|
11
|
+
// co-located default; the runner discovers browser tests under any browser dir.
|
|
12
|
+
import { html } from '@webjsdev/core';
|
|
13
|
+
import { ssrFixture } from '@webjsdev/core/testing';
|
|
14
|
+
import '../counter-card.ts';
|
|
15
|
+
|
|
16
|
+
const assert = (cond, msg) => { if (!cond) throw new Error(msg || 'assertion failed'); };
|
|
17
|
+
|
|
18
|
+
suite('<counter-card>', () => {
|
|
19
|
+
test('SSRs its initial state and the default label', async () => {
|
|
20
|
+
const el = await ssrFixture(html`<counter-card></counter-card>`);
|
|
21
|
+
assert(el.textContent.includes('0'), 'the count starts at 0');
|
|
22
|
+
assert(el.textContent.includes('Clicks'), 'the default label renders');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('reads the label reactive prop', async () => {
|
|
26
|
+
const el = await ssrFixture(html`<counter-card label="Taps"></counter-card>`);
|
|
27
|
+
assert(el.textContent.includes('Taps'), 'the provided label renders');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('increments on click (hydrated interactivity)', async () => {
|
|
31
|
+
const el = await ssrFixture(html`<counter-card></counter-card>`);
|
|
32
|
+
el.querySelector('button').click();
|
|
33
|
+
await el.updateComplete;
|
|
34
|
+
assert(el.textContent.includes('1'), 'the count becomes 1 after one click');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -14,6 +14,12 @@ export async function listTodos(): Promise<Todo[]> {
|
|
|
14
14
|
// imported column mis-compiles to a bad SQL alias in rc.3. Do NOT use
|
|
15
15
|
// `db.select({ col })` either (its projection overload trips TS2554 in rc.3).
|
|
16
16
|
// See the Database (Drizzle) section in this app's AGENTS.md.
|
|
17
|
+
//
|
|
18
|
+
// Two equivalent read styles, both fine: the relational query API
|
|
19
|
+
// (`db.query.todos.findFirst({ where: { id } })` / `findMany`, used here and in
|
|
20
|
+
// toggle-todo) reads by an object filter; the core builder
|
|
21
|
+
// (`db.select().from(todos).where(eq(todos.id, id))`) reads with the `eq()`
|
|
22
|
+
// helper. Reach for whichever fits; the relational form is terser for by-id reads.
|
|
17
23
|
const rows = await db.query.todos.findMany({ orderBy: { createdAt: 'desc' } });
|
|
18
24
|
return rows as Todo[];
|
|
19
25
|
}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Run: webjs test --browser
|
|
5
5
|
* npx wtr
|
|
6
|
+
* First run only, if Chromium is missing: npx playwright install chromium
|
|
7
|
+
*
|
|
8
|
+
* The runner's mocha UI is `tdd`: use suite() / test() (NOT describe / it), and
|
|
9
|
+
* throw to fail (there is no chai / expect in the importmap; a tiny inline assert
|
|
10
|
+
* like the one below is the idiom). See modules/components/components/browser for
|
|
11
|
+
* a co-located COMPONENT browser test using ssrFixture.
|
|
6
12
|
*
|
|
7
13
|
* Tests here have full access to real browser APIs: Shadow DOM,
|
|
8
14
|
* adoptedStyleSheets, IntersectionObserver, events, etc.
|
|
@@ -33,7 +33,15 @@ export default {
|
|
|
33
33
|
// Browser tests are `.js` (web-test-runner serves them through its own test
|
|
34
34
|
// framework); the components + modules they import are `.ts`, served
|
|
35
35
|
// transformed by the webjs middleware below.
|
|
36
|
-
|
|
36
|
+
// Browser tests live under any `browser/` dir: the top-level `test/<feature>/
|
|
37
|
+
// browser/`, OR co-located next to the code they exercise (a component's
|
|
38
|
+
// `modules/<feature>/components/browser/`, the modules-architecture default).
|
|
39
|
+
files: [
|
|
40
|
+
'test/**/browser/**/*.test.js',
|
|
41
|
+
'app/**/browser/**/*.test.js',
|
|
42
|
+
'modules/**/browser/**/*.test.js',
|
|
43
|
+
'components/**/browser/**/*.test.js',
|
|
44
|
+
],
|
|
37
45
|
// webjs's importmap resolves `@webjsdev/core`, the `#` app aliases, and
|
|
38
46
|
// vendors, so web-test-runner must NOT rewrite bare specifiers to
|
|
39
47
|
// node_modules paths.
|