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.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # Mobile App Builder (`mobile-app-builder`)
2
+
3
+ [![npm version](https://badge.fury.io/js/mobile-app-builder.svg)](https://badge.fury.io/js/mobile-app-builder)
4
+ [![npm downloads](https://img.shields.io/npm/dt/mobile-app-builder.svg)](https://www.npmjs.com/package/mobile-app-builder)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A cross-platform CLI builder and signer for **Expo** (Managed & Bare), **Native React Native**, and **Android** mobile projects. It automates bootstrapping dependencies, building, signing with keystores, and publishing applications.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Introduction](#introduction)
12
+ - [Features](#features)
13
+ - [Prerequisites](#prerequisites)
14
+ - [Installation](#installation)
15
+ - [Usage](#usage)
16
+ - [Command Options](#command-options)
17
+ - [Standalone Artifact Signing](#standalone-artifact-signing)
18
+ - [Build Profiles](#build-profiles)
19
+ - [Workflow Steps](#workflow-steps)
20
+ - [Development](#development)
21
+ - [License](#license)
22
+
23
+ ## Introduction
24
+
25
+ `mobile-app-builder` is an all-in-one CLI tool designed to streamline mobile builds for both **Expo** and **Native (Bare) React Native** projects. Instead of wrestling with complex cloud build configurations or manual Gradle signing setups, this tool automatically detects your project type, manages Android configurations safely, compiles APKs or AABs via Gradle, signs them with production or custom keystores, and optionally publishes to GitHub Releases or Google Play Store.
26
+
27
+ ## Features
28
+
29
+ - **Dual Project Support**: Works seamlessly on Expo projects (Managed CNG and Bare) as well as Native (Bare) React Native projects.
30
+ - **Permanent Android Folder Safety**: Automatically detects Native/Bare projects and **never** deletes or wipes your source-controlled `android/` folder.
31
+ - **Automated Native Generation**: Automatically runs native prebuild only for Managed Expo projects where the `android/` directory is ephemeral.
32
+ - **Universal Signing Pipeline**: Strips prior signatures and signs `.apk` and `.aab` artifacts using `apksigner` and `jarsigner`. Generates release keystores interactively and saves configurations to `.env.local`.
33
+ - **Dynamic Configuration & Versioning**: Automatically syncs version codes and version names directly into `package.json`, `app.json`, and `android/app/build.gradle`.
34
+ - **Multiple Build Profiles**: Supports `dev` (Debug APK), `preview` (Release APK), and `prod` (Release AAB).
35
+ - **Environment Management**: Inject specific `.env` files for different build flavors.
36
+ - **Publishing & CI Integration**: Generates GitHub Actions workflows (for production AABs), creates GitHub Releases, and triggers Play Store rollouts.
37
+
38
+ ## Prerequisites
39
+
40
+ Ensure you have the following installed on your machine (the `doctor` command can verify or install missing dependencies for you):
41
+
42
+ - [Node.js](https://nodejs.org/) (>= 22.0.0)
43
+ - [Java Development Kit (JDK 21)](https://adoptium.net/)
44
+ - [Android SDK](https://developer.android.com/studio) (Command Line Tools, Platform Tools, Build Tools)
45
+ - [Git](https://git-scm.com/)
46
+ - [GitHub CLI (gh)](https://cli.github.com/) (Required if using GitHub Release / Publish flags)
47
+
48
+ ## Installation
49
+
50
+ You can install `mobile-app-builder` directly from npm, or use it locally.
51
+
52
+ ```bash
53
+ # Global installation
54
+ npm install -g mobile-app-builder
55
+
56
+ # Local installation in a React Native or Expo project
57
+ npm install --save-dev mobile-app-builder
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ Navigate to your React Native or Expo project directory and run the command using `mobile-builder` (or `mobile-app-builder`):
63
+
64
+ ```bash
65
+ # Check environment and dependencies
66
+ npx mobile-builder doctor
67
+
68
+ # Build Android app
69
+ npx mobile-builder android [options]
70
+ ```
71
+
72
+ ### Examples
73
+
74
+ **Build a development APK:**
75
+
76
+ ```bash
77
+ npx mobile-builder android --dev
78
+ ```
79
+
80
+ **Build a release preview APK:**
81
+
82
+ ```bash
83
+ npx mobile-builder android --preview
84
+ ```
85
+
86
+ **Build a production AAB and publish:**
87
+
88
+ ```bash
89
+ npx mobile-builder android --prod --publish --track production --notes "Production release"
90
+ ```
91
+
92
+ **Test the build pipeline without running Gradle:**
93
+
94
+ ```bash
95
+ npx mobile-builder android --preview --dry-run
96
+ ```
97
+
98
+ ## Standalone Artifact Signing
99
+
100
+ Already built an APK or AAB? You can sign or re-sign it directly with:
101
+
102
+ ```bash
103
+ # Signs the latest AAB or APK found in ./build or current directory
104
+ npx mobile-builder sign
105
+
106
+ # Sign a specific artifact with a specified keystore
107
+ npx mobile-builder sign ./build/my-app.aab --keystore ./release.jks --alias my-key
108
+ ```
109
+
110
+ ## Command Options
111
+
112
+ | Option | Description |
113
+ | ----------------- | ------------------------------------------------------------------------- |
114
+ | `--dev` | Build a Development APK (`assembleDebug`, preserves cache) |
115
+ | `--preview` | Build a Preview APK (`assembleRelease`) |
116
+ | `--prod` | Build a Production AAB (`bundleRelease`) |
117
+ | `--clean` | Forces clean regeneration (Managed Expo only) |
118
+ | `--bump` | Explicitly bump patch version in `package.json` |
119
+ | `--no-bump` | Skip bumping patch version in `package.json` |
120
+ | `--publish` | Publish to Google Play Store (via GitHub Actions workflow for `--prod`) |
121
+ | `--track <track>` | Google Play Store track (e.g., `production`, `beta`, `alpha`, `internal`) |
122
+ | `--notes <notes>` | Release notes for GitHub Release |
123
+ | `--dry-run` | Run the full script but skip Gradle build, signing, and publishing |
124
+ | `--env <file>` | Path to a specific environment file to load (e.g., `.env.production`) |
125
+ | `--cleanup` | Force cleanup of ephemeral `android/` directory (Managed Expo only) |
126
+ | `--no-cleanup` | Keep the generated `android/` folder after build |
127
+
128
+ ## Build Profiles
129
+
130
+ The builder automatically maps profiles to the correct build types and artifact formats:
131
+
132
+ 1. **Dev (`--dev`)**
133
+ - **Profile Name**: `development`
134
+ - **Build Task**: `assembleDebug`
135
+ - **Output**: `app-debug.apk`
136
+ - **Versioning**: Preserves current version string and local `versionCode`
137
+ - **GitHub Release**: Marked as **Pre-release**
138
+ - **Speed**: Preserves `android/` folder and Gradle daemon for fast incremental rebuilds
139
+
140
+ 2. **Preview (`--preview`)**
141
+ - **Profile Name**: `preview`
142
+ - **Build Task**: `assembleRelease`
143
+ - **Output**: `app-release.apk`
144
+ - **Signing**: Automatically signed with release keystore
145
+ - **GitHub Release**: Marked as **Pre-release**
146
+
147
+ 3. **Prod (`--prod`)**
148
+ - **Profile Name**: `production`
149
+ - **Build Task**: `bundleRelease`
150
+ - **Output**: `app-release.aab`
151
+ - **Versioning**: Bumps patch version and increments `versionCode`
152
+ - **Signing**: Strips prior signatures and signs AAB via `jarsigner`
153
+ - **GitHub Release**: Marked as official **Latest Release**
154
+ - **CI/CD**: Generates `.github/workflows/publish.yml` and triggers Play Store rollout if `--publish` is specified
155
+
156
+ ## Workflow Steps
157
+
158
+ When you run a build, the CLI automatically performs the following steps:
159
+
160
+ 1. **Project Detection**: Detects whether the project is Expo (Managed or Bare) or Native React Native.
161
+ 2. **Bootstrapping**: Validates system dependencies (Node, Java 21, Android SDK, Git, gh).
162
+ 3. **Version Management**: Bumps `package.json` (for prod or if `--bump` is set) and syncs `versionCode` into Gradle or EAS.
163
+ 4. **Native Generation (Expo Managed only)**: Generates Android project files (`expo prebuild`). Skipped for Native React Native and Bare projects.
164
+ 5. **Gradle Patching**: Injects memory optimizations, caching, daemon, and incremental settings into `gradle.properties`.
165
+ 6. **Gradle Build**: Runs the Gradle wrapper with `--build-cache`, `--parallel`, and `--configure-on-demand`.
166
+ 7. **Signing**: Strips any previous signatures and signs the artifact with keystore credentials (`apksigner` for APK, `jarsigner` for AAB).
167
+ 8. **Copying**: Moves the finished `.apk` or `.aab` to the `build/` directory in the root.
168
+ 9. **GitHub Release**: Automatically creates/updates a GitHub Release (tagged as **Pre-release** for dev/preview). Skips gracefully if remote is unconfigured.
169
+ 10. **Play Store Rollout (Optional)**: If `--publish` is passed with `--prod`, triggers the Google Play Store deployment workflow.
170
+ 11. **Cleanup**: Cleans up ephemeral `android/` only on Managed Expo builds, while strictly preserving permanent native source code.
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ # Clone the repository
176
+ git clone https://github.com/georgechitechi/mobile-app-builder.git
177
+ cd mobile-app-builder
178
+
179
+ # Install dependencies
180
+ npm install
181
+
182
+ # Watch mode for TypeScript
183
+ npm run dev
184
+
185
+ # Build the final dist
186
+ npm run build
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT License. See [LICENSE](LICENSE) for more details.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const dotenv_1 = require("dotenv");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ // Load .env and .env.local from the current working directory
11
+ const cwd = process.cwd();
12
+ if (fs_1.default.existsSync(path_1.default.join(cwd, ".env"))) {
13
+ (0, dotenv_1.config)({ path: path_1.default.join(cwd, ".env"), quiet: true });
14
+ }
15
+ if (fs_1.default.existsSync(path_1.default.join(cwd, ".env.local"))) {
16
+ (0, dotenv_1.config)({ path: path_1.default.join(cwd, ".env.local"), quiet: true });
17
+ }
18
+ const commander_1 = require("commander");
19
+ const buildAndroid_js_1 = require("./commands/buildAndroid.js");
20
+ const doctor_js_1 = require("./commands/doctor.js");
21
+ const buildIos_js_1 = require("./commands/buildIos.js");
22
+ const sign_js_1 = require("./commands/sign.js");
23
+ const upload_js_1 = require("./commands/upload.js");
24
+ const pkgJsonPath = path_1.default.resolve(__dirname, "../package.json");
25
+ let version = "0.0.1";
26
+ try {
27
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgJsonPath, "utf-8"));
28
+ if (pkg.version) {
29
+ version = pkg.version;
30
+ }
31
+ }
32
+ catch { }
33
+ commander_1.program
34
+ .name("mobile-builder")
35
+ .description("Cross-platform CLI builder and signer for mobile applications (Expo, React Native, and Android projects)")
36
+ .version(version);
37
+ commander_1.program
38
+ .command("doctor")
39
+ .aliases(["doc", "health"])
40
+ .description("Check dependencies and bootstrap environment")
41
+ .action(() => {
42
+ (0, doctor_js_1.doctor)().catch((err) => {
43
+ console.error(err);
44
+ process.exit(1);
45
+ });
46
+ });
47
+ commander_1.program
48
+ .command("android")
49
+ .aliases(["build:android", "ba"])
50
+ .description("Build Android app (APK or AAB)")
51
+ .option("--dev", "Build development profile")
52
+ .option("--preview", "Build preview profile")
53
+ .option("--prod", "Build production profile (AAB)")
54
+ .option("--clean", "Clean before building")
55
+ .option("--bump", "Explicitly bump patch version in package.json")
56
+ .option("--no-bump", "Skip bumping patch version in package.json")
57
+ .option("--publish", "Publish to Play Store")
58
+ .option("--track <track>", "Play Store Track", "alpha")
59
+ .option("--notes <notes>", "Release Notes")
60
+ .option("--dry-run", "Validate environment without running Gradle")
61
+ .option("--env <file>", "Path to .env file to inject")
62
+ .option("--cleanup", "Force cleaning up the android folder after build")
63
+ .option("--no-cleanup", "Skip cleaning up the android folder after build")
64
+ .action((options) => {
65
+ (0, buildAndroid_js_1.buildAndroid)(options).catch((err) => {
66
+ console.error(err);
67
+ process.exit(1);
68
+ });
69
+ });
70
+ commander_1.program
71
+ .command("sign [file]")
72
+ .aliases(["artifact:sign", "resign"])
73
+ .description("Sign an existing AAB or APK while stripping any prior signatures")
74
+ .option("-f, --file <path>", "Path to the AAB or APK file")
75
+ .option("-k, --keystore <path>", "Keystore file path")
76
+ .option("-a, --alias <alias>", "Keystore key alias")
77
+ .option("-p, --storepass <password>", "Keystore store password")
78
+ .option("--keypass <password>", "Key password")
79
+ .option("--no-verify", "Skip verifying signature after signing")
80
+ .action((file, options) => {
81
+ (0, sign_js_1.signCommand)(file, options).catch((err) => {
82
+ console.error(err);
83
+ process.exit(1);
84
+ });
85
+ });
86
+ commander_1.program
87
+ .command("upload [file]")
88
+ .aliases(["release:upload", "up"])
89
+ .description("Upload an existing AAB or APK to GitHub Releases")
90
+ .option("-f, --file <path>", "Path to the AAB or APK file")
91
+ .option("-t, --tag <tag>", "GitHub release tag (e.g. v1.0.0 or v1.0.0-prod)")
92
+ .option("--title <title>", "Release title")
93
+ .option("-n, --notes <notes>", "Release notes")
94
+ .option("--publish", "Trigger Play Store deployment workflow after upload (AAB only)")
95
+ .option("--track <track>", "Play Store Track (alpha, beta, production, internal)", "alpha")
96
+ .option("--prerelease", "Mark GitHub release as pre-release")
97
+ .action((file, options) => {
98
+ (0, upload_js_1.uploadCommand)(file, options).catch((err) => {
99
+ console.error(err);
100
+ process.exit(1);
101
+ });
102
+ });
103
+ commander_1.program
104
+ .command("ios")
105
+ .aliases(["build:ios", "bi"])
106
+ .description("Build iOS app (coming soon)")
107
+ .action(() => {
108
+ (0, buildIos_js_1.buildIos)().catch((err) => {
109
+ console.error(err);
110
+ process.exit(1);
111
+ });
112
+ });
113
+ commander_1.program.parse(process.argv);
@@ -0,0 +1,15 @@
1
+ export interface BuildAndroidOptions {
2
+ dev?: boolean;
3
+ preview?: boolean;
4
+ prod?: boolean;
5
+ clean?: boolean;
6
+ bump?: boolean;
7
+ noBump?: boolean;
8
+ publish?: boolean;
9
+ track: string;
10
+ notes?: string;
11
+ dryRun?: boolean;
12
+ env?: string;
13
+ cleanup?: boolean;
14
+ }
15
+ export declare function buildAndroid(options: BuildAndroidOptions): Promise<void>;
@@ -0,0 +1,160 @@
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.buildAndroid = buildAndroid;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const execa_1 = require("execa");
11
+ const dotenv_1 = __importDefault(require("dotenv"));
12
+ const dependencies_js_1 = require("../utils/dependencies.js");
13
+ const config_js_1 = require("../utils/config.js");
14
+ const gradle_js_1 = require("../utils/gradle.js");
15
+ const signing_js_1 = require("../utils/signing.js");
16
+ const github_js_1 = require("../utils/github.js");
17
+ const project_js_1 = require("../utils/project.js");
18
+ async function buildAndroid(options) {
19
+ const projectInfo = await (0, dependencies_js_1.ensureProjectType)();
20
+ console.log(chalk_1.default.cyan("Bootstrapping drop-in tool architecture..."));
21
+ if (options.env) {
22
+ console.log(chalk_1.default.cyan(`Loading environment variables from ${options.env}...`));
23
+ dotenv_1.default.config({
24
+ path: path_1.default.resolve(process.cwd(), options.env),
25
+ quiet: true,
26
+ });
27
+ }
28
+ const profile = options.dev ? "dev" : options.prod ? "prod" : "preview";
29
+ const fullProfileName = options.dev
30
+ ? "development"
31
+ : options.prod
32
+ ? "production"
33
+ : "preview";
34
+ const isAab = profile === "prod";
35
+ const buildType = profile === "dev" ? "Debug" : "Release";
36
+ const assembleTask = isAab
37
+ ? "bundleRelease"
38
+ : profile === "dev"
39
+ ? "assembleDebug"
40
+ : "assembleRelease";
41
+ const apkDir = profile === "dev" ? "debug" : "release";
42
+ const ext = isAab ? "aab" : "apk";
43
+ process.env.NODE_ENV = isAab ? "production" : "development";
44
+ process.env.APP_VARIANT = fullProfileName;
45
+ if (projectInfo.type === "expo") {
46
+ process.env.EAS_BUILD_PROFILE = fullProfileName;
47
+ }
48
+ try {
49
+ await (0, dependencies_js_1.ensureGit)();
50
+ await (0, dependencies_js_1.ensureNode)(22);
51
+ await (0, dependencies_js_1.ensureGitHubCLI)();
52
+ await (0, dependencies_js_1.ensureJava)();
53
+ await (0, dependencies_js_1.ensureAndroidSdk)();
54
+ const shouldBump = options.bump === true || (profile === "prod" && options.noBump !== true);
55
+ await (0, project_js_1.syncProjectVersionConfig)(shouldBump, projectInfo.type);
56
+ const currentVersionCode = await (0, config_js_1.syncVersionCode)(fullProfileName, shouldBump, projectInfo.type);
57
+ const { appName, appVersion, appPackageName } = await (0, config_js_1.getAppConfigInfo)(projectInfo.type);
58
+ if (!appPackageName) {
59
+ console.log(chalk_1.default.red("Error: Could not dynamically determine Android package name / applicationId!"));
60
+ process.exit(1);
61
+ }
62
+ const outputName = `${appName}-v${appVersion}-${profile}.${ext}`;
63
+ if (isAab) {
64
+ await (0, github_js_1.generatePublishWorkflow)(appPackageName);
65
+ }
66
+ if (projectInfo.isManaged) {
67
+ await (0, config_js_1.generateNativeProject)(!!options.clean);
68
+ }
69
+ else {
70
+ console.log(chalk_1.default.gray(`\nStep 04: Using existing native android project (${projectInfo.type === "expo" ? "Bare Expo" : "Native React Native"}). Skipping generation.`));
71
+ if (!(await fs_extra_1.default.pathExists(path_1.default.join(process.cwd(), "android")))) {
72
+ console.log(chalk_1.default.red("Error: 'android' directory not found in project root!"));
73
+ process.exit(1);
74
+ }
75
+ }
76
+ await (0, gradle_js_1.patchGradleProperties)();
77
+ await (0, gradle_js_1.syncBuildGradle)(appVersion, currentVersionCode);
78
+ console.log(chalk_1.default.cyan(`\nStep 06: Building ${buildType} ${ext} (${assembleTask})...`));
79
+ if (options.dryRun) {
80
+ console.log(chalk_1.default.yellow("\nDry run requested. Skipping Gradle build, signing, and publishing."));
81
+ console.log(chalk_1.default.green("\nDry run completed successfully!"));
82
+ return;
83
+ }
84
+ await (0, gradle_js_1.runGradleBuild)(assembleTask);
85
+ console.log(chalk_1.default.cyan(`\nStep 07: Copying ${ext} to build folder...`));
86
+ const outDir = path_1.default.join(process.cwd(), "build");
87
+ await fs_extra_1.default.ensureDir(outDir);
88
+ const artifactSubDir = isAab ? "bundle/release" : `apk/${apkDir}`;
89
+ const artifactDir = path_1.default.join(process.cwd(), "android/app/build/outputs", artifactSubDir);
90
+ const sourceName = isAab
91
+ ? "app-release.aab"
92
+ : profile === "dev"
93
+ ? "app-debug.apk"
94
+ : (await fs_extra_1.default.pathExists(path_1.default.join(artifactDir, "app-release-unsigned.apk")))
95
+ ? "app-release-unsigned.apk"
96
+ : "app-release.apk";
97
+ const artifactPath = path_1.default.join(artifactDir, sourceName);
98
+ const destPath = path_1.default.join(outDir, outputName);
99
+ if (await fs_extra_1.default.pathExists(artifactPath)) {
100
+ await fs_extra_1.default.copy(artifactPath, destPath, { overwrite: true });
101
+ if (!(await fs_extra_1.default.pathExists(destPath))) {
102
+ console.log(chalk_1.default.red(`Error: Failed to confirm ${ext} in build folder at ${destPath}.`));
103
+ process.exit(1);
104
+ }
105
+ console.log(chalk_1.default.green(`Success! ${ext} generated at ${destPath}`));
106
+ }
107
+ else {
108
+ console.log(chalk_1.default.red(`Error: ${ext} not found at expected location (${artifactPath}).`));
109
+ process.exit(1);
110
+ }
111
+ console.log(chalk_1.default.cyan(`\nStep 08: Signing ${ext}...`));
112
+ await (0, signing_js_1.signArtifact)(outputName, outDir, isAab, appName);
113
+ const tag = `v${appVersion}${profile ? "-" + profile : ""}`;
114
+ const isPrerelease = profile !== "prod";
115
+ const published = await (0, github_js_1.publishGitHubRelease)(tag, destPath, tag, options.notes || "", fullProfileName, isPrerelease);
116
+ if (options.publish && isAab && published) {
117
+ await (0, github_js_1.triggerGitHubWorkflow)(tag, options.track);
118
+ }
119
+ else if (options.publish && isAab && !published) {
120
+ console.log(chalk_1.default.yellow(`Note: Skipping Play Store trigger because artifact could not be published to GitHub Releases.`));
121
+ }
122
+ else if (options.publish && !isAab) {
123
+ console.log(chalk_1.default.yellow(`Note: Skipping Play Store trigger (only production AAB builds can be deployed to Play Store).`));
124
+ }
125
+ console.log(chalk_1.default.green("\nBuild completed successfully!"));
126
+ }
127
+ finally {
128
+ const shouldCleanup = projectInfo.isManaged &&
129
+ (options.cleanup !== undefined ? options.cleanup : profile !== "dev");
130
+ if (shouldCleanup) {
131
+ console.log(chalk_1.default.gray("\nCleaning up ephemeral android folder..."));
132
+ if (await fs_extra_1.default.pathExists("android")) {
133
+ if (await fs_extra_1.default.pathExists(path_1.default.join(process.cwd(), "android", "gradlew"))) {
134
+ console.log(chalk_1.default.gray("Stopping Gradle daemon to release file locks..."));
135
+ try {
136
+ await (0, execa_1.execa)(process.platform === "win32" ? ".\\gradlew.bat" : "./gradlew", ["--stop"], { cwd: "android" });
137
+ }
138
+ catch { }
139
+ }
140
+ try {
141
+ await fs_extra_1.default.remove("android");
142
+ console.log(chalk_1.default.green("Cleanup completed."));
143
+ }
144
+ catch (err) {
145
+ console.log(chalk_1.default.yellow(`Cleanup partially failed (file lock). Run 'gradlew --stop' if you need to manually delete it.`));
146
+ }
147
+ }
148
+ }
149
+ else {
150
+ if (projectInfo.isManaged) {
151
+ console.log(chalk_1.default.gray(profile === "dev"
152
+ ? "\nPreserving android/ folder and Gradle daemon for fast rebuilds (use --cleanup to delete)."
153
+ : "\nSkipping cleanup (--no-cleanup specified). The android/ folder has been left intact."));
154
+ }
155
+ else {
156
+ console.log(chalk_1.default.gray("\nPreserving permanent android/ source directory intact."));
157
+ }
158
+ }
159
+ }
160
+ }
@@ -0,0 +1 @@
1
+ export declare function buildIos(): Promise<void>;
@@ -0,0 +1,11 @@
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.buildIos = buildIos;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ async function buildIos() {
9
+ console.log(chalk_1.default.cyan("Starting iOS build pipeline..."));
10
+ console.log(chalk_1.default.yellow("iOS build support is coming soon! This command is currently a placeholder."));
11
+ }
@@ -0,0 +1 @@
1
+ export declare function doctor(): Promise<void>;
@@ -0,0 +1,28 @@
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.doctor = doctor;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const dependencies_js_1 = require("../utils/dependencies.js");
9
+ async function doctor() {
10
+ try {
11
+ await (0, dependencies_js_1.ensureProjectType)();
12
+ console.log(chalk_1.default.cyan("Running environment diagnostics...\n"));
13
+ await (0, dependencies_js_1.ensureNode)(22);
14
+ await (0, dependencies_js_1.ensureGit)();
15
+ await (0, dependencies_js_1.ensureGitHubCLI)();
16
+ await (0, dependencies_js_1.ensureJava)();
17
+ const sdkPath = await (0, dependencies_js_1.ensureAndroidSdk)();
18
+ console.log(chalk_1.default.green("\nAll dependency checks passed!"));
19
+ if (sdkPath) {
20
+ console.log(chalk_1.default.gray(`Android SDK Path: ${sdkPath}`));
21
+ }
22
+ }
23
+ catch (err) {
24
+ console.error(chalk_1.default.red("\nDiagnostics failed!"));
25
+ console.error(err);
26
+ process.exit(1);
27
+ }
28
+ }
@@ -0,0 +1,10 @@
1
+ export interface SignCommandOptions {
2
+ file?: string;
3
+ keystore?: string;
4
+ alias?: string;
5
+ storepass?: string;
6
+ keypass?: string;
7
+ verify?: boolean;
8
+ }
9
+ export declare function findLatestArtifact(extFilter?: string[]): Promise<string | null>;
10
+ export declare function signCommand(fileArg?: string, options?: SignCommandOptions): Promise<void>;