@capacitor/cli 9.0.0-alpha.6 → 9.0.0-alpha.7

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,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__testables = exports.migrateToSwiftUI = void 0;
4
+ const fs_extra_1 = require("fs-extra");
5
+ const path_1 = require("path");
6
+ const common_1 = require("../common");
7
+ const log_1 = require("../log");
8
+ const fs_1 = require("../util/fs");
9
+ const spm_1 = require("../util/spm");
10
+ const template_1 = require("../util/template");
11
+ const xcode_1 = require("../util/xcode");
12
+ const OPEN_URL_SIG = /func application\([^)]*\bopen url:/;
13
+ const CONTINUE_SIG = /func application\([^)]*\bcontinue userActivity:/;
14
+ const CONFIGURATION_FOR_CONNECTING_SIG = /func application\([^)]*\bconfigurationForConnecting\b/;
15
+ async function migrateToSwiftUI(config) {
16
+ const signals = readDetectionSignals(config);
17
+ const state = classify(signals);
18
+ switch (state) {
19
+ case 'already-migrated':
20
+ log_1.logger.info('SwiftUI migration: project already uses the SwiftUI App-struct layout, skipping.');
21
+ return;
22
+ case 'partial':
23
+ log_1.logger.warn(`SwiftUI migration: project is in a partial state (${describeSignals(signals)}). ` +
24
+ `Skipping automated migration — finish the migration by hand or reset the iOS project to a clean 8.4/8.5 state first.`);
25
+ return;
26
+ case 'eligible':
27
+ break;
28
+ }
29
+ const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
30
+ const rewrite = planAppDelegateRewrite(appDelegatePath);
31
+ if (rewrite.status === 'skipped') {
32
+ log_1.logger.warn(`SwiftUI migration: skipping automated migration — ${rewrite.reason}`);
33
+ printManualSteps();
34
+ return;
35
+ }
36
+ const assets = await loadTemplateAssets(config);
37
+ if (!assets) {
38
+ log_1.logger.error('SwiftUI migration: could not read shipped iOS template assets; skipping.');
39
+ return;
40
+ }
41
+ await (0, common_1.runTask)('Pointing Info.plist at the SwiftUI scene setup.', async () => {
42
+ const result = await (0, spm_1.setSwiftUISceneManifest)(config);
43
+ if (result.status === 'skipped') {
44
+ log_1.logger.warn(`Info.plist not updated: ${result.reason}`);
45
+ }
46
+ });
47
+ await (0, common_1.runTask)('Writing App.swift and CapacitorView.swift.', async () => {
48
+ const files = [
49
+ ['App.swift', assets.app],
50
+ ['CapacitorView.swift', assets.capacitorView],
51
+ ];
52
+ for (const [name, contents] of files) {
53
+ if (!writeAppTargetFile(config, name, contents)) {
54
+ log_1.logger.warn(`${name} already exists, skipping.`);
55
+ }
56
+ }
57
+ });
58
+ await (0, common_1.runTask)('Converting AppDelegate.swift into a UIApplicationDelegateAdaptor.', async () => {
59
+ (0, fs_extra_1.writeFileSync)(appDelegatePath, rewrite.source);
60
+ });
61
+ await (0, common_1.runTask)('Registering App.swift and CapacitorView.swift with the Xcode App target.', async () => {
62
+ var _a;
63
+ const pbxprojPath = (0, path_1.join)(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
64
+ for (const name of ['App.swift', 'CapacitorView.swift']) {
65
+ try {
66
+ const { added } = (0, xcode_1.addSwiftFileToAppTarget)(pbxprojPath, 'App', name);
67
+ if (!added) {
68
+ log_1.logger.warn(`${name} is already registered in the App target, skipping.`);
69
+ }
70
+ }
71
+ catch (err) {
72
+ log_1.logger.warn(`Could not register ${name} automatically: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}. ` +
73
+ `Add ${name} to the App target in Xcode manually.`);
74
+ }
75
+ }
76
+ });
77
+ await scanAndWarn(config);
78
+ printNextSteps(config);
79
+ }
80
+ exports.migrateToSwiftUI = migrateToSwiftUI;
81
+ async function scanAndWarn(config) {
82
+ const findings = [];
83
+ const swiftFiles = await (0, fs_1.readdirp)(config.ios.platformDirAbs, {
84
+ filter: (item) => {
85
+ if (!item.stats.isFile())
86
+ return false;
87
+ if (!item.path.endsWith('.swift'))
88
+ return false;
89
+ const p = item.path;
90
+ return (!p.includes(`${path_1.sep}Pods${path_1.sep}`) &&
91
+ !p.includes(`${path_1.sep}build${path_1.sep}`) &&
92
+ !p.includes(`${path_1.sep}DerivedData${path_1.sep}`) &&
93
+ !p.includes(`${path_1.sep}.build${path_1.sep}`));
94
+ },
95
+ });
96
+ const tokenPatterns = [
97
+ { token: /UIApplication\.shared\.applicationState/, label: 'UIApplication.shared.applicationState' },
98
+ { token: /\btmpWindow\b/, label: 'tmpWindow' },
99
+ { token: /\bTmpViewController\b/, label: 'TmpViewController' },
100
+ ];
101
+ for (const filePath of swiftFiles) {
102
+ const source = (0, fs_extra_1.readFileSync)(filePath, 'utf-8');
103
+ source.split('\n').forEach((line, idx) => {
104
+ for (const { token, label } of tokenPatterns) {
105
+ if (token.test(line)) {
106
+ findings.push(`${filePath}:${idx + 1}: uses ${label}`);
107
+ }
108
+ }
109
+ });
110
+ }
111
+ const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
112
+ if ((0, fs_extra_1.existsSync)(appDelegatePath)) {
113
+ const source = (0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8');
114
+ if (hasCustomDelegateBody(source, OPEN_URL_SIG)) {
115
+ findings.push(`${appDelegatePath}: custom application(_:open:) body — review for UIScene compatibility`);
116
+ }
117
+ if (hasCustomDelegateBody(source, CONTINUE_SIG)) {
118
+ findings.push(`${appDelegatePath}: custom application(_:continue:) body — review for UIScene compatibility`);
119
+ }
120
+ }
121
+ if (findings.length === 0)
122
+ return;
123
+ log_1.logger.warn('UIScene scan found patterns that may need manual review:');
124
+ for (const finding of findings) {
125
+ log_1.logger.warn(` ${finding}`);
126
+ }
127
+ }
128
+ function hasCustomDelegateBody(source, sigRegex) {
129
+ const match = source.match(sigRegex);
130
+ if (!match || match.index === undefined)
131
+ return false;
132
+ const openIdx = source.indexOf('{', match.index);
133
+ if (openIdx === -1)
134
+ return false;
135
+ const closeIdx = findMatchingBrace(source, openIdx);
136
+ if (closeIdx === null)
137
+ return false;
138
+ const body = source.slice(openIdx + 1, closeIdx);
139
+ const codeLines = body
140
+ .split('\n')
141
+ .map((l) => l.trim())
142
+ .filter((l) => l.length > 0 && !l.startsWith('//'));
143
+ if (codeLines.length === 0)
144
+ return false;
145
+ return codeLines.some((l) => !l.includes('ApplicationDelegateProxy.shared'));
146
+ }
147
+ function hasCustomWindowSetup(source) {
148
+ return source.split('\n').some((line) => {
149
+ const trimmed = line.trim();
150
+ if (!/\bwindow\b/.test(trimmed))
151
+ return false;
152
+ if (trimmed.startsWith('//'))
153
+ return false;
154
+ return !/^var\s+window\s*:\s*UIWindow\?$/.test(trimmed);
155
+ });
156
+ }
157
+ function printNextSteps(config) {
158
+ log_1.logger.info('');
159
+ log_1.logger.info('SwiftUI migration next steps:');
160
+ log_1.logger.info(' • Review any warnings above for legacy API usage or custom AppDelegate URL/activity handlers.');
161
+ log_1.logger.info(' • App.swift now owns the app entry point; move UIKit root-window customizations into its scene body.');
162
+ for (const leftover of leftoverUIKitFiles(config)) {
163
+ log_1.logger.info(` • ${leftover} is no longer used by the app and can be deleted from the Xcode project.`);
164
+ }
165
+ log_1.logger.info(' • Full guide: https://capacitorjs.com/docs/next/updating/9-0');
166
+ }
167
+ function printManualSteps() {
168
+ log_1.logger.info('');
169
+ log_1.logger.info('Migrate the iOS project to the SwiftUI App-struct layout by hand:');
170
+ log_1.logger.info(' • Add App.swift (an @main struct conforming to App) and CapacitorView.swift to the App target.');
171
+ log_1.logger.info(' • Drop @main, the UIWindow property, and configurationForConnecting from AppDelegate.');
172
+ log_1.logger.info(' • Reference AppDelegate from App.swift with @UIApplicationDelegateAdaptor.');
173
+ log_1.logger.info(' • Reduce UIApplicationSceneManifest to UIApplicationSupportsMultipleScenes only.');
174
+ log_1.logger.info(' • Remove UIMainStoryboardFile from Info.plist.');
175
+ log_1.logger.info(' • Route URLs and universal links from the SwiftUI scene body through');
176
+ log_1.logger.info(' SceneDelegateProxy.shared.handle(openURL:) and .handle(userActivity:).');
177
+ log_1.logger.info(' • Full guide: https://capacitorjs.com/docs/next/updating/9-0');
178
+ }
179
+ function leftoverUIKitFiles(config) {
180
+ const candidates = [
181
+ (0, path_1.join)(config.ios.nativeTargetDirAbs, 'SceneDelegate.swift'),
182
+ (0, path_1.join)(config.ios.nativeTargetDirAbs, 'Base.lproj', 'Main.storyboard'),
183
+ ];
184
+ return candidates.filter((path) => (0, fs_extra_1.existsSync)(path));
185
+ }
186
+ async function loadTemplateAssets(config) {
187
+ const packageManager = await config.ios.packageManager;
188
+ const archiveName = packageManager === 'SPM' ? 'ios-spm-template.tar.gz' : 'ios-pods-template.tar.gz';
189
+ const archivePath = (0, path_1.join)(config.cli.assetsDirAbs, archiveName);
190
+ const tempDir = (0, path_1.join)(config.cli.assetsDirAbs, 'tempSwiftUITemplate');
191
+ try {
192
+ await (0, template_1.extractTemplate)(archivePath, tempDir);
193
+ const appPath = (0, path_1.join)(tempDir, 'App', 'App', 'App.swift');
194
+ const capacitorViewPath = (0, path_1.join)(tempDir, 'App', 'App', 'CapacitorView.swift');
195
+ if (!(0, fs_extra_1.existsSync)(appPath) || !(0, fs_extra_1.existsSync)(capacitorViewPath)) {
196
+ return null;
197
+ }
198
+ return {
199
+ app: (0, fs_extra_1.readFileSync)(appPath, 'utf-8'),
200
+ capacitorView: (0, fs_extra_1.readFileSync)(capacitorViewPath, 'utf-8'),
201
+ };
202
+ }
203
+ finally {
204
+ (0, fs_1.deleteFolderRecursive)(tempDir);
205
+ }
206
+ }
207
+ function writeAppTargetFile(config, name, contents) {
208
+ const path = (0, path_1.join)(config.ios.nativeTargetDirAbs, name);
209
+ if ((0, fs_extra_1.existsSync)(path)) {
210
+ return false;
211
+ }
212
+ (0, fs_extra_1.writeFileSync)(path, contents);
213
+ return true;
214
+ }
215
+ function planAppDelegateRewrite(path) {
216
+ if (!(0, fs_extra_1.existsSync)(path)) {
217
+ return { status: 'skipped', reason: 'AppDelegate.swift not found.' };
218
+ }
219
+ return rewriteAppDelegateForAdaptor((0, fs_extra_1.readFileSync)(path, 'utf-8'));
220
+ }
221
+ /**
222
+ * Reshape a pre-9.0 AppDelegate into one that can be adopted by an @main SwiftUI App
223
+ * struct through @UIApplicationDelegateAdaptor.
224
+ *
225
+ * Refuses the rewrite whenever the class does more than the stock template does, because
226
+ * SwiftUI takes over both the window and the scene-level URL/activity callbacks.
227
+ */
228
+ function rewriteAppDelegateForAdaptor(source) {
229
+ if (!/\bclass\s+AppDelegate\b/.test(source)) {
230
+ return { status: 'skipped', reason: 'no AppDelegate class found in AppDelegate.swift.' };
231
+ }
232
+ if (hasCustomWindowSetup(source)) {
233
+ return {
234
+ status: 'skipped',
235
+ reason: 'AppDelegate manages its own UIWindow, which the SwiftUI App struct now owns.',
236
+ };
237
+ }
238
+ if (hasCustomDelegateBody(source, OPEN_URL_SIG) || hasCustomDelegateBody(source, CONTINUE_SIG)) {
239
+ return {
240
+ status: 'skipped',
241
+ reason: 'AppDelegate has custom application(_:open:) or application(_:continue:) handling, ' +
242
+ 'which scenes no longer call.',
243
+ };
244
+ }
245
+ let rewritten = stripMainAttribute(source);
246
+ rewritten = rewritten.replace(/(\bclass\s+AppDelegate\s*:\s*)UIResponder\b/, '$1NSObject');
247
+ rewritten = rewritten.replace(/^[ \t]*var\s+window\s*:\s*UIWindow\?[ \t]*\r?\n(?:[ \t]*\r?\n)?/m, '');
248
+ const withoutSceneConfiguration = removeMethod(rewritten, CONFIGURATION_FOR_CONNECTING_SIG);
249
+ if (withoutSceneConfiguration === null) {
250
+ return { status: 'skipped', reason: 'could not parse the configurationForConnecting method in AppDelegate.swift.' };
251
+ }
252
+ return { status: 'rewritten', source: withoutSceneConfiguration };
253
+ }
254
+ function stripMainAttribute(source) {
255
+ return source.replace(/@(?:main|UIApplicationMain)[ \t]*(?:\r?\n[ \t]*)?(?=class\s+AppDelegate\b)/, '');
256
+ }
257
+ function removeMethod(source, sigRegex) {
258
+ const match = source.match(sigRegex);
259
+ if (!match || match.index === undefined) {
260
+ return source;
261
+ }
262
+ const openIdx = source.indexOf('{', match.index);
263
+ if (openIdx === -1) {
264
+ return null;
265
+ }
266
+ const closeIdx = findMatchingBrace(source, openIdx);
267
+ if (closeIdx === null) {
268
+ return null;
269
+ }
270
+ const lines = source.split('\n');
271
+ const signatureLine = countNewlines(source.slice(0, match.index));
272
+ const closingLine = countNewlines(source.slice(0, closeIdx));
273
+ const firstLine = signatureLine > 0 && lines[signatureLine - 1].trim() === '' ? signatureLine - 1 : signatureLine;
274
+ lines.splice(firstLine, closingLine - firstLine + 1);
275
+ return lines.join('\n');
276
+ }
277
+ function findMatchingBrace(source, openIdx) {
278
+ let depth = 1;
279
+ let i = openIdx + 1;
280
+ while (i < source.length && depth > 0) {
281
+ const ch = source[i];
282
+ if (ch === '{')
283
+ depth++;
284
+ else if (ch === '}')
285
+ depth--;
286
+ i++;
287
+ }
288
+ return depth === 0 ? i - 1 : null;
289
+ }
290
+ function countNewlines(source) {
291
+ let count = 0;
292
+ for (const ch of source) {
293
+ if (ch === '\n')
294
+ count++;
295
+ }
296
+ return count;
297
+ }
298
+ function readDetectionSignals(config) {
299
+ const appStructPath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'App.swift');
300
+ const capacitorViewPath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'CapacitorView.swift');
301
+ const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
302
+ return {
303
+ hasSwiftUIManifest: (0, spm_1.hasSwiftUISceneManifest)(config),
304
+ hasAppStruct: (0, fs_extra_1.existsSync)(appStructPath) && /struct\s+\w+\s*:\s*App\b/.test((0, fs_extra_1.readFileSync)(appStructPath, 'utf-8')),
305
+ hasCapacitorView: (0, fs_extra_1.existsSync)(capacitorViewPath),
306
+ hasDelegateAdaptorShape: (0, fs_extra_1.existsSync)(appDelegatePath) && !/@(?:main|UIApplicationMain)\b/.test((0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8')),
307
+ };
308
+ }
309
+ function classify(signals) {
310
+ const { hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape } = signals;
311
+ const trueCount = [hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape].filter(Boolean).length;
312
+ if (trueCount === 0)
313
+ return 'eligible';
314
+ if (trueCount === 4)
315
+ return 'already-migrated';
316
+ return 'partial';
317
+ }
318
+ function describeSignals({ hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape, }) {
319
+ const present = [];
320
+ const missing = [];
321
+ (hasSwiftUIManifest ? present : missing).push('SwiftUI UIApplicationSceneManifest');
322
+ (hasAppStruct ? present : missing).push('App.swift');
323
+ (hasCapacitorView ? present : missing).push('CapacitorView.swift');
324
+ (hasDelegateAdaptorShape ? present : missing).push('AppDelegate without @main');
325
+ return `present: [${present.join(', ')}]; missing: [${missing.join(', ')}]`;
326
+ }
327
+ // Exported for tests.
328
+ exports.__testables = {
329
+ classify,
330
+ describeSignals,
331
+ hasCustomDelegateBody,
332
+ hasCustomWindowSetup,
333
+ rewriteAppDelegateForAdaptor,
334
+ scanAndWarn,
335
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setAllStringIn = exports.migrateCommand = void 0;
3
+ exports.__testables = exports.setAllStringIn = exports.migrateCommand = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs_extra_1 = require("fs-extra");
6
6
  const path_1 = require("path");
@@ -14,6 +14,8 @@ const log_1 = require("../log");
14
14
  const fs_1 = require("../util/fs");
15
15
  const subprocess_1 = require("../util/subprocess");
16
16
  const template_1 = require("../util/template");
17
+ const migrate_edge_to_edge_1 = require("./migrate-edge-to-edge");
18
+ const migrate_swiftui_1 = require("./migrate-swiftui");
17
19
  // eslint-disable-next-line prefer-const
18
20
  let allDependencies = {};
19
21
  const libs = ['@capacitor/core', '@capacitor/cli', '@capacitor/ios', '@capacitor/android'];
@@ -49,6 +51,23 @@ const pluginVersion = 'next';
49
51
  const gradleVersion = '9.5.1';
50
52
  const iOSVersion = '16';
51
53
  let installFailed = false;
54
+ // AGP 9 changed the defaults of these properties and Android Studio's AGP Upgrade Assistant
55
+ // writes them into gradle.properties as compatibility shims. Capacitor apps need none of them,
56
+ // most are deprecated and will be removed in AGP 10, and some (android.builtInKotlin,
57
+ // android.sdk.defaultTargetSdkToCompileSdkIfUnset) break a Capacitor 9 build.
58
+ const agpMigrationAssistantProperties = [
59
+ 'android.builtInKotlin',
60
+ 'android.defaults.buildfeatures.resvalues',
61
+ 'android.dependency.useConstraints',
62
+ 'android.enableAppCompileTimeRClass',
63
+ 'android.newDsl',
64
+ 'android.r8.optimizedResourceShrinking',
65
+ 'android.r8.strictFullModeForKeepRules',
66
+ 'android.sdk.defaultTargetSdkToCompileSdkIfUnset',
67
+ 'android.uniquePackageNames',
68
+ 'android.usesSdkInManifest.disallowed',
69
+ ];
70
+ const jcenterLine = /^jcenter\(\)\s*(\/\/.*)?$/;
52
71
  async function migrateCommand(config, noprompt, packagemanager) {
53
72
  if (config === null) {
54
73
  (0, errors_1.fatal)('Config data missing');
@@ -138,6 +157,7 @@ async function migrateCommand(config, noprompt, packagemanager) {
138
157
  await (0, common_1.runTask)(`Migrating AppDelegate.swift`, () => {
139
158
  return updateAppDelegate((0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift'));
140
159
  });
160
+ await (0, migrate_swiftui_1.migrateToSwiftUI)(config);
141
161
  }
142
162
  if (!installFailed) {
143
163
  await (0, common_1.runTask)(`Running cap sync.`, () => {
@@ -152,8 +172,44 @@ async function migrateCommand(config, noprompt, packagemanager) {
152
172
  await (0, common_1.runTask)(`Migrating AndroidManifest.xml by adding density to Activity configChanges.`, () => {
153
173
  return updateAndroidManifest((0, path_1.join)(config.android.srcMainDirAbs, 'AndroidManifest.xml'));
154
174
  });
155
- const gradleWrapperVersion = getGradleWrapperVersion((0, path_1.join)(config.android.platformDirAbs, 'gradle', 'wrapper', 'gradle-wrapper.properties'));
156
- if (!installFailed && (0, semver_1.gte)(gradleVersion, gradleWrapperVersion)) {
175
+ await (0, common_1.runTask)(`Migrating the main Activity to enable edge-to-edge.`, () => {
176
+ return (0, migrate_edge_to_edge_1.migrateToEdgeToEdge)(config);
177
+ });
178
+ const gradleWrapperPath = (0, path_1.join)(config.android.platformDirAbs, 'gradle', 'wrapper', 'gradle-wrapper.properties');
179
+ const gradleWrapperVersion = getGradleWrapperVersion(gradleWrapperPath);
180
+ const outdatedGradle = (0, semver_1.gte)(gradleVersion, gradleWrapperVersion);
181
+ if (outdatedGradle) {
182
+ await (0, common_1.runTask)(`Updating gradle-wrapper.properties`, () => {
183
+ return updateFile(config, gradleWrapperPath, 'services.gradle.org/distributions/gradle-', '.zip', `${gradleVersion}-all`);
184
+ });
185
+ }
186
+ await (0, common_1.runTask)(`Migrating app build.gradle file.`, () => {
187
+ return updateAppBuildGradle((0, path_1.join)(config.android.appDirAbs, 'build.gradle'));
188
+ });
189
+ // settings.gradle
190
+ await (0, common_1.runTask)(`Migrating settings.gradle file.`, () => {
191
+ return (async () => {
192
+ const settingsPath = (0, path_1.join)(config.android.platformDirAbs, 'settings.gradle');
193
+ let txt = readFile(settingsPath);
194
+ if (!txt) {
195
+ return;
196
+ }
197
+ txt = txt.replace(`include ':capacitor-cordova-android-plugins'\n`, '');
198
+ txt = txt.replace(`project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')\n`, '');
199
+ (0, fs_extra_1.writeFileSync)(settingsPath, txt, { encoding: 'utf-8' });
200
+ })();
201
+ });
202
+ // gradle.properties
203
+ await (0, common_1.runTask)(`Migrating gradle.properties file.`, () => {
204
+ return updateGradleProperties((0, path_1.join)(config.android.platformDirAbs, 'gradle.properties'));
205
+ });
206
+ // Gradle 9 removed jcenter(), so it has to go before the wrapper upgrade below or the
207
+ // wrapper task itself fails at the configuration phase once it runs on Gradle 9
208
+ await (0, common_1.runTask)(`Removing jcenter() from gradle files.`, () => {
209
+ return removeJCenter(config);
210
+ });
211
+ // Always run before root build.gradle changes as the AGP update could be incompatible with current gradle
212
+ if (!installFailed && outdatedGradle) {
157
213
  try {
158
214
  await (0, common_1.runTask)(`Upgrading gradle wrapper`, () => {
159
215
  return updateGradleWrapperFiles(config.android.platformDirAbs);
@@ -168,7 +224,7 @@ async function migrateCommand(config, noprompt, packagemanager) {
168
224
  log_1.logger.error(`gradlew file does not have executable permissions. This can happen if the Android platform was added on a Windows machine. Please run ${colors_1.default.input(`chmod +x ./${config.android.platformDir}/gradlew`)} and ${colors_1.default.input(`cd ${config.android.platformDir} && ./gradlew wrapper --distribution-type all --gradle-version ${gradleVersion} --warning-mode all`)} to update the files manually`);
169
225
  }
170
226
  else {
171
- log_1.logger.error(`gradle wrapper files were not updated`);
227
+ log_1.logger.error(`gradle wrapper files were not updated\n${e}`);
172
228
  }
173
229
  }
174
230
  }
@@ -178,22 +234,6 @@ async function migrateCommand(config, noprompt, packagemanager) {
178
234
  await (0, common_1.runTask)(`Migrating root build.gradle file.`, () => {
179
235
  return updateBuildGradle((0, path_1.join)(config.android.platformDirAbs, 'build.gradle'), variablesAndClasspaths);
180
236
  });
181
- await (0, common_1.runTask)(`Migrating app build.gradle file.`, () => {
182
- return updateAppBuildGradle((0, path_1.join)(config.android.appDirAbs, 'build.gradle'));
183
- });
184
- // settings.gradle
185
- await (0, common_1.runTask)(`Migrating settings.gradle file.`, () => {
186
- return (async () => {
187
- const settingsPath = (0, path_1.join)(config.android.platformDirAbs, 'settings.gradle');
188
- let txt = readFile(settingsPath);
189
- if (!txt) {
190
- return;
191
- }
192
- txt = txt.replace(`include ':capacitor-cordova-android-plugins'\n`, '');
193
- txt = txt.replace(`project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')\n`, '');
194
- (0, fs_extra_1.writeFileSync)(settingsPath, txt, { encoding: 'utf-8' });
195
- })();
196
- });
197
237
  // Variables gradle
198
238
  await (0, common_1.runTask)(`Migrating variables.gradle file.`, () => {
199
239
  return (async () => {
@@ -231,16 +271,15 @@ async function migrateCommand(config, noprompt, packagemanager) {
231
271
  }
232
272
  const pluginVariables = {
233
273
  firebaseMessagingVersion: '25.0.1',
234
- playServicesLocationVersion: '21.3.0',
235
- androidxBrowserVersion: '1.9.0',
236
- androidxMaterialVersion: '1.13.0',
237
- androidxExifInterfaceVersion: '1.4.1',
238
- androidxCoreKTXVersion: '1.17.0',
239
- googleMapsPlayServicesVersion: '19.2.0',
240
- googleMapsUtilsVersion: '3.19.1',
241
- googleMapsKtxVersion: '5.2.1',
242
- googleMapsUtilsKtxVersion: '5.2.1',
243
- kotlinxCoroutinesVersion: '1.10.2',
274
+ playServicesLocationVersion: '21.4.0',
275
+ androidxBrowserVersion: '1.10.0',
276
+ androidxMaterialVersion: '1.14.0',
277
+ androidxExifInterfaceVersion: '1.4.2',
278
+ googleMapsPlayServicesVersion: '20.0.0',
279
+ googleMapsUtilsVersion: '5.0.0',
280
+ googleMapsKtxVersion: '6.0.1',
281
+ googleMapsUtilsKtxVersion: '6.0.1',
282
+ kotlinxCoroutinesVersion: '1.11.0',
244
283
  coreSplashScreenVersion: '1.2.0',
245
284
  };
246
285
  for (const variable of Object.keys(pluginVariables)) {
@@ -321,7 +360,17 @@ async function installLatestLibs(dependencyManager, runInstall, config) {
321
360
  }
322
361
  }
323
362
  async function writeBreakingChanges() {
324
- const breaking = ['@capacitor/push-notifications', '@capacitor/splash-screen'];
363
+ const breaking = [
364
+ '@capacitor/action-sheet',
365
+ '@capacitor/barcode-scanner',
366
+ '@capacitor/browser',
367
+ '@capacitor/camera',
368
+ '@capacitor/geolocation',
369
+ '@capacitor/google-maps',
370
+ '@capacitor/inappbrowser',
371
+ '@capacitor/push-notifications',
372
+ '@capacitor/splash-screen',
373
+ ];
325
374
  const broken = [];
326
375
  for (const lib of breaking) {
327
376
  if (allDependencies[lib]) {
@@ -331,6 +380,15 @@ async function writeBreakingChanges() {
331
380
  if (broken.length > 0) {
332
381
  log_1.logger.info(`IMPORTANT: Review https://capacitorjs.com/docs/next/updating/9-0#plugins for breaking changes in these plugins that you use: ${broken.join(', ')}.`);
333
382
  }
383
+ if (allDependencies['@capacitor/android']) {
384
+ log_1.logger.info(`IMPORTANT: The SystemBars 'insetsHandling' option now defaults to 'native' instead of 'css'. ` +
385
+ `If your app reads the '--safe-area-inset-*' CSS variables that Capacitor injected, ` +
386
+ `set 'plugins.SystemBars.insetsHandling' to 'css' in your Capacitor configuration file.`);
387
+ }
388
+ if (allDependencies['@capacitor/ios']) {
389
+ log_1.logger.info('IMPORTANT: Capacitor 9.0 boots iOS apps from a SwiftUI App struct with UIScene. ' +
390
+ 'See https://capacitorjs.com/docs/next/updating/9-0 for the full migration guide.');
391
+ }
334
392
  }
335
393
  async function getAndroidVariablesAndClasspaths(config) {
336
394
  const tempAndroidTemplateFolder = (0, path_1.join)(config.cli.assetsDirAbs, 'tempAndroidTemplate');
@@ -380,7 +438,7 @@ function getGradleWrapperVersion(filename) {
380
438
  if (!txt) {
381
439
  return '0.0.0';
382
440
  }
383
- const version = txt.substring(txt.indexOf('gradle-') + 7, txt.indexOf('-all.zip'));
441
+ const version = txt.substring(txt.indexOf('gradle-') + 7, txt.indexOf('.zip', txt.indexOf('gradle-')));
384
442
  const semverVersion = (_a = (0, semver_1.coerce)(version)) === null || _a === void 0 ? void 0 : _a.version;
385
443
  return semverVersion ? semverVersion : '0.0.0';
386
444
  }
@@ -427,8 +485,69 @@ async function updateAppBuildGradle(filename) {
427
485
  }
428
486
 
429
487
  `, '');
488
+ if (!replaced.includes('def androidxAppCompatVersion = rootProject.ext.androidxAppCompatVersion')) {
489
+ replaced = replaced.replace(`apply plugin: 'com.android.application'\n\n`, `apply plugin: 'com.android.application'
490
+
491
+ def androidxAppCompatVersion = rootProject.ext.androidxAppCompatVersion
492
+ def androidxCoordinatorLayoutVersion = rootProject.ext.androidxCoordinatorLayoutVersion
493
+ def coreSplashScreenVersion = rootProject.ext.coreSplashScreenVersion
494
+ def junitVersion = rootProject.ext.junitVersion
495
+ def androidxJunitVersion = rootProject.ext.androidxJunitVersion
496
+ def androidxEspressoCoreVersion = rootProject.ext.androidxEspressoCoreVersion
497
+
498
+ `);
499
+ }
430
500
  (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
431
501
  }
502
+ async function updateGradleProperties(filename) {
503
+ const txt = readFile(filename);
504
+ if (!txt) {
505
+ return;
506
+ }
507
+ const replaced = removeAGPMigrationProperties(txt);
508
+ if (replaced !== txt) {
509
+ (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
510
+ }
511
+ }
512
+ function removeAGPMigrationProperties(txt) {
513
+ return txt
514
+ .split('\n')
515
+ .filter((line) => {
516
+ const property = line.split('=')[0].trim();
517
+ if (!agpMigrationAssistantProperties.includes(property)) {
518
+ return true;
519
+ }
520
+ log_1.logger.info(`Removed ${property} from gradle.properties.`);
521
+ return false;
522
+ })
523
+ .join('\n');
524
+ }
525
+ async function removeJCenter(config) {
526
+ // jcenter() has only ever been in the root build.gradle of our templates, never in app/build.gradle
527
+ const filename = (0, path_1.join)(config.android.platformDirAbs, 'build.gradle');
528
+ const txt = readFile(filename);
529
+ if (!txt) {
530
+ return;
531
+ }
532
+ const replaced = removeJCenterFromGradleFile(txt);
533
+ if (replaced !== txt) {
534
+ (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
535
+ }
536
+ }
537
+ // mavenCentral() is not added back: it has been in the template since Capacitor 4 and the
538
+ // 3 to 4 migrator always added it, so every app is assumed to declare it already
539
+ function removeJCenterFromGradleFile(txt) {
540
+ return txt
541
+ .split('\n')
542
+ .filter((line) => {
543
+ if (!jcenterLine.test(line.trim())) {
544
+ return true;
545
+ }
546
+ log_1.logger.info(`Removed jcenter().`);
547
+ return false;
548
+ })
549
+ .join('\n');
550
+ }
432
551
  async function updateFile(config, filename, textStart, textEnd, replacement, skipIfNotFound) {
433
552
  if (config === null) {
434
553
  return false;
@@ -526,3 +645,10 @@ async function updateAppDelegate(filename) {
526
645
  (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
527
646
  }
528
647
  }
648
+ exports.__testables = {
649
+ agpMigrationAssistantProperties,
650
+ removeAGPMigrationProperties,
651
+ removeJCenter,
652
+ removeJCenterFromGradleFile,
653
+ updateGradleProperties,
654
+ };