juxscript 1.0.67 → 1.0.69

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.
@@ -4,11 +4,10 @@ import path from 'path';
4
4
  import fs from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { startWatcher } from './watcher.js';
7
- import { verifyRuntime } from './verifier.js'; // ✅ Import Verifier
7
+ import { verifyRuntime } from './verifier.js';
8
8
  import { WebSocketServer } from 'ws';
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
11
 
13
12
  /**
14
13
  * Try to start a server on a port, with fallback to next port if busy
@@ -17,160 +16,65 @@ const __dirname = path.dirname(__filename);
17
16
  async function tryPort(startPort, maxAttempts = 5, reservedPorts = []) {
18
17
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
19
18
  const port = startPort + attempt;
20
-
21
- // Skip if this port is already reserved
22
- if (reservedPorts.includes(port)) {
23
- continue;
24
- }
25
-
19
+ if (reservedPorts.includes(port)) continue;
26
20
  try {
27
- // Test if port is available
28
21
  await new Promise((resolve, reject) => {
29
22
  const testServer = http.createServer();
30
23
  testServer.once('error', reject);
31
- testServer.once('listening', () => {
32
- testServer.close();
33
- resolve(port);
34
- });
24
+ testServer.once('listening', () => { testServer.close(); resolve(port); });
35
25
  testServer.listen(port);
36
26
  });
37
27
  return port;
38
- } catch (err) {
39
- if (err.code === 'EADDRINUSE') {
40
- if (attempt < maxAttempts - 1) {
41
- console.log(` Port ${port} in use, trying ${port + 1}...`);
42
- }
43
- continue;
44
- }
45
- throw err;
46
- }
28
+ } catch (err) { if (err.code !== 'EADDRINUSE') throw err; }
47
29
  }
48
- throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${startPort}`);
30
+ throw new Error(`Could not find available port`);
49
31
  }
50
32
 
51
- // ✅ FIX: Added config parameter to serve function signature
52
33
  async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist', config = {}) {
53
34
  const app = express();
54
35
  const absoluteDistDir = path.resolve(distDir);
55
36
  const projectRoot = path.resolve('.');
56
37
 
57
38
  app.use(express.json());
39
+ if (!fs.existsSync(absoluteDistDir)) { process.exit(1); }
58
40
 
59
- if (!fs.existsSync(absoluteDistDir)) {
60
- console.error(`❌ Error: ${path.basename(distDir)}/ directory not found at ${absoluteDistDir}`);
61
- console.error(' Run: npx jux build\n');
62
- process.exit(1);
63
- }
64
-
65
- // Strong cache prevention
66
- app.use((req, res, next) => {
67
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
68
- res.setHeader('Pragma', 'no-cache');
69
- res.setHeader('Expires', '0');
70
- res.setHeader('ETag', 'W/"' + Date.now() + '"');
71
- next();
72
- });
73
-
74
- // ✅ ADD: Explicit MIME type for JavaScript files
75
41
  app.use((req, res, next) => {
76
- if (req.path.endsWith('.js')) {
77
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
78
- }
42
+ res.setHeader('Cache-Control', 'no-store');
43
+ if (req.path.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
79
44
  next();
80
45
  });
81
-
82
- // ✅ SILENCE FAVICON: Return 204 (No Content) to stop browser 404 errors
83
46
  app.get('/favicon.ico', (req, res) => res.status(204).end());
84
-
85
- // Serve static files
86
47
  app.use(express.static(absoluteDistDir));
87
-
88
- // FIX: Only apply SPA fallback to routes, not static files
89
- app.use((req, res, next) => {
90
- // Skip SPA fallback for file extensions (static assets)
91
- if (/\.[a-zA-Z0-9]+$/.test(req.path)) {
92
- return res.status(404).send('File not found');
93
- }
94
-
95
- // SPA fallback for routes only
96
- if (req.accepts('html')) {
97
- const indexPath = path.join(absoluteDistDir, 'index.html');
98
- if (fs.existsSync(indexPath)) {
99
- res.sendFile(indexPath);
100
- } else {
101
- res.status(404).send('index.html not found. Run `npx jux build` first.');
102
- }
103
- } else {
104
- res.status(404).send('Not found');
105
- }
48
+ app.use((req, res) => {
49
+ if (/\.[a-zA-Z0-9]+$/.test(req.path)) return res.status(404).send('Not found');
50
+ if (req.accepts('html')) res.sendFile(path.join(absoluteDistDir, 'index.html'));
51
+ else res.status(404).send('Not found');
106
52
  });
107
53
 
108
- // Find available ports sequentially to avoid collision
109
- console.log('🔍 Finding available ports...');
110
-
111
- // First, find HTTP port
112
54
  const availableHttpPort = await tryPort(httpPort);
113
-
114
- // Then find WS port, excluding the HTTP port we just allocated
115
55
  const availableWsPort = await tryPort(wsPort, 5, [availableHttpPort]);
116
-
117
- // Create HTTP server
118
56
  const server = http.createServer(app);
119
-
120
- // WebSocket server for hot reload
121
57
  const wss = new WebSocketServer({ port: availableWsPort });
122
58
  const clients = [];
123
59
 
124
60
  wss.on('connection', (ws) => {
125
61
  clients.push(ws);
126
- console.log('🔌 WebSocket client connected');
127
-
128
- ws.on('close', () => {
129
- console.log('🔌 WebSocket client disconnected');
130
- const index = clients.indexOf(ws);
131
- if (index > -1) clients.splice(index, 1);
132
- });
133
-
134
- ws.on('error', (error) => {
135
- console.error('WebSocket error:', error);
136
- });
62
+ ws.on('close', () => { const i = clients.indexOf(ws); if (i > -1) clients.splice(i, 1); });
137
63
  });
138
64
 
139
- console.log(`🔌 WebSocket server running at ws://localhost:${availableWsPort}`);
140
-
141
- // Start HTTP server
142
65
  server.listen(availableHttpPort, async () => {
143
- console.log(`🚀 JUX dev server running at http://localhost:${availableHttpPort}`);
144
- console.log(` Serving: ${absoluteDistDir}`);
145
- // console.log(` Press Ctrl+C to stop\n`); removed to group logs
146
-
147
- // ✅ NEW: Post-Startup Runtime Verification
148
- // Detects "Served HTML as JS" errors immediately
66
+ console.log(`🚀 Server: http://localhost:${availableHttpPort}`);
149
67
  const isHealthy = await verifyRuntime(availableHttpPort);
150
- if (!isHealthy) {
151
- console.error('🛑 Server failed runtime verification. Shutting down due to critical errors.');
152
- process.exit(1);
153
- }
68
+ if (!isHealthy) { console.error('🛑 Runtime Check Failed.'); process.exit(1); }
154
69
  });
155
70
 
156
- // Start file watcher
157
71
  const sourceName = config?.directories?.source || 'jux';
158
72
  const juxSource = path.join(projectRoot, sourceName);
159
-
160
73
  if (fs.existsSync(juxSource)) {
161
- console.log(`👀 Watching: ${juxSource}\n`);
162
- // ✅ Pass availableWsPort so we can regenerate index.html with correct port on rebuild
163
74
  startWatcher(juxSource, absoluteDistDir, clients, config, availableWsPort);
164
75
  }
165
76
 
166
- // Graceful shutdown
167
- const shutdown = async () => {
168
- console.log('\n\n👋 Shutting down server...');
169
- wss.close();
170
- server.close();
171
- process.exit(0);
172
- };
173
-
77
+ const shutdown = async () => { wss.close(); server.close(); process.exit(0); };
174
78
  process.on('SIGINT', shutdown);
175
79
  process.on('SIGTERM', shutdown);
176
80
 
@@ -1,11 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import {
4
- copyLibToOutput,
5
- bundleJuxFilesToRouter,
6
- generateIndexHtml
7
- } from './compiler.js';
8
- import { verifyStaticBuild } from './verifier.js'; // ✅ Import Verifier
3
+ import { bundleJuxFilesToRouter, generateIndexHtml } from './compiler.js';
4
+ import { verifyStaticBuild } from './verifier.js';
9
5
 
10
6
  let isRebuilding = false;
11
7
  let rebuildQueued = false;
@@ -15,22 +11,14 @@ let rebuildQueued = false;
15
11
  */
