@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 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
+ });