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.
- package/bin/cli.js +66 -307
- package/create/index.jux +79 -90
- package/create/layout.jux +38 -18
- package/lib/componentsv2/element/component.js +23 -0
- package/lib/componentsv2/element/component.js.map +1 -0
- package/lib/componentsv2/element/component.ts +32 -0
- package/lib/componentsv2/element/engine.js +66 -0
- package/lib/componentsv2/element/engine.js.map +1 -0
- package/lib/componentsv2/element/engine.ts +88 -0
- package/lib/componentsv2/element/skin.js +53 -0
- package/lib/componentsv2/element/skin.js.map +1 -0
- package/lib/componentsv2/element/skin.ts +64 -0
- package/lib/componentsv2/element/structure.css +16 -0
- package/lib/componentsv2/index.js +3 -2
- package/lib/componentsv2/index.js.map +1 -1
- package/lib/componentsv2/index.ts +4 -6
- package/lib/componentsv2/tools/Scaffold.js +0 -16
- package/machinery/compiler.js +103 -739
- package/machinery/server.js +16 -112
- package/machinery/watcher.js +16 -106
- package/package.json +1 -1
- package/create/all.jux +0 -343
package/machinery/server.js
CHANGED
|
@@ -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';
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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(`🚀
|
|
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
|
-
|
|
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
|
|
package/machinery/watcher.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
}
|