@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.
- package/assets/android-template.tar.gz +0 -0
- package/assets/capacitor-cordova-android-plugins.tar.gz +0 -0
- package/assets/capacitor-cordova-ios-plugins.tar.gz +0 -0
- package/assets/ios-pods-template.tar.gz +0 -0
- package/assets/ios-spm-template.tar.gz +0 -0
- package/dist/android/update.js +6 -3
- package/dist/config.js +5 -3
- package/dist/cordova.js +16 -3
- package/dist/declarations.d.ts +70 -4
- package/dist/ios/update.js +21 -7
- package/dist/tasks/migrate-edge-to-edge.js +158 -0
- package/dist/tasks/migrate-swiftui.js +335 -0
- package/dist/tasks/migrate.js +158 -32
- package/dist/util/capacitor-package.js +297 -0
- package/dist/util/cordova-ios.js +144 -12
- package/dist/util/node.js +35 -1
- package/dist/util/spm.js +82 -11
- package/dist/util/xcode.js +48 -0
- package/package.json +4 -2
|
@@ -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
|
+
};
|
package/dist/tasks/migrate.js
CHANGED
|
@@ -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
|
-
|
|
156
|
-
|
|
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.
|
|
235
|
-
androidxBrowserVersion: '1.
|
|
236
|
-
androidxMaterialVersion: '1.
|
|
237
|
-
androidxExifInterfaceVersion: '1.4.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 = [
|
|
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('
|
|
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
|
+
};
|