vaderjs 2.3.18 → 2.3.19
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/main.ts +137 -99
- package/package.json +1 -1
package/main.ts
CHANGED
|
@@ -5,7 +5,6 @@ import fs from "fs/promises";
|
|
|
5
5
|
import fsSync from "fs";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { initProject } from "vaderjs/cli";
|
|
8
|
-
import { rimraf } from "rimraf";
|
|
9
8
|
|
|
10
9
|
const colors = {
|
|
11
10
|
reset: "\x1b[0m",
|
|
@@ -37,7 +36,7 @@ async function timedStep(name, fn) {
|
|
|
37
36
|
logger.success(`Finished '${name}' in ${duration}ms`);
|
|
38
37
|
} catch (e) {
|
|
39
38
|
logger.error(`Error during '${name}':`, e);
|
|
40
|
-
if (!isDev) process.exit(1);
|
|
39
|
+
if (!globalThis.isDev) process.exit(1);
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
|
|
@@ -89,10 +88,8 @@ class HTMLInjectionManager {
|
|
|
89
88
|
private injections: Map<string, string> = new Map();
|
|
90
89
|
|
|
91
90
|
add(html: string, id?: string): void {
|
|
92
|
-
// Generate an ID if not provided (based on content hash)
|
|
93
91
|
const injectionId = id || this.generateId(html);
|
|
94
92
|
|
|
95
|
-
// Only add if not already present
|
|
96
93
|
if (!this.injections.has(injectionId)) {
|
|
97
94
|
this.injections.set(injectionId, html);
|
|
98
95
|
logger.info(`Added HTML injection: ${injectionId}`);
|
|
@@ -102,26 +99,21 @@ class HTMLInjectionManager {
|
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
private generateId(html: string): string {
|
|
105
|
-
// Simple ID generation based on content
|
|
106
|
-
// For CSS links, extract the href
|
|
107
102
|
const hrefMatch = html.match(/href=["']([^"']+)["']/);
|
|
108
103
|
if (hrefMatch) {
|
|
109
104
|
return `link:${hrefMatch[1]}`;
|
|
110
105
|
}
|
|
111
106
|
|
|
112
|
-
// For script tags, extract the src
|
|
113
107
|
const srcMatch = html.match(/src=["']([^"']+)["']/);
|
|
114
108
|
if (srcMatch) {
|
|
115
109
|
return `script:${srcMatch[1]}`;
|
|
116
110
|
}
|
|
117
111
|
|
|
118
|
-
// For meta tags, extract name or property
|
|
119
112
|
const nameMatch = html.match(/(?:name|property)=["']([^"']+)["']/);
|
|
120
113
|
if (nameMatch) {
|
|
121
114
|
return `meta:${nameMatch[1]}`;
|
|
122
115
|
}
|
|
123
116
|
|
|
124
|
-
// Fallback to hash of content
|
|
125
117
|
let hash = 0;
|
|
126
118
|
for (let i = 0; i < html.length; i++) {
|
|
127
119
|
hash = ((hash << 5) - hash) + html.charCodeAt(i);
|
|
@@ -137,10 +129,6 @@ class HTMLInjectionManager {
|
|
|
137
129
|
clear(): void {
|
|
138
130
|
this.injections.clear();
|
|
139
131
|
}
|
|
140
|
-
|
|
141
|
-
has(id: string): boolean {
|
|
142
|
-
return this.injections.has(id);
|
|
143
|
-
}
|
|
144
132
|
}
|
|
145
133
|
|
|
146
134
|
const htmlInjectionManager = new HTMLInjectionManager();
|
|
@@ -182,7 +170,6 @@ async function runPluginHook(hookName: 'onBuildStart' | 'onBuildFinish', api: Pl
|
|
|
182
170
|
|
|
183
171
|
// Load plugins from config
|
|
184
172
|
async function loadPluginsFromConfig() {
|
|
185
|
-
console.log(config)
|
|
186
173
|
if (!config.plugins || !Array.isArray(config.plugins)) {
|
|
187
174
|
logger.info("No plugins defined in config");
|
|
188
175
|
return;
|
|
@@ -194,32 +181,26 @@ async function loadPluginsFromConfig() {
|
|
|
194
181
|
try {
|
|
195
182
|
let plugin;
|
|
196
183
|
|
|
197
|
-
// If plugin is a string, import it
|
|
198
184
|
if (typeof pluginConfig === 'string') {
|
|
199
185
|
const pluginPath = path.isAbsolute(pluginConfig)
|
|
200
186
|
? pluginConfig
|
|
201
187
|
: path.join(PROJECT_ROOT, pluginConfig);
|
|
202
188
|
|
|
203
|
-
// Check if file exists
|
|
204
189
|
if (fsSync.existsSync(pluginPath)) {
|
|
205
190
|
const pluginModule = await import(pluginPath);
|
|
206
191
|
plugin = pluginModule.default || pluginModule;
|
|
207
192
|
} else {
|
|
208
|
-
// Try as npm package
|
|
209
193
|
plugin = await import(pluginConfig);
|
|
210
194
|
}
|
|
211
195
|
}
|
|
212
|
-
// If plugin is an object with resolve property
|
|
213
196
|
else if (typeof pluginConfig === 'object' && pluginConfig.resolve) {
|
|
214
197
|
const pluginModule = await import(pluginConfig.resolve);
|
|
215
198
|
plugin = pluginModule.default || pluginModule;
|
|
216
199
|
|
|
217
|
-
// Pass options to plugin if it's a factory function
|
|
218
200
|
if (typeof plugin === 'function' && pluginConfig.options) {
|
|
219
201
|
plugin = await plugin(pluginConfig.options);
|
|
220
202
|
}
|
|
221
203
|
}
|
|
222
|
-
// If plugin is already a plugin object
|
|
223
204
|
else if (typeof pluginConfig === 'object' && pluginConfig.name) {
|
|
224
205
|
plugin = pluginConfig;
|
|
225
206
|
}
|
|
@@ -238,7 +219,7 @@ async function loadPluginsFromConfig() {
|
|
|
238
219
|
plugins = loadedPlugins;
|
|
239
220
|
}
|
|
240
221
|
|
|
241
|
-
// Also load from plugins directory
|
|
222
|
+
// Also load from plugins directory
|
|
242
223
|
async function loadPluginsFromDirectory() {
|
|
243
224
|
const pluginsDir = path.join(PROJECT_ROOT, "plugins");
|
|
244
225
|
|
|
@@ -257,7 +238,6 @@ async function loadPluginsFromDirectory() {
|
|
|
257
238
|
const plugin = pluginModule.default || pluginModule;
|
|
258
239
|
|
|
259
240
|
if (plugin && typeof plugin === 'object' && plugin.name) {
|
|
260
|
-
// Check if plugin already loaded from config
|
|
261
241
|
if (!plugins.some(p => p.name === plugin.name)) {
|
|
262
242
|
loadedPlugins.push(plugin);
|
|
263
243
|
logger.info(`Loaded plugin from directory: ${plugin.name} v${plugin.version}`);
|
|
@@ -279,7 +259,6 @@ async function loadPluginsFromDirectory() {
|
|
|
279
259
|
async function ensureJSConfig() {
|
|
280
260
|
const jsconfigPath = path.join(PROJECT_ROOT, "jsconfig.json");
|
|
281
261
|
|
|
282
|
-
// Check if jsconfig.json already exists
|
|
283
262
|
let existingConfig = {};
|
|
284
263
|
if (fsSync.existsSync(jsconfigPath)) {
|
|
285
264
|
try {
|
|
@@ -290,7 +269,6 @@ async function ensureJSConfig() {
|
|
|
290
269
|
}
|
|
291
270
|
}
|
|
292
271
|
|
|
293
|
-
// Define the required VaderJS configuration
|
|
294
272
|
const vaderConfig = {
|
|
295
273
|
compilerOptions: {
|
|
296
274
|
jsx: "react",
|
|
@@ -299,7 +277,6 @@ async function ensureJSConfig() {
|
|
|
299
277
|
}
|
|
300
278
|
};
|
|
301
279
|
|
|
302
|
-
// Merge with existing config (preserve other settings)
|
|
303
280
|
const mergedConfig = {
|
|
304
281
|
...existingConfig,
|
|
305
282
|
compilerOptions: {
|
|
@@ -308,7 +285,6 @@ async function ensureJSConfig() {
|
|
|
308
285
|
}
|
|
309
286
|
};
|
|
310
287
|
|
|
311
|
-
// Write the config
|
|
312
288
|
await fs.writeFile(jsconfigPath, JSON.stringify(mergedConfig, null, 2));
|
|
313
289
|
logger.success(`jsconfig.json created/updated at ${jsconfigPath}`);
|
|
314
290
|
}
|
|
@@ -339,7 +315,6 @@ class FileWatcher {
|
|
|
339
315
|
)
|
|
340
316
|
return;
|
|
341
317
|
|
|
342
|
-
// Debounce file changes
|
|
343
318
|
this.pendingFiles.add(file);
|
|
344
319
|
|
|
345
320
|
if (this.debounceTimer) {
|
|
@@ -350,13 +325,12 @@ class FileWatcher {
|
|
|
350
325
|
const files = Array.from(this.pendingFiles);
|
|
351
326
|
this.pendingFiles.clear();
|
|
352
327
|
|
|
353
|
-
// Only trigger if not already rebuilding
|
|
354
328
|
if (!this.rebuildInProgress) {
|
|
355
329
|
this.callbacks.forEach((cb) => {
|
|
356
330
|
files.forEach(file => cb(file));
|
|
357
331
|
});
|
|
358
332
|
}
|
|
359
|
-
}, 100);
|
|
333
|
+
}, 100);
|
|
360
334
|
});
|
|
361
335
|
|
|
362
336
|
this.watchers.set(dir, watcher);
|
|
@@ -503,7 +477,6 @@ async function copyPublicAssets() {
|
|
|
503
477
|
}
|
|
504
478
|
}
|
|
505
479
|
|
|
506
|
-
// Helper function to find App file
|
|
507
480
|
function findAppFile(): string | null {
|
|
508
481
|
const possiblePaths = [
|
|
509
482
|
path.join(PROJECT_ROOT, "App.tsx"),
|
|
@@ -523,28 +496,23 @@ function findAppFile(): string | null {
|
|
|
523
496
|
return null;
|
|
524
497
|
}
|
|
525
498
|
|
|
526
|
-
// Helper to deduplicate HTML injections
|
|
527
499
|
function getUniqueInjections(injections: string[]): string[] {
|
|
528
500
|
const seen = new Set<string>();
|
|
529
501
|
const unique: string[] = [];
|
|
530
502
|
|
|
531
503
|
for (const injection of injections) {
|
|
532
|
-
// Create a normalized key for comparison
|
|
533
504
|
let key = injection;
|
|
534
505
|
|
|
535
|
-
// For link tags, normalize by href
|
|
536
506
|
const hrefMatch = injection.match(/href=["']([^"']+)["']/);
|
|
537
507
|
if (hrefMatch) {
|
|
538
508
|
key = `link:${hrefMatch[1]}`;
|
|
539
509
|
}
|
|
540
510
|
|
|
541
|
-
// For script tags, normalize by src
|
|
542
511
|
const srcMatch = injection.match(/src=["']([^"']+)["']/);
|
|
543
512
|
if (srcMatch) {
|
|
544
513
|
key = `script:${srcMatch[1]}`;
|
|
545
514
|
}
|
|
546
515
|
|
|
547
|
-
// For meta tags, normalize by name/property
|
|
548
516
|
const nameMatch = injection.match(/(?:name|property)=["']([^"']+)["']/);
|
|
549
517
|
if (nameMatch) {
|
|
550
518
|
key = `meta:${nameMatch[1]}`;
|
|
@@ -559,9 +527,107 @@ function getUniqueInjections(injections: string[]): string[] {
|
|
|
559
527
|
return unique;
|
|
560
528
|
}
|
|
561
529
|
|
|
562
|
-
|
|
530
|
+
// --- VERCEL CONFIG GENERATION ---
|
|
531
|
+
|
|
532
|
+
async function generateVercelConfig(routes: { route: string; htmlPath: string }[]) {
|
|
533
|
+
if (config.host_provider !== "vercel") {
|
|
534
|
+
logger.info("Hosting provider is not 'vercel', skipping vercel.json generation");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
logger.info("🔧 Generating Vercel configuration for deployment...");
|
|
539
|
+
|
|
540
|
+
// Build the vercel.json configuration
|
|
541
|
+
const vercelConfig: any = {
|
|
542
|
+
version: 2,
|
|
543
|
+
buildCommand: "bun run build",
|
|
544
|
+
outputDirectory: "dist",
|
|
545
|
+
installCommand: "bun install",
|
|
546
|
+
framework: null,
|
|
547
|
+
rewrites: [],
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// Add specific rewrites for each generated HTML route
|
|
551
|
+
for (const route of routes) {
|
|
552
|
+
if (route.route === "index") {
|
|
553
|
+
// Root route
|
|
554
|
+
vercelConfig.rewrites.push({
|
|
555
|
+
source: "/",
|
|
556
|
+
destination: "/dist/index.html",
|
|
557
|
+
});
|
|
558
|
+
} else {
|
|
559
|
+
// Nested route - exact match
|
|
560
|
+
vercelConfig.rewrites.push({
|
|
561
|
+
source: `/${route.route}`,
|
|
562
|
+
destination: `/dist/${route.route}/index.html`,
|
|
563
|
+
});
|
|
564
|
+
// Also handle subpaths (for client-side routing)
|
|
565
|
+
vercelConfig.rewrites.push({
|
|
566
|
+
source: `/${route.route}/:path*`,
|
|
567
|
+
destination: `/dist/${route.route}/index.html`,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Fallback for static assets (CSS, JS, images)
|
|
573
|
+
vercelConfig.rewrites.push({
|
|
574
|
+
source: "/:path*",
|
|
575
|
+
destination: "/dist/:path*",
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// Final fallback for any unmatched routes (SPA behavior)
|
|
579
|
+
vercelConfig.rewrites.push({
|
|
580
|
+
source: "/(.*)",
|
|
581
|
+
destination: "/dist/index.html",
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Remove duplicate rewrites (keep first occurrence for each source pattern)
|
|
585
|
+
const uniqueRewrites = [];
|
|
586
|
+
const seenSources = new Set();
|
|
587
|
+
for (const rewrite of vercelConfig.rewrites) {
|
|
588
|
+
if (!seenSources.has(rewrite.source)) {
|
|
589
|
+
seenSources.add(rewrite.source);
|
|
590
|
+
uniqueRewrites.push(rewrite);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
vercelConfig.rewrites = uniqueRewrites;
|
|
594
|
+
|
|
595
|
+
// Write vercel.json to PROJECT ROOT (not inside dist)
|
|
596
|
+
const vercelPath = path.join(PROJECT_ROOT, "vercel.json");
|
|
597
|
+
await fs.writeFile(vercelPath, JSON.stringify(vercelConfig, null, 2));
|
|
598
|
+
|
|
599
|
+
logger.success(`✅ vercel.json generated at ${vercelPath}`);
|
|
600
|
+
logger.info(` Routes configured to serve from ./dist directory`);
|
|
601
|
+
logger.info(` ${routes.length} route(s) configured`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async function generateVercelProjectConfig() {
|
|
605
|
+
if (config.hosting !== "vercel") return;
|
|
606
|
+
|
|
607
|
+
const vercelDir = path.join(PROJECT_ROOT, ".vercel");
|
|
608
|
+
await fs.mkdir(vercelDir, { recursive: true });
|
|
609
|
+
|
|
610
|
+
const projectConfig = {
|
|
611
|
+
projectId: config.vercelProjectId || "",
|
|
612
|
+
orgId: config.vercelOrgId || "",
|
|
613
|
+
settings: {
|
|
614
|
+
framework: null,
|
|
615
|
+
devCommand: "bun run dev",
|
|
616
|
+
installCommand: "bun install",
|
|
617
|
+
buildCommand: "bun run build",
|
|
618
|
+
outputDirectory: "dist",
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
const vercelProjectPath = path.join(vercelDir, "project.json");
|
|
623
|
+
await fs.writeFile(vercelProjectPath, JSON.stringify(projectConfig, null, 2));
|
|
624
|
+
logger.info(`📁 Generated .vercel/project.json`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// --- BUILD APP ENTRYPOINTS ---
|
|
628
|
+
|
|
629
|
+
async function buildAppEntrypoints() {
|
|
563
630
|
|
|
564
|
-
// Get unique HTML injections from plugins
|
|
565
631
|
const allInjections = htmlInjectionManager.getAll();
|
|
566
632
|
const uniqueInjections = getUniqueInjections(allInjections);
|
|
567
633
|
const htmlInjectionsString = uniqueInjections.join('\n ');
|
|
@@ -570,34 +636,31 @@ function getUniqueInjections(injections: string[]): string[] {
|
|
|
570
636
|
logger.info(`Injecting ${uniqueInjections.length} unique HTML items into page`);
|
|
571
637
|
}
|
|
572
638
|
|
|
573
|
-
|
|
574
|
-
// Handle app directory structure with nested routes
|
|
575
639
|
if (!fsSync.existsSync(APP_DIR)) {
|
|
576
|
-
logger.warn("No
|
|
640
|
+
logger.warn("No app directory found");
|
|
577
641
|
return;
|
|
578
642
|
}
|
|
579
643
|
|
|
580
|
-
// Find all index.{tsx,jsx} files in app directory (including nested)
|
|
581
644
|
const entrypoints: { route: string; path: string }[] = [];
|
|
582
|
-
|
|
645
|
+
const generatedRoutes: { route: string; htmlPath: string }[] = [];
|
|
646
|
+
|
|
583
647
|
function findIndexFiles(dir: string, baseRoute: string = "") {
|
|
584
648
|
const entries = fsSync.readdirSync(dir, { withFileTypes: true });
|
|
585
|
-
console.log(entries)
|
|
586
649
|
|
|
587
650
|
for (const entry of entries) {
|
|
588
651
|
const fullPath = path.join(dir, entry.name);
|
|
589
652
|
const routePath = path.join(baseRoute, entry.name);
|
|
590
653
|
|
|
591
654
|
if (entry.isDirectory()) {
|
|
592
|
-
// Recursively search subdirectories
|
|
593
655
|
findIndexFiles(fullPath, routePath);
|
|
594
656
|
} else if (entry.name.match(/^index\.(tsx|jsx)$/)) {
|
|
595
|
-
// Found an index file
|
|
596
657
|
let route = baseRoute;
|
|
597
658
|
|
|
598
|
-
// Special handling for root index file
|
|
599
659
|
if (route === "") {
|
|
600
660
|
route = "index";
|
|
661
|
+
} else {
|
|
662
|
+
// Convert Windows backslashes to forward slashes for URL
|
|
663
|
+
route = route.replace(/\\/g, '/');
|
|
601
664
|
}
|
|
602
665
|
|
|
603
666
|
entrypoints.push({
|
|
@@ -617,19 +680,13 @@ function getUniqueInjections(injections: string[]): string[] {
|
|
|
617
680
|
return;
|
|
618
681
|
}
|
|
619
682
|
|
|
620
|
-
// Build each entrypoint
|
|
621
683
|
for (const entry of entrypoints) {
|
|
622
|
-
// Determine output directory based on route
|
|
623
684
|
let outDir: string;
|
|
624
685
|
let outputName = "index.js";
|
|
625
686
|
|
|
626
|
-
console.log(entry)
|
|
627
|
-
|
|
628
687
|
if (entry.route === "index") {
|
|
629
|
-
// Root route goes to dist root
|
|
630
688
|
outDir = DIST_DIR;
|
|
631
689
|
} else {
|
|
632
|
-
// Nested route goes to corresponding subdirectory
|
|
633
690
|
outDir = path.join(DIST_DIR, entry.route);
|
|
634
691
|
}
|
|
635
692
|
|
|
@@ -645,16 +702,25 @@ function getUniqueInjections(injections: string[]): string[] {
|
|
|
645
702
|
jsxFragment: "Fragment",
|
|
646
703
|
jsxImportSource: "vaderjs",
|
|
647
704
|
naming: outputName,
|
|
648
|
-
external: [],
|
|
705
|
+
external: [],
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const htmlPath = path.join(outDir, "index.html");
|
|
709
|
+
generatedRoutes.push({
|
|
710
|
+
route: entry.route,
|
|
711
|
+
htmlPath: htmlPath
|
|
649
712
|
});
|
|
650
713
|
|
|
651
|
-
|
|
714
|
+
const pageTitle = entry.route === "index"
|
|
715
|
+
? (config.title || 'Vader App')
|
|
716
|
+
: `${config.title || 'Vader App'} - ${entry.route.charAt(0).toUpperCase() + entry.route.slice(1)}`;
|
|
717
|
+
|
|
652
718
|
const html = `<!DOCTYPE html>
|
|
653
719
|
<html>
|
|
654
720
|
<head>
|
|
655
721
|
<meta charset="UTF-8"/>
|
|
656
722
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
657
|
-
<title>${
|
|
723
|
+
<title>${pageTitle}</title>
|
|
658
724
|
${htmlInjectionsString}
|
|
659
725
|
</head>
|
|
660
726
|
<body>
|
|
@@ -663,25 +729,25 @@ ${htmlInjectionsString}
|
|
|
663
729
|
</body>
|
|
664
730
|
</html>`;
|
|
665
731
|
|
|
666
|
-
const htmlPath = path.join(outDir, "index.html");
|
|
667
732
|
await fs.writeFile(htmlPath, html);
|
|
668
|
-
|
|
669
733
|
logger.success(`Generated ${htmlPath}`);
|
|
670
734
|
}
|
|
671
735
|
|
|
672
|
-
// Log summary
|
|
673
736
|
logger.success(`Built ${entrypoints.length} routes: ${entrypoints.map(e => e.route).join(', ')}`);
|
|
737
|
+
|
|
738
|
+
// Generate Vercel config if hosting is set to vercel
|
|
739
|
+
if (config.host_provider === "vercel") {
|
|
740
|
+
await generateVercelConfig(generatedRoutes);
|
|
741
|
+
}
|
|
674
742
|
}
|
|
675
743
|
|
|
676
744
|
// Windows-compatible directory removal
|
|
677
745
|
async function removeDirectory(dir: string) {
|
|
678
746
|
try {
|
|
679
747
|
if (fsSync.existsSync(dir)) {
|
|
680
|
-
// Use fs.rm with recursive option for Windows compatibility
|
|
681
748
|
await fs.rm(dir, { recursive: true, force: true });
|
|
682
749
|
}
|
|
683
750
|
} catch (error) {
|
|
684
|
-
// Fallback: manual deletion for stubborn directories
|
|
685
751
|
if (fsSync.existsSync(dir)) {
|
|
686
752
|
const files = await fs.readdir(dir);
|
|
687
753
|
for (const file of files) {
|
|
@@ -698,38 +764,33 @@ async function removeDirectory(dir: string) {
|
|
|
698
764
|
}
|
|
699
765
|
}
|
|
700
766
|
|
|
767
|
+
// --- MAIN BUILD ---
|
|
768
|
+
|
|
701
769
|
async function buildAll(dev = false) {
|
|
702
770
|
const start = performance.now();
|
|
703
771
|
|
|
704
|
-
// Clear HTML injections before build
|
|
705
772
|
htmlInjectionManager.clear();
|
|
706
773
|
|
|
707
|
-
// Load plugins from config first
|
|
708
774
|
await loadPluginsFromConfig();
|
|
709
|
-
|
|
710
|
-
// Also load from plugins directory (backward compatibility)
|
|
711
775
|
await loadPluginsFromDirectory();
|
|
712
776
|
|
|
713
|
-
// Create plugin API
|
|
714
777
|
const pluginAPI = createPluginAPI();
|
|
715
|
-
|
|
716
|
-
// Run onBuildStart hooks
|
|
717
778
|
await runPluginHook('onBuildStart', pluginAPI);
|
|
718
779
|
|
|
719
|
-
// Ensure jsconfig.json exists before building
|
|
720
780
|
await ensureJSConfig();
|
|
721
|
-
|
|
722
|
-
// Remove dist directory with Windows compatibility
|
|
723
781
|
await removeDirectory(DIST_DIR);
|
|
724
|
-
|
|
725
782
|
await fs.mkdir(DIST_DIR, { recursive: true });
|
|
726
783
|
|
|
727
784
|
await timedStep("Build Vader Core", buildVaderCore);
|
|
728
785
|
await timedStep("Build Src", buildSrc);
|
|
729
786
|
await timedStep("Copy Public", copyPublicAssets);
|
|
730
787
|
await timedStep("Build App", buildAppEntrypoints);
|
|
788
|
+
|
|
789
|
+
// Generate Vercel project config if needed (optional)
|
|
790
|
+
if (config.hosting === "vercel") {
|
|
791
|
+
await generateVercelProjectConfig();
|
|
792
|
+
}
|
|
731
793
|
|
|
732
|
-
// Run onBuildFinish hooks
|
|
733
794
|
await runPluginHook('onBuildFinish', pluginAPI);
|
|
734
795
|
|
|
735
796
|
logger.success(
|
|
@@ -739,11 +800,10 @@ async function buildAll(dev = false) {
|
|
|
739
800
|
|
|
740
801
|
// --- DEV SERVER ---
|
|
741
802
|
|
|
742
|
-
|
|
803
|
+
async function runDevServer() {
|
|
743
804
|
let buildPromise: Promise<void> | null = null;
|
|
744
805
|
let reloadTimeout: NodeJS.Timeout | null = null;
|
|
745
806
|
|
|
746
|
-
// Function to trigger reload with debounce
|
|
747
807
|
const triggerReload = (clients: Set<any>) => {
|
|
748
808
|
if (reloadTimeout) {
|
|
749
809
|
clearTimeout(reloadTimeout);
|
|
@@ -754,9 +814,7 @@ async function buildAll(dev = false) {
|
|
|
754
814
|
for (const c of clients) {
|
|
755
815
|
try {
|
|
756
816
|
c.send("reload");
|
|
757
|
-
} catch (e) {
|
|
758
|
-
// Client might be disconnected
|
|
759
|
-
}
|
|
817
|
+
} catch (e) {}
|
|
760
818
|
}
|
|
761
819
|
reloadTimeout = null;
|
|
762
820
|
}, 50);
|
|
@@ -773,27 +831,21 @@ async function buildAll(dev = false) {
|
|
|
773
831
|
fetch(req, server) {
|
|
774
832
|
const url = new URL(req.url);
|
|
775
833
|
|
|
776
|
-
// Handle HMR upgrade
|
|
777
834
|
if (url.pathname === "/__hmr" && server.upgrade(req)) return;
|
|
778
835
|
|
|
779
|
-
// Remove leading slash and handle path
|
|
780
836
|
let requestPath = url.pathname;
|
|
781
837
|
|
|
782
|
-
// If path ends with slash, serve index.html from that directory
|
|
783
838
|
if (requestPath.endsWith('/')) {
|
|
784
839
|
requestPath = path.join(requestPath, 'index.html');
|
|
785
840
|
}
|
|
786
841
|
|
|
787
|
-
// Build the file path
|
|
788
842
|
let filePath = path.join(DIST_DIR, requestPath);
|
|
789
843
|
|
|
790
|
-
// If no extension, try as directory with index.html
|
|
791
844
|
if (!path.extname(filePath)) {
|
|
792
845
|
const indexPath = path.join(filePath, 'index.html');
|
|
793
846
|
if (fsSync.existsSync(indexPath)) {
|
|
794
847
|
filePath = indexPath;
|
|
795
848
|
} else {
|
|
796
|
-
// Try as .html file
|
|
797
849
|
const htmlPath = filePath + '.html';
|
|
798
850
|
if (fsSync.existsSync(htmlPath)) {
|
|
799
851
|
filePath = htmlPath;
|
|
@@ -801,15 +853,12 @@ async function buildAll(dev = false) {
|
|
|
801
853
|
}
|
|
802
854
|
}
|
|
803
855
|
|
|
804
|
-
// Special handling for root
|
|
805
856
|
if (url.pathname === "/" || url.pathname === "") {
|
|
806
857
|
filePath = path.join(DIST_DIR, "index.html");
|
|
807
858
|
}
|
|
808
859
|
|
|
809
|
-
// Check if file exists and serve it
|
|
810
860
|
if (fsSync.existsSync(filePath)) {
|
|
811
861
|
const file = Bun.file(filePath);
|
|
812
|
-
// Set correct content type
|
|
813
862
|
const ext = path.extname(filePath);
|
|
814
863
|
const contentType = {
|
|
815
864
|
'.html': 'text/html',
|
|
@@ -831,8 +880,7 @@ async function buildAll(dev = false) {
|
|
|
831
880
|
});
|
|
832
881
|
}
|
|
833
882
|
|
|
834
|
-
|
|
835
|
-
logger.warn(`404 Not Found: ${requestPath} (resolved to ${filePath})`);
|
|
883
|
+
logger.warn(`404 Not Found: ${requestPath}`);
|
|
836
884
|
return new Response("Not Found", { status: 404 });
|
|
837
885
|
},
|
|
838
886
|
|
|
@@ -848,31 +896,26 @@ async function buildAll(dev = false) {
|
|
|
848
896
|
},
|
|
849
897
|
});
|
|
850
898
|
|
|
851
|
-
// Watch all relevant directories
|
|
852
899
|
watcher.watch(APP_DIR);
|
|
853
900
|
watcher.watch(SRC_DIR);
|
|
854
901
|
watcher.watch(PUBLIC_DIR);
|
|
855
902
|
|
|
856
|
-
// Watch config file
|
|
857
903
|
const configPath = path.join(PROJECT_ROOT, "vaderjs.config.ts");
|
|
858
904
|
if (fsSync.existsSync(configPath)) {
|
|
859
905
|
watcher.watch(configPath);
|
|
860
906
|
}
|
|
861
907
|
|
|
862
|
-
// Watch plugins directory
|
|
863
908
|
const pluginsDir = path.join(PROJECT_ROOT, "plugins");
|
|
864
909
|
if (fsSync.existsSync(pluginsDir)) {
|
|
865
910
|
watcher.watch(pluginsDir);
|
|
866
911
|
}
|
|
867
912
|
|
|
868
|
-
// Also watch for root App file
|
|
869
913
|
const rootAppFile = findAppFile();
|
|
870
914
|
if (rootAppFile) {
|
|
871
915
|
watcher.watch(path.dirname(rootAppFile));
|
|
872
916
|
}
|
|
873
917
|
|
|
874
918
|
watcher.onChange(async (file) => {
|
|
875
|
-
// Don't trigger rebuild if one is already in progress
|
|
876
919
|
if (buildPromise) {
|
|
877
920
|
logger.info("Build already in progress, skipping...");
|
|
878
921
|
return;
|
|
@@ -882,7 +925,6 @@ async function buildAll(dev = false) {
|
|
|
882
925
|
watcher.setRebuildStatus(true);
|
|
883
926
|
|
|
884
927
|
try {
|
|
885
|
-
// If config or plugin file changed, reload config and plugins
|
|
886
928
|
if (file.includes('vaderjs.config.ts') || file.includes('plugins')) {
|
|
887
929
|
logger.info("Config or plugin changed, reloading...");
|
|
888
930
|
config = await loadConfig();
|
|
@@ -891,7 +933,6 @@ async function buildAll(dev = false) {
|
|
|
891
933
|
await loadPluginsFromDirectory();
|
|
892
934
|
}
|
|
893
935
|
|
|
894
|
-
// Run onFileChange hooks for plugins
|
|
895
936
|
const pluginAPI = createPluginAPI();
|
|
896
937
|
for (const plugin of plugins) {
|
|
897
938
|
if (plugin.onFileChange) {
|
|
@@ -899,12 +940,10 @@ async function buildAll(dev = false) {
|
|
|
899
940
|
}
|
|
900
941
|
}
|
|
901
942
|
|
|
902
|
-
// Rebuild
|
|
903
943
|
buildPromise = buildAll(true);
|
|
904
944
|
await buildPromise;
|
|
905
945
|
buildPromise = null;
|
|
906
946
|
|
|
907
|
-
// Trigger reload for all connected clients
|
|
908
947
|
triggerReload(clients);
|
|
909
948
|
|
|
910
949
|
} catch (error) {
|
|
@@ -918,7 +957,6 @@ async function buildAll(dev = false) {
|
|
|
918
957
|
logger.success(`Dev server running http://localhost:${port}`);
|
|
919
958
|
logger.info("Waiting for changes... (Press Ctrl+C to stop)");
|
|
920
959
|
|
|
921
|
-
// Log available routes
|
|
922
960
|
const distFiles = fsSync.readdirSync(DIST_DIR, { recursive: true });
|
|
923
961
|
const htmlFiles = distFiles.filter(f => f.toString().endsWith('index.html'));
|
|
924
962
|
if (htmlFiles.length > 0) {
|
|
@@ -978,7 +1016,6 @@ ${colors.reset}`);
|
|
|
978
1016
|
|
|
979
1017
|
if (cmd === "init") {
|
|
980
1018
|
await initProject(process.argv[3]);
|
|
981
|
-
// Also create jsconfig.json when initializing a new project
|
|
982
1019
|
await ensureJSConfig();
|
|
983
1020
|
return;
|
|
984
1021
|
}
|
|
@@ -1012,6 +1049,7 @@ Make sure you have:
|
|
|
1012
1049
|
- App.tsx or App.jsx in your project root
|
|
1013
1050
|
- OR an app/ directory with index.tsx/jsx files
|
|
1014
1051
|
- vaderjs installed as a dependency
|
|
1052
|
+
- For Vercel deployment: set hosting: "vercel" in vaderjs.config.ts
|
|
1015
1053
|
`);
|
|
1016
1054
|
}
|
|
1017
1055
|
}
|