@webjsdev/cli 0.10.0 → 0.10.1
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
CHANGED
|
@@ -373,6 +373,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
373
373
|
'.claude/hooks/block-prose-punctuation.sh',
|
|
374
374
|
'.claude/hooks/guard-branch-context.sh',
|
|
375
375
|
'.claude/hooks/nudge-uncommitted.sh',
|
|
376
|
+
'.claude/hooks/require-tests-with-src.sh',
|
|
376
377
|
// Gemini CLI config + hooks
|
|
377
378
|
'.gemini/settings.json',
|
|
378
379
|
'.gemini/hooks/nudge-uncommitted.sh',
|
|
@@ -405,7 +406,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
405
406
|
|
|
406
407
|
// Make hook scripts executable
|
|
407
408
|
const { chmod } = await import('node:fs/promises');
|
|
408
|
-
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh']) {
|
|
409
|
+
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh', 'require-tests-with-src.sh']) {
|
|
409
410
|
const hookPath = join(appDir, '.claude', 'hooks', hook);
|
|
410
411
|
if (existsSync(hookPath)) await chmod(hookPath, 0o755);
|
|
411
412
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse hook (scaffolded by `webjs create`): block a `git commit`
|
|
4
|
+
# that adds or changes application code without any accompanying test.
|
|
5
|
+
#
|
|
6
|
+
# webjs is AI-first: most apps are built with an AI agent, and the
|
|
7
|
+
# easiest corner to cut is shipping a feature with no test. This gate
|
|
8
|
+
# makes "every change ships with a test" a hard floor, not a suggestion.
|
|
9
|
+
#
|
|
10
|
+
# What a hook CANNOT do: judge WHICH test layer a change needs (a unit
|
|
11
|
+
# test vs a browser/e2e test is a judgement call). So it enforces the
|
|
12
|
+
# floor (some real test must accompany app code) and reminds you to add
|
|
13
|
+
# browser/e2e coverage for interactive surfaces. `webjs test` runs the
|
|
14
|
+
# actual suite in the commit hook.
|
|
15
|
+
#
|
|
16
|
+
# Scope: fires only on `git commit`. Inspects the STAGED diff.
|
|
17
|
+
#
|
|
18
|
+
# Blocks (exit 2) when the staged diff changes app code (app/, modules/,
|
|
19
|
+
# components/, lib/) but stages no test (test/** or *.test.* / *.spec.*).
|
|
20
|
+
# Allowed: commits with no app-code change, commits that stage a test
|
|
21
|
+
# alongside, and WEBJS_NO_TEST_GATE=1 for a genuine non-code commit.
|
|
22
|
+
#
|
|
23
|
+
# Bypass (humans, emergencies): git commit --no-verify.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
if [ "${WEBJS_NO_TEST_GATE:-}" = "1" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
payload=$(cat)
|
|
32
|
+
cmd=$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
|
|
33
|
+
if [ -z "$cmd" ]; then exit 0; fi
|
|
34
|
+
# Match `git commit` as a whole word so sibling subcommands
|
|
35
|
+
# (git commit-graph, git commit-tree) and string mentions do not trip it.
|
|
36
|
+
if ! printf '%s' "$cmd" | grep -Eq '(^|[^[:alnum:]-])git commit([^[:alnum:]-]|$)'; then
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then exit 0; fi
|
|
41
|
+
|
|
42
|
+
staged=$(git diff --cached --name-only 2>/dev/null || true)
|
|
43
|
+
if [ -z "$staged" ]; then exit 0; fi
|
|
44
|
+
|
|
45
|
+
# App code lives under app/, modules/, components/, lib/. A `.server.*`
|
|
46
|
+
# file is still app code. Match source extensions only (skip .css, .md).
|
|
47
|
+
app_code=$(printf '%s\n' "$staged" \
|
|
48
|
+
| grep -E '^(app|modules|components|lib)/.*\.([mc]?[jt]sx?)$' || true)
|
|
49
|
+
if [ -z "$app_code" ]; then exit 0; fi
|
|
50
|
+
|
|
51
|
+
test_staged=$(printf '%s\n' "$staged" \
|
|
52
|
+
| grep -E '(^|/)test/|\.test\.[mc]?[jt]sx?$|\.spec\.[mc]?[jt]sx?$' || true)
|
|
53
|
+
|
|
54
|
+
if [ -z "$test_staged" ]; then
|
|
55
|
+
cat >&2 <<'EOF'
|
|
56
|
+
BLOCKED: this commit changes app code but stages no test.
|
|
57
|
+
|
|
58
|
+
You staged application code (app/, modules/, components/, lib/) with no
|
|
59
|
+
accompanying test. Every change ships with a test. Add or update the test
|
|
60
|
+
that proves the new behaviour, then `git add` it.
|
|
61
|
+
|
|
62
|
+
Pick the layer the change needs (a unit test is not always enough):
|
|
63
|
+
- logic / actions / queries / utils -> a unit test
|
|
64
|
+
- a component, hydration, a server action called from the client, the
|
|
65
|
+
router, anything interactive -> a browser or e2e test that asserts the
|
|
66
|
+
real behaviour in a browser, not just the function in isolation.
|
|
67
|
+
|
|
68
|
+
See `webjs test` and the testing guide. Genuine non-code commit (docs,
|
|
69
|
+
config) that needs no test? Re-run with WEBJS_NO_TEST_GATE=1.
|
|
70
|
+
|
|
71
|
+
Hook: .claude/hooks/require-tests-with-src.sh
|
|
72
|
+
EOF
|
|
73
|
+
exit 2
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Reminder for interactive surfaces: a unit test alone rarely covers them.
|
|
77
|
+
interactive=$(printf '%s\n' "$app_code" | grep -E '^components/|/components/' || true)
|
|
78
|
+
if [ -n "$interactive" ]; then
|
|
79
|
+
jq -n --arg ctx "Reminder: this commit changes component code. A unit test alone usually is not enough for an interactive component; add a browser test (webjs test --browser) that asserts the rendered/hydrated behaviour." '{
|
|
80
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: $ctx }
|
|
81
|
+
}'
|
|
82
|
+
fi
|
|
83
|
+
exit 0
|
|
@@ -21,6 +21,31 @@ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
|
21
21
|
exit 1
|
|
22
22
|
fi
|
|
23
23
|
|
|
24
|
+
# Require a test to accompany app-code changes. Tool-agnostic floor: a
|
|
25
|
+
# commit that stages app code (app/, modules/, components/, lib/) with no
|
|
26
|
+
# test (test/** or *.test.* / *.spec.*) is blocked. The full suite below
|
|
27
|
+
# proves tests PASS; this proves a test was WRITTEN. Bypass for a genuine
|
|
28
|
+
# non-code commit with WEBJS_NO_TEST_GATE=1, or --no-verify in emergencies.
|
|
29
|
+
if [ "${WEBJS_NO_TEST_GATE:-}" != "1" ]; then
|
|
30
|
+
STAGED=$(git diff --cached --name-only 2>/dev/null)
|
|
31
|
+
APP_CODE=$(printf '%s\n' "$STAGED" | grep -E '^(app|modules|components|lib)/.*\.([mc]?[jt]sx?)$' || true)
|
|
32
|
+
if [ -n "$APP_CODE" ]; then
|
|
33
|
+
TESTS=$(printf '%s\n' "$STAGED" | grep -E '(^|/)test/|\.test\.[mc]?[jt]sx?$|\.spec\.[mc]?[jt]sx?$' || true)
|
|
34
|
+
if [ -z "$TESTS" ]; then
|
|
35
|
+
echo ""
|
|
36
|
+
echo "ERROR: app code changed but no test is staged."
|
|
37
|
+
echo "Every change ships with a test. Add or update the test that"
|
|
38
|
+
echo "proves the new behaviour (a browser/e2e test for interactive"
|
|
39
|
+
echo "or component code), then git add it."
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Genuine non-code commit? WEBJS_NO_TEST_GATE=1 git commit ..."
|
|
42
|
+
echo "Emergency bypass: git commit --no-verify"
|
|
43
|
+
echo ""
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
24
49
|
# webjs test + webjs check on every commit. Tool-agnostic enforcement:
|
|
25
50
|
# fires regardless of which agent (Claude, Cursor, Antigravity, Copilot,
|
|
26
51
|
# human) is making the commit. Skipped if the CLI is not yet installed
|
package/templates/AGENTS.md
CHANGED
|
@@ -883,8 +883,15 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
883
883
|
## Workflow expectations for AI agents
|
|
884
884
|
|
|
885
885
|
1. Branch before editing. Never push to `main` directly.
|
|
886
|
-
2. Every code change comes with
|
|
887
|
-
|
|
886
|
+
2. Every code change comes with a test, AGENTS.md / docs updates if the
|
|
887
|
+
feature surface changed, `webjs check` passing. A unit test is not
|
|
888
|
+
always enough: a component, hydration, the client router, or a server
|
|
889
|
+
action called from the client needs a browser test
|
|
890
|
+
(`webjs test --browser`) asserting the behaviour in a real browser. A
|
|
891
|
+
commit that stages app code (`app/`, `modules/`, `components/`, `lib/`)
|
|
892
|
+
with no test is blocked by `.claude/hooks/require-tests-with-src.sh`
|
|
893
|
+
and the `.hooks/pre-commit` floor (bypass a genuine non-code commit
|
|
894
|
+
with `WEBJS_NO_TEST_GATE=1`).
|
|
888
895
|
3. Commit and push **per logical unit**, not at the end. A logical unit is one
|
|
889
896
|
feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
|
|
890
897
|
spanning different concerns, commit the current group before continuing.
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -451,6 +451,15 @@ test/
|
|
|
451
451
|
- `webjs test --browser` (or `npx wtr`) runs the browser tests.
|
|
452
452
|
- `WEBJS_E2E=1 webjs test` adds the e2e tests.
|
|
453
453
|
|
|
454
|
+
**Every change ships with a test, enforced at commit time.** A commit
|
|
455
|
+
that stages app code (`app/`, `modules/`, `components/`, `lib/`) without
|
|
456
|
+
staging a test is blocked by `.claude/hooks/require-tests-with-src.sh`
|
|
457
|
+
(Claude Code) and the universal `.hooks/pre-commit` (any agent or human).
|
|
458
|
+
A unit test alone is not enough for interactive or component code: add
|
|
459
|
+
the browser test that asserts the rendered/hydrated behaviour. Bypass a
|
|
460
|
+
genuine non-code commit with `WEBJS_NO_TEST_GATE=1`, or `--no-verify` in
|
|
461
|
+
an emergency.
|
|
462
|
+
|
|
454
463
|
### Choosing a feature folder
|
|
455
464
|
|
|
456
465
|
Use the same name as the matching module folder when one exists:
|