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

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,7 +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
- // eslint-disable-next-line prefer-const
17
+ const migrate_edge_to_edge_1 = require("./migrate-edge-to-edge");
18
+ const migrate_swiftui_1 = require("./migrate-swiftui");
18
19
  let allDependencies = {};
19
20
  const libs = ['@capacitor/core', '@capacitor/cli', '@capacitor/ios', '@capacitor/android'];
20
21
  const plugins = [
@@ -49,6 +50,23 @@ const pluginVersion = 'next';
49
50
  const gradleVersion = '9.5.1';
50
51
  const iOSVersion = '16';
51
52
  let installFailed = false;
53
+ // AGP 9 changed the defaults of these properties and Android Studio's AGP Upgrade Assistant
54
+ // writes them into gradle.properties as compatibility shims. Capacitor apps need none of them,
55
+ // most are deprecated and will be removed in AGP 10, and some (android.builtInKotlin,
56
+ // android.sdk.defaultTargetSdkToCompileSdkIfUnset) break a Capacitor 9 build.
57
+ const agpMigrationAssistantProperties = [
58
+ 'android.builtInKotlin',
59
+ 'android.defaults.buildfeatures.resvalues',
60
+ 'android.dependency.useConstraints',
61
+ 'android.enableAppCompileTimeRClass',
62
+ 'android.newDsl',
63
+ 'android.r8.optimizedResourceShrinking',
64
+ 'android.r8.strictFullModeForKeepRules',
65
+ 'android.sdk.defaultTargetSdkToCompileSdkIfUnset',
66
+ 'android.uniquePackageNames',
67
+ 'android.usesSdkInManifest.disallowed',
68
+ ];
69
+ const jcenterLine = /^jcenter\(\)\s*(\/\/.*)?$/;
52
70
  async function migrateCommand(config, noprompt, packagemanager) {
53
71
  if (config === null) {
54
72
  (0, errors_1.fatal)('Config data missing');
@@ -113,7 +131,7 @@ async function migrateCommand(config, noprompt, packagemanager) {
113
131
  return installLatestLibs(installerType, runNpmInstall, config);
114
132
  });
115
133
  }
116
- catch (ex) {
134
+ catch {
117
135
  log_1.logger.error(`${installerType} install failed. Try deleting node_modules folder and running ${colors_1.default.input(`${installerType} install --force`)} manually.`);
118
136
  installFailed = true;
119
137
  }
@@ -138,6 +156,7 @@ async function migrateCommand(config, noprompt, packagemanager) {
138
156
  await (0, common_1.runTask)(`Migrating AppDelegate.swift`, () => {
139
157
  return updateAppDelegate((0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift'));
140
158
  });
159
+ await (0, migrate_swiftui_1.migrateToSwiftUI)(config);
141
160
  }
142
161
  if (!installFailed) {
143
162
  await (0, common_1.runTask)(`Running cap sync.`, () => {
@@ -152,8 +171,44 @@ async function migrateCommand(config, noprompt, packagemanager) {
152
171
  await (0, common_1.runTask)(`Migrating AndroidManifest.xml by adding density to Activity configChanges.`, () => {
153
172
  return updateAndroidManifest((0, path_1.join)(config.android.srcMainDirAbs, 'AndroidManifest.xml'));
154
173
  });
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)) {
174
+ await (0, common_1.runTask)(`Migrating the main Activity to enable edge-to-edge.`, () => {
175
+ return (0, migrate_edge_to_edge_1.migrateToEdgeToEdge)(config);
176
+ });
177
+ const gradleWrapperPath = (0, path_1.join)(config.android.platformDirAbs, 'gradle', 'wrapper', 'gradle-wrapper.properties');
178
+ const gradleWrapperVersion = getGradleWrapperVersion(gradleWrapperPath);
179
+ const outdatedGradle = (0, semver_1.gte)(gradleVersion, gradleWrapperVersion);
180
+ if (outdatedGradle) {
181
+ await (0, common_1.runTask)(`Updating gradle-wrapper.properties`, () => {
182
+ return updateFile(config, gradleWrapperPath, 'services.gradle.org/distributions/gradle-', '.zip', `${gradleVersion}-all`);
183
+ });
184
+ }
185
+ await (0, common_1.runTask)(`Migrating app build.gradle file.`, () => {
186
+ return updateAppBuildGradle((0, path_1.join)(config.android.appDirAbs, 'build.gradle'));
187
+ });
188
+ // settings.gradle
189
+ await (0, common_1.runTask)(`Migrating settings.gradle file.`, () => {
190
+ return (async () => {
191
+ const settingsPath = (0, path_1.join)(config.android.platformDirAbs, 'settings.gradle');
192
+ let txt = readFile(settingsPath);
193
+ if (!txt) {
194
+ return;
195
+ }
196
+ txt = txt.replace(`include ':capacitor-cordova-android-plugins'\n`, '');
197
+ txt = txt.replace(`project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')\n`, '');
198
+ (0, fs_extra_1.writeFileSync)(settingsPath, txt, { encoding: 'utf-8' });
199
+ })();
200
+ });
201
+ // gradle.properties
202
+ await (0, common_1.runTask)(`Migrating gradle.properties file.`, () => {
203
+ return updateGradleProperties((0, path_1.join)(config.android.platformDirAbs, 'gradle.properties'));
204
+ });
205
+ // Gradle 9 removed jcenter(), so it has to go before the wrapper upgrade below or the
206
+ // wrapper task itself fails at the configuration phase once it runs on Gradle 9
207
+ await (0, common_1.runTask)(`Removing jcenter() from gradle files.`, () => {
208
+ return removeJCenter(config);
209
+ });
210
+ // Always run before root build.gradle changes as the AGP update could be incompatible with current gradle
211
+ if (!installFailed && outdatedGradle) {
157
212
  try {
158
213
  await (0, common_1.runTask)(`Upgrading gradle wrapper`, () => {
159
214
  return updateGradleWrapperFiles(config.android.platformDirAbs);
@@ -168,7 +223,7 @@ async function migrateCommand(config, noprompt, packagemanager) {
168
223
  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
224
  }
170
225
  else {
171
- log_1.logger.error(`gradle wrapper files were not updated`);
226
+ log_1.logger.error(`gradle wrapper files were not updated\n${e}`);
172
227
  }
173
228
  }
174
229
  }
@@ -178,22 +233,6 @@ async function migrateCommand(config, noprompt, packagemanager) {
178
233
  await (0, common_1.runTask)(`Migrating root build.gradle file.`, () => {
179
234
  return updateBuildGradle((0, path_1.join)(config.android.platformDirAbs, 'build.gradle'), variablesAndClasspaths);
180
235
  });
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
236
  // Variables gradle
198
237
  await (0, common_1.runTask)(`Migrating variables.gradle file.`, () => {
199
238
  return (async () => {
@@ -231,16 +270,15 @@ async function migrateCommand(config, noprompt, packagemanager) {
231
270
  }
232
271
  const pluginVariables = {
233
272
  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',
273
+ playServicesLocationVersion: '21.4.0',
274
+ androidxBrowserVersion: '1.10.0',
275
+ androidxMaterialVersion: '1.14.0',
276
+ androidxExifInterfaceVersion: '1.4.2',
277
+ googleMapsPlayServicesVersion: '20.0.0',
278
+ googleMapsUtilsVersion: '5.0.0',
279
+ googleMapsKtxVersion: '6.0.1',
280
+ googleMapsUtilsKtxVersion: '6.0.1',
281
+ kotlinxCoroutinesVersion: '1.11.0',
244
282
  coreSplashScreenVersion: '1.2.0',
245
283
  };
246
284
  for (const variable of Object.keys(pluginVariables)) {
@@ -321,7 +359,17 @@ async function installLatestLibs(dependencyManager, runInstall, config) {
321
359
  }
322
360
  }
323
361
  async function writeBreakingChanges() {
324
- const breaking = ['@capacitor/push-notifications', '@capacitor/splash-screen'];
362
+ const breaking = [
363
+ '@capacitor/action-sheet',
364
+ '@capacitor/barcode-scanner',
365
+ '@capacitor/browser',
366
+ '@capacitor/camera',
367
+ '@capacitor/geolocation',
368
+ '@capacitor/google-maps',
369
+ '@capacitor/inappbrowser',
370
+ '@capacitor/push-notifications',
371
+ '@capacitor/splash-screen',
372
+ ];
325
373
  const broken = [];
326
374
  for (const lib of breaking) {
327
375
  if (allDependencies[lib]) {
@@ -331,6 +379,15 @@ async function writeBreakingChanges() {
331
379
  if (broken.length > 0) {
332
380
  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
381
  }
382
+ if (allDependencies['@capacitor/android']) {
383
+ log_1.logger.info(`IMPORTANT: The SystemBars 'insetsHandling' option now defaults to 'native' instead of 'css'. ` +
384
+ `If your app reads the '--safe-area-inset-*' CSS variables that Capacitor injected, ` +
385
+ `set 'plugins.SystemBars.insetsHandling' to 'css' in your Capacitor configuration file.`);
386
+ }
387
+ if (allDependencies['@capacitor/ios']) {
388
+ log_1.logger.info('IMPORTANT: Capacitor 9.0 boots iOS apps from a SwiftUI App struct with UIScene. ' +
389
+ 'See https://capacitorjs.com/docs/next/updating/9-0 for the full migration guide.');
390
+ }
334
391
  }
335
392
  async function getAndroidVariablesAndClasspaths(config) {
336
393
  const tempAndroidTemplateFolder = (0, path_1.join)(config.cli.assetsDirAbs, 'tempAndroidTemplate');
@@ -380,7 +437,7 @@ function getGradleWrapperVersion(filename) {
380
437
  if (!txt) {
381
438
  return '0.0.0';
382
439
  }
383
- const version = txt.substring(txt.indexOf('gradle-') + 7, txt.indexOf('-all.zip'));
440
+ const version = txt.substring(txt.indexOf('gradle-') + 7, txt.indexOf('.zip', txt.indexOf('gradle-')));
384
441
  const semverVersion = (_a = (0, semver_1.coerce)(version)) === null || _a === void 0 ? void 0 : _a.version;
385
442
  return semverVersion ? semverVersion : '0.0.0';
386
443
  }
@@ -427,8 +484,69 @@ async function updateAppBuildGradle(filename) {
427
484
  }
428
485
 
429
486
  `, '');
487
+ if (!replaced.includes('def androidxAppCompatVersion = rootProject.ext.androidxAppCompatVersion')) {
488
+ replaced = replaced.replace(`apply plugin: 'com.android.application'\n\n`, `apply plugin: 'com.android.application'
489
+
490
+ def androidxAppCompatVersion = rootProject.ext.androidxAppCompatVersion
491
+ def androidxCoordinatorLayoutVersion = rootProject.ext.androidxCoordinatorLayoutVersion
492
+ def coreSplashScreenVersion = rootProject.ext.coreSplashScreenVersion
493
+ def junitVersion = rootProject.ext.junitVersion
494
+ def androidxJunitVersion = rootProject.ext.androidxJunitVersion
495
+ def androidxEspressoCoreVersion = rootProject.ext.androidxEspressoCoreVersion
496
+
497
+ `);
498
+ }
430
499
  (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
431
500
  }
501
+ async function updateGradleProperties(filename) {
502
+ const txt = readFile(filename);
503
+ if (!txt) {
504
+ return;
505
+ }
506
+ const replaced = removeAGPMigrationProperties(txt);
507
+ if (replaced !== txt) {
508
+ (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
509
+ }
510
+ }
511
+ function removeAGPMigrationProperties(txt) {
512
+ return txt
513
+ .split('\n')
514
+ .filter((line) => {
515
+ const property = line.split('=')[0].trim();
516
+ if (!agpMigrationAssistantProperties.includes(property)) {
517
+ return true;
518
+ }
519
+ log_1.logger.info(`Removed ${property} from gradle.properties.`);
520
+ return false;
521
+ })
522
+ .join('\n');
523
+ }
524
+ async function removeJCenter(config) {
525
+ // jcenter() has only ever been in the root build.gradle of our templates, never in app/build.gradle
526
+ const filename = (0, path_1.join)(config.android.platformDirAbs, 'build.gradle');
527
+ const txt = readFile(filename);
528
+ if (!txt) {
529
+ return;
530
+ }
531
+ const replaced = removeJCenterFromGradleFile(txt);
532
+ if (replaced !== txt) {
533
+ (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
534
+ }
535
+ }
536
+ // mavenCentral() is not added back: it has been in the template since Capacitor 4 and the
537
+ // 3 to 4 migrator always added it, so every app is assumed to declare it already
538
+ function removeJCenterFromGradleFile(txt) {
539
+ return txt
540
+ .split('\n')
541
+ .filter((line) => {
542
+ if (!jcenterLine.test(line.trim())) {
543
+ return true;
544
+ }
545
+ log_1.logger.info(`Removed jcenter().`);
546
+ return false;
547
+ })
548
+ .join('\n');
549
+ }
432
550
  async function updateFile(config, filename, textStart, textEnd, replacement, skipIfNotFound) {
433
551
  if (config === null) {
434
552
  return false;
@@ -526,3 +644,10 @@ async function updateAppDelegate(filename) {
526
644
  (0, fs_extra_1.writeFileSync)(filename, replaced, 'utf-8');
527
645
  }
528
646
  }
647
+ exports.__testables = {
648
+ agpMigrationAssistantProperties,
649
+ removeAGPMigrationProperties,
650
+ removeJCenter,
651
+ removeJCenterFromGradleFile,
652
+ updateGradleProperties,
653
+ };