@webjsdev/cli 0.8.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/README.md +71 -0
- package/bin/webjs.js +279 -0
- package/lib/create.js +898 -0
- package/lib/saas-template.js +397 -0
- package/package.json +39 -0
- package/templates/.claude/hooks/block-prose-punctuation.sh +236 -0
- package/templates/.claude/hooks/guard-branch-context.sh +39 -0
- package/templates/.claude/hooks/nudge-uncommitted.sh +46 -0
- package/templates/.claude/settings.json +35 -0
- package/templates/.claude.json +9 -0
- package/templates/.cursor/hooks/nudge-uncommitted.sh +38 -0
- package/templates/.cursor/hooks.json +8 -0
- package/templates/.cursorrules +99 -0
- package/templates/.editorconfig +18 -0
- package/templates/.env.example +27 -0
- package/templates/.gemini/hooks/nudge-uncommitted.sh +42 -0
- package/templates/.gemini/settings.json +15 -0
- package/templates/.github/copilot-instructions.md +85 -0
- package/templates/.github/pull_request_template.md +14 -0
- package/templates/.hooks/pre-commit +48 -0
- package/templates/.opencode/plugins/nudge-uncommitted.ts +62 -0
- package/templates/.windsurfrules +91 -0
- package/templates/AGENTS.md +816 -0
- package/templates/CLAUDE.md +2 -0
- package/templates/CONVENTIONS.md +901 -0
- package/templates/lib/utils/ui.ts +83 -0
- package/templates/public/tailwind-browser.js +947 -0
- package/templates/test/hello/browser/hello.test.js +40 -0
- package/templates/test/hello/e2e/hello.test.ts +87 -0
- package/templates/test/hello/hello.test.ts +24 -0
- package/templates/web-test-runner.config.js +33 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# guard-branch-context.sh - Claude Code PreToolUse hook
|
|
4
|
+
#
|
|
5
|
+
# Rules:
|
|
6
|
+
# - On main/master → ask (agent should create a feature branch first)
|
|
7
|
+
# - On any other branch → allow (feature branches are free to edit)
|
|
8
|
+
# - Bypass mode → allow everything
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat /dev/stdin)
|
|
11
|
+
|
|
12
|
+
# Bypass mode - full autonomy
|
|
13
|
+
SETTINGS="$HOME/.claude/settings.json"
|
|
14
|
+
if [ -f "$SETTINGS" ]; then
|
|
15
|
+
BYPASS=$(jq -r '.skipDangerousModePermissionPrompt // false' "$SETTINGS" 2>/dev/null)
|
|
16
|
+
if [ "$BYPASS" = "true" ]; then
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
26
|
+
[ -z "$BRANCH" ] && exit 0
|
|
27
|
+
|
|
28
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
29
|
+
jq -n --arg reason "You are on '$BRANCH'. Create a feature branch first (git checkout -b feature/<name>), or approve to edit on '$BRANCH'." '{
|
|
30
|
+
hookSpecificOutput: {
|
|
31
|
+
hookEventName: "PreToolUse",
|
|
32
|
+
permissionDecision: "ask",
|
|
33
|
+
permissionDecisionReason: $reason
|
|
34
|
+
}
|
|
35
|
+
}'
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
exit 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Claude Code PostToolUse hook.
|
|
4
|
+
#
|
|
5
|
+
# After each Edit, Write, MultiEdit, or NotebookEdit, counts
|
|
6
|
+
# uncommitted changes in the working tree. When the count
|
|
7
|
+
# crosses a threshold (default 4, override with the
|
|
8
|
+
# WEBJS_COMMIT_NUDGE_THRESHOLD env var), injects a reminder
|
|
9
|
+
# into the model's context via hookSpecificOutput.
|
|
10
|
+
#
|
|
11
|
+
# Soft nudge. Does NOT block the edit. The goal is to keep
|
|
12
|
+
# the agent honest about the "commit per logical unit" rule,
|
|
13
|
+
# not to interrupt valid work.
|
|
14
|
+
#
|
|
15
|
+
# Skipped on main/master and outside a git work tree.
|
|
16
|
+
|
|
17
|
+
set -e
|
|
18
|
+
|
|
19
|
+
THRESHOLD="${WEBJS_COMMIT_NUDGE_THRESHOLD:-4}"
|
|
20
|
+
|
|
21
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
26
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Read stdin so we don't break Claude Code's hook contract.
|
|
31
|
+
cat /dev/stdin >/dev/null 2>&1 || true
|
|
32
|
+
|
|
33
|
+
CHANGED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
34
|
+
|
|
35
|
+
if [ -z "$CHANGED" ] || [ "$CHANGED" -lt "$THRESHOLD" ]; then
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
REASON="You have ${CHANGED} uncommitted changes on '${BRANCH}'. The webjs convention is small, focused commits per logical unit (one feature, one fix, one rename, one doc rewrite). Before continuing with more edits, group the current changes into a meaningful commit. See AGENTS.md \"Git workflow\" for the rule and the rationale. To raise the threshold for this hook in long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD."
|
|
40
|
+
|
|
41
|
+
jq -n --arg ctx "$REASON" '{
|
|
42
|
+
hookSpecificOutput: {
|
|
43
|
+
hookEventName: "PostToolUse",
|
|
44
|
+
additionalContext: $ctx
|
|
45
|
+
}
|
|
46
|
+
}'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|Bash",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": ".claude/hooks/block-prose-punctuation.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "Edit|Write",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": ".claude/hooks/guard-branch-context.sh"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PostToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": ".claude/hooks/nudge-uncommitted.sh"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Cursor afterFileEdit hook.
|
|
4
|
+
#
|
|
5
|
+
# Counterpart of .claude/hooks/nudge-uncommitted.sh. After each
|
|
6
|
+
# file edit, counts uncommitted changes in the working tree.
|
|
7
|
+
# When the count crosses a threshold (default 4, override with
|
|
8
|
+
# WEBJS_COMMIT_NUDGE_THRESHOLD), injects a reminder via the
|
|
9
|
+
# top-level additional_context field (snake_case, unlike Claude
|
|
10
|
+
# Code's nested hookSpecificOutput.additionalContext).
|
|
11
|
+
#
|
|
12
|
+
# Soft nudge. Exit 0 always. Skipped on main/master and outside
|
|
13
|
+
# a git work tree.
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
THRESHOLD="${WEBJS_COMMIT_NUDGE_THRESHOLD:-4}"
|
|
18
|
+
|
|
19
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
24
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
cat /dev/stdin >/dev/null 2>&1 || true
|
|
29
|
+
|
|
30
|
+
CHANGED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
31
|
+
|
|
32
|
+
if [ -z "$CHANGED" ] || [ "$CHANGED" -lt "$THRESHOLD" ]; then
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
REASON="You have ${CHANGED} uncommitted changes on '${BRANCH}'. The webjs convention is small, focused commits per logical unit (one feature, one fix, one rename, one doc rewrite). Before continuing with more edits, group the current changes into a meaningful commit. See AGENTS.md \"Git workflow\" for the rule and the rationale. To raise the threshold for this hook in long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD."
|
|
37
|
+
|
|
38
|
+
jq -n --arg ctx "$REASON" '{ additional_context: $ctx }'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Cursor Rules - webjs app
|
|
2
|
+
|
|
3
|
+
You are working on a webjs app - an AI-first, no-build, web-components-first
|
|
4
|
+
framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
|
|
5
|
+
project-specific conventions before writing any code. When AGENTS.md doesn't
|
|
6
|
+
cover what you need, the full hosted docs are at **https://docs.webjs.com**.
|
|
7
|
+
|
|
8
|
+
## Persistence + scaffold rules (non-negotiable)
|
|
9
|
+
|
|
10
|
+
- **Use Prisma + SQLite for data, never JSON files.** It's already wired up
|
|
11
|
+
(`prisma/schema.prisma`, `lib/prisma.server.ts`, `npm run db:migrate`). For ANY
|
|
12
|
+
data the app stores (todos, posts, messages, products, comments…),
|
|
13
|
+
define a Prisma model. NEVER create `data/*.json`, `db.json`, or any
|
|
14
|
+
JSON file as a fake database. NEVER use module-scope arrays / Maps as
|
|
15
|
+
a substitute. NEVER use localStorage for app data. `webjs check`'s
|
|
16
|
+
`no-json-data-files` rule will fail the build if you do.
|
|
17
|
+
- **The scaffold is reference, not the final product.** Replace
|
|
18
|
+
`app/page.ts`, the example `User` model, the example users module, etc.
|
|
19
|
+
with the app the user actually asked for. Don't ship "Hello from
|
|
20
|
+
<app-name>" as the deliverable.
|
|
21
|
+
- **Only three templates exist:** `webjs create <name>` (default
|
|
22
|
+
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
23
|
+
other `--template` value. Pick:
|
|
24
|
+
- Any product UI (todo, blog, dashboard, marketplace, social…) → default
|
|
25
|
+
- HTTP/JSON API only, no UI → `--template api`
|
|
26
|
+
- Auth / login / signup / SaaS → `--template saas`
|
|
27
|
+
|
|
28
|
+
## Before starting ANY work
|
|
29
|
+
|
|
30
|
+
FIRST, before writing any code:
|
|
31
|
+
1. Run `git branch --show-current` to check the branch.
|
|
32
|
+
- If on main/master: STOP. Ask the user which branch to use, or create
|
|
33
|
+
one with `git checkout -b feature/<name>`.
|
|
34
|
+
- If on a feature branch: verify it matches the current task. Ask if unsure.
|
|
35
|
+
2. Sync with parent: `git fetch origin && git log HEAD..origin/main --oneline`
|
|
36
|
+
- If upstream has new commits: `git rebase origin/main` before starting.
|
|
37
|
+
- Resolve any conflicts before proceeding with the task.
|
|
38
|
+
|
|
39
|
+
## Autonomous mode (sandbox / no-prompt mode)
|
|
40
|
+
|
|
41
|
+
If running without interactive approval, auto-decide:
|
|
42
|
+
- On main? Auto-create feature/<task-slug> branch
|
|
43
|
+
- Parent has new commits? Auto-rebase before starting
|
|
44
|
+
- Merge? Auto-merge in autonomous mode, delete feature branches after
|
|
45
|
+
- Commit message? Auto-generate (meaningful, no AI attribution)
|
|
46
|
+
- Tests failing? Fix them. Convention violations? Fix them.
|
|
47
|
+
Quality bar stays the same - just no blocking on questions.
|
|
48
|
+
|
|
49
|
+
## Mandatory workflow (never skip)
|
|
50
|
+
|
|
51
|
+
1. TESTS: Server tests in test/<feature>/ (node:test), browser tests in
|
|
52
|
+
test/<feature>/browser/ (WTR + Playwright, real Chromium). Run `webjs test`
|
|
53
|
+
after every change. Never deliver code without passing tests.
|
|
54
|
+
|
|
55
|
+
2. DOCS: Update AGENTS.md for API changes. Update docs/ and website/ if
|
|
56
|
+
they exist. The user should never have to ask for tests or docs.
|
|
57
|
+
|
|
58
|
+
3. CONVENTIONS: Run `webjs check` and fix violations before committing.
|
|
59
|
+
|
|
60
|
+
## Git rules
|
|
61
|
+
|
|
62
|
+
- COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
|
|
63
|
+
one rename, one doc rewrite per commit. Always `git push` after
|
|
64
|
+
committing. This is automatic.
|
|
65
|
+
- HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
|
|
66
|
+
commit before continuing. The Claude Code hook at
|
|
67
|
+
`.claude/hooks/nudge-uncommitted.sh` fires at threshold 4. Cursor users
|
|
68
|
+
should self-enforce the same rule. Batching multiple logical units into
|
|
69
|
+
one commit is the failure mode this rule exists to prevent.
|
|
70
|
+
- Write meaningful commit messages: what changed and why, not "update files"
|
|
71
|
+
- NEVER add "Co-Authored-By", "Generated by", "AI-assisted" or similar
|
|
72
|
+
attribution trailers to commits
|
|
73
|
+
- NEVER use em-dashes (U+2014), a hyphen-as-pause (` - `), or a
|
|
74
|
+
semicolon-as-pause (` ; `) in commit messages or anywhere else.
|
|
75
|
+
Rewrite the sentence so no pause-punctuation crutch is needed.
|
|
76
|
+
Use a period, comma, colon, parentheses, or a restructured phrasing.
|
|
77
|
+
Plain hyphens stay fine in compound words, CLI flags, filenames,
|
|
78
|
+
and ranges. Semicolons stay fine inside code
|
|
79
|
+
- Work on feature branches, not main
|
|
80
|
+
- NEVER push directly to main - create a pull request
|
|
81
|
+
- NEVER merge any branch without explicit user permission. Always ask:
|
|
82
|
+
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
83
|
+
Wait for approval AND the delete/keep preference before proceeding.
|
|
84
|
+
This applies to ALL merges, not just merges into main.
|
|
85
|
+
- Run tests before every commit
|
|
86
|
+
- Keep commits small and focused
|
|
87
|
+
|
|
88
|
+
## Framework rules
|
|
89
|
+
|
|
90
|
+
- No build step: source files are served as ES modules
|
|
91
|
+
- **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server falls back to esbuild + inline sourcemap for those files (~3x wire bytes per request).
|
|
92
|
+
- Web components with shadow DOM: use `static styles = css` not inline styles
|
|
93
|
+
- One function per server action file (*.server.ts)
|
|
94
|
+
- Components must call customElements.define('tag', Class)
|
|
95
|
+
- Server-only code (@prisma/client, node:*, anything that needs Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap the access in a .server.{js,ts} file; the framework rewrites that import into an RPC stub for the browser. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); follow the same rule per file: if a lib/ file needs Node APIs, only import it from server-only files.
|
|
96
|
+
- Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat` ship. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported - use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
|
|
97
|
+
- **Progressive enhancement is the default.** Pages AND every web component are SSR'd to real HTML. Write components so the first paint is the right content (read SSR-meaningful defaults in `constructor()`, not `connectedCallback` - the server doesn't call lifecycle hooks). Initial data for components comes from the page function (server-side fetch + pass as attribute/property), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` + server action over `fetch` + click handler - the framework upgrades plain forms to partial-swap submissions automatically.
|
|
98
|
+
- **Client navigation is auto-magic.** Real `<a href>` and `<form action>` get partial-swap behavior with no opt-in. Layouts persist across navigation - put shared chrome (sidenav, header) in `layout.ts`, page-specific content in `page.ts`. For validation errors, return 4xx HTML from a `route.ts` POST handler; the router renders it in place preserving the user's input. For non-layout swap regions, wrap in `<webjs-frame id="...">`. See "Client navigation patterns" in AGENTS.md.
|
|
99
|
+
- See AGENTS.md for the complete directive decision guide
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# EditorConfig - consistent formatting across editors and AI agents
|
|
2
|
+
# https://editorconfig.org
|
|
3
|
+
|
|
4
|
+
root = true
|
|
5
|
+
|
|
6
|
+
[*]
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 2
|
|
9
|
+
end_of_line = lf
|
|
10
|
+
charset = utf-8
|
|
11
|
+
trim_trailing_whitespace = true
|
|
12
|
+
insert_final_newline = true
|
|
13
|
+
|
|
14
|
+
[*.md]
|
|
15
|
+
trim_trailing_whitespace = false
|
|
16
|
+
|
|
17
|
+
[Makefile]
|
|
18
|
+
indent_style = tab
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# webjs environment variables
|
|
2
|
+
# Copy to .env and fill in your values: cp .env.example .env
|
|
3
|
+
#
|
|
4
|
+
# Opinionated defaults: set these and the framework auto-configures.
|
|
5
|
+
|
|
6
|
+
# ── Server ──────────────────────────────────────────────────────────
|
|
7
|
+
PORT=3000
|
|
8
|
+
|
|
9
|
+
# ── Auth (required for authentication) ──────────────────────────────
|
|
10
|
+
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
11
|
+
AUTH_SECRET=
|
|
12
|
+
|
|
13
|
+
# ── OAuth providers (optional - enable the ones you need) ───────────
|
|
14
|
+
# AUTH_GOOGLE_ID=
|
|
15
|
+
# AUTH_GOOGLE_SECRET=
|
|
16
|
+
# AUTH_GITHUB_ID=
|
|
17
|
+
# AUTH_GITHUB_SECRET=
|
|
18
|
+
|
|
19
|
+
# ── Redis (production scaling) ──────────────────────────────────────
|
|
20
|
+
# Set this and cache, sessions, rate limiting, and pub/sub all
|
|
21
|
+
# automatically use Redis. Without it, everything uses in-memory
|
|
22
|
+
# defaults (great for development).
|
|
23
|
+
# REDIS_URL=redis://localhost:6379
|
|
24
|
+
|
|
25
|
+
# ── Database ────────────────────────────────────────────────────────
|
|
26
|
+
# Used by Prisma. SQLite for dev, PostgreSQL/MySQL for production.
|
|
27
|
+
DATABASE_URL=file:./dev.db
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Gemini CLI AfterTool hook.
|
|
4
|
+
#
|
|
5
|
+
# Counterpart of .claude/hooks/nudge-uncommitted.sh. After each
|
|
6
|
+
# write_file or replace, counts uncommitted changes in the working
|
|
7
|
+
# tree. When the count crosses a threshold (default 4, override
|
|
8
|
+
# with the WEBJS_COMMIT_NUDGE_THRESHOLD env var), injects a
|
|
9
|
+
# reminder via hookSpecificOutput.additionalContext (same shape
|
|
10
|
+
# as Claude Code).
|
|
11
|
+
#
|
|
12
|
+
# Soft nudge. Does NOT block the edit (exit 0). Skipped on
|
|
13
|
+
# main/master and outside a git work tree.
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
THRESHOLD="${WEBJS_COMMIT_NUDGE_THRESHOLD:-4}"
|
|
18
|
+
|
|
19
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
|
24
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
cat /dev/stdin >/dev/null 2>&1 || true
|
|
29
|
+
|
|
30
|
+
CHANGED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
31
|
+
|
|
32
|
+
if [ -z "$CHANGED" ] || [ "$CHANGED" -lt "$THRESHOLD" ]; then
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
REASON="You have ${CHANGED} uncommitted changes on '${BRANCH}'. The webjs convention is small, focused commits per logical unit (one feature, one fix, one rename, one doc rewrite). Before continuing with more edits, group the current changes into a meaningful commit. See AGENTS.md \"Git workflow\" for the rule and the rationale. To raise the threshold for this hook in long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD."
|
|
37
|
+
|
|
38
|
+
jq -n --arg ctx "$REASON" '{
|
|
39
|
+
hookSpecificOutput: {
|
|
40
|
+
additionalContext: $ctx
|
|
41
|
+
}
|
|
42
|
+
}'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# GitHub Copilot Instructions: webjs app
|
|
2
|
+
|
|
3
|
+
You are working on a webjs app, an AI-first, no-build, web-components-first
|
|
4
|
+
framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
|
|
5
|
+
project-specific conventions. When AGENTS.md doesn't cover what you need,
|
|
6
|
+
the full hosted docs are at **https://docs.webjs.com**.
|
|
7
|
+
|
|
8
|
+
## Persistence + scaffold rules (non-negotiable)
|
|
9
|
+
|
|
10
|
+
- **Use Prisma + SQLite for data, never JSON files.** It's already wired up
|
|
11
|
+
(`prisma/schema.prisma`, `lib/prisma.server.ts`, `npm run db:migrate`). For ANY
|
|
12
|
+
data the app stores (todos, posts, messages, products, comments…),
|
|
13
|
+
define a Prisma model. NEVER create `data/*.json`, `db.json`, or any
|
|
14
|
+
JSON file as a fake database. NEVER use module-scope arrays / Maps as
|
|
15
|
+
a substitute. NEVER use localStorage for app data. `webjs check`'s
|
|
16
|
+
`no-json-data-files` rule will fail the build if you do.
|
|
17
|
+
- **The scaffold is reference, not the final product.** Replace
|
|
18
|
+
`app/page.ts`, the example `User` model, the example users module, etc.
|
|
19
|
+
with the app the user actually asked for. Don't ship "Hello from
|
|
20
|
+
<app-name>" as the deliverable.
|
|
21
|
+
- **Only three templates exist:** `webjs create <name>` (default
|
|
22
|
+
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
23
|
+
other `--template` value. Pick:
|
|
24
|
+
- Any product UI (todo, blog, dashboard, marketplace, social…) → default
|
|
25
|
+
- HTTP/JSON API only, no UI → `--template api`
|
|
26
|
+
- Auth / login / signup / SaaS → `--template saas`
|
|
27
|
+
|
|
28
|
+
## Before starting ANY work
|
|
29
|
+
|
|
30
|
+
FIRST, before writing any code:
|
|
31
|
+
1. Check `git branch --show-current`.
|
|
32
|
+
- If on main/master: create a feature branch before editing.
|
|
33
|
+
- If on a feature branch: verify it matches the task at hand.
|
|
34
|
+
2. Sync: `git fetch origin && git rebase origin/main` if behind.
|
|
35
|
+
|
|
36
|
+
## Autonomous mode
|
|
37
|
+
|
|
38
|
+
If running without interactive approval (sandbox, auto-approve, etc.):
|
|
39
|
+
- On main? Auto-create feature/<task-slug> branch
|
|
40
|
+
- Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
|
|
41
|
+
- Auto-generate meaningful commit messages. Fix tests and violations.
|
|
42
|
+
|
|
43
|
+
## Mandatory workflow
|
|
44
|
+
|
|
45
|
+
Every code change must include:
|
|
46
|
+
1. Commit and push PER LOGICAL UNIT, not at the end. One feature, one fix,
|
|
47
|
+
one rename, one doc rewrite per commit. Always `git push` after
|
|
48
|
+
committing. Don't accumulate changes. If you have 5+ unstaged files
|
|
49
|
+
spanning different concerns, commit before continuing. The Claude Code
|
|
50
|
+
hook at `.claude/hooks/nudge-uncommitted.sh` enforces threshold 4 for
|
|
51
|
+
Claude users; Copilot users should self-enforce the same rule. Automatic.
|
|
52
|
+
2. Server tests in test/<feature>/*.test.ts (node:test for actions, queries, utilities)
|
|
53
|
+
3. Browser tests in test/<feature>/browser/*.test.js (WTR + Playwright, real Chromium)
|
|
54
|
+
4. Documentation updates (AGENTS.md for API, docs/ for user guides)
|
|
55
|
+
5. Convention validation: `webjs check` must pass
|
|
56
|
+
|
|
57
|
+
## Git rules
|
|
58
|
+
|
|
59
|
+
- Commit after each logical unit of work
|
|
60
|
+
- Meaningful commit messages: what changed and why
|
|
61
|
+
- NEVER add Co-Authored-By or AI attribution trailers to commits
|
|
62
|
+
- Work on feature branches, create PRs, never push directly to main
|
|
63
|
+
- NEVER merge any branch without explicit user permission. Always ask:
|
|
64
|
+
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
65
|
+
Wait for approval AND the delete/keep preference. Applies to ALL merges.
|
|
66
|
+
- Run `webjs test` before every commit
|
|
67
|
+
|
|
68
|
+
## Code patterns
|
|
69
|
+
|
|
70
|
+
- **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server falls back to esbuild + inline sourcemap for those files (~3x wire bytes per request).
|
|
71
|
+
- Tagged template: html`<div>${value}</div>` with css`...` for styles
|
|
72
|
+
- Components: extend WebComponent, declare `static properties` (and `static styles` for shadow-DOM components), call `Class.register('tag-name')` at the bottom of the file. The tag name is the argument to `.register()`, not a static field.
|
|
73
|
+
- Server actions: *.server.ts files with one exported async function each
|
|
74
|
+
- Directives: webjs ships only `unsafeHTML`, `live`, and `repeat`. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported - use plain template-literal expressions and lifecycle hooks instead.
|
|
75
|
+
- Context: import { createContext, ContextProvider, ContextConsumer } from '@webjsdev/core/context'
|
|
76
|
+
- Task: import { Task, TaskStatus } from '@webjsdev/core/task'
|
|
77
|
+
- Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts)
|
|
78
|
+
|
|
79
|
+
## What NOT to do
|
|
80
|
+
|
|
81
|
+
- Don't introduce build tools or bundlers in the critical path
|
|
82
|
+
- Server-only code (@prisma/client, node:*, anything needing Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap in a .server.{js,ts} file; the framework rewrites that import to an RPC stub for the browser. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file.
|
|
83
|
+
- Don't use inline style="..." on components (use static styles = css`...`)
|
|
84
|
+
- Component state lives in signals from @webjsdev/core. Module-scope signals share state across components; instance signals (created in the constructor) carry component-local state. Reactive properties (static properties + declare) are for HTML attributes and .prop=${...} hydration.
|
|
85
|
+
- Don't skip tests or documentation updates
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What does this PR do? 1-3 bullet points. -->
|
|
4
|
+
|
|
5
|
+
## Test plan
|
|
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
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
- [ ] AGENTS.md updated (if API surface changed)
|
|
14
|
+
- [ ] Docs updated (if docs/ exists and feature is documented)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# pre-commit hook - blocks commits on main/master.
|
|
4
|
+
#
|
|
5
|
+
# No AI agent, no editor, no human can commit to main directly.
|
|
6
|
+
# Create a feature branch first. This is git-level enforcement.
|
|
7
|
+
#
|
|
8
|
+
# To bypass in emergencies: git commit --no-verify
|
|
9
|
+
|
|
10
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
11
|
+
|
|
12
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
13
|
+
echo ""
|
|
14
|
+
echo "ERROR: Cannot commit directly to '$BRANCH'."
|
|
15
|
+
echo ""
|
|
16
|
+
echo "Create a feature branch first:"
|
|
17
|
+
echo " git checkout -b feature/<name>"
|
|
18
|
+
echo ""
|
|
19
|
+
echo "To bypass (emergencies only): git commit --no-verify"
|
|
20
|
+
echo ""
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# webjs test + webjs check on every commit. Tool-agnostic enforcement:
|
|
25
|
+
# fires regardless of which agent (Claude, Cursor, Windsurf, 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
|
+
exit 0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode commit-frequency nudge plugin.
|
|
3
|
+
*
|
|
4
|
+
* Counterpart of the Claude Code, Gemini CLI, and Cursor hooks in
|
|
5
|
+
* `.claude/hooks/`, `.gemini/hooks/`, and `.cursor/hooks/`. After
|
|
6
|
+
* each edit/write tool call, counts uncommitted changes in the
|
|
7
|
+
* working tree. When the count crosses a threshold (default 4,
|
|
8
|
+
* override with the WEBJS_COMMIT_NUDGE_THRESHOLD env var), appends
|
|
9
|
+
* a reminder to the tool result so the agent sees it on the next
|
|
10
|
+
* turn.
|
|
11
|
+
*
|
|
12
|
+
* Soft nudge by design. Does NOT block the edit. The goal is to
|
|
13
|
+
* keep the agent honest about the "commit per logical unit" rule,
|
|
14
|
+
* not to interrupt valid work.
|
|
15
|
+
*
|
|
16
|
+
* Skipped on main/master (different guard rules cover that) and
|
|
17
|
+
* outside a git work tree.
|
|
18
|
+
*
|
|
19
|
+
* Auto-discovered by OpenCode at startup. No opencode.json entry
|
|
20
|
+
* needed. Lives in .opencode/plugins/ at the project root.
|
|
21
|
+
*
|
|
22
|
+
* Docs: https://opencode.ai/docs/plugins/
|
|
23
|
+
*/
|
|
24
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
25
|
+
|
|
26
|
+
export const NudgeUncommitted: Plugin = async ({ $ }) => {
|
|
27
|
+
const THRESHOLD = Number(process.env.WEBJS_COMMIT_NUDGE_THRESHOLD ?? 4);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
"tool.execute.after": async (input, output) => {
|
|
31
|
+
if (input.tool !== "edit" && input.tool !== "write") return;
|
|
32
|
+
|
|
33
|
+
let branch = "";
|
|
34
|
+
try {
|
|
35
|
+
branch = (await $`git symbolic-ref --short HEAD`.text()).trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return; // not in a git work tree
|
|
38
|
+
}
|
|
39
|
+
if (branch === "main" || branch === "master") return;
|
|
40
|
+
|
|
41
|
+
let changed = 0;
|
|
42
|
+
try {
|
|
43
|
+
const out = (await $`git status --porcelain`.text()).trim();
|
|
44
|
+
changed = out === "" ? 0 : out.split("\n").length;
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (changed < THRESHOLD) return;
|
|
49
|
+
|
|
50
|
+
const reason =
|
|
51
|
+
`[webjs] You have ${changed} uncommitted changes on '${branch}'. ` +
|
|
52
|
+
`The webjs convention is small, focused commits per logical unit ` +
|
|
53
|
+
`(one feature, one fix, one rename, one doc rewrite). Before ` +
|
|
54
|
+
`continuing with more edits, group the current changes into a ` +
|
|
55
|
+
`meaningful commit. See AGENTS.md "Git workflow" for the rule ` +
|
|
56
|
+
`and the rationale. To raise the threshold for this hook in ` +
|
|
57
|
+
`long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD.`;
|
|
58
|
+
|
|
59
|
+
output.output = output.output ? `${output.output}\n\n${reason}` : reason;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|