@webjskit/cli 0.1.0
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 +55 -0
- package/bin/webjs.js +226 -0
- package/lib/create.js +519 -0
- package/lib/saas-template.js +238 -0
- package/package.json +35 -0
- package/templates/.claude/hooks/guard-branch-context.sh +39 -0
- package/templates/.claude/hooks/guard-main-merge.sh +44 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.claude.json +9 -0
- package/templates/.cursorrules +63 -0
- package/templates/.editorconfig +18 -0
- package/templates/.env.example +27 -0
- package/templates/.github/copilot-instructions.md +59 -0
- package/templates/.github/pull_request_template.md +14 -0
- package/templates/.hooks/pre-commit +24 -0
- package/templates/.windsurfrules +53 -0
- package/templates/CLAUDE.md +70 -0
- package/templates/CONVENTIONS.md +589 -0
- package/templates/app/_utils/ui.ts +83 -0
- package/templates/public/tailwind-browser.js +947 -0
- package/templates/test/browser/example.test.js +40 -0
- package/templates/test/e2e/example.test.ts +87 -0
- package/templates/test/unit/example.test.ts +24 -0
- package/templates/web-test-runner.config.js +26 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SaaS template files for `webjs create --template saas`.
|
|
3
|
+
* Extracted to avoid nested template literal escaping issues.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} appDir
|
|
11
|
+
*/
|
|
12
|
+
export async function writeSaasFiles(appDir) {
|
|
13
|
+
// lib/prisma.ts
|
|
14
|
+
await mkdir(join(appDir, 'lib'), { recursive: true });
|
|
15
|
+
await writeFile(join(appDir, 'lib', 'prisma.ts'), [
|
|
16
|
+
"import { PrismaClient } from '@prisma/client';",
|
|
17
|
+
"",
|
|
18
|
+
"const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };",
|
|
19
|
+
"export const prisma = globalForPrisma.prisma || new PrismaClient();",
|
|
20
|
+
"if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;",
|
|
21
|
+
"",
|
|
22
|
+
].join('\n'));
|
|
23
|
+
|
|
24
|
+
// lib/password.ts
|
|
25
|
+
await writeFile(join(appDir, 'lib', 'password.ts'), [
|
|
26
|
+
"import { scrypt, randomBytes, timingSafeEqual } from 'node:crypto';",
|
|
27
|
+
"import { promisify } from 'node:util';",
|
|
28
|
+
"",
|
|
29
|
+
"const scryptAsync = promisify(scrypt);",
|
|
30
|
+
"",
|
|
31
|
+
"export async function hash(password: string): Promise<string> {",
|
|
32
|
+
" const salt = randomBytes(16).toString('hex');",
|
|
33
|
+
" const buf = (await scryptAsync(password, salt, 64)) as Buffer;",
|
|
34
|
+
" return salt + ':' + buf.toString('hex');",
|
|
35
|
+
"}",
|
|
36
|
+
"",
|
|
37
|
+
"export async function compare(password: string, stored: string): Promise<boolean> {",
|
|
38
|
+
" const [salt, key] = stored.split(':');",
|
|
39
|
+
" const buf = (await scryptAsync(password, salt, 64)) as Buffer;",
|
|
40
|
+
" return timingSafeEqual(buf, Buffer.from(key, 'hex'));",
|
|
41
|
+
"}",
|
|
42
|
+
"",
|
|
43
|
+
].join('\n'));
|
|
44
|
+
|
|
45
|
+
// lib/auth.ts
|
|
46
|
+
await writeFile(join(appDir, 'lib', 'auth.ts'), [
|
|
47
|
+
"import { createAuth, Credentials } from '@webjskit/server';",
|
|
48
|
+
"import { prisma } from './prisma.ts';",
|
|
49
|
+
"import { compare } from './password.ts';",
|
|
50
|
+
"",
|
|
51
|
+
"export const { auth, signIn, signOut, handlers } = createAuth({",
|
|
52
|
+
" providers: [",
|
|
53
|
+
" Credentials({",
|
|
54
|
+
" async authorize(credentials: { email: string; password: string }) {",
|
|
55
|
+
" const user = await prisma.user.findUnique({ where: { email: credentials.email } });",
|
|
56
|
+
" if (!user || !await compare(credentials.password, user.passwordHash)) return null;",
|
|
57
|
+
" return { id: String(user.id), name: user.name, email: user.email };",
|
|
58
|
+
" },",
|
|
59
|
+
" }),",
|
|
60
|
+
" ],",
|
|
61
|
+
" secret: process.env.AUTH_SECRET,",
|
|
62
|
+
"});",
|
|
63
|
+
"",
|
|
64
|
+
].join('\n'));
|
|
65
|
+
|
|
66
|
+
// prisma/schema.prisma
|
|
67
|
+
await mkdir(join(appDir, 'prisma'), { recursive: true });
|
|
68
|
+
await writeFile(join(appDir, 'prisma', 'schema.prisma'), [
|
|
69
|
+
'datasource db {',
|
|
70
|
+
' provider = "sqlite"',
|
|
71
|
+
' url = env("DATABASE_URL")',
|
|
72
|
+
'}',
|
|
73
|
+
'',
|
|
74
|
+
'generator client {',
|
|
75
|
+
' provider = "prisma-client-js"',
|
|
76
|
+
'}',
|
|
77
|
+
'',
|
|
78
|
+
'model User {',
|
|
79
|
+
' id Int @id @default(autoincrement())',
|
|
80
|
+
' email String @unique',
|
|
81
|
+
' name String?',
|
|
82
|
+
' passwordHash String',
|
|
83
|
+
' createdAt DateTime @default(now())',
|
|
84
|
+
'}',
|
|
85
|
+
'',
|
|
86
|
+
].join('\n'));
|
|
87
|
+
|
|
88
|
+
// modules/auth/actions/signup.server.ts
|
|
89
|
+
await mkdir(join(appDir, 'modules', 'auth', 'actions'), { recursive: true });
|
|
90
|
+
await mkdir(join(appDir, 'modules', 'auth', 'queries'), { recursive: true });
|
|
91
|
+
|
|
92
|
+
await writeFile(join(appDir, 'modules', 'auth', 'actions', 'signup.server.ts'), [
|
|
93
|
+
"'use server';",
|
|
94
|
+
"",
|
|
95
|
+
"import { prisma } from '../../../lib/prisma.ts';",
|
|
96
|
+
"import { hash } from '../../../lib/password.ts';",
|
|
97
|
+
"",
|
|
98
|
+
"export async function signup(input: { name: string; email: string; password: string }) {",
|
|
99
|
+
" const exists = await prisma.user.findUnique({ where: { email: input.email } });",
|
|
100
|
+
" if (exists) return { success: false as const, error: 'Email already registered', status: 409 };",
|
|
101
|
+
" const user = await prisma.user.create({",
|
|
102
|
+
" data: { name: input.name, email: input.email, passwordHash: await hash(input.password) },",
|
|
103
|
+
" });",
|
|
104
|
+
" return { success: true as const, data: { id: user.id, name: user.name, email: user.email } };",
|
|
105
|
+
"}",
|
|
106
|
+
"",
|
|
107
|
+
].join('\n'));
|
|
108
|
+
|
|
109
|
+
// modules/auth/queries/current-user.server.ts
|
|
110
|
+
await writeFile(join(appDir, 'modules', 'auth', 'queries', 'current-user.server.ts'), [
|
|
111
|
+
"'use server';",
|
|
112
|
+
"",
|
|
113
|
+
"import { auth } from '../../../lib/auth.ts';",
|
|
114
|
+
"",
|
|
115
|
+
"export async function currentUser() {",
|
|
116
|
+
" const session = await auth();",
|
|
117
|
+
" return session?.user ?? null;",
|
|
118
|
+
"}",
|
|
119
|
+
"",
|
|
120
|
+
].join('\n'));
|
|
121
|
+
|
|
122
|
+
// modules/auth/types.ts
|
|
123
|
+
await writeFile(join(appDir, 'modules', 'auth', 'types.ts'), [
|
|
124
|
+
"export interface User {",
|
|
125
|
+
" id: number;",
|
|
126
|
+
" name: string | null;",
|
|
127
|
+
" email: string;",
|
|
128
|
+
"}",
|
|
129
|
+
"",
|
|
130
|
+
"export type ActionResult<T> =",
|
|
131
|
+
" | { success: true; data: T }",
|
|
132
|
+
" | { success: false; error: string; status: number };",
|
|
133
|
+
"",
|
|
134
|
+
].join('\n'));
|
|
135
|
+
|
|
136
|
+
// app/api/auth/[...path]/route.ts
|
|
137
|
+
await mkdir(join(appDir, 'app', 'api', 'auth', '[...path]'), { recursive: true });
|
|
138
|
+
await writeFile(join(appDir, 'app', 'api', 'auth', '[...path]', 'route.ts'), [
|
|
139
|
+
"import { handlers } from '../../../../../lib/auth.ts';",
|
|
140
|
+
"export const GET = handlers.GET;",
|
|
141
|
+
"export const POST = handlers.POST;",
|
|
142
|
+
"",
|
|
143
|
+
].join('\n'));
|
|
144
|
+
|
|
145
|
+
// app/login/page.ts
|
|
146
|
+
await mkdir(join(appDir, 'app', 'login'), { recursive: true });
|
|
147
|
+
await writeFile(join(appDir, 'app', 'login', 'page.ts'), [
|
|
148
|
+
"import { html } from '@webjskit/core';",
|
|
149
|
+
"",
|
|
150
|
+
"export const metadata = { title: 'Login' };",
|
|
151
|
+
"",
|
|
152
|
+
"export default function LoginPage() {",
|
|
153
|
+
" return html`",
|
|
154
|
+
" <h1>Login</h1>",
|
|
155
|
+
" <form method=\"POST\" action=\"/api/auth/callback/credentials\">",
|
|
156
|
+
" <label>Email <input type=\"email\" name=\"email\" required></label>",
|
|
157
|
+
" <label>Password <input type=\"password\" name=\"password\" required></label>",
|
|
158
|
+
" <button type=\"submit\">Sign in</button>",
|
|
159
|
+
" </form>",
|
|
160
|
+
" <p>Don't have an account? <a href=\"/signup\">Sign up</a></p>",
|
|
161
|
+
" `;",
|
|
162
|
+
"}",
|
|
163
|
+
"",
|
|
164
|
+
].join('\n'));
|
|
165
|
+
|
|
166
|
+
// app/signup/page.ts
|
|
167
|
+
await mkdir(join(appDir, 'app', 'signup'), { recursive: true });
|
|
168
|
+
await writeFile(join(appDir, 'app', 'signup', 'page.ts'), [
|
|
169
|
+
"import { html } from '@webjskit/core';",
|
|
170
|
+
"",
|
|
171
|
+
"export const metadata = { title: 'Sign up' };",
|
|
172
|
+
"",
|
|
173
|
+
"export default function SignupPage() {",
|
|
174
|
+
" return html`",
|
|
175
|
+
" <h1>Sign up</h1>",
|
|
176
|
+
" <form id=\"signup-form\">",
|
|
177
|
+
" <label>Name <input type=\"text\" name=\"name\" required></label>",
|
|
178
|
+
" <label>Email <input type=\"email\" name=\"email\" required></label>",
|
|
179
|
+
" <label>Password <input type=\"password\" name=\"password\" required minlength=\"8\"></label>",
|
|
180
|
+
" <button type=\"submit\">Create account</button>",
|
|
181
|
+
" </form>",
|
|
182
|
+
" <p>Already have an account? <a href=\"/login\">Log in</a></p>",
|
|
183
|
+
" `;",
|
|
184
|
+
"}",
|
|
185
|
+
"",
|
|
186
|
+
].join('\n'));
|
|
187
|
+
|
|
188
|
+
// app/dashboard/middleware.ts
|
|
189
|
+
await mkdir(join(appDir, 'app', 'dashboard', 'settings'), { recursive: true });
|
|
190
|
+
await writeFile(join(appDir, 'app', 'dashboard', 'middleware.ts'), [
|
|
191
|
+
"import { auth } from '../../lib/auth.ts';",
|
|
192
|
+
"",
|
|
193
|
+
"export default async function requireAuth(req: Request, next: () => Promise<Response>) {",
|
|
194
|
+
" const session = await auth();",
|
|
195
|
+
" if (!session?.user) {",
|
|
196
|
+
" return new Response(null, { status: 302, headers: { location: '/login' } });",
|
|
197
|
+
" }",
|
|
198
|
+
" return next();",
|
|
199
|
+
"}",
|
|
200
|
+
"",
|
|
201
|
+
].join('\n'));
|
|
202
|
+
|
|
203
|
+
// app/dashboard/page.ts
|
|
204
|
+
await writeFile(join(appDir, 'app', 'dashboard', 'page.ts'), [
|
|
205
|
+
"import { html } from '@webjskit/core';",
|
|
206
|
+
"import { currentUser } from '../../modules/auth/queries/current-user.server.ts';",
|
|
207
|
+
"",
|
|
208
|
+
"export const metadata = { title: 'Dashboard' };",
|
|
209
|
+
"",
|
|
210
|
+
"export default async function Dashboard() {",
|
|
211
|
+
" const user = await currentUser();",
|
|
212
|
+
" return html`",
|
|
213
|
+
" <h1>Dashboard</h1>",
|
|
214
|
+
" <p>Welcome, ${`\\$\\{user?.name || user?.email\\}`}!</p>",
|
|
215
|
+
" <a href=\"/dashboard/settings\">Settings</a>",
|
|
216
|
+
" `;",
|
|
217
|
+
"}",
|
|
218
|
+
"",
|
|
219
|
+
].join('\n'));
|
|
220
|
+
|
|
221
|
+
// app/dashboard/settings/page.ts
|
|
222
|
+
await writeFile(join(appDir, 'app', 'dashboard', 'settings', 'page.ts'), [
|
|
223
|
+
"import { html } from '@webjskit/core';",
|
|
224
|
+
"import { currentUser } from '../../../modules/auth/queries/current-user.server.ts';",
|
|
225
|
+
"",
|
|
226
|
+
"export const metadata = { title: 'Settings' };",
|
|
227
|
+
"",
|
|
228
|
+
"export default async function Settings() {",
|
|
229
|
+
" const user = await currentUser();",
|
|
230
|
+
" return html`",
|
|
231
|
+
" <h1>Settings</h1>",
|
|
232
|
+
" <p>Email: ${`\\$\\{user?.email\\}`}</p>",
|
|
233
|
+
" <p>Name: ${`\\$\\{user?.name || 'Not set'\\}`}</p>",
|
|
234
|
+
" `;",
|
|
235
|
+
"}",
|
|
236
|
+
"",
|
|
237
|
+
].join('\n'));
|
|
238
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webjskit/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "webjs CLI — dev, start, create, db",
|
|
6
|
+
"bin": {
|
|
7
|
+
"webjs": "bin/webjs.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@webjskit/server": "0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/vivek7405/webjs.git",
|
|
24
|
+
"directory": "packages/cli"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/vivek7405/webjs#readme",
|
|
27
|
+
"bugs": "https://github.com/vivek7405/webjs/issues",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"keywords": [
|
|
30
|
+
"webjs",
|
|
31
|
+
"cli",
|
|
32
|
+
"dev",
|
|
33
|
+
"scaffold"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -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,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# guard-main-merge.sh — Claude Code PreToolUse hook
|
|
4
|
+
#
|
|
5
|
+
# Rules:
|
|
6
|
+
# - git merge → ask (merging to parent branch needs approval)
|
|
7
|
+
# - git push on a feature branch → allow (free to push)
|
|
8
|
+
# - git push targeting main → ask
|
|
9
|
+
# - Bypass mode → allow everything
|
|
10
|
+
|
|
11
|
+
COMMAND=$(jq -r '.tool_input.command // empty' < /dev/stdin)
|
|
12
|
+
[ -z "$COMMAND" ] && exit 0
|
|
13
|
+
|
|
14
|
+
# Bypass mode — full autonomy
|
|
15
|
+
SETTINGS="$HOME/.claude/settings.json"
|
|
16
|
+
if [ -f "$SETTINGS" ]; then
|
|
17
|
+
BYPASS=$(jq -r '.skipDangerousModePermissionPrompt // false' "$SETTINGS" 2>/dev/null)
|
|
18
|
+
if [ "$BYPASS" = "true" ]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
NORMALIZED=$(printf '%s' "$COMMAND" | tr -s '[:space:]' ' ')
|
|
24
|
+
|
|
25
|
+
ask_with_reason() {
|
|
26
|
+
jq -n --arg reason "$1" '{
|
|
27
|
+
hookSpecificOutput: {
|
|
28
|
+
hookEventName: "PreToolUse",
|
|
29
|
+
permissionDecision: "ask",
|
|
30
|
+
permissionDecisionReason: $reason
|
|
31
|
+
}
|
|
32
|
+
}'
|
|
33
|
+
exit 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if [[ "$NORMALIZED" == *"git merge"* ]]; then
|
|
37
|
+
ask_with_reason "This command contains 'git merge'. Merging requires approval. After merging, should the source branch be deleted or kept? Approve to proceed."
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [[ "$NORMALIZED" == *"git push"* ]] && [[ "$NORMALIZED" == *"main"* ]]; then
|
|
41
|
+
ask_with_reason "This looks like 'git push' targeting main. Approve to proceed."
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Bash",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": ".claude/hooks/guard-main-merge.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
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Before starting ANY work
|
|
8
|
+
|
|
9
|
+
FIRST, before writing any code:
|
|
10
|
+
1. Run `git branch --show-current` to check the branch.
|
|
11
|
+
- If on main/master: STOP. Ask the user which branch to use, or create
|
|
12
|
+
one with `git checkout -b feature/<name>`.
|
|
13
|
+
- If on a feature branch: verify it matches the current task. Ask if unsure.
|
|
14
|
+
2. Sync with parent: `git fetch origin && git log HEAD..origin/main --oneline`
|
|
15
|
+
- If upstream has new commits: `git rebase origin/main` before starting.
|
|
16
|
+
- Resolve any conflicts before proceeding with the task.
|
|
17
|
+
|
|
18
|
+
## Autonomous mode (sandbox / no-prompt mode)
|
|
19
|
+
|
|
20
|
+
If running without interactive approval, auto-decide:
|
|
21
|
+
- On main? Auto-create feature/<task-slug> branch
|
|
22
|
+
- Parent has new commits? Auto-rebase before starting
|
|
23
|
+
- Merge? Auto-merge in autonomous mode, delete feature branches after
|
|
24
|
+
- Commit message? Auto-generate (meaningful, no AI attribution)
|
|
25
|
+
- Tests failing? Fix them. Convention violations? Fix them.
|
|
26
|
+
Quality bar stays the same — just no blocking on questions.
|
|
27
|
+
|
|
28
|
+
## Mandatory workflow (never skip)
|
|
29
|
+
|
|
30
|
+
1. TESTS: Server tests in test/unit/ (node:test), browser tests in
|
|
31
|
+
test/browser/ (WTR + Playwright, real Chromium). Run `npx webjs test`
|
|
32
|
+
after every change. Never deliver code without passing tests.
|
|
33
|
+
|
|
34
|
+
2. DOCS: Update AGENTS.md for API changes. Update docs/ and website/ if
|
|
35
|
+
they exist. The user should never have to ask for tests or docs.
|
|
36
|
+
|
|
37
|
+
3. CONVENTIONS: Run `npx webjs check` and fix violations before committing.
|
|
38
|
+
|
|
39
|
+
## Git rules
|
|
40
|
+
|
|
41
|
+
- COMMIT AND PUSH OFTEN. After each logical unit of work — don't accumulate
|
|
42
|
+
changes. Always `git push` after committing. This is automatic.
|
|
43
|
+
- Write meaningful commit messages: what changed and why, not "update files"
|
|
44
|
+
- NEVER add "Co-Authored-By", "Generated by", "AI-assisted" or similar
|
|
45
|
+
attribution trailers to commits
|
|
46
|
+
- Work on feature branches, not main
|
|
47
|
+
- NEVER push directly to main — create a pull request
|
|
48
|
+
- NEVER merge any branch without explicit user permission. Always ask:
|
|
49
|
+
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
50
|
+
Wait for approval AND the delete/keep preference before proceeding.
|
|
51
|
+
This applies to ALL merges, not just merges into main.
|
|
52
|
+
- Run tests before every commit
|
|
53
|
+
- Keep commits small and focused
|
|
54
|
+
|
|
55
|
+
## Framework rules
|
|
56
|
+
|
|
57
|
+
- No build step: source files are served as ES modules
|
|
58
|
+
- Web components with shadow DOM: use `static styles = css` not inline styles
|
|
59
|
+
- One function per server action file (*.server.ts)
|
|
60
|
+
- Components must call customElements.define('tag', Class)
|
|
61
|
+
- Never import @prisma/client or node:* from client components
|
|
62
|
+
- Use directives (classMap, styleMap, ref, etc.) from '@webjskit/core/directives'
|
|
63
|
+
- 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,59 @@
|
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Before starting ANY work
|
|
8
|
+
|
|
9
|
+
FIRST, before writing any code:
|
|
10
|
+
1. Check `git branch --show-current`.
|
|
11
|
+
- If on main/master: create a feature branch before editing.
|
|
12
|
+
- If on a feature branch: verify it matches the task at hand.
|
|
13
|
+
2. Sync: `git fetch origin && git rebase origin/main` if behind.
|
|
14
|
+
|
|
15
|
+
## Autonomous mode
|
|
16
|
+
|
|
17
|
+
If running without interactive approval (sandbox, auto-approve, etc.):
|
|
18
|
+
- On main? Auto-create feature/<task-slug> branch
|
|
19
|
+
- Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
|
|
20
|
+
- Auto-generate meaningful commit messages. Fix tests and violations.
|
|
21
|
+
|
|
22
|
+
## Mandatory workflow
|
|
23
|
+
|
|
24
|
+
Every code change must include:
|
|
25
|
+
1. Commit and push — COMMIT AND PUSH after each logical unit of work.
|
|
26
|
+
Always `git push` after committing. Don't accumulate changes. Automatic.
|
|
27
|
+
2. Server tests in test/unit/*.test.ts (node:test for actions, queries, utilities)
|
|
28
|
+
3. Browser tests in test/browser/*.test.js (WTR + Playwright, real Chromium)
|
|
29
|
+
4. Documentation updates (AGENTS.md for API, docs/ for user guides)
|
|
30
|
+
5. Convention validation: `npx webjs check` must pass
|
|
31
|
+
|
|
32
|
+
## Git rules
|
|
33
|
+
|
|
34
|
+
- Commit after each logical unit of work
|
|
35
|
+
- Meaningful commit messages: what changed and why
|
|
36
|
+
- NEVER add Co-Authored-By or AI attribution trailers to commits
|
|
37
|
+
- Work on feature branches, create PRs, never push directly to main
|
|
38
|
+
- NEVER merge any branch without explicit user permission. Always ask:
|
|
39
|
+
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
40
|
+
Wait for approval AND the delete/keep preference. Applies to ALL merges.
|
|
41
|
+
- Run `npx webjs test` before every commit
|
|
42
|
+
|
|
43
|
+
## Code patterns
|
|
44
|
+
|
|
45
|
+
- Tagged template: html`<div>${value}</div>` with css`...` for styles
|
|
46
|
+
- Components: extend WebComponent, use static tag/styles/properties, call Class.register('tag')
|
|
47
|
+
- Server actions: *.server.ts files with one exported async function each
|
|
48
|
+
- Directives: import { classMap, styleMap, ref, when, ... } from '@webjskit/core/directives'
|
|
49
|
+
- Context: import { createContext, ContextProvider, ContextConsumer } from '@webjskit/core/context'
|
|
50
|
+
- Task: import { Task, TaskStatus } from '@webjskit/core/task'
|
|
51
|
+
- Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts)
|
|
52
|
+
|
|
53
|
+
## What NOT to do
|
|
54
|
+
|
|
55
|
+
- Don't introduce build tools or bundlers in the critical path
|
|
56
|
+
- Don't import @prisma/client or node:* from client components
|
|
57
|
+
- Don't use inline style="..." on components (use static styles = css`...`)
|
|
58
|
+
- Don't mutate this.state directly (use this.setState())
|
|
59
|
+
- 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 (`npx webjs test` passes)
|
|
8
|
+
- [ ] E2E tests added/updated for user-facing changes (`npx webjs test --e2e` passes)
|
|
9
|
+
- [ ] `npx 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,24 @@
|
|
|
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
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Windsurf 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.
|
|
6
|
+
|
|
7
|
+
## Before starting ANY work
|
|
8
|
+
|
|
9
|
+
FIRST, before writing any code:
|
|
10
|
+
1. Check `git branch --show-current`.
|
|
11
|
+
- If on main/master: create a feature branch before editing.
|
|
12
|
+
- If on a feature branch: verify it matches the current task.
|
|
13
|
+
2. Sync: `git fetch origin && git rebase origin/main` if behind.
|
|
14
|
+
|
|
15
|
+
## Autonomous mode (sandbox / no-prompt)
|
|
16
|
+
|
|
17
|
+
If running without interactive approval, auto-decide:
|
|
18
|
+
- On main? Auto-create feature/<task-slug> branch
|
|
19
|
+
- Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
|
|
20
|
+
- Auto-generate commit messages. Fix failing tests and violations.
|
|
21
|
+
Quality bar stays the same — no blocking on questions.
|
|
22
|
+
|
|
23
|
+
## Mandatory workflow (never skip)
|
|
24
|
+
|
|
25
|
+
Every code change must include:
|
|
26
|
+
1. Server tests in test/unit/*.test.ts (node:test)
|
|
27
|
+
2. Browser tests in test/browser/*.test.js (WTR + Playwright, real Chromium)
|
|
28
|
+
3. Documentation updates (AGENTS.md, docs/, website/ if they exist)
|
|
29
|
+
4. Convention check: `npx webjs check` must pass
|
|
30
|
+
|
|
31
|
+
The user should never have to ask for tests or documentation.
|
|
32
|
+
|
|
33
|
+
## Git rules
|
|
34
|
+
|
|
35
|
+
- COMMIT AND PUSH OFTEN. After each logical unit of work — don't accumulate
|
|
36
|
+
changes. Always `git push` after committing. This is automatic.
|
|
37
|
+
- Meaningful commit messages: what changed and why
|
|
38
|
+
- NEVER add Co-Authored-By or AI attribution trailers to commits
|
|
39
|
+
- Work on feature branches, never push directly to main
|
|
40
|
+
- Create pull requests for review
|
|
41
|
+
- NEVER merge any branch without explicit user permission. Always ask:
|
|
42
|
+
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
43
|
+
Wait for approval AND the delete/keep preference. Applies to ALL merges.
|
|
44
|
+
- Run `npx webjs test` before every commit
|
|
45
|
+
|
|
46
|
+
## Framework specifics
|
|
47
|
+
|
|
48
|
+
- No build step: ES modules served directly
|
|
49
|
+
- Web components with shadow DOM by default
|
|
50
|
+
- One function per server action file (*.server.ts)
|
|
51
|
+
- Use webjs directives: classMap, styleMap, ref, when, choose, guard, etc.
|
|
52
|
+
- Use Context for cross-component data, Task for async data in components
|
|
53
|
+
- Full API reference in AGENTS.md
|