@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
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @webjskit/cli
|
|
2
|
+
|
|
3
|
+
CLI for [webjs](https://github.com/vivek7405/webjs) — scaffold, develop,
|
|
4
|
+
build, and run webjs apps.
|
|
5
|
+
|
|
6
|
+
Installing this package gives you the `webjs` command.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Most users won't install globally. Scaffold a new app instead:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npx @webjskit/cli create my-app
|
|
14
|
+
cd my-app
|
|
15
|
+
npm install
|
|
16
|
+
npm run dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or globally:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install -g @webjskit/cli
|
|
23
|
+
webjs create my-app
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
webjs create <name> # scaffold a full-stack app (default)
|
|
30
|
+
webjs create <name> --template api # backend-only API app
|
|
31
|
+
webjs create <name> --template saas # auth + dashboard + Prisma User model
|
|
32
|
+
|
|
33
|
+
webjs dev # dev server with live reload
|
|
34
|
+
webjs start # production server
|
|
35
|
+
webjs build # production bundle
|
|
36
|
+
webjs check # validate project conventions
|
|
37
|
+
webjs test # run server + browser tests
|
|
38
|
+
webjs db <prisma-subcommand> # prisma passthrough (saas template)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Scaffolded templates
|
|
42
|
+
|
|
43
|
+
The scaffold seeds opinionated defaults so AI agents produce consistent code:
|
|
44
|
+
|
|
45
|
+
- `AGENTS.md` + `CONVENTIONS.md` (the machine-readable contract)
|
|
46
|
+
- `.claude/`, `.cursorrules`, `.windsurfrules`, `.github/copilot-instructions.md`
|
|
47
|
+
- `test/unit/` and `test/browser/` with example tests
|
|
48
|
+
- Tailwind CSS via CLI (no browser runtime at build time)
|
|
49
|
+
- TypeScript, `.editorconfig`, `.gitignore`
|
|
50
|
+
|
|
51
|
+
See the full framework docs at https://github.com/vivek7405/webjs.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/bin/webjs.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
const USAGE = `webjs — commands:
|
|
10
|
+
webjs dev [--port 3000] Start dev server with live reload
|
|
11
|
+
webjs build Bundle components + pages for production
|
|
12
|
+
webjs start [--port 3000] Start production server
|
|
13
|
+
[--http2 --cert <path> --key <path>] Serve HTTP/2 over TLS (falls back to h1.1)
|
|
14
|
+
webjs test [--server|--browser] Run server + browser tests
|
|
15
|
+
webjs check Validate app against conventions
|
|
16
|
+
webjs create <name> [--template full-stack|api|saas] Scaffold a new webjs app
|
|
17
|
+
webjs db generate Run \`prisma generate\`
|
|
18
|
+
webjs db migrate [name] Run \`prisma migrate dev\`
|
|
19
|
+
webjs db studio Run \`prisma studio\`
|
|
20
|
+
webjs help Show this help`;
|
|
21
|
+
|
|
22
|
+
/** @param {string[]} args */
|
|
23
|
+
function flag(args, name, def) {
|
|
24
|
+
const i = args.indexOf(name);
|
|
25
|
+
if (i === -1) return def;
|
|
26
|
+
return args[i + 1];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
switch (cmd) {
|
|
31
|
+
case 'dev': {
|
|
32
|
+
// If we're already inside the --watch child, start the server directly.
|
|
33
|
+
if (process.env.__WEBJS_DEV_CHILD === '1') {
|
|
34
|
+
const { startServer } = await import('@webjskit/server');
|
|
35
|
+
const port = Number(flag(rest, '--port', process.env.PORT || 3000));
|
|
36
|
+
await startServer({ appDir: process.cwd(), port, dev: true });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Otherwise, spawn ourselves as a child with node --watch.
|
|
41
|
+
// This restarts the process on file changes, guaranteeing a fresh
|
|
42
|
+
// Node ESM module cache. Without this, edits to transitively-imported
|
|
43
|
+
// modules (actions, queries, components, utils) don't take effect
|
|
44
|
+
// because Node caches ESM by URL with no public invalidation API.
|
|
45
|
+
// Build watch paths from directories that exist in the project.
|
|
46
|
+
const { existsSync } = await import('node:fs');
|
|
47
|
+
const watchPaths = [];
|
|
48
|
+
for (const dir of ['app', 'components', 'modules', 'lib', 'actions']) {
|
|
49
|
+
if (existsSync(dir)) watchPaths.push('--watch-path', dir);
|
|
50
|
+
}
|
|
51
|
+
// Watch root middleware/config if present
|
|
52
|
+
for (const f of ['middleware.ts', 'middleware.js']) {
|
|
53
|
+
if (existsSync(f)) watchPaths.push('--watch-path', f);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const child = spawn(
|
|
57
|
+
process.execPath,
|
|
58
|
+
[
|
|
59
|
+
'--watch',
|
|
60
|
+
'--watch-preserve-output',
|
|
61
|
+
...watchPaths,
|
|
62
|
+
...process.argv.slice(1),
|
|
63
|
+
],
|
|
64
|
+
{
|
|
65
|
+
stdio: 'inherit',
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
env: { ...process.env, __WEBJS_DEV_CHILD: '1' },
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'start': {
|
|
74
|
+
const { startServer } = await import('@webjskit/server');
|
|
75
|
+
const port = Number(flag(rest, '--port', process.env.PORT || 3000));
|
|
76
|
+
const http2 = rest.includes('--http2');
|
|
77
|
+
const cert = flag(rest, '--cert');
|
|
78
|
+
const key = flag(rest, '--key');
|
|
79
|
+
await startServer({ appDir: process.cwd(), port, dev: false, http2, cert, key });
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'build': {
|
|
83
|
+
const { buildBundle } = await import('@webjskit/server');
|
|
84
|
+
const t = Date.now();
|
|
85
|
+
const result = await buildBundle({
|
|
86
|
+
appDir: process.cwd(),
|
|
87
|
+
minify: rest.includes('--no-minify') ? false : true,
|
|
88
|
+
sourcemap: rest.includes('--no-sourcemap') ? false : true,
|
|
89
|
+
});
|
|
90
|
+
if (result.bundleFile) {
|
|
91
|
+
console.log(`webjs: bundled ${result.entries.length} entries → ${result.bundleFile} (${Date.now() - t}ms)`);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'db': {
|
|
96
|
+
const sub = rest[0];
|
|
97
|
+
const args = rest.slice(1);
|
|
98
|
+
const map = { generate: ['generate'], migrate: ['migrate', 'dev', ...args], studio: ['studio'] };
|
|
99
|
+
const prismaArgs = map[sub];
|
|
100
|
+
if (!prismaArgs) { console.error('Unknown db subcommand.\n' + USAGE); process.exit(1); }
|
|
101
|
+
const child = spawn('npx', ['prisma', ...prismaArgs], { stdio: 'inherit', cwd: process.cwd() });
|
|
102
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'test': {
|
|
106
|
+
const cwd = process.cwd();
|
|
107
|
+
const { existsSync } = await import('node:fs');
|
|
108
|
+
|
|
109
|
+
// Two test runners:
|
|
110
|
+
// 1. node:test for server-side tests (test/server/*.test.ts, test/unit/*.test.ts)
|
|
111
|
+
// 2. WTR + Playwright for browser tests (test/browser/*.test.js)
|
|
112
|
+
//
|
|
113
|
+
// `webjs test` → runs both
|
|
114
|
+
// `webjs test --server` → server tests only (node:test)
|
|
115
|
+
// `webjs test --browser` → browser tests only (WTR + Playwright)
|
|
116
|
+
|
|
117
|
+
const serverOnly = rest.includes('--server');
|
|
118
|
+
const browserOnly = rest.includes('--browser');
|
|
119
|
+
const runServer = !browserOnly;
|
|
120
|
+
const runBrowser = !serverOnly;
|
|
121
|
+
|
|
122
|
+
// --- Server tests (node:test) ---
|
|
123
|
+
if (runServer) {
|
|
124
|
+
const { readdir } = await import('node:fs/promises');
|
|
125
|
+
const testFiles = [];
|
|
126
|
+
|
|
127
|
+
for (const dir of ['test/server', 'test/unit', 'test']) {
|
|
128
|
+
const fullDir = join(cwd, dir);
|
|
129
|
+
if (!existsSync(fullDir)) continue;
|
|
130
|
+
const files = await readdir(fullDir);
|
|
131
|
+
for (const f of files) {
|
|
132
|
+
if (/\.test\.(js|ts|mjs|mts)$/.test(f)) {
|
|
133
|
+
const full = join(fullDir, f);
|
|
134
|
+
if (!testFiles.includes(full)) testFiles.push(full);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (testFiles.length > 0) {
|
|
140
|
+
console.log(`webjs test: running ${testFiles.length} server test file(s)…\n`);
|
|
141
|
+
const child = spawn(process.execPath, ['--test', ...testFiles], {
|
|
142
|
+
stdio: 'inherit', cwd, env: { ...process.env },
|
|
143
|
+
});
|
|
144
|
+
const code = await new Promise(r => child.on('exit', r));
|
|
145
|
+
if (code !== 0) process.exit(code ?? 1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --- Browser tests (WTR + Playwright) ---
|
|
150
|
+
if (runBrowser) {
|
|
151
|
+
const wtrConfig = join(cwd, 'web-test-runner.config.js');
|
|
152
|
+
if (existsSync(wtrConfig) || existsSync(join(cwd, 'web-test-runner.config.mjs'))) {
|
|
153
|
+
console.log(`\nwebjs test: running browser tests (WTR + Playwright)…\n`);
|
|
154
|
+
const child = spawn('npx', ['wtr'], {
|
|
155
|
+
stdio: 'inherit', cwd, env: { ...process.env },
|
|
156
|
+
});
|
|
157
|
+
const code = await new Promise(r => child.on('exit', r));
|
|
158
|
+
if (code !== 0) process.exit(code ?? 1);
|
|
159
|
+
} else if (!serverOnly) {
|
|
160
|
+
// No WTR config — check for test/browser directory
|
|
161
|
+
const browserDir = join(cwd, 'test', 'browser');
|
|
162
|
+
if (existsSync(browserDir)) {
|
|
163
|
+
console.log(`\nwebjs test: running browser tests (WTR + Playwright)…\n`);
|
|
164
|
+
const child = spawn('npx', ['wtr', '--files', 'test/browser/**/*.test.js'], {
|
|
165
|
+
stdio: 'inherit', cwd, env: { ...process.env },
|
|
166
|
+
});
|
|
167
|
+
const code = await new Promise(r => child.on('exit', r));
|
|
168
|
+
if (code !== 0) process.exit(code ?? 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('\nwebjs test: done ✓');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'check': {
|
|
177
|
+
const { checkConventions, RULES } = await import('@webjskit/server/check');
|
|
178
|
+
const violations = await checkConventions(process.cwd());
|
|
179
|
+
|
|
180
|
+
if (rest.includes('--rules')) {
|
|
181
|
+
console.log('webjs check — available rules:\n');
|
|
182
|
+
for (const r of RULES) {
|
|
183
|
+
console.log(` ${r.name.padEnd(30)} ${r.description}`);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (violations.length === 0) {
|
|
189
|
+
console.log('webjs check: all conventions pass ✓');
|
|
190
|
+
} else {
|
|
191
|
+
console.log(`webjs check: ${violations.length} violation(s) found\n`);
|
|
192
|
+
for (const v of violations) {
|
|
193
|
+
console.log(` ✗ [${v.rule}] ${v.file}`);
|
|
194
|
+
console.log(` ${v.message}`);
|
|
195
|
+
if (v.fix) console.log(` Fix: ${v.fix}`);
|
|
196
|
+
console.log();
|
|
197
|
+
}
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case 'create': {
|
|
203
|
+
const name = rest[0];
|
|
204
|
+
if (!name) {
|
|
205
|
+
console.error('Usage: webjs create <app-name> [--template full-stack|api]');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const template = flag(rest, '--template', 'full-stack');
|
|
209
|
+
const { scaffoldApp } = await import('../lib/create.js');
|
|
210
|
+
await scaffoldApp(name, process.cwd(), { template });
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'help':
|
|
214
|
+
case undefined:
|
|
215
|
+
console.log(USAGE);
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
console.error(`Unknown command: ${cmd}\n` + USAGE);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main().catch((e) => {
|
|
224
|
+
console.error(e);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|