what-framework-cli 0.12.4 → 0.13.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/cli.js +27 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-framework-cli",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "What Framework CLI - Dev server, build, and deployment tools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "author": "ZVN DEV (https://zvndev.com)",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "what-framework": "^0.12.4"
25
+ "what-framework": "^0.13.0"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // Commands: dev, build, preview, generate
5
5
 
6
6
  import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, statSync, copyFileSync, realpathSync } from 'fs';
7
- import { join, resolve, relative, extname, basename, normalize, sep } from 'path';
7
+ import { join, resolve, relative, extname, normalize, sep } from 'path';
8
8
  import { createServer } from 'http';
9
9
  import { spawn } from 'child_process';
10
10
  import { createRequire } from 'module';
@@ -76,7 +76,7 @@ function isAllowedOrigin(origin, allowedHosts) {
76
76
  class SimpleWebSocketServer {
77
77
  constructor({ server, allowedHosts = new Set() }) {
78
78
  this.clients = new Set();
79
- server.on('upgrade', (req, socket, head) => {
79
+ server.on('upgrade', (req, socket, _head) => {
80
80
  if (req.headers.upgrade?.toLowerCase() !== 'websocket') return;
81
81
 
82
82
  const key = req.headers['sec-websocket-key'];
@@ -112,6 +112,10 @@ class SimpleWebSocketServer {
112
112
  class SimpleWebSocket {
113
113
  constructor(socket) {
114
114
  this.socket = socket;
115
+ /** @type {(() => void) | null} Assigned by the server that owns this client. */
116
+ this.onclose = null;
117
+ /** @type {((event: { data: string }) => void) | null} */
118
+ this.onmessage = null;
115
119
  this.socket.on('close', () => this.onclose?.());
116
120
  this.socket.on('error', () => this.onclose?.());
117
121
  this.socket.on('data', (data) => this._handleData(data));
@@ -153,7 +157,7 @@ class SimpleWebSocket {
153
157
  if (opcode === 0x01) { // Text frame
154
158
  this.onmessage?.({ data: payload.toString('utf8') });
155
159
  }
156
- } catch (e) {}
160
+ } catch {}
157
161
  }
158
162
 
159
163
  send(data) {
@@ -179,7 +183,7 @@ class SimpleWebSocket {
179
183
  }
180
184
 
181
185
  this.socket.write(Buffer.concat([header, payload]));
182
- } catch (e) {}
186
+ } catch {}
183
187
  }
184
188
 
185
189
  close() {
@@ -187,7 +191,7 @@ class SimpleWebSocket {
187
191
  const closeFrame = Buffer.from([0x88, 0x00]);
188
192
  this.socket.write(closeFrame);
189
193
  this.socket.end();
190
- } catch (e) {}
194
+ } catch {}
191
195
  }
192
196
  }
193
197
 
@@ -261,7 +265,7 @@ async function dev() {
261
265
  const runtimeDirs = requireRuntimeDirs('what dev');
262
266
 
263
267
  const server = createServer(async (req, res) => {
264
- const url = new URL(req.url, `http://${host}:${port}`);
268
+ const url = new URL(req.url || '/', `http://${host}:${port}`);
265
269
  let pathname = url.pathname;
266
270
 
267
271
  // Handle server actions
@@ -285,7 +289,9 @@ async function dev() {
285
289
  req.on('end', async () => {
286
290
  if (tooLarge) return;
287
291
  try {
288
- const { args } = JSON.parse(body);
292
+ // Parsed for validation only: a malformed body must reach the catch
293
+ // below rather than be answered with the dev placeholder.
294
+ JSON.parse(body);
289
295
  // In production, this would call the registered action
290
296
  // For dev, we'll return a placeholder response
291
297
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -370,8 +376,8 @@ async function dev() {
370
376
  const noMcp = args.includes('--no-mcp');
371
377
  if (!noMcp) {
372
378
  try {
373
- const { resolve: importResolve } = await import('node:module');
374
- // Check if the package exists by trying to resolve it
379
+ // Presence is checked by looking for the installed binary, not by
380
+ // resolving the specifier.
375
381
  const mcpBin = join(cwd, 'node_modules', '.bin', 'what-devtools-mcp');
376
382
  const { existsSync: mcpExists } = await import('node:fs');
377
383
  if (mcpExists(mcpBin)) {
@@ -426,7 +432,7 @@ async function dev() {
426
432
  for (const client of wsClients) {
427
433
  try {
428
434
  client.send(message);
429
- } catch (e) {
435
+ } catch {
430
436
  wsClients.delete(client);
431
437
  }
432
438
  }
@@ -574,7 +580,7 @@ async function preview() {
574
580
  }
575
581
 
576
582
  const server = createServer((req, res) => {
577
- let pathname = new URL(req.url, `http://localhost:${port}`).pathname;
583
+ let pathname = new URL(req.url || '/', `http://localhost:${port}`).pathname;
578
584
  if (pathname === '/') pathname = '/index.html';
579
585
 
580
586
  // Security: Prevent path traversal
@@ -638,7 +644,10 @@ async function generate() {
638
644
  try {
639
645
  const mod = await import(pathToFileURL(page).href);
640
646
  if (typeof (mod.default || mod) !== 'function') {
641
- throw new Error('no default-exported component');
647
+ // ERROR_CODES.PAGE_NO_DEFAULT_EXPORT
648
+ throw Object.assign(new Error('no default-exported component'), {
649
+ code: 'ERR_PAGE_NO_DEFAULT_EXPORT',
650
+ });
642
651
  }
643
652
  const { body, head } = await renderPage(mod, { params: {}, query: {}, path: route });
644
653
  html = staticDocument(body, head);
@@ -794,38 +803,16 @@ async function loadConfigAsync() {
794
803
  const mod = await import(fileUrl.href);
795
804
  _configCache = mod.default || mod;
796
805
  return _configCache;
797
- } catch (e) { /* use defaults */ }
806
+ } catch { /* use defaults */ }
798
807
  }
799
808
  _configCache = { mode: 'hybrid', pagesDir: 'src/pages', outDir: 'dist' };
800
809
  return _configCache;
801
810
  }
802
811
 
803
- // Synchronous wrapper for backward compatibility returns defaults,
804
- // then callers that need the real config should use loadConfigAsync()
805
- function loadConfig() {
806
- // If we already loaded async, return cached
807
- if (_configCache) return _configCache;
808
- // Fallback: try JSON.parse for simple object configs (no code execution)
809
- const configPath = join(cwd, 'what.config.js');
810
- if (existsSync(configPath)) {
811
- try {
812
- const src = readFileSync(configPath, 'utf-8');
813
- const match = src.match(/export default\s*(\{[\s\S]*?\})/);
814
- if (match) {
815
- // Only parse if the content is valid JSON (safe subset)
816
- // Convert JS object literal to JSON: add quotes to keys
817
- const jsonLike = match[1]
818
- .replace(/\/\/[^\n]*/g, '') // strip line comments
819
- .replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
820
- .replace(/,\s*([}\]])/g, '$1') // strip trailing commas
821
- .replace(/(['"])?(\w+)(['"])?\s*:/g, '"$2":') // quote keys
822
- .replace(/:\s*'([^']*)'/g, ': "$1"'); // single quotes to double
823
- return JSON.parse(jsonLike);
824
- }
825
- } catch (e) { /* use defaults */ }
826
- }
827
- return { mode: 'hybrid', pagesDir: 'src/pages', outDir: 'dist' };
828
- }
812
+ // A synchronous `loadConfig` lived here, commented as a backward-compatible
813
+ // wrapper. It was never exported and nothing called it; every call site uses
814
+ // loadConfigAsync(). Its JS-object-literal-to-JSON regex chain was also a
815
+ // second, divergent config parser that no test covered.
829
816
 
830
817
  function collectFiles(dir) {
831
818
  const files = [];
@@ -1202,7 +1189,7 @@ function watchFiles(dir, onChange) {
1202
1189
  }
1203
1190
  files.set(f, mtime);
1204
1191
  }
1205
- } catch (e) {
1192
+ } catch {
1206
1193
  // File was deleted during scan
1207
1194
  }
1208
1195
  }