mobile-app-builder 0.0.1

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.
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findLatestArtifact = findLatestArtifact;
7
+ exports.signCommand = signCommand;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const execa_1 = require("execa");
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const dependencies_js_1 = require("../utils/dependencies.js");
13
+ const signing_js_1 = require("../utils/signing.js");
14
+ const project_js_1 = require("../utils/project.js");
15
+ const config_js_1 = require("../utils/config.js");
16
+ async function findLatestArtifact(extFilter) {
17
+ const searchDirs = [path_1.default.join(process.cwd(), "build"), process.cwd()];
18
+ for (const dir of searchDirs) {
19
+ if (await fs_extra_1.default.pathExists(dir)) {
20
+ const files = await fs_extra_1.default.readdir(dir);
21
+ const matched = files
22
+ .filter((f) => {
23
+ const ext = path_1.default.extname(f).toLowerCase();
24
+ return extFilter
25
+ ? extFilter.includes(ext)
26
+ : [".aab", ".apk"].includes(ext);
27
+ })
28
+ .map((f) => path_1.default.join(dir, f));
29
+ if (matched.length > 0) {
30
+ const stats = await Promise.all(matched.map(async (f) => ({ path: f, stat: await fs_extra_1.default.stat(f) })));
31
+ stats.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
32
+ return stats[0].path;
33
+ }
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ async function signCommand(fileArg, options = {}) {
39
+ await (0, dependencies_js_1.ensureJava)();
40
+ let targetPath = fileArg || options.file;
41
+ if (!targetPath) {
42
+ targetPath = (await findLatestArtifact()) ?? undefined;
43
+ }
44
+ if (!targetPath) {
45
+ console.error(chalk_1.default.red("Error: No AAB or APK file specified or found in ./build or current directory."));
46
+ process.exit(1);
47
+ }
48
+ targetPath = path_1.default.resolve(process.cwd(), targetPath);
49
+ if (!(await fs_extra_1.default.pathExists(targetPath))) {
50
+ console.error(chalk_1.default.red(`Error: File not found: ${targetPath}`));
51
+ process.exit(1);
52
+ }
53
+ const ext = path_1.default.extname(targetPath).toLowerCase();
54
+ if (ext !== ".aab" && ext !== ".apk") {
55
+ console.error(chalk_1.default.red(`Error: Unsupported file type '${ext}'. Expected .aab or .apk.`));
56
+ process.exit(1);
57
+ }
58
+ const isAab = ext === ".aab";
59
+ console.log(chalk_1.default.cyan(`\nProcessing artifact: ${path_1.default.basename(targetPath)}`));
60
+ // Determine App Name from project configuration for certificate default
61
+ const { appName: detectedName } = await (0, config_js_1.getAppConfigInfo)();
62
+ const appName = (0, project_js_1.toProperName)(detectedName || "App");
63
+ // 1. Resolve Keystore Credentials (or interactively create if not found)
64
+ const { keystorePath, ksAlias, ksPass, keyPass } = await (0, signing_js_1.ensureOrPromptKeystore)(options.keystore, options.alias, options.storepass, options.keypass, appName, path_1.default.dirname(targetPath));
65
+ console.log(chalk_1.default.gray(`Using keystore: ${keystorePath} (alias: ${ksAlias})`));
66
+ // 2. Strip ALL existing signatures to prevent multiple certificate chains
67
+ await (0, signing_js_1.stripExistingSignatures)(targetPath);
68
+ // 3. Sign artifact
69
+ if (isAab) {
70
+ console.log(chalk_1.default.cyan(`Signing ${path_1.default.basename(targetPath)} with jarsigner...`));
71
+ try {
72
+ await (0, execa_1.execa)("jarsigner", [
73
+ "-sigalg",
74
+ "SHA256withRSA",
75
+ "-digestalg",
76
+ "SHA-256",
77
+ "-keystore",
78
+ keystorePath,
79
+ "-storepass",
80
+ ksPass,
81
+ "-keypass",
82
+ keyPass,
83
+ targetPath,
84
+ ksAlias,
85
+ ]);
86
+ console.log(chalk_1.default.green("Successfully signed AAB!"));
87
+ }
88
+ catch (err) {
89
+ console.error(chalk_1.default.red(`jarsigner failed:\n${err.stderr || err.message}`));
90
+ process.exit(1);
91
+ }
92
+ }
93
+ else {
94
+ console.log(chalk_1.default.cyan(`Signing ${path_1.default.basename(targetPath)} with apksigner...`));
95
+ let apksignerJar = "";
96
+ const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT || "C:/Android";
97
+ const buildToolsDir = path_1.default.join(androidHome, "build-tools");
98
+ if (await fs_extra_1.default.pathExists(buildToolsDir)) {
99
+ const versions = await fs_extra_1.default.readdir(buildToolsDir);
100
+ if (versions.length > 0) {
101
+ versions.sort((a, b) => b.localeCompare(a));
102
+ apksignerJar = path_1.default.join(buildToolsDir, versions[0], "lib", "apksigner.jar");
103
+ }
104
+ }
105
+ if (await fs_extra_1.default.pathExists(apksignerJar)) {
106
+ try {
107
+ await (0, execa_1.execa)("java", [
108
+ "-jar",
109
+ apksignerJar,
110
+ "sign",
111
+ "--ks",
112
+ keystorePath,
113
+ "--ks-key-alias",
114
+ ksAlias,
115
+ "--ks-pass",
116
+ `pass:${ksPass}`,
117
+ "--key-pass",
118
+ `pass:${keyPass}`,
119
+ "--v4-signing-enabled",
120
+ "false",
121
+ targetPath,
122
+ ]);
123
+ console.log(chalk_1.default.green("Successfully signed APK with apksigner!"));
124
+ }
125
+ catch (err) {
126
+ console.error(chalk_1.default.red(`apksigner failed:\n${err.stderr || err.message}`));
127
+ process.exit(1);
128
+ }
129
+ }
130
+ else {
131
+ console.log(chalk_1.default.yellow("apksigner not found. Falling back to jarsigner for APK..."));
132
+ try {
133
+ await (0, execa_1.execa)("jarsigner", [
134
+ "-sigalg",
135
+ "SHA256withRSA",
136
+ "-digestalg",
137
+ "SHA-256",
138
+ "-keystore",
139
+ keystorePath,
140
+ "-storepass",
141
+ ksPass,
142
+ "-keypass",
143
+ keyPass,
144
+ targetPath,
145
+ ksAlias,
146
+ ]);
147
+ console.log(chalk_1.default.green("Successfully signed APK with jarsigner!"));
148
+ }
149
+ catch (err) {
150
+ console.error(chalk_1.default.red(`jarsigner failed:\n${err.stderr || err.message}`));
151
+ process.exit(1);
152
+ }
153
+ }
154
+ }
155
+ // 4. Verify signature
156
+ if (options.verify !== false) {
157
+ console.log(chalk_1.default.cyan("\nVerifying certificate chain..."));
158
+ try {
159
+ const { stdout } = await (0, execa_1.execa)("jarsigner", [
160
+ "-verify",
161
+ "-verbose",
162
+ targetPath,
163
+ ]);
164
+ const signers = stdout.match(/- Signed by "[^"]+"/g);
165
+ if (signers && signers.length > 0) {
166
+ console.log(chalk_1.default.green(`Verified ${signers.length} certificate chain(s):`));
167
+ for (const s of signers) {
168
+ console.log(chalk_1.default.gray(` ${s}`));
169
+ }
170
+ if (signers.length === 1) {
171
+ console.log(chalk_1.default.green("✓ Artifact is verified with exactly one certificate chain."));
172
+ }
173
+ else {
174
+ console.log(chalk_1.default.yellow("⚠️ Warning: Multiple certificate chains detected!"));
175
+ }
176
+ }
177
+ else {
178
+ console.log(chalk_1.default.yellow("Note: Verified successfully without explicit signer lines."));
179
+ }
180
+ }
181
+ catch (err) {
182
+ console.log(chalk_1.default.yellow(`Warning during verification: ${err.message}`));
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,10 @@
1
+ export interface UploadCommandOptions {
2
+ file?: string;
3
+ tag?: string;
4
+ title?: string;
5
+ notes?: string;
6
+ publish?: boolean;
7
+ track?: string;
8
+ prerelease?: boolean;
9
+ }
10
+ export declare function uploadCommand(fileArg?: string, options?: UploadCommandOptions): Promise<void>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadCommand = uploadCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const execa_1 = require("execa");
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const dependencies_js_1 = require("../utils/dependencies.js");
12
+ const github_js_1 = require("../utils/github.js");
13
+ const sign_js_1 = require("./sign.js");
14
+ async function uploadCommand(fileArg, options = {}) {
15
+ await (0, dependencies_js_1.ensureGitHubCLI)();
16
+ if (!(await (0, github_js_1.hasGitRemote)())) {
17
+ console.error(chalk_1.default.red("Error: No git remote found. Please connect your repository to GitHub ('git remote add origin <url>') before uploading."));
18
+ process.exit(1);
19
+ }
20
+ if (!(await (0, github_js_1.isGhAuthenticated)())) {
21
+ console.error(chalk_1.default.red("Error: GitHub CLI is not authenticated. Please run 'gh auth login' before uploading."));
22
+ process.exit(1);
23
+ }
24
+ let targetPath = fileArg || options.file;
25
+ if (!targetPath) {
26
+ targetPath = (await (0, sign_js_1.findLatestArtifact)()) ?? undefined;
27
+ }
28
+ if (!targetPath) {
29
+ console.error(chalk_1.default.red("Error: No AAB or APK file specified or found in ./build or current directory."));
30
+ process.exit(1);
31
+ }
32
+ targetPath = path_1.default.resolve(process.cwd(), targetPath);
33
+ if (!(await fs_extra_1.default.pathExists(targetPath))) {
34
+ console.error(chalk_1.default.red(`Error: File not found: ${targetPath}`));
35
+ process.exit(1);
36
+ }
37
+ const filename = path_1.default.basename(targetPath);
38
+ const ext = path_1.default.extname(targetPath).toLowerCase();
39
+ const isAab = ext === ".aab";
40
+ // Determine tag
41
+ let tag = options.tag;
42
+ if (!tag) {
43
+ const match = filename.match(/v\d+\.\d+\.\d+(?:-[a-zA-Z0-9_]+)?/);
44
+ if (match) {
45
+ tag = match[0];
46
+ }
47
+ else {
48
+ const pkgPath = path_1.default.join(process.cwd(), "package.json");
49
+ if (await fs_extra_1.default.pathExists(pkgPath)) {
50
+ try {
51
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
52
+ if (pkg.version) {
53
+ const profileSuffix = isAab ? "-prod" : "";
54
+ tag = `v${pkg.version}${profileSuffix}`;
55
+ }
56
+ }
57
+ catch { }
58
+ }
59
+ }
60
+ }
61
+ if (!tag) {
62
+ console.error(chalk_1.default.red("Error: Could not determine release tag. Please specify with --tag <tag> (e.g. --tag v1.0.0-prod)."));
63
+ process.exit(1);
64
+ }
65
+ const title = options.title || tag;
66
+ const notes = options.notes || "";
67
+ const isPrerelease = options.prerelease !== undefined
68
+ ? options.prerelease
69
+ : tag.includes("-dev") || tag.includes("-preview");
70
+ console.log(chalk_1.default.cyan(`\nUploading ${filename} to GitHub Release '${tag}'...`));
71
+ try {
72
+ let releaseExists = false;
73
+ try {
74
+ await (0, execa_1.execa)("gh", ["release", "view", tag]);
75
+ releaseExists = true;
76
+ }
77
+ catch { }
78
+ if (releaseExists) {
79
+ console.log(chalk_1.default.yellow(`Release '${tag}' already exists. Uploading asset with --clobber...`));
80
+ await (0, execa_1.execa)("gh", ["release", "upload", tag, targetPath, "--clobber"], {
81
+ stdio: "inherit",
82
+ });
83
+ }
84
+ else {
85
+ console.log(chalk_1.default.yellow(`Creating new release '${tag}' and uploading asset...`));
86
+ const createArgs = [
87
+ "release",
88
+ "create",
89
+ tag,
90
+ targetPath,
91
+ `--title=${title}`,
92
+ `--notes=${notes}`,
93
+ ];
94
+ if (isPrerelease) {
95
+ createArgs.push("--prerelease");
96
+ }
97
+ await (0, execa_1.execa)("gh", createArgs, { stdio: "inherit" });
98
+ }
99
+ console.log(chalk_1.default.green(`\n✓ Successfully uploaded ${filename} to release ${tag}!`));
100
+ }
101
+ catch (err) {
102
+ console.error(chalk_1.default.red(`\nError: Failed to upload release: ${err.stderr || err.message}`));
103
+ process.exit(1);
104
+ }
105
+ if (options.publish) {
106
+ if (isAab) {
107
+ const track = options.track || "alpha";
108
+ await (0, github_js_1.triggerGitHubWorkflow)(tag, track);
109
+ }
110
+ else {
111
+ console.log(chalk_1.default.yellow("Note: Skipping Play Store trigger (--publish is only applicable for .aab bundles)."));
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./commands/buildAndroid.js";
2
+ export * from "./commands/doctor.js";
3
+ export * from "./commands/sign.js";
4
+ export * from "./commands/upload.js";
5
+ export * from "./commands/buildIos.js";
6
+ export * from "./utils/dependencies.js";
7
+ export * from "./utils/config.js";
8
+ export * from "./utils/gradle.js";
9
+ export * from "./utils/signing.js";
10
+ export * from "./utils/github.js";
11
+ export * from "./utils/project.js";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./commands/buildAndroid.js"), exports);
18
+ __exportStar(require("./commands/doctor.js"), exports);
19
+ __exportStar(require("./commands/sign.js"), exports);
20
+ __exportStar(require("./commands/upload.js"), exports);
21
+ __exportStar(require("./commands/buildIos.js"), exports);
22
+ __exportStar(require("./utils/dependencies.js"), exports);
23
+ __exportStar(require("./utils/config.js"), exports);
24
+ __exportStar(require("./utils/gradle.js"), exports);
25
+ __exportStar(require("./utils/signing.js"), exports);
26
+ __exportStar(require("./utils/github.js"), exports);
27
+ __exportStar(require("./utils/project.js"), exports);
@@ -0,0 +1,7 @@
1
+ export declare function syncVersionCode(profileName: string, increment?: boolean, projectType?: "expo" | "react-native"): Promise<number>;
2
+ export declare function getAppConfigInfo(projectType?: "expo" | "react-native"): Promise<{
3
+ appName: string;
4
+ appVersion: string;
5
+ appPackageName: string | null;
6
+ }>;
7
+ export declare function generateNativeProject(clean: boolean): Promise<void>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.syncVersionCode = syncVersionCode;
7
+ exports.getAppConfigInfo = getAppConfigInfo;
8
+ exports.generateNativeProject = generateNativeProject;
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const execa_1 = require("execa");
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const project_js_1 = require("./project.js");
14
+ const gradle_js_1 = require("./gradle.js");
15
+ async function syncVersionCode(profileName, increment = false, projectType = "react-native") {
16
+ console.log(chalk_1.default.cyan("\nStep 03: Resolving and syncing Android versionCode..."));
17
+ if (projectType !== "expo") {
18
+ const localCode = await (0, gradle_js_1.getGradleVersionCode)();
19
+ if (increment) {
20
+ const nextCode = localCode + 1;
21
+ console.log(chalk_1.default.green(`Incrementing Android versionCode from ${localCode} to ${nextCode}.`));
22
+ return nextCode;
23
+ }
24
+ console.log(chalk_1.default.green(`Using current Android versionCode: ${localCode}`));
25
+ return localCode;
26
+ }
27
+ // Expo project: attempt remote version sync
28
+ try {
29
+ const { stdout } = await (0, execa_1.execa)("npx", [
30
+ "--yes",
31
+ "--quiet",
32
+ "eas-cli",
33
+ "build:version:get",
34
+ "--platform",
35
+ "android",
36
+ "--profile",
37
+ profileName,
38
+ "--json",
39
+ "--non-interactive",
40
+ ]);
41
+ const idx = stdout.indexOf("{");
42
+ if (idx >= 0) {
43
+ const obj = JSON.parse(stdout.substring(idx));
44
+ const remoteVersionCode = obj.versionCode || 1;
45
+ console.log(chalk_1.default.green(`Remote Version Code found: ${remoteVersionCode}`));
46
+ if (increment) {
47
+ try {
48
+ const nextVersionCode = parseInt(String(remoteVersionCode), 10) + 1;
49
+ console.log(chalk_1.default.yellow(`\nNOTE: Syncing new version code (${nextVersionCode}) into remote registry...`));
50
+ if (process.platform === "win32") {
51
+ require("child_process").exec(`powershell -WindowStyle Hidden -Command "Start-Sleep -Seconds 2; Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${nextVersionCode}{ENTER}')"`);
52
+ }
53
+ else if (process.platform === "darwin") {
54
+ require("child_process").exec(`osascript -e 'delay 2' -e 'tell application "System Events" to keystroke "${nextVersionCode}" & return'`);
55
+ }
56
+ await (0, execa_1.execa)("npx", [
57
+ "--yes",
58
+ "--quiet",
59
+ "eas-cli",
60
+ "build:version:set",
61
+ "--platform",
62
+ "android",
63
+ "--profile",
64
+ profileName,
65
+ ], { stdio: "inherit" });
66
+ }
67
+ catch (err) {
68
+ console.log(chalk_1.default.yellow("Failed to sync incremented version code to remote service."));
69
+ }
70
+ }
71
+ else {
72
+ console.log(chalk_1.default.gray(`Using current remote version code (${remoteVersionCode}) without incrementing.`));
73
+ }
74
+ return remoteVersionCode;
75
+ }
76
+ }
77
+ catch (err) {
78
+ const fallbackCode = await (0, gradle_js_1.getGradleVersionCode)();
79
+ const finalCode = increment ? fallbackCode + 1 : fallbackCode;
80
+ console.log(chalk_1.default.gray(`Using local Gradle versionCode (${finalCode}).`));
81
+ return finalCode;
82
+ }
83
+ return 1;
84
+ }
85
+ async function getAppConfigInfo(projectType = "react-native") {
86
+ let appName = "app";
87
+ let appVersion = "unknown";
88
+ let appPackageName = null;
89
+ // Try Expo config if Expo project
90
+ if (projectType === "expo") {
91
+ try {
92
+ const { stdout } = await (0, execa_1.execa)("npx", [
93
+ "--yes",
94
+ "--quiet",
95
+ "expo",
96
+ "config",
97
+ "--json",
98
+ ]);
99
+ const idx = stdout.indexOf("{");
100
+ if (idx >= 0) {
101
+ const config = JSON.parse(stdout.substring(idx));
102
+ appName = (config.name || "app").replace(/ /g, "-");
103
+ appVersion = config.version || "unknown";
104
+ if (config.android?.package) {
105
+ appPackageName = config.android.package;
106
+ }
107
+ }
108
+ }
109
+ catch { }
110
+ }
111
+ // Fallback or Native React Native project inspection
112
+ const pkgPath = path_1.default.join(process.cwd(), "package.json");
113
+ if (await fs_extra_1.default.pathExists(pkgPath)) {
114
+ try {
115
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
116
+ if (appVersion === "unknown" && pkg.version) {
117
+ appVersion = pkg.version;
118
+ }
119
+ if (appName === "app") {
120
+ const rawName = pkg.displayName || pkg.productName || pkg.name;
121
+ if (rawName) {
122
+ appName = rawName.replace(/ /g, "-");
123
+ }
124
+ }
125
+ }
126
+ catch { }
127
+ }
128
+ if (appName === "app") {
129
+ const nativeName = await (0, project_js_1.getNativeAppName)();
130
+ appName = nativeName.replace(/ /g, "-");
131
+ }
132
+ if (!appPackageName) {
133
+ appPackageName = await (0, project_js_1.getNativePackageName)();
134
+ }
135
+ if (!appPackageName) {
136
+ const files = [
137
+ "app.config.ts",
138
+ "app.config.js",
139
+ "app.json",
140
+ "android/app/build.gradle",
141
+ ];
142
+ for (const file of files) {
143
+ const p = path_1.default.join(process.cwd(), file);
144
+ if (await fs_extra_1.default.pathExists(p)) {
145
+ const content = await fs_extra_1.default.readFile(p, "utf8");
146
+ const match = content.match(/package(?:Name|)["':\s]+([a-zA-Z0-9\._]+)["']/i) ||
147
+ content.match(/applicationId\s+["']([^"']+)["']/);
148
+ if (match) {
149
+ appPackageName = match[1];
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return { appName, appVersion, appPackageName };
156
+ }
157
+ async function generateNativeProject(clean) {
158
+ if (clean || !(await fs_extra_1.default.pathExists(path_1.default.join(process.cwd(), "android")))) {
159
+ if (await fs_extra_1.default.pathExists(path_1.default.join(process.cwd(), "android", "gradlew"))) {
160
+ console.log(chalk_1.default.gray("Stopping Gradle daemon to release file locks..."));
161
+ try {
162
+ await (0, execa_1.execa)(process.platform === "win32" ? ".\\gradlew.bat" : "./gradlew", ["--stop"], { cwd: "android" });
163
+ }
164
+ catch { }
165
+ }
166
+ if (await fs_extra_1.default.pathExists("android")) {
167
+ await fs_extra_1.default.remove("android");
168
+ }
169
+ console.log(chalk_1.default.cyan("\nStep 04: Generating Android native project files..."));
170
+ await (0, execa_1.execa)("npx", ["expo", "prebuild", "--platform", "android", "--clean"], { stdio: "inherit" });
171
+ }
172
+ else {
173
+ console.log(chalk_1.default.gray("Skipping regeneration to use existing native cache."));
174
+ }
175
+ }
@@ -0,0 +1,13 @@
1
+ export declare function ensureGit(): Promise<void>;
2
+ export declare function ensureNode(minMajorVersion?: number): Promise<void>;
3
+ export declare function ensureGitHubCLI(): Promise<void>;
4
+ export declare function ensureJava(): Promise<void>;
5
+ export declare function ensureAndroidSdk(): Promise<string>;
6
+ export type ProjectType = "expo" | "react-native";
7
+ export interface ProjectInfo {
8
+ type: ProjectType;
9
+ isManaged: boolean;
10
+ hasAndroidDir: boolean;
11
+ }
12
+ export declare function detectProjectType(): Promise<ProjectInfo | null>;
13
+ export declare function ensureProjectType(): Promise<ProjectInfo>;