@thelacanians/vue-native-cli 0.4.13 → 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 +472 -192
- package/native/android/VueNativeCore/build.gradle.kts +1 -1
- 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/JSPolyfills.swift +15 -2
- 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
|
|
13
|
-
var VERSION = "0.4.
|
|
14
|
-
var createCommand = new
|
|
253
|
+
import { existsSync as existsSync2 } from "fs";
|
|
254
|
+
import pc2 from "picocolors";
|
|
255
|
+
var VERSION = "0.4.14";
|
|
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>
|
|
@@ -156,6 +398,11 @@ targets:
|
|
|
156
398
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
157
399
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
158
400
|
</array>
|
|
401
|
+
<key>NSAppTransportSecurity</key>
|
|
402
|
+
<dict>
|
|
403
|
+
<key>NSAllowsLocalNetworking</key>
|
|
404
|
+
<true/>
|
|
405
|
+
</dict>
|
|
159
406
|
<!-- Uncomment the privacy descriptions for features your app uses -->
|
|
160
407
|
<!-- <key>NSCameraUsageDescription</key><string>This app needs camera access</string> -->
|
|
161
408
|
<!-- <key>NSMicrophoneUsageDescription</key><string>This app needs microphone access</string> -->
|
|
@@ -168,7 +415,7 @@ targets:
|
|
|
168
415
|
</dict>
|
|
169
416
|
</plist>
|
|
170
417
|
`);
|
|
171
|
-
await writeFile(
|
|
418
|
+
await writeFile(join2(iosSrcDir, "AppDelegate.swift"), `import UIKit
|
|
172
419
|
|
|
173
420
|
@main
|
|
174
421
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
@@ -192,7 +439,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
192
439
|
}
|
|
193
440
|
}
|
|
194
441
|
`);
|
|
195
|
-
await writeFile(
|
|
442
|
+
await writeFile(join2(iosSrcDir, "SceneDelegate.swift"), `import UIKit
|
|
196
443
|
import VueNativeCore
|
|
197
444
|
|
|
198
445
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -220,29 +467,29 @@ class AppViewController: VueNativeViewController {
|
|
|
220
467
|
#endif
|
|
221
468
|
}
|
|
222
469
|
`);
|
|
223
|
-
const androidDir =
|
|
224
|
-
const androidAppDir =
|
|
470
|
+
const androidDir = join2(dir, "android");
|
|
471
|
+
const androidAppDir = join2(androidDir, "app");
|
|
225
472
|
const androidPkg = `com.vuenative.${name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()}`;
|
|
226
473
|
const androidPkgPath = androidPkg.replace(/\./g, "/");
|
|
227
|
-
const androidSrcDir =
|
|
228
|
-
const androidKotlinDir =
|
|
229
|
-
const androidResValuesDir =
|
|
230
|
-
const androidResXmlDir =
|
|
231
|
-
const androidDebugResXmlDir =
|
|
232
|
-
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");
|
|
233
480
|
await mkdir(androidKotlinDir, { recursive: true });
|
|
234
481
|
await mkdir(androidResValuesDir, { recursive: true });
|
|
235
482
|
await mkdir(androidResXmlDir, { recursive: true });
|
|
236
483
|
await mkdir(androidDebugResXmlDir, { recursive: true });
|
|
237
484
|
await mkdir(androidGradleWrapperDir, { recursive: true });
|
|
238
|
-
await writeFile(
|
|
485
|
+
await writeFile(join2(androidDir, "build.gradle.kts"), `// Top-level build file
|
|
239
486
|
plugins {
|
|
240
487
|
id("com.android.application") version "8.7.3" apply false
|
|
241
488
|
id("com.android.library") version "8.7.3" apply false
|
|
242
489
|
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
|
|
243
490
|
}
|
|
244
491
|
`);
|
|
245
|
-
await writeFile(
|
|
492
|
+
await writeFile(join2(androidDir, "settings.gradle.kts"), `pluginManagement {
|
|
246
493
|
repositories {
|
|
247
494
|
google()
|
|
248
495
|
mavenCentral()
|
|
@@ -268,7 +515,7 @@ dependencyResolutionManagement {
|
|
|
268
515
|
rootProject.name = "${name}"
|
|
269
516
|
include(":app")
|
|
270
517
|
`);
|
|
271
|
-
await writeFile(
|
|
518
|
+
await writeFile(join2(androidAppDir, "build.gradle.kts"), `plugins {
|
|
272
519
|
id("com.android.application")
|
|
273
520
|
id("org.jetbrains.kotlin.android")
|
|
274
521
|
}
|
|
@@ -316,13 +563,13 @@ dependencies {
|
|
|
316
563
|
implementation("androidx.core:core-ktx:1.15.0")
|
|
317
564
|
}
|
|
318
565
|
`);
|
|
319
|
-
await writeFile(
|
|
566
|
+
await writeFile(join2(androidAppDir, "proguard-rules.pro"), `# Vue Native
|
|
320
567
|
-keep class com.vuenative.** { *; }
|
|
321
568
|
|
|
322
569
|
# J2V8
|
|
323
570
|
-keep class com.eclipsesource.v8.** { *; }
|
|
324
571
|
`);
|
|
325
|
-
await writeFile(
|
|
572
|
+
await writeFile(join2(androidSrcDir, "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
326
573
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
327
574
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
328
575
|
|
|
@@ -345,12 +592,12 @@ dependencies {
|
|
|
345
592
|
</application>
|
|
346
593
|
</manifest>
|
|
347
594
|
`);
|
|
348
|
-
await writeFile(
|
|
595
|
+
await writeFile(join2(androidResValuesDir, "strings.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
349
596
|
<resources>
|
|
350
597
|
<string name="app_name">${name}</string>
|
|
351
598
|
</resources>
|
|
352
599
|
`);
|
|
353
|
-
await writeFile(
|
|
600
|
+
await writeFile(join2(androidResValuesDir, "themes.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
354
601
|
<resources>
|
|
355
602
|
<style name="Theme.VueNative" parent="Theme.MaterialComponents.Light.NoActionBar">
|
|
356
603
|
<item name="colorPrimary">#4F46E5</item>
|
|
@@ -363,12 +610,12 @@ dependencies {
|
|
|
363
610
|
</style>
|
|
364
611
|
</resources>
|
|
365
612
|
`);
|
|
366
|
-
await writeFile(
|
|
613
|
+
await writeFile(join2(androidResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
367
614
|
<network-security-config>
|
|
368
615
|
<base-config cleartextTrafficPermitted="false" />
|
|
369
616
|
</network-security-config>
|
|
370
617
|
`);
|
|
371
|
-
await writeFile(
|
|
618
|
+
await writeFile(join2(androidDebugResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
372
619
|
<network-security-config>
|
|
373
620
|
<domain-config cleartextTrafficPermitted="true">
|
|
374
621
|
<domain includeSubdomains="true">localhost</domain>
|
|
@@ -377,7 +624,7 @@ dependencies {
|
|
|
377
624
|
</domain-config>
|
|
378
625
|
</network-security-config>
|
|
379
626
|
`);
|
|
380
|
-
await writeFile(
|
|
627
|
+
await writeFile(join2(androidKotlinDir, "MainActivity.kt"), `package ${androidPkg}
|
|
381
628
|
|
|
382
629
|
import com.vuenative.core.VueNativeActivity
|
|
383
630
|
|
|
@@ -391,20 +638,20 @@ class MainActivity : VueNativeActivity() {
|
|
|
391
638
|
}
|
|
392
639
|
}
|
|
393
640
|
`);
|
|
394
|
-
await writeFile(
|
|
641
|
+
await writeFile(join2(androidDir, "gradle.properties"), `# Project-wide Gradle settings
|
|
395
642
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
396
643
|
android.useAndroidX=true
|
|
397
644
|
kotlin.code.style=official
|
|
398
645
|
android.nonTransitiveRClass=true
|
|
399
646
|
`);
|
|
400
|
-
await writeFile(
|
|
647
|
+
await writeFile(join2(androidGradleWrapperDir, "gradle-wrapper.properties"), `distributionBase=GRADLE_USER_HOME
|
|
401
648
|
distributionPath=wrapper/dists
|
|
402
649
|
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
403
650
|
networkTimeout=10000
|
|
404
651
|
zipStoreBase=GRADLE_USER_HOME
|
|
405
652
|
zipStorePath=wrapper/dists
|
|
406
653
|
`);
|
|
407
|
-
await writeFile(
|
|
654
|
+
await writeFile(join2(dir, "vue-native.config.ts"), `import { defineConfig } from '@thelacanians/vue-native-cli'
|
|
408
655
|
|
|
409
656
|
export default defineConfig({
|
|
410
657
|
name: '${name}',
|
|
@@ -419,7 +666,7 @@ export default defineConfig({
|
|
|
419
666
|
},
|
|
420
667
|
})
|
|
421
668
|
`);
|
|
422
|
-
await writeFile(
|
|
669
|
+
await writeFile(join2(dir, "env.d.ts"), `/// <reference types="vite/client" />
|
|
423
670
|
declare module '*.vue' {
|
|
424
671
|
import type { DefineComponent } from '@thelacanians/vue-native-runtime'
|
|
425
672
|
const component: DefineComponent<{}, {}, any>
|
|
@@ -427,7 +674,7 @@ declare module '*.vue' {
|
|
|
427
674
|
}
|
|
428
675
|
declare const __DEV__: boolean
|
|
429
676
|
`);
|
|
430
|
-
await writeFile(
|
|
677
|
+
await writeFile(join2(dir, ".gitignore"), `node_modules/
|
|
431
678
|
dist/
|
|
432
679
|
*.xcuserstate
|
|
433
680
|
*.xcuserdatad/
|
|
@@ -450,30 +697,30 @@ local.properties
|
|
|
450
697
|
*.jks
|
|
451
698
|
`);
|
|
452
699
|
const cliDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
453
|
-
const bundledNative =
|
|
454
|
-
if (
|
|
455
|
-
const nativeDir =
|
|
700
|
+
const bundledNative = join2(cliDir, "native");
|
|
701
|
+
if (existsSync2(bundledNative)) {
|
|
702
|
+
const nativeDir = join2(dir, "native");
|
|
456
703
|
await cp(bundledNative, nativeDir, { recursive: true });
|
|
457
|
-
console.log(
|
|
704
|
+
console.log(pc2.dim(" Bundled native/ copied as fallback.\n"));
|
|
458
705
|
}
|
|
459
|
-
console.log(
|
|
460
|
-
console.log(
|
|
461
|
-
console.log(
|
|
462
|
-
console.log(
|
|
463
|
-
console.log(
|
|
464
|
-
console.log(
|
|
465
|
-
console.log(
|
|
466
|
-
console.log(
|
|
467
|
-
console.log(
|
|
468
|
-
console.log(
|
|
469
|
-
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"));
|
|
470
717
|
} catch (err) {
|
|
471
|
-
console.error(
|
|
718
|
+
console.error(pc2.red(`Error creating project: ${err.message}`));
|
|
472
719
|
process.exit(1);
|
|
473
720
|
}
|
|
474
721
|
});
|
|
475
722
|
async function generateTemplateFiles(dir, name, template) {
|
|
476
|
-
const pagesDir =
|
|
723
|
+
const pagesDir = join2(dir, "app", "pages");
|
|
477
724
|
if (template === "blank") {
|
|
478
725
|
await generateBlankTemplate(dir, pagesDir);
|
|
479
726
|
} else if (template === "tabs") {
|
|
@@ -483,7 +730,7 @@ async function generateTemplateFiles(dir, name, template) {
|
|
|
483
730
|
}
|
|
484
731
|
}
|
|
485
732
|
async function generateBlankTemplate(dir, pagesDir) {
|
|
486
|
-
await writeFile(
|
|
733
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
487
734
|
import { createRouter } from '@thelacanians/vue-native-navigation'
|
|
488
735
|
import App from './App.vue'
|
|
489
736
|
import Home from './pages/Home.vue'
|
|
@@ -496,7 +743,7 @@ const app = createApp(App)
|
|
|
496
743
|
app.use(router)
|
|
497
744
|
app.start()
|
|
498
745
|
`);
|
|
499
|
-
await writeFile(
|
|
746
|
+
await writeFile(join2(dir, "app", "App.vue"), `<template>
|
|
500
747
|
<VSafeArea :style="{ flex: 1, backgroundColor: '#ffffff' }">
|
|
501
748
|
<RouterView />
|
|
502
749
|
</VSafeArea>
|
|
@@ -506,7 +753,7 @@ app.start()
|
|
|
506
753
|
import { RouterView } from '@thelacanians/vue-native-navigation'
|
|
507
754
|
</script>
|
|
508
755
|
`);
|
|
509
|
-
await writeFile(
|
|
756
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
510
757
|
import { ref } from 'vue'
|
|
511
758
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
512
759
|
|
|
@@ -558,13 +805,13 @@ const styles = createStyleSheet({
|
|
|
558
805
|
`);
|
|
559
806
|
}
|
|
560
807
|
async function generateTabsTemplate(dir, pagesDir) {
|
|
561
|
-
await writeFile(
|
|
808
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
562
809
|
import App from './App.vue'
|
|
563
810
|
|
|
564
811
|
const app = createApp(App)
|
|
565
812
|
app.start()
|
|
566
813
|
`);
|
|
567
|
-
await writeFile(
|
|
814
|
+
await writeFile(join2(dir, "app", "App.vue"), `<script setup lang="ts">
|
|
568
815
|
import { createTabNavigator } from '@thelacanians/vue-native-navigation'
|
|
569
816
|
import Home from './pages/Home.vue'
|
|
570
817
|
import Settings from './pages/Settings.vue'
|
|
@@ -583,7 +830,7 @@ const { TabNavigator } = createTabNavigator()
|
|
|
583
830
|
</VSafeArea>
|
|
584
831
|
</template>
|
|
585
832
|
`);
|
|
586
|
-
await writeFile(
|
|
833
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
587
834
|
import { ref } from 'vue'
|
|
588
835
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
589
836
|
|
|
@@ -627,7 +874,7 @@ const styles = createStyleSheet({
|
|
|
627
874
|
</VView>
|
|
628
875
|
</template>
|
|
629
876
|
`);
|
|
630
|
-
await writeFile(
|
|
877
|
+
await writeFile(join2(pagesDir, "Settings.vue"), `<script setup lang="ts">
|
|
631
878
|
import { ref } from 'vue'
|
|
632
879
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
633
880
|
|
|
@@ -671,13 +918,13 @@ const styles = createStyleSheet({
|
|
|
671
918
|
`);
|
|
672
919
|
}
|
|
673
920
|
async function generateDrawerTemplate(dir, pagesDir) {
|
|
674
|
-
await writeFile(
|
|
921
|
+
await writeFile(join2(dir, "app", "main.ts"), `import { createApp } from '@thelacanians/vue-native-runtime'
|
|
675
922
|
import App from './App.vue'
|
|
676
923
|
|
|
677
924
|
const app = createApp(App)
|
|
678
925
|
app.start()
|
|
679
926
|
`);
|
|
680
|
-
await writeFile(
|
|
927
|
+
await writeFile(join2(dir, "app", "App.vue"), `<script setup lang="ts">
|
|
681
928
|
import { createDrawerNavigator } from '@thelacanians/vue-native-navigation'
|
|
682
929
|
import Home from './pages/Home.vue'
|
|
683
930
|
import About from './pages/About.vue'
|
|
@@ -696,7 +943,7 @@ const { DrawerNavigator } = createDrawerNavigator()
|
|
|
696
943
|
</VSafeArea>
|
|
697
944
|
</template>
|
|
698
945
|
`);
|
|
699
|
-
await writeFile(
|
|
946
|
+
await writeFile(join2(pagesDir, "Home.vue"), `<script setup lang="ts">
|
|
700
947
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
701
948
|
import { useDrawer } from '@thelacanians/vue-native-navigation'
|
|
702
949
|
|
|
@@ -749,7 +996,7 @@ const styles = createStyleSheet({
|
|
|
749
996
|
</VView>
|
|
750
997
|
</template>
|
|
751
998
|
`);
|
|
752
|
-
await writeFile(
|
|
999
|
+
await writeFile(join2(pagesDir, "About.vue"), `<script setup lang="ts">
|
|
753
1000
|
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
|
|
754
1001
|
import { useDrawer } from '@thelacanians/vue-native-navigation'
|
|
755
1002
|
|
|
@@ -805,19 +1052,19 @@ const styles = createStyleSheet({
|
|
|
805
1052
|
}
|
|
806
1053
|
|
|
807
1054
|
// src/commands/dev.ts
|
|
808
|
-
import { Command as
|
|
809
|
-
import { spawn, execSync } from "child_process";
|
|
1055
|
+
import { Command as Command3 } from "commander";
|
|
1056
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
810
1057
|
import { readFile } from "fs/promises";
|
|
811
|
-
import { existsSync as
|
|
812
|
-
import { join as
|
|
1058
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1059
|
+
import { join as join3 } from "path";
|
|
813
1060
|
import { watch } from "chokidar";
|
|
814
1061
|
import { WebSocketServer, WebSocket } from "ws";
|
|
815
|
-
import
|
|
1062
|
+
import pc3 from "picocolors";
|
|
816
1063
|
var DEFAULT_PORT = 8174;
|
|
817
1064
|
var BUNDLE_FILE = "dist/vue-native-bundle.js";
|
|
818
1065
|
function detectIOSSimulators() {
|
|
819
1066
|
try {
|
|
820
|
-
const output =
|
|
1067
|
+
const output = execSync2("xcrun simctl list devices available -j", {
|
|
821
1068
|
stdio: "pipe",
|
|
822
1069
|
encoding: "utf8"
|
|
823
1070
|
});
|
|
@@ -842,33 +1089,33 @@ function detectIOSSimulators() {
|
|
|
842
1089
|
}
|
|
843
1090
|
function bootSimulator(udid) {
|
|
844
1091
|
try {
|
|
845
|
-
|
|
1092
|
+
execSync2(`xcrun simctl boot "${udid}"`, { stdio: "pipe" });
|
|
846
1093
|
} catch {
|
|
847
1094
|
}
|
|
848
1095
|
try {
|
|
849
|
-
|
|
1096
|
+
execSync2("open -a Simulator", { stdio: "pipe" });
|
|
850
1097
|
} catch {
|
|
851
1098
|
}
|
|
852
1099
|
}
|
|
853
1100
|
function detectAndroidEmulators() {
|
|
854
1101
|
try {
|
|
855
|
-
const output =
|
|
1102
|
+
const output = execSync2("adb devices", { stdio: "pipe", encoding: "utf8" });
|
|
856
1103
|
const lines = output.split("\n").filter((l) => l.includes("device") && !l.startsWith("List"));
|
|
857
1104
|
return lines.map((l) => l.split(" ")[0]).filter(Boolean);
|
|
858
1105
|
} catch {
|
|
859
1106
|
return [];
|
|
860
1107
|
}
|
|
861
1108
|
}
|
|
862
|
-
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) => {
|
|
863
1110
|
const port = parseInt(options.port, 10);
|
|
864
1111
|
const cwd = process.cwd();
|
|
865
|
-
const bundlePath =
|
|
866
|
-
console.log(
|
|
1112
|
+
const bundlePath = join3(cwd, BUNDLE_FILE);
|
|
1113
|
+
console.log(pc3.cyan("\n Vue Native Dev Server\n"));
|
|
867
1114
|
if (options.ios) {
|
|
868
|
-
console.log(
|
|
1115
|
+
console.log(pc3.white(" Detecting iOS Simulators..."));
|
|
869
1116
|
const simulators = detectIOSSimulators();
|
|
870
1117
|
if (simulators.length === 0) {
|
|
871
|
-
console.log(
|
|
1118
|
+
console.log(pc3.yellow(" No iOS Simulators found. Install Xcode and create a simulator."));
|
|
872
1119
|
} else {
|
|
873
1120
|
let target = simulators.find((s) => s.state === "Booted");
|
|
874
1121
|
if (!target && options.simulator) {
|
|
@@ -879,46 +1126,69 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
879
1126
|
}
|
|
880
1127
|
if (target) {
|
|
881
1128
|
if (target.state !== "Booted") {
|
|
882
|
-
console.log(
|
|
1129
|
+
console.log(pc3.white(` Booting ${target.name}...`));
|
|
883
1130
|
bootSimulator(target.udid);
|
|
884
1131
|
}
|
|
885
|
-
console.log(
|
|
1132
|
+
console.log(pc3.green(` iOS Simulator ready: ${target.name}`));
|
|
886
1133
|
}
|
|
887
1134
|
}
|
|
888
1135
|
console.log();
|
|
889
1136
|
}
|
|
890
1137
|
if (options.android) {
|
|
891
|
-
console.log(
|
|
1138
|
+
console.log(pc3.white(" Detecting Android emulators..."));
|
|
892
1139
|
const emulators = detectAndroidEmulators();
|
|
893
1140
|
if (emulators.length === 0) {
|
|
894
|
-
console.log(
|
|
1141
|
+
console.log(pc3.yellow(" No Android emulators detected. Start one via Android Studio or `emulator -avd <name>`."));
|
|
895
1142
|
} else {
|
|
896
|
-
console.log(
|
|
1143
|
+
console.log(pc3.green(` Android emulator(s) connected: ${emulators.join(", ")}`));
|
|
897
1144
|
}
|
|
898
1145
|
console.log();
|
|
899
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
|
+
};
|
|
900
1171
|
const wss = new WebSocketServer({
|
|
901
1172
|
port,
|
|
902
1173
|
verifyClient: (info) => {
|
|
903
|
-
|
|
904
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
1174
|
+
return isPrivateOrLocal(info.req.socket.remoteAddress);
|
|
905
1175
|
}
|
|
906
1176
|
});
|
|
907
1177
|
const clients = /* @__PURE__ */ new Set();
|
|
908
1178
|
wss.on("connection", (ws) => {
|
|
909
1179
|
clients.add(ws);
|
|
910
1180
|
ws.send(JSON.stringify({ type: "connected" }));
|
|
911
|
-
console.log(
|
|
1181
|
+
console.log(pc3.green(` Client connected (${clients.size} total)`));
|
|
912
1182
|
readFile(bundlePath, "utf8").then((bundle) => {
|
|
913
1183
|
if (ws.readyState === WebSocket.OPEN) {
|
|
914
1184
|
ws.send(JSON.stringify({ type: "bundle", bundle }));
|
|
915
|
-
console.log(
|
|
1185
|
+
console.log(pc3.dim(` Sent bundle to new client (${Math.round(bundle.length / 1024)}KB)`));
|
|
916
1186
|
}
|
|
917
1187
|
}).catch(() => {
|
|
918
1188
|
});
|
|
919
1189
|
ws.on("close", () => {
|
|
920
1190
|
clients.delete(ws);
|
|
921
|
-
console.log(
|
|
1191
|
+
console.log(pc3.dim(` Client disconnected (${clients.size} remaining)`));
|
|
922
1192
|
});
|
|
923
1193
|
ws.on("message", (data) => {
|
|
924
1194
|
try {
|
|
@@ -929,34 +1199,43 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
929
1199
|
});
|
|
930
1200
|
});
|
|
931
1201
|
wss.on("error", (err) => {
|
|
932
|
-
console.error(
|
|
1202
|
+
console.error(pc3.red(`WebSocket server error: ${err.message}`));
|
|
933
1203
|
});
|
|
934
|
-
console.log(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (existsSync2(iosDir)) {
|
|
938
|
-
console.log(pc2.dim(` iOS app should connect to ws://localhost:${port}`));
|
|
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}`)}`));
|
|
939
1207
|
}
|
|
940
|
-
|
|
941
|
-
|
|
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
|
+
}
|
|
942
1215
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
+
}
|
|
1221
|
+
}
|
|
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(
|
|
946
1225
|
"bun",
|
|
947
1226
|
["run", "vite", "build", "--watch", "--mode", "development"],
|
|
948
1227
|
{ cwd, stdio: "pipe" }
|
|
949
1228
|
);
|
|
950
1229
|
vite.stdout?.on("data", (data) => {
|
|
951
1230
|
const text = data.toString().trim();
|
|
952
|
-
if (text) console.log(
|
|
1231
|
+
if (text) console.log(pc3.dim(` [vite] ${text}`));
|
|
953
1232
|
});
|
|
954
1233
|
vite.stderr?.on("data", (data) => {
|
|
955
1234
|
const text = data.toString().trim();
|
|
956
|
-
if (text) console.log(
|
|
1235
|
+
if (text) console.log(pc3.yellow(` [vite] ${text}`));
|
|
957
1236
|
});
|
|
958
1237
|
vite.on("error", (err) => {
|
|
959
|
-
console.error(
|
|
1238
|
+
console.error(pc3.red(`Vite error: ${err.message}`));
|
|
960
1239
|
});
|
|
961
1240
|
const watcher = watch(bundlePath, {
|
|
962
1241
|
persistent: true,
|
|
@@ -976,7 +1255,7 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
976
1255
|
sent++;
|
|
977
1256
|
}
|
|
978
1257
|
}
|
|
979
|
-
console.log(
|
|
1258
|
+
console.log(pc3.green(` Bundle updated (${Math.round(bundle.length / 1024)}KB) -> sent to ${sent} client(s)`));
|
|
980
1259
|
} catch {
|
|
981
1260
|
}
|
|
982
1261
|
}
|
|
@@ -988,7 +1267,7 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
988
1267
|
}
|
|
989
1268
|
}, 3e4);
|
|
990
1269
|
process.on("SIGINT", () => {
|
|
991
|
-
console.log(
|
|
1270
|
+
console.log(pc3.yellow("\n Shutting down dev server..."));
|
|
992
1271
|
vite.kill();
|
|
993
1272
|
wss.close();
|
|
994
1273
|
process.exit(0);
|
|
@@ -996,30 +1275,30 @@ var devCommand = new Command2("dev").description("Start the Vue Native dev serve
|
|
|
996
1275
|
});
|
|
997
1276
|
|
|
998
1277
|
// src/commands/run.ts
|
|
999
|
-
import { Command as
|
|
1000
|
-
import { spawn as
|
|
1001
|
-
import { existsSync as
|
|
1002
|
-
import { join as
|
|
1003
|
-
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";
|
|
1004
1283
|
function findAppPath(_buildDir) {
|
|
1005
|
-
const derivedDataBase =
|
|
1284
|
+
const derivedDataBase = join4(
|
|
1006
1285
|
process.env.HOME || "~",
|
|
1007
1286
|
"Library/Developer/Xcode/DerivedData"
|
|
1008
1287
|
);
|
|
1009
|
-
if (
|
|
1288
|
+
if (existsSync4(derivedDataBase)) {
|
|
1010
1289
|
try {
|
|
1011
|
-
const projects =
|
|
1290
|
+
const projects = readdirSync2(derivedDataBase);
|
|
1012
1291
|
for (const project of projects.reverse()) {
|
|
1013
|
-
const productsDir =
|
|
1292
|
+
const productsDir = join4(
|
|
1014
1293
|
derivedDataBase,
|
|
1015
1294
|
project,
|
|
1016
1295
|
"Build/Products/Debug-iphonesimulator"
|
|
1017
1296
|
);
|
|
1018
|
-
if (
|
|
1019
|
-
const entries =
|
|
1297
|
+
if (existsSync4(productsDir)) {
|
|
1298
|
+
const entries = readdirSync2(productsDir);
|
|
1020
1299
|
const app = entries.find((e) => e.endsWith(".app"));
|
|
1021
1300
|
if (app) {
|
|
1022
|
-
return
|
|
1301
|
+
return join4(productsDir, app);
|
|
1023
1302
|
}
|
|
1024
1303
|
}
|
|
1025
1304
|
}
|
|
@@ -1029,8 +1308,8 @@ function findAppPath(_buildDir) {
|
|
|
1029
1308
|
return null;
|
|
1030
1309
|
}
|
|
1031
1310
|
function readBundleId(iosDir) {
|
|
1032
|
-
const plistPath =
|
|
1033
|
-
if (
|
|
1311
|
+
const plistPath = join4(iosDir, "Sources", "Info.plist");
|
|
1312
|
+
if (existsSync4(plistPath)) {
|
|
1034
1313
|
try {
|
|
1035
1314
|
const content = readFileSync(plistPath, "utf8");
|
|
1036
1315
|
const match = content.match(
|
|
@@ -1045,34 +1324,34 @@ function readBundleId(iosDir) {
|
|
|
1045
1324
|
return "com.vuenative.app";
|
|
1046
1325
|
}
|
|
1047
1326
|
function findApkPath(androidDir) {
|
|
1048
|
-
const apkDir =
|
|
1049
|
-
if (
|
|
1327
|
+
const apkDir = join4(androidDir, "app", "build", "outputs", "apk", "debug");
|
|
1328
|
+
if (existsSync4(apkDir)) {
|
|
1050
1329
|
try {
|
|
1051
|
-
const entries =
|
|
1330
|
+
const entries = readdirSync2(apkDir);
|
|
1052
1331
|
const apk = entries.find((e) => e.endsWith(".apk") && !e.includes("androidTest"));
|
|
1053
1332
|
if (apk) {
|
|
1054
|
-
return
|
|
1333
|
+
return join4(apkDir, apk);
|
|
1055
1334
|
}
|
|
1056
1335
|
} catch {
|
|
1057
1336
|
}
|
|
1058
1337
|
}
|
|
1059
1338
|
return null;
|
|
1060
1339
|
}
|
|
1061
|
-
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) => {
|
|
1062
1341
|
if (platform !== "ios" && platform !== "android") {
|
|
1063
|
-
console.error(
|
|
1342
|
+
console.error(pc4.red('Platform must be "ios" or "android"'));
|
|
1064
1343
|
process.exit(1);
|
|
1065
1344
|
}
|
|
1066
1345
|
const cwd = process.cwd();
|
|
1067
|
-
console.log(
|
|
1346
|
+
console.log(pc4.cyan(`
|
|
1068
1347
|
\u{1F4F1} Vue Native \u2014 Run ${platform === "ios" ? "iOS" : "Android"}
|
|
1069
1348
|
`));
|
|
1070
|
-
console.log(
|
|
1349
|
+
console.log(pc4.white(" Building JS bundle..."));
|
|
1071
1350
|
try {
|
|
1072
|
-
|
|
1073
|
-
console.log(
|
|
1351
|
+
execSync3("bun run vite build", { cwd, stdio: "inherit" });
|
|
1352
|
+
console.log(pc4.green(" \u2713 Bundle built\n"));
|
|
1074
1353
|
} catch {
|
|
1075
|
-
console.error(
|
|
1354
|
+
console.error(pc4.red(" \u2717 Bundle build failed"));
|
|
1076
1355
|
process.exit(1);
|
|
1077
1356
|
}
|
|
1078
1357
|
if (platform === "ios") {
|
|
@@ -1083,14 +1362,14 @@ var runCommand = new Command3("run").description("Build and run the app").argume
|
|
|
1083
1362
|
});
|
|
1084
1363
|
function runIOS(cwd, options) {
|
|
1085
1364
|
let xcodeProject = null;
|
|
1086
|
-
const iosDir =
|
|
1087
|
-
if (
|
|
1365
|
+
const iosDir = join4(cwd, "ios");
|
|
1366
|
+
if (existsSync4(iosDir)) {
|
|
1088
1367
|
for (const ext of [".xcworkspace", ".xcodeproj"]) {
|
|
1089
1368
|
try {
|
|
1090
|
-
const entries =
|
|
1369
|
+
const entries = readdirSync2(iosDir);
|
|
1091
1370
|
const match = entries.find((e) => e.endsWith(ext));
|
|
1092
1371
|
if (match) {
|
|
1093
|
-
xcodeProject =
|
|
1372
|
+
xcodeProject = join4(iosDir, match);
|
|
1094
1373
|
break;
|
|
1095
1374
|
}
|
|
1096
1375
|
} catch {
|
|
@@ -1098,17 +1377,17 @@ function runIOS(cwd, options) {
|
|
|
1098
1377
|
}
|
|
1099
1378
|
}
|
|
1100
1379
|
if (!xcodeProject) {
|
|
1101
|
-
console.log(
|
|
1102
|
-
console.log(
|
|
1103
|
-
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"));
|
|
1104
1383
|
return;
|
|
1105
1384
|
}
|
|
1106
1385
|
const isWorkspace = xcodeProject.endsWith(".xcworkspace");
|
|
1107
1386
|
const scheme = options.scheme || xcodeProject.split("/").pop()?.replace(/\.(xcworkspace|xcodeproj)$/, "") || "App";
|
|
1108
1387
|
const destination = options.device ? "generic/platform=iOS" : `platform=iOS Simulator,name=${options.simulator}`;
|
|
1109
1388
|
const projectFlag = isWorkspace ? "-workspace" : "-project";
|
|
1110
|
-
console.log(
|
|
1111
|
-
const xcodebuild =
|
|
1389
|
+
console.log(pc4.white(` Building ${scheme} for ${options.device ? "device" : options.simulator}...`));
|
|
1390
|
+
const xcodebuild = spawn3(
|
|
1112
1391
|
"xcodebuild",
|
|
1113
1392
|
[projectFlag, xcodeProject, "-scheme", scheme, "-destination", destination, "build"],
|
|
1114
1393
|
{
|
|
@@ -1120,71 +1399,71 @@ function runIOS(cwd, options) {
|
|
|
1120
1399
|
xcodebuild.stderr?.on("data", (data) => {
|
|
1121
1400
|
const text = data.toString().trim();
|
|
1122
1401
|
if (text.includes("error:") || text.includes("warning:")) {
|
|
1123
|
-
console.log(
|
|
1402
|
+
console.log(pc4.dim(` ${text}`));
|
|
1124
1403
|
}
|
|
1125
1404
|
});
|
|
1126
1405
|
xcodebuild.on("close", (code) => {
|
|
1127
1406
|
if (code !== 0) {
|
|
1128
|
-
console.error(
|
|
1407
|
+
console.error(pc4.red(` \u2717 Build failed (exit code ${code})`));
|
|
1129
1408
|
process.exit(1);
|
|
1130
1409
|
}
|
|
1131
|
-
console.log(
|
|
1410
|
+
console.log(pc4.green(" \u2713 Build successful\n"));
|
|
1132
1411
|
if (options.device) {
|
|
1133
|
-
console.log(
|
|
1412
|
+
console.log(pc4.green(" App built for device. Install via Xcode.\n"));
|
|
1134
1413
|
return;
|
|
1135
1414
|
}
|
|
1136
1415
|
const simulatorName = options.simulator;
|
|
1137
|
-
const bundleId = options.bundleId || readBundleId(
|
|
1138
|
-
console.log(
|
|
1416
|
+
const bundleId = options.bundleId || readBundleId(join4(cwd, "ios"));
|
|
1417
|
+
console.log(pc4.white(` Booting simulator "${simulatorName}"...`));
|
|
1139
1418
|
try {
|
|
1140
|
-
|
|
1419
|
+
execSync3(`xcrun simctl boot "${simulatorName}"`, { stdio: "pipe" });
|
|
1141
1420
|
} catch {
|
|
1142
1421
|
}
|
|
1143
1422
|
try {
|
|
1144
|
-
|
|
1423
|
+
execSync3("open -a Simulator", { stdio: "pipe" });
|
|
1145
1424
|
} catch {
|
|
1146
1425
|
}
|
|
1147
|
-
const appPath = findAppPath(
|
|
1426
|
+
const appPath = findAppPath(join4(cwd, "ios"));
|
|
1148
1427
|
if (appPath) {
|
|
1149
|
-
console.log(
|
|
1428
|
+
console.log(pc4.white(` Installing app on simulator...`));
|
|
1150
1429
|
try {
|
|
1151
|
-
|
|
1152
|
-
console.log(
|
|
1430
|
+
execSync3(`xcrun simctl install booted "${appPath}"`, { stdio: "pipe" });
|
|
1431
|
+
console.log(pc4.green(" \u2713 App installed"));
|
|
1153
1432
|
} catch (err) {
|
|
1154
|
-
console.error(
|
|
1433
|
+
console.error(pc4.red(` \u2717 Failed to install app: ${err.message}`));
|
|
1155
1434
|
process.exit(1);
|
|
1156
1435
|
}
|
|
1157
|
-
console.log(
|
|
1436
|
+
console.log(pc4.white(` Launching ${bundleId}...`));
|
|
1158
1437
|
try {
|
|
1159
|
-
|
|
1160
|
-
console.log(
|
|
1438
|
+
execSync3(`xcrun simctl launch booted "${bundleId}"`, { stdio: "pipe" });
|
|
1439
|
+
console.log(pc4.green(` \u2713 App launched on ${simulatorName}
|
|
1161
1440
|
`));
|
|
1162
1441
|
} catch (err) {
|
|
1163
|
-
console.error(
|
|
1442
|
+
console.error(pc4.red(` \u2717 Failed to launch app: ${err.message}`));
|
|
1164
1443
|
process.exit(1);
|
|
1165
1444
|
}
|
|
1166
1445
|
} else {
|
|
1167
|
-
console.log(
|
|
1168
|
-
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"));
|
|
1169
1448
|
}
|
|
1170
1449
|
});
|
|
1171
1450
|
}
|
|
1172
1451
|
function runAndroid(cwd, options) {
|
|
1173
|
-
const androidDir =
|
|
1174
|
-
if (!
|
|
1175
|
-
console.log(
|
|
1176
|
-
console.log(
|
|
1177
|
-
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"));
|
|
1178
1457
|
return;
|
|
1179
1458
|
}
|
|
1180
|
-
const gradlew =
|
|
1181
|
-
if (!
|
|
1182
|
-
console.error(
|
|
1183
|
-
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"));
|
|
1184
1463
|
process.exit(1);
|
|
1185
1464
|
}
|
|
1186
|
-
console.log(
|
|
1187
|
-
const gradle =
|
|
1465
|
+
console.log(pc4.white(" Building Android app with Gradle..."));
|
|
1466
|
+
const gradle = spawn3(
|
|
1188
1467
|
"./gradlew",
|
|
1189
1468
|
["assembleDebug"],
|
|
1190
1469
|
{
|
|
@@ -1204,48 +1483,48 @@ function runAndroid(cwd, options) {
|
|
|
1204
1483
|
gradle.stdout?.on("data", (data) => {
|
|
1205
1484
|
const text = data.toString().trim();
|
|
1206
1485
|
if (text) {
|
|
1207
|
-
console.log(
|
|
1486
|
+
console.log(pc4.dim(` ${text}`));
|
|
1208
1487
|
}
|
|
1209
1488
|
});
|
|
1210
1489
|
gradle.stderr?.on("data", (data) => {
|
|
1211
1490
|
const text = data.toString().trim();
|
|
1212
1491
|
if (text.includes("ERROR") || text.includes("FAILURE")) {
|
|
1213
|
-
console.log(
|
|
1492
|
+
console.log(pc4.red(` ${text}`));
|
|
1214
1493
|
}
|
|
1215
1494
|
});
|
|
1216
1495
|
gradle.on("error", (err) => {
|
|
1217
|
-
console.error(
|
|
1496
|
+
console.error(pc4.red(` Gradle process error: ${err.message}`));
|
|
1218
1497
|
cleanupGradle();
|
|
1219
1498
|
});
|
|
1220
1499
|
gradle.on("close", (code) => {
|
|
1221
1500
|
if (code !== 0) {
|
|
1222
|
-
console.error(
|
|
1501
|
+
console.error(pc4.red(` \u2717 Gradle build failed (exit code ${code})`));
|
|
1223
1502
|
process.exit(1);
|
|
1224
1503
|
}
|
|
1225
|
-
console.log(
|
|
1504
|
+
console.log(pc4.green(" \u2713 Build successful\n"));
|
|
1226
1505
|
const apkPath = findApkPath(androidDir);
|
|
1227
1506
|
if (!apkPath) {
|
|
1228
|
-
console.log(
|
|
1229
|
-
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"));
|
|
1230
1509
|
return;
|
|
1231
1510
|
}
|
|
1232
|
-
console.log(
|
|
1511
|
+
console.log(pc4.white(" Installing APK on device/emulator..."));
|
|
1233
1512
|
try {
|
|
1234
|
-
|
|
1235
|
-
console.log(
|
|
1513
|
+
execSync3(`adb install -r "${apkPath}"`, { stdio: "pipe" });
|
|
1514
|
+
console.log(pc4.green(" \u2713 APK installed"));
|
|
1236
1515
|
} catch (err) {
|
|
1237
|
-
console.error(
|
|
1238
|
-
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"));
|
|
1239
1518
|
process.exit(1);
|
|
1240
1519
|
}
|
|
1241
1520
|
const componentName = `${options.package}/${options.activity}`;
|
|
1242
|
-
console.log(
|
|
1521
|
+
console.log(pc4.white(` Launching ${componentName}...`));
|
|
1243
1522
|
try {
|
|
1244
|
-
|
|
1245
|
-
console.log(
|
|
1523
|
+
execSync3(`adb shell am start -n "${componentName}"`, { stdio: "pipe" });
|
|
1524
|
+
console.log(pc4.green(` \u2713 App launched
|
|
1246
1525
|
`));
|
|
1247
1526
|
} catch (err) {
|
|
1248
|
-
console.error(
|
|
1527
|
+
console.error(pc4.red(` \u2717 Failed to launch app: ${err.message}`));
|
|
1249
1528
|
process.exit(1);
|
|
1250
1529
|
}
|
|
1251
1530
|
});
|
|
@@ -1253,6 +1532,7 @@ function runAndroid(cwd, options) {
|
|
|
1253
1532
|
|
|
1254
1533
|
// src/cli.ts
|
|
1255
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);
|
|
1256
1536
|
program.addCommand(createCommand);
|
|
1257
1537
|
program.addCommand(devCommand);
|
|
1258
1538
|
program.addCommand(runCommand);
|