juxscript 1.0.61 → 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.css +0 -1612
- package/presets/default/layout.jux +0 -55
package/bin/cli.js
CHANGED
|
@@ -14,7 +14,9 @@ const command = process.argv[2];
|
|
|
14
14
|
// ═══════════════════════════════════════════════════════════════════
|
|
15
15
|
|
|
16
16
|
if (command === 'create') {
|
|
17
|
-
const
|
|
17
|
+
const timestamp = new Date().toISOString().slice(0, 16).replace(/[-:T]/g, '').slice(0, 12);
|
|
18
|
+
const projectName = process.argv[3] || `my-jux-app-${timestamp}`;
|
|
19
|
+
|
|
18
20
|
const projectPath = path.join(process.cwd(), projectName);
|
|
19
21
|
|
|
20
22
|
console.log(`
|
|
@@ -64,16 +66,12 @@ if (command === 'create') {
|
|
|
64
66
|
console.error(`\n ⚠️ npm install failed, but continuing...`);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
console.log(`\n🎨 Initializing JUX project structure...`);
|
|
68
69
|
execSync('npx jux init', { stdio: 'inherit' });
|
|
69
70
|
|
|
70
71
|
console.log(`\n📝 Creating .gitignore...`);
|
|
71
72
|
fs.writeFileSync('.gitignore', `.jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
|
|
72
73
|
console.log(` ✓ .gitignore created`);
|
|
73
74
|
|
|
74
|
-
// ❌ REMOVE: Don't copy example file during create
|
|
75
|
-
// The working config is enough, users can view example on GitHub/npm
|
|
76
|
-
|
|
77
75
|
console.log(`
|
|
78
76
|
╔═══════════════════════════════════════════════════════╗
|
|
79
77
|
║ ║
|
|
@@ -120,7 +118,6 @@ Next steps:
|
|
|
120
118
|
// ALL OTHER COMMANDS - Require dependencies to be installed
|
|
121
119
|
// ═══════════════════════════════════════════════════════════════════
|
|
122
120
|
|
|
123
|
-
// ✅ Now import dependencies (only needed for init, build, serve)
|
|
124
121
|
import {
|
|
125
122
|
copyLibToOutput,
|
|
126
123
|
copyProjectAssets,
|
|
@@ -129,56 +126,39 @@ import {
|
|
|
129
126
|
bundleJuxFilesToRouter,
|
|
130
127
|
generateIndexHtml
|
|
131
128
|
} from '../machinery/compiler.js';
|
|
132
|
-
import {
|
|
129
|
+
import { verifyStaticBuild } from '../machinery/verifier.js'; // ✅ Import Verifier
|
|
130
|
+
import { resolveConfig } from '../machinery/config.js'; // ✅ Import Config Resolver
|
|
133
131
|
import { start } from '../machinery/server.js';
|
|
134
|
-
import
|
|
135
|
-
|
|
136
|
-
// ✅ Load config BEFORE setting up PATHS
|
|
137
|
-
const config = await loadConfig(process.cwd());
|
|
138
|
-
|
|
139
|
-
// CLEAR PATH CONTRACT - CONVENTIONS
|
|
140
|
-
const PATHS = {
|
|
141
|
-
// Where jux package is installed (in node_modules/juxscript or local dev)
|
|
142
|
-
packageRoot: path.resolve(__dirname, '..'),
|
|
143
|
-
|
|
144
|
-
// Where the user's project root is (where they run `npx jux`)
|
|
145
|
-
projectRoot: process.cwd(),
|
|
132
|
+
// ❌ REMOVED static import of config to prevent crash on incorrect path
|
|
146
133
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
get juxLib() {
|
|
154
|
-
return path.resolve(__dirname, '..', 'lib');
|
|
155
|
-
},
|
|
134
|
+
/**
|
|
135
|
+
* Dynamically load and normalize juxconfig.js from the user's project root
|
|
136
|
+
*/
|
|
137
|
+
async function loadUserConfig(projectRoot) {
|
|
138
|
+
const configPath = path.join(projectRoot, 'juxconfig.js');
|
|
139
|
+
let rawConfig = {};
|
|
156
140
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
141
|
+
if (fs.existsSync(configPath)) {
|
|
142
|
+
try {
|
|
143
|
+
const module = await import(configPath);
|
|
144
|
+
// Support export default defineConfig({...}) OR export const config = {...}
|
|
145
|
+
rawConfig = module.default || module.config || {};
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.warn(`⚠️ Error loading juxconfig.js: ${err.message}. Using defaults.`);
|
|
148
|
+
}
|
|
160
149
|
}
|
|
161
|
-
};
|
|
162
150
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
console.log(` Source: ${PATHS.juxSource} (from config: ${config.sourceDir})`);
|
|
167
|
-
console.log(` Output: ${PATHS.frontendDist} (from config: ${config.distDir})`);
|
|
168
|
-
console.log(` Lib: ${PATHS.juxLib}\n`);
|
|
151
|
+
// ✅ Normalize DX sugar (short paths, missing extensions, aliases) into strict config
|
|
152
|
+
return resolveConfig(rawConfig, projectRoot);
|
|
153
|
+
}
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
* Recursively find .jux files in a directory
|
|
172
|
-
*/
|
|
155
|
+
// Recursively find .jux files
|
|
173
156
|
function findJuxFiles(dir, fileList = []) {
|
|
174
157
|
if (!fs.existsSync(dir)) return fileList;
|
|
175
|
-
|
|
176
158
|
const files = fs.readdirSync(dir);
|
|
177
|
-
|
|
178
159
|
files.forEach(file => {
|
|
179
160
|
const filePath = path.join(dir, file);
|
|
180
161
|
const stat = fs.statSync(filePath);
|
|
181
|
-
|
|
182
162
|
if (stat.isDirectory()) {
|
|
183
163
|
if (file !== 'node_modules' && file !== 'jux-dist' && file !== '.git' && file !== 'server') {
|
|
184
164
|
findJuxFiles(filePath, fileList);
|
|
@@ -187,297 +167,185 @@ function findJuxFiles(dir, fileList = []) {
|
|
|
187
167
|
fileList.push(filePath);
|
|
188
168
|
}
|
|
189
169
|
});
|
|
190
|
-
|
|
191
170
|
return fileList;
|
|
192
171
|
}
|
|
193
172
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
174
|
+
// MAIN EXECUTION BLOCK (Async)
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
176
|
+
(async () => {
|
|
177
|
+
// 1. Resolve Configuration relative to CWD
|
|
178
|
+
const projectRoot = process.cwd();
|
|
179
|
+
const config = await loadUserConfig(projectRoot);
|
|
180
|
+
|
|
181
|
+
// 2. Define PATHS Contract
|
|
182
|
+
const PATHS = {
|
|
183
|
+
packageRoot: path.resolve(__dirname, '..'), // node_modules/juxscript
|
|
184
|
+
projectRoot: projectRoot,
|
|
185
|
+
|
|
186
|
+
// Use config values
|
|
187
|
+
get juxSource() { return path.join(this.projectRoot, config.directories?.source || 'jux'); },
|
|
188
|
+
get juxLib() { return path.resolve(this.packageRoot, 'lib'); },
|
|
189
|
+
get frontendDist() { return path.join(this.projectRoot, config.directories?.distribution || '.jux-dist'); }
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
console.log('📍 JUX Paths:');
|
|
193
|
+
console.log(` Package: ${PATHS.packageRoot}`);
|
|
194
|
+
console.log(` Project: ${PATHS.projectRoot}`);
|
|
195
|
+
console.log(` Source: ${PATHS.juxSource}`);
|
|
196
|
+
console.log(` Output: ${PATHS.frontendDist}`);
|
|
197
|
+
console.log(` Lib: ${PATHS.juxLib}\n`);
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Build the entire JUX project
|
|
201
|
+
*/
|
|
202
|
+
async function buildProject(isServe = false, wsPort = 3001) {
|
|
203
|
+
const buildStartTime = performance.now();
|
|
204
|
+
console.log('++ Building JUX frontend...\n');
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
try {
|
|
207
|
+
// Verify jux source directory exists
|
|
208
|
+
if (!fs.existsSync(PATHS.juxSource)) {
|
|
209
|
+
console.error(`❌ Source directory not found: ${PATHS.juxSource}`);
|
|
210
|
+
console.error(` Please create a directory named '${config.directories?.source || 'jux'}'`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
// Clean and create frontend dist
|
|
215
|
+
if (fs.existsSync(PATHS.frontendDist)) {
|
|
216
|
+
fs.rmSync(PATHS.frontendDist, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
fs.mkdirSync(PATHS.frontendDist, { recursive: true });
|
|
219
|
+
|
|
220
|
+
// Copy dependencies
|
|
221
|
+
const libStartTime = performance.now();
|
|
222
|
+
await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
|
|
223
|
+
const libTime = performance.now() - libStartTime;
|
|
224
|
+
console.log(`⏱️ Lib copy time: ${libTime.toFixed(0)}ms\n`);
|
|
225
|
+
|
|
226
|
+
const presetsStartTime = performance.now();
|
|
227
|
+
await copyPresetsToOutput(PATHS.packageRoot, PATHS.frontendDist);
|
|
228
|
+
const presetsTime = performance.now() - presetsStartTime;
|
|
229
|
+
console.log(`⏱️ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
|
|
230
|
+
|
|
231
|
+
const assetsStartTime = performance.now();
|
|
232
|
+
await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
|
|
233
|
+
const assetsTime = performance.now() - assetsStartTime;
|
|
234
|
+
console.log(`⏱️ Assets copy time: ${assetsTime.toFixed(0)}ms\n`);
|
|
235
|
+
|
|
236
|
+
const tsStartTime = performance.now();
|
|
237
|
+
await transpileProjectTypeScript(PATHS.juxSource, PATHS.frontendDist);
|
|
238
|
+
const tsTime = performance.now() - tsStartTime;
|
|
239
|
+
console.log(`⏱️ TypeScript transpile time: ${tsTime.toFixed(0)}ms\n`);
|
|
240
|
+
|
|
241
|
+
// Bundle Routers
|
|
242
|
+
const projectJuxFiles = findJuxFiles(PATHS.juxSource);
|
|
243
|
+
console.log(`📝 Found ${projectJuxFiles.length} .jux file(s)\n`);
|
|
244
|
+
|
|
245
|
+
if (projectJuxFiles.length === 0) {
|
|
246
|
+
console.warn('⚠️ No .jux files found to bundle');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
216
249
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await generateDocs(PATHS.juxLib);
|
|
224
|
-
docsTime = performance.now() - docsStartTime;
|
|
225
|
-
console.log(`✅ Documentation generated (${docsTime.toFixed(0)}ms)\n`);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
|
|
228
|
-
console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
|
|
229
|
-
}
|
|
230
|
-
*/
|
|
231
|
-
|
|
232
|
-
// Step 2: Copy jux lib to frontend dist
|
|
233
|
-
const libStartTime = performance.now();
|
|
234
|
-
await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
|
|
235
|
-
const libTime = performance.now() - libStartTime;
|
|
236
|
-
console.log(`⏱️ Lib copy time: ${libTime.toFixed(0)}ms\n`);
|
|
237
|
-
|
|
238
|
-
// Step 3: Copy presets folder
|
|
239
|
-
const presetsStartTime = performance.now();
|
|
240
|
-
await copyPresetsToOutput(PATHS.packageRoot, PATHS.frontendDist);
|
|
241
|
-
const presetsTime = performance.now() - presetsStartTime;
|
|
242
|
-
console.log(`⏱️ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
|
|
243
|
-
|
|
244
|
-
// Step 4: Copy project assets (CSS, JS, images)
|
|
245
|
-
const assetsStartTime = performance.now();
|
|
246
|
-
await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
|
|
247
|
-
const assetsTime = performance.now() - assetsStartTime;
|
|
248
|
-
console.log(`⏱️ Assets copy time: ${assetsTime.toFixed(0)}ms\n`);
|
|
249
|
-
|
|
250
|
-
// Step 5: Transpile TypeScript files
|
|
251
|
-
const tsStartTime = performance.now();
|
|
252
|
-
await transpileProjectTypeScript(PATHS.juxSource, PATHS.frontendDist);
|
|
253
|
-
const tsTime = performance.now() - tsStartTime;
|
|
254
|
-
console.log(`⏱️ TypeScript transpile time: ${tsTime.toFixed(0)}ms\n`);
|
|
255
|
-
|
|
256
|
-
// Step 6: Bundle all .jux files into router
|
|
257
|
-
const projectJuxFiles = findJuxFiles(PATHS.juxSource);
|
|
258
|
-
console.log(`📝 Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
|
|
259
|
-
|
|
260
|
-
if (projectJuxFiles.length === 0) {
|
|
261
|
-
console.warn('⚠️ No .jux files found to bundle');
|
|
262
|
-
process.exit(1);
|
|
263
|
-
}
|
|
250
|
+
const bundleStartTime = performance.now();
|
|
251
|
+
const bundleResult = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
|
|
252
|
+
routePrefix: '',
|
|
253
|
+
config // Pass config to bundler
|
|
254
|
+
});
|
|
255
|
+
const bundleTime = performance.now() - bundleStartTime;
|
|
264
256
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
270
|
-
const bundleTime = performance.now() - bundleStartTime;
|
|
271
|
-
|
|
272
|
-
// ✅ Generate unified index.html
|
|
273
|
-
const indexStartTime = performance.now();
|
|
274
|
-
generateIndexHtml(PATHS.frontendDist, bundleResult);
|
|
275
|
-
const indexTime = performance.now() - indexStartTime;
|
|
276
|
-
|
|
277
|
-
const totalBuildTime = performance.now() - buildStartTime;
|
|
278
|
-
|
|
279
|
-
// ✅ FIX: Use bundleResult.mainJsFilename instead of bare mainJsFilename
|
|
280
|
-
console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${bundleResult.mainJsFilename}\n`);
|
|
281
|
-
|
|
282
|
-
// ✅ Build summary with timing breakdown
|
|
283
|
-
console.log(`📊 Build Summary:`);
|
|
284
|
-
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
285
|
-
console.log(` Library copy: ${libTime.toFixed(0)}ms`);
|
|
286
|
-
console.log(` Presets copy: ${presetsTime.toFixed(0)}ms`);
|
|
287
|
-
console.log(` Assets copy: ${assetsTime.toFixed(0)}ms`);
|
|
288
|
-
console.log(` TypeScript: ${tsTime.toFixed(0)}ms`);
|
|
289
|
-
console.log(` Router bundle: ${bundleTime.toFixed(0)}ms`);
|
|
290
|
-
console.log(` Index generation: ${indexTime.toFixed(0)}ms`);
|
|
291
|
-
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
292
|
-
console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
|
|
293
|
-
|
|
294
|
-
// Show usage
|
|
295
|
-
if (!isServe) {
|
|
296
|
-
console.log('📦 Serve from your backend:');
|
|
297
|
-
console.log(` Express: app.use(express.static('jux-dist'))`);
|
|
298
|
-
console.log(` Flask: app = Flask(__name__, static_folder='jux-dist')`);
|
|
299
|
-
console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
|
|
300
|
-
console.log('');
|
|
301
|
-
console.log('📍 Available routes:');
|
|
302
|
-
// ✅ FIX: Use bundleResult.routes instead of local routes variable
|
|
303
|
-
bundleResult.routes.forEach(r => {
|
|
304
|
-
console.log(` ${r.path}`);
|
|
257
|
+
const indexStartTime = performance.now();
|
|
258
|
+
generateIndexHtml(PATHS.frontendDist, bundleResult, {
|
|
259
|
+
isDev: isServe,
|
|
260
|
+
wsPort: isServe ? wsPort : undefined
|
|
305
261
|
});
|
|
306
|
-
|
|
307
|
-
}
|
|
262
|
+
const indexTime = performance.now() - indexStartTime;
|
|
308
263
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
264
|
+
// ✅ NEW: Post-Compilation Verification
|
|
265
|
+
if (!verifyStaticBuild(PATHS.frontendDist)) {
|
|
266
|
+
console.error('🛑 Critical Build Failure. Aborting.');
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const totalBuildTime = performance.now() - buildStartTime;
|
|
271
|
+
console.log(`\n✅ Bundled → ${PATHS.frontendDist}/${bundleResult.mainJsFilename}\n`);
|
|
272
|
+
|
|
273
|
+
// Build Summary Log
|
|
274
|
+
console.log(`📊 Build Summary:`);
|
|
275
|
+
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
276
|
+
console.log(` Library copy: ${libTime.toFixed(0)}ms`);
|
|
277
|
+
console.log(` Router bundle: ${bundleTime.toFixed(0)}ms`);
|
|
278
|
+
console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
|
|
279
|
+
|
|
280
|
+
if (!isServe) {
|
|
281
|
+
// ...existing code...
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(`❌ Build error:`, err.message);
|
|
286
|
+
console.error(err.stack);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
314
289
|
}
|
|
315
|
-
}
|
|
316
290
|
|
|
317
|
-
|
|
291
|
+
// ═══════════════════════════════════════════════════════════════
|
|
292
|
+
// COMMAND DISPATCHER
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════
|
|
294
|
+
|
|
318
295
|
if (command === 'init') {
|
|
319
296
|
console.log('🎨 Initializing JUX project...\n');
|
|
320
|
-
|
|
321
297
|
const juxDir = PATHS.juxSource;
|
|
322
|
-
|
|
323
298
|
if (fs.existsSync(juxDir)) {
|
|
324
|
-
console.error('❌
|
|
299
|
+
console.error('❌ Source directory already exists');
|
|
325
300
|
process.exit(1);
|
|
326
301
|
}
|
|
327
302
|
|
|
328
|
-
//
|
|
329
|
-
fs.mkdirSync(juxDir, { recursive: true });
|
|
330
|
-
|
|
331
|
-
// ✅ Copy presets/default/ directly to jux/ (no presets subfolder!)
|
|
332
|
-
const defaultPresetSrc = path.join(PATHS.packageRoot, 'presets', 'default');
|
|
303
|
+
fs.mkdirSync(juxDir, { recursive: true }); // create it.
|
|
333
304
|
|
|
305
|
+
// Copy default presets
|
|
306
|
+
const defaultPresetSrc = path.join(PATHS.packageRoot, 'create');
|
|
334
307
|
if (fs.existsSync(defaultPresetSrc)) {
|
|
335
|
-
console.log('📦 Copying default preset boilerplate...');
|
|
336
|
-
|
|
337
308
|
const entries = fs.readdirSync(defaultPresetSrc, { withFileTypes: true });
|
|
338
|
-
let copiedCount = 0;
|
|
339
|
-
|
|
340
309
|
for (const entry of entries) {
|
|
341
|
-
const srcPath = path.join(defaultPresetSrc, entry.name);
|
|
342
|
-
const destPath = path.join(juxDir, entry.name);
|
|
343
|
-
|
|
344
310
|
if (entry.isFile()) {
|
|
345
|
-
fs.copyFileSync(
|
|
346
|
-
console.log(`+ Created
|
|
347
|
-
copiedCount++;
|
|
311
|
+
fs.copyFileSync(path.join(defaultPresetSrc, entry.name), path.join(juxDir, entry.name));
|
|
312
|
+
// console.log(`+ Created ${entry.name}`);
|
|
348
313
|
}
|
|
349
314
|
}
|
|
350
|
-
|
|
351
|
-
console.log(`✅ Copied ${copiedCount} boilerplate file(s) to jux/\n`);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ✅ Create a simple index.jux if none exists
|
|
355
|
-
const indexJuxDest = path.join(juxDir, 'index.jux');
|
|
356
|
-
if (!fs.existsSync(indexJuxDest)) {
|
|
357
|
-
const basicContent = `import { jux, state } from 'juxscript';
|
|
358
|
-
|
|
359
|
-
jux.hero('welcome', {
|
|
360
|
-
title: 'Welcome to JUX',
|
|
361
|
-
subtitle: 'Start building your app'
|
|
362
|
-
}).render('#app');
|
|
363
|
-
|
|
364
|
-
const count = state(0);
|
|
365
|
-
|
|
366
|
-
jux.button('increment')
|
|
367
|
-
.label('Click me!')
|
|
368
|
-
.bind('click', () => count.value++)
|
|
369
|
-
.style('margin: 2rem 0;')
|
|
370
|
-
.render('#app');
|
|
371
|
-
|
|
372
|
-
jux.paragraph('counter')
|
|
373
|
-
.sync('text', count, val => \`Count: \${val}\`)
|
|
374
|
-
.render('#app');
|
|
375
|
-
`;
|
|
376
|
-
fs.writeFileSync(indexJuxDest, basicContent);
|
|
377
|
-
console.log('+ Created jux/index.jux');
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Create package.json if it doesn't exist
|
|
381
|
-
const pkgPath = path.join(PATHS.projectRoot, 'package.json');
|
|
382
|
-
if (!fs.existsSync(pkgPath)) {
|
|
383
|
-
const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
384
|
-
|
|
385
|
-
const pkgContent = {
|
|
386
|
-
"name": projectName,
|
|
387
|
-
"version": "0.1.0",
|
|
388
|
-
"type": "module",
|
|
389
|
-
"scripts": {
|
|
390
|
-
"dev": "jux serve",
|
|
391
|
-
"build": "jux build"
|
|
392
|
-
},
|
|
393
|
-
"dependencies": {
|
|
394
|
-
"juxscript": "latest"
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
|
|
398
|
-
console.log('+ Created package.json');
|
|
399
315
|
}
|
|
400
316
|
|
|
401
|
-
// Create .
|
|
402
|
-
const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
|
|
403
|
-
const gitignoreContent = `${config.distDir}/\nnode_modules/\n.DS_Store\n`; // ✅ Uses config.distDir
|
|
404
|
-
|
|
405
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
406
|
-
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
407
|
-
console.log('+ Created .gitignore');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// ✅ Copy juxconfig to root
|
|
317
|
+
// Create juxconfig.js
|
|
411
318
|
const configExampleSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
|
|
412
|
-
|
|
413
|
-
if (fs.existsSync(configExampleSrc)) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
fs.copyFileSync(configExampleSrc, configDest);
|
|
417
|
-
console.log('+ Created juxconfig.js');
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// ❌ REMOVE: Don't copy example - it's available in node_modules
|
|
421
|
-
// const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
|
|
422
|
-
// if (!fs.existsSync(configExampleDest)) {
|
|
423
|
-
// fs.copyFileSync(configExampleSrc, configExampleDest);
|
|
424
|
-
// console.log('+ Created juxconfig.example.js (reference)');
|
|
425
|
-
// }
|
|
319
|
+
const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
|
|
320
|
+
if (fs.existsSync(configExampleSrc) && !fs.existsSync(configDest)) {
|
|
321
|
+
fs.copyFileSync(configExampleSrc, configDest);
|
|
322
|
+
console.log('+ Created juxconfig.js');
|
|
426
323
|
}
|
|
427
324
|
|
|
428
|
-
console.log('\n✅ JUX project initialized
|
|
429
|
-
console.log('Project structure:');
|
|
430
|
-
console.log(` ${config.sourceDir}/ # Your source files`);
|
|
431
|
-
console.log(` ${config.distDir}/ # Build output (git-ignored)`);
|
|
432
|
-
console.log(' juxconfig.js # Configuration');
|
|
433
|
-
console.log(' package.json # Dependencies\n');
|
|
434
|
-
console.log('Next steps:');
|
|
435
|
-
console.log(' npm install # Install dependencies');
|
|
436
|
-
console.log(' npm run dev # Start dev server\n');
|
|
437
|
-
console.log('📖 Config reference: node_modules/juxscript/juxconfig.example.js\n');
|
|
325
|
+
console.log('\n✅ JUX project initialized!');
|
|
438
326
|
|
|
439
327
|
} else if (command === 'build') {
|
|
440
|
-
// ✅ Run bootstrap before build
|
|
441
|
-
await runBootstrap(config.bootstrap);
|
|
442
328
|
await buildProject(false);
|
|
443
329
|
console.log(`✅ Build complete: ${PATHS.frontendDist}`);
|
|
444
330
|
|
|
445
331
|
} else if (command === 'serve') {
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
const wsPort = parseInt(process.argv[4]) || config.ports.ws;
|
|
332
|
+
const httpPort = parseInt(process.argv[3]) || config.defaults?.httpPort || 3000;
|
|
333
|
+
const wsPort = parseInt(process.argv[4]) || config.defaults?.wsPort || 3001;
|
|
449
334
|
|
|
450
|
-
await runBootstrap(config.bootstrap);
|
|
451
335
|
await buildProject(true, wsPort);
|
|
452
|
-
await start(httpPort, wsPort, PATHS.frontendDist);
|
|
336
|
+
await start(httpPort, wsPort, PATHS.frontendDist, config); // Pass config to server
|
|
453
337
|
|
|
454
338
|
} else {
|
|
339
|
+
// ...existing code...
|
|
340
|
+
// (Help text)
|
|
455
341
|
console.log(`
|
|
456
342
|
JUX CLI - A JavaScript UX authorship platform
|
|
457
|
-
|
|
458
343
|
Usage:
|
|
459
|
-
npx jux create [name]
|
|
460
|
-
npx jux init
|
|
461
|
-
npx jux build
|
|
462
|
-
npx jux serve
|
|
463
|
-
|
|
464
|
-
Project Structure:
|
|
465
|
-
my-project/
|
|
466
|
-
├── ${config.distDir}/ # Build output (git-ignored, like .git)
|
|
467
|
-
│ ├── lib/
|
|
468
|
-
│ ├── main.js
|
|
469
|
-
│ └── index.html
|
|
470
|
-
├── ${config.sourceDir}/ # Your .jux source files
|
|
471
|
-
│ ├── index.jux
|
|
472
|
-
│ ├── layout.css
|
|
473
|
-
│ └── layout.jux
|
|
474
|
-
├── juxconfig.js # Configuration
|
|
475
|
-
└── package.json
|
|
476
|
-
|
|
477
|
-
Examples:
|
|
478
|
-
npx jux create my-app Create new project
|
|
479
|
-
npx jux serve Dev server (ports 3000/3001)
|
|
480
|
-
npx jux serve 8080 8081 Custom ports
|
|
481
|
-
`);
|
|
344
|
+
npx jux create [name]
|
|
345
|
+
npx jux init
|
|
346
|
+
npx jux build
|
|
347
|
+
npx jux serve
|
|
348
|
+
`);
|
|
482
349
|
}
|
|
350
|
+
|
|
483
351
|
})();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# JUX HEADLESS LIBRARY
|
|
2
|
+
**Formula:** `Logic (Engine) + View (Skin) = Component`
|
|
3
|
+
|
|
4
|
+
## The Engine (Service Contract)
|
|
5
|
+
The Engine defines the component's capabilities and data shape. It acts as the "Operating System" for the component.
|
|
6
|
+
|
|
7
|
+
**1. Isomorphic & Headless**
|
|
8
|
+
The Engine is pure TypeScript/JavaScript logic. It has **zero dependencies** on the DOM, `window`, or browser APIs.
|
|
9
|
+
* **Benefit:** Deeply testable in Node.js without fragile mocks or JSDOM.
|
|
10
|
+
* **Benefit:** Portable across environments (Server-Side Rendering, CLI tools, Native wrappers).
|
|
11
|
+
|
|
12
|
+
**2. The Observer Pattern (The Wire)**
|
|
13
|
+
The Engine manages subscriptions. Skins do not poll for changes; they subscribe.
|
|
14
|
+
* When state mutates, the Engine notifies all listeners immediately with a fresh snapshot.
|
|
15
|
+
* **Unidirectional Flow:** Skins cannot modify State directly. They must invoke Engine methods (commands).
|
|
16
|
+
|
|
17
|
+
**3. State Management**
|
|
18
|
+
State is the Engine's repository of truth. By default, it is in-memory and ephemeral.
|
|
19
|
+
|
|
20
|
+
### Persistence Strategy
|
|
21
|
+
State persistence is handled via **Dependency Injection**. A "Persistence Driver" can be passed to the Engine's constructor.
|
|
22
|
+
* **Separation of Concerns:** The Engine knows *when* data changes, but the Driver knows *how* to save it (LocalStorage, Database, FileSystem).
|
|
23
|
+
* **Decision Criteria:** Not every Engine needs persistence. Ideally, persist **Domain State** (User Data like 'List Items') but keep **UI State** (Transient data like 'Menu Open') in memory.
|
|
24
|
+
|
|
25
|
+
## Why Distinct Engines?
|
|
26
|
+
Why not one generic Engine?
|
|
27
|
+
Engines encapsulate specific **Business Logic** and **Data Shapes**.
|
|
28
|
+
* A `ListEngine` methods: `addItem`, `removeItem`, `sort`.
|
|
29
|
+
* A `ButtonEngine` methods: `click`, `disable`, `enable`.
|
|
30
|
+
* A `FormEngine` methods: `validate`, `submit`, `reset`.
|
|
31
|
+
|
|
32
|
+
Engines are thin layers that translate specific high-level intent (API) into low-level state patches.
|
|
33
|
+
|
|
34
|
+
### The Public API
|
|
35
|
+
**An Engine's Methods are the Public API.**
|
|
36
|
+
This is the primary interface for:
|
|
37
|
+
1. **The Application Developer**: To drive the component programmatically (e.g., `listEngine.addItem('Buy Milk')`).
|
|
38
|
+
2. **The Skin**: To wire up UI events (e.g., `button.onclick = () => engine.click()`).
|
|
39
|
+
3. **The Component Author**: To strictly define what mutations are valid, preventing "illegal" states.
|
|
40
|
+
|
|
41
|
+
## BaseEngine
|
|
42
|
+
The base engine serves as the 'State and Context' Manager for the Jux Ecosystem. You can gather the current state of any jux component by calling the `componentsEngineName.state`
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
```javascript
|
|
46
|
+
// composer.js
|
|
47
|
+
import { ListEngine } from './ListEngine.js';
|
|
48
|
+
// 1. Initialize the Engine (The Logic)
|
|
49
|
+
const listEngine = new ListEngine(['Apples', 'Oranges']); // Initialize List Engine
|
|
50
|
+
|
|
51
|
+
// You can still trigger engine commands directly from the console!
|
|
52
|
+
window.listEngine = listEngine; // Expose list engine to console
|
|
53
|
+
/* call this directly from the console =>
|
|
54
|
+
|
|
55
|
+
console.log('List Engine State: ', listEngine.state);
|
|
56
|
+
|
|
57
|
+
*/
|
|
58
|
+
```
|
|
59
|
+
```javascript
|
|
60
|
+
|
|
61
|
+
// yields
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
items: ['Apples', 'Oranges'],
|
|
65
|
+
listType: 'unordered'
|
|
66
|
+
}*/
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Tests
|
|
71
|
+
Both Engines and Skins require tests.
|
|
72
|
+
|
|
73
|
+
**Skins** require use of an html emulator (in memory HTML generator like JSDOM).
|
|
74
|
+
For Skins our Unit Tests aim to assert the **"View Contract"**:
|
|
75
|
+
1. **Render Accuracy**: Does the resulting HTML match the State? (e.g., if state has 3 items, do we see 3 `<li>` tags?)
|
|
76
|
+
2. **Event Wiring**: Do DOM interactions (clicks, inputs) trigger the correct Engine methods?
|
|
77
|
+
3. **Reactivity**: When the Engine updates, does the DOM update automatically without full page reloads?
|
|
78
|
+
|
|
79
|
+
**Engines** use simple Node.js execution. (node test.js etc.).
|
|
80
|
+
For Engines our Unit Tests aim to assert the **"Business Logic"**:
|
|
81
|
+
1. **State Transitions**: Given an initial state and an action (method call), is the resulting state correct?
|
|
82
|
+
2. **Invariants**: Are invalid actions prevented or handled gracefully?
|
|
83
|
+
3. **Zero-UI Dependency**: Can the logic run to completion without any DOM APIs present?
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Yes, your ListEngine is 100% Isomorphic (also known as "Universal").
|
|
2
|
+
|
|
3
|
+
Why?
|
|
4
|
+
|
|
5
|
+
No DOM Access: It never touches window, document, or HTMLElement. That is entirely delegated to ListSkin.
|
|
6
|
+
No Node API Access: It doesn't use fs or http.
|
|
7
|
+
Pure Logic: It only relies on standard JavaScript objects (Array, JSON) and your internal BaseEngine.
|
|
8
|
+
This means the exact same file can be used to drive your UI in the browser and run your Unit Tests in Node.js (or Deno/Bun) without any mocking or compilation tricks.
|
|
9
|
+
|
|
10
|
+
I will add a JSDoc tag to ListEngine.js to document this architectural trait.
|