juxscript 1.0.45 → 1.0.47
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 +191 -155
- package/lib/components/docs-data.json +1 -1
- package/machinery/compiler.js +7 -59
- package/machinery/server.js +0 -62
- package/machinery/watcher.js +43 -116
- package/package.json +1 -1
- package/machinery/config.js +0 -94
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const command = process.argv[2];
|
|
11
|
+
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
13
|
+
// CREATE COMMAND - Runs BEFORE dependencies are installed
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
if (command === 'create') {
|
|
17
|
+
const projectName = process.argv[3] || 'my-jux-app';
|
|
18
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
19
|
+
|
|
20
|
+
console.log(`
|
|
21
|
+
╔═══════════════════════════════════════════════════════╗
|
|
22
|
+
║ ║
|
|
23
|
+
║ 🎨 Welcome to JUX ║
|
|
24
|
+
║ ║
|
|
25
|
+
║ Creating your new JUX project... ║
|
|
26
|
+
║ ║
|
|
27
|
+
╚═══════════════════════════════════════════════════════╝
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(projectPath)) {
|
|
31
|
+
console.error(`❌ Directory "${projectName}" already exists.`);
|
|
32
|
+
console.error(` Please choose a different name or remove the existing directory.\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { execSync } = await import('child_process');
|
|
38
|
+
|
|
39
|
+
console.log(`📁 Creating directory: ${projectName}`);
|
|
40
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
41
|
+
process.chdir(projectPath);
|
|
42
|
+
|
|
43
|
+
console.log(`📦 Initializing package.json...`);
|
|
44
|
+
const packageJson = {
|
|
45
|
+
name: projectName,
|
|
46
|
+
version: '0.1.0',
|
|
47
|
+
type: 'module',
|
|
48
|
+
scripts: {
|
|
49
|
+
dev: 'jux serve',
|
|
50
|
+
build: 'jux build'
|
|
51
|
+
},
|
|
52
|
+
dependencies: {
|
|
53
|
+
juxscript: 'latest'
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
57
|
+
console.log(` ✓ package.json created`);
|
|
58
|
+
|
|
59
|
+
console.log(`\n📥 Installing juxscript...\n`);
|
|
60
|
+
try {
|
|
61
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
62
|
+
console.log(`\n ✓ Dependencies installed`);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`\n ⚠️ npm install failed, but continuing...`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\n🎨 Initializing JUX project structure...`);
|
|
68
|
+
execSync('npx jux init', { stdio: 'inherit' });
|
|
69
|
+
|
|
70
|
+
console.log(`\n📝 Creating .gitignore...`);
|
|
71
|
+
fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
|
|
72
|
+
console.log(` ✓ .gitignore created`);
|
|
73
|
+
|
|
74
|
+
console.log(`
|
|
75
|
+
╔═══════════════════════════════════════════════════════╗
|
|
76
|
+
║ ║
|
|
77
|
+
║ ✅ Project created successfully! ║
|
|
78
|
+
║ ║
|
|
79
|
+
╚═══════════════════════════════════════════════════════╝
|
|
80
|
+
|
|
81
|
+
📚 Resources:
|
|
82
|
+
Documentation: [coming soon]
|
|
83
|
+
GitHub: https://github.com/juxscript/jux
|
|
84
|
+
Examples: https://github.com/juxscript/examples
|
|
85
|
+
|
|
86
|
+
⭐ If you find JUX useful, please star us on GitHub!
|
|
87
|
+
🔒 Security: Report issues to security@juxscript.com [placeholder]
|
|
88
|
+
|
|
89
|
+
Say goodbye to markup </</>>>.
|
|
90
|
+
Happy javascripting your frontend! 🎉
|
|
91
|
+
|
|
92
|
+
Next steps:
|
|
93
|
+
cd ${projectName}
|
|
94
|
+
npm run dev
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`\n❌ Project creation failed:`, err.message);
|
|
99
|
+
|
|
100
|
+
if (fs.existsSync(projectPath)) {
|
|
101
|
+
try {
|
|
102
|
+
process.chdir('..');
|
|
103
|
+
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
104
|
+
console.log(` ✓ Cleaned up failed project directory\n`);
|
|
105
|
+
} catch (cleanupErr) {
|
|
106
|
+
console.error(` ⚠️ Could not clean up directory\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.exit(0); // ✅ Exit after create completes
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
117
|
+
// ALL OTHER COMMANDS - Require dependencies to be installed
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
119
|
+
|
|
120
|
+
// ✅ Now import dependencies (only needed for init, build, serve)
|
|
3
121
|
import {
|
|
4
122
|
copyLibToOutput,
|
|
5
123
|
copyProjectAssets,
|
|
@@ -10,16 +128,6 @@ import {
|
|
|
10
128
|
} from '../machinery/compiler.js';
|
|
11
129
|
import { generateDocs } from '../machinery/doc-generator.js';
|
|
12
130
|
import { start } from '../machinery/server.js';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import fs from 'fs';
|
|
15
|
-
import { fileURLToPath } from 'url';
|
|
16
|
-
import { loadConfig, runBootstrap } from '../machinery/config.js';
|
|
17
|
-
|
|
18
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
-
const __dirname = path.dirname(__filename);
|
|
20
|
-
|
|
21
|
-
// Load configuration first (before PATHS)
|
|
22
|
-
const config = await loadConfig(process.cwd());
|
|
23
131
|
|
|
24
132
|
// CLEAR PATH CONTRACT - CONVENTIONS
|
|
25
133
|
const PATHS = {
|
|
@@ -29,14 +137,20 @@ const PATHS = {
|
|
|
29
137
|
// Where the user's project root is (where they run `npx jux`)
|
|
30
138
|
projectRoot: process.cwd(),
|
|
31
139
|
|
|
32
|
-
// Where user's .jux source files live (
|
|
33
|
-
juxSource
|
|
140
|
+
// Where user's .jux source files live (CONVENTION: ./jux/)
|
|
141
|
+
get juxSource() {
|
|
142
|
+
return path.join(this.projectRoot, 'jux');
|
|
143
|
+
},
|
|
34
144
|
|
|
35
145
|
// Where jux lib files are (components, layouts, etc.)
|
|
36
|
-
juxLib
|
|
146
|
+
get juxLib() {
|
|
147
|
+
return path.join(this.packageRoot, 'lib');
|
|
148
|
+
},
|
|
37
149
|
|
|
38
|
-
// Where frontend build output goes (
|
|
39
|
-
frontendDist
|
|
150
|
+
// Where frontend build output goes (CONVENTION: ./jux-dist/)
|
|
151
|
+
get frontendDist() {
|
|
152
|
+
return path.join(this.projectRoot, 'jux-dist');
|
|
153
|
+
}
|
|
40
154
|
};
|
|
41
155
|
|
|
42
156
|
console.log('📍 JUX Paths:');
|
|
@@ -46,10 +160,6 @@ console.log(` Source: ${PATHS.juxSource}`);
|
|
|
46
160
|
console.log(` Output: ${PATHS.frontendDist}`);
|
|
47
161
|
console.log(` Lib: ${PATHS.juxLib}\n`);
|
|
48
162
|
|
|
49
|
-
const command = process.argv[2];
|
|
50
|
-
const watchMode = process.argv.includes('--watch');
|
|
51
|
-
const bundleMode = process.argv.includes('--bundle');
|
|
52
|
-
|
|
53
163
|
/**
|
|
54
164
|
* Recursively find .jux files in a directory
|
|
55
165
|
*/
|
|
@@ -78,9 +188,8 @@ function findJuxFiles(dir, fileList = []) {
|
|
|
78
188
|
* Build the entire JUX project (ALWAYS uses router bundle)
|
|
79
189
|
*
|
|
80
190
|
* @param {boolean} isServe - Whether building for dev server
|
|
81
|
-
* @param {number} wsPort - WebSocket port for hot reload
|
|
82
191
|
*/
|
|
83
|
-
async function buildProject(isServe = false
|
|
192
|
+
async function buildProject(isServe = false) {
|
|
84
193
|
const buildStartTime = performance.now();
|
|
85
194
|
console.log('🔨 Building JUX frontend...\n');
|
|
86
195
|
|
|
@@ -100,14 +209,14 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
100
209
|
|
|
101
210
|
// Step 1: Generate documentation
|
|
102
211
|
const docsStartTime = performance.now();
|
|
103
|
-
let docsTime = 0;
|
|
212
|
+
let docsTime = 0; // ✅ Declare with default value
|
|
104
213
|
console.log('📚 Generating documentation...');
|
|
105
214
|
try {
|
|
106
215
|
await generateDocs(PATHS.juxLib);
|
|
107
216
|
docsTime = performance.now() - docsStartTime;
|
|
108
217
|
console.log(`✅ Documentation generated (${docsTime.toFixed(0)}ms)\n`);
|
|
109
218
|
} catch (error) {
|
|
110
|
-
docsTime = performance.now() - docsStartTime;
|
|
219
|
+
docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
|
|
111
220
|
console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
|
|
112
221
|
}
|
|
113
222
|
|
|
@@ -123,7 +232,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
123
232
|
const presetsTime = performance.now() - presetsStartTime;
|
|
124
233
|
console.log(`⏱️ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
|
|
125
234
|
|
|
126
|
-
// Step 4: Copy project assets
|
|
235
|
+
// Step 4: Copy project assets (CSS, JS, images)
|
|
127
236
|
const assetsStartTime = performance.now();
|
|
128
237
|
await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
|
|
129
238
|
const assetsTime = performance.now() - assetsStartTime;
|
|
@@ -144,6 +253,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
144
253
|
process.exit(1);
|
|
145
254
|
}
|
|
146
255
|
|
|
256
|
+
// ✅ Bundle and get the generated filename
|
|
147
257
|
const bundleStartTime = performance.now();
|
|
148
258
|
const mainJsFilename = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
|
|
149
259
|
routePrefix: ''
|
|
@@ -173,14 +283,16 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
173
283
|
};
|
|
174
284
|
});
|
|
175
285
|
|
|
286
|
+
// ✅ Generate unified index.html
|
|
176
287
|
const indexStartTime = performance.now();
|
|
177
|
-
generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename
|
|
288
|
+
generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename);
|
|
178
289
|
const indexTime = performance.now() - indexStartTime;
|
|
179
290
|
|
|
180
291
|
const totalBuildTime = performance.now() - buildStartTime;
|
|
181
292
|
|
|
182
293
|
console.log(`\n✅ Bundled ${projectJuxFiles.length} page(s) → ${PATHS.frontendDist}/${mainJsFilename}\n`);
|
|
183
294
|
|
|
295
|
+
// ✅ Build summary with timing breakdown
|
|
184
296
|
console.log(`📊 Build Summary:`);
|
|
185
297
|
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
186
298
|
console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
|
|
@@ -193,6 +305,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
193
305
|
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
194
306
|
console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
|
|
195
307
|
|
|
308
|
+
// Show usage
|
|
196
309
|
if (!isServe) {
|
|
197
310
|
console.log('📦 Serve from your backend:');
|
|
198
311
|
console.log(` Express: app.use(express.static('jux-dist'))`);
|
|
@@ -215,102 +328,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
215
328
|
}
|
|
216
329
|
|
|
217
330
|
(async () => {
|
|
218
|
-
if (command === '
|
|
219
|
-
const projectName = process.argv[3] || 'my-jux-app';
|
|
220
|
-
const projectPath = path.join(PATHS.projectRoot, projectName);
|
|
221
|
-
|
|
222
|
-
console.log(`
|
|
223
|
-
╔═══════════════════════════════════════════════════════╗
|
|
224
|
-
║ ║
|
|
225
|
-
║ 🎨 Welcome to JUX ║
|
|
226
|
-
║ ║
|
|
227
|
-
║ Creating your new JUX project... ║
|
|
228
|
-
║ ║
|
|
229
|
-
╚═══════════════════════════════════════════════════════╝
|
|
230
|
-
`);
|
|
231
|
-
|
|
232
|
-
if (fs.existsSync(projectPath)) {
|
|
233
|
-
console.error(`❌ Directory "${projectName}" already exists.`);
|
|
234
|
-
console.error(` Please choose a different name or remove the existing directory.\n`);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const { execSync } = await import('child_process');
|
|
240
|
-
|
|
241
|
-
console.log(`📁 Creating directory: ${projectName}`);
|
|
242
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
243
|
-
process.chdir(projectPath);
|
|
244
|
-
|
|
245
|
-
console.log(`📦 Initializing package.json...`);
|
|
246
|
-
const packageJson = {
|
|
247
|
-
name: projectName,
|
|
248
|
-
version: '0.1.0',
|
|
249
|
-
type: 'module',
|
|
250
|
-
scripts: {
|
|
251
|
-
dev: 'jux serve',
|
|
252
|
-
build: 'jux build'
|
|
253
|
-
},
|
|
254
|
-
dependencies: {
|
|
255
|
-
juxscript: 'latest' // ✅ Changed from '^1.0.8' to 'latest'
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
259
|
-
console.log(` ✓ package.json created`);
|
|
260
|
-
|
|
261
|
-
console.log(`\n📥 Installing juxscript...\n`);
|
|
262
|
-
try {
|
|
263
|
-
execSync('npm install', { stdio: 'inherit' });
|
|
264
|
-
console.log(`\n ✓ Dependencies installed`);
|
|
265
|
-
} catch (err) {
|
|
266
|
-
console.error(`\n ⚠️ npm install failed, but continuing...`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
console.log(`\n🎨 Initializing JUX project structure...`);
|
|
270
|
-
execSync('npx jux init', { stdio: 'inherit' });
|
|
271
|
-
|
|
272
|
-
console.log(`\n📝 Creating .gitignore...`);
|
|
273
|
-
fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
|
|
274
|
-
console.log(` ✓ .gitignore created`);
|
|
275
|
-
|
|
276
|
-
console.log(`
|
|
277
|
-
╔═══════════════════════════════════════════════════════╗
|
|
278
|
-
║ ║
|
|
279
|
-
║ ✅ Project created successfully! ║
|
|
280
|
-
║ ║
|
|
281
|
-
╚═══════════════════════════════════════════════════════╝
|
|
282
|
-
|
|
283
|
-
📚 Resources:
|
|
284
|
-
Documentation: [coming soon]
|
|
285
|
-
GitHub: https://github.com/juxscript/jux
|
|
286
|
-
Examples: https://github.com/juxscript/examples
|
|
287
|
-
|
|
288
|
-
⭐ If you find JUX useful, please star us on GitHub!
|
|
289
|
-
🔒 Security: Report issues to security@juxscript.com [placeholder]
|
|
290
|
-
|
|
291
|
-
Say goodbye to markup </</>>>.
|
|
292
|
-
Happy javascripting your frontend! 🎉
|
|
293
|
-
|
|
294
|
-
Next steps, run: cd ${projectName} && npm run dev
|
|
295
|
-
`);
|
|
296
|
-
|
|
297
|
-
} catch (err) {
|
|
298
|
-
console.error(`\n❌ Project creation failed:`, err.message);
|
|
299
|
-
|
|
300
|
-
if (fs.existsSync(projectPath)) {
|
|
301
|
-
try {
|
|
302
|
-
process.chdir('..');
|
|
303
|
-
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
304
|
-
console.log(` ✓ Cleaned up failed project directory\n`);
|
|
305
|
-
} catch (cleanupErr) {
|
|
306
|
-
console.error(` ⚠️ Could not clean up directory\n`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
} else if (command === 'init') {
|
|
331
|
+
if (command === 'init') {
|
|
314
332
|
console.log('🎨 Initializing JUX project...\n');
|
|
315
333
|
|
|
316
334
|
const juxDir = PATHS.juxSource;
|
|
@@ -320,8 +338,10 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
320
338
|
process.exit(1);
|
|
321
339
|
}
|
|
322
340
|
|
|
341
|
+
// Create structure
|
|
323
342
|
fs.mkdirSync(juxDir, { recursive: true });
|
|
324
343
|
|
|
344
|
+
// Copy jux.jux as the starter index.jux (if it exists)
|
|
325
345
|
const juxJuxSrc = path.join(PATHS.packageRoot, 'presets', 'jux.jux');
|
|
326
346
|
const indexJuxDest = path.join(juxDir, 'index.jux');
|
|
327
347
|
|
|
@@ -329,6 +349,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
329
349
|
fs.copyFileSync(juxJuxSrc, indexJuxDest);
|
|
330
350
|
console.log('+ Created jux/index.jux from jux.jux template');
|
|
331
351
|
} else {
|
|
352
|
+
// Fallback to hey.jux if jux.jux doesn't exist
|
|
332
353
|
const heyJuxSrc = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
|
|
333
354
|
if (fs.existsSync(heyJuxSrc)) {
|
|
334
355
|
fs.copyFileSync(heyJuxSrc, indexJuxDest);
|
|
@@ -341,6 +362,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
341
362
|
}
|
|
342
363
|
}
|
|
343
364
|
|
|
365
|
+
// Copy entire presets folder to jux/presets/ (excluding jux.jux)
|
|
344
366
|
const presetsSrc = path.join(PATHS.packageRoot, 'presets');
|
|
345
367
|
const presetsDest = path.join(juxDir, 'presets');
|
|
346
368
|
|
|
@@ -354,6 +376,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
354
376
|
const srcPath = path.join(src, entry.name);
|
|
355
377
|
const destPath = path.join(dest, entry.name);
|
|
356
378
|
|
|
379
|
+
// Skip jux.jux since we already copied it to index.jux
|
|
357
380
|
if (entry.isFile() && entry.name === 'jux.jux') {
|
|
358
381
|
continue;
|
|
359
382
|
}
|
|
@@ -378,51 +401,37 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
378
401
|
}
|
|
379
402
|
}
|
|
380
403
|
|
|
404
|
+
// Create package.json if it doesn't exist
|
|
381
405
|
const pkgPath = path.join(PATHS.projectRoot, 'package.json');
|
|
382
406
|
if (!fs.existsSync(pkgPath)) {
|
|
383
|
-
// ✅ Get project name from current directory or default
|
|
384
|
-
const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
385
|
-
|
|
386
407
|
const pkgContent = {
|
|
387
|
-
"name":
|
|
388
|
-
"version": "
|
|
408
|
+
"name": "my-jux-project",
|
|
409
|
+
"version": "1.0.0",
|
|
389
410
|
"type": "module",
|
|
390
411
|
"scripts": {
|
|
391
|
-
"
|
|
392
|
-
"
|
|
412
|
+
"build": "jux build",
|
|
413
|
+
"serve": "jux serve"
|
|
393
414
|
},
|
|
394
415
|
"dependencies": {
|
|
395
|
-
"juxscript": "
|
|
416
|
+
"juxscript": "^1.0.8"
|
|
396
417
|
}
|
|
397
418
|
};
|
|
398
419
|
fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
|
|
399
420
|
console.log('+ Created package.json');
|
|
400
421
|
}
|
|
401
422
|
|
|
423
|
+
// Create .gitignore
|
|
402
424
|
const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
|
|
403
|
-
const gitignoreContent = `jux-dist
|
|
425
|
+
const gitignoreContent = `jux-dist/
|
|
426
|
+
node_modules/
|
|
427
|
+
.DS_Store
|
|
428
|
+
`;
|
|
404
429
|
|
|
405
430
|
if (!fs.existsSync(gitignorePath)) {
|
|
406
431
|
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
407
432
|
console.log('+ Created .gitignore');
|
|
408
433
|
}
|
|
409
434
|
|
|
410
|
-
// ✅ Create actual juxconfig.js (not just example)
|
|
411
|
-
const configSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
|
|
412
|
-
const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
|
|
413
|
-
|
|
414
|
-
if (fs.existsSync(configSrc) && !fs.existsSync(configDest)) {
|
|
415
|
-
fs.copyFileSync(configSrc, configDest);
|
|
416
|
-
console.log('+ Created juxconfig.js (customize as needed)');
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Also copy example as reference
|
|
420
|
-
const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
|
|
421
|
-
if (fs.existsSync(configSrc) && !fs.existsSync(configExampleDest)) {
|
|
422
|
-
fs.copyFileSync(configSrc, configExampleDest);
|
|
423
|
-
console.log('+ Created juxconfig.example.js (reference)');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
435
|
console.log('\n✅ JUX project initialized!\n');
|
|
427
436
|
console.log('Next steps:');
|
|
428
437
|
console.log(' npm install # Install dependencies');
|
|
@@ -430,15 +439,18 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
430
439
|
console.log('Check out the docs: https://juxscript.com/docs\n');
|
|
431
440
|
|
|
432
441
|
} else if (command === 'build') {
|
|
442
|
+
// ✅ Always builds router bundle
|
|
433
443
|
await buildProject(false);
|
|
434
444
|
console.log(`✅ Build complete: ${PATHS.frontendDist}`);
|
|
435
445
|
|
|
436
446
|
} else if (command === 'serve') {
|
|
437
|
-
|
|
438
|
-
|
|
447
|
+
// ✅ Always serves router bundle
|
|
448
|
+
await buildProject(true);
|
|
449
|
+
|
|
450
|
+
// Parse port arguments: npx jux serve [httpPort] [wsPort]
|
|
451
|
+
const httpPort = parseInt(process.argv[3]) || 3000;
|
|
452
|
+
const wsPort = parseInt(process.argv[4]) || 3001;
|
|
439
453
|
|
|
440
|
-
await runBootstrap(config.bootstrap);
|
|
441
|
-
await buildProject(true, wsPort);
|
|
442
454
|
await start(httpPort, wsPort);
|
|
443
455
|
|
|
444
456
|
} else {
|
|
@@ -446,15 +458,39 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
446
458
|
JUX CLI - A JavaScript UX authorship platform
|
|
447
459
|
|
|
448
460
|
Usage:
|
|
449
|
-
npx jux
|
|
450
|
-
npx jux init Initialize JUX in current directory
|
|
461
|
+
npx jux init Initialize a new JUX project
|
|
451
462
|
npx jux build Build router bundle to ./jux-dist/
|
|
452
463
|
npx jux serve [http] [ws] Start dev server with hot reload
|
|
453
464
|
|
|
465
|
+
Arguments:
|
|
466
|
+
[http] HTTP server port (default: 3000)
|
|
467
|
+
[ws] WebSocket port (default: 3001)
|
|
468
|
+
|
|
469
|
+
Project Structure:
|
|
470
|
+
my-project/
|
|
471
|
+
├── jux/ # Your .jux source files
|
|
472
|
+
│ ├── index.jux # Entry point
|
|
473
|
+
│ └── pages/ # Additional pages
|
|
474
|
+
├── jux-dist/ # Build output (git-ignore this)
|
|
475
|
+
├── server/ # Your backend
|
|
476
|
+
└── package.json
|
|
477
|
+
|
|
478
|
+
Import Style:
|
|
479
|
+
// In your project's .jux files
|
|
480
|
+
import { jux, state } from 'juxscript';
|
|
481
|
+
import 'juxscript/presets/notion.js';
|
|
482
|
+
|
|
483
|
+
Getting Started:
|
|
484
|
+
1. npx jux init # Create project structure
|
|
485
|
+
2. npm install # Install dependencies
|
|
486
|
+
3. npx jux serve # Dev server with hot reload
|
|
487
|
+
4. Serve jux-dist/ from your backend
|
|
488
|
+
|
|
454
489
|
Examples:
|
|
455
|
-
npx jux
|
|
456
|
-
npx jux serve
|
|
457
|
-
npx jux serve 8080
|
|
490
|
+
npx jux build # Build production bundle
|
|
491
|
+
npx jux serve # Dev server (ports 3000/3001)
|
|
492
|
+
npx jux serve 8080 # HTTP on 8080, WS on 3001
|
|
493
|
+
npx jux serve 8080 8081 # HTTP on 8080, WS on 8081
|
|
458
494
|
`);
|
|
459
495
|
}
|
|
460
496
|
})();
|
package/machinery/compiler.js
CHANGED
|
@@ -673,12 +673,16 @@ render();
|
|
|
673
673
|
*
|
|
674
674
|
* @param {string} distDir - Destination directory
|
|
675
675
|
* @param {Array<{path: string, functionName: string}>} routes - Route definitions
|
|
676
|
-
* @param {string} mainJsFilename - The generated main.js filename
|
|
677
|
-
* @param {number} wsPort - WebSocket port for hot reload
|
|
676
|
+
* @param {string} mainJsFilename - The generated main.js filename (e.g., 'main.1234567890.js')
|
|
678
677
|
*/
|
|
679
|
-
export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js'
|
|
678
|
+
export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
|
|
680
679
|
console.log('📄 Generating index.html...');
|
|
681
680
|
|
|
681
|
+
// Generate navigation links
|
|
682
|
+
const navLinks = routes
|
|
683
|
+
.map(r => ` <a href="${r.path}">${r.functionName.replace(/_/g, ' ')}</a>`)
|
|
684
|
+
.join(' |\n');
|
|
685
|
+
|
|
682
686
|
const importMapScript = generateImportMapScript();
|
|
683
687
|
|
|
684
688
|
const html = `<!DOCTYPE html>
|
|
@@ -693,62 +697,6 @@ export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js', w
|
|
|
693
697
|
<div id="app"></div>
|
|
694
698
|
${importMapScript}
|
|
695
699
|
<script type="module" src="/${mainJsFilename}"></script>
|
|
696
|
-
|
|
697
|
-
<!-- Hot Reload Script -->
|
|
698
|
-
<script>
|
|
699
|
-
(function() {
|
|
700
|
-
const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
|
|
701
|
-
|
|
702
|
-
ws.onopen = () => {
|
|
703
|
-
console.log('🔌 Hot reload connected');
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
ws.onmessage = (event) => {
|
|
707
|
-
const data = JSON.parse(event.data);
|
|
708
|
-
|
|
709
|
-
if (data.type === 'reload') {
|
|
710
|
-
console.log('🔄 Hot reload triggered - reloading page...');
|
|
711
|
-
location.reload();
|
|
712
|
-
} else if (data.type === 'css-reload') {
|
|
713
|
-
console.log('🎨 CSS hot reload:', data.path);
|
|
714
|
-
|
|
715
|
-
// Find all link tags pointing to this CSS file
|
|
716
|
-
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
717
|
-
links.forEach(link => {
|
|
718
|
-
if (link.href.includes(data.path)) {
|
|
719
|
-
const newLink = link.cloneNode();
|
|
720
|
-
newLink.href = data.path + '?t=' + Date.now();
|
|
721
|
-
link.parentNode.insertBefore(newLink, link.nextSibling);
|
|
722
|
-
setTimeout(() => link.remove(), 100);
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
// Also reload any @import in style tags
|
|
727
|
-
const styles = document.querySelectorAll('style');
|
|
728
|
-
styles.forEach(style => {
|
|
729
|
-
if (style.textContent.includes(data.path)) {
|
|
730
|
-
style.textContent = style.textContent.replace(
|
|
731
|
-
new RegExp(data.path + '(\\\\?t=\\\\d+)?', 'g'),
|
|
732
|
-
data.path + '?t=' + Date.now()
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
ws.onclose = () => {
|
|
740
|
-
console.log('🔌 Hot reload disconnected');
|
|
741
|
-
// Try to reconnect after 1 second
|
|
742
|
-
setTimeout(() => {
|
|
743
|
-
location.reload();
|
|
744
|
-
}, 1000);
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
ws.onerror = (error) => {
|
|
748
|
-
console.error('🔌 Hot reload error:', error);
|
|
749
|
-
};
|
|
750
|
-
})();
|
|
751
|
-
</script>
|
|
752
700
|
</body>
|
|
753
701
|
</html>`;
|
|
754
702
|
|
package/machinery/server.js
CHANGED
|
@@ -149,66 +149,4 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './jux-dist') {
|
|
|
149
149
|
|
|
150
150
|
export async function start(httpPort = 3000, wsPort = 3001) {
|
|
151
151
|
return serve(httpPort, wsPort, './jux-dist');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
|
|
155
|
-
const html = `<!DOCTYPE html>
|
|
156
|
-
<html lang="en">
|
|
157
|
-
<head>
|
|
158
|
-
<meta charset="UTF-8">
|
|
159
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
160
|
-
<title>Jux Application</title>
|
|
161
|
-
</head>
|
|
162
|
-
<body data-theme="">
|
|
163
|
-
<div id="app"></div>
|
|
164
|
-
${importMapScript}
|
|
165
|
-
<script type="module" src="/${mainJsFilename}"></script>
|
|
166
|
-
|
|
167
|
-
<!-- Hot Reload Script -->
|
|
168
|
-
<script>
|
|
169
|
-
(function() {
|
|
170
|
-
const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
|
|
171
|
-
|
|
172
|
-
ws.onmessage = (event) => {
|
|
173
|
-
const data = JSON.parse(event.data);
|
|
174
|
-
|
|
175
|
-
if (data.type === 'reload') {
|
|
176
|
-
console.log('🔄 Hot reload triggered');
|
|
177
|
-
location.reload();
|
|
178
|
-
} else if (data.type === 'css-reload') {
|
|
179
|
-
console.log('🎨 CSS hot reload:', data.path);
|
|
180
|
-
|
|
181
|
-
// Find all link tags pointing to this CSS file
|
|
182
|
-
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
183
|
-
links.forEach(link => {
|
|
184
|
-
if (link.href.includes(data.path)) {
|
|
185
|
-
const newLink = link.cloneNode();
|
|
186
|
-
newLink.href = data.path + '?t=' + Date.now();
|
|
187
|
-
link.parentNode.insertBefore(newLink, link.nextSibling);
|
|
188
|
-
setTimeout(() => link.remove(), 100);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Also reload any @import in style tags
|
|
193
|
-
const styles = document.querySelectorAll('style');
|
|
194
|
-
styles.forEach(style => {
|
|
195
|
-
if (style.textContent.includes(data.path)) {
|
|
196
|
-
style.textContent = style.textContent.replace(
|
|
197
|
-
new RegExp(data.path + '(\\?t=\\d+)?', 'g'),
|
|
198
|
-
data.path + '?t=' + Date.now()
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
ws.onclose = () => {
|
|
206
|
-
console.log('🔌 Hot reload disconnected');
|
|
207
|
-
};
|
|
208
|
-
})();
|
|
209
|
-
</script>
|
|
210
|
-
</body>
|
|
211
|
-
</html>`;
|
|
212
|
-
|
|
213
|
-
return html;
|
|
214
152
|
}
|
package/machinery/watcher.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
copyLibToOutput,
|
|
5
|
+
bundleJuxFilesToRouter,
|
|
6
|
+
generateIndexHtml
|
|
7
|
+
} from './compiler.js';
|
|
4
8
|
|
|
5
9
|
let isRebuilding = false;
|
|
6
10
|
let rebuildQueued = false;
|
|
@@ -115,130 +119,53 @@ async function fullRebuild(juxSource, distDir) {
|
|
|
115
119
|
* Start watching for file changes and rebuild on change
|
|
116
120
|
*/
|
|
117
121
|
export function startWatcher(juxSource, distDir, wsClients) {
|
|
118
|
-
console.log(`👀 Watching
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
console.log(`👀 Watching: ${juxSource}`);
|
|
123
|
+
|
|
124
|
+
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;
|
|
126
132
|
}
|
|
127
|
-
debounceTimers.set(key, setTimeout(() => {
|
|
128
|
-
debounceTimers.delete(key);
|
|
129
|
-
fn();
|
|
130
|
-
}, delay));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Recursively watch directories
|
|
134
|
-
function watchRecursive(dir) {
|
|
135
|
-
if (!fs.existsSync(dir)) return;
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
139
|
-
if (!filename) return;
|
|
140
|
-
|
|
141
|
-
const filePath = path.join(dir, filename);
|
|
142
|
-
const ext = path.extname(filename);
|
|
143
|
-
|
|
144
|
-
// Skip certain patterns
|
|
145
|
-
if (filename.includes('node_modules') ||
|
|
146
|
-
filename.includes('jux-dist') ||
|
|
147
|
-
filename.includes('.git') ||
|
|
148
|
-
filename.startsWith('.')) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Debounce to avoid multiple rapid events
|
|
153
|
-
debounce(filePath, async () => {
|
|
154
|
-
// ✅ Handle CSS files
|
|
155
|
-
if (ext === '.css') {
|
|
156
|
-
console.log(`\n🎨 CSS changed: ${filename}`);
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const relativePath = path.relative(juxSource, filePath);
|
|
160
|
-
const destPath = path.join(distDir, relativePath);
|
|
161
|
-
const destDir = path.dirname(destPath);
|
|
162
|
-
|
|
163
|
-
if (!fs.existsSync(destDir)) {
|
|
164
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fs.copyFileSync(filePath, destPath);
|
|
168
|
-
console.log(` ✓ Copied to: ${path.relative(process.cwd(), destPath)}`);
|
|
169
|
-
|
|
170
|
-
// Notify browser to reload CSS
|
|
171
|
-
wsClients.forEach(client => {
|
|
172
|
-
if (client.readyState === 1) {
|
|
173
|
-
client.send(JSON.stringify({
|
|
174
|
-
type: 'css-reload',
|
|
175
|
-
path: `/${relativePath}`
|
|
176
|
-
}));
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
console.log(` 🔄 Browser CSS reloaded\n`);
|
|
181
|
-
} catch (err) {
|
|
182
|
-
console.error(` ❌ CSS copy failed:`, err.message);
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ✅ Handle .jux files
|
|
188
|
-
if (ext === '.jux') {
|
|
189
|
-
console.log(`\n📝 File changed: ${filename}`);
|
|
190
|
-
console.log(' 🔨 Rebuilding...');
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
await bundleJuxFilesToRouter(juxSource, distDir, { routePrefix: '' });
|
|
194
|
-
|
|
195
|
-
wsClients.forEach(client => {
|
|
196
|
-
if (client.readyState === 1) {
|
|
197
|
-
client.send(JSON.stringify({ type: 'reload' }));
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
console.log(' ✅ Rebuild complete');
|
|
202
|
-
console.log(' 🔄 Browser reloaded\n');
|
|
203
|
-
} catch (err) {
|
|
204
|
-
console.error(' ❌ Rebuild failed:', err.message);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
133
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
134
|
+
// Debounce: If already rebuilding, queue another rebuild
|
|
135
|
+
if (isRebuilding) {
|
|
136
|
+
rebuildQueued = true;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
211
139
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const destPath = path.join(distDir, relativePath);
|
|
215
|
-
const destDir = path.dirname(destPath);
|
|
140
|
+
isRebuilding = true;
|
|
141
|
+
console.log(`\n📝 File changed: ${filename}`);
|
|
216
142
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
143
|
+
// Rebuild the entire bundle
|
|
144
|
+
const success = await fullRebuild(juxSource, distDir);
|
|
220
145
|
|
|
221
|
-
|
|
222
|
-
console.log(` ✓ Copied to: ${path.relative(process.cwd(), destPath)}`);
|
|
146
|
+
isRebuilding = false;
|
|
223
147
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
});
|
|
148
|
+
// Notify all WebSocket clients to reload
|
|
149
|
+
if (success && wsClients && wsClients.length > 0) {
|
|
150
|
+
console.log(`🔌 Notifying ${wsClients.length} client(s) to reload`);
|
|
229
151
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
152
|
+
// ✅ Add small delay to ensure file is fully written
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
wsClients.forEach(client => {
|
|
155
|
+
if (client.readyState === 1) { // OPEN
|
|
156
|
+
client.send(JSON.stringify({ type: 'reload' }));
|
|
234
157
|
}
|
|
235
158
|
});
|
|
236
|
-
});
|
|
237
|
-
} catch (err) {
|
|
238
|
-
console.error(` ⚠️ Failed to watch ${dir}:`, err.message);
|
|
159
|
+
}, 100);
|
|
239
160
|
}
|
|
240
|
-
}
|
|
241
161
|
|
|
242
|
-
|
|
243
|
-
|
|
162
|
+
// Process queued rebuild if needed
|
|
163
|
+
if (rebuildQueued) {
|
|
164
|
+
rebuildQueued = false;
|
|
165
|
+
console.log('🔄 Processing queued rebuild...');
|
|
166
|
+
setTimeout(() => watcher.emit('change', 'change', filename), 500);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return watcher;
|
|
244
171
|
}
|
package/package.json
CHANGED
package/machinery/config.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Default JUX configuration
|
|
6
|
-
*/
|
|
7
|
-
export const defaultConfig = {
|
|
8
|
-
// Source directory for .jux files
|
|
9
|
-
sourceDir: 'jux',
|
|
10
|
-
|
|
11
|
-
// Output directory for built files
|
|
12
|
-
distDir: 'jux-dist',
|
|
13
|
-
|
|
14
|
-
// Dev server ports
|
|
15
|
-
ports: {
|
|
16
|
-
http: 3000,
|
|
17
|
-
ws: 3001
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
// Build options
|
|
21
|
-
build: {
|
|
22
|
-
minify: false,
|
|
23
|
-
sourcemap: true
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
// Bootstrap functions (run before app starts)
|
|
27
|
-
bootstrap: []
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Load juxconfig.js from project root
|
|
32
|
-
* @param {string} projectRoot - Project root directory
|
|
33
|
-
* @returns {object} Merged configuration
|
|
34
|
-
*/
|
|
35
|
-
export async function loadConfig(projectRoot) {
|
|
36
|
-
const configPath = path.join(projectRoot, 'juxconfig.js');
|
|
37
|
-
|
|
38
|
-
if (!fs.existsSync(configPath)) {
|
|
39
|
-
console.log('ℹ️ No juxconfig.js found, using defaults');
|
|
40
|
-
return defaultConfig;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
console.log(`📋 Loading config from: ${configPath}`);
|
|
45
|
-
|
|
46
|
-
// Dynamic import for ES modules
|
|
47
|
-
const configUrl = `file://${configPath}`;
|
|
48
|
-
const { default: userConfig } = await import(configUrl);
|
|
49
|
-
|
|
50
|
-
// Merge with defaults
|
|
51
|
-
const config = {
|
|
52
|
-
...defaultConfig,
|
|
53
|
-
...userConfig,
|
|
54
|
-
ports: {
|
|
55
|
-
...defaultConfig.ports,
|
|
56
|
-
...(userConfig.ports || {})
|
|
57
|
-
},
|
|
58
|
-
build: {
|
|
59
|
-
...defaultConfig.build,
|
|
60
|
-
...(userConfig.build || {})
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
console.log(` ✓ Config loaded`);
|
|
65
|
-
console.log(` Source: ${config.sourceDir}`);
|
|
66
|
-
console.log(` Output: ${config.distDir}\n`);
|
|
67
|
-
|
|
68
|
-
return config;
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.warn(`⚠️ Failed to load juxconfig.js:`, err.message);
|
|
71
|
-
console.warn(` Using default configuration\n`);
|
|
72
|
-
return defaultConfig;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Run bootstrap functions from config
|
|
78
|
-
* @param {Array<Function>} bootstrapFns - Array of bootstrap functions
|
|
79
|
-
*/
|
|
80
|
-
export async function runBootstrap(bootstrapFns) {
|
|
81
|
-
if (!bootstrapFns || bootstrapFns.length === 0) return;
|
|
82
|
-
|
|
83
|
-
console.log(`🚀 Running ${bootstrapFns.length} bootstrap function(s)...`);
|
|
84
|
-
|
|
85
|
-
for (const fn of bootstrapFns) {
|
|
86
|
-
try {
|
|
87
|
-
await fn();
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error(` ❌ Bootstrap function failed:`, err.message);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log(` ✓ Bootstrap complete\n`);
|
|
94
|
-
}
|