juxscript 1.0.24 → 1.0.25

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/bin/cli.js CHANGED
@@ -247,18 +247,17 @@ async function buildProject(isServe = false) {
247
247
  console.log('+ Created jux/index.jux');
248
248
  }
249
249
 
250
- // Copy preset styles to jux/styles/
251
- const presetsSrc = path.join(PATHS.packageRoot, 'presets');
250
+ // Copy preset styles from presets/styles/ to jux/styles/
251
+ const presetsStylesSrc = path.join(PATHS.packageRoot, 'presets', 'styles');
252
252
  const stylesDest = path.join(juxDir, 'styles');
253
253
 
254
- if (fs.existsSync(presetsSrc)) {
254
+ if (fs.existsSync(presetsStylesSrc)) {
255
255
  fs.mkdirSync(stylesDest, { recursive: true });
256
256
 
257
- const presetFiles = fs.readdirSync(presetsSrc);
258
- presetFiles.forEach(file => {
259
- // Only copy CSS files, skip hey.jux
257
+ const styleFiles = fs.readdirSync(presetsStylesSrc);
258
+ styleFiles.forEach(file => {
260
259
  if (file.endsWith('.css')) {
261
- const srcFile = path.join(presetsSrc, file);
260
+ const srcFile = path.join(presetsStylesSrc, file);
262
261
  const destFile = path.join(stylesDest, file);
263
262
  fs.copyFileSync(srcFile, destFile);
264
263
  console.log(`+ Copied preset style: styles/${file}`);
@@ -312,17 +311,24 @@ node_modules/
312
311
  // ✅ Always serves router bundle
313
312
  await buildProject(true);
314
313
 
315
- const port = parseInt(process.argv[3]) || 3000;
316
- await start(port);
314
+ // Parse port arguments: npx jux serve [httpPort] [wsPort]
315
+ const httpPort = parseInt(process.argv[3]) || 3000;
316
+ const wsPort = parseInt(process.argv[4]) || 3001;
317
+
318
+ await start(httpPort, wsPort);
317
319
 
318
320
  } else {
319
321
  console.log(`
320
322
  JUX CLI - A JavaScript UX authorship platform
321
323
 
322
324
  Usage:
323
- npx jux init Initialize a new JUX project
324
- npx jux build Build router bundle to ./jux-dist/
325
- npx jux serve [port] Start dev server with hot reload (default: 3000)
325
+ npx jux init Initialize a new JUX project
326
+ npx jux build Build router bundle to ./jux-dist/
327
+ npx jux serve [http] [ws] Start dev server with hot reload
328
+
329
+ Arguments:
330
+ [http] HTTP server port (default: 3000)
331
+ [ws] WebSocket port (default: 3001)
326
332
 
327
333
  Project Structure:
328
334
  my-project/
@@ -345,9 +351,10 @@ Getting Started:
345
351
  4. Serve jux-dist/ from your backend
346
352
 
347
353
  Examples:
348
- npx jux build Build production bundle
349
- npx jux serve Start dev server on port 3000
350
- npx jux serve 8080 Start dev server on port 8080
354
+ npx jux build # Build production bundle
355
+ npx jux serve # Dev server (ports 3000/3001)
356
+ npx jux serve 8080 # HTTP on 8080, WS on 3001
357
+ npx jux serve 8080 8081 # HTTP on 8080, WS on 8081
351
358
  `);
352
359
  }
353
360
  })();
@@ -2059,5 +2059,5 @@
2059
2059
  }
2060
2060
  ],
2061
2061
  "version": "1.0.0",
2062
- "lastUpdated": "2026-01-28T04:47:53.565Z"
2062
+ "lastUpdated": "2026-01-28T14:47:56.941Z"
2063
2063
  }
@@ -580,8 +580,23 @@ ${routeTable}
580
580
  const app = document.getElementById('app');
581
581
 
582
582
  function render() {
583
- const path = location.pathname;
584
- const view = routes[path] || JuxNotFound;
583
+ let path = location.pathname;
584
+
585
+ // Try exact match first
586
+ let view = routes[path];
587
+
588
+ // If no match and path ends with /, try appending 'index'
589
+ if (!view && path.endsWith('/')) {
590
+ view = routes[path + 'index'] || routes[path.slice(0, -1) + '/index'];
591
+ }
592
+
593
+ // If still no match and path doesn't end with /, try appending '/index'
594
+ if (!view && !path.endsWith('/')) {
595
+ view = routes[path + '/index'];
596
+ }
597
+
598
+ // Fall back to 404
599
+ view = view || JuxNotFound;
585
600
 
586
601
  app.innerHTML = '';
587
602
  app.removeAttribute('data-jux-page');
@@ -9,7 +9,45 @@ import { WebSocketServer } from 'ws';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
11
 
12
- async function serve(port = 3000, distDir = './jux-dist') {
12
+ /**
13
+ * Try to start a server on a port, with fallback to next port if busy
14
+ * Returns the actual port that was successfully allocated
15
+ */
16
+ async function tryPort(startPort, maxAttempts = 5, reservedPorts = []) {
17
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
18
+ const port = startPort + attempt;
19
+
20
+ // Skip if this port is already reserved
21
+ if (reservedPorts.includes(port)) {
22
+ continue;
23
+ }
24
+
25
+ try {
26
+ // Test if port is available
27
+ await new Promise((resolve, reject) => {
28
+ const testServer = http.createServer();
29
+ testServer.once('error', reject);
30
+ testServer.once('listening', () => {
31
+ testServer.close();
32
+ resolve(port);
33
+ });
34
+ testServer.listen(port);
35
+ });
36
+ return port;
37
+ } catch (err) {
38
+ if (err.code === 'EADDRINUSE') {
39
+ if (attempt < maxAttempts - 1) {
40
+ console.log(` Port ${port} in use, trying ${port + 1}...`);
41
+ }
42
+ continue;
43
+ }
44
+ throw err;
45
+ }
46
+ }
47
+ throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${startPort}`);
48
+ }
49
+
50
+ async function serve(httpPort = 3000, wsPort = 3001, distDir = './jux-dist') {
13
51
  const app = express();
14
52
  const absoluteDistDir = path.resolve(distDir);
15
53
  const projectRoot = path.resolve('.');
@@ -22,50 +60,46 @@ async function serve(port = 3000, distDir = './jux-dist') {
22
60
  process.exit(1);
23
61
  }
24
62
 
25
- // Strong cache prevention for main.js
63
+ // Strong cache prevention
26
64
  app.use((req, res, next) => {
27
65
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
28
66
  res.setHeader('Pragma', 'no-cache');
29
67
  res.setHeader('Expires', '0');
30
-
31
- // ✅ Add ETag prevention
32
68
  res.setHeader('ETag', 'W/"' + Date.now() + '"');
33
-
34
69
  next();
35
70
  });
36
71
 
37
- // Disable caching in dev mode
38
- app.use((req, res, next) => {
39
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
40
- res.setHeader('Pragma', 'no-cache');
41
- res.setHeader('Expires', '0');
42
- next();
43
- });
44
-
45
- // Serve static files (CSS, JS, images, etc.)
72
+ // Serve static files
46
73
  app.use(express.static(absoluteDistDir));
47
74
 
48
- // SPA fallback: only for requests that accept HTML (not static files)
75
+ // SPA fallback
49
76
  app.get('*', (req, res) => {
50
- // If request accepts HTML, serve index.html
51
77
  if (req.accepts('html')) {
52
78
  const indexPath = path.join(absoluteDistDir, 'index.html');
53
79
  if (fs.existsSync(indexPath)) {
54
80
  res.sendFile(indexPath);
55
81
  } else {
56
- res.status(404).send('index.html not found. Run `npx jux bundle` first.');
82
+ res.status(404).send('index.html not found. Run `npx jux build` first.');
57
83
  }
58
84
  } else {
59
- // For non-HTML requests (like .js, .css), return 404
60
85
  res.status(404).send('Not found');
61
86
  }
62
87
  });
63
88
 
89
+ // Find available ports sequentially to avoid collision
90
+ console.log('🔍 Finding available ports...');
91
+
92
+ // First, find HTTP port
93
+ const availableHttpPort = await tryPort(httpPort);
94
+
95
+ // Then find WS port, excluding the HTTP port we just allocated
96
+ const availableWsPort = await tryPort(wsPort, 5, [availableHttpPort]);
97
+
64
98
  // Create HTTP server
65
99
  const server = http.createServer(app);
66
100
 
67
101
  // WebSocket server for hot reload
68
- const wss = new WebSocketServer({ port: 3001 });
102
+ const wss = new WebSocketServer({ port: availableWsPort });
69
103
  const clients = [];
70
104
 
71
105
  wss.on('connection', (ws) => {
@@ -83,11 +117,11 @@ async function serve(port = 3000, distDir = './jux-dist') {
83
117
  });
84
118
  });
85
119
 
86
- console.log('🔌 WebSocket server running at ws://localhost:3001');
120
+ console.log(`🔌 WebSocket server running at ws://localhost:${availableWsPort}`);
87
121
 
88
122
  // Start HTTP server
89
- server.listen(port, () => {
90
- console.log(`🚀 JUX dev server running at http://localhost:${port}`);
123
+ server.listen(availableHttpPort, () => {
124
+ console.log(`🚀 JUX dev server running at http://localhost:${availableHttpPort}`);
91
125
  console.log(` Serving: ${absoluteDistDir}`);
92
126
  console.log(` Press Ctrl+C to stop\n`);
93
127
  });
@@ -110,9 +144,9 @@ async function serve(port = 3000, distDir = './jux-dist') {
110
144
  process.on('SIGINT', shutdown);
111
145
  process.on('SIGTERM', shutdown);
112
146
 
113
- return { server };
147
+ return { server, httpPort: availableHttpPort, wsPort: availableWsPort };
114
148
  }
115
149
 
116
- export async function start(port = 3000) {
117
- return serve(port, './jux-dist');
150
+ export async function start(httpPort = 3000, wsPort = 3001) {
151
+ return serve(httpPort, wsPort, './jux-dist');
118
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",