16
12
  function findJuxFiles(dir, fileList = []) {
17
13
  if (!fs.existsSync(dir)) return fileList;
18
-
19
- const files = fs.readdirSync(dir);
20
-
21
- files.forEach(file => {
14
+ fs.readdirSync(dir).forEach(file => {
22
15
  const filePath = path.join(dir, file);
23
- const stat = fs.statSync(filePath);
24
-
25
- if (stat.isDirectory()) {
26
- if (file !== 'node_modules' && file !== 'jux-dist' && file !== '.git' && file !== 'server') {
27
- findJuxFiles(filePath, fileList);
28
- }
16
+ if (fs.statSync(filePath).isDirectory()) {
17
+ if (!['node_modules', 'jux-dist', '.git', 'server'].includes(file)) findJuxFiles(filePath, fileList);
29
18
  } else if (file.endsWith('.jux')) {
30
19
  fileList.push(filePath);
31
20
  }
32
21
  });
33
-
34
22
  return fileList;
35
23
  }
36
24
 
@@ -38,41 +26,13 @@ function findJuxFiles(dir, fileList = []) {
38
26
  * Full rebuild of the entire bundle
39
27
  */
40
28
  async function fullRebuild(juxSource, distDir, config, wsPort) {
41
- const startTime = performance.now(); // ✅ Start timing
42
- console.log('\n🔄 Rebuilding bundle...');
43
-
44
29
  try {
45
- // Find all .jux files
46
- const projectJuxFiles = findJuxFiles(juxSource);
47
- console.log(` Found ${projectJuxFiles.length} .jux file(s)`);
48
- // ✅ Time the bundling step
49
- const bundleStartTime = performance.now();
50
- // Bundle all files
51
-
52
- // ✅ Return bundleResult with vendor info
53
- const bundleResult = await bundleJuxFilesToRouter(juxSource, distDir, {
54
- routePrefix: '',
55
- config
56
- });
57
-
58
- const bundleTime = performance.now() - bundleStartTime;
59
- // ✅ Generate unified index.html (INJECT HOT RELOAD SCRIPT)
60
- generateIndexHtml(distDir, bundleResult, {
61
- isDev: true,
62
- wsPort: wsPort || 3001
63
- });
64
-
65
- // ✅ NEW: Verify Rebuild Artifacts
66
- if (!verifyStaticBuild(distDir)) {
67
- console.error('⚠️ Rebuild failed validation! Skipped client reload to preserve state.');
68
- return false; // Return false prevents WebSocket reload message
69
- }
70
-
30
+ const bundleResult = await bundleJuxFilesToRouter(juxSource, distDir, { routePrefix: '', config });
31
+ generateIndexHtml(distDir, bundleResult, { isDev: true, wsPort: wsPort || 3001 });
32
+ if (!verifyStaticBuild(distDir)) return false;
71
33
  return true;
72
-
73
34
  } catch (err) {
74
- const failTime = performance.now() - startTime;
75
- console.error(`❌ Rebuild failed after ${failTime.toFixed(0)}ms:`, err.message);
35
+ console.error(`❌ Rebuild failed:`, err.message);
76
36
  return false;
77
37
  }
78
38
  }
@@ -81,80 +41,30 @@ async function fullRebuild(juxSource, distDir, config, wsPort) {
81
41
  * Start watching for file changes and rebuild on change
82
42
  */
83
43
  export function startWatcher(juxSource, distDir, wsClients, config, wsPort) {
84
- console.log(`👀 Watching: ${juxSource}`);
44
+ console.log(`👀 Watching for changes...`);
85
45
 
86
46
  const watcher = fs.watch(juxSource, { recursive: true }, async (eventType, filename) => {
87
-
88
- // Debounce: If already rebuilding, queue another rebuild
89
- if (isRebuilding) {
90
- rebuildQueued = true;
91
- return;
92
- }
93
-
47
+ if (isRebuilding) { rebuildQueued = true; return; }
94
48
  isRebuilding = true;
95
- console.log(`\n📝 File changed: ${filename}`);
96
49
 
97
- // Rebuild the entire bundle (PASS PORT)
50
+ process.stdout.write(`\n📝 Change: ${filename} -> Rebuilding...`);
98
51
  const success = await fullRebuild(juxSource, distDir, config, wsPort);
99
-
100
52
  isRebuilding = false;
101
53
 
102
- // Notify all WebSocket clients to reload
103
54
  if (success && wsClients && wsClients.length > 0) {
104
- console.log(`🔌 Notifying ${wsClients.length} client(s) to reload`);
105
-
106
- // ✅ Add small delay to ensure file is fully written
55
+ console.log(' Reloaded.');
107
56
  setTimeout(() => {
108
- wsClients.forEach(client => {
109
- if (client.readyState === 1) { // OPEN
110
- client.send(JSON.stringify({ type: 'reload' }));
111
- }
112
- });
57
+ wsClients.forEach(client => { if (client.readyState === 1) client.send(JSON.stringify({ type: 'reload' })); });
113
58
  }, 100);
59
+ } else if (!success) {
60
+ console.log(' ❌ Failed.');
114
61
  }
115
62
 
116
- // Process queued rebuild if needed
117
63
  if (rebuildQueued) {
118
64
  rebuildQueued = false;
119
- console.log('🔄 Processing queued rebuild...');
120
65
  setTimeout(() => watcher.emit('change', 'change', filename), 500);
121
66
  }
122
67
  });
123
68
 
124
69
  return watcher;
125
- }
126
-
127
- async function rebuildOnChange(filePath) {
128
- const rebuildStart = performance.now();
129
- console.log(`\n📝 File changed: ${path.relative(projectRoot, filePath)}\n`);
130
- console.log('🔄 Rebuilding bundle...');
131
-
132
- try {
133
- // ✅ FIX: Capture bundleResult from bundleJuxFilesToRouter
134
- const bundleResult = await bundleJuxFilesToRouter(projectRoot, distDir, {
135
- routePrefix: ''
136
- });
137
-
138
- // ✅ FIX: Pass bundleResult to generateIndexHtml
139
- generateIndexHtml(distDir, bundleResult);
140
-
141
- const rebuildTime = performance.now() - rebuildStart;
142
-
143
- console.log(`\n⏱️ Rebuild Performance:`);
144
- console.log(` Bundle generation: ${rebuildTime.toFixed(0)}ms`);
145
- console.log(` Route/index.html: ${(performance.now() - rebuildStart - rebuildTime).toFixed(0)}ms`);
146
- console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
147
- console.log(` Total rebuild: ${(performance.now() - rebuildStart).toFixed(0)}ms`);
148
-
149
- if (rebuildTime < 100) {
150
- console.log(` ✅ Lightning fast! ⚡`);
151
- }
152
-
153
- // ✅ FIX: Notify clients with bundleResult
154
- notifyClients({ type: 'reload', bundleResult });
155
-
156
- } catch (error) {
157
- console.error('❌ Rebuild failed:', error.message);
158
- notifyClients({ type: 'error', message: error.message });
159
- }
160
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",