@webjsdev/cli 0.10.0 → 0.10.2
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/bin/webjs.js +22 -8
- package/lib/create.js +11 -1
- package/package.json +1 -1
- package/templates/.claude/hooks/require-tests-with-src.sh +83 -0
- package/templates/.claude/settings.json +9 -0
- package/templates/.dockerignore +43 -0
- package/templates/.github/workflows/ci.yml +105 -0
- package/templates/.hooks/pre-commit +6 -25
- package/templates/AGENTS.md +25 -5
- package/templates/CONVENTIONS.md +22 -4
- package/templates/Dockerfile +38 -0
- package/templates/compose.yaml +28 -0
package/bin/webjs.js
CHANGED
|
@@ -148,17 +148,31 @@ async function main() {
|
|
|
148
148
|
const { readdir } = await import('node:fs/promises');
|
|
149
149
|
const testFiles = [];
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
// Walk test/ recursively so the documented feature-folder layout
|
|
152
|
+
// (test/<feature>/<name>.test.ts) is discovered, not just files
|
|
153
|
+
// sitting directly in test/. Two kinds are NOT run here:
|
|
154
|
+
// - **/browser/** → real-browser tests, owned by WTR below.
|
|
155
|
+
// - **/e2e/** → full-app boot, opt-in via WEBJS_E2E=1 (the
|
|
156
|
+
// documented "WEBJS_E2E=1 webjs test adds the
|
|
157
|
+
// e2e tests" semantics).
|
|
158
|
+
const runE2E = !!process.env.WEBJS_E2E;
|
|
159
|
+
const walk = async (dir, segments) => {
|
|
160
|
+
let entries;
|
|
161
|
+
try { entries = await readdir(dir, { withFileTypes: true }); }
|
|
162
|
+
catch { return; }
|
|
163
|
+
for (const ent of entries) {
|
|
164
|
+
if (ent.name === 'node_modules') continue;
|
|
165
|
+
const full = join(dir, ent.name);
|
|
166
|
+
if (ent.isDirectory()) {
|
|
167
|
+
if (ent.name === 'browser') continue;
|
|
168
|
+
if (ent.name === 'e2e' && !runE2E) continue;
|
|
169
|
+
await walk(full, [...segments, ent.name]);
|
|
170
|
+
} else if (/\.test\.(js|ts|mjs|mts)$/.test(ent.name)) {
|
|
158
171
|
if (!testFiles.includes(full)) testFiles.push(full);
|
|
159
172
|
}
|
|
160
173
|
}
|
|
161
|
-
}
|
|
174
|
+
};
|
|
175
|
+
await walk(join(cwd, 'test'), []);
|
|
162
176
|
|
|
163
177
|
if (testFiles.length > 0) {
|
|
164
178
|
console.log(`webjs test: running ${testFiles.length} server test file(s)…\n`);
|
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',
|
|
@@ -391,7 +392,16 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
391
392
|
'.cursorrules',
|
|
392
393
|
'.github/copilot-instructions.md',
|
|
393
394
|
'.github/pull_request_template.md',
|
|
395
|
+
// CI is the test gate (the pre-commit hook only blocks main). Runs
|
|
396
|
+
// webjs check + the unit / browser / e2e layers on every PR and push
|
|
397
|
+
// to main, mirroring the webjs framework's own CI.
|
|
398
|
+
'.github/workflows/ci.yml',
|
|
394
399
|
'.editorconfig',
|
|
400
|
+
// Production / deploy scaffolding. `docker compose up --build` runs
|
|
401
|
+
// the app locally with the same Dockerfile production builds from.
|
|
402
|
+
'Dockerfile',
|
|
403
|
+
'compose.yaml',
|
|
404
|
+
'.dockerignore',
|
|
395
405
|
];
|
|
396
406
|
for (const f of templateFiles) {
|
|
397
407
|
const src = join(TEMPLATES, f);
|
|
@@ -405,7 +415,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
405
415
|
|
|
406
416
|
// Make hook scripts executable
|
|
407
417
|
const { chmod } = await import('node:fs/promises');
|
|
408
|
-
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh']) {
|
|
418
|
+
for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh', 'require-tests-with-src.sh']) {
|
|
409
419
|
const hookPath = join(appDir, '.claude', 'hooks', hook);
|
|
410
420
|
if (existsSync(hookPath)) await chmod(hookPath, 0o755);
|
|
411
421
|
}
|
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
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.github
|
|
3
|
+
node_modules
|
|
4
|
+
**/node_modules
|
|
5
|
+
|
|
6
|
+
# `.webjs/` is ignored EXCEPT for `.webjs/vendor/`, which holds the committed
|
|
7
|
+
# importmap manifest (and optionally downloaded bundle bytes) the server needs
|
|
8
|
+
# at boot without reaching api.jspm.io. DO NOT collapse to `**/.webjs`: parent
|
|
9
|
+
# exclusion blocks child negations and the vendor files would never ship.
|
|
10
|
+
.webjs/*
|
|
11
|
+
!.webjs/vendor/
|
|
12
|
+
!.webjs/vendor/**
|
|
13
|
+
|
|
14
|
+
dist
|
|
15
|
+
build
|
|
16
|
+
out
|
|
17
|
+
.cache
|
|
18
|
+
|
|
19
|
+
# Local env files. The container gets its env from compose / uncloud, not a
|
|
20
|
+
# committed file. Keep the example for reference.
|
|
21
|
+
.env
|
|
22
|
+
!.env.example
|
|
23
|
+
|
|
24
|
+
# Prisma local SQLite. Schema + migrations ship; the runtime volume owns the db.
|
|
25
|
+
dev.db
|
|
26
|
+
dev.db-journal
|
|
27
|
+
prisma/dev.db
|
|
28
|
+
prisma/dev.db-journal
|
|
29
|
+
|
|
30
|
+
# logs
|
|
31
|
+
*.log
|
|
32
|
+
npm-debug.log*
|
|
33
|
+
|
|
34
|
+
# editors / OS
|
|
35
|
+
.vscode
|
|
36
|
+
.idea
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
|
|
40
|
+
# tests aren't needed in the production image
|
|
41
|
+
coverage/
|
|
42
|
+
test/
|
|
43
|
+
**/test/
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
name: CI
|
|
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.
|
|
8
|
+
#
|
|
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).
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
pull_request:
|
|
16
|
+
branches: [main]
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
|
|
20
|
+
# A newer push to the same branch cancels the older in-flight run.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: ci-${{ github.ref }}
|
|
23
|
+
cancel-in-progress: true
|
|
24
|
+
|
|
25
|
+
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
|
+
|
|
38
|
+
unit:
|
|
39
|
+
name: Unit + integration (node --test)
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v6
|
|
43
|
+
- uses: actions/setup-node@v6
|
|
44
|
+
with:
|
|
45
|
+
node-version: '24'
|
|
46
|
+
cache: npm
|
|
47
|
+
- run: npm ci
|
|
48
|
+
- run: npx prisma generate
|
|
49
|
+
- name: Apply migrations to the test database
|
|
50
|
+
run: npx prisma migrate deploy
|
|
51
|
+
env:
|
|
52
|
+
DATABASE_URL: file:./ci.db
|
|
53
|
+
# --server keeps this job to node:test (the browser layer is its own
|
|
54
|
+
# job below). Without WEBJS_E2E the e2e folders are skipped too.
|
|
55
|
+
- run: npm run test:server
|
|
56
|
+
env:
|
|
57
|
+
DATABASE_URL: file:./ci.db
|
|
58
|
+
|
|
59
|
+
browser:
|
|
60
|
+
name: Browser (web-test-runner / Playwright)
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v6
|
|
64
|
+
- uses: actions/setup-node@v6
|
|
65
|
+
with:
|
|
66
|
+
node-version: '24'
|
|
67
|
+
cache: npm
|
|
68
|
+
- run: npm ci
|
|
69
|
+
- run: npx prisma generate
|
|
70
|
+
- name: Install Playwright Chromium
|
|
71
|
+
run: npx playwright install --with-deps chromium
|
|
72
|
+
- run: npm run test:browser
|
|
73
|
+
|
|
74
|
+
e2e:
|
|
75
|
+
name: E2E (full app boot)
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v6
|
|
79
|
+
- uses: actions/setup-node@v6
|
|
80
|
+
with:
|
|
81
|
+
node-version: '24'
|
|
82
|
+
cache: npm
|
|
83
|
+
- run: npm ci
|
|
84
|
+
- run: npx prisma generate
|
|
85
|
+
- name: Apply migrations to the test database
|
|
86
|
+
run: npx prisma migrate deploy
|
|
87
|
+
env:
|
|
88
|
+
DATABASE_URL: file:./ci.db
|
|
89
|
+
# The scaffold's e2e test (test/hello/e2e/) drives a real browser
|
|
90
|
+
# via puppeteer-core, which is not a default dependency (the test
|
|
91
|
+
# skips when it is absent). Install it and Chromium so the e2e
|
|
92
|
+
# layer actually runs in CI rather than skipping silently.
|
|
93
|
+
- name: Install puppeteer-core + Chromium
|
|
94
|
+
run: |
|
|
95
|
+
npm install --no-save puppeteer-core
|
|
96
|
+
npx playwright install --with-deps chromium
|
|
97
|
+
- name: Resolve the Chromium binary path
|
|
98
|
+
run: echo "CHROMIUM_PATH=$(node -e "console.log(require('playwright-core').chromium.executablePath())")" >> "$GITHUB_ENV"
|
|
99
|
+
# --server with WEBJS_E2E=1 runs node:test including the e2e folders
|
|
100
|
+
# (the runner gates them on that env var) and skips the browser layer.
|
|
101
|
+
- name: Run e2e
|
|
102
|
+
env:
|
|
103
|
+
WEBJS_E2E: '1'
|
|
104
|
+
DATABASE_URL: file:./ci.db
|
|
105
|
+
run: npm run test:server
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
#
|
|
3
|
-
# pre-commit hook
|
|
3
|
+
# pre-commit hook. Blocks commits on main/master.
|
|
4
4
|
#
|
|
5
5
|
# No AI agent, no editor, no human can commit to main directly.
|
|
6
6
|
# Create a feature branch first. This is git-level enforcement.
|
|
7
7
|
#
|
|
8
8
|
# To bypass in emergencies: git commit --no-verify
|
|
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.
|
|
9
14
|
|
|
10
15
|
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
11
16
|
|
|
@@ -21,28 +26,4 @@ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
|
21
26
|
exit 1
|
|
22
27
|
fi
|
|
23
28
|
|
|
24
|
-
# webjs test + webjs check on every commit. Tool-agnostic enforcement:
|
|
25
|
-
# fires regardless of which agent (Claude, Cursor, Antigravity, Copilot,
|
|
26
|
-
# human) is making the commit. Skipped if the CLI is not yet installed
|
|
27
|
-
# (fresh clone before npm install).
|
|
28
|
-
if command -v webjs >/dev/null 2>&1 || [ -x "node_modules/.bin/webjs" ]; then
|
|
29
|
-
echo "Running webjs test..."
|
|
30
|
-
if ! npx --no-install webjs test; then
|
|
31
|
-
echo ""
|
|
32
|
-
echo "ERROR: webjs test failed. Fix tests before committing."
|
|
33
|
-
echo "To bypass (emergencies only): git commit --no-verify"
|
|
34
|
-
echo ""
|
|
35
|
-
exit 1
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
echo "Running webjs check..."
|
|
39
|
-
if ! npx --no-install webjs check; then
|
|
40
|
-
echo ""
|
|
41
|
-
echo "ERROR: webjs check failed. Fix convention violations before committing."
|
|
42
|
-
echo "To bypass (emergencies only): git commit --no-verify"
|
|
43
|
-
echo ""
|
|
44
|
-
exit 1
|
|
45
|
-
fi
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
29
|
exit 0
|
package/templates/AGENTS.md
CHANGED
|
@@ -301,6 +301,16 @@ In Docker / Railway, prefer `npm start` (or `node node_modules/.bin/npm
|
|
|
301
301
|
start`) as the CMD over `node ... webjs.js start ...`. The npm form
|
|
302
302
|
fires `prestart`; the direct binary form skips it.
|
|
303
303
|
|
|
304
|
+
**Containerized deploy ships with the scaffold.** `Dockerfile`,
|
|
305
|
+
`compose.yaml`, and `.dockerignore` are scaffolded at the app root. The
|
|
306
|
+
Dockerfile pins `node:24-alpine` (the same Node major CI uses), installs
|
|
307
|
+
deps, runs `prisma generate`, and starts via `npm start` so `prestart`
|
|
308
|
+
applies migrations. Run it locally with `docker compose up --build` (the
|
|
309
|
+
app comes up on http://localhost:8080 against a SQLite file on a named
|
|
310
|
+
volume). For production, point `DATABASE_URL` at managed Postgres and set
|
|
311
|
+
`AUTH_SECRET`. The `.dockerignore` keeps the `.webjs/vendor/` importmap in
|
|
312
|
+
the image while excluding `node_modules`, tests, and local state.
|
|
313
|
+
|
|
304
314
|
**Health and readiness probes.** Every webjs server answers two endpoints:
|
|
305
315
|
`/__webjs/health` (liveness, 200 once the process is listening) and
|
|
306
316
|
`/__webjs/ready` (readiness, 503 until the instance is fully warm, then 200).
|
|
@@ -883,8 +893,17 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
883
893
|
## Workflow expectations for AI agents
|
|
884
894
|
|
|
885
895
|
1. Branch before editing. Never push to `main` directly.
|
|
886
|
-
2. Every code change comes with
|
|
887
|
-
|
|
896
|
+
2. Every code change comes with a test, AGENTS.md / docs updates if the
|
|
897
|
+
feature surface changed, `webjs check` passing. A unit test is not
|
|
898
|
+
always enough: a component, hydration, the client router, or a server
|
|
899
|
+
action called from the client needs a browser test
|
|
900
|
+
(`webjs test --browser`) asserting the behaviour in a real browser. A
|
|
901
|
+
commit that stages app code (`app/`, `modules/`, `components/`, `lib/`)
|
|
902
|
+
with no test is blocked for Claude Code by
|
|
903
|
+
`.claude/hooks/require-tests-with-src.sh`. The test suite itself runs in
|
|
904
|
+
CI (`.github/workflows/ci.yml`), not in the pre-commit hook, so `git
|
|
905
|
+
commit` stays fast and the gate cannot be skipped with a local
|
|
906
|
+
`--no-verify`.
|
|
888
907
|
3. Commit and push **per logical unit**, not at the end. A logical unit is one
|
|
889
908
|
feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
|
|
890
909
|
spanning different concerns, commit the current group before continuing.
|
|
@@ -900,9 +919,10 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
900
919
|
| Antigravity (Google) | text rule only (post-write hooks not yet exposed) | `.agents/rules/workflow.md` |
|
|
901
920
|
| GitHub Copilot | text rule only (no hooks API) | `.github/copilot-instructions.md` |
|
|
902
921
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
922
|
+
The `.hooks/pre-commit` hook blocks commits to main and nothing else;
|
|
923
|
+
`webjs test` + `webjs check` run in CI (`.github/workflows/ci.yml`) on
|
|
924
|
+
every PR and push to main, regardless of which agent (or human) made
|
|
925
|
+
the commit. No AI attribution trailers in commit messages.
|
|
906
926
|
4. Run the **pre-merge self-review loop** before signaling the PR is
|
|
907
927
|
ready. After committing the work, trigger a fresh-context review
|
|
908
928
|
pass (a new chat / composer tab / subagent / Cascade thread
|
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.** For Claude Code, a commit that
|
|
455
|
+
stages app code (`app/`, `modules/`, `components/`, `lib/`) without
|
|
456
|
+
staging a test is blocked by `.claude/hooks/require-tests-with-src.sh`.
|
|
457
|
+
A unit test alone is not enough for interactive or component code: add
|
|
458
|
+
the browser test that asserts the rendered/hydrated behaviour. The test
|
|
459
|
+
suite itself runs in CI (`.github/workflows/ci.yml`) on every PR and
|
|
460
|
+
push to main, not in the local pre-commit hook, so `git commit` stays
|
|
461
|
+
fast and the gate cannot be skipped with a local `--no-verify`.
|
|
462
|
+
|
|
454
463
|
### Choosing a feature folder
|
|
455
464
|
|
|
456
465
|
Use the same name as the matching module folder when one exists:
|
|
@@ -994,10 +1003,19 @@ This project enforces a git workflow via agent-specific config files
|
|
|
994
1003
|
Other agents enforce this via `.cursorrules`, `.agents/rules/workflow.md`,
|
|
995
1004
|
`.github/copilot-instructions.md`.
|
|
996
1005
|
|
|
997
|
-
**Pre-commit
|
|
998
|
-
- `
|
|
999
|
-
|
|
1000
|
-
|
|
1006
|
+
**Pre-commit hook (`.hooks/pre-commit`):**
|
|
1007
|
+
- Blocks commits to `main` / `master`. Nothing else runs locally, so
|
|
1008
|
+
`git commit` stays fast. Keeping commits to one logical unit (no
|
|
1009
|
+
unrelated files) is your discipline plus the `nudge-uncommitted`
|
|
1010
|
+
hooks, not something this hook enforces.
|
|
1011
|
+
|
|
1012
|
+
**CI gate (`.github/workflows/ci.yml`), on every PR and push to main:**
|
|
1013
|
+
- `webjs check` (conventions) must pass
|
|
1014
|
+
- `webjs test` (unit + integration), the browser layer, and the e2e
|
|
1015
|
+
layer must pass
|
|
1016
|
+
- Mark these as required status checks in the branch-protection rule for
|
|
1017
|
+
main so a PR can only merge when the gate is green. The gate lives in
|
|
1018
|
+
CI, where a local `--no-verify` cannot skip it.
|
|
1001
1019
|
|
|
1002
1020
|
---
|
|
1003
1021
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Production image for the {{APP_NAME}} webjs app.
|
|
2
|
+
#
|
|
3
|
+
# Works with a plain `docker build` / `docker compose up`, and is the same
|
|
4
|
+
# artifact the webdeploy hosting tool (ubicloud + uncloud) builds and ships.
|
|
5
|
+
#
|
|
6
|
+
# webjs serves .ts directly via Node's built-in type-stripping, so there is
|
|
7
|
+
# NO JavaScript build step. **Node 24+ is REQUIRED**: on older Node the runtime
|
|
8
|
+
# falls back to esbuild, whose class-declaration transform breaks webjs's SSR
|
|
9
|
+
# walker for multi-class component files. Do not lower this base image
|
|
10
|
+
# below 24 (the same version the CI workflow and the framework pin).
|
|
11
|
+
FROM node:24-alpine
|
|
12
|
+
|
|
13
|
+
# openssl + ca-certificates are required by Prisma's query engine at runtime.
|
|
14
|
+
RUN apk add --no-cache openssl ca-certificates
|
|
15
|
+
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
# Install deps first so this layer is cached unless the manifests change.
|
|
19
|
+
# package-lock.json is optional (it's absent when the app was scaffolded with
|
|
20
|
+
# --no-install); the glob keeps the COPY working with or without it.
|
|
21
|
+
COPY package.json package-lock.json* ./
|
|
22
|
+
RUN npm install --no-audit --no-fund
|
|
23
|
+
|
|
24
|
+
# App source. node_modules and local state are excluded via .dockerignore.
|
|
25
|
+
COPY . .
|
|
26
|
+
|
|
27
|
+
# Generate the Prisma client at build time (every scaffold ships a
|
|
28
|
+
# prisma/schema.prisma). If you remove Prisma from the app, delete this line.
|
|
29
|
+
RUN npx prisma generate
|
|
30
|
+
|
|
31
|
+
ENV NODE_ENV=production
|
|
32
|
+
# webjs start reads $PORT (default 8080). compose / uncloud / Railway set it.
|
|
33
|
+
ENV PORT=8080
|
|
34
|
+
EXPOSE 8080
|
|
35
|
+
|
|
36
|
+
# `npm start` runs `prestart: prisma migrate deploy` (idempotent, a no-op when
|
|
37
|
+
# there are no migrations yet) and then `webjs start`, which serves on $PORT.
|
|
38
|
+
CMD ["npm", "start"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# {{APP_NAME}} local parity with production, using the same Dockerfile, one service.
|
|
2
|
+
#
|
|
3
|
+
# docker compose up --build → http://localhost:8080
|
|
4
|
+
#
|
|
5
|
+
# In production the webdeploy tool (ubicloud + uncloud) provisions a managed
|
|
6
|
+
# Postgres and injects DATABASE_URL + AUTH_SECRET for you. Locally this uses
|
|
7
|
+
# the scaffold's SQLite file on a named volume so data survives `compose down`.
|
|
8
|
+
services:
|
|
9
|
+
app:
|
|
10
|
+
build: .
|
|
11
|
+
ports:
|
|
12
|
+
- "8080:8080"
|
|
13
|
+
environment:
|
|
14
|
+
PORT: 8080
|
|
15
|
+
# SQLite on a volume for local dev. For production, point DATABASE_URL at
|
|
16
|
+
# your managed Postgres and switch prisma/schema.prisma's provider to
|
|
17
|
+
# "postgresql".
|
|
18
|
+
DATABASE_URL: file:/data/dev.db
|
|
19
|
+
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
20
|
+
AUTH_SECRET: ${AUTH_SECRET:-change-me-please-at-least-32-characters!}
|
|
21
|
+
# Uncomment to back cache / sessions / rate-limiting / pub-sub with Redis
|
|
22
|
+
# instead of the in-memory defaults.
|
|
23
|
+
# REDIS_URL: redis://redis:6379
|
|
24
|
+
volumes:
|
|
25
|
+
- app-data:/data
|
|
26
|
+
|
|
27
|
+
volumes:
|
|
28
|
+
app-data:
|