vaderjs-native 1.0.8 → 1.0.9

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 (3) hide show
  1. package/main.ts +0 -0
  2. package/package.json +2 -2
  3. package/main.js +0 -739
package/main.ts CHANGED
File without changes
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "vaderjs-native",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Build Native Applications using Vaderjs framework.",
5
5
  "bin": {
6
- "vaderjs": "./main.js"
6
+ "vaderjs": "./main.ts"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
package/main.js DELETED
@@ -1,739 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
-
4
- import { build, serve } from "bun";
5
- import fs from "fs/promises";
6
- import fsSync from "fs";
7
- import path from "path";
8
- import { init } from "./cli";
9
- import os from "os";
10
- import { execSync } from "child_process";
11
- import { existsSync, writeFileSync } from "fs";
12
- // --- UTILITIES for a Sleek CLI ---
13
-
14
- async function ensureLocalProperties() {
15
- const ROOT = path.join(
16
- PROJECT_ROOT,
17
- "node_modules",
18
- "vaderjs-native",
19
- "app-template"
20
- );
21
-
22
- const localPropsPath = path.join(ROOT, "local.properties");
23
- if (fsSync.existsSync(localPropsPath)) return;
24
-
25
- // Determine SDK path
26
- let sdkPath = process.env.ANDROID_HOME;
27
-
28
- if (!sdkPath) {
29
- const home = os.homedir();
30
- if (process.platform === "win32") {
31
- sdkPath = path.join(home, "AppData", "Local", "Android", "Sdk");
32
- } else if (process.platform === "darwin") {
33
- sdkPath = path.join(home, "Library", "Android", "sdk");
34
- } else {
35
- // Linux
36
- sdkPath = path.join(home, "Android", "Sdk");
37
- }
38
- }
39
-
40
- if (!existsSync(sdkPath)) {
41
- throw new Error(
42
- `Android SDK not found! Please set ANDROID_HOME or install the SDK at: ${sdkPath}`
43
- );
44
- }
45
-
46
- const content = `sdk.dir=${sdkPath.replace(/\\/g, "\\\\")}\n`;
47
- writeFileSync(localPropsPath, content);
48
- logger.success(`Created local.properties with sdk.dir=${sdkPath}`);
49
- }
50
-
51
- async function copyCompiledJavascriptToJava(isDev = false) {
52
- if (isDev) return;
53
-
54
- const javaAssetsDir = path.join(
55
- PROJECT_ROOT,
56
- "node_modules",
57
- "vaderjs-native",
58
- "app-template",
59
- "app",
60
- "src",
61
- "main",
62
- "assets",
63
- "myapp"
64
- );
65
-
66
- // Remove old files/folder if it exists
67
- if (fsSync.existsSync(javaAssetsDir)) {
68
- await fs.rm(javaAssetsDir, { recursive: true, force: true });
69
- }
70
-
71
- // Recreate the directory and copy fresh files
72
- await fs.mkdir(javaAssetsDir, { recursive: true });
73
- await fs.cp(DIST_DIR, javaAssetsDir, { recursive: true });
74
-
75
- logger.success(`Copied compiled Javascript to Java assets at ${javaAssetsDir}`);
76
- }
77
-
78
- import { spawnSync } from "child_process";
79
- async function buildAPK() {
80
- await ensureLocalProperties();
81
- const ROOT = path.join(
82
- PROJECT_ROOT,
83
- "node_modules",
84
- "vaderjs-native",
85
- "app-template"
86
- );
87
-
88
- const BUILD_DIR = path.join(ROOT, "app", "build");
89
-
90
- // Remove old build folder safely
91
- if (fsSync.existsSync(BUILD_DIR)) {
92
- try {
93
- fsSync.rmSync(BUILD_DIR, { recursive: true, force: true });
94
- console.log("Old build folder removed.");
95
- } catch (err) {
96
- console.warn("Failed to remove old build folder, continuing...", err);
97
- }
98
- }
99
-
100
- const APK_DEST_DIR = path.join(PROJECT_ROOT, "build");
101
- if (!fsSync.existsSync(APK_DEST_DIR)) {
102
- fsSync.mkdirSync(APK_DEST_DIR, { recursive: true });
103
- }
104
-
105
- const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
106
-
107
- console.log("Building APK...");
108
-
109
- const result = spawnSync(gradleCmd, ["assembleDebug"], {
110
- cwd: ROOT,
111
- stdio: "inherit",
112
- shell: true,
113
- });
114
-
115
- if (result.error) {
116
- console.error("Error running Gradle:", result.error);
117
- }
118
-
119
- // Kill any lingering Java processes from this build (Windows)
120
- if (process.platform === "win32") {
121
- try {
122
- execSync('taskkill /F /IM java.exe /T', { stdio: "ignore" });
123
- console.log("Cleaned up lingering Java processes.");
124
- } catch (err) {
125
- console.warn("No lingering Java processes to clean.", err.message);
126
- }
127
- } else {
128
- // macOS/Linux
129
- try {
130
- execSync("pkill -f java", { stdio: "ignore" });
131
- console.log("Cleaned up lingering Java processes.");
132
- } catch (err) {
133
- console.warn("No lingering Java processes to clean.", err.message);
134
- }
135
- }
136
-
137
- // APK paths
138
- const APK_SRC = path.join(
139
- ROOT,
140
- "app",
141
- "build",
142
- "outputs",
143
- "apk",
144
- "debug",
145
- "app-debug.apk"
146
- );
147
- const APK_DEST = path.join(APK_DEST_DIR, "myapp-debug.apk");
148
-
149
- if (fsSync.existsSync(APK_SRC)) {
150
- fsSync.copyFileSync(APK_SRC, APK_DEST);
151
- console.log(`APK built successfully at ${APK_DEST}`);
152
- } else {
153
- console.error("APK build failed: APK file not found.");
154
- }
155
- }
156
-
157
- function safeWatch(dir, cb) {
158
- try {
159
- const watcher = fsSync.watch(dir, { recursive: true }, cb);
160
- watcher.on("error", (err) => logger.warn(`Watcher error on ${dir}:`, err));
161
- return watcher;
162
- } catch (err) {
163
- logger.warn(`Failed to watch ${dir}:`, err);
164
- }
165
- }
166
-
167
-
168
- const colors = {
169
- reset: "\x1b[0m",
170
- red: "\x1b[31m",
171
- green: "\x1b[32m",
172
- yellow: "\x1b[33m",
173
- blue: "\x1b[34m",
174
- magenta: "\x1b[35m",
175
- cyan: "\x1b[36m",
176
- gray: "\x1b[90m",
177
- bold: "\x1b[1m",
178
- };
179
-
180
- const logger = {
181
- _log: (level, color, symbol, ...args) => {
182
- const timestamp = new Date().toISOString();
183
- console.log(
184
- `${colors.gray}[${timestamp}]${colors.reset} ${color}${symbol}${colors.reset} ${colors.bold}${level}:${colors.reset}`,
185
- ...args
186
- );
187
- },
188
- info: (...args) => logger._log("INFO", colors.cyan, "ℹ", ...args),
189
- success: (...args) => logger._log("SUCCESS", colors.green, "✅", ...args),
190
- warn: (...args) => logger._log("WARN", colors.yellow, "⚠️", ...args),
191
- error: (...args) => logger._log("ERROR", colors.red, "❌", ...args),
192
- step: (...args) => {
193
- const separator = colors.magenta + "═".repeat(50) + colors.reset;
194
- console.log(`\n${separator}`);
195
- console.log(`${colors.magenta}🚀 STEP:${colors.reset} ${colors.bold}`, ...args);
196
- console.log(separator);
197
- },
198
- debug: (...args) => logger._log("DEBUG", colors.blue, "🐛", ...args),
199
- plugin: (...args) => logger._log("PLUGIN", colors.magenta, "🧩", ...args),
200
- table: (title, data) => {
201
- console.log(`${colors.cyan}${title}${colors.reset}`);
202
- console.table(data);
203
- },
204
- };
205
-
206
-
207
- async function timedStep(name, fn) {
208
- logger.step(`${name}...`);
209
- const start = performance.now();
210
- try {
211
- await fn();
212
- const duration = (performance.now() - start).toFixed(2);
213
- logger.success(`Finished '${name}' in ${duration}ms`);
214
- } catch (e) {
215
- logger.error(`Error during '${name}':`, e);
216
- if (!isDev) process.exit(1);
217
- }
218
- }
219
-
220
- // --- CONSTANTS ---
221
-
222
- const PROJECT_ROOT = process.cwd();
223
- const APP_DIR = path.join(PROJECT_ROOT, "app");
224
- const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
225
- const DIST_DIR = path.join(PROJECT_ROOT, "dist");
226
- const SRC_DIR = path.join(PROJECT_ROOT, "src");
227
- const VADER_SRC_PATH = path.join(PROJECT_ROOT, "node_modules", "vaderjs-native", "index.ts");
228
- const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
229
-
230
-
231
- // --- CONFIG & PLUGIN SYSTEM ---
232
-
233
- let config = {};
234
- let htmlInjections = [];
235
-
236
- const vaderAPI = {
237
- runCommand: async (cmd) => {
238
- if (typeof cmd === "string") cmd = cmd.split(" ");
239
- const p = Bun.spawn(cmd);
240
- await p.exited;
241
- },
242
- injectHTML: (content) => htmlInjections.push(content),
243
- log: (msg) => logger.info(`[Plugin] ${msg}`),
244
- getProjectRoot: () => PROJECT_ROOT,
245
- getDistDir: () => DIST_DIR,
246
- getPublicDir: () => PUBLIC_DIR,
247
- };
248
-
249
- async function loadConfig() {
250
- try {
251
- const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
252
- return configModule.default || configModule;
253
- } catch {
254
- console.log(path.join(PROJECT_ROOT, "vaderjs.config.js"))
255
- logger.warn("No 'vader.config.js' found, using defaults.");
256
- return {};
257
- }
258
- }
259
-
260
- export function defineConfig(config) {
261
- return config;
262
- }
263
-
264
- async function runPluginHook(hookName) {
265
- if (!config.plugins) return;
266
- for (const plugin of config.plugins) {
267
- if (typeof plugin[hookName] === "function") {
268
- try {
269
- await plugin[hookName](vaderAPI);
270
- } catch (e) {
271
- logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous'}):`, e);
272
- }
273
- }
274
- }
275
- }
276
-
277
-
278
-
279
- // --- BUILD LOGIC ---
280
-
281
- /**
282
- * Step 1: Transpile and bundle the core vaderjs library.
283
- */
284
- async function buildVaderCore() {
285
- if (!fsSync.existsSync(VADER_SRC_PATH)) {
286
- logger.error("VaderJS source not found:", VADER_SRC_PATH);
287
- throw new Error("Missing vaderjs dependency.");
288
- }
289
-
290
- await build({
291
- entrypoints: [VADER_SRC_PATH],
292
- outdir: path.join(DIST_DIR, "src", "vader"),
293
- target: "browser",
294
- minify: false,
295
- sourcemap: "external",
296
- jsxFactory: "e",
297
- jsxFragment: "Fragment",
298
- jsxImportSource: "vaderjs",
299
- });
300
- }
301
-
302
- /**
303
- * Step 2: Patches source code to remove server-side hook imports.
304
- */
305
- function patchHooksUsage(code) {
306
- return code.replace(/import\s+{[^}]*use(State|Effect|Memo|Navigation)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g, "");
307
- }
308
- function publicAssetPlugin() {
309
- return {
310
- name: "public-asset-replacer",
311
- setup(build) {
312
- build.onLoad({ filter: /\.(js|ts|jsx|tsx|html)$/ }, async (args) => {
313
- let code = await fs.readFile(args.path, "utf8");
314
-
315
- code = code.replace(/\{\{public:(.+?)\}\}/g, (_, relPath) => {
316
- const absPath = path.join(PUBLIC_DIR, relPath.trim());
317
- if (fsSync.existsSync(absPath)) {
318
- return "/" + relPath.trim().replace(/\\/g, "/");
319
- }
320
- logger.warn(`Public asset not found: ${relPath}`);
321
- return relPath;
322
- });
323
-
324
- return {
325
- contents: code,
326
- loader: args.path.endsWith(".html")
327
- ? "text"
328
- : args.path.endsWith(".tsx")
329
- ? "tsx"
330
- : args.path.endsWith(".jsx")
331
- ? "jsx"
332
- : args.path.endsWith(".ts")
333
- ? "ts"
334
- : "js",
335
- };
336
- });
337
- },
338
- };
339
- }
340
-
341
- /**
342
- * Step 3: Pre-processes all files in `/src` into a temporary directory.
343
- */
344
-
345
- async function preprocessSources(srcDir, tempDir) {
346
- await fs.mkdir(tempDir, { recursive: true });
347
- for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
348
- const srcPath = path.join(srcDir, entry.name);
349
- const destPath = path.join(tempDir, entry.name);
350
-
351
- if (entry.isDirectory()) {
352
- await preprocessSources(srcPath, destPath);
353
- } else if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
354
- let content = await fs.readFile(srcPath, "utf8");
355
- content = patchHooksUsage(content);
356
- await fs.writeFile(destPath, content);
357
- } else {
358
- await fs.copyFile(srcPath, destPath);
359
- }
360
- }
361
- }
362
-
363
- /**
364
- * Step 4: Build the application's source code from the preprocessed temp directory.
365
- */
366
- async function buildSrc() {
367
- if (!fsSync.existsSync(SRC_DIR)) return;
368
-
369
- if (fsSync.existsSync(TEMP_SRC_DIR)) {
370
- await fs.rm(TEMP_SRC_DIR, { recursive: true, force: true });
371
- }
372
- await preprocessSources(SRC_DIR, TEMP_SRC_DIR);
373
-
374
- const entrypoints = fsSync.readdirSync(TEMP_SRC_DIR, { recursive: true })
375
- .map(file => path.join(TEMP_SRC_DIR, file))
376
- .filter(file => /\.(ts|tsx|js|jsx)$/.test(file));
377
-
378
- if (entrypoints.length === 0) {
379
- logger.info("No source files found in /src to build.");
380
- return;
381
- }
382
-
383
- await build({
384
- entrypoints,
385
- outdir: path.join(DIST_DIR, "src"),
386
- root: TEMP_SRC_DIR,
387
- naming: { entry: "[dir]/[name].js" },
388
- jsxFactory: "e",
389
- jsxFragment: "Fragment",
390
- jsxImportSource: "vaderjs-native",
391
- target: "browser",
392
- minify: false,
393
- plugins: [
394
- publicAssetPlugin(),
395
- ],
396
- external: ["vaderjs-native"],
397
- });
398
- }
399
-
400
- /**
401
- * Step 5: Copy all assets from the `/public` directory to `/dist`.
402
- */
403
- async function copyPublicAssets() {
404
- if (!fsSync.existsSync(PUBLIC_DIR)) return;
405
- // Copy contents of public into dist, not the public folder itself
406
- for (const item of await fs.readdir(PUBLIC_DIR)) {
407
- await fs.cp(path.join(PUBLIC_DIR, item), path.join(DIST_DIR, item), { recursive: true });
408
- }
409
- }
410
-
411
- async function buildAppEntrypoints(isDev = false) {
412
- if (!fsSync.existsSync(APP_DIR)) {
413
- logger.warn("No '/app' directory found, skipping app entrypoint build.");
414
- return;
415
- }
416
-
417
- if (!fsSync.existsSync(DIST_DIR)) {
418
- await fs.mkdir(DIST_DIR, { recursive: true });
419
- }
420
-
421
- const devClientScript = isDev
422
- ? `<script>
423
- new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
424
- if (msg.data === "reload") location.reload();
425
- };
426
- </script>`
427
- : "";
428
-
429
- const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
430
- .filter(file => /index\.(jsx|tsx)$/.test(file))
431
- .map(file => ({
432
- name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
433
- path: path.join(APP_DIR, file)
434
- }));
435
-
436
- // Helper to resolve any asset path from /public
437
- function resolvePublicPath(p) {
438
- const assetPath = p.replace(/^(\.\/|\/)/, ""); // strip leading ./ or /
439
- const absPath = path.join(PUBLIC_DIR, assetPath);
440
- if (fsSync.existsSync(absPath)) {
441
- return "/" + assetPath.replace(/\\/g, "/");
442
- }
443
- return p; // leave unchanged if not in public
444
- }
445
-
446
- for (const { name, path: entryPath } of entries) {
447
- const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
448
- const outJsPath = path.join(outDir, 'index.js');
449
- await fs.mkdir(outDir, { recursive: true });
450
-
451
- // --- CSS HANDLING ---
452
- const cssLinks = [];
453
- let content = await fs.readFile(entryPath, "utf8");
454
- const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
455
- for (const match of cssImports) {
456
- const cssImportPath = match[1];
457
- const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
458
- if (fsSync.existsSync(sourceCssPath)) {
459
- const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
460
- const destCssPath = path.join(DIST_DIR, relativeCssPath);
461
- await fs.copyFile(sourceCssPath, destCssPath);
462
- const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
463
- cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
464
- } else {
465
- logger.warn(`CSS file not found: ${sourceCssPath}`);
466
- }
467
- }
468
-
469
- // --- HTML GENERATION ---
470
- let htmlContent = `<!DOCTYPE html>
471
- <html lang="en">
472
- <head>
473
- <meta charset="UTF-8" />
474
- <title>VaderJS App - ${name}</title>
475
- <meta name="viewport" content="width=device-width, initial-scale=1" />
476
- ${cssLinks.join("\n ")}
477
- ${htmlInjections.join("\n ")}
478
- </head>
479
- <body>
480
- <div id="app"></div>
481
- <script src="./index.js"></script>
482
- ${devClientScript}
483
- </body>
484
- </html>`;
485
-
486
- // --- FIX ASSET PATHS IN HTML ---
487
- htmlContent = htmlContent.replace(
488
- /(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
489
- (match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
490
- );
491
-
492
- await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
493
-
494
- // --- JS BUILD ---
495
- await build({
496
- entrypoints: [entryPath],
497
- outdir: outDir,
498
- target: "browser",
499
- minify: false,
500
- sourcemap: "external",
501
- jsxFactory: "Vader.createElement",
502
- jsxFragment: "Fragment",
503
- plugins: [
504
- publicAssetPlugin(),
505
- ],
506
- loader: {
507
- '.js': 'jsx',
508
- '.ts': 'tsx',
509
- '.css': 'text',
510
- }
511
- });
512
-
513
- // --- FIX IMPORT PATHS IN JS ---
514
- let jsContent = await fs.readFile(outJsPath, "utf8");
515
- const vaderSource = await fs.readFile(VADER_SRC_PATH, "utf8");
516
- // Vader import fix
517
-
518
- // Asset path fix for JS
519
- jsContent = jsContent.replace(
520
- /import\s+\*\s+as\s+Vader\s+from\s+['"]vaderjs['"];?/,
521
- vaderSource
522
- );
523
-
524
- await fs.writeFile(outJsPath, jsContent);
525
- }
526
- }
527
-
528
-
529
-
530
- function getLocalIP() {
531
- const interfaces = os.networkInterfaces();
532
- for (const name of Object.keys(interfaces)) {
533
- for (const iface of interfaces[name]) {
534
- // Skip internal (loopback) and non-ipv4 addresses
535
- if (iface.family === 'IPv4' && !iface.internal) {
536
- return iface.address;
537
- }
538
- }
539
- }
540
- return '127.0.0.1';
541
- }
542
-
543
- async function patchWebActivity(isDev) {
544
- const filePath = path.join(
545
- PROJECT_ROOT,
546
- "node_modules",
547
- "vaderjs-native",
548
- "app-template",
549
- "app",
550
- "src",
551
- "main",
552
- "java",
553
- "com",
554
- "example",
555
- "myapplication",
556
- "MainActivity.kt"
557
- );
558
-
559
- try {
560
- let content = await fs.readFile(filePath, "utf8");
561
-
562
- // Set the base URL based on mode
563
- const baseUrl = isDev
564
- ? `http://${getLocalIP()}:3000/`
565
- : "file:///android_asset/myapp/";
566
-
567
- // 1. Update the JS_URL variable
568
- // Matches: private var baseUrl = "http://192.168.1.102:3000"
569
- content = content.replace(/private\s+var\s+baseUrl\s*=\s*".*?"/, `private var baseUrl = "${baseUrl}"`);
570
-
571
-
572
- await fs.writeFile(filePath, content);
573
- console.log(`Successfully patched MainActivity.kt for ${isDev ? 'Development' : 'Production'}`);
574
- console.log(`URL set to: ${baseUrl}`);
575
- } catch (err) {
576
- console.error("Error patching file:", err);
577
- }
578
- }
579
-
580
- async function buildAll(isDev = false) {
581
- logger.info(`Starting VaderJS ${isDev ? 'development' : 'production'} build...`);
582
- const totalTime = performance.now();
583
-
584
- htmlInjections = [];
585
-
586
- // Ensure dist directory exists before cleaning
587
- if (fsSync.existsSync(DIST_DIR)) {
588
- await fs.rm(DIST_DIR, { recursive: true, force: true });
589
- }
590
-
591
- // Create the dist directory if it doesn't exist
592
- await fs.mkdir(DIST_DIR, { recursive: true });
593
-
594
- await runPluginHook("onBuildStart");
595
-
596
- // Build the components in steps and handle errors properly
597
- await timedStep("Building VaderJS Core", buildVaderCore);
598
- await timedStep("Building App Source (/src)", buildSrc);
599
- await timedStep("Copying Public Assets", copyPublicAssets);
600
- await timedStep("Building App Entrypoints (/app)", () => buildAppEntrypoints(isDev));
601
- if (!isDev) {
602
- await timedStep("Copy Compiled Javascript to Java", () => copyCompiledJavascriptToJava(isDev));
603
- await timedStep("Building APK", buildAPK);
604
- }else{
605
- await timedStep("Patching WebActivity for Dev Server", () => patchWebActivity(isDev));
606
- }
607
-
608
- await runPluginHook("onBuildFinish");
609
-
610
- // Calculate the total duration and log it
611
- const duration = (performance.now() - totalTime).toFixed(2);
612
- logger.success(`Total build finished in ${duration}ms. Output is in /dist.`);
613
- }
614
-
615
- async function runDevServer() {
616
- await buildAll(true);
617
-
618
- const clients = new Set();
619
- const port = config.port || 3000;
620
-
621
- logger.info(`Starting dev server at http://localhost:${port}`);
622
-
623
- serve({
624
- port,
625
- fetch(req, server) {
626
- const url = new URL(req.url);
627
- if (url.pathname === "/__hmr" && server.upgrade(req)) {
628
- return;
629
- }
630
- let filePath = path.join(DIST_DIR, url.pathname);
631
- if (!path.extname(filePath)) {
632
- filePath = path.join(filePath, "index.html");
633
- }
634
- const file = Bun.file(filePath);
635
- return file.exists().then(exists =>
636
- exists ? new Response(file) : new Response("Not Found", { status: 404 })
637
- );
638
- },
639
- websocket: {
640
- open: (ws) => clients.add(ws),
641
- close: (ws) => clients.delete(ws),
642
- },
643
- });
644
-
645
- const debouncedBuild = debounce(async () => {
646
- try {
647
- await buildAll(true);
648
- for (const client of clients) {
649
- client.send("reload");
650
- }
651
- } catch (e) {
652
- logger.error("Rebuild failed:", e);
653
- }
654
- }, 200);
655
-
656
- const watchDirs = [APP_DIR, SRC_DIR, PUBLIC_DIR].filter(fsSync.existsSync);
657
- for (const dir of watchDirs) {
658
- safeWatch(dir, debouncedBuild);
659
- }
660
- }
661
-
662
- async function runProdServer() {
663
- const port = config.port || 3000;
664
- logger.info(`Serving production build from /dist on http://localhost:${port}`);
665
- serve({
666
- port,
667
- fetch(req) {
668
- const url = new URL(req.url);
669
- let filePath = path.join(DIST_DIR, url.pathname);
670
- if (!path.extname(filePath)) {
671
- filePath = path.join(filePath, "index.html");
672
- }
673
- const file = Bun.file(filePath);
674
- return file.exists().then(exists =>
675
- exists ? new Response(file) : new Response("Not Found", { status: 404 })
676
- );
677
- },
678
- });
679
- }
680
-
681
- function debounce(fn, delay) {
682
- let timeoutId;
683
- return (...args) => {
684
- clearTimeout(timeoutId);
685
- timeoutId = setTimeout(() => fn(...args), delay);
686
- };
687
- }
688
-
689
- // --- SCRIPT ENTRYPOINT ---
690
-
691
- async function main() {
692
- const banner = `${colors.magenta}
693
- __ __ ____ ____ _______ __
694
- | | / |/ __ \ / __ \ / ____/ |/ /
695
- | | / / / / // /_/ // /___ | /
696
- | | / / /_/ / \____// /___ / |
697
- |____/____/_____/ /_____/ |_| |_|
698
- ${colors.reset}`;
699
-
700
- console.log(banner);
701
-
702
-
703
- config = await loadConfig();
704
- config.port = config.port || 3000;
705
-
706
- const command = process.argv[2];
707
-
708
- if (command === "dev") {
709
- globalThis.isDev = true
710
- await runDevServer();
711
- } else if (command === "build") {
712
- await buildAll(false);
713
- } else if (command === "serve") {
714
- await buildAll(false);
715
- await runProdServer();
716
- }
717
- else if (command === "init") {
718
- init().catch((e) => {
719
- console.error("Initialization failed:", e);
720
- process.exit(1);
721
- });
722
-
723
- } else {
724
- logger.error(`Unknown command: '${command}'.`);
725
- logger.info("Available commands: 'dev', 'build', 'serve', 'init'");
726
- process.exit(1);
727
- }
728
- }
729
-
730
- main().catch(err => {
731
- logger.error("An unexpected error occurred:", err);
732
- process.exit(1);
733
- });
734
- process.on("unhandledRejection", (err) => {
735
- logger.error("Unhandled Promise rejection:", err);
736
- });
737
- process.on("uncaughtException", (err) => {
738
- logger.error("Uncaught Exception:", err);
739
- });