juxscript 1.0.45 → 1.0.47

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
@@ -1,5 +1,123 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const command = process.argv[2];
11
+
12
+ // ═══════════════════════════════════════════════════════════════════
13
+ // CREATE COMMAND - Runs BEFORE dependencies are installed
14
+ // ═══════════════════════════════════════════════════════════════════
15
+
16
+ if (command === 'create') {
17
+ const projectName = process.argv[3] || 'my-jux-app';
18
+ const projectPath = path.join(process.cwd(), projectName);
19
+
20
+ console.log(`
21
+ ╔═══════════════════════════════════════════════════════╗
22
+ ║ ║
23
+ ║ 🎨 Welcome to JUX ║
24
+ ║ ║
25
+ ║ Creating your new JUX project... ║
26
+ ║ ║
27
+ ╚═══════════════════════════════════════════════════════╝
28
+ `);
29
+
30
+ if (fs.existsSync(projectPath)) {
31
+ console.error(`❌ Directory "${projectName}" already exists.`);
32
+ console.error(` Please choose a different name or remove the existing directory.\n`);
33
+ process.exit(1);
34
+ }
35
+
36
+ try {
37
+ const { execSync } = await import('child_process');
38
+
39
+ console.log(`📁 Creating directory: ${projectName}`);
40
+ fs.mkdirSync(projectPath, { recursive: true });
41
+ process.chdir(projectPath);
42
+
43
+ console.log(`📦 Initializing package.json...`);
44
+ const packageJson = {
45
+ name: projectName,
46
+ version: '0.1.0',
47
+ type: 'module',
48
+ scripts: {
49
+ dev: 'jux serve',
50
+ build: 'jux build'
51
+ },
52
+ dependencies: {
53
+ juxscript: 'latest'
54
+ }
55
+ };
56
+ fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
57
+ console.log(` ✓ package.json created`);
58
+
59
+ console.log(`\n📥 Installing juxscript...\n`);
60
+ try {
61
+ execSync('npm install', { stdio: 'inherit' });
62
+ console.log(`\n ✓ Dependencies installed`);
63
+ } catch (err) {
64
+ console.error(`\n ⚠️ npm install failed, but continuing...`);
65
+ }
66
+
67
+ console.log(`\n🎨 Initializing JUX project structure...`);
68
+ execSync('npx jux init', { stdio: 'inherit' });
69
+
70
+ console.log(`\n📝 Creating .gitignore...`);
71
+ fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
72
+ console.log(` ✓ .gitignore created`);
73
+
74
+ console.log(`
75
+ ╔═══════════════════════════════════════════════════════╗
76
+ ║ ║
77
+ ║ ✅ Project created successfully! ║
78
+ ║ ║
79
+ ╚═══════════════════════════════════════════════════════╝
80
+
81
+ 📚 Resources:
82
+ Documentation: [coming soon]
83
+ GitHub: https://github.com/juxscript/jux
84
+ Examples: https://github.com/juxscript/examples
85
+
86
+ ⭐ If you find JUX useful, please star us on GitHub!
87
+ 🔒 Security: Report issues to security@juxscript.com [placeholder]
88
+
89
+ Say goodbye to markup </</>>>.
90
+ Happy javascripting your frontend! 🎉
91
+
92
+ Next steps:
93
+ cd ${projectName}
94
+ npm run dev
95
+ `);
96
+
97
+ } catch (err) {
98
+ console.error(`\n❌ Project creation failed:`, err.message);
99
+
100
+ if (fs.existsSync(projectPath)) {
101
+ try {
102
+ process.chdir('..');
103
+ fs.rmSync(projectPath, { recursive: true, force: true });
104
+ console.log(` ✓ Cleaned up failed project directory\n`);
105
+ } catch (cleanupErr) {
106
+ console.error(` ⚠️ Could not clean up directory\n`);
107
+ }
108
+ }
109
+
110
+ process.exit(1);
111
+ }
112
+
113
+ process.exit(0); // ✅ Exit after create completes
114
+ }
115
+
116
+ // ═══════════════════════════════════════════════════════════════════
117
+ // ALL OTHER COMMANDS - Require dependencies to be installed
118
+ // ═══════════════════════════════════════════════════════════════════
119
+
120
+ // ✅ Now import dependencies (only needed for init, build, serve)
3
121
  import {
4
122
  copyLibToOutput,
5
123
  copyProjectAssets,
@@ -10,16 +128,6 @@ import {
10
128
  } from '../machinery/compiler.js';
11
129
  import { generateDocs } from '../machinery/doc-generator.js';
12
130
  import { start } from '../machinery/server.js';
13
- import path from 'path';
14
- import fs from 'fs';
15
- import { fileURLToPath } from 'url';
16
- import { loadConfig, runBootstrap } from '../machinery/config.js';
17
-
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = path.dirname(__filename);
20
-
21
- // Load configuration first (before PATHS)
22
- const config = await loadConfig(process.cwd());
23
131
 
24
132
  // CLEAR PATH CONTRACT - CONVENTIONS
25
133
  const PATHS = {
@@ -29,14 +137,20 @@ const PATHS = {
29
137
  // Where the user's project root is (where they run `npx jux`)
30
138
  projectRoot: process.cwd(),
31
139
 
32
- // Where user's .jux source files live (from config or default)
33
- juxSource: path.join(process.cwd(), config.sourceDir),
140
+ // Where user's .jux source files live (CONVENTION: ./jux/)
141
+ get juxSource() {
142
+ return path.join(this.projectRoot, 'jux');
143
+ },
34
144
 
35
145
  // Where jux lib files are (components, layouts, etc.)
36
- juxLib: path.resolve(__dirname, '..', 'lib'),
146
+ get juxLib() {
147
+ return path.join(this.packageRoot, 'lib');
148
+ },
37
149
 
38
- // Where frontend build output goes (from config or default)
39
- frontendDist: path.join(process.cwd(), config.distDir)
150
+ // Where frontend build output goes (CONVENTION: ./jux-dist/)
151
+ get frontendDist() {
152
+ return path.join(this.projectRoot, 'jux-dist');
153
+ }
40
154
  };
41
155
 
42
156
  console.log('📍 JUX Paths:');
@@ -46,10 +160,6 @@ console.log(` Source: ${PATHS.juxSource}`);
46
160
  console.log(` Output: ${PATHS.frontendDist}`);
47
161
  console.log(` Lib: ${PATHS.juxLib}\n`);
48
162
 
49
- const command = process.argv[2];
50
- const watchMode = process.argv.includes('--watch');
51
- const bundleMode = process.argv.includes('--bundle');
52
-
53
163
  /**
54
164
  * Recursively find .jux files in a directory
55
165
  */
@@ -78,9 +188,8 @@ function findJuxFiles(dir, fileList = []) {
78
188
  * Build the entire JUX project (ALWAYS uses router bundle)
79
189
  *
80
190
  * @param {boolean} isServe - Whether building for dev server
81
- * @param {number} wsPort - WebSocket port for hot reload
82
191
  */
83
- async function buildProject(isServe = false, wsPort = 3001) {
192
+ async function buildProject(isServe = false) {
84
193
  const buildStartTime = performance.now();
85
194
  console.log('🔨 Building JUX frontend...\n');
86
195
 
@@ -100,14 +209,14 @@ async function buildProject(isServe = false, wsPort = 3001) {
100
209
 
101
210
  // Step 1: Generate documentation
102
211
  const docsStartTime = performance.now();
103
- let docsTime = 0;
212
+ let docsTime = 0; // ✅ Declare with default value
104
213
  console.log('📚 Generating documentation...');
105
214
  try {
106
215
  await generateDocs(PATHS.juxLib);
107
216
  docsTime = performance.now() - docsStartTime;
108
217
  console.log(`✅ Documentation generated (${docsTime.toFixed(0)}ms)\n`);
109
218
  } catch (error) {
110
- docsTime = performance.now() - docsStartTime;
219
+ docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
111
220
  console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
112
221
  }
113
222
 
@@ -123,7 +232,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
123
232
  const presetsTime = performance.now() - presetsStartTime;
124
233
  console.log(`⏱️ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
125
234
 
126
- // Step 4: Copy project assets
235
+ // Step 4: Copy project assets (CSS, JS, images)
127
236
  const assetsStartTime = performance.now();
128
237
  await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
129
238
  const assetsTime = performance.now() - assetsStartTime;
@@ -144,6 +253,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
144
253
  process.exit(1);
145
254
  }
146
255
 
256
+ // ✅ Bundle and get the generated filename
147
257
  const bundleStartTime = performance.now();
148
258
  const mainJsFilename = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
149
259
  routePrefix: ''
@@ -173,14 +283,16 @@ async function buildProject(isServe = false, wsPort = 3001) {
173
283
  };
174
284
  });
175
285
 
286
+ // ✅ Generate unified index.html
176
287
  const indexStartTime = performance.now();
177
- generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename, wsPort);
288
+ generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename);
178
289
  const indexTime = performance.now() - indexStartTime;
179
290
 
180
291
  const totalBuildTime = performance.now() - buildStartTime;
181
292
 
182
293
  console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${mainJsFilename}\n`);
183
294
 
295
+ // ✅ Build summary with timing breakdown
184
296
  console.log(`📊 Build Summary:`);
185
297
  console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
186
298
  console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
@@ -193,6 +305,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
193
305
  console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
194
306
  console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
195
307
 
308
+ // Show usage
196
309
  if (!isServe) {
197
310
  console.log('📦 Serve from your backend:');
198
311
  console.log(` Express: app.use(express.static('jux-dist'))`);
@@ -215,102 +328,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
215
328
  }
216
329
 
217
330
  (async () => {
218
- if (command === 'create') {
219
- const projectName = process.argv[3] || 'my-jux-app';
220
- const projectPath = path.join(PATHS.projectRoot, projectName);
221
-
222
- console.log(`
223
- ╔═══════════════════════════════════════════════════════╗
224
- ║ ║
225
- ║ 🎨 Welcome to JUX ║
226
- ║ ║
227
- ║ Creating your new JUX project... ║
228
- ║ ║
229
- ╚═══════════════════════════════════════════════════════╝
230
- `);
231
-
232
- if (fs.existsSync(projectPath)) {
233
- console.error(`❌ Directory "${projectName}" already exists.`);
234
- console.error(` Please choose a different name or remove the existing directory.\n`);
235
- process.exit(1);
236
- }
237
-
238
- try {
239
- const { execSync } = await import('child_process');
240
-
241
- console.log(`📁 Creating directory: ${projectName}`);
242
- fs.mkdirSync(projectPath, { recursive: true });
243
- process.chdir(projectPath);
244
-
245
- console.log(`📦 Initializing package.json...`);
246
- const packageJson = {
247
- name: projectName,
248
- version: '0.1.0',
249
- type: 'module',
250
- scripts: {
251
- dev: 'jux serve',
252
- build: 'jux build'
253
- },
254
- dependencies: {
255
- juxscript: 'latest' // ✅ Changed from '^1.0.8' to 'latest'
256
- }
257
- };
258
- fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
259
- console.log(` ✓ package.json created`);
260
-
261
- console.log(`\n📥 Installing juxscript...\n`);
262
- try {
263
- execSync('npm install', { stdio: 'inherit' });
264
- console.log(`\n ✓ Dependencies installed`);
265
- } catch (err) {
266
- console.error(`\n ⚠️ npm install failed, but continuing...`);
267
- }
268
-
269
- console.log(`\n🎨 Initializing JUX project structure...`);
270
- execSync('npx jux init', { stdio: 'inherit' });
271
-
272
- console.log(`\n📝 Creating .gitignore...`);
273
- fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
274
- console.log(` ✓ .gitignore created`);
275
-
276
- console.log(`
277
- ╔═══════════════════════════════════════════════════════╗
278
- ║ ║
279
- ║ ✅ Project created successfully! ║
280
- ║ ║
281
- ╚═══════════════════════════════════════════════════════╝
282
-
283
- 📚 Resources:
284
- Documentation: [coming soon]
285
- GitHub: https://github.com/juxscript/jux
286
- Examples: https://github.com/juxscript/examples
287
-
288
- ⭐ If you find JUX useful, please star us on GitHub!
289
- 🔒 Security: Report issues to security@juxscript.com [placeholder]
290
-
291
- Say goodbye to markup </</>>>.
292
- Happy javascripting your frontend! 🎉
293
-
294
- Next steps, run: cd ${projectName} && npm run dev
295
- `);
296
-
297
- } catch (err) {
298
- console.error(`\n❌ Project creation failed:`, err.message);
299
-
300
- if (fs.existsSync(projectPath)) {
301
- try {
302
- process.chdir('..');
303
- fs.rmSync(projectPath, { recursive: true, force: true });
304
- console.log(` ✓ Cleaned up failed project directory\n`);
305
- } catch (cleanupErr) {
306
- console.error(` ⚠️ Could not clean up directory\n`);
307
- }
308
- }
309
-
310
- process.exit(1);
311
- }
312
-
313
- } else if (command === 'init') {
331
+ if (command === 'init') {
314
332
  console.log('🎨 Initializing JUX project...\n');
315
333
 
316
334
  const juxDir = PATHS.juxSource;
@@ -320,8 +338,10 @@ Next steps, run: cd ${projectName} && npm run dev
320
338
  process.exit(1);
321
339
  }
322
340
 
341
+ // Create structure
323
342
  fs.mkdirSync(juxDir, { recursive: true });
324
343
 
344
+ // Copy jux.jux as the starter index.jux (if it exists)
325
345
  const juxJuxSrc = path.join(PATHS.packageRoot, 'presets', 'jux.jux');
326
346
  const indexJuxDest = path.join(juxDir, 'index.jux');
327
347
 
@@ -329,6 +349,7 @@ Next steps, run: cd ${projectName} && npm run dev
329
349
  fs.copyFileSync(juxJuxSrc, indexJuxDest);
330
350
  console.log('+ Created jux/index.jux from jux.jux template');
331
351
  } else {
352
+ // Fallback to hey.jux if jux.jux doesn't exist
332
353
  const heyJuxSrc = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
333
354
  if (fs.existsSync(heyJuxSrc)) {
334
355
  fs.copyFileSync(heyJuxSrc, indexJuxDest);
@@ -341,6 +362,7 @@ Next steps, run: cd ${projectName} && npm run dev
341
362
  }
342
363
  }
343
364
 
365
+ // Copy entire presets folder to jux/presets/ (excluding jux.jux)
344
366
  const presetsSrc = path.join(PATHS.packageRoot, 'presets');
345
367
  const presetsDest = path.join(juxDir, 'presets');
346
368
 
@@ -354,6 +376,7 @@ Next steps, run: cd ${projectName} && npm run dev
354
376
  const srcPath = path.join(src, entry.name);
355
377
  const destPath = path.join(dest, entry.name);
356
378
 
379
+ // Skip jux.jux since we already copied it to index.jux
357
380
  if (entry.isFile() && entry.name === 'jux.jux') {
358
381
  continue;
359
382
  }
@@ -378,51 +401,37 @@ Next steps, run: cd ${projectName} && npm run dev
378
401
  }
379
402
  }
380
403
 
404
+ // Create package.json if it doesn't exist
381
405
  const pkgPath = path.join(PATHS.projectRoot, 'package.json');
382
406
  if (!fs.existsSync(pkgPath)) {
383
- // ✅ Get project name from current directory or default
384
- const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
385
-
386
407
  const pkgContent = {
387
- "name": projectName,
388
- "version": "0.1.0",
408
+ "name": "my-jux-project",
409
+ "version": "1.0.0",
389
410
  "type": "module",
390
411
  "scripts": {
391
- "dev": "jux serve",
392
- "build": "jux build"
412
+ "build": "jux build",
413
+ "serve": "jux serve"
393
414
  },
394
415
  "dependencies": {
395
- "juxscript": "latest" // ✅ Always use latest stable
416
+ "juxscript": "^1.0.8"
396
417
  }
397
418
  };
398
419
  fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
399
420
  console.log('+ Created package.json');
400
421
  }
401
422
 
423
+ // Create .gitignore
402
424
  const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
403
- const gitignoreContent = `jux-dist/\nnode_modules/\n.DS_Store\n`;
425
+ const gitignoreContent = `jux-dist/
426
+ node_modules/
427
+ .DS_Store
428
+ `;
404
429
 
405
430
  if (!fs.existsSync(gitignorePath)) {
406
431
  fs.writeFileSync(gitignorePath, gitignoreContent);
407
432
  console.log('+ Created .gitignore');
408
433
  }
409
434
 
410
- // ✅ Create actual juxconfig.js (not just example)
411
- const configSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
412
- const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
413
-
414
- if (fs.existsSync(configSrc) && !fs.existsSync(configDest)) {
415
- fs.copyFileSync(configSrc, configDest);
416
- console.log('+ Created juxconfig.js (customize as needed)');
417
- }
418
-
419
- // Also copy example as reference
420
- const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
421
- if (fs.existsSync(configSrc) && !fs.existsSync(configExampleDest)) {
422
- fs.copyFileSync(configSrc, configExampleDest);
423
- console.log('+ Created juxconfig.example.js (reference)');
424
- }
425
-
426
435
  console.log('\n✅ JUX project initialized!\n');
427
436
  console.log('Next steps:');
428
437
  console.log(' npm install # Install dependencies');
@@ -430,15 +439,18 @@ Next steps, run: cd ${projectName} && npm run dev
430
439
  console.log('Check out the docs: https://juxscript.com/docs\n');
431
440
 
432
441
  } else if (command === 'build') {
442
+ // ✅ Always builds router bundle
433
443
  await buildProject(false);
434
444
  console.log(`✅ Build complete: ${PATHS.frontendDist}`);
435
445
 
436
446
  } else if (command === 'serve') {
437
- const httpPort = parseInt(process.argv[3]) || config.ports.http;
438
- const wsPort = parseInt(process.argv[4]) || config.ports.ws;
447
+ // Always serves router bundle
448
+ await buildProject(true);
449
+
450
+ // Parse port arguments: npx jux serve [httpPort] [wsPort]
451
+ const httpPort = parseInt(process.argv[3]) || 3000;
452
+ const wsPort = parseInt(process.argv[4]) || 3001;
439
453
 
440
- await runBootstrap(config.bootstrap);
441
- await buildProject(true, wsPort);
442
454
  await start(httpPort, wsPort);
443
455
 
444
456
  } else {
@@ -446,15 +458,39 @@ Next steps, run: cd ${projectName} && npm run dev
446
458
  JUX CLI - A JavaScript UX authorship platform
447
459
 
448
460
  Usage:
449
- npx jux create [name] Create a new JUX project
450
- npx jux init Initialize JUX in current directory
461
+ npx jux init Initialize a new JUX project
451
462
  npx jux build Build router bundle to ./jux-dist/
452
463
  npx jux serve [http] [ws] Start dev server with hot reload
453
464
 
465
+ Arguments:
466
+ [http] HTTP server port (default: 3000)
467
+ [ws] WebSocket port (default: 3001)
468
+
469
+ Project Structure:
470
+ my-project/
471
+ ├── jux/ # Your .jux source files
472
+ │ ├── index.jux # Entry point
473
+ │ └── pages/ # Additional pages
474
+ ├── jux-dist/ # Build output (git-ignore this)
475
+ ├── server/ # Your backend
476
+ └── package.json
477
+
478
+ Import Style:
479
+ // In your project's .jux files
480
+ import { jux, state } from 'juxscript';
481
+ import 'juxscript/presets/notion.js';
482
+
483
+ Getting Started:
484
+ 1. npx jux init # Create project structure
485
+ 2. npm install # Install dependencies
486
+ 3. npx jux serve # Dev server with hot reload
487
+ 4. Serve jux-dist/ from your backend
488
+
454
489
  Examples:
455
- npx jux create my-app Create new project
456
- npx jux serve Dev server (ports 3000/3001)
457
- npx jux serve 8080 8081 Custom ports
490
+ npx jux build # Build production bundle
491
+ npx jux serve # Dev server (ports 3000/3001)
492
+ npx jux serve 8080 # HTTP on 8080, WS on 3001
493
+ npx jux serve 8080 8081 # HTTP on 8080, WS on 8081
458
494
  `);
459
495
  }
460
496
  })();
@@ -2053,5 +2053,5 @@
2053
2053
  }
2054
2054
  ],
2055
2055
  "version": "1.0.0",
2056
- "lastUpdated": "2026-01-28T20:10:47.892Z"
2056
+ "lastUpdated": "2026-01-28T20:46:06.367Z"
2057
2057
  }
@@ -673,12 +673,16 @@ render();
673
673
  *
674
674
  * @param {string} distDir - Destination directory
675
675
  * @param {Array<{path: string, functionName: string}>} routes - Route definitions
676
- * @param {string} mainJsFilename - The generated main.js filename
677
- * @param {number} wsPort - WebSocket port for hot reload
676
+ * @param {string} mainJsFilename - The generated main.js filename (e.g., 'main.1234567890.js')
678
677
  */
679
- export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js', wsPort = 3001) {
678
+ export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
680
679
  console.log('📄 Generating index.html...');
681
680
 
681
+ // Generate navigation links
682
+ const navLinks = routes
683
+ .map(r => ` <a href="${r.path}">${r.functionName.replace(/_/g, ' ')}</a>`)
684
+ .join(' |\n');
685
+
682
686
  const importMapScript = generateImportMapScript();
683
687
 
684
688
  const html = `<!DOCTYPE html>
@@ -693,62 +697,6 @@ export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js', w
693
697
  <div id="app"></div>
694
698
  ${importMapScript}
695
699
  <script type="module" src="/${mainJsFilename}"></script>
696
-
697
- <!-- Hot Reload Script -->
698
- <script>
699
- (function() {
700
- const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
701
-
702
- ws.onopen = () => {
703
- console.log('🔌 Hot reload connected');
704
- };
705
-
706
- ws.onmessage = (event) => {
707
- const data = JSON.parse(event.data);
708
-
709
- if (data.type === 'reload') {
710
- console.log('🔄 Hot reload triggered - reloading page...');
711
- location.reload();
712
- } else if (data.type === 'css-reload') {
713
- console.log('🎨 CSS hot reload:', data.path);
714
-
715
- // Find all link tags pointing to this CSS file
716
- const links = document.querySelectorAll('link[rel="stylesheet"]');
717
- links.forEach(link => {
718
- if (link.href.includes(data.path)) {
719
- const newLink = link.cloneNode();
720
- newLink.href = data.path + '?t=' + Date.now();
721
- link.parentNode.insertBefore(newLink, link.nextSibling);
722
- setTimeout(() => link.remove(), 100);
723
- }
724
- });
725
-
726
- // Also reload any @import in style tags
727
- const styles = document.querySelectorAll('style');
728
- styles.forEach(style => {
729
- if (style.textContent.includes(data.path)) {
730
- style.textContent = style.textContent.replace(
731
- new RegExp(data.path + '(\\\\?t=\\\\d+)?', 'g'),
732
- data.path + '?t=' + Date.now()
733
- );
734
- }
735
- });
736
- }
737
- };
738
-
739
- ws.onclose = () => {
740
- console.log('🔌 Hot reload disconnected');
741
- // Try to reconnect after 1 second
742
- setTimeout(() => {
743
- location.reload();
744
- }, 1000);
745
- };
746
-
747
- ws.onerror = (error) => {
748
- console.error('🔌 Hot reload error:', error);
749
- };
750
- })();
751
- </script>
752
700
  </body>
753
701
  </html>`;
754
702
 
@@ -149,66 +149,4 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './jux-dist') {
149
149
 
150
150
  export async function start(httpPort = 3000, wsPort = 3001) {
151
151
  return serve(httpPort, wsPort, './jux-dist');
152
- }
153
-
154
- function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
155
- const html = `<!DOCTYPE html>
156
- <html lang="en">
157
- <head>
158
- <meta charset="UTF-8">
159
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
160
- <title>Jux Application</title>
161
- </head>
162
- <body data-theme="">
163
- <div id="app"></div>
164
- ${importMapScript}
165
- <script type="module" src="/${mainJsFilename}"></script>
166
-
167
- <!-- Hot Reload Script -->
168
- <script>
169
- (function() {
170
- const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
171
-
172
- ws.onmessage = (event) => {
173
- const data = JSON.parse(event.data);
174
-
175
- if (data.type === 'reload') {
176
- console.log('🔄 Hot reload triggered');
177
- location.reload();
178
- } else if (data.type === 'css-reload') {
179
- console.log('🎨 CSS hot reload:', data.path);
180
-
181
- // Find all link tags pointing to this CSS file
182
- const links = document.querySelectorAll('link[rel="stylesheet"]');
183
- links.forEach(link => {
184
- if (link.href.includes(data.path)) {
185
- const newLink = link.cloneNode();
186
- newLink.href = data.path + '?t=' + Date.now();
187
- link.parentNode.insertBefore(newLink, link.nextSibling);
188
- setTimeout(() => link.remove(), 100);
189
- }
190
- });
191
-
192
- // Also reload any @import in style tags
193
- const styles = document.querySelectorAll('style');
194
- styles.forEach(style => {
195
- if (style.textContent.includes(data.path)) {
196
- style.textContent = style.textContent.replace(
197
- new RegExp(data.path + '(\\?t=\\d+)?', 'g'),
198
- data.path + '?t=' + Date.now()
199
- );
200
- }
201
- });
202
- }
203
- };
204
-
205
- ws.onclose = () => {
206
- console.log('🔌 Hot reload disconnected');
207
- };
208
- })();
209
- </script>
210
- </body>
211
- </html>`;
212
-
213
- return html;
214
152
  }
@@ -1,6 +1,10 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { bundleJuxFilesToRouter } from './compiler.js';
3
+ import {
4
+ copyLibToOutput,
5
+ bundleJuxFilesToRouter,
6
+ generateIndexHtml
7
+ } from './compiler.js';
4
8
 
5
9
  let isRebuilding = false;
6
10
  let rebuildQueued = false;
@@ -115,130 +119,53 @@ async function fullRebuild(juxSource, distDir) {
115
119
  * Start watching for file changes and rebuild on change
116
120
  */
117
121
  export function startWatcher(juxSource, distDir, wsClients) {
118
- console.log(`👀 Watching for changes in: ${juxSource}`);
119
-
120
- // Debounce map to prevent multiple rapid triggers
121
- const debounceTimers = new Map();
122
-
123
- function debounce(key, fn, delay = 100) {
124
- if (debounceTimers.has(key)) {
125
- clearTimeout(debounceTimers.get(key));
122
+ console.log(`👀 Watching: ${juxSource}`);
123
+
124
+ const watcher = fs.watch(juxSource, { recursive: true }, async (eventType, filename) => {
125
+ // Ignore non-.jux files and certain patterns
126
+ if (!filename ||
127
+ !filename.endsWith('.jux') ||
128
+ filename.includes('node_modules') ||
129
+ filename.includes('jux-dist') ||
130
+ filename.startsWith('.')) {
131
+ return;
126
132
  }
127
- debounceTimers.set(key, setTimeout(() => {
128
- debounceTimers.delete(key);
129
- fn();
130
- }, delay));
131
- }
132
-
133
- // Recursively watch directories
134
- function watchRecursive(dir) {
135
- if (!fs.existsSync(dir)) return;
136
-
137
- try {
138
- fs.watch(dir, { recursive: true }, (eventType, filename) => {
139
- if (!filename) return;
140
-
141
- const filePath = path.join(dir, filename);
142
- const ext = path.extname(filename);
143
-
144
- // Skip certain patterns
145
- if (filename.includes('node_modules') ||
146
- filename.includes('jux-dist') ||
147
- filename.includes('.git') ||
148
- filename.startsWith('.')) {
149
- return;
150
- }
151
-
152
- // Debounce to avoid multiple rapid events
153
- debounce(filePath, async () => {
154
- // ✅ Handle CSS files
155
- if (ext === '.css') {
156
- console.log(`\n🎨 CSS changed: ${filename}`);
157
-
158
- try {
159
- const relativePath = path.relative(juxSource, filePath);
160
- const destPath = path.join(distDir, relativePath);
161
- const destDir = path.dirname(destPath);
162
-
163
- if (!fs.existsSync(destDir)) {
164
- fs.mkdirSync(destDir, { recursive: true });
165
- }
166
-
167
- fs.copyFileSync(filePath, destPath);
168
- console.log(` ✓ Copied to: ${path.relative(process.cwd(), destPath)}`);
169
-
170
- // Notify browser to reload CSS
171
- wsClients.forEach(client => {
172
- if (client.readyState === 1) {
173
- client.send(JSON.stringify({
174
- type: 'css-reload',
175
- path: `/${relativePath}`
176
- }));
177
- }
178
- });
179
-
180
- console.log(` 🔄 Browser CSS reloaded\n`);
181
- } catch (err) {
182
- console.error(` ❌ CSS copy failed:`, err.message);
183
- }
184
- return;
185
- }
186
-
187
- // ✅ Handle .jux files
188
- if (ext === '.jux') {
189
- console.log(`\n📝 File changed: ${filename}`);
190
- console.log(' 🔨 Rebuilding...');
191
-
192
- try {
193
- await bundleJuxFilesToRouter(juxSource, distDir, { routePrefix: '' });
194
-
195
- wsClients.forEach(client => {
196
- if (client.readyState === 1) {
197
- client.send(JSON.stringify({ type: 'reload' }));
198
- }
199
- });
200
-
201
- console.log(' ✅ Rebuild complete');
202
- console.log(' 🔄 Browser reloaded\n');
203
- } catch (err) {
204
- console.error(' ❌ Rebuild failed:', err.message);
205
- }
206
- }
207
133
 
208
- // Handle .js files (non-_dev-imports.js)
209
- if (ext === '.js' && !filename.includes('_dev-imports.js')) {
210
- console.log(`\n📦 JS asset changed: ${filename}`);
134
+ // Debounce: If already rebuilding, queue another rebuild
135
+ if (isRebuilding) {
136
+ rebuildQueued = true;
137
+ return;
138
+ }
211
139
 
212
- try {
213
- const relativePath = path.relative(juxSource, filePath);
214
- const destPath = path.join(distDir, relativePath);
215
- const destDir = path.dirname(destPath);
140
+ isRebuilding = true;
141
+ console.log(`\n📝 File changed: ${filename}`);
216
142
 
217
- if (!fs.existsSync(destDir)) {
218
- fs.mkdirSync(destDir, { recursive: true });
219
- }
143
+ // Rebuild the entire bundle
144
+ const success = await fullRebuild(juxSource, distDir);
220
145
 
221
- fs.copyFileSync(filePath, destPath);
222
- console.log(` ✓ Copied to: ${path.relative(process.cwd(), destPath)}`);
146
+ isRebuilding = false;
223
147
 
224
- wsClients.forEach(client => {
225
- if (client.readyState === 1) {
226
- client.send(JSON.stringify({ type: 'reload' }));
227
- }
228
- });
148
+ // Notify all WebSocket clients to reload
149
+ if (success && wsClients && wsClients.length > 0) {
150
+ console.log(`🔌 Notifying ${wsClients.length} client(s) to reload`);
229
151
 
230
- console.log(` 🔄 Browser reloaded\n`);
231
- } catch (err) {
232
- console.error(` ❌ JS copy failed:`, err.message);
233
- }
152
+ // Add small delay to ensure file is fully written
153
+ setTimeout(() => {
154
+ wsClients.forEach(client => {
155
+ if (client.readyState === 1) { // OPEN
156
+ client.send(JSON.stringify({ type: 'reload' }));
234
157
  }
235
158
  });
236
- });
237
- } catch (err) {
238
- console.error(` ⚠️ Failed to watch ${dir}:`, err.message);
159
+ }, 100);
239
160
  }
240
- }
241
161
 
242
- // Start watching
243
- watchRecursive(juxSource);
162
+ // Process queued rebuild if needed
163
+ if (rebuildQueued) {
164
+ rebuildQueued = false;
165
+ console.log('🔄 Processing queued rebuild...');
166
+ setTimeout(() => watcher.emit('change', 'change', filename), 500);
167
+ }
168
+ });
169
+
170
+ return watcher;
244
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
@@ -1,94 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- /**
5
- * Default JUX configuration
6
- */
7
- export const defaultConfig = {
8
- // Source directory for .jux files
9
- sourceDir: 'jux',
10
-
11
- // Output directory for built files
12
- distDir: 'jux-dist',
13
-
14
- // Dev server ports
15
- ports: {
16
- http: 3000,
17
- ws: 3001
18
- },
19
-
20
- // Build options
21
- build: {
22
- minify: false,
23
- sourcemap: true
24
- },
25
-
26
- // Bootstrap functions (run before app starts)
27
- bootstrap: []
28
- };
29
-
30
- /**
31
- * Load juxconfig.js from project root
32
- * @param {string} projectRoot - Project root directory
33
- * @returns {object} Merged configuration
34
- */
35
- export async function loadConfig(projectRoot) {
36
- const configPath = path.join(projectRoot, 'juxconfig.js');
37
-
38
- if (!fs.existsSync(configPath)) {
39
- console.log('ℹ️ No juxconfig.js found, using defaults');
40
- return defaultConfig;
41
- }
42
-
43
- try {
44
- console.log(`📋 Loading config from: ${configPath}`);
45
-
46
- // Dynamic import for ES modules
47
- const configUrl = `file://${configPath}`;
48
- const { default: userConfig } = await import(configUrl);
49
-
50
- // Merge with defaults
51
- const config = {
52
- ...defaultConfig,
53
- ...userConfig,
54
- ports: {
55
- ...defaultConfig.ports,
56
- ...(userConfig.ports || {})
57
- },
58
- build: {
59
- ...defaultConfig.build,
60
- ...(userConfig.build || {})
61
- }
62
- };
63
-
64
- console.log(` ✓ Config loaded`);
65
- console.log(` Source: ${config.sourceDir}`);
66
- console.log(` Output: ${config.distDir}\n`);
67
-
68
- return config;
69
- } catch (err) {
70
- console.warn(`⚠️ Failed to load juxconfig.js:`, err.message);
71
- console.warn(` Using default configuration\n`);
72
- return defaultConfig;
73
- }
74
- }
75
-
76
- /**
77
- * Run bootstrap functions from config
78
- * @param {Array<Function>} bootstrapFns - Array of bootstrap functions
79
- */
80
- export async function runBootstrap(bootstrapFns) {
81
- if (!bootstrapFns || bootstrapFns.length === 0) return;
82
-
83
- console.log(`🚀 Running ${bootstrapFns.length} bootstrap function(s)...`);
84
-
85
- for (const fn of bootstrapFns) {
86
- try {
87
- await fn();
88
- } catch (err) {
89
- console.error(` ❌ Bootstrap function failed:`, err.message);
90
- }
91
- }
92
-
93
- console.log(` ✓ Bootstrap complete\n`);
94
- }