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