catalyst-core 1.1.0 → 1.2.0-canary.2
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/bin/catalyst.js +54 -6
- package/changelog.md +27 -1
- package/dist/errors/registry.js +306 -18
- package/dist/errors-index.json +273 -70
- package/dist/native/androidProject/app/build.gradle.kts +53 -79
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +3 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +32 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +5 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +25 -0
- package/dist/native/androidSetup.js +76 -170
- package/dist/native/androidToolchain.js +642 -0
- package/dist/native/appIcon.js +100 -0
- package/dist/native/buildAndroid/assets.js +29 -5
- package/dist/native/buildAndroid/build.js +110 -223
- package/dist/native/buildAndroid/config.js +57 -65
- package/dist/native/buildAndroid/devices.js +176 -0
- package/dist/native/buildAndroid/index.js +271 -46
- package/dist/native/buildAndroid/output.js +43 -0
- package/dist/native/buildAndroid/signing.js +456 -0
- package/dist/native/buildAppAndroid.js +27 -7
- package/dist/native/buildAppIos.js +139 -20
- package/dist/native/buildIos/assets.js +76 -4
- package/dist/native/buildIos/build.js +31 -181
- package/dist/native/buildIos/config.js +126 -11
- package/dist/native/buildIos/index.js +354 -34
- package/dist/native/buildIos/localhostCert.js +87 -0
- package/dist/native/buildIos/output.js +28 -0
- package/dist/native/buildIos/signing.js +596 -0
- package/dist/native/credentials.js +309 -0
- package/dist/native/exportCredentials.js +51 -0
- package/dist/native/iosToolchain.js +583 -0
- package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +1 -3
- package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +2 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/PrivacyInfo.xcprivacy +40 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +0 -2
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -4
- package/dist/native/localState.js +148 -0
- package/dist/native/nativeConfig.js +621 -0
- package/dist/native/releaseOutput.js +53 -0
- package/dist/native/renameAndroidProject.js +154 -369
- package/dist/native/setupEmulatorIos.js +58 -275
- package/dist/native/setupServer.js +520 -124
- package/dist/native/utils.js +418 -115
- package/dist/vite/resolveDevServerConfig.js +21 -0
- package/mcp_v2/knowledge-base.json +13 -11
- package/mcp_v2/test/config.test.ts +40 -0
- package/mcp_v2/tools/build.js +80 -104
- package/mcp_v2/tools/config.js +181 -174
- package/package.json +1 -1
- package/test/cli/nativeFlags.test.ts +97 -0
- package/test/native/androidDevices.test.ts +230 -0
- package/test/native/androidElfAlignment.test.ts +148 -0
- package/test/native/androidSigning.test.ts +646 -0
- package/test/native/androidToolchain.test.ts +766 -0
- package/test/native/appIcon.test.ts +124 -0
- package/test/native/credentials.test.ts +241 -0
- package/test/native/interaction.test.ts +179 -0
- package/test/native/iosConfigPhase.test.ts +611 -0
- package/test/native/iosSigning.test.ts +1060 -0
- package/test/native/iosToolchain.test.ts +821 -0
- package/test/native/localState.test.ts +80 -0
- package/test/native/nativeConfig.test.ts +602 -0
- package/test/native/setupServer.test.ts +529 -0
- package/test/native/utils.test.ts +102 -127
- package/test/vite/resolveDevServerConfig.test.js +25 -0
- package/dist/native/androidProject/keystore/Untitled +0 -0
- package/dist/native/androidProject/keystore/release-key.jks +0 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
|
@@ -52,6 +52,9 @@ class SimpleProgress {
|
|
|
52
52
|
const mark = type === "error" ? _theme.t.bad(_theme.glyph.fail) : type === "warning" ? _theme.t.warn(_theme.glyph.warn) : type === "success" ? _theme.t.ok(_theme.glyph.done) : _theme.t.dim(_theme.glyph.info);
|
|
53
53
|
console.log(`${_theme.GUTTER}${_theme.t.dim(_theme.glyph.pipe)} ${mark} ${message}`);
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
// Gradle's current task; buildApp:android shows it on the spinner's row.
|
|
57
|
+
status() {}
|
|
55
58
|
printTreeContent(title, items) {
|
|
56
59
|
this.ensureHeader();
|
|
57
60
|
console.log(`\n${_theme.GUTTER}${_theme.t.bold(title)}`);
|
|
@@ -67,238 +70,109 @@ class SimpleProgress {
|
|
|
67
70
|
console.log("");
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
const progress = new SimpleProgress("Android AAB Builder");
|
|
71
|
-
async function createKeystore(projectPaths, androidConfig) {
|
|
72
|
-
const {
|
|
73
|
-
newProjectPath
|
|
74
|
-
} = projectPaths;
|
|
75
|
-
const {
|
|
76
|
-
keystoreConfig
|
|
77
|
-
} = androidConfig;
|
|
78
|
-
if (!keystoreConfig) {
|
|
79
|
-
progress.log("Keystore configuration not found, skipping keystore creation", "info");
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
progress.log("Checking keystore configuration...", "info");
|
|
83
|
-
|
|
84
|
-
// Validate keystore passwords are not placeholder values
|
|
85
|
-
if (keystoreConfig.storePassword === "your_store_password" || keystoreConfig.keyPassword === "your_key_password" || keystoreConfig.storePassword === "your_password" || keystoreConfig.keyPassword === "your_password") {
|
|
86
|
-
throw new Error('Please update keystore passwords in config file. Do not use placeholder values like "your_store_password" or "your_key_password".');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Define keystore path
|
|
90
|
-
const keystorePath = _path.default.join(newProjectPath, "app", "keystore.jks");
|
|
91
|
-
|
|
92
|
-
// Check if keystore already exists
|
|
93
|
-
if (_fs.default.existsSync(keystorePath)) {
|
|
94
|
-
progress.log("Keystore already exists, verifying accessibility...", "info");
|
|
95
|
-
|
|
96
|
-
// Test keystore accessibility
|
|
97
|
-
try {
|
|
98
|
-
const testCommand = `keytool -list -keystore "${keystorePath}" -storepass "${keystoreConfig.storePassword}" -alias "${keystoreConfig.keyAlias}"`;
|
|
99
|
-
(0, _utils.runCommand)(testCommand);
|
|
100
|
-
progress.log("Existing keystore verified successfully", "success");
|
|
101
|
-
return keystorePath;
|
|
102
|
-
} catch (testError) {
|
|
103
|
-
progress.log("Existing keystore verification failed, creating new one...", "warning");
|
|
104
|
-
// Remove the faulty keystore
|
|
105
|
-
_fs.default.unlinkSync(keystorePath);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
progress.log("Creating new keystore...", "info");
|
|
109
|
-
try {
|
|
110
|
-
// Validate required keystore configuration
|
|
111
|
-
const requiredFields = ["keyAlias", "storePassword", "keyPassword", "organizationInfo"];
|
|
112
|
-
for (const field of requiredFields) {
|
|
113
|
-
if (!keystoreConfig[field]) {
|
|
114
|
-
throw new Error(`Missing required keystore config field: ${field}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const {
|
|
118
|
-
organizationInfo
|
|
119
|
-
} = keystoreConfig;
|
|
120
|
-
const requiredOrgFields = ["companyName", "city", "state", "countryCode"];
|
|
121
|
-
for (const field of requiredOrgFields) {
|
|
122
|
-
if (!organizationInfo[field]) {
|
|
123
|
-
throw new Error(`Missing required organization info field: ${field}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Create keystore directory if it doesn't exist
|
|
128
|
-
const keystoreDir = _path.default.dirname(keystorePath);
|
|
129
|
-
if (!_fs.default.existsSync(keystoreDir)) {
|
|
130
|
-
_fs.default.mkdirSync(keystoreDir, {
|
|
131
|
-
recursive: true
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
73
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
74
|
+
// Run on its own, this script prints every step and detail line itself.
|
|
75
|
+
// buildApp:android passes its progress instead (buildAndroidAAB's `progress`
|
|
76
|
+
// option): the steps and details then ride on its "Build signed AAB" spinner
|
|
77
|
+
// like the rest of buildApp, printed as lines only with --verbose/--debug,
|
|
78
|
+
// while warnings and errors still print.
|
|
79
|
+
let progress = new SimpleProgress("Android AAB Builder");
|
|
80
|
+
const STEP_DETAILS = {
|
|
81
|
+
config: "reading the configuration",
|
|
82
|
+
validation: "checking the Android project",
|
|
83
|
+
backup: "backing up the project",
|
|
84
|
+
createTempDeployment: "copying the project to a build folder",
|
|
85
|
+
updateFileContents: "applying the app id and name",
|
|
86
|
+
createSignedAAB: "running Gradle",
|
|
87
|
+
finalizeAAB: "saving the AAB"
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* This script's progress calls, reported through a TerminalProgress that
|
|
92
|
+
* already has a step running. `info` and `success` become spinner detail
|
|
93
|
+
* (lines under --verbose), warnings and errors print. No summary box and no
|
|
94
|
+
* failure line: the caller prints its own summary and diagnostic.
|
|
95
|
+
*/
|
|
96
|
+
function nestedProgress(outer) {
|
|
97
|
+
return {
|
|
98
|
+
currentStep: null,
|
|
99
|
+
start(stepId) {
|
|
100
|
+
this.currentStep = {
|
|
101
|
+
id: stepId
|
|
102
|
+
};
|
|
103
|
+
outer.log(STEP_DETAILS[stepId] || stepId, "info");
|
|
104
|
+
},
|
|
105
|
+
complete() {
|
|
106
|
+
this.currentStep = null;
|
|
107
|
+
},
|
|
108
|
+
fail() {
|
|
109
|
+
this.currentStep = null;
|
|
110
|
+
},
|
|
111
|
+
log(message, type = "info") {
|
|
112
|
+
outer.log(message, type);
|
|
113
|
+
},
|
|
114
|
+
status(detail) {
|
|
115
|
+
outer.status(detail);
|
|
116
|
+
},
|
|
117
|
+
printTreeContent() {}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
155
120
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
progress.log("New keystore verified successfully", "success");
|
|
161
|
-
} catch (testError) {
|
|
162
|
-
throw new Error(`Created keystore failed verification: ${testError.message}`);
|
|
163
|
-
}
|
|
164
|
-
progress.log(`Keystore created at: ${keystorePath}`, "note");
|
|
165
|
-
progress.log(`Key alias: ${keystoreConfig.keyAlias}`, "info");
|
|
166
|
-
progress.log(`Validity: ${keystoreConfig.validityYears || 25} years`, "info");
|
|
167
|
-
progress.log(`Store type: PKCS12`, "info");
|
|
168
|
-
return keystorePath;
|
|
169
|
-
} catch (error) {
|
|
170
|
-
throw new Error(`Failed to create keystore: ${error.message}`);
|
|
171
|
-
}
|
|
121
|
+
/** "> Task :app:bundleRelease" becomes "app:bundleRelease"; null otherwise. */
|
|
122
|
+
function gradleTask(line) {
|
|
123
|
+
const match = /^>\s*Task\s+:(\S+)/.exec(String(line).trim());
|
|
124
|
+
return match ? match[1] : null;
|
|
172
125
|
}
|
|
173
|
-
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate the release webview config and build the signed bundle inside the
|
|
129
|
+
* temp copy. Two Gradle runs: the manifest's allowBackup placeholder is read
|
|
130
|
+
* from webview_config.properties while Gradle configures the project, so the
|
|
131
|
+
* file must exist before bundleRelease is configured.
|
|
132
|
+
*
|
|
133
|
+
* Signing and version values arrive in `gradle.env` as ORG_GRADLE_PROJECT_*
|
|
134
|
+
* variables; nothing secret is put on the command line.
|
|
135
|
+
*/
|
|
136
|
+
async function createSignedAAB(projectPaths, androidConfig) {
|
|
174
137
|
const {
|
|
175
138
|
newProjectPath
|
|
176
139
|
} = projectPaths;
|
|
177
140
|
const {
|
|
178
|
-
|
|
141
|
+
gradle
|
|
179
142
|
} = androidConfig;
|
|
180
|
-
if (!
|
|
181
|
-
throw new Error("
|
|
143
|
+
if (!gradle || !gradle.configPath || !gradle.env) {
|
|
144
|
+
throw new Error("A resolved config and upload key are required to build the signed AAB");
|
|
182
145
|
}
|
|
183
146
|
progress.log("Building signed Android App Bundle (AAB)...", "info");
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
progress.log(`Could not list directory contents: ${listError.message}`, "warning");
|
|
214
|
-
}
|
|
215
|
-
throw new Error(`gradlew not found at: ${gradlewPath}. Ensure this is a valid Android project.`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Make gradlew executable
|
|
219
|
-
try {
|
|
220
|
-
;
|
|
221
|
-
(0, _utils.runCommand)(`chmod +x ./gradlew`);
|
|
222
|
-
} catch (chmodError) {
|
|
223
|
-
progress.log(`Warning: Could not make gradlew executable: ${chmodError.message}`, "warning");
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
// Clean previous builds
|
|
227
|
-
progress.log("Cleaning previous builds...", "info");
|
|
228
|
-
try {
|
|
229
|
-
;
|
|
230
|
-
(0, _utils.runCommand)("./gradlew clean");
|
|
231
|
-
progress.log("Clean completed successfully", "success");
|
|
232
|
-
} catch (cleanError) {
|
|
233
|
-
progress.log(`Warning: Clean failed: ${cleanError.message}`, "warning");
|
|
234
|
-
// Continue anyway as this might not be critical
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Build signed AAB with PKCS12-compatible parameters
|
|
238
|
-
progress.log("Building signed AAB...", "info");
|
|
239
|
-
|
|
240
|
-
// For PKCS12 keystores, use store password for both store and key
|
|
241
|
-
const effectiveKeyPassword = keystoreConfig.storePassword;
|
|
242
|
-
const bundleCommand = ["./gradlew bundleRelease --console=plain", `-Pandroid.injected.signing.store.file="${keystorePath}"`, `-Pandroid.injected.signing.store.password="${keystoreConfig.storePassword}"`, `-Pandroid.injected.signing.key.alias="${keystoreConfig.keyAlias}"`, `-Pandroid.injected.signing.key.password="${effectiveKeyPassword}"`].join(" ");
|
|
243
|
-
|
|
244
|
-
// Never log this command as-is: it carries the keystore and key
|
|
245
|
-
// passwords, which would put them in the terminal and in CI logs.
|
|
246
|
-
progress.log("Executing: ./gradlew bundleRelease (signing args redacted)", "info");
|
|
247
|
-
(0, _utils.runCommand)(bundleCommand);
|
|
248
|
-
progress.log("Bundle build completed", "success");
|
|
249
|
-
|
|
250
|
-
// Find the generated AAB file
|
|
251
|
-
const aabPath = _path.default.join(newProjectPath, "app", "build", "outputs", "bundle", "release", "app-release.aab");
|
|
252
|
-
progress.log(`Looking for AAB file at: ${aabPath}`, "info");
|
|
253
|
-
if (!_fs.default.existsSync(aabPath)) {
|
|
254
|
-
// Try to find AAB files in the build output directory
|
|
255
|
-
const bundleDir = _path.default.join(newProjectPath, "app", "build", "outputs", "bundle");
|
|
256
|
-
progress.log(`AAB not found at expected location. Checking bundle directory: ${bundleDir}`, "warning");
|
|
257
|
-
if (_fs.default.existsSync(bundleDir)) {
|
|
258
|
-
try {
|
|
259
|
-
const findAabCommand = `find "${bundleDir}" -name "*.aab" -type f`;
|
|
260
|
-
const foundAabs = (0, _utils.runCommand)(findAabCommand);
|
|
261
|
-
if (foundAabs.trim()) {
|
|
262
|
-
progress.log(`Found AAB files: ${foundAabs.trim()}`, "info");
|
|
263
|
-
} else {
|
|
264
|
-
progress.log("No AAB files found in bundle directory", "warning");
|
|
265
|
-
}
|
|
266
|
-
} catch (findError) {
|
|
267
|
-
progress.log(`Error searching for AAB files: ${findError.message}`, "warning");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
throw new Error("Signed AAB file was not generated successfully");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Get file size
|
|
274
|
-
const stats = _fs.default.statSync(aabPath);
|
|
275
|
-
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
276
|
-
progress.log(`Signed AAB created successfully!`, "success");
|
|
277
|
-
progress.log(`AAB location: ${aabPath}`, "info");
|
|
278
|
-
progress.log(`AAB size: ${fileSizeMB} MB`, "info");
|
|
279
|
-
|
|
280
|
-
// Optionally copy AAB to a more accessible location
|
|
281
|
-
const outputDir = _path.default.join(newProjectPath, "release");
|
|
282
|
-
if (!_fs.default.existsSync(outputDir)) {
|
|
283
|
-
_fs.default.mkdirSync(outputDir, {
|
|
284
|
-
recursive: true
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
const outputAabPath = _path.default.join(outputDir, `${androidConfig.newProjectName}-release.aab`);
|
|
288
|
-
_fs.default.copyFileSync(aabPath, outputAabPath);
|
|
289
|
-
progress.log(`AAB copied to: ${outputAabPath}`, "success");
|
|
290
|
-
return {
|
|
291
|
-
aabPath,
|
|
292
|
-
outputAabPath,
|
|
293
|
-
fileSize: fileSizeMB
|
|
294
|
-
};
|
|
295
|
-
} finally {
|
|
296
|
-
// Restore original working directory
|
|
297
|
-
process.chdir(originalCwd);
|
|
298
|
-
}
|
|
299
|
-
} catch (error) {
|
|
300
|
-
throw new Error(`Failed to create signed AAB: ${error.message}`);
|
|
301
|
-
}
|
|
147
|
+
const absoluteProjectPath = _path.default.resolve(newProjectPath);
|
|
148
|
+
const gradlewPath = _path.default.join(absoluteProjectPath, "gradlew");
|
|
149
|
+
if (!_fs.default.existsSync(gradlewPath)) {
|
|
150
|
+
throw new Error(`gradlew not found at: ${gradlewPath}. Ensure this is a valid Android project.`);
|
|
151
|
+
}
|
|
152
|
+
_fs.default.chmodSync(gradlewPath, 0o755);
|
|
153
|
+
const common = [`-PconfigPath=${gradle.configPath}`, "-PcatalystBuildType=release", "--console=plain"];
|
|
154
|
+
const run = args => (0, _utils.runInteractiveCommand)(gradlewPath, args, {}, {
|
|
155
|
+
env: gradle.env,
|
|
156
|
+
cwd: absoluteProjectPath,
|
|
157
|
+
onLine: line => progress.status(gradleTask(line))
|
|
158
|
+
});
|
|
159
|
+
progress.log("Generating the release webview configuration...", "info");
|
|
160
|
+
await run(["generateWebViewConfig", `-PbuildOptimisation=${Boolean(gradle.buildOptimisation)}`, ...common]);
|
|
161
|
+
progress.log("Building signed AAB (clean bundleRelease)...", "info");
|
|
162
|
+
await run(["clean", "bundleRelease", ...common]);
|
|
163
|
+
progress.log("Bundle build completed", "success");
|
|
164
|
+
const aabPath = _path.default.join(newProjectPath, "app", "build", "outputs", "bundle", "release", "app-release.aab");
|
|
165
|
+
if (!_fs.default.existsSync(aabPath)) {
|
|
166
|
+
throw new Error(`Signed AAB file was not generated at ${aabPath}`);
|
|
167
|
+
}
|
|
168
|
+
const stats = _fs.default.statSync(aabPath);
|
|
169
|
+
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
170
|
+
progress.log(`Signed AAB created (${fileSizeMB} MB)`, "success");
|
|
171
|
+
return {
|
|
172
|
+
aabPath,
|
|
173
|
+
outputAabPath: aabPath,
|
|
174
|
+
fileSize: fileSizeMB
|
|
175
|
+
};
|
|
302
176
|
}
|
|
303
177
|
async function initializeConfig(configPath) {
|
|
304
178
|
if (!configPath) {
|
|
@@ -539,7 +413,8 @@ async function createTempDeploymentProject(projectPaths, androidConfig) {
|
|
|
539
413
|
progress.log("Creating temporary deployment project...", "info");
|
|
540
414
|
|
|
541
415
|
// Create a temporary directory based on package name (if available) or timestamp
|
|
542
|
-
|
|
416
|
+
// Only [A-Za-z0-9_] in the name: the path reaches shell commands below.
|
|
417
|
+
const tempDirSuffix = packageName ? packageName.replace(/[^A-Za-z0-9_]/g, "_") : Date.now().toString();
|
|
543
418
|
const tempDir = _path.default.join(_path.default.dirname(oldProjectPath), `temp_build_${tempDirSuffix}`);
|
|
544
419
|
|
|
545
420
|
// Remove existing temp directory with same name if it exists
|
|
@@ -604,10 +479,13 @@ async function createTempDeploymentProject(projectPaths, androidConfig) {
|
|
|
604
479
|
path: "app/src/main/AndroidManifest.xml",
|
|
605
480
|
desc: "Android manifest"
|
|
606
481
|
}];
|
|
482
|
+
|
|
483
|
+
// Both the Groovy and the Kotlin DSL names are listed, so one of each
|
|
484
|
+
// pair is always absent: info, not a warning.
|
|
607
485
|
filesToCheck.forEach(file => {
|
|
608
486
|
const fullPath = _path.default.join(tempDir, file.path);
|
|
609
487
|
const exists = _fs.default.existsSync(fullPath);
|
|
610
|
-
progress.log(` ${file.desc}: ${exists ? "found" : "not found"}`,
|
|
488
|
+
progress.log(` ${file.desc}: ${exists ? "found" : "not found"}`, "info");
|
|
611
489
|
});
|
|
612
490
|
|
|
613
491
|
// List all files in temp directory for debugging
|
|
@@ -637,8 +515,8 @@ async function createTempDeploymentProject(projectPaths, androidConfig) {
|
|
|
637
515
|
for (const dir of dirsToRename) {
|
|
638
516
|
const newDirName = dir.split(oldProjectName).join(newProjectName);
|
|
639
517
|
if (dir !== newDirName) {
|
|
640
|
-
|
|
641
|
-
|
|
518
|
+
// fs, not a shell: newProjectName comes from the app's config.
|
|
519
|
+
_fs.default.renameSync(dir, newDirName);
|
|
642
520
|
progress.log(`Renamed subdirectory: ${_path.default.basename(dir)} → ${_path.default.basename(newDirName)}`, "success");
|
|
643
521
|
}
|
|
644
522
|
}
|
|
@@ -655,29 +533,21 @@ async function finalizeAABAndCleanup(projectPaths, androidConfig, aabResult, tem
|
|
|
655
533
|
} = projectPaths;
|
|
656
534
|
progress.log("Finalizing AAB and cleaning up temporary files...", "info");
|
|
657
535
|
try {
|
|
658
|
-
//
|
|
659
|
-
|
|
536
|
+
// buildApp:android passes the exact destination
|
|
537
|
+
// (build-output/android/<version>-<build>/<name>.aab). Run on its
|
|
538
|
+
// own, this script keeps its dated name in the output folder.
|
|
539
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T")[0];
|
|
540
|
+
const finalAabPath = androidConfig.finalAabPath || _path.default.join(androidConfig.outputPath || _path.default.join(_path.default.dirname(oldProjectPath), "build-output"), `${androidConfig.newProjectName}-release-${timestamp}.aab`);
|
|
541
|
+
const outputDir = _path.default.dirname(finalAabPath);
|
|
542
|
+
const finalAabName = _path.default.basename(finalAabPath);
|
|
660
543
|
if (!_fs.default.existsSync(outputDir)) {
|
|
661
544
|
_fs.default.mkdirSync(outputDir, {
|
|
662
545
|
recursive: true
|
|
663
546
|
});
|
|
664
547
|
progress.log(`Created output directory: ${outputDir}`, "success");
|
|
665
548
|
}
|
|
666
|
-
|
|
667
|
-
// Copy AAB to final location with descriptive name
|
|
668
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T")[0];
|
|
669
|
-
const finalAabName = `${androidConfig.newProjectName}-release-${timestamp}.aab`;
|
|
670
|
-
const finalAabPath = _path.default.join(outputDir, finalAabName);
|
|
671
549
|
_fs.default.copyFileSync(aabResult.outputAabPath, finalAabPath);
|
|
672
|
-
progress.log(`AAB saved to: ${finalAabPath}`, "
|
|
673
|
-
|
|
674
|
-
// Copy keystore to output directory for future use
|
|
675
|
-
const keystorePath = _path.default.join(tempDeploymentPath, "app", "keystore.jks");
|
|
676
|
-
if (_fs.default.existsSync(keystorePath)) {
|
|
677
|
-
const finalKeystorePath = _path.default.join(outputDir, `${androidConfig.newProjectName}-keystore.jks`);
|
|
678
|
-
_fs.default.copyFileSync(keystorePath, finalKeystorePath);
|
|
679
|
-
progress.log(`Keystore copied to: ${finalKeystorePath}`, "success");
|
|
680
|
-
}
|
|
550
|
+
progress.log(`AAB saved to: ${finalAabPath}`, "success");
|
|
681
551
|
|
|
682
552
|
// Clean up temporary deployment directory
|
|
683
553
|
await cleanupTempDeployment(tempDeploymentPath);
|
|
@@ -743,19 +613,21 @@ async function updateFileContents(projectPaths, androidConfig) {
|
|
|
743
613
|
const newPackageDir = _path.default.dirname(newPackagePath);
|
|
744
614
|
_fs.default.mkdirSync(newPackageDir, {
|
|
745
615
|
recursive: true
|
|
746
|
-
})
|
|
616
|
+
});
|
|
747
617
|
|
|
748
618
|
// Move source files
|
|
749
|
-
|
|
750
|
-
|
|
619
|
+
// fs, not a shell: the path is built from the app id.
|
|
620
|
+
_fs.default.renameSync(oldPackagePath, newPackagePath);
|
|
751
621
|
progress.log(`Moved ${sourceSet} source files to: ${newPackagePath}`, "success");
|
|
752
622
|
} else {
|
|
753
623
|
progress.log(`Package path unchanged for ${sourceSet}; skipping source directory move`, "info");
|
|
754
624
|
}
|
|
755
625
|
|
|
756
626
|
// Update package declarations in source files
|
|
757
|
-
const
|
|
758
|
-
|
|
627
|
+
const sourceFiles = _fs.default.readdirSync(newPackagePath, {
|
|
628
|
+
recursive: true,
|
|
629
|
+
withFileTypes: true
|
|
630
|
+
}).filter(entry => entry.isFile() && /\.(kt|java)$/.test(entry.name)).map(entry => _path.default.join(entry.parentPath || entry.path, entry.name));
|
|
759
631
|
let updatedCount = 0;
|
|
760
632
|
for (const file of sourceFiles) {
|
|
761
633
|
let content = _fs.default.readFileSync(file, "utf8");
|
|
@@ -812,8 +684,7 @@ async function updateFileContents(projectPaths, androidConfig) {
|
|
|
812
684
|
for (const file of filesToRename) {
|
|
813
685
|
const newFileName = file.split(oldProjectName).join(newProjectName);
|
|
814
686
|
if (file !== newFileName) {
|
|
815
|
-
;
|
|
816
|
-
(0, _utils.runCommand)(`mv "${file}" "${newFileName}"`);
|
|
687
|
+
_fs.default.renameSync(file, newFileName);
|
|
817
688
|
progress.log(`Renamed file: ${_path.default.basename(file)} → ${_path.default.basename(newFileName)}`, "success");
|
|
818
689
|
}
|
|
819
690
|
}
|
|
@@ -850,7 +721,26 @@ async function updateFileContents(projectPaths, androidConfig) {
|
|
|
850
721
|
throw new Error(`Error updating file contents: ${error.message}`);
|
|
851
722
|
}
|
|
852
723
|
}
|
|
853
|
-
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Build the signed AAB in a renamed temp copy of the Android project.
|
|
727
|
+
*
|
|
728
|
+
* @param {string | object} configPathOrConfig - a config.json path, or the config object buildApp:android builds
|
|
729
|
+
* @param {object} [options]
|
|
730
|
+
* @param {object} [options.progress] - the caller's TerminalProgress, with a step running; see nestedProgress
|
|
731
|
+
*/
|
|
732
|
+
async function buildAndroidAAB(configPathOrConfig, {
|
|
733
|
+
progress: outer
|
|
734
|
+
} = {}) {
|
|
735
|
+
const previous = progress;
|
|
736
|
+
if (outer) progress = nestedProgress(outer);
|
|
737
|
+
try {
|
|
738
|
+
return await runAABBuild(configPathOrConfig);
|
|
739
|
+
} finally {
|
|
740
|
+
progress = previous;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async function runAABBuild(configPathOrConfig) {
|
|
854
744
|
let androidConfig;
|
|
855
745
|
let projectPaths;
|
|
856
746
|
let backupPath;
|
|
@@ -909,44 +799,19 @@ async function buildAndroidAAB(configPathOrConfig) {
|
|
|
909
799
|
newProjectPath: tempDeploymentPath
|
|
910
800
|
}, androidConfig);
|
|
911
801
|
progress.complete("updateFileContents");
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
progress.start("createKeystore");
|
|
915
|
-
const keystorePath = await createKeystore({
|
|
802
|
+
progress.start("createSignedAAB");
|
|
803
|
+
let aabResult = await createSignedAAB({
|
|
916
804
|
...projectPaths,
|
|
917
805
|
newProjectPath: tempDeploymentPath
|
|
918
806
|
}, androidConfig);
|
|
919
|
-
progress.complete("
|
|
920
|
-
|
|
921
|
-
// Create signed AAB if keystore was created/found and enabled in config
|
|
922
|
-
let aabResult = null;
|
|
923
|
-
if (keystorePath && androidConfig.createSignedAAB) {
|
|
924
|
-
progress.start("createSignedAAB");
|
|
925
|
-
aabResult = await createSignedAAB({
|
|
926
|
-
...projectPaths,
|
|
927
|
-
newProjectPath: tempDeploymentPath
|
|
928
|
-
}, androidConfig, keystorePath);
|
|
929
|
-
progress.complete("createSignedAAB");
|
|
807
|
+
progress.complete("createSignedAAB");
|
|
930
808
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
progress.complete("finalizeAAB");
|
|
936
|
-
}
|
|
937
|
-
} else if (!androidConfig.createSignedAAB) {
|
|
938
|
-
progress.log("Signed AAB creation skipped (not enabled in config)", "info");
|
|
939
|
-
// DON'T Clean up temp deployment - keep it for verification
|
|
940
|
-
progress.log(`Temp renamed project kept at: ${tempDeploymentPath}`, "info");
|
|
941
|
-
// await cleanupTempDeployment(tempDeploymentPath)
|
|
942
|
-
} else if (!keystorePath) {
|
|
943
|
-
progress.log("Signed AAB creation skipped (no keystore available)", "warning");
|
|
944
|
-
// DON'T Clean up temp deployment - keep it for verification
|
|
945
|
-
progress.log(`Temp renamed project kept at: ${tempDeploymentPath}`, "info");
|
|
946
|
-
// await cleanupTempDeployment(tempDeploymentPath)
|
|
947
|
-
}
|
|
809
|
+
// Copy AAB to final location and cleanup temp deployment
|
|
810
|
+
progress.start("finalizeAAB");
|
|
811
|
+
aabResult = await finalizeAABAndCleanup(projectPaths, androidConfig, aabResult, tempDeploymentPath);
|
|
812
|
+
progress.complete("finalizeAAB");
|
|
948
813
|
|
|
949
|
-
//
|
|
814
|
+
// Completion summary, standalone only: buildApp:android prints its own.
|
|
950
815
|
progress.printTreeContent("Build Summary", ["Android AAB build completed successfully:", {
|
|
951
816
|
text: `Original project: ${projectPaths.oldProjectPath}`,
|
|
952
817
|
indent: 1,
|
|
@@ -972,11 +837,6 @@ async function buildAndroidAAB(configPathOrConfig) {
|
|
|
972
837
|
indent: 1,
|
|
973
838
|
prefix: "├─ ",
|
|
974
839
|
color: "gray"
|
|
975
|
-
}, {
|
|
976
|
-
text: `Keystore: ${keystorePath ? "Created/Verified" : "Skipped"}`,
|
|
977
|
-
indent: 1,
|
|
978
|
-
prefix: "├─ ",
|
|
979
|
-
color: "gray"
|
|
980
840
|
}, {
|
|
981
841
|
text: `Signed AAB: ${aabResult ? "Created" : "Skipped"}`,
|
|
982
842
|
indent: 1,
|
|
@@ -1008,92 +868,17 @@ async function buildAndroidAAB(configPathOrConfig) {
|
|
|
1008
868
|
prefix: "└─ ",
|
|
1009
869
|
color: "gray"
|
|
1010
870
|
}])]);
|
|
1011
|
-
|
|
871
|
+
return aabResult;
|
|
1012
872
|
} catch (error) {
|
|
1013
873
|
// Clean up temp deployment on error
|
|
1014
874
|
if (tempDeploymentPath) {
|
|
1015
875
|
await cleanupTempDeployment(tempDeploymentPath);
|
|
1016
876
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
prefix: "├─ ",
|
|
1023
|
-
color: "yellow"
|
|
1024
|
-
}, {
|
|
1025
|
-
text: "Ensure project path exists and is accessible",
|
|
1026
|
-
indent: 1,
|
|
1027
|
-
prefix: "├─ ",
|
|
1028
|
-
color: "yellow"
|
|
1029
|
-
}, {
|
|
1030
|
-
text: "Check file/directory permissions",
|
|
1031
|
-
indent: 1,
|
|
1032
|
-
prefix: "├─ ",
|
|
1033
|
-
color: "yellow"
|
|
1034
|
-
}, {
|
|
1035
|
-
text: "Verify no processes are using the project directory",
|
|
1036
|
-
indent: 1,
|
|
1037
|
-
prefix: "├─ ",
|
|
1038
|
-
color: "yellow"
|
|
1039
|
-
}, {
|
|
1040
|
-
text: "Ensure sufficient disk space for project copy",
|
|
1041
|
-
indent: 1,
|
|
1042
|
-
prefix: "├─ ",
|
|
1043
|
-
color: "yellow"
|
|
1044
|
-
}, {
|
|
1045
|
-
text: 'Update keystore passwords (do not use "your_store_password")',
|
|
1046
|
-
indent: 1,
|
|
1047
|
-
prefix: "└─ ",
|
|
1048
|
-
color: "yellow"
|
|
1049
|
-
}, "\nConfiguration Details:", {
|
|
1050
|
-
text: `Config: ${typeof configPathOrConfig === "string" ? configPathOrConfig : "Direct object from buildAppAndroid.js"}`,
|
|
1051
|
-
indent: 1,
|
|
1052
|
-
prefix: "├─ ",
|
|
1053
|
-
color: "gray"
|
|
1054
|
-
}, {
|
|
1055
|
-
text: `Project path: ${androidConfig?.projectPath || "Not loaded"}`,
|
|
1056
|
-
indent: 1,
|
|
1057
|
-
prefix: "├─ ",
|
|
1058
|
-
color: "gray"
|
|
1059
|
-
}, {
|
|
1060
|
-
text: `Deployment path: ${androidConfig?.deploymentPath || "Not loaded"}`,
|
|
1061
|
-
indent: 1,
|
|
1062
|
-
prefix: "├─ ",
|
|
1063
|
-
color: "gray"
|
|
1064
|
-
}, {
|
|
1065
|
-
text: `Old project name: ${androidConfig?.oldProjectName || "Not loaded"}`,
|
|
1066
|
-
indent: 1,
|
|
1067
|
-
prefix: "├─ ",
|
|
1068
|
-
color: "gray"
|
|
1069
|
-
}, {
|
|
1070
|
-
text: `New project name: ${androidConfig?.newProjectName || "Not loaded"}`,
|
|
1071
|
-
indent: 1,
|
|
1072
|
-
prefix: "├─ ",
|
|
1073
|
-
color: "gray"
|
|
1074
|
-
}, {
|
|
1075
|
-
text: `Overwrite existing: ${androidConfig?.overwriteExisting !== undefined ? androidConfig.overwriteExisting : "Not loaded"}`,
|
|
1076
|
-
indent: 1,
|
|
1077
|
-
prefix: "└─ ",
|
|
1078
|
-
color: "gray"
|
|
1079
|
-
}, "\nFor keystore issues, ensure your config has:", {
|
|
1080
|
-
text: "Real passwords (not placeholder values)",
|
|
1081
|
-
indent: 1,
|
|
1082
|
-
prefix: "├─ ",
|
|
1083
|
-
color: "cyan"
|
|
1084
|
-
}, {
|
|
1085
|
-
text: "Valid organization information",
|
|
1086
|
-
indent: 1,
|
|
1087
|
-
prefix: "├─ ",
|
|
1088
|
-
color: "cyan"
|
|
1089
|
-
}, {
|
|
1090
|
-
text: "Consistent store and key passwords for PKCS12",
|
|
1091
|
-
indent: 1,
|
|
1092
|
-
prefix: "└─ ",
|
|
1093
|
-
color: "cyan"
|
|
1094
|
-
}]);
|
|
1095
|
-
}
|
|
1096
|
-
process.exit(1);
|
|
877
|
+
|
|
878
|
+
// The caller (buildApp:android) prints the full diagnostic, including
|
|
879
|
+
// Gradle's own error, from `error.output`.
|
|
880
|
+
if (progress.currentStep) progress.fail(progress.currentStep.id, error.message);
|
|
881
|
+
throw error;
|
|
1097
882
|
}
|
|
1098
883
|
}
|
|
1099
884
|
|