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.
Files changed (141) hide show
  1. package/bin/cli.js +161 -293
  2. package/docs/v2comps/HEADLESS.md +83 -0
  3. package/docs/v2comps/ISOMORPHISM.md +10 -0
  4. package/juxconfig.example.js +63 -58
  5. package/lib/componentsv2/base/BaseEngine.js +258 -0
  6. package/lib/componentsv2/base/BaseEngine.js.map +1 -0
  7. package/lib/componentsv2/base/BaseEngine.ts +303 -0
  8. package/lib/componentsv2/base/BaseSkin.js +108 -0
  9. package/lib/componentsv2/base/BaseSkin.js.map +1 -0
  10. package/lib/componentsv2/base/BaseSkin.ts +137 -0
  11. package/lib/componentsv2/base/GlobalBus.js +56 -0
  12. package/lib/componentsv2/base/GlobalBus.js.map +1 -0
  13. package/lib/componentsv2/base/GlobalBus.ts +60 -0
  14. package/lib/componentsv2/base/State.js +68 -0
  15. package/lib/componentsv2/base/State.js.map +1 -0
  16. package/lib/componentsv2/base/State.ts +62 -0
  17. package/lib/componentsv2/grid/component.js +41 -0
  18. package/lib/componentsv2/grid/component.js.map +1 -0
  19. package/lib/componentsv2/grid/component.ts +67 -0
  20. package/lib/componentsv2/grid/engine.js +73 -0
  21. package/lib/componentsv2/grid/engine.js.map +1 -0
  22. package/lib/componentsv2/grid/engine.ts +110 -0
  23. package/lib/componentsv2/grid/skin.js +95 -0
  24. package/lib/componentsv2/grid/skin.js.map +1 -0
  25. package/lib/componentsv2/grid/skin.ts +105 -0
  26. package/lib/componentsv2/grid/structure.css +58 -0
  27. package/lib/componentsv2/index.js +218 -0
  28. package/lib/componentsv2/index.js.map +1 -0
  29. package/lib/componentsv2/index.ts +253 -0
  30. package/lib/componentsv2/input/component.js +21 -0
  31. package/lib/componentsv2/input/component.js.map +1 -0
  32. package/lib/componentsv2/input/component.ts +28 -0
  33. package/lib/componentsv2/input/engine.js +50 -0
  34. package/lib/componentsv2/input/engine.js.map +1 -0
  35. package/lib/componentsv2/input/engine.ts +76 -0
  36. package/lib/componentsv2/input/skin.js +91 -0
  37. package/lib/componentsv2/input/skin.js.map +1 -0
  38. package/lib/componentsv2/input/skin.ts +91 -0
  39. package/lib/componentsv2/input/structure.css +47 -0
  40. package/lib/componentsv2/list/component.js +83 -0
  41. package/lib/componentsv2/list/component.js.map +1 -0
  42. package/lib/componentsv2/list/component.ts +97 -0
  43. package/lib/componentsv2/list/engine.js +261 -0
  44. package/lib/componentsv2/list/engine.js.map +1 -0
  45. package/lib/componentsv2/list/engine.ts +345 -0
  46. package/lib/componentsv2/list/skin.js +343 -0
  47. package/lib/componentsv2/list/skin.js.map +1 -0
  48. package/lib/componentsv2/list/skin.ts +367 -0
  49. package/lib/componentsv2/list/structure.css +359 -0
  50. package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
  51. package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
  52. package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
  53. package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
  54. package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
  55. package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
  56. package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
  57. package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
  58. package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
  59. package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
  60. package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
  61. package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
  62. package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
  63. package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
  64. package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
  65. package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
  66. package/lib/componentsv2/tools/CreateSkin.js +62 -0
  67. package/lib/componentsv2/tools/DocSpam.js +134 -0
  68. package/lib/componentsv2/tools/FluencyAudit.js +141 -0
  69. package/lib/componentsv2/tools/OptionsAudit.js +177 -0
  70. package/lib/componentsv2/tools/Scaffold.js +140 -0
  71. package/lib/utils/fetch.js +428 -0
  72. package/lib/utils/fetch.js.map +1 -0
  73. package/machinery/build.js +2 -1
  74. package/machinery/compiler.js +200 -37
  75. package/machinery/config.js +93 -6
  76. package/machinery/diagnose.js +72 -0
  77. package/machinery/jux-module-pattern.md +118 -0
  78. package/machinery/server.js +23 -7
  79. package/machinery/verifier.js +143 -0
  80. package/machinery/watcher.js +53 -64
  81. package/package.json +11 -2
  82. package/lib/components/alert.ts +0 -200
  83. package/lib/components/app.ts +0 -258
  84. package/lib/components/badge.ts +0 -101
  85. package/lib/components/base/BaseComponent.ts +0 -417
  86. package/lib/components/base/FormInput.ts +0 -227
  87. package/lib/components/button.ts +0 -178
  88. package/lib/components/card.ts +0 -173
  89. package/lib/components/chart.ts +0 -231
  90. package/lib/components/checkbox.ts +0 -242
  91. package/lib/components/code.ts +0 -123
  92. package/lib/components/container.ts +0 -140
  93. package/lib/components/data.ts +0 -135
  94. package/lib/components/datepicker.ts +0 -234
  95. package/lib/components/dialog.ts +0 -172
  96. package/lib/components/divider.ts +0 -100
  97. package/lib/components/dropdown.ts +0 -186
  98. package/lib/components/element.ts +0 -267
  99. package/lib/components/error-handler.ts +0 -285
  100. package/lib/components/fileupload.ts +0 -309
  101. package/lib/components/grid.ts +0 -291
  102. package/lib/components/guard.ts +0 -92
  103. package/lib/components/heading.ts +0 -96
  104. package/lib/components/helpers.ts +0 -41
  105. package/lib/components/hero.ts +0 -224
  106. package/lib/components/icon.ts +0 -160
  107. package/lib/components/icons.ts +0 -175
  108. package/lib/components/include.ts +0 -440
  109. package/lib/components/input.ts +0 -457
  110. package/lib/components/list.ts +0 -419
  111. package/lib/components/loading.ts +0 -100
  112. package/lib/components/menu.ts +0 -260
  113. package/lib/components/modal.ts +0 -239
  114. package/lib/components/nav.ts +0 -257
  115. package/lib/components/paragraph.ts +0 -97
  116. package/lib/components/progress.ts +0 -139
  117. package/lib/components/radio.ts +0 -278
  118. package/lib/components/req.ts +0 -302
  119. package/lib/components/script.ts +0 -43
  120. package/lib/components/select.ts +0 -252
  121. package/lib/components/sidebar.ts +0 -167
  122. package/lib/components/style.ts +0 -43
  123. package/lib/components/switch.ts +0 -246
  124. package/lib/components/table.ts +0 -1249
  125. package/lib/components/tabs.ts +0 -250
  126. package/lib/components/theme-toggle.ts +0 -300
  127. package/lib/components/token-calculator.ts +0 -313
  128. package/lib/components/tooltip.ts +0 -144
  129. package/lib/components/view.ts +0 -190
  130. package/lib/components/write.ts +0 -272
  131. package/lib/jux.ts +0 -365
  132. package/lib/layouts/default.css +0 -260
  133. package/lib/layouts/figma.css +0 -334
  134. package/lib/reactivity/state.ts +0 -78
  135. package/machinery/bundleAssets.js +0 -0
  136. package/machinery/bundleJux.js +0 -0
  137. package/machinery/bundleVendors.js +0 -0
  138. package/presets/default/all.jux +0 -343
  139. package/presets/default/index.jux +0 -90
  140. package/presets/default/layout.jux +0 -57
  141. package/presets/default/style.css +0 -1612
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 projectName = process.argv[3] || 'my-jux-app';
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 { generateDocs } from '../machinery/doc-generator.js';
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 { loadConfig, runBootstrap } from '../machinery/config.js';
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
- // Where user's .jux source files live (CONVENTION: ./jux/)
148
- get juxSource() {
149
- return path.join(this.projectRoot, config.sourceDir);
150
- },
151
-
152
- // Where jux lib files are (components, layouts, etc.)
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
- // Where frontend build output goes (CONVENTION: ./jux-dist/)
158
- get frontendDist() {
159
- return path.join(this.projectRoot, config.distDir);
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
- console.log('📍 JUX Paths:');
164
- console.log(` Package: ${PATHS.packageRoot}`);
165
- console.log(` Project: ${PATHS.projectRoot}`);
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
- * Build the entire JUX project (ALWAYS uses router bundle)
196
- *
197
- * @param {boolean} isServe - Whether building for dev server
198
- */
199
- async function buildProject(isServe = false, wsPort = 3001) {
200
- const buildStartTime = performance.now();
201
- console.log('🔨 Building JUX frontend...\n');
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
- try {
204
- // Verify jux source directory exists
205
- if (!fs.existsSync(PATHS.juxSource)) {
206
- console.error(`❌ Source directory not found: ${PATHS.juxSource}`);
207
- console.error(` Please create a 'jux/' directory with your .jux files`);
208
- process.exit(1);
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
- // Clean and create frontend dist
212
- if (fs.existsSync(PATHS.frontendDist)) {
213
- fs.rmSync(PATHS.frontendDist, { recursive: true, force: true });
214
- }
215
- fs.mkdirSync(PATHS.frontendDist, { recursive: true });
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
- // Step 1: Generate documentation
218
- /*
219
- const docsStartTime = performance.now();
220
- let docsTime = 0; // Declare with default value
221
- console.log('📚 Generating documentation...');
222
- try {
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
- // Bundle and get the generated filename
266
- const bundleStartTime = performance.now();
267
- const bundleResult = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
268
- routePrefix: ''
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
- console.log('');
307
- }
262
+ const indexTime = performance.now() - indexStartTime;
308
263
 
309
- } catch (err) {
310
- const failTime = performance.now() - buildStartTime;
311
- console.error(`❌ Build error after ${failTime.toFixed(0)}ms:`, err.message);
312
- console.error(err.stack);
313
- process.exit(1);
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
- (async () => {
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('❌ jux/ directory already exists');
299
+ console.error('❌ Source directory already exists');
325
300
  process.exit(1);
326
301
  }
327
302
 
328
- // Create jux/ structure
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(srcPath, destPath);
346
- console.log(`+ Created jux/${entry.name}`);
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 .gitignore with config.distDir
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
- const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
415
- if (!fs.existsSync(configDest)) {
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!\n');
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
- // Use ports from config
447
- const httpPort = parseInt(process.argv[3]) || config.ports.http;
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] Create a new JUX project
460
- npx jux init Initialize JUX in current directory
461
- npx jux build Build router bundle to ${config.distDir}/
462
- npx jux serve [http] [ws] Start dev server with hot reload
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.