@thelacanians/vue-native-cli 0.4.14 → 0.4.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/dist/cli.js +466 -191
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +38 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +79 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +106 -112
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,29 +3,271 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
|
-
// src/commands/
|
|
6
|
+
// src/commands/build.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
+
import { spawn, execSync } from "child_process";
|
|
9
|
+
import { existsSync, readdirSync, mkdirSync, copyFileSync } from "fs";
|
|
10
|
+
import { join, basename } from "path";
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
function findXcodeProject(iosDir) {
|
|
13
|
+
if (!existsSync(iosDir)) return null;
|
|
14
|
+
for (const ext of [".xcworkspace", ".xcodeproj"]) {
|
|
15
|
+
try {
|
|
16
|
+
const entries = readdirSync(iosDir);
|
|
17
|
+
const match = entries.find((e) => e.endsWith(ext));
|
|
18
|
+
if (match) {
|
|
19
|
+
return {
|
|
20
|
+
path: join(iosDir, match),
|
|
21
|
+
isWorkspace: ext === ".xcworkspace"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function findReleaseApk(androidDir) {
|
|
30
|
+
const apkDir = join(androidDir, "app", "build", "outputs", "apk", "release");
|
|
31
|
+
if (existsSync(apkDir)) {
|
|
32
|
+
try {
|
|
33
|
+
const entries = readdirSync(apkDir);
|
|
34
|
+
const apk = entries.find((e) => e.endsWith(".apk") && !e.includes("androidTest"));
|
|
35
|
+
if (apk) {
|
|
36
|
+
return join(apkDir, apk);
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function findReleaseAab(androidDir) {
|
|
44
|
+
const aabDir = join(androidDir, "app", "build", "outputs", "bundle", "release");
|
|
45
|
+
if (existsSync(aabDir)) {
|
|
46
|
+
try {
|
|
47
|
+
const entries = readdirSync(aabDir);
|
|
48
|
+
const aab = entries.find((e) => e.endsWith(".aab"));
|
|
49
|
+
if (aab) {
|
|
50
|
+
return join(aabDir, aab);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function ensureOutputDir(outputPath) {
|
|
58
|
+
if (!existsSync(outputPath)) {
|
|
59
|
+
mkdirSync(outputPath, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var buildCommand = new Command("build").description("Create a release build of the app").argument("<platform>", "platform to build for (ios, android)").option("--mode <mode>", "build mode", "release").option("--output <path>", "output directory for the build artifact", "./build").option("--scheme <scheme>", "Xcode scheme to build (iOS only)").option("--aab", "build Android App Bundle (.aab) instead of APK").action(async (platform, options) => {
|
|
63
|
+
if (platform !== "ios" && platform !== "android") {
|
|
64
|
+
console.error(pc.red('Platform must be "ios" or "android"'));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
const outputDir = join(cwd, options.output);
|
|
69
|
+
console.log(pc.cyan(`
|
|
70
|
+
Vue Native \u2014 ${options.mode.charAt(0).toUpperCase() + options.mode.slice(1)} Build (${platform === "ios" ? "iOS" : "Android"})
|
|
71
|
+
`));
|
|
72
|
+
console.log(pc.white(" Building JS bundle for production..."));
|
|
73
|
+
try {
|
|
74
|
+
execSync("bun run vite build --mode production", { cwd, stdio: "inherit" });
|
|
75
|
+
console.log(pc.green(" \u2713 Bundle built\n"));
|
|
76
|
+
} catch {
|
|
77
|
+
console.error(pc.red(" \u2717 Bundle build failed"));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (platform === "ios") {
|
|
81
|
+
buildIOS(cwd, outputDir, options);
|
|
82
|
+
} else {
|
|
83
|
+
buildAndroid(cwd, outputDir, options);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
function buildIOS(cwd, outputDir, options) {
|
|
87
|
+
const iosDir = join(cwd, "ios");
|
|
88
|
+
const project = findXcodeProject(iosDir);
|
|
89
|
+
if (!project) {
|
|
90
|
+
console.log(pc.yellow(" No Xcode project found in ./ios/"));
|
|
91
|
+
console.log(pc.dim(" To add iOS support, create an Xcode project in the ios/ directory."));
|
|
92
|
+
console.log(pc.dim(" Bundle has been built to dist/vue-native-bundle.js\n"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const projectFlag = project.isWorkspace ? "-workspace" : "-project";
|
|
96
|
+
const scheme = options.scheme || project.path.split("/").pop()?.replace(/\.(xcworkspace|xcodeproj)$/, "") || "App";
|
|
97
|
+
const configuration = options.mode === "release" ? "Release" : "Debug";
|
|
98
|
+
const archivePath = join(outputDir, `${scheme}.xcarchive`);
|
|
99
|
+
ensureOutputDir(outputDir);
|
|
100
|
+
console.log(pc.white(` Archiving ${scheme} (${configuration})...`));
|
|
101
|
+
console.log(pc.dim(` Archive path: ${archivePath}`));
|
|
102
|
+
const xcodebuild = spawn(
|
|
103
|
+
"xcodebuild",
|
|
104
|
+
[
|
|
105
|
+
projectFlag,
|
|
106
|
+
project.path,
|
|
107
|
+
"-scheme",
|
|
108
|
+
scheme,
|
|
109
|
+
"-configuration",
|
|
110
|
+
configuration,
|
|
111
|
+
"-destination",
|
|
112
|
+
"generic/platform=iOS",
|
|
113
|
+
"-archivePath",
|
|
114
|
+
archivePath,
|
|
115
|
+
"archive"
|
|
116
|
+
],
|
|
117
|
+
{
|
|
118
|
+
cwd,
|
|
119
|
+
stdio: "pipe",
|
|
120
|
+
env: { ...process.env, DEVELOPER_DIR: "/Applications/Xcode.app/Contents/Developer" }
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
const cleanup = () => {
|
|
124
|
+
if (xcodebuild && !xcodebuild.killed) {
|
|
125
|
+
xcodebuild.kill();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
process.on("exit", cleanup);
|
|
129
|
+
process.on("SIGINT", cleanup);
|
|
130
|
+
process.on("SIGTERM", cleanup);
|
|
131
|
+
xcodebuild.stdout?.on("data", (data) => {
|
|
132
|
+
const text = data.toString().trim();
|
|
133
|
+
if (text.includes("Compiling") || text.includes("Linking") || text.includes("Signing")) {
|
|
134
|
+
console.log(pc.dim(` ${text.split("\n").pop()}`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
xcodebuild.stderr?.on("data", (data) => {
|
|
138
|
+
const text = data.toString().trim();
|
|
139
|
+
if (text.includes("error:")) {
|
|
140
|
+
console.log(pc.red(` ${text}`));
|
|
141
|
+
} else if (text.includes("warning:")) {
|
|
142
|
+
console.log(pc.yellow(` ${text}`));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
xcodebuild.on("error", (err) => {
|
|
146
|
+
console.error(pc.red(` xcodebuild process error: ${err.message}`));
|
|
147
|
+
cleanup();
|
|
148
|
+
});
|
|
149
|
+
xcodebuild.on("close", (code) => {
|
|
150
|
+
if (code !== 0) {
|
|
151
|
+
console.error(pc.red(` \u2717 Archive failed (exit code ${code})`));
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
console.log(pc.green(" \u2713 Archive successful\n"));
|
|
155
|
+
if (existsSync(archivePath)) {
|
|
156
|
+
console.log(pc.green(` Archive: ${archivePath}`));
|
|
157
|
+
console.log(pc.dim(" To export an IPA, open the archive in Xcode Organizer or run:"));
|
|
158
|
+
console.log(pc.dim(` xcodebuild -exportArchive -archivePath "${archivePath}" -exportOptionsPlist ExportOptions.plist -exportPath "${outputDir}"
|
|
159
|
+
`));
|
|
160
|
+
} else {
|
|
161
|
+
console.log(pc.yellow(" Archive path not found. Check Xcode build settings.\n"));
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function buildAndroid(cwd, outputDir, options) {
|
|
166
|
+
const androidDir = join(cwd, "android");
|
|
167
|
+
if (!existsSync(androidDir)) {
|
|
168
|
+
console.log(pc.yellow(" No android/ directory found."));
|
|
169
|
+
console.log(pc.dim(" To add Android support, create an Android project in the android/ directory."));
|
|
170
|
+
console.log(pc.dim(" Bundle has been built to dist/vue-native-bundle.js\n"));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const gradlew = join(androidDir, "gradlew");
|
|
174
|
+
if (!existsSync(gradlew)) {
|
|
175
|
+
console.error(pc.red(" \u2717 gradlew not found in android/ directory"));
|
|
176
|
+
console.log(pc.dim(" Make sure your Android project has the Gradle wrapper.\n"));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const gradleTask = options.aab ? "bundleRelease" : "assembleRelease";
|
|
180
|
+
const artifactType = options.aab ? "AAB" : "APK";
|
|
181
|
+
ensureOutputDir(outputDir);
|
|
182
|
+
console.log(pc.white(` Building release ${artifactType} with Gradle...`));
|
|
183
|
+
const gradle = spawn(
|
|
184
|
+
"./gradlew",
|
|
185
|
+
[gradleTask],
|
|
186
|
+
{
|
|
187
|
+
cwd: androidDir,
|
|
188
|
+
stdio: "pipe",
|
|
189
|
+
env: { ...process.env }
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
const cleanupGradle = () => {
|
|
193
|
+
if (gradle && !gradle.killed) {
|
|
194
|
+
gradle.kill();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
process.on("exit", cleanupGradle);
|
|
198
|
+
process.on("SIGINT", cleanupGradle);
|
|
199
|
+
process.on("SIGTERM", cleanupGradle);
|
|
200
|
+
gradle.stdout?.on("data", (data) => {
|
|
201
|
+
const text = data.toString().trim();
|
|
202
|
+
if (text) {
|
|
203
|
+
console.log(pc.dim(` ${text}`));
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
gradle.stderr?.on("data", (data) => {
|
|
207
|
+
const text = data.toString().trim();
|
|
208
|
+
if (text.includes("ERROR") || text.includes("FAILURE")) {
|
|
209
|
+
console.log(pc.red(` ${text}`));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
gradle.on("error", (err) => {
|
|
213
|
+
console.error(pc.red(` Gradle process error: ${err.message}`));
|
|
214
|
+
cleanupGradle();
|
|
215
|
+
});
|
|
216
|
+
gradle.on("close", (code) => {
|
|
217
|
+
if (code !== 0) {
|
|
218
|
+
console.error(pc.red(` \u2717 Gradle build failed (exit code ${code})`));
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
console.log(pc.green(" \u2713 Build successful\n"));
|
|
222
|
+
if (options.aab) {
|
|
223
|
+
const aabPath = findReleaseAab(androidDir);
|
|
224
|
+
if (aabPath) {
|
|
225
|
+
const destPath = join(outputDir, basename(aabPath));
|
|
226
|
+
copyFileSync(aabPath, destPath);
|
|
227
|
+
console.log(pc.green(` AAB copied to: ${destPath}`));
|
|
228
|
+
console.log(pc.dim(" Upload this file to the Google Play Console.\n"));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(pc.yellow(" Could not locate release AAB."));
|
|
231
|
+
console.log(pc.dim(" Expected at android/app/build/outputs/bundle/release/\n"));
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
const apkPath = findReleaseApk(androidDir);
|
|
235
|
+
if (apkPath) {
|
|
236
|
+
const destPath = join(outputDir, basename(apkPath));
|
|
237
|
+
copyFileSync(apkPath, destPath);
|
|
238
|
+
console.log(pc.green(` APK copied to: ${destPath}`));
|
|
239
|
+
console.log(pc.dim(' Install with: adb install -r "' + destPath + '"\n'));
|
|
240
|
+
} else {
|
|
241
|
+
console.log(pc.yellow(" Could not locate release APK."));
|
|
242
|
+
console.log(pc.dim(" Expected at android/app/build/outputs/apk/release/\n"));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/commands/create.ts
|
|
249
|
+
import { Command as Command2 } from "commander";
|
|
8
250
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
9
|
-
import { join, dirname } from "path";
|
|
251
|
+
import { join as join2, dirname } from "path";
|
|
10
252
|
import { fileURLToPath } from "url";
|
|
11
|
-
import { existsSync } from "fs";
|
|
12
|
-
import
|
|
253
|
+
import { existsSync as existsSync2 } from "fs";
|
|
254
|
+
import pc2 from "picocolors";
|
|
13
255
|
var VERSION = "0.4.14";
|
|
14
|
-
var createCommand = new
|
|
256
|
+
var createCommand = new Command2("create").description("Create a new Vue Native project").argument("<name>", "project name").option("-t, --template <template>", "project template (blank, tabs, drawer)", "blank").action(async (name, options) => {
|
|
15
257
|
const template = options.template;
|
|
16
258
|
if (!["blank", "tabs", "drawer"].includes(template)) {
|
|
17
|
-
console.error(
|
|
259
|
+
console.error(pc2.red(`Invalid template "${template}". Choose: blank, tabs, drawer`));
|
|
18
260
|
process.exit(1);
|
|
19
261
|
}
|
|
20
|
-
const dir =
|
|
21
|
-
console.log(
|
|
22
|
-
Creating Vue Native project: ${
|
|
262
|
+
const dir = join2(process.cwd(), name);
|
|
263
|
+
console.log(pc2.cyan(`
|
|
264
|
+
Creating Vue Native project: ${pc2.bold(name)} (template: ${template})
|
|
23
265
|
`));
|
|
24
266
|
try {
|
|
25
267
|
await mkdir(dir, { recursive: true });
|
|
26
|
-
await mkdir(
|
|
27
|
-
await mkdir(
|
|
28
|
-
await writeFile(
|
|
268
|
+
await mkdir(join2(dir, "app"), { recursive: true });
|
|
269
|
+
await mkdir(join2(dir, "app", "pages"), { recursive: true });
|
|
270
|
+
await writeFile(join2(dir, "package.json"), JSON.stringify({
|
|
29
271
|
name,
|
|
30
272
|
version: "0.0.1",
|
|
31
273
|
private: true,
|
|
@@ -47,7 +289,7 @@ Creating Vue Native project: ${pc.bold(name)} (template: ${template})
|
|
|
47
289
|
"typescript": "^5.7.0"
|
|
48
290
|
}
|
|
49
291
|
}, null, 2));
|
|
50
|
-
await writeFile(
|
|
292
|
+
await writeFile(join2(dir, "vite.config.ts"), `import { defineConfig } from 'vite'
|
|
51
293
|
import vue from '@vitejs/plugin-vue'
|
|
52
294
|
import vueNative from '@thelacanians/vue-native-vite-plugin'
|
|
53
295
|
|
|
@@ -55,7 +297,7 @@ export default defineConfig({
|
|
|
55
297
|
plugins: [vue(), vueNative()],
|
|
56
298
|
})
|
|
57
299
|
`);
|
|
58
|
-
await writeFile(
|
|
300
|
+
await writeFile(join2(dir, "tsconfig.json"), JSON.stringify({
|
|
59
301
|
compilerOptions: {
|
|
60
302
|
target: "ES2020",
|
|
61
303
|
module: "ESNext",
|
|
@@ -71,12 +313,12 @@ export default defineConfig({
|
|
|
71
313
|
include: ["app/**/*", "env.d.ts"]
|
|
72
314
|
}, null, 2));
|
|
73
315
|
await generateTemplateFiles(dir, name, template);
|
|
74
|
-
const iosDir =
|
|
75
|
-
const iosSrcDir =
|
|
316
|
+
const iosDir = join2(dir, "ios");
|
|
317
|
+
const iosSrcDir = join2(iosDir, "Sources");
|
|
76
318
|
await mkdir(iosSrcDir, { recursive: true });
|
|
77
319
|
const xcodeProjectName = name.replace(/[^a-zA-Z0-9]/g, "");
|
|
78
320
|
const bundleId = `com.vuenative.${name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()}`;
|
|
79
|
-
await writeFile(
|
|
321
|
+
await writeFile(join2(iosDir, "project.yml"), `name: ${xcodeProjectName}
|
|
80
322
|
options:
|
|
81
323
|
bundleIdPrefix: com.vuenative
|
|
82
324
|
deploymentTarget:
|
|
@@ -107,7 +349,7 @@ targets:
|
|
|
107
349
|
- path: ../dist/vue-native-bundle.js
|
|
108
350
|
optional: true
|
|
109
351
|
`);
|
|
110
|
-
await writeFile(
|
|
352
|
+
await writeFile(join2(iosSrcDir, "Info.plist"), `<?xml version="1.0" encoding="UTF-8"?>
|
|
111
353
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
112
354
|
<plist version="1.0">
|
|
113
355
|
<dict>
|
|
@@ -173,7 +415,7 @@ targets:
|
|
|
173
415
|
</dict>
|
|
174
416
|
</plist>
|
|
175
417
|
`);
|
|
176
|
-
await writeFile(
|
|
418
|
+
await writeFile(join2(iosSrcDir, "AppDelegate.swift"), `import UIKit
|
|
177
419
|
|
|
178
420
|
@main
|
|
179
421
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
@@ -197,7 +439,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
197
439
|
}
|
|
198
440
|
}
|
|
199
441
|
`);
|
|
200
|
-
await writeFile(
|
|
442
|
+
await writeFile(join2(iosSrcDir, "SceneDelegate.swift"), `import UIKit
|
|
201
443
|
import VueNativeCore
|
|
202
444
|
|
|
203
445
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -225,29 +467,29 @@ class AppViewController: VueNativeViewController {
|
|
|
225
467
|
#endif
|
|
226
468
|
}
|
|
227
469
|
`);
|
|
228
|
-
const androidDir =
|
|
229
|
-
const androidAppDir =
|
|
470
|
+
const androidDir = join2(dir, "android");
|
|
471
|
+
const androidAppDir = join2(androidDir, "app");
|
|
230
472
|
const androidPkg = `com.vuenative.${name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()}`;
|
|
231
473
|
const androidPkgPath = androidPkg.replace(/\./g, "/");
|
|
232
|
-
const androidSrcDir =
|
|
233
|
-
const androidKotlinDir =
|
|
234
|
-
const androidResValuesDir =
|
|
235
|
-
const androidResXmlDir =
|
|
236
|
-
const androidDebugResXmlDir =
|
|
237
|
-
const androidGradleWrapperDir =
|
|
474
|
+
const androidSrcDir = join2(androidAppDir, "src", "main");
|
|
475
|
+
const androidKotlinDir = join2(androidSrcDir, "kotlin", androidPkgPath);
|
|
476
|
+
const androidResValuesDir = join2(androidSrcDir, "res", "values");
|
|
477
|
+
const androidResXmlDir = join2(androidSrcDir, "res", "xml");
|
|
478
|
+
const androidDebugResXmlDir = join2(androidAppDir, "src", "debug", "res", "xml");
|
|
479
|
+
const androidGradleWrapperDir = join2(androidDir, "gradle", "wrapper");
|
|
238
480
|
await mkdir(androidKotlinDir, { recursive: true });
|
|
239
481
|
await mkdir(androidResValuesDir, { recursive: true });
|
|
240
482
|
await mkdir(androidResXmlDir, { recursive: true });
|
|
241
483
|
await mkdir(androidDebugResXmlDir, { recursive: true });
|
|
242
484
|
await mkdir(androidGradleWrapperDir, { recursive: true });
|
|
243
|
-
await writeFile(
|
|
485
|
+
await writeFile(join2(androidDir, "build.gradle.kts"), `// Top-level build file
|
|
244
486
|
plugins {
|
|
245
487
|
id("com.android.application") version "8.7.3" apply false
|
|
246
488
|
id("com.android.library") version "8.7.3" apply false
|
|
247
489
|
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
|
|
248
490
|
}
|
|
249
491
|
`);
|
|
250
|
-
await writeFile(
|
|
492
|
+
await writeFile(join2(androidDir, "settings.gradle.kts"), `pluginManagement {
|
|
251
493
|
repositories {
|
|
252
494
|
google()
|
|
253
495
|
mavenCentral()
|
|
@@ -273,7 +515,7 @@ dependencyResolutionManagement {
|
|
|
273
515
|
rootProject.name = "${name}"
|
|
274
516
|
include(":app")
|
|
275
517
|
`);
|
|
276
|
-
await writeFile(
|
|
518
|
+
await writeFile(join2(androidAppDir, "build.gradle.kts"), `plugins {
|
|
277
519
|
id("com.android.application")
|
|
278
520
|
id("org.jetbrains.kotlin.android")
|
|
279
521
|
}
|
|
@@ -321,13 +563,13 @@ dependencies {
|
|
|
321
563
|
implementation("androidx.core:core-ktx:1.15.0")
|
|
322
564
|
}
|
|
323
565
|
`);
|
|
324
|
-
await writeFile(
|
|
566
|
+
await writeFile(join2(androidAppDir, "proguard-rules.pro"), `# Vue Native
|
|
325
567
|
-keep class com.vuenative.** { *; }
|
|
326
568
|
|
|
327
569
|
# J2V8
|
|
328
570
|
-keep class com.eclipsesource.v8.** { *; }
|
|
329
571
|
`);
|
|
330
|
-
await writeFile(
|
|
572
|
+
await writeFile(join2(androidSrcDir, "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
331
573
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
332
574
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
333
575
|
|
|
@@ -350,12 +592,12 @@ dependencies {
|
|
|
350
592
|
</application>
|
|
351
593
|
</manifest>
|
|
352
594
|
`);
|
|
353
|
-
await writeFile(
|
|
595
|
+
await writeFile(join2(androidResValuesDir, "strings.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
354
596
|
<resources>
|
|
355
597
|
<string name="app_name">${name}</string>
|
|
356
598
|
</resources>
|
|
357
599
|
`);
|
|
358
|
-
await writeFile(
|
|
600
|
+
await writeFile(join2(androidResValuesDir, "themes.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
359
601
|
<resources>
|
|
360
602
|
<style name="Theme.VueNative" parent="Theme.MaterialComponents.Light.NoActionBar">
|
|
361
603
|
<item name="colorPrimary">#4F46E5</item>
|
|
@@ -368,12 +610,12 @@ dependencies {
|
|
|
368
610
|
</style>
|
|
369
611
|
</resources>
|
|
370
612
|
`);
|
|
371
|
-
await writeFile(
|
|
613
|
+
await writeFile(join2(androidResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
372
614
|
<network-security-config>
|
|
373
615
|
<base-config cleartextTrafficPermitted="false" />
|
|
374
616
|
</network-security-config>
|
|
375
617
|
`);
|
|
376
|
-
await writeFile(
|
|
618
|
+
await writeFile(join2(androidDebugResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
377
619
|
<network-security-config>
|
|
378
620
|
<domain-config cleartextTrafficPermitted="true">
|
|
379
621
|
<domain includeSubdomains="true">localhost</domain>
|
|
@@ -382,7 +624,7 @@ dependencies {
|
|
|
382
624
|
</domain-config>
|
|
383
625
|
</network-security-config>
|
|
384
626
|
`);
|
|
385
|
-
await writeFile(
|
|
627
|
+
await writeFile(join2(androidKotlinDir, "MainActivity.kt"), `package ${androidPkg}
|
|
386
628
|
|
|
387
629
|
import com.vuenative.core.VueNativeActivity
|
|
388
630
|
|
|
@@ -396,20 +638,20 @@ class MainActivity : VueNativeActivity() {
|
|
|
396
638
|
}
|
|
397
639
|
}
|
|
398
640
|
`);
|
|
399
|
-
await writeFile(
|
|
641
|
+
await writeFile(join2(androidDir, "gradle.properties"), `# Project-wide Gradle settings
|
|
400
642
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
401
643
|
android.useAndroidX=true
|
|
402
644
|
kotlin.code.style=official
|
|
403
645
|
android.nonTransitiveRClass=true
|
|
404
646
|
`);
|
|
405
|
-
await writeFile(
|
|
647
|
+
await writeFile(join2(androidGradleWrapperDir, "gradle-wrapper.properties"), `distributionBase=GRADLE_USER_HOME
|
|
406
648
|
distributionPath=wrapper/dists
|
|
407
649
|
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
408
650
|
networkTimeout=10000
|
|
409
651
|
zipStoreBase=GRADLE_USER_HOME
|
|
410
652
|
zipStorePath=wrapper/dists
|
|
411
653
|
`);
|
|
412
|
-
await writeFile(
|
|
654
|
+
await writeFile(join2(dir, "vue-native.config.ts"), `import { defineConfig } from '@thelacanians/vue-native-cli'
|
|
413
655
|
|
|
414
656
|
export default defineConfig({
|
|
415
657
|
name: '${name}',
|
|
@@ -424,7 +666,7 @@ export default defineConfig({
|
|
|
424
666
|
},
|
|
425
667
|
})
|
|
426
668
|
`);
|
|
427
|
-
await writeFile(
|
|
669
|
+
await writeFile(join2(dir, "env.d.ts"), `/// <reference types="vite/client" />
|
|
428
670
|
declare module '*.vue' {
|
|
429
671
|
import type { DefineComponent } from '@thelacanians/vue-native-runtime'
|
|
430
672
|
const component: DefineComponent<{}, {}, any>
|
|
@@ -432,7 +674,7 @@ declare module '*.vue' {
|
|
|
432
674
|
}
|
|
433
675
|
declare const __DEV__: boolean
|
|
434
676
|
`);
|
|
435
|
-
await writeFile(
|
|
677
|
+
await writeFile(join2(dir, ".gitignore"), `node_modules/
|
|
436
678
|
dist/
|
|
437
679
|
*.xcuserstate
|
|
438
680
|
*.xcuserdatad/
|
|
@@ -455,30 +697,30 @@ local.properties
|
|
|
455
697
|
*.jks
|
|
456
698
|
`);
|
|
457
699
|
const cliDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
458
|
-
const bundledNative =
|
|
459
|
-
if (
|
|
460
|
-
const nativeDir =
|
|
700
|
+
const bundledNative = join2(cliDir, "native");
|
|
701
|
+
if (existsSync2(bundledNative)) {
|
|
702
|
+
const nativeDir = join2(dir, "native");
|
|
461
703
|
await cp(bundledNative, nativeDir, { recursive: true });
|
|
462
|
-
console.log(
|
|
704
|
+
console.log(pc2.dim(" Bundled native/ copied as fallback.\n"));
|
|
463
705
|
}
|
|
464
|
-
console.log(
|
|
465
|
-
console.log(
|
|
466
|
-
console.log(
|
|
467
|
-
console.log(
|
|
468
|
-
console.log(
|
|
469
|
-
console.log(
|
|
470
|
-
console.log(
|
|
471
|
-
console.log(
|
|
472
|
-
console.log(
|
|
473
|
-
console.log(
|
|
474
|
-
console.log(
|
|
706
|
+
console.log(pc2.green(" Project created successfully!\n"));
|
|
707
|
+
console.log(pc2.white(" Next steps:\n"));
|
|
708
|
+
console.log(pc2.white(` cd ${name}`));
|
|
709
|
+
console.log(pc2.white(" bun install"));
|
|
710
|
+
console.log(pc2.white(" vue-native dev\n"));
|
|
711
|
+
console.log(pc2.white(" To run on iOS:"));
|
|
712
|
+
console.log(pc2.white(" vue-native run ios\n"));
|
|
713
|
+
console.log(pc2.white(" To run on Android:"));
|
|
714
|
+
console.log(pc2.dim(" Open android/ in Android Studio, or run:"));
|
|
715
|
+
console.log(pc2.dim(" cd android && gradle wrapper && cd .."));
|
|
716
|
+
console.log(pc2.white(" vue-native run android\n"));
|
|
475
717
|
} catch (err) {
|
|
476
|
-
console.error(
|
|
718
|
+
console.error(pc2.red(`Error creating project: ${err.message}`));
|
|
477
719
|
process.exit(1);
|
|
478
720
|
}
|
|
479
721
|
});
|
|
480
722
|
async function generateTemplateFiles(dir, name, template) {
|
|
481
|
-
const pagesDir =
|
|
723
|
+
const pagesDir = join2(dir, "app", "pages");
|
|
482
724
|
if (template === "blank") {
|
|
483
725
|
await generateBlankTemplate(dir, pagesDir);
|
|
484
726
|
} else if (template === "tabs") {
|
|
@@ -488,7 +730,7 @@ async function generateTemplateFiles(dir, name, template) {
|
|
|
488
730
|
}
|
|
489
731
|
}
|
|
490
732
|
async function generateBlankTemplate(dir, pagesDir) {
|
|
491
|
-
await writeFile(
|
|
733
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
492
734
|
import { createRouter } from '@thelacanians/vue-native-navigation'
|
|
493
735
|
import App from './App.vue'
|
|
494
736
|
import Home from './pages/Home.vue'
|
|
@@ -501,7 +743,7 @@ const app = createApp(App)
|
|
|
501
743
|
app.use(router)
|
|
502
744
|
app.start()
|
|
503
745
|
`);
|
|
504
|
-
await writeFile(
|
|
746
|
+
await writeFile(join2(dir, "app", "App.vue"), `<template>
|
|
505
747
|
<VSafeArea :style="{ flex: 1, backgroundColor: '#ffffff' }">
|
|
506
748
|
<RouterView />
|
|
507
749
|
</VSafeArea>
|
|
@@ -511,7 +753,7 @@ app.start()
|
|
|
511
753
|
import { RouterView } from '@thelacanians/vue-native-navigation'
|
|
512
754
|
</script>
|
|
513
755
|
`);
|
|
514
|
-
await writeFile(
|
|
756
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
515
757
|
import { ref } from 'vue'
|
|
516
758
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
517
759
|
|
|
@@ -563,13 +805,13 @@ const styles = createStyleSheet({
|
|
|
563
805
|
`);
|
|
564
806
|
}
|
|
565
807
|
async function generateTabsTemplate(dir, pagesDir) {
|
|
566
|
-
await writeFile(
|
|
808
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
567
809
|
import App from './App.vue'
|
|
568
810
|
|
|
569
811
|
const app = createApp(App)
|
|
570
812
|
app.start()
|
|
571
813
|
`);
|
|
572
|
-
await writeFile(
|
|
814
|
+
await writeFile(join2(dir, "app", "App.vue"), `<script setup lang="ts">
|
|
573
815
|
import { createTabNavigator } from '@thelacanians/vue-native-navigation'
|
|
574
816
|
import Home from './pages/Home.vue'
|
|
575
817
|
import Settings from './pages/Settings.vue'
|
|
@@ -588,7 +830,7 @@ const { TabNavigator } = createTabNavigator()
|
|
|
588
830
|
</VSafeArea>
|
|
589
831
|
</template>
|
|
590
832
|
`);
|
|
591
|
-
await writeFile(
|
|
833
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
592
834
|
import { ref } from 'vue'
|
|
593
835
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
594
836
|
|
|
@@ -632,7 +874,7 @@ const styles = createStyleSheet({
|
|
|
632
874
|
</VView>
|
|
633
875
|
</template>
|
|
634
876
|
`);
|
|
635
|
-
await writeFile(
|
|
877
|
+
await writeFile(join2(pagesDir, "Settings.vue"), `<script setup lang="ts">
|
|
636
878
|
import { ref } from 'vue'
|
|
637
879
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
638
880
|
|
|
@@ -676,13 +918,13 @@ const styles = createStyleSheet({
|
|
|
676
918
|
`);
|
|
677
919
|
}
|
|
678
920
|
async function generateDrawerTemplate(dir, pagesDir) {
|
|
679
|
-
await writeFile(
|
|
921
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
680
922
|
import App from './App.vue'
|
|
681
923
|
|
|
682
924
|
const app = createApp(App)
|
|
683
925
|
app.start()
|
|
684
926
|
`);
|
|
685
|
-
await writeFile(
|
|
927
|
+
await writeFile(join2(dir, "app", "App.vue"), `<script setup lang="ts">
|
|
686
928
|
import { createDrawerNavigator } from '@thelacanians/vue-native-navigation'
|
|
687
929
|
import Home from './pages/Home.vue'
|
|
688
930
|
import About from './pages/About.vue'
|
|
@@ -701,7 +943,7 @@ const { DrawerNavigator } = createDrawerNavigator()
|
|
|
701
943
|
</VSafeArea>
|
|
702
944
|
</template>
|
|
703
945
|
`);
|
|
704
|
-
await writeFile(
|
|
946
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
705
947
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
706
948
|
import { useDrawer } from '@thelacanians/vue-native-navigation'
|
|
707
949
|
|
|
@@ -754,7 +996,7 @@ const styles = createStyleSheet({
|
|
|
754
996
|
</VView>
|
|
755
997
|
</template>
|
|
756
998
|
`);
|
|
757
|
-
await writeFile(
|
|
999
|
+
await writeFile(join2(pagesDir, "About.vue"), `<script setup lang="ts">
|
|
758
1000
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
759
1001
|
import { useDrawer } from '@thelacanians/vue-native-navigation'
|
|
760
1002
|
|
|
@@ -810,19 +1052,19 @@ const styles = createStyleSheet({
|
|
|
810
1052
|
}
|
|
811
1053
|
|
|
812
1054
|
// src/commands/dev.ts
|
|
813
|
-
import { Command as
|
|
814
|
-
import { spawn, execSync } from "child_process";
|
|
1055
|
+
import { Command as Command3 } from "commander";
|
|
1056
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
815
1057
|
import { readFile } from "fs/promises";
|
|
816
|
-
import { existsSync as
|
|
817
|
-
import { join as
|
|
1058
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1059
|
+
import { join as join3 } from "path";
|
|
818
1060
|
import { watch } from "chokidar";
|
|
819
1061
|
import { WebSocketServer, WebSocket } from "ws";
|
|
820
|
-
import
|
|
1062
|
+
import pc3 from "picocolors";
|
|
821
1063
|
var DEFAULT_PORT = 8174;
|
|
822
1064
|
var BUNDLE_FILE = "dist/vue-native-bundle.js";
|
|
823
1065
|
function detectIOSSimulators() {
|
|
824
1066
|
try {
|
|
825
|
-
const output =
|
|
1067
|
+
const output = execSync2("xcrun simctl list devices available -j", {
|
|
826
1068
|
stdio: "pipe",
|
|
827
1069
|
encoding: "utf8"
|
|
828
1070
|
});
|
|
@@ -847,33 +1089,33 @@ function detectIOSSimulators() {
|
|
|
847
1089
|
}
|
|
848
1090
|
function bootSimulator(udid) {
|
|
849
1091
|
try {
|
|
850
|
-
|
|
1092
|
+
execSync2(`xcrun simctl boot "${udid}"`, { stdio: "pipe" });
|
|
851
1093
|
} catch {
|
|
852
1094
|
}
|
|
853
1095
|
try {
|
|
854
|
-
|
|
1096
|
+
execSync2("open -a Simulator", { stdio: "pipe" });
|
|
855
1097
|
} catch {
|
|
856
1098
|
}
|
|
857
1099
|
}
|
|
858
1100
|
function detectAndroidEmulators() {
|
|
859
1101
|
try {
|
|
860
|
-
const output =
|
|
1102
|
+
const output = execSync2("adb devices", { stdio: "pipe", encoding: "utf8" });
|
|
861
1103
|
const lines = output.split("\n").filter((l) => l.includes("device") && !l.startsWith("List"));
|
|
862
1104
|
return lines.map((l) => l.split(" ")[0]).filter(Boolean);
|
|
863
1105
|
} catch {
|
|
864
1106
|
return [];
|
|
865
1107
|
}
|
|
866
1108
|
}
|
|
867
|
-
var devCommand = new
|
|
1109
|
+
var devCommand = new Command3("dev").description("Start the Vue Native dev server with hot reload").option("-p, --port <port>", "WebSocket port for hot reload", String(DEFAULT_PORT)).option("--ios", "auto-detect and launch iOS Simulator").option("--android", "auto-detect Android emulator").option("--simulator <name>", "specify iOS Simulator name").action(async (options) => {
|
|
868
1110
|
const port = parseInt(options.port, 10);
|
|
869
1111
|
const cwd = process.cwd();
|
|
870
|
-
const bundlePath =
|
|
871
|
-
console.log(
|
|
1112
|
+
const bundlePath = join3(cwd, BUNDLE_FILE);
|
|
1113
|
+
console.log(pc3.cyan("\n Vue Native Dev Server\n"));
|
|
872
1114
|
if (options.ios) {
|
|
873
|
-
console.log(
|
|
1115
|
+
console.log(pc3.white(" Detecting iOS Simulators..."));
|
|
874
1116
|
const simulators = detectIOSSimulators();
|
|
875
1117
|
if (simulators.length === 0) {
|
|
876
|
-
console.log(
|
|
1118
|
+
console.log(pc3.yellow(" No iOS Simulators found. Install Xcode and create a simulator."));
|
|
877
1119
|
} else {
|
|
878
1120
|
let target = simulators.find((s) => s.state === "Booted");
|
|
879
1121
|
if (!target && options.simulator) {
|
|
@@ -884,46 +1126,69 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
884
1126
|
}
|
|
885
1127
|
if (target) {
|
|
886
1128
|
if (target.state !== "Booted") {
|
|
887
|
-
console.log(
|
|
1129
|
+
console.log(pc3.white(` Booting ${target.name}...`));
|
|
888
1130
|
bootSimulator(target.udid);
|
|
889
1131
|
}
|
|
890
|
-
console.log(
|
|
1132
|
+
console.log(pc3.green(` iOS Simulator ready: ${target.name}`));
|
|
891
1133
|
}
|
|
892
1134
|
}
|
|
893
1135
|
console.log();
|
|
894
1136
|
}
|
|
895
1137
|
if (options.android) {
|
|
896
|
-
console.log(
|
|
1138
|
+
console.log(pc3.white(" Detecting Android emulators..."));
|
|
897
1139
|
const emulators = detectAndroidEmulators();
|
|
898
1140
|
if (emulators.length === 0) {
|
|
899
|
-
console.log(
|
|
1141
|
+
console.log(pc3.yellow(" No Android emulators detected. Start one via Android Studio or `emulator -avd <name>`."));
|
|
900
1142
|
} else {
|
|
901
|
-
console.log(
|
|
1143
|
+
console.log(pc3.green(` Android emulator(s) connected: ${emulators.join(", ")}`));
|
|
902
1144
|
}
|
|
903
1145
|
console.log();
|
|
904
1146
|
}
|
|
1147
|
+
let lanIP = "";
|
|
1148
|
+
try {
|
|
1149
|
+
const nets = await import("os").then((os) => os.networkInterfaces());
|
|
1150
|
+
for (const ifaces of Object.values(nets)) {
|
|
1151
|
+
for (const iface of ifaces ?? []) {
|
|
1152
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1153
|
+
lanIP = iface.address;
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (lanIP) break;
|
|
1158
|
+
}
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
const isPrivateOrLocal = (addr) => {
|
|
1162
|
+
if (!addr) return false;
|
|
1163
|
+
const ip = addr.replace(/^::ffff:/, "");
|
|
1164
|
+
if (ip === "127.0.0.1" || ip === "::1") return true;
|
|
1165
|
+
if (ip === "10.0.2.2") return true;
|
|
1166
|
+
if (ip.startsWith("10.")) return true;
|
|
1167
|
+
if (ip.startsWith("192.168.")) return true;
|
|
1168
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(ip)) return true;
|
|
1169
|
+
return false;
|
|
1170
|
+
};
|
|
905
1171
|
const wss = new WebSocketServer({
|
|
906
1172
|
port,
|
|
907
1173
|
verifyClient: (info) => {
|
|
908
|
-
|
|
909
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
1174
|
+
return isPrivateOrLocal(info.req.socket.remoteAddress);
|
|
910
1175
|
}
|
|
911
1176
|
});
|
|
912
1177
|
const clients = /* @__PURE__ */ new Set();
|
|
913
1178
|
wss.on("connection", (ws) => {
|
|
914
1179
|
clients.add(ws);
|
|
915
1180
|
ws.send(JSON.stringify({ type: "connected" }));
|
|
916
|
-
console.log(
|
|
1181
|
+
console.log(pc3.green(` Client connected (${clients.size} total)`));
|
|
917
1182
|
readFile(bundlePath, "utf8").then((bundle) => {
|
|
918
1183
|
if (ws.readyState === WebSocket.OPEN) {
|
|
919
1184
|
ws.send(JSON.stringify({ type: "bundle", bundle }));
|
|
920
|
-
console.log(
|
|
1185
|
+
console.log(pc3.dim(` Sent bundle to new client (${Math.round(bundle.length / 1024)}KB)`));
|
|
921
1186
|
}
|
|
922
1187
|
}).catch(() => {
|
|
923
1188
|
});
|
|
924
1189
|
ws.on("close", () => {
|
|
925
1190
|
clients.delete(ws);
|
|
926
|
-
console.log(
|
|
1191
|
+
console.log(pc3.dim(` Client disconnected (${clients.size} remaining)`));
|
|
927
1192
|
});
|
|
928
1193
|
ws.on("message", (data) => {
|
|
929
1194
|
try {
|
|
@@ -934,34 +1199,43 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
934
1199
|
});
|
|
935
1200
|
});
|
|
936
1201
|
wss.on("error", (err) => {
|
|
937
|
-
console.error(
|
|
1202
|
+
console.error(pc3.red(`WebSocket server error: ${err.message}`));
|
|
938
1203
|
});
|
|
939
|
-
console.log(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1204
|
+
console.log(pc3.white(` Hot reload server: ${pc3.bold(`ws://localhost:${port}`)}`));
|
|
1205
|
+
if (lanIP) {
|
|
1206
|
+
console.log(pc3.white(` LAN address: ${pc3.bold(`ws://${lanIP}:${port}`)}`));
|
|
1207
|
+
}
|
|
1208
|
+
const iosDir = join3(cwd, "ios");
|
|
1209
|
+
const androidDir = join3(cwd, "android");
|
|
1210
|
+
if (existsSync3(iosDir)) {
|
|
1211
|
+
console.log(pc3.dim(` iOS Simulator: ws://localhost:${port}`));
|
|
1212
|
+
if (lanIP) {
|
|
1213
|
+
console.log(pc3.dim(` iOS Device (WiFi): ws://${lanIP}:${port}`));
|
|
1214
|
+
}
|
|
944
1215
|
}
|
|
945
|
-
if (
|
|
946
|
-
console.log(
|
|
1216
|
+
if (existsSync3(androidDir)) {
|
|
1217
|
+
console.log(pc3.dim(` Android emulator: ws://10.0.2.2:${port}`));
|
|
1218
|
+
if (lanIP) {
|
|
1219
|
+
console.log(pc3.dim(` Android Device: ws://${lanIP}:${port}`));
|
|
1220
|
+
}
|
|
947
1221
|
}
|
|
948
|
-
console.log(
|
|
949
|
-
console.log(
|
|
950
|
-
const vite =
|
|
1222
|
+
console.log(pc3.dim(" Waiting for app to connect...\n"));
|
|
1223
|
+
console.log(pc3.white(" Starting Vite build watcher...\n"));
|
|
1224
|
+
const vite = spawn2(
|
|
951
1225
|
"bun",
|
|
952
1226
|
["run", "vite", "build", "--watch", "--mode", "development"],
|
|
953
1227
|
{ cwd, stdio: "pipe" }
|
|
954
1228
|
);
|
|
955
1229
|
vite.stdout?.on("data", (data) => {
|
|
956
1230
|
const text = data.toString().trim();
|
|
957
|
-
if (text) console.log(
|
|
1231
|
+
if (text) console.log(pc3.dim(` [vite] ${text}`));
|
|
958
1232
|
});
|
|
959
1233
|
vite.stderr?.on("data", (data) => {
|
|
960
1234
|
const text = data.toString().trim();
|
|
961
|
-
if (text) console.log(
|
|
1235
|
+
if (text) console.log(pc3.yellow(` [vite] ${text}`));
|
|
962
1236
|
});
|
|
963
1237
|
vite.on("error", (err) => {
|
|
964
|
-
console.error(
|
|
1238
|
+
console.error(pc3.red(`Vite error: ${err.message}`));
|
|
965
1239
|
});
|
|
966
1240
|
const watcher = watch(bundlePath, {
|
|
967
1241
|
persistent: true,
|
|
@@ -981,7 +1255,7 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
981
1255
|
sent++;
|
|
982
1256
|
}
|
|
983
1257
|
}
|
|
984
|
-
console.log(
|
|
1258
|
+
console.log(pc3.green(` Bundle updated (${Math.round(bundle.length / 1024)}KB) -> sent to ${sent} client(s)`));
|
|
985
1259
|
} catch {
|
|
986
1260
|
}
|
|
987
1261
|
}
|
|
@@ -993,7 +1267,7 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
993
1267
|
}
|
|
994
1268
|
}, 3e4);
|
|
995
1269
|
process.on("SIGINT", () => {
|
|
996
|
-
console.log(
|
|
1270
|
+
console.log(pc3.yellow("\n Shutting down dev server..."));
|
|
997
1271
|
vite.kill();
|
|
998
1272
|
wss.close();
|
|
999
1273
|
process.exit(0);
|
|
@@ -1001,30 +1275,30 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
1001
1275
|
});
|
|
1002
1276
|
|
|
1003
1277
|
// src/commands/run.ts
|
|
1004
|
-
import { Command as
|
|
1005
|
-
import { spawn as
|
|
1006
|
-
import { existsSync as
|
|
1007
|
-
import { join as
|
|
1008
|
-
import
|
|
1278
|
+
import { Command as Command4 } from "commander";
|
|
1279
|
+
import { spawn as spawn3, execSync as execSync3 } from "child_process";
|
|
1280
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync } from "fs";
|
|
1281
|
+
import { join as join4 } from "path";
|
|
1282
|
+
import pc4 from "picocolors";
|
|
1009
1283
|
function findAppPath(_buildDir) {
|
|
1010
|
-
const derivedDataBase =
|
|
1284
|
+
const derivedDataBase = join4(
|
|
1011
1285
|
process.env.HOME || "~",
|
|
1012
1286
|
"Library/Developer/Xcode/DerivedData"
|
|
1013
1287
|
);
|
|
1014
|
-
if (
|
|
1288
|
+
if (existsSync4(derivedDataBase)) {
|
|
1015
1289
|
try {
|
|
1016
|
-
const projects =
|
|
1290
|
+
const projects = readdirSync2(derivedDataBase);
|
|
1017
1291
|
for (const project of projects.reverse()) {
|
|
1018
|
-
const productsDir =
|
|
1292
|
+
const productsDir = join4(
|
|
1019
1293
|
derivedDataBase,
|
|
1020
1294
|
project,
|
|
1021
1295
|
"Build/Products/Debug-iphonesimulator"
|
|
1022
1296
|
);
|
|
1023
|
-
if (
|
|
1024
|
-
const entries =
|
|
1297
|
+
if (existsSync4(productsDir)) {
|
|
1298
|
+
const entries = readdirSync2(productsDir);
|
|
1025
1299
|
const app = entries.find((e) => e.endsWith(".app"));
|
|
1026
1300
|
if (app) {
|
|
1027
|
-
return
|
|
1301
|
+
return join4(productsDir, app);
|
|
1028
1302
|
}
|
|
1029
1303
|
}
|
|
1030
1304
|
}
|
|
@@ -1034,8 +1308,8 @@ function findAppPath(_buildDir) {
|
|
|
1034
1308
|
return null;
|
|
1035
1309
|
}
|
|
1036
1310
|
function readBundleId(iosDir) {
|
|
1037
|
-
const plistPath =
|
|
1038
|
-
if (
|
|
1311
|
+
const plistPath = join4(iosDir, "Sources", "Info.plist");
|
|
1312
|
+
if (existsSync4(plistPath)) {
|
|
1039
1313
|
try {
|
|
1040
1314
|
const content = readFileSync(plistPath, "utf8");
|
|
1041
1315
|
const match = content.match(
|
|
@@ -1050,34 +1324,34 @@ function readBundleId(iosDir) {
|
|
|
1050
1324
|
return "com.vuenative.app";
|
|
1051
1325
|
}
|
|
1052
1326
|
function findApkPath(androidDir) {
|
|
1053
|
-
const apkDir =
|
|
1054
|
-
if (
|
|
1327
|
+
const apkDir = join4(androidDir, "app", "build", "outputs", "apk", "debug");
|
|
1328
|
+
if (existsSync4(apkDir)) {
|
|
1055
1329
|
try {
|
|
1056
|
-
const entries =
|
|
1330
|
+
const entries = readdirSync2(apkDir);
|
|
1057
1331
|
const apk = entries.find((e) => e.endsWith(".apk") && !e.includes("androidTest"));
|
|
1058
1332
|
if (apk) {
|
|
1059
|
-
return
|
|
1333
|
+
return join4(apkDir, apk);
|
|
1060
1334
|
}
|
|
1061
1335
|
} catch {
|
|
1062
1336
|
}
|
|
1063
1337
|
}
|
|
1064
1338
|
return null;
|
|
1065
1339
|
}
|
|
1066
|
-
var runCommand = new
|
|
1340
|
+
var runCommand = new Command4("run").description("Build and run the app").argument("<platform>", "platform to run on (ios, android)").option("--device", "run on physical device instead of simulator").option("--scheme <scheme>", "Xcode scheme to build").option("--simulator <name>", "simulator name", "iPhone 16").option("--bundle-id <id>", "app bundle identifier").option("--package <name>", "Android package name", "com.vuenative.app").option("--activity <name>", "Android activity name", ".MainActivity").action(async (platform, options) => {
|
|
1067
1341
|
if (platform !== "ios" && platform !== "android") {
|
|
1068
|
-
console.error(
|
|
1342
|
+
console.error(pc4.red('Platform must be "ios" or "android"'));
|
|
1069
1343
|
process.exit(1);
|
|
1070
1344
|
}
|
|
1071
1345
|
const cwd = process.cwd();
|
|
1072
|
-
console.log(
|
|
1346
|
+
console.log(pc4.cyan(`
|
|
1073
1347
|
\u{1F4F1} Vue Native \u2014 Run ${platform === "ios" ? "iOS" : "Android"}
|
|
1074
1348
|
`));
|
|
1075
|
-
console.log(
|
|
1349
|
+
console.log(pc4.white(" Building JS bundle..."));
|
|
1076
1350
|
try {
|
|
1077
|
-
|
|
1078
|
-
console.log(
|
|
1351
|
+
execSync3("bun run vite build", { cwd, stdio: "inherit" });
|
|
1352
|
+
console.log(pc4.green(" \u2713 Bundle built\n"));
|
|
1079
1353
|
} catch {
|
|
1080
|
-
console.error(
|
|
1354
|
+
console.error(pc4.red(" \u2717 Bundle build failed"));
|
|
1081
1355
|
process.exit(1);
|
|
1082
1356
|
}
|
|
1083
1357
|
if (platform === "ios") {
|
|
@@ -1088,14 +1362,14 @@ var runCommand = new Command3("run").description("Build and run the app").argume
|
|
|
1088
1362
|
});
|
|
1089
1363
|
function runIOS(cwd, options) {
|
|
1090
1364
|
let xcodeProject = null;
|
|
1091
|
-
const iosDir =
|
|
1092
|
-
if (
|
|
1365
|
+
const iosDir = join4(cwd, "ios");
|
|
1366
|
+
if (existsSync4(iosDir)) {
|
|
1093
1367
|
for (const ext of [".xcworkspace", ".xcodeproj"]) {
|
|
1094
1368
|
try {
|
|
1095
|
-
const entries =
|
|
1369
|
+
const entries = readdirSync2(iosDir);
|
|
1096
1370
|
const match = entries.find((e) => e.endsWith(ext));
|
|
1097
1371
|
if (match) {
|
|
1098
|
-
xcodeProject =
|
|
1372
|
+
xcodeProject = join4(iosDir, match);
|
|
1099
1373
|
break;
|
|
1100
1374
|
}
|
|
1101
1375
|
} catch {
|
|
@@ -1103,17 +1377,17 @@ function runIOS(cwd, options) {
|
|
|
1103
1377
|
}
|
|
1104
1378
|
}
|
|
1105
1379
|
if (!xcodeProject) {
|
|
1106
|
-
console.log(
|
|
1107
|
-
console.log(
|
|
1108
|
-
console.log(
|
|
1380
|
+
console.log(pc4.yellow(" No Xcode project found in ./ios/"));
|
|
1381
|
+
console.log(pc4.dim(" To add iOS support, create an Xcode project in the ios/ directory."));
|
|
1382
|
+
console.log(pc4.dim(" Bundle has been built to dist/vue-native-bundle.js\n"));
|
|
1109
1383
|
return;
|
|
1110
1384
|
}
|
|
1111
1385
|
const isWorkspace = xcodeProject.endsWith(".xcworkspace");
|
|
1112
1386
|
const scheme = options.scheme || xcodeProject.split("/").pop()?.replace(/\.(xcworkspace|xcodeproj)$/, "") || "App";
|
|
1113
1387
|
const destination = options.device ? "generic/platform=iOS" : `platform=iOS Simulator,name=${options.simulator}`;
|
|
1114
1388
|
const projectFlag = isWorkspace ? "-workspace" : "-project";
|
|
1115
|
-
console.log(
|
|
1116
|
-
const xcodebuild =
|
|
1389
|
+
console.log(pc4.white(` Building ${scheme} for ${options.device ? "device" : options.simulator}...`));
|
|
1390
|
+
const xcodebuild = spawn3(
|
|
1117
1391
|
"xcodebuild",
|
|
1118
1392
|
[projectFlag, xcodeProject, "-scheme", scheme, "-destination", destination, "build"],
|
|
1119
1393
|
{
|
|
@@ -1125,71 +1399,71 @@ function runIOS(cwd, options) {
|
|
|
1125
1399
|
xcodebuild.stderr?.on("data", (data) => {
|
|
1126
1400
|
const text = data.toString().trim();
|
|
1127
1401
|
if (text.includes("error:") || text.includes("warning:")) {
|
|
1128
|
-
console.log(
|
|
1402
|
+
console.log(pc4.dim(` ${text}`));
|
|
1129
1403
|
}
|
|
1130
1404
|
});
|
|
1131
1405
|
xcodebuild.on("close", (code) => {
|
|
1132
1406
|
if (code !== 0) {
|
|
1133
|
-
console.error(
|
|
1407
|
+
console.error(pc4.red(` \u2717 Build failed (exit code ${code})`));
|
|
1134
1408
|
process.exit(1);
|
|
1135
1409
|
}
|
|
1136
|
-
console.log(
|
|
1410
|
+
console.log(pc4.green(" \u2713 Build successful\n"));
|
|
1137
1411
|
if (options.device) {
|
|
1138
|
-
console.log(
|
|
1412
|
+
console.log(pc4.green(" App built for device. Install via Xcode.\n"));
|
|
1139
1413
|
return;
|
|
1140
1414
|
}
|
|
1141
1415
|
const simulatorName = options.simulator;
|
|
1142
|
-
const bundleId = options.bundleId || readBundleId(
|
|
1143
|
-
console.log(
|
|
1416
|
+
const bundleId = options.bundleId || readBundleId(join4(cwd, "ios"));
|
|
1417
|
+
console.log(pc4.white(` Booting simulator "${simulatorName}"...`));
|
|
1144
1418
|
try {
|
|
1145
|
-
|
|
1419
|
+
execSync3(`xcrun simctl boot "${simulatorName}"`, { stdio: "pipe" });
|
|
1146
1420
|
} catch {
|
|
1147
1421
|
}
|
|
1148
1422
|
try {
|
|
1149
|
-
|
|
1423
|
+
execSync3("open -a Simulator", { stdio: "pipe" });
|
|
1150
1424
|
} catch {
|
|
1151
1425
|
}
|
|
1152
|
-
const appPath = findAppPath(
|
|
1426
|
+
const appPath = findAppPath(join4(cwd, "ios"));
|
|
1153
1427
|
if (appPath) {
|
|
1154
|
-
console.log(
|
|
1428
|
+
console.log(pc4.white(` Installing app on simulator...`));
|
|
1155
1429
|
try {
|
|
1156
|
-
|
|
1157
|
-
console.log(
|
|
1430
|
+
execSync3(`xcrun simctl install booted "${appPath}"`, { stdio: "pipe" });
|
|
1431
|
+
console.log(pc4.green(" \u2713 App installed"));
|
|
1158
1432
|
} catch (err) {
|
|
1159
|
-
console.error(
|
|
1433
|
+
console.error(pc4.red(` \u2717 Failed to install app: ${err.message}`));
|
|
1160
1434
|
process.exit(1);
|
|
1161
1435
|
}
|
|
1162
|
-
console.log(
|
|
1436
|
+
console.log(pc4.white(` Launching ${bundleId}...`));
|
|
1163
1437
|
try {
|
|
1164
|
-
|
|
1165
|
-
console.log(
|
|
1438
|
+
execSync3(`xcrun simctl launch booted "${bundleId}"`, { stdio: "pipe" });
|
|
1439
|
+
console.log(pc4.green(` \u2713 App launched on ${simulatorName}
|
|
1166
1440
|
`));
|
|
1167
1441
|
} catch (err) {
|
|
1168
|
-
console.error(
|
|
1442
|
+
console.error(pc4.red(` \u2717 Failed to launch app: ${err.message}`));
|
|
1169
1443
|
process.exit(1);
|
|
1170
1444
|
}
|
|
1171
1445
|
} else {
|
|
1172
|
-
console.log(
|
|
1173
|
-
console.log(
|
|
1446
|
+
console.log(pc4.yellow(" Could not locate .app bundle in DerivedData."));
|
|
1447
|
+
console.log(pc4.dim(" Try running the app from Xcode directly.\n"));
|
|
1174
1448
|
}
|
|
1175
1449
|
});
|
|
1176
1450
|
}
|
|
1177
1451
|
function runAndroid(cwd, options) {
|
|
1178
|
-
const androidDir =
|
|
1179
|
-
if (!
|
|
1180
|
-
console.log(
|
|
1181
|
-
console.log(
|
|
1182
|
-
console.log(
|
|
1452
|
+
const androidDir = join4(cwd, "android");
|
|
1453
|
+
if (!existsSync4(androidDir)) {
|
|
1454
|
+
console.log(pc4.yellow(" No android/ directory found."));
|
|
1455
|
+
console.log(pc4.dim(" To add Android support, create an Android project in the android/ directory."));
|
|
1456
|
+
console.log(pc4.dim(" Bundle has been built to dist/vue-native-bundle.js\n"));
|
|
1183
1457
|
return;
|
|
1184
1458
|
}
|
|
1185
|
-
const gradlew =
|
|
1186
|
-
if (!
|
|
1187
|
-
console.error(
|
|
1188
|
-
console.log(
|
|
1459
|
+
const gradlew = join4(androidDir, "gradlew");
|
|
1460
|
+
if (!existsSync4(gradlew)) {
|
|
1461
|
+
console.error(pc4.red(" \u2717 gradlew not found in android/ directory"));
|
|
1462
|
+
console.log(pc4.dim(" Make sure your Android project has the Gradle wrapper.\n"));
|
|
1189
1463
|
process.exit(1);
|
|
1190
1464
|
}
|
|
1191
|
-
console.log(
|
|
1192
|
-
const gradle =
|
|
1465
|
+
console.log(pc4.white(" Building Android app with Gradle..."));
|
|
1466
|
+
const gradle = spawn3(
|
|
1193
1467
|
"./gradlew",
|
|
1194
1468
|
["assembleDebug"],
|
|
1195
1469
|
{
|
|
@@ -1209,48 +1483,48 @@ function runAndroid(cwd, options) {
|
|
|
1209
1483
|
gradle.stdout?.on("data", (data) => {
|
|
1210
1484
|
const text = data.toString().trim();
|
|
1211
1485
|
if (text) {
|
|
1212
|
-
console.log(
|
|
1486
|
+
console.log(pc4.dim(` ${text}`));
|
|
1213
1487
|
}
|
|
1214
1488
|
});
|
|
1215
1489
|
gradle.stderr?.on("data", (data) => {
|
|
1216
1490
|
const text = data.toString().trim();
|
|
1217
1491
|
if (text.includes("ERROR") || text.includes("FAILURE")) {
|
|
1218
|
-
console.log(
|
|
1492
|
+
console.log(pc4.red(` ${text}`));
|
|
1219
1493
|
}
|
|
1220
1494
|
});
|
|
1221
1495
|
gradle.on("error", (err) => {
|
|
1222
|
-
console.error(
|
|
1496
|
+
console.error(pc4.red(` Gradle process error: ${err.message}`));
|
|
1223
1497
|
cleanupGradle();
|
|
1224
1498
|
});
|
|
1225
1499
|
gradle.on("close", (code) => {
|
|
1226
1500
|
if (code !== 0) {
|
|
1227
|
-
console.error(
|
|
1501
|
+
console.error(pc4.red(` \u2717 Gradle build failed (exit code ${code})`));
|
|
1228
1502
|
process.exit(1);
|
|
1229
1503
|
}
|
|
1230
|
-
console.log(
|
|
1504
|
+
console.log(pc4.green(" \u2713 Build successful\n"));
|
|
1231
1505
|
const apkPath = findApkPath(androidDir);
|
|
1232
1506
|
if (!apkPath) {
|
|
1233
|
-
console.log(
|
|
1234
|
-
console.log(
|
|
1507
|
+
console.log(pc4.yellow(" Could not locate debug APK."));
|
|
1508
|
+
console.log(pc4.dim(" Expected at android/app/build/outputs/apk/debug/\n"));
|
|
1235
1509
|
return;
|
|
1236
1510
|
}
|
|
1237
|
-
console.log(
|
|
1511
|
+
console.log(pc4.white(" Installing APK on device/emulator..."));
|
|
1238
1512
|
try {
|
|
1239
|
-
|
|
1240
|
-
console.log(
|
|
1513
|
+
execSync3(`adb install -r "${apkPath}"`, { stdio: "pipe" });
|
|
1514
|
+
console.log(pc4.green(" \u2713 APK installed"));
|
|
1241
1515
|
} catch (err) {
|
|
1242
|
-
console.error(
|
|
1243
|
-
console.log(
|
|
1516
|
+
console.error(pc4.red(` \u2717 Failed to install APK: ${err.message}`));
|
|
1517
|
+
console.log(pc4.dim(" Make sure an emulator is running or a device is connected (adb devices).\n"));
|
|
1244
1518
|
process.exit(1);
|
|
1245
1519
|
}
|
|
1246
1520
|
const componentName = `${options.package}/${options.activity}`;
|
|
1247
|
-
console.log(
|
|
1521
|
+
console.log(pc4.white(` Launching ${componentName}...`));
|
|
1248
1522
|
try {
|
|
1249
|
-
|
|
1250
|
-
console.log(
|
|
1523
|
+
execSync3(`adb shell am start -n "${componentName}"`, { stdio: "pipe" });
|
|
1524
|
+
console.log(pc4.green(` \u2713 App launched
|
|
1251
1525
|
`));
|
|
1252
1526
|
} catch (err) {
|
|
1253
|
-
console.error(
|
|
1527
|
+
console.error(pc4.red(` \u2717 Failed to launch app: ${err.message}`));
|
|
1254
1528
|
process.exit(1);
|
|
1255
1529
|
}
|
|
1256
1530
|
});
|
|
@@ -1258,6 +1532,7 @@ function runAndroid(cwd, options) {
|
|
|
1258
1532
|
|
|
1259
1533
|
// src/cli.ts
|
|
1260
1534
|
program.name("vue-native").description("Vue Native \u2014 build native iOS and Android apps with Vue.js").version("0.1.0");
|
|
1535
|
+
program.addCommand(buildCommand);
|
|
1261
1536
|
program.addCommand(createCommand);
|
|
1262
1537
|
program.addCommand(devCommand);
|
|
1263
1538
|
program.addCommand(runCommand);
|