juxscript 1.0.23 → 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
@@ -49,7 +49,6 @@ console.log(` Output: ${PATHS.frontendDist}`);
49
49
  console.log(` Lib: ${PATHS.juxLib}\n`);
50
50
 
51
51
  const command = process.argv[2];
52
- const subCommand = process.argv[3]; // For serve <pagename>
53
52
  const watchMode = process.argv.includes('--watch');
54
53
  const bundleMode = process.argv.includes('--bundle');
55
54
 
@@ -234,28 +233,31 @@ async function buildProject(isServe = false) {
234
233
  // Create structure
235
234
  fs.mkdirSync(juxDir, { recursive: true });
236
235
 
237
- // Copy hey.jux from presets as starter template
238
- const heyTemplatePath = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
239
- const heyTargetPath = path.join(juxDir, 'hey.jux');
236
+ // Copy hey.jux as the starter template
237
+ const heyJuxSrc = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
238
+ const heyJuxDest = path.join(juxDir, 'index.jux');
240
239
 
241
- if (fs.existsSync(heyTemplatePath)) {
242
- fs.copyFileSync(heyTemplatePath, heyTargetPath);
243
- console.log('+ Created jux/hey.jux (starter template)');
240
+ if (fs.existsSync(heyJuxSrc)) {
241
+ fs.copyFileSync(heyJuxSrc, heyJuxDest);
242
+ console.log('+ Created jux/index.jux from hey.jux template');
244
243
  } else {
245
- console.warn('⚠️ hey.jux template not found in presets/');
244
+ console.warn('⚠️ hey.jux template not found, creating basic file');
245
+ const basicContent = `import { jux } from 'juxscript';\n\njux.heading('welcome').text('Welcome to JUX').render('#app');`;
246
+ fs.writeFileSync(heyJuxDest, basicContent);
247
+ console.log('+ Created jux/index.jux');
246
248
  }
247
249
 
248
- // Copy over presets/*.css as styles/ to jux/
249
- 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');
250
252
  const stylesDest = path.join(juxDir, 'styles');
251
253
 
252
- if (fs.existsSync(presetsSrc)) {
254
+ if (fs.existsSync(presetsStylesSrc)) {
253
255
  fs.mkdirSync(stylesDest, { recursive: true });
254
256
 
255
- const presetFiles = fs.readdirSync(presetsSrc);
256
- presetFiles.forEach(file => {
257
+ const styleFiles = fs.readdirSync(presetsStylesSrc);
258
+ styleFiles.forEach(file => {
257
259
  if (file.endsWith('.css')) {
258
- const srcFile = path.join(presetsSrc, file);
260
+ const srcFile = path.join(presetsStylesSrc, file);
259
261
  const destFile = path.join(stylesDest, file);
260
262
  fs.copyFileSync(srcFile, destFile);
261
263
  console.log(`+ Copied preset style: styles/${file}`);
@@ -279,7 +281,7 @@ async function buildProject(isServe = false) {
279
281
  }
280
282
  };
281
283
  fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
282
- console.log(' Created package.json');
284
+ console.log('+ Created package.json');
283
285
  }
284
286
 
285
287
  // Create .gitignore
@@ -294,9 +296,10 @@ node_modules/
294
296
  console.log('+ Created .gitignore');
295
297
  }
296
298
 
297
- console.log('+ Created jux/ directory\n');
299
+ console.log('\n✅ JUX project initialized!\n');
298
300
  console.log('Next steps:');
299
- console.log(' npx jux serve hey # Start dev server for hey.jux\n');
301
+ console.log(' npm install # Install dependencies');
302
+ console.log(' npx jux serve # Start dev server with hot reload\n');
300
303
  console.log('Check out the docs: https://juxscript.com/docs\n');
301
304
 
302
305
  } else if (command === 'build') {
@@ -305,37 +308,35 @@ node_modules/
305
308
  console.log(`✅ Build complete: ${PATHS.frontendDist}`);
306
309
 
307
310
  } else if (command === 'serve') {
308
- // ✅ Serve with optional page parameter
309
- const pageName = subCommand; // e.g., "hey" from "npx jux serve hey"
310
-
311
- if (pageName) {
312
- console.log(`🎯 Serving specific page: ${pageName}\n`);
313
- }
314
-
311
+ // ✅ Always serves router bundle
315
312
  await buildProject(true);
316
313
 
317
- const port = pageName ? 3000 : (parseInt(subCommand) || 3000);
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;
318
317
 
319
- // Start server
320
- await start(port, pageName);
318
+ await start(httpPort, wsPort);
321
319
 
322
320
  } else {
323
321
  console.log(`
324
322
  JUX CLI - A JavaScript UX authorship platform
325
323
 
326
324
  Usage:
327
- npx jux init Initialize a new JUX project
328
- npx jux build Build router bundle to ./jux-dist/
329
- npx jux serve [page] Start dev server (optionally for specific page)
330
- npx jux serve [port] Start dev server on custom port
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)
331
332
 
332
333
  Project Structure:
333
334
  my-project/
334
335
  ├── jux/ # Your .jux source files
335
- │ ├── hey.jux # Starter page (created by init)
336
- │ └── pages/ # Additional pages
337
- ├── jux-dist/ # Build output (git-ignore this)
338
- ├── server/ # Your backend
336
+ │ ├── index.jux # Entry point
337
+ │ └── pages/ # Additional pages
338
+ ├── jux-dist/ # Build output (git-ignore this)
339
+ ├── server/ # Your backend
339
340
  └── package.json
340
341
 
341
342
  Import Style:
@@ -344,17 +345,16 @@ Import Style:
344
345
  import 'juxscript/presets/notion.js';
345
346
 
346
347
  Getting Started:
347
- 1. npx jux init # Create project structure
348
- 2. npm install # Install dependencies
349
- 3. npx jux serve hey # Dev server for hey.jux at localhost:3000/hey
350
- 4. npx jux serve # Dev server for all pages
351
- 5. Serve jux-dist/ from your backend
348
+ 1. npx jux init # Create project structure
349
+ 2. npm install # Install dependencies
350
+ 3. npx jux serve # Dev server with hot reload
351
+ 4. Serve jux-dist/ from your backend
352
352
 
353
353
  Examples:
354
- npx jux build Build production bundle
355
- npx jux serve Start dev server on port 3000 (all pages)
356
- npx jux serve hey Start dev server at localhost:3000/hey
357
- 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
358
358
  `);
359
359
  }
360
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
- next();
35
- });
36
-
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
69
  next();
43
70
  });
44
71
 
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,16 +117,13 @@ 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(`\n🚀 JUX dev server running`);
91
- console.log(` Local: http://localhost:${port}${pageName ? '/' + pageName : ''}`);
92
- if (pageName) {
93
- console.log(` (Root redirects to /${pageName})`);
94
- }
95
- console.log('');
123
+ server.listen(availableHttpPort, () => {
124
+ console.log(`🚀 JUX dev server running at http://localhost:${availableHttpPort}`);
125
+ console.log(` Serving: ${absoluteDistDir}`);
126
+ console.log(` Press Ctrl+C to stop\n`);
96
127
  });
97
128
 
98
129
  // Start file watcher
@@ -113,30 +144,9 @@ async function serve(port = 3000, distDir = './jux-dist') {
113
144
  process.on('SIGINT', shutdown);
114
145
  process.on('SIGTERM', shutdown);
115
146
 
116
- return { server };
147
+ return { server, httpPort: availableHttpPort, wsPort: availableWsPort };
117
148
  }
118
149
 
119
- export async function start(port = 3000, pageName = null) {
120
- const app = express();
121
-
122
- // ...existing code for static files...
123
-
124
- // If pageName specified, redirect root to that page
125
- if (pageName) {
126
- app.get('/', (req, res) => {
127
- res.redirect(`/${pageName}`);
128
- });
129
- console.log(`🎯 Root (/) redirects to /${pageName}`);
130
- }
131
-
132
- // ...existing code...
133
-
134
- app.listen(port, () => {
135
- console.log(`\n🚀 JUX dev server running`);
136
- console.log(` Local: http://localhost:${port}${pageName ? '/' + pageName : ''}`);
137
- if (pageName) {
138
- console.log(` (Root redirects to /${pageName})`);
139
- }
140
- console.log('');
141
- });
150
+ export async function start(httpPort = 3000, wsPort = 3001) {
151
+ return serve(httpPort, wsPort, './jux-dist');
142
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
package/presets/hey.jux CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jux } from 'juxscript';
2
2
 
3
- // Welcome page - edit this to build your app
4
- jux.hero('welcome-hero', {
3
+ // Welcome page with examples
4
+ jux.hero('welcome', {
5
5
  title: 'Welcome to JUX',
6
6
  subtitle: 'A JavaScript UX authorship platform'
7
7
  }).render('#app');
@@ -12,35 +12,49 @@ jux.container('getting-started')
12
12
  .style('max-width: 800px; margin: 2rem auto; padding: 2rem;')
13
13
  .render('#app');
14
14
 
15
- jux.heading('gs-title')
15
+ jux.heading('start-heading')
16
16
  .level(2)
17
17
  .text('Getting Started')
18
18
  .render('#getting-started');
19
19
 
20
- jux.paragraph('gs-intro')
21
- .text('Edit jux/hey.jux to start building your application.')
20
+ jux.paragraph('start-intro')
21
+ .text('Edit this file to build your app. Here are some quick tips:')
22
22
  .style('margin: 1rem 0;')
23
23
  .render('#getting-started');
24
24
 
25
- jux.list('gs-steps', {
25
+ jux.list('quick-tips', {
26
26
  items: [
27
- 'Run npx jux build to compile',
28
- 'Run npx jux serve to dev with hot reload',
29
- 'Run npx jux serve hey to serve just this page',
30
- 'Serve jux-dist/ from your backend in production'
27
+ 'Run npx jux serve for dev mode with hot reload',
28
+ 'Run npx jux build to compile for production',
29
+ 'Serve jux-dist/ from your backend',
30
+ 'Check out the docs at juxscript.com/docs'
31
31
  ]
32
32
  }).render('#getting-started');
33
33
 
34
34
  jux.divider().render('#app');
35
35
 
36
- jux.button('demo-button')
37
- .label('Click Me!')
38
- .variant('primary')
39
- .bind('click', () => {
40
- jux.alert('demo-alert')
41
- .type('success')
42
- .message('Button clicked! You are ready to build with JUX.')
43
- .render('#app');
44
- })
45
- .style('margin: 2rem auto; display: block;')
36
+ jux.container('example-container')
37
+ .style('max-width: 800px; margin: 2rem auto; padding: 2rem; background: #f9fafb; border-radius: 8px;')
46
38
  .render('#app');
39
+
40
+ jux.heading('example-heading')
41
+ .level(3)
42
+ .text('Quick Example')
43
+ .render('#example-container');
44
+
45
+ jux.code('example-code')
46
+ .language('javascript')
47
+ .code(`import { jux, state } from 'juxscript';
48
+
49
+ // Create a reactive counter
50
+ const count = state(0);
51
+
52
+ jux.button('increment')
53
+ .label('Click me!')
54
+ .bind('click', () => count.value++)
55
+ .render('#app');
56
+
57
+ jux.paragraph('counter')
58
+ .sync('text', count, val => \`Count: \${val}\`)
59
+ .render('#app');`)
60
+ .render('#example-container');