vaderjs-native 1.0.13 → 1.0.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/index.ts CHANGED
@@ -1802,7 +1802,78 @@ export function platform(): "windows" | "android" | "web" {
1802
1802
  // 3. Fallback to web
1803
1803
  return "web";
1804
1804
  }
1805
-
1805
+ /**
1806
+ * Creates a Vader.js component with automatic prop memoization.
1807
+ * @template P - Component props type
1808
+ * @param {(props: P) => VNode} renderFn - The component render function
1809
+ * @returns {(props: P) => VNode} A memoized component function
1810
+ */
1811
+ export function component<P extends object>( renderFn: (props: P) => VNode): (props: P) => VNode {
1812
+ // Create a wrapper function that will be the actual component
1813
+ const ComponentWrapper = (props: P): VNode => {
1814
+ // Check if props have changed
1815
+ let fiber = wipFiber;
1816
+ while (fiber && fiber.type !== ComponentWrapper) {
1817
+ fiber = fiber.alternate;
1818
+ }
1819
+
1820
+ const prevProps = fiber?.alternate?.props || {};
1821
+ const nextProps = props;
1822
+
1823
+ // Create a simple props comparison
1824
+ // For now, we'll do a shallow comparison of props
1825
+ let shouldUpdate = false;
1826
+
1827
+ // Check if props count changed
1828
+ const prevKeys = Object.keys(prevProps);
1829
+ const nextKeys = Object.keys(nextProps);
1830
+
1831
+ if (prevKeys.length !== nextKeys.length) {
1832
+ shouldUpdate = true;
1833
+ } else {
1834
+ // Check each prop
1835
+ for (const key of nextKeys) {
1836
+ if (nextProps[key] !== prevProps[key]) {
1837
+ shouldUpdate = true;
1838
+ break;
1839
+ }
1840
+ }
1841
+ }
1842
+
1843
+ // Mark fiber for memoization
1844
+ const currentFiber = wipFiber;
1845
+ if (currentFiber) {
1846
+ currentFiber.propsCache = nextProps;
1847
+ currentFiber.__compareProps = (prev: P, next: P) => {
1848
+ const prevKeys = Object.keys(prev);
1849
+ const nextKeys = Object.keys(next);
1850
+
1851
+ if (prevKeys.length !== nextKeys.length) return false;
1852
+
1853
+ for (const key of nextKeys) {
1854
+ if (next[key] !== prev[key]) return false;
1855
+ }
1856
+
1857
+ return true;
1858
+ };
1859
+
1860
+ currentFiber.__skipMemo = !shouldUpdate;
1861
+ }
1862
+
1863
+ // If props haven't changed, return the previous fiber's children
1864
+ if (!shouldUpdate && fiber?.alternate?.child) {
1865
+ return fiber.alternate.child.props.children[0];
1866
+ }
1867
+
1868
+ // Otherwise render with new props
1869
+ return renderFn(props);
1870
+ };
1871
+
1872
+ // Set display name for debugging
1873
+ (ComponentWrapper as any).displayName = name;
1874
+
1875
+ return ComponentWrapper;
1876
+ }
1806
1877
  const Vader = {
1807
1878
  render,
1808
1879
  createElement,
@@ -1826,6 +1897,7 @@ const Vader = {
1826
1897
  Link,
1827
1898
  showToast,
1828
1899
  platform,
1900
+ component,
1829
1901
  App
1830
1902
  };
1831
1903
 
package/main.ts CHANGED
@@ -1,12 +1,19 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { colors } from "./cli/logger";
4
3
  import { build, serve } from "bun";
5
4
  import fs from "fs/promises";
6
5
  import fsSync from "fs";
7
6
  import path from "path";
8
- import { init } from "./cli";
7
+ import { initProject, addPlugin, listPlugins, removePlugin } from "./cli";
9
8
  import { logger, timedStep } from "./cli/logger";
9
+ import { colors } from "./cli/logger";
10
+ import runDevServer from "./cli/web/server";
11
+ import { runProdServer } from "./cli/web/server";
12
+ import { androidDev } from "./cli/android/dev.js";
13
+ import { buildAndroid } from "./cli/android/build";
14
+ import { buildWindows } from "./cli/windows/build";
15
+ import openWinApp from "./cli/windows/dev";
16
+ import { Config } from "./config";
10
17
 
11
18
  // --- CONSTANTS ---
12
19
  const PROJECT_ROOT = process.cwd();
@@ -17,17 +24,157 @@ const SRC_DIR = path.join(PROJECT_ROOT, "src");
17
24
  const VADER_SRC_PATH = path.join(PROJECT_ROOT, "node_modules", "vaderjs-native", "index.ts");
18
25
  const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
19
26
 
20
- // --- CACHE SYSTEM (Initialize first) ---
27
+ // --- CACHE SYSTEM ---
21
28
  const buildCache = new Map<string, { mtime: number; hash: string }>();
22
29
  const configCache = new Map<string, { config: Config; mtime: number }>();
23
30
  let config: Config = {};
24
31
  let htmlInjections: string[] = [];
25
32
 
33
+ // --- SIMPLIFIED WATCHER ---
34
+ class FileWatcher {
35
+ constructor() {
36
+ this.watchers = new Map();
37
+ this.onChangeCallbacks = [];
38
+ this.isRebuilding = false;
39
+ this.lastRebuildTime = 0;
40
+ this.REBUILD_COOLDOWN = 1000; // 1 second cooldown between rebuilds
41
+ }
42
+
43
+ shouldIgnorePath(filePath) {
44
+ const normalized = path.normalize(filePath);
45
+ // Ignore dist folder and its contents
46
+ if (normalized.includes(path.normalize(DIST_DIR))) {
47
+ return true;
48
+ }
49
+ // Ignore node_modules
50
+ if (normalized.includes(path.normalize('node_modules'))) {
51
+ return true;
52
+ }
53
+ // Ignore .git folder
54
+ if (normalized.includes(path.normalize('.git'))) {
55
+ return true;
56
+ }
57
+ // Ignore the temporary source directory
58
+ if (normalized.includes(path.normalize(TEMP_SRC_DIR))) {
59
+ return true;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ async watchDirectory(dirPath, recursive = true) {
65
+ // Skip if directory should be ignored
66
+ if (this.shouldIgnorePath(dirPath) || !fsSync.existsSync(dirPath)) {
67
+ return;
68
+ }
69
+
70
+ try {
71
+ // Close existing watcher if any
72
+ if (this.watchers.has(dirPath)) {
73
+ try {
74
+ this.watchers.get(dirPath).close();
75
+ } catch (err) {
76
+ // Ignore close errors
77
+ }
78
+ }
79
+
80
+ // Create new watcher
81
+ const watcher = fsSync.watch(dirPath, { recursive }, (eventType, filename) => {
82
+ if (!filename) return;
83
+
84
+ const changedFile = path.join(dirPath, filename);
85
+ const normalizedChanged = path.normalize(changedFile);
86
+
87
+ // Skip if file should be ignored
88
+ if (this.shouldIgnorePath(normalizedChanged)) {
89
+ return;
90
+ }
91
+
92
+ // Check if this is a file we care about
93
+ if (this.shouldTriggerRebuild(normalizedChanged)) {
94
+ logger.info(`File changed: ${path.relative(PROJECT_ROOT, normalizedChanged)}`);
95
+
96
+ // Only trigger if not already rebuilding and cooldown has passed
97
+ const now = Date.now();
98
+ if (!this.isRebuilding && (now - this.lastRebuildTime) > this.REBUILD_COOLDOWN) {
99
+ this.triggerChange(normalizedChanged);
100
+ } else if (this.isRebuilding) {
101
+ logger.info(`Skipping rebuild - already rebuilding`);
102
+ } else {
103
+ logger.info(`Skipping rebuild - cooldown period`);
104
+ }
105
+ }
106
+ });
107
+
108
+ watcher.on('error', (err) => {
109
+ logger.warn(`Watcher error on ${dirPath}:`, err.message);
110
+ });
111
+
112
+ this.watchers.set(dirPath, watcher);
113
+
114
+ logger.info(`Watching directory: ${path.relative(PROJECT_ROOT, dirPath)}`);
115
+ } catch (err) {
116
+ logger.warn(`Could not watch directory ${dirPath}:`, err.message);
117
+ }
118
+ }
119
+
120
+ shouldTriggerRebuild(filePath) {
121
+ // Only trigger rebuild for specific file types
122
+ const ext = path.extname(filePath).toLowerCase();
123
+ const triggerExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.html', '.json', '.config.js', '.config.ts'];
124
+ return triggerExtensions.includes(ext) || ext === '';
125
+ }
126
+
127
+ triggerChange(filePath) {
128
+ for (const callback of this.onChangeCallbacks) {
129
+ try {
130
+ callback(filePath);
131
+ } catch (err) {
132
+ logger.error("Change callback error:", err);
133
+ }
134
+ }
135
+ }
136
+
137
+ onChange(callback) {
138
+ this.onChangeCallbacks.push(callback);
139
+ return () => {
140
+ const index = this.onChangeCallbacks.indexOf(callback);
141
+ if (index > -1) this.onChangeCallbacks.splice(index, 1);
142
+ };
143
+ }
144
+
145
+ setRebuilding(state) {
146
+ this.isRebuilding = state;
147
+ if (state) {
148
+ this.lastRebuildTime = Date.now();
149
+ }
150
+ }
151
+
152
+ clear() {
153
+ for (const [dir, watcher] of this.watchers) {
154
+ try {
155
+ watcher.close();
156
+ } catch (err) {
157
+ // Ignore close errors
158
+ }
159
+ }
160
+ this.watchers.clear();
161
+ this.onChangeCallbacks = [];
162
+ this.isRebuilding = false;
163
+ }
164
+ }
165
+
166
+ const watcher = new FileWatcher();
167
+
26
168
  // --- CONFIG & PLUGIN SYSTEM ---
27
169
  interface VaderAPI {
28
170
  runCommand: (cmd: string | string[]) => Promise<void>;
29
171
  injectHTML: (content: string) => void;
30
- log: (msg: string) => void;
172
+ log: {
173
+ warn: (msg: string) => void;
174
+ info: (msg: string) => void;
175
+ success: (msg: string) => void;
176
+ step: (msg: string) => void;
177
+ };
31
178
  getProjectRoot: () => string;
32
179
  getDistDir: () => string;
33
180
  getPublicDir: () => string;
@@ -40,7 +187,12 @@ const vaderAPI: VaderAPI = {
40
187
  await p.exited;
41
188
  },
42
189
  injectHTML: (content) => htmlInjections.push(content),
43
- log: (msg) => logger.info(`[Plugin] ${msg}`),
190
+ log: {
191
+ warn: (msg) => logger.warn(msg),
192
+ info: (msg) => logger.info(msg),
193
+ success: (msg) => logger.success(msg),
194
+ step: (msg) => logger.step(msg)
195
+ },
44
196
  getProjectRoot: () => PROJECT_ROOT,
45
197
  getDistDir: () => DIST_DIR,
46
198
  getPublicDir: () => PUBLIC_DIR,
@@ -51,8 +203,8 @@ export async function loadConfig(projectDir?: string): Promise<Config> {
51
203
  projectDir = projectDir || process.cwd();
52
204
  const configKey = `config-${projectDir}`;
53
205
 
54
- const configPathTs = path.join(projectDir, "vaderjs.config.ts");
55
206
  const configPathJs = path.join(projectDir, "vaderjs.config.js");
207
+ const configPathTs = path.join(projectDir, "vaderjs.config.ts");
56
208
 
57
209
  let configPath: string | null = null;
58
210
  let stat: fs.Stats | null = null;
@@ -160,6 +312,20 @@ async function parallelForEach<T>(
160
312
  }
161
313
  }
162
314
 
315
+ async function copyIfNeeded(src: string, dest: string): Promise<void> {
316
+ const cacheKey = `copy-${src}`;
317
+ const stat = await fs.stat(src).catch(() => null);
318
+ if (!stat) return;
319
+
320
+ const cached = buildCache.get(cacheKey);
321
+ if (cached && cached.mtime === stat.mtimeMs) {
322
+ return;
323
+ }
324
+
325
+ await fs.copyFile(src, dest);
326
+ buildCache.set(cacheKey, { mtime: stat.mtimeMs, hash: await getFileHash(src) });
327
+ }
328
+
163
329
  // --- BUILD LOGIC ---
164
330
 
165
331
  /**
@@ -380,31 +546,42 @@ async function copyPublicAssetsRecursive(srcDir: string, destDir: string): Promi
380
546
  });
381
547
  }
382
548
 
383
- async function copyIfNeeded(src: string, dest: string): Promise<void> {
384
- const cacheKey = `copy-${src}`;
385
- const stat = await fs.stat(src).catch(() => null);
386
- if (!stat) return;
549
+ async function buildAppEntrypoint(entryPath: string, name: string, isDev = false): Promise<void> {
550
+ const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
551
+ const outJsPath = path.join(outDir, 'index.js');
552
+ const outHtmlPath = path.join(outDir, 'index.html');
387
553
 
388
- const cached = buildCache.get(cacheKey);
389
- if (cached && cached.mtime === stat.mtimeMs) {
554
+ // Check if rebuild is needed
555
+ if (!isDev && !(await needsRebuild(entryPath, outJsPath))) {
556
+ logger.info(`Entrypoint "${name}" is up to date`);
390
557
  return;
391
558
  }
392
559
 
393
- await fs.copyFile(src, dest);
394
- buildCache.set(cacheKey, { mtime: stat.mtimeMs, hash: await getFileHash(src) });
395
- }
396
-
397
- /**
398
- * Step 6: Build app entrypoints with incremental compilation
399
- */
400
- async function buildAppEntrypoints(isDev = false): Promise<void> {
401
- if (!fsSync.existsSync(APP_DIR)) {
402
- logger.warn("No '/app' directory found, skipping app entrypoint build.");
403
- return;
404
- }
560
+ await fs.mkdir(outDir, { recursive: true });
405
561
 
406
- await fs.mkdir(DIST_DIR, { recursive: true });
562
+ // --- CSS HANDLING ---
563
+ const cssLinks: string[] = [];
564
+ let content = await fs.readFile(entryPath, "utf8");
565
+ const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
566
+
567
+ await parallelForEach(cssImports, async (match) => {
568
+ const cssImportPath = match[1];
569
+ const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
570
+
571
+ try {
572
+ await fs.access(sourceCssPath);
573
+ const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
574
+ const destCssPath = path.join(DIST_DIR, relativeCssPath);
575
+
576
+ await copyIfNeeded(sourceCssPath, destCssPath);
577
+ const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
578
+ cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
579
+ } catch {
580
+ logger.warn(`CSS file not found: ${sourceCssPath}`);
581
+ }
582
+ });
407
583
 
584
+ // --- HTML GENERATION ---
408
585
  const devClientScript = isDev
409
586
  ? `<script>
410
587
  const ws = new WebSocket("ws://" + location.host + "/__hmr");
@@ -415,71 +592,7 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
415
592
  </script>`
416
593
  : "";
417
594
 
418
- // Find all entrypoints
419
- const entrypoints: Array<{ name: string; path: string }> = [];
420
- function findEntrypoints(dir: string, baseDir = ""): void {
421
- const items = fsSync.readdirSync(dir, { withFileTypes: true });
422
- for (const item of items) {
423
- const fullPath = path.join(dir, item.name);
424
- const relativePath = path.join(baseDir, item.name);
425
-
426
- if (item.isDirectory()) {
427
- findEntrypoints(fullPath, relativePath);
428
- } else if (item.name === "index.tsx" || item.name === "index.jsx") {
429
- const name = baseDir || 'index';
430
- entrypoints.push({ name, path: fullPath });
431
- }
432
- }
433
- }
434
-
435
- findEntrypoints(APP_DIR);
436
-
437
- if (entrypoints.length === 0) {
438
- logger.info("No app entrypoints found.");
439
- return;
440
- }
441
-
442
- // Pre-load Vader source for faster processing
443
- const vaderSourcePromise = fs.readFile(VADER_SRC_PATH, "utf8");
444
-
445
- // Process entrypoints in parallel
446
- await parallelForEach(entrypoints, async ({ name, path: entryPath }) => {
447
- const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
448
- const outJsPath = path.join(outDir, 'index.js');
449
- const outHtmlPath = path.join(outDir, 'index.html');
450
-
451
- // Check if rebuild is needed
452
- if (!isDev && !(await needsRebuild(entryPath, outJsPath))) {
453
- logger.info(`Entrypoint "${name}" is up to date`);
454
- return;
455
- }
456
-
457
- await fs.mkdir(outDir, { recursive: true });
458
-
459
- // --- CSS HANDLING ---
460
- const cssLinks: string[] = [];
461
- let content = await fs.readFile(entryPath, "utf8");
462
- const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
463
-
464
- await parallelForEach(cssImports, async (match) => {
465
- const cssImportPath = match[1];
466
- const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
467
-
468
- try {
469
- await fs.access(sourceCssPath);
470
- const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
471
- const destCssPath = path.join(DIST_DIR, relativeCssPath);
472
-
473
- await copyIfNeeded(sourceCssPath, destCssPath);
474
- const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
475
- cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
476
- } catch {
477
- logger.warn(`CSS file not found: ${sourceCssPath}`);
478
- }
479
- });
480
-
481
- // --- HTML GENERATION ---
482
- const windowsStyle = globalThis.isBuildingForWindows ? `
595
+ const windowsStyle = globalThis.isBuildingForWindows ? `
483
596
  <style>
484
597
  .title-bar {
485
598
  display: flex;
@@ -493,8 +606,8 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
493
606
  -webkit-app-region: no-drag;
494
607
  }
495
608
  </style>` : '';
496
-
497
- const htmlContent = `<!DOCTYPE html>
609
+
610
+ const htmlContent = `<!DOCTYPE html>
498
611
  <html lang="en">
499
612
  <head>
500
613
  <meta charset="UTF-8" />
@@ -506,52 +619,71 @@ async function buildAppEntrypoints(isDev = false): Promise<void> {
506
619
  </head>
507
620
  <body>
508
621
  <div id="app"></div>
509
- <script src="./index.js"></script>
510
- ${devClientScript}
622
+ <script src="./index.js"></script>
511
623
  </body>
512
624
  </html>`;
513
-
514
- await fs.writeFile(outHtmlPath, htmlContent);
515
-
516
- // --- JS BUILD ---
517
- await build({
518
- entrypoints: [entryPath],
519
- outdir: outDir,
520
- target: "browser",
521
- minify: !isDev,
522
- sourcemap: isDev ? "inline" : "external",
523
- jsxFactory: "Vader.createElement",
524
- jsxFragment: "Fragment",
525
- plugins: [publicAssetPlugin()],
526
- loader: {
527
- '.js': 'jsx',
528
- '.ts': 'tsx',
529
- '.css': 'text',
530
- },
531
- define: isDev ? {
532
- 'process.env.NODE_ENV': '"development"'
533
- } : {
534
- 'process.env.NODE_ENV': '"production"'
535
- }
536
- });
537
-
538
- // --- FIX IMPORT PATHS IN JS ---
539
- let jsContent = await fs.readFile(outJsPath, "utf8");
540
- const vaderSource = await vaderSourcePromise;
541
-
542
- // Replace Vader import with actual source
543
- jsContent = jsContent.replace(
544
- /import\s+\*\s+as\s+Vader\s+from\s+['"]vaderjs['"];?/,
545
- vaderSource
546
- );
547
-
548
- await fs.writeFile(outJsPath, jsContent);
549
-
550
- // Update cache
551
- const stat = await fs.stat(entryPath);
552
- const hash = await getFileHash(entryPath);
553
- buildCache.set(`${entryPath}:${outJsPath}`, { mtime: stat.mtimeMs, hash });
625
+
626
+ await fs.writeFile(outHtmlPath, htmlContent);
627
+
628
+ // --- JS BUILD ---
629
+ await build({
630
+ entrypoints: [entryPath],
631
+ outdir: outDir,
632
+ target: "browser",
633
+ minify: !isDev,
634
+ sourcemap: isDev ? "inline" : "external",
635
+ jsxFactory: "Vader.createElement",
636
+ jsxFragment: "Fragment",
637
+ plugins: [publicAssetPlugin()],
638
+ loader: {
639
+ '.js': 'jsx',
640
+ '.ts': 'tsx',
641
+ '.css': 'text',
642
+ },
643
+ define: isDev ? {
644
+ 'process.env.NODE_ENV': '"development"'
645
+ } : {
646
+ 'process.env.NODE_ENV': '"production"'
647
+ }
554
648
  });
649
+
650
+ // --- FIX IMPORT PATHS IN JS ---
651
+ let jsContent = await fs.readFile(outJsPath, "utf8");
652
+ const vaderSource = await fs.readFile(VADER_SRC_PATH, "utf8");
653
+
654
+ // Replace Vader import with actual source
655
+ jsContent = jsContent.replace(
656
+ /import\s+\*\s+as\s+Vader\s+from\s+['"]vaderjs['"];?/,
657
+ vaderSource
658
+ );
659
+
660
+ await fs.writeFile(outJsPath, jsContent);
661
+
662
+ // Update cache
663
+ const stat = await fs.stat(entryPath);
664
+ const hash = await getFileHash(entryPath);
665
+ buildCache.set(`${entryPath}:${outJsPath}`, { mtime: stat.mtimeMs, hash });
666
+ }
667
+
668
+ async function buildAppEntrypoints(isDev = false): Promise<void> {
669
+ if (!fsSync.existsSync(APP_DIR)) {
670
+ logger.warn("No '/app' directory found, skipping app entrypoint build.");
671
+ return;
672
+ }
673
+
674
+ await fs.mkdir(DIST_DIR, { recursive: true });
675
+
676
+ // Find all index.jsx/tsx files in app directory
677
+ const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
678
+ .filter(file => /index\.(jsx|tsx)$/.test(file))
679
+ .map(file => ({
680
+ name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
681
+ path: path.join(APP_DIR, file)
682
+ }));
683
+
684
+ for (const { name, path: entryPath } of entries) {
685
+ await buildAppEntrypoint(entryPath, name, isDev);
686
+ }
555
687
  }
556
688
 
557
689
  // --- MAIN BUILD FUNCTION ---
@@ -602,15 +734,6 @@ export async function buildAll(isDev = false): Promise<void> {
602
734
  logger.success(`Build completed in ${duration}ms. Output in ${DIST_DIR}`);
603
735
  }
604
736
 
605
- // --- IMPORTS ---
606
- import runDevServer from "./cli/web/server";
607
- import { runProdServer } from "./cli/web/server";
608
- import { androidDev } from "./cli/android/dev.js";
609
- import { buildAndroid } from "./cli/android/build";
610
- import { buildWindows } from "vaderjs-native/cli/windows/build";
611
- import openWinApp from "vaderjs-native/cli/windows/dev";
612
- import { Config } from "vaderjs-native/config";
613
-
614
737
  // --- SCRIPT ENTRYPOINT ---
615
738
  async function main(): Promise<void> {
616
739
  // Banner
@@ -622,19 +745,53 @@ async function main(): Promise<void> {
622
745
  |____/____/_____/ /_____/ |_| |_|
623
746
  ${colors.reset}`);
624
747
 
625
- // Load config with caching
626
- config.port = config.port || 3000;
627
-
628
748
  const command = process.argv[2];
629
- const env = process.env.NODE_ENV || 'development';
749
+ const arg = process.argv[3];
630
750
 
631
751
  // Set global flags
632
752
  globalThis.isDev = command?.includes('dev') || false;
633
753
  globalThis.isBuildingForWindows = command?.includes('windows') || false;
634
754
 
755
+ // Commands that don't require config
756
+ if (command === "init") {
757
+ await initProject(arg);
758
+ return;
759
+ }
760
+
761
+ if (command === "add") {
762
+ if (!arg) {
763
+ logger.error("Please specify a plugin to add.");
764
+ process.exit(1);
765
+ }
766
+ await addPlugin(arg);
767
+ return;
768
+ }
769
+
770
+ // Load config for runtime commands
771
+ config = await loadConfig();
772
+ config.port = config.port || 3000;
773
+
635
774
  // Command router
636
775
  const commandHandlers: Record<string, () => Promise<void>> = {
776
+ 'add': async () => {
777
+ if (!arg) {
778
+ logger.error("Please specify a plugin to add.");
779
+ process.exit(1);
780
+ }
781
+ await addPlugin(arg);
782
+ },
783
+ 'list_plugins': async () => {
784
+ await listPlugins();
785
+ },
786
+ 'remove': async () => {
787
+ if (!arg) {
788
+ logger.error("Please specify a plugin to remove.");
789
+ process.exit(1);
790
+ }
791
+ await removePlugin(arg);
792
+ },
637
793
  'dev': async () => {
794
+ globalThis.isDev = true;
638
795
  await runDevServer("web");
639
796
  },
640
797
  'android:dev': async () => {
@@ -663,24 +820,27 @@ async function main(): Promise<void> {
663
820
  'serve': async () => {
664
821
  await buildAll(false);
665
822
  await runProdServer();
666
- },
667
- 'init': async () => {
668
- await init().catch((e: Error) => {
669
- console.error("Initialization failed:", e);
670
- process.exit(1);
671
- });
672
- },
823
+ }
673
824
  };
674
825
 
675
826
  if (command && command in commandHandlers) {
676
827
  await commandHandlers[command]();
677
- } else if (command) {
678
- logger.error(`Unknown command: ${command}`);
679
- logger.info("Available commands: dev, android:dev, android:build, windows:dev, windows:build, build, serve, init");
680
- process.exit(1);
681
828
  } else {
682
- logger.error("No command provided");
683
- logger.info("Available commands: dev, android:dev, android:build, windows:dev, windows:build, build, serve, init");
829
+ logger.error(`Unknown command: ${command ?? ""}`);
830
+ logger.info(`
831
+ Available commands:
832
+ dev Start dev server
833
+ build Build for production
834
+ serve Build + serve production
835
+ init [dir] Create a new Vader project
836
+ add <plugin> Add a Vader plugin
837
+ remove <plugin> Remove a Vader plugin
838
+ list_plugins List currently installed Vaderjs plugins
839
+ android:dev Start Android development
840
+ android:build Build Android app
841
+ windows:dev Start Windows development
842
+ windows:build Build Windows app
843
+ `.trim());
684
844
  process.exit(1);
685
845
  }
686
846
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs-native",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Build Native Applications using Vaderjs framework.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.ts"
@@ -15,6 +15,7 @@
15
15
  "ansi-colors": "latest",
16
16
  "autoprefixer": "^10.4.23",
17
17
  "postcss-cli": "^11.0.1",
18
- "tailwindcss": "latest"
18
+ "tailwindcss": "latest",
19
+ "vaderjs-types":"latest"
19
20
  }
20
21
  }