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 +191 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +113 -0
- package/dist/commands/buildAndroid.d.ts +15 -0
- package/dist/commands/buildAndroid.js +160 -0
- package/dist/commands/buildIos.d.ts +1 -0
- package/dist/commands/buildIos.js +11 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +28 -0
- package/dist/commands/sign.d.ts +10 -0
- package/dist/commands/sign.js +185 -0
- package/dist/commands/upload.d.ts +10 -0
- package/dist/commands/upload.js +114 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.js +175 -0
- package/dist/utils/dependencies.d.ts +13 -0
- package/dist/utils/dependencies.js +388 -0
- package/dist/utils/github.d.ts +5 -0
- package/dist/utils/github.js +216 -0
- package/dist/utils/gradle.d.ts +4 -0
- package/dist/utils/gradle.js +76 -0
- package/dist/utils/project.d.ts +22 -0
- package/dist/utils/project.js +143 -0
- package/dist/utils/signing.d.ts +11 -0
- package/dist/utils/signing.js +311 -0
- package/package.json +62 -0
|
@@ -0,0 +1,388 @@
|
|
|
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.ensureGit = ensureGit;
|
|
7
|
+
exports.ensureNode = ensureNode;
|
|
8
|
+
exports.ensureGitHubCLI = ensureGitHubCLI;
|
|
9
|
+
exports.ensureJava = ensureJava;
|
|
10
|
+
exports.ensureAndroidSdk = ensureAndroidSdk;
|
|
11
|
+
exports.detectProjectType = detectProjectType;
|
|
12
|
+
exports.ensureProjectType = ensureProjectType;
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const execa_1 = require("execa");
|
|
15
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const isWin = process.platform === "win32";
|
|
19
|
+
async function ensureGit() {
|
|
20
|
+
try {
|
|
21
|
+
await (0, execa_1.execa)("git", ["--version"]);
|
|
22
|
+
console.log(chalk_1.default.green("Git is installed."));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.log(chalk_1.default.yellow("\nGit is missing. Attempting to install via winget..."));
|
|
26
|
+
if (isWin) {
|
|
27
|
+
try {
|
|
28
|
+
await (0, execa_1.execa)("winget", [
|
|
29
|
+
"install",
|
|
30
|
+
"--id",
|
|
31
|
+
"Git.Git",
|
|
32
|
+
"-e",
|
|
33
|
+
"--source",
|
|
34
|
+
"winget",
|
|
35
|
+
"--accept-package-agreements",
|
|
36
|
+
"--accept-source-agreements",
|
|
37
|
+
], { stdio: "inherit" });
|
|
38
|
+
console.log(chalk_1.default.green("Git installed successfully! Please restart terminal if it is not in PATH."));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(chalk_1.default.red("Failed to install Git. Please install manually."));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(chalk_1.default.red("Git is required. Please install manually."));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function ensureNode(minMajorVersion = 22) {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await (0, execa_1.execa)("node", ["--version"]);
|
|
54
|
+
const match = stdout.match(/^v?(\d+)\./);
|
|
55
|
+
if (match && parseInt(match[1]) >= minMajorVersion) {
|
|
56
|
+
console.log(chalk_1.default.green(`Node.js is installed (${stdout.trim()}, >= v${minMajorVersion}).`));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(chalk_1.default.yellow(`Installed Node.js (${stdout.trim()}) is outdated. Required >= v${minMajorVersion}.`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
console.log(chalk_1.default.yellow("\nNode.js is not installed."));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function ensureGitHubCLI() {
|
|
67
|
+
try {
|
|
68
|
+
await (0, execa_1.execa)("gh", ["--version"]);
|
|
69
|
+
console.log(chalk_1.default.green("gh is installed."));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
console.log(chalk_1.default.yellow("\ngh not found. Attempting to install via winget..."));
|
|
73
|
+
if (isWin) {
|
|
74
|
+
try {
|
|
75
|
+
await (0, execa_1.execa)("winget", [
|
|
76
|
+
"install",
|
|
77
|
+
"--id",
|
|
78
|
+
"GitHub.cli",
|
|
79
|
+
"-e",
|
|
80
|
+
"--source",
|
|
81
|
+
"winget",
|
|
82
|
+
"--accept-package-agreements",
|
|
83
|
+
"--accept-source-agreements",
|
|
84
|
+
], { stdio: "inherit" });
|
|
85
|
+
console.log(chalk_1.default.green("GitHub CLI installed successfully!"));
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error(chalk_1.default.red("Failed to install GitHub CLI."));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.error(chalk_1.default.red("GitHub CLI required. Install manually."));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function ensureJava() {
|
|
99
|
+
const getJavaVer = async (cmdPath) => {
|
|
100
|
+
try {
|
|
101
|
+
const { stdout, stderr } = await (0, execa_1.execa)(cmdPath, ["-version"], {
|
|
102
|
+
reject: false,
|
|
103
|
+
});
|
|
104
|
+
const out = stdout || stderr;
|
|
105
|
+
const match = out.match(/version "(?:1\.)?(\d+)/);
|
|
106
|
+
return match ? parseInt(match[1]) : 0;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const findJdkFolder = async () => {
|
|
113
|
+
if (!isWin)
|
|
114
|
+
return null;
|
|
115
|
+
const searchBasePaths = [
|
|
116
|
+
"C:/Program Files/Microsoft",
|
|
117
|
+
"C:/Program Files/Eclipse Adoptium",
|
|
118
|
+
"C:/Program Files/Java",
|
|
119
|
+
];
|
|
120
|
+
for (const base of searchBasePaths) {
|
|
121
|
+
if (await fs_extra_1.default.pathExists(base)) {
|
|
122
|
+
const dirs = await fs_extra_1.default.readdir(base);
|
|
123
|
+
const jdk21Dirs = dirs.filter((d) => d.startsWith("jdk-21"));
|
|
124
|
+
if (jdk21Dirs.length > 0) {
|
|
125
|
+
jdk21Dirs.sort((a, b) => b.localeCompare(a));
|
|
126
|
+
return path_1.default.join(base, jdk21Dirs[0]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
let javaPath = "";
|
|
133
|
+
if (process.env.JAVA_HOME &&
|
|
134
|
+
(await fs_extra_1.default.pathExists(path_1.default.join(process.env.JAVA_HOME, "bin/java.exe")))) {
|
|
135
|
+
javaPath = path_1.default.join(process.env.JAVA_HOME, "bin/java.exe");
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
try {
|
|
139
|
+
const { stdout } = await (0, execa_1.execa)(isWin ? "where" : "which", ["java"]);
|
|
140
|
+
if (stdout) {
|
|
141
|
+
javaPath = stdout.split("\n")[0].trim();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
}
|
|
146
|
+
if (javaPath) {
|
|
147
|
+
const ver = await getJavaVer(javaPath);
|
|
148
|
+
if (ver === 21) {
|
|
149
|
+
console.log(chalk_1.default.green(`Java 21 is available at: ${javaPath}`));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(chalk_1.default.yellow(`Existing Java version (${ver}) is incompatible (required: JDK 21).`));
|
|
154
|
+
javaPath = "";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let existingJdk = await findJdkFolder();
|
|
158
|
+
if (existingJdk) {
|
|
159
|
+
process.env.JAVA_HOME = existingJdk;
|
|
160
|
+
process.env.Path = `${path_1.default.join(existingJdk, "bin")}${path_1.default.delimiter}${process.env.Path || ""}`;
|
|
161
|
+
console.log(chalk_1.default.green(`Located JDK 21 at: ${existingJdk}`));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(chalk_1.default.yellow("\nJDK 21 not found. Attempting to install Microsoft OpenJDK 21 via winget..."));
|
|
165
|
+
if (isWin) {
|
|
166
|
+
try {
|
|
167
|
+
await (0, execa_1.execa)("winget", [
|
|
168
|
+
"install",
|
|
169
|
+
"--id",
|
|
170
|
+
"Microsoft.OpenJDK.21",
|
|
171
|
+
"-e",
|
|
172
|
+
"--source",
|
|
173
|
+
"winget",
|
|
174
|
+
"--accept-package-agreements",
|
|
175
|
+
"--accept-source-agreements",
|
|
176
|
+
], { stdio: "inherit" });
|
|
177
|
+
existingJdk = await findJdkFolder();
|
|
178
|
+
if (existingJdk) {
|
|
179
|
+
process.env.JAVA_HOME = existingJdk;
|
|
180
|
+
process.env.Path = `${path_1.default.join(existingJdk, "bin")}${path_1.default.delimiter}${process.env.Path || ""}`;
|
|
181
|
+
console.log(chalk_1.default.green(`[SUCCESS] Set JAVA_HOME to ${existingJdk} and updated PATH`));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
console.error(chalk_1.default.red("Failed to install JDK 21."));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.error(chalk_1.default.red("Please install Java (JDK 21) manually and re-run."));
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
if (!(await findJdkFolder())) {
|
|
194
|
+
console.error(chalk_1.default.red("[ERROR] Could not install or locate JDK 21. Please install Java 21 manually."));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function ensureAndroidSdk() {
|
|
199
|
+
const candidatePaths = [
|
|
200
|
+
process.env.ANDROID_HOME,
|
|
201
|
+
process.env.ANDROID_SDK_ROOT,
|
|
202
|
+
"C:/Android",
|
|
203
|
+
path_1.default.join(os_1.default.homedir(), "AppData/Local/Android/Sdk"),
|
|
204
|
+
].filter(Boolean);
|
|
205
|
+
let validSdk = null;
|
|
206
|
+
for (const p of candidatePaths) {
|
|
207
|
+
if ((await fs_extra_1.default.pathExists(path_1.default.join(p, "platform-tools"))) ||
|
|
208
|
+
(await fs_extra_1.default.pathExists(path_1.default.join(p, "build-tools")))) {
|
|
209
|
+
validSdk = p.replace(/\\/g, "/");
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (validSdk) {
|
|
214
|
+
console.log(chalk_1.default.green(`Android SDK Found at ${validSdk}`));
|
|
215
|
+
process.env.ANDROID_HOME = validSdk;
|
|
216
|
+
process.env.ANDROID_SDK_ROOT = validSdk;
|
|
217
|
+
const tools = [
|
|
218
|
+
path_1.default.join(validSdk, "platform-tools"),
|
|
219
|
+
path_1.default.join(validSdk, "cmdline-tools", "latest", "bin"),
|
|
220
|
+
path_1.default.join(validSdk, "emulator"),
|
|
221
|
+
];
|
|
222
|
+
process.env.Path = `${tools.join(path_1.default.delimiter)}${path_1.default.delimiter}${process.env.Path || ""}`;
|
|
223
|
+
return validSdk;
|
|
224
|
+
}
|
|
225
|
+
console.log(chalk_1.default.yellow("\nNo Android SDK found on disk. Installing standalone command line tools to C:/Android..."));
|
|
226
|
+
validSdk = "C:/Android";
|
|
227
|
+
await fs_extra_1.default.ensureDir(validSdk);
|
|
228
|
+
const cmdlineToolsDir = path_1.default.join(validSdk, "cmdline-tools", "latest");
|
|
229
|
+
if (!(await fs_extra_1.default.pathExists(path_1.default.join(cmdlineToolsDir, "bin", "sdkmanager.bat")))) {
|
|
230
|
+
console.log(chalk_1.default.cyan("-> Downloading official Google Android Command Line Tools..."));
|
|
231
|
+
let zipName = "commandlinetools-win-11479570_latest.zip";
|
|
232
|
+
try {
|
|
233
|
+
const res = await fetch("https://dl.google.com/android/repository/repository2-3.xml");
|
|
234
|
+
const text = await res.text();
|
|
235
|
+
const matches = text.match(/commandlinetools-win-(\d+)_latest\.zip/g);
|
|
236
|
+
if (matches && matches.length > 0) {
|
|
237
|
+
matches.sort((a, b) => parseInt(b.match(/\d+/)[0]) - parseInt(a.match(/\d+/)[0]));
|
|
238
|
+
zipName = matches[0];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch { }
|
|
242
|
+
const zipUrl = `https://dl.google.com/android/repository/${zipName}`;
|
|
243
|
+
const rand = Math.floor(Math.random() * 100000);
|
|
244
|
+
const zipPath = path_1.default.join(os_1.default.tmpdir(), `cmdline-tools-${rand}.zip`);
|
|
245
|
+
console.log(chalk_1.default.cyan(`-> Downloading ${zipName} (~130MB)...`));
|
|
246
|
+
const res = await fetch(zipUrl);
|
|
247
|
+
if (!res.ok)
|
|
248
|
+
throw new Error(`Failed to download SDK: ${res.statusText}`);
|
|
249
|
+
const buffer = await res.arrayBuffer();
|
|
250
|
+
await fs_extra_1.default.writeFile(zipPath, Buffer.from(buffer));
|
|
251
|
+
console.log(chalk_1.default.green("[SUCCESS] Download complete."));
|
|
252
|
+
console.log(chalk_1.default.cyan("-> Extracting SDK tools archive..."));
|
|
253
|
+
const extractTemp = path_1.default.join(os_1.default.tmpdir(), `android-sdk-temp-${rand}`);
|
|
254
|
+
await fs_extra_1.default.remove(extractTemp);
|
|
255
|
+
await (0, execa_1.execa)("powershell", [
|
|
256
|
+
"-Command",
|
|
257
|
+
`Expand-Archive -Path '${zipPath}' -DestinationPath '${extractTemp}' -Force`,
|
|
258
|
+
]);
|
|
259
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(validSdk, "cmdline-tools"));
|
|
260
|
+
if (await fs_extra_1.default.pathExists(path_1.default.join(extractTemp, "cmdline-tools"))) {
|
|
261
|
+
await fs_extra_1.default.move(path_1.default.join(extractTemp, "cmdline-tools"), cmdlineToolsDir, {
|
|
262
|
+
overwrite: true,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
await fs_extra_1.default.remove(zipPath);
|
|
266
|
+
await fs_extra_1.default.remove(extractTemp);
|
|
267
|
+
console.log(chalk_1.default.green("[SUCCESS] Extraction complete."));
|
|
268
|
+
}
|
|
269
|
+
const sdkManager = path_1.default.join(validSdk, "cmdline-tools", "latest", "bin", "sdkmanager.bat");
|
|
270
|
+
if (await fs_extra_1.default.pathExists(sdkManager)) {
|
|
271
|
+
console.log(chalk_1.default.cyan("-> Running sdkmanager to install: platform-tools, build-tools;34.0.0, platforms;android-34..."));
|
|
272
|
+
const licenseAccept = Array(10).fill("y").join("\n") + "\n";
|
|
273
|
+
try {
|
|
274
|
+
const packages = [
|
|
275
|
+
"platform-tools",
|
|
276
|
+
"build-tools;34.0.0",
|
|
277
|
+
"platforms;android-34",
|
|
278
|
+
];
|
|
279
|
+
const sdkProcess = (0, execa_1.execa)("cmd.exe", [
|
|
280
|
+
"/c",
|
|
281
|
+
`${sdkManager} --sdk_root="${validSdk}" ${packages.join(" ")}`,
|
|
282
|
+
]);
|
|
283
|
+
if (sdkProcess.stdin) {
|
|
284
|
+
sdkProcess.stdin.write(licenseAccept);
|
|
285
|
+
sdkProcess.stdin.end();
|
|
286
|
+
}
|
|
287
|
+
await sdkProcess;
|
|
288
|
+
const licenseProcess = (0, execa_1.execa)("cmd.exe", [
|
|
289
|
+
"/c",
|
|
290
|
+
`${sdkManager} --licenses --sdk_root="${validSdk}"`,
|
|
291
|
+
]);
|
|
292
|
+
if (licenseProcess.stdin) {
|
|
293
|
+
licenseProcess.stdin.write(licenseAccept);
|
|
294
|
+
licenseProcess.stdin.end();
|
|
295
|
+
}
|
|
296
|
+
await licenseProcess;
|
|
297
|
+
console.log(chalk_1.default.green("[SUCCESS] SDK packages and licenses accepted successfully."));
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
console.log(chalk_1.default.yellow(`[WARNING] sdkmanager encountered an issue: ${e.message}`));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
process.env.ANDROID_HOME = validSdk;
|
|
304
|
+
process.env.ANDROID_SDK_ROOT = validSdk;
|
|
305
|
+
const tools = [
|
|
306
|
+
path_1.default.join(validSdk, "platform-tools"),
|
|
307
|
+
path_1.default.join(validSdk, "cmdline-tools", "latest", "bin"),
|
|
308
|
+
path_1.default.join(validSdk, "emulator"),
|
|
309
|
+
];
|
|
310
|
+
process.env.Path = `${tools.join(path_1.default.delimiter)}${path_1.default.delimiter}${process.env.Path || ""}`;
|
|
311
|
+
return validSdk;
|
|
312
|
+
}
|
|
313
|
+
async function detectProjectType() {
|
|
314
|
+
const cwd = process.cwd();
|
|
315
|
+
const pkgPath = path_1.default.join(cwd, "package.json");
|
|
316
|
+
const appJsonPath = path_1.default.join(cwd, "app.json");
|
|
317
|
+
const appConfigTs = path_1.default.join(cwd, "app.config.ts");
|
|
318
|
+
const appConfigJs = path_1.default.join(cwd, "app.config.js");
|
|
319
|
+
const androidDir = path_1.default.join(cwd, "android");
|
|
320
|
+
const hasAndroidDir = await fs_extra_1.default.pathExists(androidDir);
|
|
321
|
+
let hasExpoDep = false;
|
|
322
|
+
let hasRNDep = false;
|
|
323
|
+
if (await fs_extra_1.default.pathExists(pkgPath)) {
|
|
324
|
+
try {
|
|
325
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
326
|
+
hasExpoDep = Boolean(pkg.dependencies?.expo || pkg.devDependencies?.expo);
|
|
327
|
+
hasRNDep = Boolean(pkg.dependencies?.["react-native"] ||
|
|
328
|
+
pkg.devDependencies?.["react-native"]);
|
|
329
|
+
}
|
|
330
|
+
catch { }
|
|
331
|
+
}
|
|
332
|
+
let hasExpoAppJson = false;
|
|
333
|
+
if (await fs_extra_1.default.pathExists(appJsonPath)) {
|
|
334
|
+
try {
|
|
335
|
+
const appJson = await fs_extra_1.default.readJson(appJsonPath);
|
|
336
|
+
hasExpoAppJson = Boolean(appJson.expo);
|
|
337
|
+
}
|
|
338
|
+
catch { }
|
|
339
|
+
}
|
|
340
|
+
const hasExpoAppConfig = (await fs_extra_1.default.pathExists(appConfigTs)) || (await fs_extra_1.default.pathExists(appConfigJs));
|
|
341
|
+
const isExpo = hasExpoDep || hasExpoAppJson || hasExpoAppConfig;
|
|
342
|
+
if (isExpo) {
|
|
343
|
+
// Check if android folder is gitignored (managed workflow indicator)
|
|
344
|
+
let isGitIgnored = false;
|
|
345
|
+
const gitignorePath = path_1.default.join(cwd, ".gitignore");
|
|
346
|
+
if (await fs_extra_1.default.pathExists(gitignorePath)) {
|
|
347
|
+
try {
|
|
348
|
+
const gitignore = await fs_extra_1.default.readFile(gitignorePath, "utf8");
|
|
349
|
+
isGitIgnored = /^\s*\/?android\/?\s*$/m.test(gitignore);
|
|
350
|
+
}
|
|
351
|
+
catch { }
|
|
352
|
+
}
|
|
353
|
+
// If android folder does not exist or is gitignored, it is managed Expo
|
|
354
|
+
const isManaged = !hasAndroidDir || isGitIgnored;
|
|
355
|
+
return {
|
|
356
|
+
type: "expo",
|
|
357
|
+
isManaged,
|
|
358
|
+
hasAndroidDir,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (hasRNDep || hasAndroidDir) {
|
|
362
|
+
const hasAndroidGradle = (await fs_extra_1.default.pathExists(path_1.default.join(androidDir, "build.gradle"))) ||
|
|
363
|
+
(await fs_extra_1.default.pathExists(path_1.default.join(androidDir, "app", "build.gradle")));
|
|
364
|
+
if (hasRNDep || hasAndroidGradle) {
|
|
365
|
+
return {
|
|
366
|
+
type: "react-native",
|
|
367
|
+
isManaged: false,
|
|
368
|
+
hasAndroidDir,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
async function ensureProjectType() {
|
|
375
|
+
const info = await detectProjectType();
|
|
376
|
+
if (!info) {
|
|
377
|
+
console.error(chalk_1.default.red("Not inside an Expo or React Native project!"));
|
|
378
|
+
console.error(chalk_1.default.yellow("Please run this command from the root of an Expo or React Native project."));
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
if (info.type === "expo") {
|
|
382
|
+
console.log(chalk_1.default.green(`Project type: Expo (${info.isManaged ? "Managed" : "Bare"} workflow).`));
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
console.log(chalk_1.default.green("Project type: React Native (Native CLI)."));
|
|
386
|
+
}
|
|
387
|
+
return info;
|
|
388
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function generatePublishWorkflow(appPackageName: string): Promise<void>;
|
|
2
|
+
export declare function hasGitRemote(): Promise<boolean>;
|
|
3
|
+
export declare function isGhAuthenticated(): Promise<boolean>;
|
|
4
|
+
export declare function publishGitHubRelease(tag: string, assetPath: string, title: string, notes: string, profileName: string, prerelease?: boolean): Promise<boolean>;
|
|
5
|
+
export declare function triggerGitHubWorkflow(tag: string, track: string): Promise<void>;
|
|
@@ -0,0 +1,216 @@
|
|
|
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.generatePublishWorkflow = generatePublishWorkflow;
|
|
7
|
+
exports.hasGitRemote = hasGitRemote;
|
|
8
|
+
exports.isGhAuthenticated = isGhAuthenticated;
|
|
9
|
+
exports.publishGitHubRelease = publishGitHubRelease;
|
|
10
|
+
exports.triggerGitHubWorkflow = triggerGitHubWorkflow;
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const execa_1 = require("execa");
|
|
13
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
async function generatePublishWorkflow(appPackageName) {
|
|
16
|
+
console.log(chalk_1.default.cyan("Validating / Generating GitHub Actions deployment workflow..."));
|
|
17
|
+
const workflowDir = path_1.default.join(process.cwd(), ".github", "workflows");
|
|
18
|
+
const workflowPath = path_1.default.join(workflowDir, "publish.yml");
|
|
19
|
+
await fs_extra_1.default.ensureDir(workflowDir);
|
|
20
|
+
const expectedWorkflowContent = `name: Deploy Release to Play Store
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
inputs:
|
|
25
|
+
tag_name:
|
|
26
|
+
description: "Release Tag Name (e.g. v1.0.0)"
|
|
27
|
+
required: true
|
|
28
|
+
type: string
|
|
29
|
+
track:
|
|
30
|
+
description: "Play Store Track"
|
|
31
|
+
required: false
|
|
32
|
+
type: choice
|
|
33
|
+
options:
|
|
34
|
+
- internal
|
|
35
|
+
- alpha
|
|
36
|
+
- beta
|
|
37
|
+
- production
|
|
38
|
+
default: "alpha"
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
deploy:
|
|
42
|
+
name: Publish AAB to Play Store
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- name: 📥 Checkout repository
|
|
46
|
+
uses: actions/checkout@v6
|
|
47
|
+
|
|
48
|
+
- name: 📥 Download AAB from GitHub Release
|
|
49
|
+
env:
|
|
50
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
run: |
|
|
52
|
+
TAG="\${{ inputs.tag_name }}"
|
|
53
|
+
echo "Downloading assets for release tag: $TAG"
|
|
54
|
+
mkdir -p release-artifacts
|
|
55
|
+
gh release download "$TAG" --pattern "*.aab" --dir release-artifacts
|
|
56
|
+
ls -la release-artifacts
|
|
57
|
+
|
|
58
|
+
- name: 📝 Prepare Release Notes
|
|
59
|
+
env:
|
|
60
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
61
|
+
run: |
|
|
62
|
+
mkdir -p whatsnew
|
|
63
|
+
NOTES="$(gh release view "\${{ inputs.tag_name }}" --json body -q .body 2>/dev/null || echo "")"
|
|
64
|
+
if [ -z "$NOTES" ] || [ "$NOTES" == "null" ]; then
|
|
65
|
+
NOTES="We are constantly working to improve your experience. This update includes performance improvements, bug fixes, and enhancements to keep the app running smoothly."
|
|
66
|
+
fi
|
|
67
|
+
# Play Store max 500 chars; truncate if needed
|
|
68
|
+
NOTES=$(echo -e "$NOTES" | head -c 500)
|
|
69
|
+
echo "$NOTES" > whatsnew/whatsnew-en-US
|
|
70
|
+
echo "--- Release Notes ---"
|
|
71
|
+
cat whatsnew/whatsnew-en-US
|
|
72
|
+
|
|
73
|
+
- name: 📱 Upload to Google Play
|
|
74
|
+
uses: r0adkll/upload-google-play@v1
|
|
75
|
+
with:
|
|
76
|
+
serviceAccountJsonPlainText: \${{ secrets.PLAY_SERVICE_ACCOUNT }}
|
|
77
|
+
packageName: ${appPackageName}
|
|
78
|
+
releaseFiles: release-artifacts/*.aab
|
|
79
|
+
tracks: \${{ inputs.track || 'alpha' }}
|
|
80
|
+
status: completed
|
|
81
|
+
whatsNewDirectory: whatsnew
|
|
82
|
+
`;
|
|
83
|
+
let writeWorkflow = false;
|
|
84
|
+
if (!(await fs_extra_1.default.pathExists(workflowPath))) {
|
|
85
|
+
console.log(chalk_1.default.yellow(`GitHub Action workflow missing! Generating ${workflowPath}...`));
|
|
86
|
+
writeWorkflow = true;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const existingWorkflow = await fs_extra_1.default.readFile(workflowPath, "utf8");
|
|
90
|
+
if (!existingWorkflow.includes(`packageName: ${appPackageName}`)) {
|
|
91
|
+
console.log(chalk_1.default.yellow(`Warning: ${workflowPath} has incorrect packageName! Auto-correcting...`));
|
|
92
|
+
writeWorkflow = true;
|
|
93
|
+
}
|
|
94
|
+
else if (!existingWorkflow.includes("r0adkll/upload-google-play@v1")) {
|
|
95
|
+
console.log(chalk_1.default.yellow(`Warning: ${workflowPath} does not use standard upload action! Updating...`));
|
|
96
|
+
writeWorkflow = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (writeWorkflow) {
|
|
100
|
+
await fs_extra_1.default.writeFile(workflowPath, expectedWorkflowContent);
|
|
101
|
+
console.log(chalk_1.default.green(`Verified and updated GitHub Actions deployment workflow.`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(chalk_1.default.green(`GitHub Action workflow exists and is validated.`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function hasGitRemote() {
|
|
108
|
+
try {
|
|
109
|
+
const { stdout } = await (0, execa_1.execa)("git", ["remote"]);
|
|
110
|
+
return stdout.trim().length > 0;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function isGhAuthenticated() {
|
|
117
|
+
try {
|
|
118
|
+
const { stdout, stderr } = await (0, execa_1.execa)("gh", ["auth", "status"], {
|
|
119
|
+
reject: false,
|
|
120
|
+
});
|
|
121
|
+
const out = (stdout || "") + (stderr || "");
|
|
122
|
+
return (out.includes("Logged in to") ||
|
|
123
|
+
out.includes("Active account: true") ||
|
|
124
|
+
out.includes("account"));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function publishGitHubRelease(tag, assetPath, title, notes, profileName, prerelease = false) {
|
|
131
|
+
console.log(chalk_1.default.cyan(`\nStep 09: Publishing build artifact to GitHub Releases (${tag})${prerelease ? " [pre-release]" : ""}...`));
|
|
132
|
+
const hasRemote = await hasGitRemote();
|
|
133
|
+
if (!hasRemote) {
|
|
134
|
+
console.log(chalk_1.default.yellow("Note: No git remote found. Skipping GitHub Release publication."));
|
|
135
|
+
console.log(chalk_1.default.gray("To enable automated releases, connect this repository to GitHub ('git remote add origin <url>')."));
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const isAuth = await isGhAuthenticated();
|
|
139
|
+
if (!isAuth) {
|
|
140
|
+
console.log(chalk_1.default.yellow("Note: GitHub CLI is not logged in. Run 'gh auth login' to enable automated releases. Skipping release publication."));
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
console.log(chalk_1.default.gray(`Committing changes and pushing...`));
|
|
145
|
+
try {
|
|
146
|
+
await (0, execa_1.execa)("git", ["add", "."]);
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
149
|
+
try {
|
|
150
|
+
await (0, execa_1.execa)("git", [
|
|
151
|
+
"commit",
|
|
152
|
+
"-m",
|
|
153
|
+
`chore: release ${tag} (${profileName})`,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
catch { }
|
|
157
|
+
try {
|
|
158
|
+
await (0, execa_1.execa)("git", ["push"]);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
console.log(chalk_1.default.gray(`Note: git push skipped (${err.message ? err.message.split("\n")[0] : "no upstream"}).`));
|
|
162
|
+
}
|
|
163
|
+
let releaseExists = false;
|
|
164
|
+
try {
|
|
165
|
+
await (0, execa_1.execa)("gh", ["release", "view", tag]);
|
|
166
|
+
releaseExists = true;
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
if (releaseExists) {
|
|
170
|
+
console.log(chalk_1.default.yellow(`Release ${tag} already exists. Updating and uploading asset...`));
|
|
171
|
+
await (0, execa_1.execa)("gh", ["release", "upload", tag, assetPath, "--clobber"], {
|
|
172
|
+
stdio: "inherit",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log(chalk_1.default.yellow(`Preparing GitHub Release ${tag}${prerelease ? " (pre-release)" : ""}...`));
|
|
177
|
+
const createArgs = [
|
|
178
|
+
"release",
|
|
179
|
+
"create",
|
|
180
|
+
tag,
|
|
181
|
+
assetPath,
|
|
182
|
+
`--title=${title}`,
|
|
183
|
+
`--notes=${notes}`,
|
|
184
|
+
];
|
|
185
|
+
if (prerelease) {
|
|
186
|
+
createArgs.push("--prerelease");
|
|
187
|
+
}
|
|
188
|
+
await (0, execa_1.execa)("gh", createArgs, { stdio: "inherit" });
|
|
189
|
+
}
|
|
190
|
+
console.log(chalk_1.default.green(`Successfully published ${tag} to GitHub Releases!`));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.log(chalk_1.default.yellow(`Warning: Failed to publish to GitHub Releases: ${err.stderr || err.message}`));
|
|
195
|
+
console.log(chalk_1.default.yellow("Your build completed successfully and the artifact is saved locally."));
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function triggerGitHubWorkflow(tag, track) {
|
|
200
|
+
console.log(chalk_1.default.cyan(`\nTriggering Play Store deployment workflow for track '${track}'...`));
|
|
201
|
+
try {
|
|
202
|
+
await (0, execa_1.execa)("gh", [
|
|
203
|
+
"workflow",
|
|
204
|
+
"run",
|
|
205
|
+
"publish.yml",
|
|
206
|
+
"-f",
|
|
207
|
+
`tag_name=${tag}`,
|
|
208
|
+
"-f",
|
|
209
|
+
`track=${track}`,
|
|
210
|
+
]);
|
|
211
|
+
console.log(chalk_1.default.green("Play Store deployment triggered successfully! Check GitHub Actions tab for progress."));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
console.log(chalk_1.default.yellow("Note: Could not automatically trigger 'publish.yml'."));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function patchGradleProperties(): Promise<void>;
|
|
2
|
+
export declare function getGradleVersionCode(): Promise<number>;
|
|
3
|
+
export declare function syncBuildGradle(appVersion: string, versionCode: number): Promise<void>;
|
|
4
|
+
export declare function runGradleBuild(assembleTask: string): Promise<void>;
|