@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.
- 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/common.js +1 -1
- package/dist/config.js +9 -6
- package/dist/cordova.js +16 -3
- package/dist/declarations.d.ts +70 -4
- package/dist/ios/common.js +1 -1
- package/dist/ios/update.js +23 -7
- package/dist/plugin.js +1 -1
- package/dist/tasks/list.js +1 -1
- package/dist/tasks/migrate-edge-to-edge.js +158 -0
- package/dist/tasks/migrate-swiftui.js +335 -0
- package/dist/tasks/migrate.js +159 -34
- package/dist/util/capacitor-package.js +324 -0
- package/dist/util/cordova-ios.js +151 -12
- package/dist/util/js.js +1 -1
- package/dist/util/monorepotools.js +2 -2
- package/dist/util/node.js +37 -3
- package/dist/util/spm.js +82 -11
- package/dist/util/subprocess.js +2 -2
- package/dist/util/xcode.js +48 -0
- package/dist/util/xml.js +1 -1
- 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,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
|
-
|
|
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
|
|
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
|
-
|
|
156
|
-
|
|
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.
|
|
235
|
-
androidxBrowserVersion: '1.
|
|
236
|
-
androidxMaterialVersion: '1.
|
|
237
|
-
androidxExifInterfaceVersion: '1.4.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 = [
|
|
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('
|
|
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
|
+
};
|