juxscript 1.0.62 → 1.0.63
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 +161 -293
- package/docs/v2comps/HEADLESS.md +83 -0
- package/docs/v2comps/ISOMORPHISM.md +10 -0
- package/juxconfig.example.js +63 -58
- package/lib/componentsv2/base/BaseEngine.js +258 -0
- package/lib/componentsv2/base/BaseEngine.js.map +1 -0
- package/lib/componentsv2/base/BaseEngine.ts +303 -0
- package/lib/componentsv2/base/BaseSkin.js +108 -0
- package/lib/componentsv2/base/BaseSkin.js.map +1 -0
- package/lib/componentsv2/base/BaseSkin.ts +137 -0
- package/lib/componentsv2/base/GlobalBus.js +56 -0
- package/lib/componentsv2/base/GlobalBus.js.map +1 -0
- package/lib/componentsv2/base/GlobalBus.ts +60 -0
- package/lib/componentsv2/base/State.js +68 -0
- package/lib/componentsv2/base/State.js.map +1 -0
- package/lib/componentsv2/base/State.ts +62 -0
- package/lib/componentsv2/grid/component.js +41 -0
- package/lib/componentsv2/grid/component.js.map +1 -0
- package/lib/componentsv2/grid/component.ts +67 -0
- package/lib/componentsv2/grid/engine.js +73 -0
- package/lib/componentsv2/grid/engine.js.map +1 -0
- package/lib/componentsv2/grid/engine.ts +110 -0
- package/lib/componentsv2/grid/skin.js +95 -0
- package/lib/componentsv2/grid/skin.js.map +1 -0
- package/lib/componentsv2/grid/skin.ts +105 -0
- package/lib/componentsv2/grid/structure.css +58 -0
- package/lib/componentsv2/index.js +218 -0
- package/lib/componentsv2/index.js.map +1 -0
- package/lib/componentsv2/index.ts +253 -0
- package/lib/componentsv2/input/component.js +21 -0
- package/lib/componentsv2/input/component.js.map +1 -0
- package/lib/componentsv2/input/component.ts +28 -0
- package/lib/componentsv2/input/engine.js +50 -0
- package/lib/componentsv2/input/engine.js.map +1 -0
- package/lib/componentsv2/input/engine.ts +76 -0
- package/lib/componentsv2/input/skin.js +91 -0
- package/lib/componentsv2/input/skin.js.map +1 -0
- package/lib/componentsv2/input/skin.ts +91 -0
- package/lib/componentsv2/input/structure.css +47 -0
- package/lib/componentsv2/list/component.js +83 -0
- package/lib/componentsv2/list/component.js.map +1 -0
- package/lib/componentsv2/list/component.ts +97 -0
- package/lib/componentsv2/list/engine.js +261 -0
- package/lib/componentsv2/list/engine.js.map +1 -0
- package/lib/componentsv2/list/engine.ts +345 -0
- package/lib/componentsv2/list/skin.js +343 -0
- package/lib/componentsv2/list/skin.js.map +1 -0
- package/lib/componentsv2/list/skin.ts +367 -0
- package/lib/componentsv2/list/structure.css +359 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
- package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
- package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
- package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
- package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
- package/lib/componentsv2/tools/CreateSkin.js +62 -0
- package/lib/componentsv2/tools/DocSpam.js +134 -0
- package/lib/componentsv2/tools/FluencyAudit.js +141 -0
- package/lib/componentsv2/tools/OptionsAudit.js +177 -0
- package/lib/componentsv2/tools/Scaffold.js +140 -0
- package/lib/utils/fetch.js +428 -0
- package/lib/utils/fetch.js.map +1 -0
- package/machinery/build.js +2 -1
- package/machinery/compiler.js +200 -37
- package/machinery/config.js +93 -6
- package/machinery/diagnose.js +72 -0
- package/machinery/jux-module-pattern.md +118 -0
- package/machinery/server.js +23 -7
- package/machinery/verifier.js +143 -0
- package/machinery/watcher.js +53 -64
- package/package.json +11 -2
- package/lib/components/alert.ts +0 -200
- package/lib/components/app.ts +0 -258
- package/lib/components/badge.ts +0 -101
- package/lib/components/base/BaseComponent.ts +0 -417
- package/lib/components/base/FormInput.ts +0 -227
- package/lib/components/button.ts +0 -178
- package/lib/components/card.ts +0 -173
- package/lib/components/chart.ts +0 -231
- package/lib/components/checkbox.ts +0 -242
- package/lib/components/code.ts +0 -123
- package/lib/components/container.ts +0 -140
- package/lib/components/data.ts +0 -135
- package/lib/components/datepicker.ts +0 -234
- package/lib/components/dialog.ts +0 -172
- package/lib/components/divider.ts +0 -100
- package/lib/components/dropdown.ts +0 -186
- package/lib/components/element.ts +0 -267
- package/lib/components/error-handler.ts +0 -285
- package/lib/components/fileupload.ts +0 -309
- package/lib/components/grid.ts +0 -291
- package/lib/components/guard.ts +0 -92
- package/lib/components/heading.ts +0 -96
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.ts +0 -224
- package/lib/components/icon.ts +0 -160
- package/lib/components/icons.ts +0 -175
- package/lib/components/include.ts +0 -440
- package/lib/components/input.ts +0 -457
- package/lib/components/list.ts +0 -419
- package/lib/components/loading.ts +0 -100
- package/lib/components/menu.ts +0 -260
- package/lib/components/modal.ts +0 -239
- package/lib/components/nav.ts +0 -257
- package/lib/components/paragraph.ts +0 -97
- package/lib/components/progress.ts +0 -139
- package/lib/components/radio.ts +0 -278
- package/lib/components/req.ts +0 -302
- package/lib/components/script.ts +0 -43
- package/lib/components/select.ts +0 -252
- package/lib/components/sidebar.ts +0 -167
- package/lib/components/style.ts +0 -43
- package/lib/components/switch.ts +0 -246
- package/lib/components/table.ts +0 -1249
- package/lib/components/tabs.ts +0 -250
- package/lib/components/theme-toggle.ts +0 -300
- package/lib/components/token-calculator.ts +0 -313
- package/lib/components/tooltip.ts +0 -144
- package/lib/components/view.ts +0 -190
- package/lib/components/write.ts +0 -272
- package/lib/jux.ts +0 -365
- package/lib/layouts/default.css +0 -260
- package/lib/layouts/figma.css +0 -334
- package/lib/reactivity/state.ts +0 -78
- package/machinery/bundleAssets.js +0 -0
- package/machinery/bundleJux.js +0 -0
- package/machinery/bundleVendors.js +0 -0
- package/presets/default/all.jux +0 -343
- package/presets/default/index.jux +0 -90
- package/presets/default/layout.jux +0 -57
- package/presets/default/style.css +0 -1612
package/machinery/server.js
CHANGED
|
@@ -4,6 +4,7 @@ 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
8
|
import { WebSocketServer } from 'ws';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -47,7 +48,8 @@ async function tryPort(startPort, maxAttempts = 5, reservedPorts = []) {
|
|
|
47
48
|
throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${startPort}`);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// ✅ FIX: Added config parameter to serve function signature
|
|
52
|
+
async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist', config = {}) {
|
|
51
53
|
const app = express();
|
|
52
54
|
const absoluteDistDir = path.resolve(distDir);
|
|
53
55
|
const projectRoot = path.resolve('.');
|
|
@@ -77,6 +79,9 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
|
|
|
77
79
|
next();
|
|
78
80
|
});
|
|
79
81
|
|
|
82
|
+
// ✅ SILENCE FAVICON: Return 204 (No Content) to stop browser 404 errors
|
|
83
|
+
app.get('/favicon.ico', (req, res) => res.status(204).end());
|
|
84
|
+
|
|
80
85
|
// Serve static files
|
|
81
86
|
app.use(express.static(absoluteDistDir));
|
|
82
87
|
|
|
@@ -134,17 +139,28 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
|
|
|
134
139
|
console.log(`🔌 WebSocket server running at ws://localhost:${availableWsPort}`);
|
|
135
140
|
|
|
136
141
|
// Start HTTP server
|
|
137
|
-
server.listen(availableHttpPort, () => {
|
|
142
|
+
server.listen(availableHttpPort, async () => {
|
|
138
143
|
console.log(`🚀 JUX dev server running at http://localhost:${availableHttpPort}`);
|
|
139
144
|
console.log(` Serving: ${absoluteDistDir}`);
|
|
140
|
-
console.log(` Press Ctrl+C to stop\n`);
|
|
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
|
|
149
|
+
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
|
+
}
|
|
141
154
|
});
|
|
142
155
|
|
|
143
156
|
// Start file watcher
|
|
144
|
-
const
|
|
157
|
+
const sourceName = config?.directories?.source || 'jux';
|
|
158
|
+
const juxSource = path.join(projectRoot, sourceName);
|
|
159
|
+
|
|
145
160
|
if (fs.existsSync(juxSource)) {
|
|
146
161
|
console.log(`👀 Watching: ${juxSource}\n`);
|
|
147
|
-
|
|
162
|
+
// ✅ Pass availableWsPort so we can regenerate index.html with correct port on rebuild
|
|
163
|
+
startWatcher(juxSource, absoluteDistDir, clients, config, availableWsPort);
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
// Graceful shutdown
|
|
@@ -161,6 +177,6 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
|
|
|
161
177
|
return { server, httpPort: availableHttpPort, wsPort: availableWsPort };
|
|
162
178
|
}
|
|
163
179
|
|
|
164
|
-
export async function start(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
|
|
165
|
-
return serve(httpPort, wsPort, distDir);
|
|
180
|
+
export async function start(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist', config = {}) {
|
|
181
|
+
return serve(httpPort, wsPort, distDir, config);
|
|
166
182
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import * as acorn from 'acorn'; // Needed to parse main.js
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks the static `distDir` for critical files and valid import map references.
|
|
8
|
+
* Returns true if healthy, false if broken.
|
|
9
|
+
*/
|
|
10
|
+
export function verifyStaticBuild(distDir) {
|
|
11
|
+
// console.log('🕵️ Verifying build integrity...');
|
|
12
|
+
const errors = [];
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(distDir)) {
|
|
15
|
+
console.error('❌ Dist dir does not exist');
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 1. Critical Files Existence
|
|
20
|
+
const critical = ['index.html', 'main.js', 'lib/jux.js'];
|
|
21
|
+
for (const f of critical) {
|
|
22
|
+
if (!fs.existsSync(path.join(distDir, f))) {
|
|
23
|
+
errors.push(`Missing critical artifact: ${f}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Import Map Integrity Check
|
|
28
|
+
let importMap = {};
|
|
29
|
+
try {
|
|
30
|
+
const indexPath = path.join(distDir, 'index.html');
|
|
31
|
+
if (fs.existsSync(indexPath)) {
|
|
32
|
+
const html = fs.readFileSync(indexPath, 'utf8');
|
|
33
|
+
const match = html.match(/<script type="importmap">([\s\S]*?)<\/script>/);
|
|
34
|
+
|
|
35
|
+
if (match && match[1]) {
|
|
36
|
+
importMap = JSON.parse(match[1]).imports || {};
|
|
37
|
+
Object.entries(importMap).forEach(([key, url]) => {
|
|
38
|
+
// Skip folders (trailing slash) and CDN links
|
|
39
|
+
if (typeof url === 'string' && !url.endsWith('/') && !url.startsWith('http')) {
|
|
40
|
+
const fsPath = url.replace(/^\//, '').split('?')[0];
|
|
41
|
+
const fullPath = path.join(distDir, fsPath);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(fullPath)) {
|
|
44
|
+
errors.push(`Broken Import Map: "${key}" -> ${url} (File not found)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
errors.push(`Import Map Verification Error: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Bundle Import Verification (Detect Ghost Dependencies)
|
|
55
|
+
const mainJsPath = path.join(distDir, 'main.js');
|
|
56
|
+
if (fs.existsSync(mainJsPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const code = fs.readFileSync(mainJsPath, 'utf8');
|
|
59
|
+
const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
60
|
+
|
|
61
|
+
ast.body.forEach(node => {
|
|
62
|
+
if (node.type === 'ImportDeclaration') {
|
|
63
|
+
const source = node.source.value;
|
|
64
|
+
|
|
65
|
+
// Allow internal relative paths if they exist
|
|
66
|
+
if (source.startsWith('/') || source.startsWith('.')) {
|
|
67
|
+
// basic existence check (simplified)
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if bare specifier exists in Import Map
|
|
72
|
+
// e.g. import axios from 'axios' -> must be in map
|
|
73
|
+
if (!importMap[source] && !importMap[source + '/']) {
|
|
74
|
+
errors.push(`Ghost Dependency: '${source}' is imported in main.js but missing from Import Map.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
} catch (e) {
|
|
79
|
+
errors.push(`Failed to parse main.js for verification: ${e.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (errors.length > 0) {
|
|
84
|
+
console.error(`\n❌ BUILD VERIFICATION FAILED (${errors.length} errors):`);
|
|
85
|
+
errors.forEach(e => console.error(` - ${e}`));
|
|
86
|
+
console.log('');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// console.log(' ✓ Static verification passed');
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pings the running server to ensure correct MIME types and Route availability.
|
|
96
|
+
* Detects "serving JS as HTML" (MIME type mismatch) errors.
|
|
97
|
+
*/
|
|
98
|
+
export function verifyRuntime(port) {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
console.log('🩺 Performing runtime diagnostics...');
|
|
101
|
+
const errors = [];
|
|
102
|
+
|
|
103
|
+
// Helper to check a single URL
|
|
104
|
+
const check = (urlPath, expectedMime) => new Promise(r => {
|
|
105
|
+
const req = http.get(`http://localhost:${port}${urlPath}`, res => {
|
|
106
|
+
// Check Status
|
|
107
|
+
if (res.statusCode !== 200 && res.statusCode !== 304) {
|
|
108
|
+
errors.push(`GET ${urlPath} : Status ${res.statusCode} (Expected 200)`);
|
|
109
|
+
} else {
|
|
110
|
+
// Check MIME type
|
|
111
|
+
const ct = res.headers['content-type'] || '';
|
|
112
|
+
if (expectedMime && !ct.includes(expectedMime)) {
|
|
113
|
+
errors.push(`GET ${urlPath} : Bad MIME type. Got "${ct}", expected "${expectedMime}". (Did you get index.html fallback?)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
res.resume(); // Consume stream
|
|
117
|
+
r();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
req.on('error', e => {
|
|
121
|
+
errors.push(`GET ${urlPath} : Connection failed - ${e.message}`);
|
|
122
|
+
r();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Run checks in parallel
|
|
127
|
+
Promise.all([
|
|
128
|
+
check('/', 'text/html'), // Route should be HTML
|
|
129
|
+
check('/main.js', 'application/javascript'), // Bundle must be JS
|
|
130
|
+
check('/lib/jux.js', 'application/javascript') // Library must be JS
|
|
131
|
+
]).then(() => {
|
|
132
|
+
if (errors.length > 0) {
|
|
133
|
+
console.error(`\n💥 RUNTIME VERIFICATION FAILED (` + errors.length + ` errors):`);
|
|
134
|
+
errors.forEach(e => console.error(` - ${e}`));
|
|
135
|
+
console.log('');
|
|
136
|
+
resolve(false);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(' ✓ Runtime health healthy (Routes & MIME types OK)\n');
|
|
139
|
+
resolve(true);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
package/machinery/watcher.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
bundleJuxFilesToRouter,
|
|
6
6
|
generateIndexHtml
|
|
7
7
|
} from './compiler.js';
|
|
8
|
+
import { verifyStaticBuild } from './verifier.js'; // ✅ Import Verifier
|
|
8
9
|
|
|
9
10
|
let isRebuilding = false;
|
|
10
11
|
let rebuildQueued = false;
|
|
@@ -36,7 +37,7 @@ function findJuxFiles(dir, fileList = []) {
|
|
|
36
37
|
/**
|
|
37
38
|
* Full rebuild of the entire bundle
|
|
38
39
|
*/
|
|
39
|
-
async function fullRebuild(juxSource, distDir) {
|
|
40
|
+
async function fullRebuild(juxSource, distDir, config, wsPort) {
|
|
40
41
|
const startTime = performance.now(); // ✅ Start timing
|
|
41
42
|
console.log('\n🔄 Rebuilding bundle...');
|
|
42
43
|
|
|
@@ -44,68 +45,29 @@ async function fullRebuild(juxSource, distDir) {
|
|
|
44
45
|
// Find all .jux files
|
|
45
46
|
const projectJuxFiles = findJuxFiles(juxSource);
|
|
46
47
|
console.log(` Found ${projectJuxFiles.length} .jux file(s)`);
|
|
47
|
-
|
|
48
48
|
// ✅ Time the bundling step
|
|
49
49
|
const bundleStartTime = performance.now();
|
|
50
|
-
|
|
51
50
|
// Bundle all files
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
|
|
52
|
+
// ✅ Return bundleResult with vendor info
|
|
53
|
+
const bundleResult = await bundleJuxFilesToRouter(juxSource, distDir, {
|
|
54
|
+
routePrefix: '',
|
|
55
|
+
config
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
const bundleTime = performance.now() - bundleStartTime;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Generate routes for index.html
|
|
62
|
-
const routes = projectJuxFiles.map(juxFile => {
|
|
63
|
-
const relativePath = path.relative(juxSource, juxFile);
|
|
64
|
-
const parsedPath = path.parse(relativePath);
|
|
65
|
-
|
|
66
|
-
const rawFunctionName = parsedPath.dir
|
|
67
|
-
? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
|
|
68
|
-
: parsedPath.name;
|
|
69
|
-
|
|
70
|
-
const functionName = rawFunctionName
|
|
71
|
-
.replace(/[-_]/g, ' ')
|
|
72
|
-
.split(' ')
|
|
73
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
74
|
-
.join('');
|
|
75
|
-
|
|
76
|
-
const routePath = '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
path: routePath.replace(/\/+/g, '/'),
|
|
80
|
-
functionName
|
|
81
|
-
};
|
|
59
|
+
// ✅ Generate unified index.html (INJECT HOT RELOAD SCRIPT)
|
|
60
|
+
generateIndexHtml(distDir, bundleResult, {
|
|
61
|
+
isDev: true,
|
|
62
|
+
wsPort: wsPort || 3001
|
|
82
63
|
});
|
|
83
64
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const totalTime = performance.now() - startTime;
|
|
89
|
-
|
|
90
|
-
// ✅ Pretty-print timing breakdown
|
|
91
|
-
console.log(`\n⏱️ Rebuild Performance:`);
|
|
92
|
-
console.log(` Bundle generation: ${bundleTime.toFixed(0)}ms`);
|
|
93
|
-
console.log(` Route/index.html: ${routeTime.toFixed(0)}ms`);
|
|
94
|
-
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
95
|
-
console.log(` Total rebuild: ${totalTime.toFixed(0)}ms`);
|
|
96
|
-
|
|
97
|
-
// ✅ Color-coded performance indicator
|
|
98
|
-
if (totalTime < 100) {
|
|
99
|
-
console.log(` ✅ Lightning fast! ⚡`);
|
|
100
|
-
} else if (totalTime < 500) {
|
|
101
|
-
console.log(` ✅ Fast rebuild`);
|
|
102
|
-
} else if (totalTime < 1000) {
|
|
103
|
-
console.log(` ⚠️ Moderate speed`);
|
|
104
|
-
} else {
|
|
105
|
-
console.log(` 🐌 Slow rebuild (consider optimizing)`);
|
|
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
|
|
106
69
|
}
|
|
107
70
|
|
|
108
|
-
console.log(`✅ Bundle rebuilt: ${mainJsFilename}\n`);
|
|
109
71
|
return true;
|
|
110
72
|
|
|
111
73
|
} catch (err) {
|
|
@@ -118,18 +80,10 @@ async function fullRebuild(juxSource, distDir) {
|
|
|
118
80
|
/**
|
|
119
81
|
* Start watching for file changes and rebuild on change
|
|
120
82
|
*/
|
|
121
|
-
export function startWatcher(juxSource, distDir, wsClients) {
|
|
83
|
+
export function startWatcher(juxSource, distDir, wsClients, config, wsPort) {
|
|
122
84
|
console.log(`👀 Watching: ${juxSource}`);
|
|
123
85
|
|
|
124
86
|
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;
|
|
132
|
-
}
|
|
133
87
|
|
|
134
88
|
// Debounce: If already rebuilding, queue another rebuild
|
|
135
89
|
if (isRebuilding) {
|
|
@@ -140,8 +94,8 @@ export function startWatcher(juxSource, distDir, wsClients) {
|
|
|
140
94
|
isRebuilding = true;
|
|
141
95
|
console.log(`\n📝 File changed: ${filename}`);
|
|
142
96
|
|
|
143
|
-
// Rebuild the entire bundle
|
|
144
|
-
const success = await fullRebuild(juxSource, distDir);
|
|
97
|
+
// Rebuild the entire bundle (PASS PORT)
|
|
98
|
+
const success = await fullRebuild(juxSource, distDir, config, wsPort);
|
|
145
99
|
|
|
146
100
|
isRebuilding = false;
|
|
147
101
|
|
|
@@ -168,4 +122,39 @@ export function startWatcher(juxSource, distDir, wsClients) {
|
|
|
168
122
|
});
|
|
169
123
|
|
|
170
124
|
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
|
+
}
|
|
171
160
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.63",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "lib/jux.js",
|
|
@@ -49,16 +49,23 @@
|
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsc",
|
|
52
|
+
"scaffold": "node lib/componentsv2/tools/Scaffold.js",
|
|
53
|
+
"create-skin": "node lib/componentsv2/tools/CreateSkin.js",
|
|
54
|
+
"doc-spam": "node lib/componentsv2/tools/DocSpam.js",
|
|
55
|
+
"test-compile-verifier": "node machinery/diagnose.js",
|
|
56
|
+
"check-types": "tsc --noEmit",
|
|
52
57
|
"dev": "tsc --watch",
|
|
53
58
|
"prepublishOnly": "npm run build",
|
|
54
59
|
"analyze": "node scripts/analyze-components.js",
|
|
55
60
|
"check-styles": "node scripts/check-inline-styles.js",
|
|
56
61
|
"find-inject-styles": "node scripts/find-inject-styles.js",
|
|
57
|
-
"find-jux-classes": "node scripts/find-jux-classes.js"
|
|
62
|
+
"find-jux-classes": "node scripts/find-jux-classes.js",
|
|
63
|
+
"test": "node --loader ts-node/esm tests/dropdown-test.js"
|
|
58
64
|
},
|
|
59
65
|
"dependencies": {
|
|
60
66
|
"acorn": "^8.15.0",
|
|
61
67
|
"axios": "^1.6.0",
|
|
68
|
+
"better-sqlite3": "^12.6.2",
|
|
62
69
|
"chart.js": "^4.5.1",
|
|
63
70
|
"chokidar": "^3.5.3",
|
|
64
71
|
"esbuild": "^0.19.0",
|
|
@@ -68,10 +75,12 @@
|
|
|
68
75
|
},
|
|
69
76
|
"devDependencies": {
|
|
70
77
|
"@types/express": "^4.17.17",
|
|
78
|
+
"@types/jsdom": "^27.0.0",
|
|
71
79
|
"@types/node": "^20.0.0",
|
|
72
80
|
"@types/ws": "^8.5.5",
|
|
73
81
|
"acorn-walk": "^8.3.4",
|
|
74
82
|
"jsdom": "^27.4.0",
|
|
83
|
+
"ts-node": "^10.9.2",
|
|
75
84
|
"typescript": "^5.0.0"
|
|
76
85
|
}
|
|
77
86
|
}
|
package/lib/components/alert.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { BaseComponent } from './base/BaseComponent.js';
|
|
2
|
-
import { renderIcon } from './icons.js';
|
|
3
|
-
|
|
4
|
-
// Event definitions
|
|
5
|
-
const TRIGGER_EVENTS = [] as const;
|
|
6
|
-
const CALLBACK_EVENTS = ['dismiss'] as const;
|
|
7
|
-
|
|
8
|
-
export interface AlertOptions {
|
|
9
|
-
message?: string;
|
|
10
|
-
type?: 'info' | 'success' | 'warning' | 'error';
|
|
11
|
-
dismissible?: boolean;
|
|
12
|
-
icon?: string;
|
|
13
|
-
style?: string;
|
|
14
|
-
class?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type AlertState = {
|
|
18
|
-
message: string;
|
|
19
|
-
type: string;
|
|
20
|
-
dismissible: boolean;
|
|
21
|
-
icon: string;
|
|
22
|
-
visible: boolean;
|
|
23
|
-
style: string;
|
|
24
|
-
class: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export class Alert extends BaseComponent<AlertState> {
|
|
28
|
-
private _alert: HTMLElement | null = null;
|
|
29
|
-
|
|
30
|
-
constructor(id: string, options: AlertOptions = {}) {
|
|
31
|
-
super(id, {
|
|
32
|
-
message: options.message ?? '',
|
|
33
|
-
type: options.type ?? 'info',
|
|
34
|
-
dismissible: options.dismissible ?? true,
|
|
35
|
-
icon: options.icon ?? '',
|
|
36
|
-
visible: true,
|
|
37
|
-
style: options.style ?? '',
|
|
38
|
-
class: options.class ?? ''
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
protected getTriggerEvents(): readonly string[] {
|
|
43
|
-
return TRIGGER_EVENTS;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
protected getCallbackEvents(): readonly string[] {
|
|
47
|
-
return CALLBACK_EVENTS;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
51
|
-
* FLUENT API
|
|
52
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
53
|
-
|
|
54
|
-
// ✅ Inherited from BaseComponent:
|
|
55
|
-
// - style(), class()
|
|
56
|
-
// - bind(), sync(), renderTo()
|
|
57
|
-
// - addClass(), removeClass(), toggleClass()
|
|
58
|
-
// - visible(), show(), hide(), toggleVisibility()
|
|
59
|
-
// - attr(), attrs(), removeAttr()
|
|
60
|
-
// - disabled(), enable(), disable()
|
|
61
|
-
// - loading(), focus(), blur(), remove()
|
|
62
|
-
|
|
63
|
-
message(value: string): this {
|
|
64
|
-
this.state.message = value;
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
type(value: 'info' | 'success' | 'warning' | 'error'): this {
|
|
69
|
-
this.state.type = value;
|
|
70
|
-
return this;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
dismissible(value: boolean): this {
|
|
74
|
-
this.state.dismissible = value;
|
|
75
|
-
return this;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
icon(value: string): this {
|
|
79
|
-
this.state.icon = value;
|
|
80
|
-
return this;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
84
|
-
* RENDER
|
|
85
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
86
|
-
|
|
87
|
-
render(targetId?: string): this {
|
|
88
|
-
const container = this._setupContainer(targetId);
|
|
89
|
-
|
|
90
|
-
const { message, type, dismissible, icon, style, class: className } = this.state;
|
|
91
|
-
const hasVisibleSync = this._syncBindings.some(b => b.property === 'visible');
|
|
92
|
-
|
|
93
|
-
// Build alert element
|
|
94
|
-
const alert = document.createElement('div');
|
|
95
|
-
alert.className = `jux-alert jux-alert-${type}`;
|
|
96
|
-
alert.id = this._id;
|
|
97
|
-
if (className) alert.className += ` ${className}`;
|
|
98
|
-
if (style) alert.setAttribute('style', style);
|
|
99
|
-
|
|
100
|
-
if (icon) {
|
|
101
|
-
const iconEl = document.createElement('span');
|
|
102
|
-
iconEl.className = 'jux-alert-icon';
|
|
103
|
-
iconEl.appendChild(renderIcon(icon));
|
|
104
|
-
alert.appendChild(iconEl);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const content = document.createElement('div');
|
|
108
|
-
content.className = 'jux-alert-content';
|
|
109
|
-
content.textContent = message;
|
|
110
|
-
alert.appendChild(content);
|
|
111
|
-
|
|
112
|
-
if (dismissible) {
|
|
113
|
-
const closeBtn = document.createElement('button');
|
|
114
|
-
closeBtn.className = 'jux-alert-close';
|
|
115
|
-
closeBtn.innerHTML = '×';
|
|
116
|
-
alert.appendChild(closeBtn);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Default dismiss behavior (only if NOT using sync)
|
|
120
|
-
if (!hasVisibleSync && dismissible) {
|
|
121
|
-
const closeBtn = alert.querySelector('.jux-alert-close');
|
|
122
|
-
closeBtn?.addEventListener('click', () => {
|
|
123
|
-
// 🎯 Fire the dismiss callback event
|
|
124
|
-
this._triggerCallback('dismiss');
|
|
125
|
-
alert.remove();
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Wire events using inherited method
|
|
130
|
-
this._wireStandardEvents(alert);
|
|
131
|
-
|
|
132
|
-
// Wire sync bindings
|
|
133
|
-
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
134
|
-
if (property === 'message') {
|
|
135
|
-
const transform = toComponent || ((v: any) => String(v));
|
|
136
|
-
|
|
137
|
-
stateObj.subscribe((val: any) => {
|
|
138
|
-
const transformed = transform(val);
|
|
139
|
-
content.textContent = transformed;
|
|
140
|
-
this.state.message = transformed;
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
else if (property === 'type') {
|
|
144
|
-
const transform = toComponent || ((v: any) => String(v));
|
|
145
|
-
|
|
146
|
-
stateObj.subscribe((val: any) => {
|
|
147
|
-
const transformed = transform(val);
|
|
148
|
-
alert.className = `jux-alert jux-alert-${transformed}`;
|
|
149
|
-
if (className) alert.className += ` ${className}`;
|
|
150
|
-
this.state.type = transformed;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
else if (property === 'visible') {
|
|
154
|
-
const transformToState = toState || ((v: any) => Boolean(v));
|
|
155
|
-
const transform = toComponent || ((v: any) => Boolean(v));
|
|
156
|
-
|
|
157
|
-
let isUpdating = false;
|
|
158
|
-
|
|
159
|
-
// State → Component
|
|
160
|
-
stateObj.subscribe((val: any) => {
|
|
161
|
-
if (isUpdating) return;
|
|
162
|
-
const transformed = transform(val);
|
|
163
|
-
alert.style.display = transformed ? 'flex' : 'none';
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Component → State (close button)
|
|
167
|
-
if (dismissible) {
|
|
168
|
-
const closeBtn = alert.querySelector('.jux-alert-close');
|
|
169
|
-
closeBtn?.addEventListener('click', () => {
|
|
170
|
-
if (isUpdating) return;
|
|
171
|
-
isUpdating = true;
|
|
172
|
-
|
|
173
|
-
alert.style.display = 'none';
|
|
174
|
-
stateObj.set(transformToState(false));
|
|
175
|
-
|
|
176
|
-
// 🎯 Fire the dismiss callback event
|
|
177
|
-
this._triggerCallback('dismiss');
|
|
178
|
-
|
|
179
|
-
setTimeout(() => { isUpdating = false; }, 0);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
container.appendChild(alert);
|
|
186
|
-
this._alert = alert;
|
|
187
|
-
|
|
188
|
-
requestAnimationFrame(() => {
|
|
189
|
-
if ((window as any).lucide) {
|
|
190
|
-
(window as any).lucide.createIcons();
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
return this;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function alert(id: string, options: AlertOptions = {}): Alert {
|
|
199
|
-
return new Alert(id, options);
|
|
200
|
-
}
|