vaderjs 2.3.14 → 2.3.15
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/LICENSE +21 -21
- package/README.md +89 -0
- package/cli.ts +227 -227
- package/config/index.ts +3 -0
- package/jsconfig.json +7 -7
- package/main.ts +535 -0
- package/package.json +16 -16
- package/plugins/index.ts +72 -72
- package/README.MD +0 -99
- package/main.js +0 -750
package/main.js
DELETED
|
@@ -1,750 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { build, serve } from "bun";
|
|
4
|
-
import fs from "fs/promises";
|
|
5
|
-
import fsSync from "fs";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import { initProject, addPlugin, listPlugins, removePlugin } from "./cli";
|
|
8
|
-
|
|
9
|
-
// --- UTILITIES for a Sleek CLI ---
|
|
10
|
-
|
|
11
|
-
const colors = {
|
|
12
|
-
reset: "\x1b[0m",
|
|
13
|
-
red: "\x1b[31m",
|
|
14
|
-
green: "\x1b[32m",
|
|
15
|
-
yellow: "\x1b[33m",
|
|
16
|
-
blue: "\x1b[34m",
|
|
17
|
-
magenta: "\x1b[35m",
|
|
18
|
-
cyan: "\x1b[36m",
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const logger = {
|
|
22
|
-
_log: (color, ...args) => console.log(color, ...args, colors.reset),
|
|
23
|
-
info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
|
|
24
|
-
success: (...args) => logger._log(colors.green, "✅", ...args),
|
|
25
|
-
warn: (...args) => logger._log(colors.yellow, "⚠️", ...args),
|
|
26
|
-
error: (...args) => logger._log(colors.red, "❌", ...args),
|
|
27
|
-
step: (...args) => logger._log(colors.magenta, "\n🚀", ...args),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
async function timedStep(name, fn) {
|
|
31
|
-
logger.step(`${name}...`);
|
|
32
|
-
const start = performance.now();
|
|
33
|
-
try {
|
|
34
|
-
await fn();
|
|
35
|
-
const duration = (performance.now() - start).toFixed(2);
|
|
36
|
-
logger.success(`Finished '${name}' in ${duration}ms`);
|
|
37
|
-
} catch (e) {
|
|
38
|
-
logger.error(`Error during '${name}':`, e);
|
|
39
|
-
if (!isDev) process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// --- CONSTANTS ---
|
|
44
|
-
|
|
45
|
-
const PROJECT_ROOT = process.cwd();
|
|
46
|
-
const APP_DIR = path.join(PROJECT_ROOT, "app");
|
|
47
|
-
const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
|
|
48
|
-
const DIST_DIR = path.join(PROJECT_ROOT, "dist");
|
|
49
|
-
const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
50
|
-
const VADER_SRC_PATH = path.join(PROJECT_ROOT, "node_modules", "vaderjs", "index.ts");
|
|
51
|
-
const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
|
|
52
|
-
|
|
53
|
-
// --- SIMPLIFIED WATCHER ---
|
|
54
|
-
|
|
55
|
-
class FileWatcher {
|
|
56
|
-
constructor() {
|
|
57
|
-
this.watchers = new Map();
|
|
58
|
-
this.onChangeCallbacks = [];
|
|
59
|
-
this.isRebuilding = false;
|
|
60
|
-
this.lastRebuildTime = 0;
|
|
61
|
-
this.REBUILD_COOLDOWN = 1000; // 1 second cooldown between rebuilds
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
shouldIgnorePath(filePath) {
|
|
65
|
-
const normalized = path.normalize(filePath);
|
|
66
|
-
// Ignore dist folder and its contents
|
|
67
|
-
if (normalized.includes(path.normalize(DIST_DIR))) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
// Ignore node_modules
|
|
71
|
-
if (normalized.includes(path.normalize('node_modules'))) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
// Ignore .git folder
|
|
75
|
-
if (normalized.includes(path.normalize('.git'))) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
// Ignore the temporary source directory
|
|
79
|
-
if (normalized.includes(path.normalize(TEMP_SRC_DIR))) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async watchDirectory(dirPath, recursive = true) {
|
|
86
|
-
// Skip if directory should be ignored
|
|
87
|
-
if (this.shouldIgnorePath(dirPath) || !fsSync.existsSync(dirPath)) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// Close existing watcher if any
|
|
93
|
-
if (this.watchers.has(dirPath)) {
|
|
94
|
-
try {
|
|
95
|
-
this.watchers.get(dirPath).close();
|
|
96
|
-
} catch (err) {
|
|
97
|
-
// Ignore close errors
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Create new watcher
|
|
102
|
-
const watcher = fsSync.watch(dirPath, { recursive }, (eventType, filename) => {
|
|
103
|
-
if (!filename) return;
|
|
104
|
-
|
|
105
|
-
const changedFile = path.join(dirPath, filename);
|
|
106
|
-
const normalizedChanged = path.normalize(changedFile);
|
|
107
|
-
|
|
108
|
-
// Skip if file should be ignored
|
|
109
|
-
if (this.shouldIgnorePath(normalizedChanged)) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Check if this is a file we care about
|
|
114
|
-
if (this.shouldTriggerRebuild(normalizedChanged)) {
|
|
115
|
-
logger.info(`File changed: ${path.relative(PROJECT_ROOT, normalizedChanged)}`);
|
|
116
|
-
|
|
117
|
-
// Only trigger if not already rebuilding and cooldown has passed
|
|
118
|
-
const now = Date.now();
|
|
119
|
-
if (!this.isRebuilding && (now - this.lastRebuildTime) > this.REBUILD_COOLDOWN) {
|
|
120
|
-
this.triggerChange(normalizedChanged);
|
|
121
|
-
} else if (this.isRebuilding) {
|
|
122
|
-
logger.info(`Skipping rebuild - already rebuilding`);
|
|
123
|
-
} else {
|
|
124
|
-
logger.info(`Skipping rebuild - cooldown period`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
watcher.on('error', (err) => {
|
|
130
|
-
logger.warn(`Watcher error on ${dirPath}:`, err.message);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
this.watchers.set(dirPath, watcher);
|
|
134
|
-
|
|
135
|
-
logger.info(`Watching directory: ${path.relative(PROJECT_ROOT, dirPath)}`);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
logger.warn(`Could not watch directory ${dirPath}:`, err.message);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
shouldTriggerRebuild(filePath) {
|
|
142
|
-
// Only trigger rebuild for specific file types
|
|
143
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
144
|
-
const triggerExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.html', '.json', '.config.js', '.config.ts'];
|
|
145
|
-
return triggerExtensions.includes(ext) || ext === '';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
triggerChange(filePath) {
|
|
149
|
-
for (const callback of this.onChangeCallbacks) {
|
|
150
|
-
try {
|
|
151
|
-
callback(filePath);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
logger.error("Change callback error:", err);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
onChange(callback) {
|
|
159
|
-
this.onChangeCallbacks.push(callback);
|
|
160
|
-
return () => {
|
|
161
|
-
const index = this.onChangeCallbacks.indexOf(callback);
|
|
162
|
-
if (index > -1) this.onChangeCallbacks.splice(index, 1);
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
setRebuilding(state) {
|
|
167
|
-
this.isRebuilding = state;
|
|
168
|
-
if (state) {
|
|
169
|
-
this.lastRebuildTime = Date.now();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
clear() {
|
|
174
|
-
for (const [dir, watcher] of this.watchers) {
|
|
175
|
-
try {
|
|
176
|
-
watcher.close();
|
|
177
|
-
} catch (err) {
|
|
178
|
-
// Ignore close errors
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
this.watchers.clear();
|
|
182
|
-
this.onChangeCallbacks = [];
|
|
183
|
-
this.isRebuilding = false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const watcher = new FileWatcher();
|
|
188
|
-
|
|
189
|
-
// --- CONFIG & PLUGIN SYSTEM ---
|
|
190
|
-
|
|
191
|
-
let config = {};
|
|
192
|
-
let htmlInjections = [];
|
|
193
|
-
|
|
194
|
-
const vaderAPI = {
|
|
195
|
-
runCommand: async (cmd) => {
|
|
196
|
-
if (typeof cmd === "string") cmd = cmd.split(" ");
|
|
197
|
-
const p = Bun.spawn(cmd);
|
|
198
|
-
await p.exited;
|
|
199
|
-
},
|
|
200
|
-
injectHTML: (content) => htmlInjections.push(content),
|
|
201
|
-
log: {
|
|
202
|
-
warn: (msg) => logger.warn(msg),
|
|
203
|
-
info: (msg) => logger.info(msg),
|
|
204
|
-
success: (msg) => logger.success(msg),
|
|
205
|
-
step: (msg) => logger.step(msg)
|
|
206
|
-
},
|
|
207
|
-
getProjectRoot: () => PROJECT_ROOT,
|
|
208
|
-
getDistDir: () => DIST_DIR,
|
|
209
|
-
getPublicDir: () => PUBLIC_DIR,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
async function loadConfig() {
|
|
213
|
-
try {
|
|
214
|
-
const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
|
|
215
|
-
return configModule.default || configModule;
|
|
216
|
-
} catch {
|
|
217
|
-
logger.warn("No 'vaderjs.config.js' found, using defaults.");
|
|
218
|
-
return {};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function defineConfig(config) {
|
|
223
|
-
return config;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async function runPluginHook(hookName) {
|
|
227
|
-
if (!config.plugins) return;
|
|
228
|
-
for (const plugin of config.plugins) {
|
|
229
|
-
if (typeof plugin[hookName] === "function") {
|
|
230
|
-
try {
|
|
231
|
-
await plugin[hookName](vaderAPI);
|
|
232
|
-
} catch (e) {
|
|
233
|
-
logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous'}):`, e);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// --- BUILD LOGIC ---
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Step 1: Transpile and bundle the core vaderjs library.
|
|
243
|
-
*/
|
|
244
|
-
async function buildVaderCore() {
|
|
245
|
-
if (!fsSync.existsSync(VADER_SRC_PATH)) {
|
|
246
|
-
logger.error("VaderJS source not found:", VADER_SRC_PATH);
|
|
247
|
-
throw new Error("Missing vaderjs dependency.");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
await build({
|
|
251
|
-
entrypoints: [VADER_SRC_PATH],
|
|
252
|
-
outdir: path.join(DIST_DIR, "src", "vader"),
|
|
253
|
-
target: "browser",
|
|
254
|
-
minify: false,
|
|
255
|
-
sourcemap: "external",
|
|
256
|
-
jsxFactory: "e",
|
|
257
|
-
jsxFragment: "Fragment",
|
|
258
|
-
jsxImportSource: "vaderjs",
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Step 2: Patches source code to remove server-side hook imports.
|
|
264
|
-
*/
|
|
265
|
-
function patchHooksUsage(code) {
|
|
266
|
-
return code.replace(/import\s+{[^}]*use(State|Effect|Memo|Navigation)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g, "");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function publicAssetPlugin() {
|
|
270
|
-
return {
|
|
271
|
-
name: "public-asset-replacer",
|
|
272
|
-
setup(build) {
|
|
273
|
-
build.onLoad({ filter: /\.(js|ts|jsx|tsx|html)$/ }, async (args) => {
|
|
274
|
-
let code = await fs.readFile(args.path, "utf8");
|
|
275
|
-
|
|
276
|
-
code = code.replace(/\{\{public:(.+?)\}\}/g, (_, relPath) => {
|
|
277
|
-
const absPath = path.join(PUBLIC_DIR, relPath.trim());
|
|
278
|
-
if (fsSync.existsSync(absPath)) {
|
|
279
|
-
return "/" + relPath.trim().replace(/\\/g, "/");
|
|
280
|
-
}
|
|
281
|
-
logger.warn(`Public asset not found: ${relPath}`);
|
|
282
|
-
return relPath;
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
contents: code,
|
|
287
|
-
loader: args.path.endsWith(".html")
|
|
288
|
-
? "text"
|
|
289
|
-
: args.path.endsWith(".tsx")
|
|
290
|
-
? "tsx"
|
|
291
|
-
: args.path.endsWith(".jsx")
|
|
292
|
-
? "jsx"
|
|
293
|
-
: args.path.endsWith(".ts")
|
|
294
|
-
? "ts"
|
|
295
|
-
: "js",
|
|
296
|
-
};
|
|
297
|
-
});
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Step 3: Pre-processes all files in `/src` into a temporary directory.
|
|
304
|
-
*/
|
|
305
|
-
async function preprocessSources(srcDir, tempDir) {
|
|
306
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
307
|
-
for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
|
|
308
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
309
|
-
const destPath = path.join(tempDir, entry.name);
|
|
310
|
-
|
|
311
|
-
if (entry.isDirectory()) {
|
|
312
|
-
await preprocessSources(srcPath, destPath);
|
|
313
|
-
} else if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
|
|
314
|
-
let content = await fs.readFile(srcPath, "utf8");
|
|
315
|
-
content = patchHooksUsage(content);
|
|
316
|
-
await fs.writeFile(destPath, content);
|
|
317
|
-
} else {
|
|
318
|
-
await fs.copyFile(srcPath, destPath);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Step 4: Build the application's source code from the preprocessed temp directory.
|
|
325
|
-
*/
|
|
326
|
-
async function buildSrc() {
|
|
327
|
-
if (!fsSync.existsSync(SRC_DIR)) return;
|
|
328
|
-
|
|
329
|
-
if (fsSync.existsSync(TEMP_SRC_DIR)) {
|
|
330
|
-
await fs.rm(TEMP_SRC_DIR, { recursive: true, force: true });
|
|
331
|
-
}
|
|
332
|
-
await preprocessSources(SRC_DIR, TEMP_SRC_DIR);
|
|
333
|
-
|
|
334
|
-
const entrypoints = fsSync.readdirSync(TEMP_SRC_DIR, { recursive: true })
|
|
335
|
-
.map(file => path.join(TEMP_SRC_DIR, file))
|
|
336
|
-
.filter(file => /\.(ts|tsx|js|jsx)$/.test(file));
|
|
337
|
-
|
|
338
|
-
if (entrypoints.length === 0) {
|
|
339
|
-
logger.info("No source files found in /src to build.");
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
await build({
|
|
344
|
-
entrypoints,
|
|
345
|
-
outdir: path.join(DIST_DIR, "src"),
|
|
346
|
-
root: TEMP_SRC_DIR,
|
|
347
|
-
naming: { entry: "[dir]/[name].js" },
|
|
348
|
-
jsxFactory: "e",
|
|
349
|
-
jsxFragment: "Fragment",
|
|
350
|
-
jsxImportSource: "vaderjs",
|
|
351
|
-
target: "browser",
|
|
352
|
-
minify: false,
|
|
353
|
-
plugins: [
|
|
354
|
-
publicAssetPlugin(),
|
|
355
|
-
],
|
|
356
|
-
external: ["vaderjs"],
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Step 5: Copy all assets from the `/public` directory to `/dist`.
|
|
362
|
-
*/
|
|
363
|
-
async function copyPublicAssets() {
|
|
364
|
-
if (!fsSync.existsSync(PUBLIC_DIR)) return;
|
|
365
|
-
// Copy contents of public into dist, not the public folder itself
|
|
366
|
-
for (const item of await fs.readdir(PUBLIC_DIR)) {
|
|
367
|
-
await fs.cp(path.join(PUBLIC_DIR, item), path.join(DIST_DIR, item), { recursive: true });
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function buildAppEntrypoint(entryPath, name, isDev = false) {
|
|
372
|
-
const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
|
|
373
|
-
const outJsPath = path.join(outDir, 'index.js');
|
|
374
|
-
await fs.mkdir(outDir, { recursive: true });
|
|
375
|
-
|
|
376
|
-
// --- CSS HANDLING ---
|
|
377
|
-
const cssLinks = [];
|
|
378
|
-
let content = await fs.readFile(entryPath, "utf8");
|
|
379
|
-
const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
|
|
380
|
-
for (const match of cssImports) {
|
|
381
|
-
const cssImportPath = match[1];
|
|
382
|
-
const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
|
|
383
|
-
if (fsSync.existsSync(sourceCssPath)) {
|
|
384
|
-
const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
|
|
385
|
-
const destCssPath = path.join(DIST_DIR, relativeCssPath);
|
|
386
|
-
await fs.mkdir(path.dirname(destCssPath), { recursive: true });
|
|
387
|
-
await fs.copyFile(sourceCssPath, destCssPath);
|
|
388
|
-
const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
|
|
389
|
-
cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
|
|
390
|
-
} else {
|
|
391
|
-
logger.warn(`CSS file not found: ${sourceCssPath}`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const devClientScript = isDev
|
|
396
|
-
? `<script>
|
|
397
|
-
new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
|
|
398
|
-
if (msg.data === "reload") location.reload();
|
|
399
|
-
};
|
|
400
|
-
</script>`
|
|
401
|
-
: "";
|
|
402
|
-
|
|
403
|
-
// --- HTML GENERATION ---
|
|
404
|
-
let htmlContent = `<!DOCTYPE html>
|
|
405
|
-
<html lang="en">
|
|
406
|
-
<head>
|
|
407
|
-
<meta charset="UTF-8" />
|
|
408
|
-
<title>VaderJS App - ${name}</title>
|
|
409
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
410
|
-
${cssLinks.join("\n ")}
|
|
411
|
-
${htmlInjections.join("\n ")}
|
|
412
|
-
</head>
|
|
413
|
-
<body>
|
|
414
|
-
<div id="app"></div>
|
|
415
|
-
<script type="module">
|
|
416
|
-
import App from '${name !== 'index' ? "/" + name : ''}/index.js';
|
|
417
|
-
import * as Vader from '/src/vader/index.js';
|
|
418
|
-
window.Vader = Vader;
|
|
419
|
-
Vader.render(Vader.createElement(App, null), document.getElementById("app"));
|
|
420
|
-
</script>
|
|
421
|
-
${devClientScript}
|
|
422
|
-
</body>
|
|
423
|
-
</html>`;
|
|
424
|
-
|
|
425
|
-
// Helper to resolve any asset path from /public
|
|
426
|
-
function resolvePublicPath(p) {
|
|
427
|
-
const assetPath = p.replace(/^(\.\/|\/)/, ""); // strip leading ./ or /
|
|
428
|
-
const absPath = path.join(PUBLIC_DIR, assetPath);
|
|
429
|
-
if (fsSync.existsSync(absPath)) {
|
|
430
|
-
return "/" + assetPath.replace(/\\/g, "/");
|
|
431
|
-
}
|
|
432
|
-
return p; // leave unchanged if not in public
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// --- FIX ASSET PATHS IN HTML ---
|
|
436
|
-
htmlContent = htmlContent.replace(
|
|
437
|
-
/(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
|
|
438
|
-
(match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
|
|
442
|
-
|
|
443
|
-
// --- JS BUILD ---
|
|
444
|
-
await build({
|
|
445
|
-
entrypoints: [entryPath],
|
|
446
|
-
outdir: outDir,
|
|
447
|
-
target: "browser",
|
|
448
|
-
minify: false,
|
|
449
|
-
sourcemap: "external",
|
|
450
|
-
external: ["vaderjs"],
|
|
451
|
-
jsxFactory: "e",
|
|
452
|
-
jsxFragment: "Fragment",
|
|
453
|
-
plugins: [
|
|
454
|
-
publicAssetPlugin(),
|
|
455
|
-
],
|
|
456
|
-
jsxImportSource: "vaderjs",
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// --- FIX IMPORT PATHS IN JS ---
|
|
460
|
-
let jsContent = await fs.readFile(outJsPath, "utf8");
|
|
461
|
-
|
|
462
|
-
// Vader import fix
|
|
463
|
-
jsContent = jsContent.replace(/from\s+['"]vaderjs['"]/g, `from '/src/vader/index.js'`);
|
|
464
|
-
|
|
465
|
-
// Asset path fix for JS
|
|
466
|
-
jsContent = jsContent.replace(
|
|
467
|
-
/(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
|
|
468
|
-
(match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
await fs.writeFile(outJsPath, jsContent);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
async function buildAppEntrypoints(isDev = false) {
|
|
475
|
-
if (!fsSync.existsSync(APP_DIR)) {
|
|
476
|
-
logger.warn("No '/app' directory found, skipping app entrypoint build.");
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (!fsSync.existsSync(DIST_DIR)) {
|
|
481
|
-
await fs.mkdir(DIST_DIR, { recursive: true });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Find all index.jsx/tsx files in app directory
|
|
485
|
-
const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
|
|
486
|
-
.filter(file => /index\.(jsx|tsx)$/.test(file))
|
|
487
|
-
.map(file => ({
|
|
488
|
-
name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
|
|
489
|
-
path: path.join(APP_DIR, file)
|
|
490
|
-
}));
|
|
491
|
-
|
|
492
|
-
for (const { name, path: entryPath } of entries) {
|
|
493
|
-
await buildAppEntrypoint(entryPath, name, isDev);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async function buildAll(isDev = false) {
|
|
498
|
-
logger.info(`Starting VaderJS ${isDev ? 'development' : 'production'} build...`);
|
|
499
|
-
const totalTime = performance.now();
|
|
500
|
-
|
|
501
|
-
htmlInjections = [];
|
|
502
|
-
|
|
503
|
-
// Ensure dist directory exists before cleaning
|
|
504
|
-
if (fsSync.existsSync(DIST_DIR)) {
|
|
505
|
-
await fs.rm(DIST_DIR, { recursive: true, force: true });
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Create the dist directory if it doesn't exist
|
|
509
|
-
await fs.mkdir(DIST_DIR, { recursive: true });
|
|
510
|
-
|
|
511
|
-
await runPluginHook("onBuildStart");
|
|
512
|
-
|
|
513
|
-
// Build the components in steps and handle errors properly
|
|
514
|
-
await timedStep("Building VaderJS Core", buildVaderCore);
|
|
515
|
-
await timedStep("Building App Source (/src)", buildSrc);
|
|
516
|
-
await timedStep("Copying Public Assets", copyPublicAssets);
|
|
517
|
-
await timedStep("Building App Entrypoints (/app)", () => buildAppEntrypoints(isDev));
|
|
518
|
-
|
|
519
|
-
await runPluginHook("onBuildFinish");
|
|
520
|
-
|
|
521
|
-
// Calculate the total duration and log it
|
|
522
|
-
const duration = (performance.now() - totalTime).toFixed(2);
|
|
523
|
-
logger.success(`Total build finished in ${duration}ms. Output is in /dist.`);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async function runDevServer() {
|
|
527
|
-
// Initial build
|
|
528
|
-
await buildAll(true);
|
|
529
|
-
|
|
530
|
-
const clients = new Set();
|
|
531
|
-
const port = config.port || 3000;
|
|
532
|
-
|
|
533
|
-
logger.info(`Starting dev server at http://localhost:${port}`);
|
|
534
|
-
|
|
535
|
-
// Set up watchers for all important directories
|
|
536
|
-
logger.info("Setting up file watchers...");
|
|
537
|
-
|
|
538
|
-
// Clear any existing watchers
|
|
539
|
-
watcher.clear();
|
|
540
|
-
|
|
541
|
-
// Watch app directory (excluding dist)
|
|
542
|
-
if (fsSync.existsSync(APP_DIR)) {
|
|
543
|
-
await watcher.watchDirectory(APP_DIR, true);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Watch src directory (excluding dist)
|
|
547
|
-
if (fsSync.existsSync(SRC_DIR)) {
|
|
548
|
-
await watcher.watchDirectory(SRC_DIR, true);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Watch public directory (excluding dist)
|
|
552
|
-
if (fsSync.existsSync(PUBLIC_DIR)) {
|
|
553
|
-
await watcher.watchDirectory(PUBLIC_DIR, true);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Watch config files by watching their directory
|
|
557
|
-
const configPath = path.join(PROJECT_ROOT, "vaderjs.config.js");
|
|
558
|
-
const configPathTs = path.join(PROJECT_ROOT, "vaderjs.config.ts");
|
|
559
|
-
|
|
560
|
-
// Watch the project root for config files
|
|
561
|
-
await watcher.watchDirectory(PROJECT_ROOT, false);
|
|
562
|
-
|
|
563
|
-
const server = serve({
|
|
564
|
-
port,
|
|
565
|
-
fetch(req, server) {
|
|
566
|
-
const url = new URL(req.url);
|
|
567
|
-
if (url.pathname === "/__hmr" && server.upgrade(req)) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
let filePath = path.join(DIST_DIR, url.pathname);
|
|
571
|
-
if (!path.extname(filePath)) {
|
|
572
|
-
filePath = path.join(filePath, "index.html");
|
|
573
|
-
}
|
|
574
|
-
const file = Bun.file(filePath);
|
|
575
|
-
return file.exists().then(exists =>
|
|
576
|
-
exists ? new Response(file) : new Response("Not Found", { status: 404 })
|
|
577
|
-
);
|
|
578
|
-
},
|
|
579
|
-
websocket: {
|
|
580
|
-
open: (ws) => clients.add(ws),
|
|
581
|
-
close: (ws) => clients.delete(ws),
|
|
582
|
-
},
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
const rebuild = async (changedFile) => {
|
|
586
|
-
try {
|
|
587
|
-
watcher.setRebuilding(true);
|
|
588
|
-
|
|
589
|
-
logger.info(`Rebuilding due to: ${path.relative(PROJECT_ROOT, changedFile)}`);
|
|
590
|
-
|
|
591
|
-
// Reload config if config file changed
|
|
592
|
-
if (changedFile.includes('vaderjs.config.')) {
|
|
593
|
-
config = await loadConfig();
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Always rebuild when something changes in development
|
|
597
|
-
await buildAll(true);
|
|
598
|
-
|
|
599
|
-
// Send reload signal to all clients
|
|
600
|
-
for (const client of clients) {
|
|
601
|
-
client.send("reload");
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
logger.success("Rebuild complete");
|
|
605
|
-
} catch (e) {
|
|
606
|
-
logger.error("Rebuild failed:", e);
|
|
607
|
-
} finally {
|
|
608
|
-
watcher.setRebuilding(false);
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
// Set up file change listener
|
|
613
|
-
const removeListener = watcher.onChange((changedFile) => {
|
|
614
|
-
rebuild(changedFile);
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// Cleanup on exit
|
|
618
|
-
const cleanup = () => {
|
|
619
|
-
removeListener();
|
|
620
|
-
watcher.clear();
|
|
621
|
-
server.stop();
|
|
622
|
-
process.exit(0);
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
process.on('SIGINT', cleanup);
|
|
626
|
-
process.on('SIGTERM', cleanup);
|
|
627
|
-
process.on('exit', cleanup);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
async function runProdServer() {
|
|
631
|
-
const port = config.port || 3000;
|
|
632
|
-
logger.info(`Serving production build from /dist on http://localhost:${port}`);
|
|
633
|
-
serve({
|
|
634
|
-
port,
|
|
635
|
-
fetch(req) {
|
|
636
|
-
const url = new URL(req.url);
|
|
637
|
-
let filePath = path.join(DIST_DIR, url.pathname);
|
|
638
|
-
if (!path.extname(filePath)) {
|
|
639
|
-
filePath = path.join(filePath, "index.html");
|
|
640
|
-
}
|
|
641
|
-
const file = Bun.file(filePath);
|
|
642
|
-
return file.exists().then(exists =>
|
|
643
|
-
exists ? new Response(file) : new Response("Not Found", { status: 404 })
|
|
644
|
-
);
|
|
645
|
-
},
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// --- SCRIPT ENTRYPOINT ---
|
|
650
|
-
|
|
651
|
-
async function main() {
|
|
652
|
-
const banner = `${colors.magenta}
|
|
653
|
-
__ __ ____ ____ _______ __
|
|
654
|
-
| | / |/ __ \\ / __ \\ / ____/ |/ /
|
|
655
|
-
| | / / / / // /_/ // /___ | /
|
|
656
|
-
| | / / /_/ / \\____// /___ / |
|
|
657
|
-
|____/____/_____/ /_____/ |_| |_|
|
|
658
|
-
${colors.reset}`;
|
|
659
|
-
|
|
660
|
-
console.log(banner);
|
|
661
|
-
|
|
662
|
-
const command = process.argv[2];
|
|
663
|
-
const arg = process.argv[3];
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
// Commands that don't require config
|
|
667
|
-
if (command === "init") {
|
|
668
|
-
await initProject(arg);
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
if (command === "add") {
|
|
673
|
-
if (!arg) {
|
|
674
|
-
logger.error("Please specify a plugin to add.");
|
|
675
|
-
process.exit(1);
|
|
676
|
-
}
|
|
677
|
-
await addPlugin(arg);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Load config for runtime commands
|
|
682
|
-
config = await loadConfig();
|
|
683
|
-
config.port ||= 3000;
|
|
684
|
-
|
|
685
|
-
switch (command) {
|
|
686
|
-
case "add":
|
|
687
|
-
if (!arg) {
|
|
688
|
-
logger.error("Please specify a plugin to add.");
|
|
689
|
-
process.exit(1);
|
|
690
|
-
}
|
|
691
|
-
await addPlugin(arg);
|
|
692
|
-
return;
|
|
693
|
-
case "list_plugins":
|
|
694
|
-
await listPlugins();
|
|
695
|
-
return;
|
|
696
|
-
case "remove":
|
|
697
|
-
if (!arg) {
|
|
698
|
-
logger.error("Please specify a plugin to remove.");
|
|
699
|
-
process.exit(1);
|
|
700
|
-
}
|
|
701
|
-
await removePlugin(arg);
|
|
702
|
-
return;
|
|
703
|
-
|
|
704
|
-
case "dev":
|
|
705
|
-
globalThis.isDev = true;
|
|
706
|
-
await runDevServer();
|
|
707
|
-
break;
|
|
708
|
-
|
|
709
|
-
case "build":
|
|
710
|
-
await buildAll(false);
|
|
711
|
-
break;
|
|
712
|
-
|
|
713
|
-
case "serve":
|
|
714
|
-
await buildAll(false);
|
|
715
|
-
await runProdServer();
|
|
716
|
-
break;
|
|
717
|
-
|
|
718
|
-
default:
|
|
719
|
-
logger.error(`Unknown command: '${command ?? ""}'`);
|
|
720
|
-
logger.info(`
|
|
721
|
-
Available commands:
|
|
722
|
-
dev Start dev server
|
|
723
|
-
build Build for production
|
|
724
|
-
serve Build + serve production
|
|
725
|
-
init [dir] Create a new Vader project
|
|
726
|
-
add <plugin> Add a Vader plugin
|
|
727
|
-
remove <plugin> Remove a Vader plugin
|
|
728
|
-
list_plugins List currently installed Vaderjs plugins
|
|
729
|
-
`.trim());
|
|
730
|
-
process.exit(1);
|
|
731
|
-
}
|
|
732
|
-
} catch (err) {
|
|
733
|
-
logger.error("Command failed");
|
|
734
|
-
console.error(err);
|
|
735
|
-
process.exit(1);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
main().catch(err => {
|
|
740
|
-
logger.error("An unexpected error occurred:", err);
|
|
741
|
-
process.exit(1);
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
process.on("unhandledRejection", (err) => {
|
|
745
|
-
logger.error("Unhandled Promise rejection:", err);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
process.on("uncaughtException", (err) => {
|
|
749
|
-
logger.error("Uncaught Exception:", err);
|
|
750
|
-
});
|