@capacitor/cli 9.0.0-alpha.8 → 9.0.0-beta.0
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/network-security-config.js +230 -0
- package/dist/android/update.js +5 -1
- package/dist/cordova.js +8 -17
- package/dist/declarations.d.ts +8 -41
- package/dist/index.js +4 -14
- package/dist/ios/update.js +8 -18
- package/dist/ipc.js +51 -11
- package/dist/run-context/declared.js +54 -0
- package/dist/run-context/index.js +111 -0
- package/dist/run-context/registry.js +45 -0
- package/dist/run-context/signals.js +115 -0
- package/dist/run-context/types.js +2 -0
- package/dist/run-context/unknown.js +46 -0
- package/dist/run-context/version.js +74 -0
- package/dist/tasks/copy.js +3 -1
- package/dist/tasks/doctor.js +4 -1
- package/dist/tasks/migrate-debug-xcconfig.js +60 -0
- package/dist/tasks/migrate-spm.js +0 -1
- package/dist/tasks/migrate-swiftui.js +344 -54
- package/dist/tasks/migrate-to-modern-project.js +27 -0
- package/dist/tasks/migrate.js +40 -2
- package/dist/tasks/run.js +8 -3
- package/dist/tasks/telemetry.js +8 -1
- package/dist/telemetry.js +303 -57
- package/dist/util/capacitor-package.js +68 -146
- package/dist/util/cordova-ios.js +9 -11
- package/dist/util/npm.js +120 -0
- package/dist/util/spm.js +35 -25
- package/dist/util/xcode.js +193 -2
- package/package.json +3 -2
|
@@ -9,22 +9,80 @@ const fs_1 = require("../util/fs");
|
|
|
9
9
|
const spm_1 = require("../util/spm");
|
|
10
10
|
const template_1 = require("../util/template");
|
|
11
11
|
const xcode_1 = require("../util/xcode");
|
|
12
|
+
const MIGRATION_GUIDE_URL = 'https://capacitorjs.com/docs/next/updating/9-0';
|
|
12
13
|
const OPEN_URL_SIG = /func application\([^)]*\bopen url:/;
|
|
13
14
|
const CONTINUE_SIG = /func application\([^)]*\bcontinue userActivity:/;
|
|
14
15
|
const CONFIGURATION_FOR_CONNECTING_SIG = /func application\([^)]*\bconfigurationForConnecting\b/;
|
|
16
|
+
// Kept byte-identical to the shipped templates; migrate-swiftui-stock-app-delegate.spec.ts
|
|
17
|
+
// fails if the two drift apart.
|
|
18
|
+
const CONFIGURATION_FOR_CONNECTING_METHOD = [
|
|
19
|
+
' func application(',
|
|
20
|
+
' _ application: UIApplication,',
|
|
21
|
+
' configurationForConnecting connectingSceneSession: UISceneSession,',
|
|
22
|
+
' options: UIScene.ConnectionOptions',
|
|
23
|
+
' ) -> UISceneConfiguration {',
|
|
24
|
+
' let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)',
|
|
25
|
+
' configuration.delegateClass = SceneDelegate.self',
|
|
26
|
+
' return configuration',
|
|
27
|
+
' }',
|
|
28
|
+
].join('\n');
|
|
29
|
+
const SUPPORTED_INTERFACE_ORIENTATIONS_SIG = /func application\([^)]*\bsupportedInterfaceOrientationsFor\b/;
|
|
30
|
+
// Kept byte-identical to the shipped templates, like CONFIGURATION_FOR_CONNECTING_METHOD.
|
|
31
|
+
const SUPPORTED_INTERFACE_ORIENTATIONS_METHOD = [
|
|
32
|
+
' func application(',
|
|
33
|
+
' _ application: UIApplication,',
|
|
34
|
+
' supportedInterfaceOrientationsFor window: UIWindow?',
|
|
35
|
+
' ) -> UIInterfaceOrientationMask {',
|
|
36
|
+
' ApplicationDelegateProxy.shared.application(application, supportedInterfaceOrientationsFor: window)',
|
|
37
|
+
' }',
|
|
38
|
+
].join('\n');
|
|
39
|
+
const SCENE_WILL_CONNECT_SIG = /func scene\([^)]*\bwillConnectTo session:/;
|
|
40
|
+
const SCENE_OPEN_URL_CONTEXTS_SIG = /func scene\([^)]*\bopenURLContexts\b/;
|
|
41
|
+
const SCENE_CONTINUE_SIG = /func scene\([^)]*\bcontinue userActivity:/;
|
|
42
|
+
const SCENE_DELEGATE_PROXY = 'SceneDelegateProxy.shared';
|
|
43
|
+
const STOCK_WILL_CONNECT_BODY = [
|
|
44
|
+
/^guard let windowScene = scene as\? UIWindowScene else \{ return \}$/,
|
|
45
|
+
/^window = UIWindow\(windowScene: windowScene\)$/,
|
|
46
|
+
/^window\?\.rootViewController = CAPBridgeViewController\(\)$/,
|
|
47
|
+
/^window\?\.makeKeyAndVisible\(\)$/,
|
|
48
|
+
/^SceneDelegateProxy\.shared\.scene\(/,
|
|
49
|
+
];
|
|
50
|
+
const STOCK_WINDOW_PROPERTY = /^var\s+window\s*:\s*UIWindow\?$/;
|
|
51
|
+
const APP_DELEGATE_CLASS_SIG = /\bclass\s+AppDelegate\s*:\s*(?:UIResponder|NSObject)\s*,\s*UIApplicationDelegate\s*\{/;
|
|
52
|
+
const DID_FINISH_LAUNCHING_SIG = /func application\([^)]*\bdidFinishLaunchingWithOptions\b/;
|
|
53
|
+
const APPLICATION_DELEGATE_PROXY = 'ApplicationDelegateProxy.shared';
|
|
54
|
+
// Every module a stock AppDelegate has ever imported; anything else is the developer's.
|
|
55
|
+
const STOCK_APP_DELEGATE_PREAMBLE = /^(?:import\s+(?:UIKit|Capacitor|Foundation)|@(?:main|UIApplicationMain))$/;
|
|
56
|
+
/**
|
|
57
|
+
* The members every stock AppDelegate has carried since Capacitor 5, with the body each is
|
|
58
|
+
* allowed to hold. `configurationForConnecting` takes any body: whatever it names as the
|
|
59
|
+
* delegate class, the 9.0 template replaces it with one naming SceneDelegate.
|
|
60
|
+
*/
|
|
61
|
+
const STOCK_APP_DELEGATE_METHODS = [
|
|
62
|
+
{ sig: DID_FINISH_LAUNCHING_SIG, bodyIsStock: (lines) => lines.length === 1 && lines[0] === 'return true' },
|
|
63
|
+
{ sig: /func applicationWillResignActive\(/, bodyIsStock: isEmpty },
|
|
64
|
+
{ sig: /func applicationDidEnterBackground\(/, bodyIsStock: isEmpty },
|
|
65
|
+
{ sig: /func applicationWillEnterForeground\(/, bodyIsStock: isEmpty },
|
|
66
|
+
{ sig: /func applicationDidBecomeActive\(/, bodyIsStock: isEmpty },
|
|
67
|
+
{ sig: /func applicationWillTerminate\(/, bodyIsStock: isEmpty },
|
|
68
|
+
{ sig: OPEN_URL_SIG, bodyIsStock: (lines) => lines.every((line) => line.includes(APPLICATION_DELEGATE_PROXY)) },
|
|
69
|
+
{ sig: CONTINUE_SIG, bodyIsStock: (lines) => lines.every((line) => line.includes(APPLICATION_DELEGATE_PROXY)) },
|
|
70
|
+
{ sig: CONFIGURATION_FOR_CONNECTING_SIG, bodyIsStock: () => true },
|
|
71
|
+
{
|
|
72
|
+
sig: SUPPORTED_INTERFACE_ORIENTATIONS_SIG,
|
|
73
|
+
bodyIsStock: (lines) => lines.every((line) => line.includes(APPLICATION_DELEGATE_PROXY)),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
15
76
|
async function migrateToSwiftUI(config) {
|
|
16
77
|
const signals = readDetectionSignals(config);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return;
|
|
26
|
-
case 'eligible':
|
|
27
|
-
break;
|
|
78
|
+
if (classify(signals) === 'already-migrated') {
|
|
79
|
+
log_1.logger.info('SwiftUI migration: project already uses the SwiftUI App-struct layout, skipping.');
|
|
80
|
+
await forwardSupportedInterfaceOrientations(config);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (hasAnySignal(signals)) {
|
|
84
|
+
log_1.logger.info(`SwiftUI migration: resuming a partially migrated project (${describeSignals(signals)}); ` +
|
|
85
|
+
`completing the missing steps.`);
|
|
28
86
|
}
|
|
29
87
|
const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
|
|
30
88
|
const rewrite = planAppDelegateRewrite(appDelegatePath);
|
|
@@ -44,7 +102,7 @@ async function migrateToSwiftUI(config) {
|
|
|
44
102
|
log_1.logger.warn(`Info.plist not updated: ${result.reason}`);
|
|
45
103
|
}
|
|
46
104
|
});
|
|
47
|
-
await (0, common_1.runTask)('Writing App.swift and
|
|
105
|
+
await (0, common_1.runTask)('Writing App.swift, CapacitorView.swift and SceneDelegate.swift.', async () => {
|
|
48
106
|
const files = [
|
|
49
107
|
['App.swift', assets.app],
|
|
50
108
|
['CapacitorView.swift', assets.capacitorView],
|
|
@@ -54,14 +112,27 @@ async function migrateToSwiftUI(config) {
|
|
|
54
112
|
log_1.logger.warn(`${name} already exists, skipping.`);
|
|
55
113
|
}
|
|
56
114
|
}
|
|
115
|
+
const sceneDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'SceneDelegate.swift');
|
|
116
|
+
if (writeSceneDelegate(sceneDelegatePath, assets.sceneDelegate) === 'kept') {
|
|
117
|
+
log_1.logger.warn(`${sceneDelegatePath} carries code of your own, so it was kept. It is still the app's scene delegate, but ` +
|
|
118
|
+
`the SwiftUI App struct now owns the window: move any window setup out of it, and route ` +
|
|
119
|
+
`scene(_:openURLContexts:) and scene(_:continue:) through SceneDelegateProxy.shared from the SwiftUI ` +
|
|
120
|
+
`scene body instead. See ${MIGRATION_GUIDE_URL}.`);
|
|
121
|
+
}
|
|
57
122
|
});
|
|
123
|
+
const replaceAppDelegate = isStockAppDelegate((0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8'));
|
|
58
124
|
await (0, common_1.runTask)('Converting AppDelegate.swift into a UIApplicationDelegateAdaptor.', async () => {
|
|
59
|
-
(0, fs_extra_1.writeFileSync)(appDelegatePath, rewrite.source);
|
|
125
|
+
(0, fs_extra_1.writeFileSync)(appDelegatePath, replaceAppDelegate ? assets.appDelegate : rewrite.source);
|
|
60
126
|
});
|
|
61
|
-
|
|
127
|
+
if (!replaceAppDelegate) {
|
|
128
|
+
log_1.logger.warn(`${appDelegatePath} carries code of your own, so it was converted in place rather than replaced with the ` +
|
|
129
|
+
`slimmed-down Capacitor 9 template. Review what it still holds against ${MIGRATION_GUIDE_URL}.`);
|
|
130
|
+
}
|
|
131
|
+
let registrationFailed = false;
|
|
132
|
+
await (0, common_1.runTask)('Registering the SwiftUI entry-point files with the Xcode App target.', async () => {
|
|
62
133
|
var _a;
|
|
63
134
|
const pbxprojPath = (0, path_1.join)(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
|
|
64
|
-
for (const name of ['App.swift', 'CapacitorView.swift']) {
|
|
135
|
+
for (const name of ['App.swift', 'CapacitorView.swift', 'SceneDelegate.swift']) {
|
|
65
136
|
try {
|
|
66
137
|
const { added } = (0, xcode_1.addSwiftFileToAppTarget)(pbxprojPath, 'App', name);
|
|
67
138
|
if (!added) {
|
|
@@ -69,15 +140,69 @@ async function migrateToSwiftUI(config) {
|
|
|
69
140
|
}
|
|
70
141
|
}
|
|
71
142
|
catch (err) {
|
|
143
|
+
registrationFailed = true;
|
|
72
144
|
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
145
|
`Add ${name} to the App target in Xcode manually.`);
|
|
74
146
|
}
|
|
75
147
|
}
|
|
76
148
|
});
|
|
149
|
+
if (registrationFailed) {
|
|
150
|
+
log_1.logger.warn('Leaving Main.storyboard in place because the App target was not fully updated.');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
await (0, common_1.runTask)('Removing the leftover main storyboard.', async () => {
|
|
154
|
+
removeLeftoverStoryboard(config);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
77
157
|
await scanAndWarn(config);
|
|
78
|
-
printNextSteps(
|
|
158
|
+
printNextSteps();
|
|
79
159
|
}
|
|
80
160
|
exports.migrateToSwiftUI = migrateToSwiftUI;
|
|
161
|
+
/**
|
|
162
|
+
* Put the 9.0 scene delegate in place.
|
|
163
|
+
*
|
|
164
|
+
* The class stays in the app target rather than moving into the framework because
|
|
165
|
+
* UISceneConfiguration.delegateClass takes a type and UIKit instantiates it itself, so the
|
|
166
|
+
* app has to own a concrete class for AppDelegate to name. A stock 8.x file is replaced
|
|
167
|
+
* outright: its window setup belongs to the SwiftUI App struct now.
|
|
168
|
+
*/
|
|
169
|
+
function writeSceneDelegate(path, contents) {
|
|
170
|
+
if (!(0, fs_extra_1.existsSync)(path)) {
|
|
171
|
+
(0, fs_extra_1.writeFileSync)(path, contents);
|
|
172
|
+
return 'written';
|
|
173
|
+
}
|
|
174
|
+
if (!isStockSceneDelegate((0, fs_extra_1.readFileSync)(path, 'utf-8'))) {
|
|
175
|
+
return 'kept';
|
|
176
|
+
}
|
|
177
|
+
(0, fs_extra_1.writeFileSync)(path, contents);
|
|
178
|
+
return 'replaced';
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Drop the storyboard the SwiftUI App struct replaces.
|
|
182
|
+
*
|
|
183
|
+
* Deregisters before unlinking so a pbxproj that cannot be rewritten leaves a working
|
|
184
|
+
* project behind rather than a dangling reference. LaunchScreen.storyboard stays: Apple
|
|
185
|
+
* still requires a launch storyboard.
|
|
186
|
+
*/
|
|
187
|
+
function removeLeftoverStoryboard(config) {
|
|
188
|
+
const pbxprojPath = (0, path_1.join)(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
|
|
189
|
+
const mainStoryboardPath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'Base.lproj', 'Main.storyboard');
|
|
190
|
+
if ((0, fs_extra_1.existsSync)(mainStoryboardPath)) {
|
|
191
|
+
removeFile(pbxprojPath, mainStoryboardPath, () => (0, xcode_1.removeStoryboardFromAppTarget)(pbxprojPath, 'Main.storyboard'));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function removeFile(pbxprojPath, path, deregister) {
|
|
195
|
+
var _a;
|
|
196
|
+
try {
|
|
197
|
+
deregister();
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
log_1.logger.warn(`Could not deregister ${path} from ${pbxprojPath}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}. ` +
|
|
201
|
+
`Delete it from the App target in Xcode manually.`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
(0, fs_extra_1.unlinkSync)(path);
|
|
205
|
+
}
|
|
81
206
|
async function scanAndWarn(config) {
|
|
82
207
|
const findings = [];
|
|
83
208
|
const swiftFiles = await (0, fs_1.readdirp)(config.ios.platformDirAbs, {
|
|
@@ -125,24 +250,87 @@ async function scanAndWarn(config) {
|
|
|
125
250
|
log_1.logger.warn(` ${finding}`);
|
|
126
251
|
}
|
|
127
252
|
}
|
|
128
|
-
function hasCustomDelegateBody(source, sigRegex) {
|
|
253
|
+
function hasCustomDelegateBody(source, sigRegex, proxyToken = 'ApplicationDelegateProxy.shared') {
|
|
254
|
+
const body = bracedBody(source, sigRegex);
|
|
255
|
+
if (body === null)
|
|
256
|
+
return false;
|
|
257
|
+
return codeLines(body).some((line) => !line.includes(proxyToken));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Whether SceneDelegate.swift still holds exactly what the 8.x template shipped, and so
|
|
261
|
+
* can be deleted without losing anything the developer wrote.
|
|
262
|
+
*/
|
|
263
|
+
function isStockSceneDelegate(source) {
|
|
264
|
+
const classBody = bracedBody(source, /\bclass\s+SceneDelegate\b/);
|
|
265
|
+
if (classBody === null)
|
|
266
|
+
return false;
|
|
267
|
+
const willConnectBody = bracedBody(classBody, SCENE_WILL_CONNECT_SIG);
|
|
268
|
+
if (willConnectBody !== null &&
|
|
269
|
+
!codeLines(willConnectBody).every((line) => STOCK_WILL_CONNECT_BODY.some((stock) => stock.test(line)))) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (hasCustomDelegateBody(classBody, SCENE_OPEN_URL_CONTEXTS_SIG, SCENE_DELEGATE_PROXY))
|
|
273
|
+
return false;
|
|
274
|
+
if (hasCustomDelegateBody(classBody, SCENE_CONTINUE_SIG, SCENE_DELEGATE_PROXY))
|
|
275
|
+
return false;
|
|
276
|
+
let members = classBody;
|
|
277
|
+
for (const sig of [SCENE_WILL_CONNECT_SIG, SCENE_OPEN_URL_CONTEXTS_SIG, SCENE_CONTINUE_SIG]) {
|
|
278
|
+
members = removeMethod(members, sig);
|
|
279
|
+
if (members === null)
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return codeLines(members).every((line) => STOCK_WINDOW_PROPERTY.test(line));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Whether AppDelegate.swift still holds exactly what a Capacitor template shipped, across
|
|
286
|
+
* every stock shape from 5.x to 8.x, and so can be replaced outright with the 9.0 template
|
|
287
|
+
* rather than surgically rewritten.
|
|
288
|
+
*
|
|
289
|
+
* The 9.0 template keeps only didFinishLaunchingWithOptions: the empty lifecycle stubs are
|
|
290
|
+
* noise, and URL and universal-link routing moved to the SwiftUI scene body, which
|
|
291
|
+
* App.swift wires to SceneDelegateProxy in the same run.
|
|
292
|
+
*/
|
|
293
|
+
function isStockAppDelegate(source) {
|
|
294
|
+
if (!APP_DELEGATE_CLASS_SIG.test(source))
|
|
295
|
+
return false;
|
|
296
|
+
const classBody = bracedBody(source, APP_DELEGATE_CLASS_SIG);
|
|
297
|
+
if (classBody === null)
|
|
298
|
+
return false;
|
|
299
|
+
const outsideClass = source.slice(0, source.search(APP_DELEGATE_CLASS_SIG));
|
|
300
|
+
if (!codeLines(outsideClass).every((line) => STOCK_APP_DELEGATE_PREAMBLE.test(line))) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
let members = classBody;
|
|
304
|
+
for (const { sig, bodyIsStock } of STOCK_APP_DELEGATE_METHODS) {
|
|
305
|
+
const body = bracedBody(members, sig);
|
|
306
|
+
if (body !== null && !bodyIsStock(codeLines(body)))
|
|
307
|
+
return false;
|
|
308
|
+
members = removeMethod(members, sig);
|
|
309
|
+
if (members === null)
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
return codeLines(members).every((line) => STOCK_WINDOW_PROPERTY.test(line));
|
|
313
|
+
}
|
|
314
|
+
function isEmpty(lines) {
|
|
315
|
+
return lines.length === 0;
|
|
316
|
+
}
|
|
317
|
+
function bracedBody(source, sigRegex) {
|
|
129
318
|
const match = source.match(sigRegex);
|
|
130
319
|
if (!match || match.index === undefined)
|
|
131
|
-
return
|
|
320
|
+
return null;
|
|
132
321
|
const openIdx = source.indexOf('{', match.index);
|
|
133
322
|
if (openIdx === -1)
|
|
134
|
-
return
|
|
323
|
+
return null;
|
|
135
324
|
const closeIdx = findMatchingBrace(source, openIdx);
|
|
136
325
|
if (closeIdx === null)
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
326
|
+
return null;
|
|
327
|
+
return source.slice(openIdx + 1, closeIdx);
|
|
328
|
+
}
|
|
329
|
+
function codeLines(body) {
|
|
330
|
+
return body
|
|
140
331
|
.split('\n')
|
|
141
|
-
.map((
|
|
142
|
-
.filter((
|
|
143
|
-
if (codeLines.length === 0)
|
|
144
|
-
return false;
|
|
145
|
-
return codeLines.some((l) => !l.includes('ApplicationDelegateProxy.shared'));
|
|
332
|
+
.map((line) => line.trim())
|
|
333
|
+
.filter((line) => line.length > 0 && !line.startsWith('//'));
|
|
146
334
|
}
|
|
147
335
|
function hasCustomWindowSetup(source) {
|
|
148
336
|
return source.split('\n').some((line) => {
|
|
@@ -151,37 +339,34 @@ function hasCustomWindowSetup(source) {
|
|
|
151
339
|
return false;
|
|
152
340
|
if (trimmed.startsWith('//'))
|
|
153
341
|
return false;
|
|
342
|
+
// The orientation forward only passes the window UIKit asks about along.
|
|
343
|
+
if (/\bsupportedInterfaceOrientationsFor\b/.test(trimmed))
|
|
344
|
+
return false;
|
|
154
345
|
return !/^var\s+window\s*:\s*UIWindow\?$/.test(trimmed);
|
|
155
346
|
});
|
|
156
347
|
}
|
|
157
|
-
function printNextSteps(
|
|
348
|
+
function printNextSteps() {
|
|
158
349
|
log_1.logger.info('');
|
|
159
350
|
log_1.logger.info('SwiftUI migration next steps:');
|
|
160
351
|
log_1.logger.info(' • Review any warnings above for legacy API usage or custom AppDelegate URL/activity handlers.');
|
|
161
352
|
log_1.logger.info(' • App.swift now owns the app entry point; move UIKit root-window customizations into its scene body.');
|
|
162
|
-
|
|
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');
|
|
353
|
+
log_1.logger.info(` • Full guide: ${MIGRATION_GUIDE_URL}`);
|
|
166
354
|
}
|
|
167
355
|
function printManualSteps() {
|
|
168
356
|
log_1.logger.info('');
|
|
169
357
|
log_1.logger.info('Migrate the iOS project to the SwiftUI App-struct layout by hand:');
|
|
170
358
|
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
|
|
359
|
+
log_1.logger.info(' • Drop @main and the UIWindow property from AppDelegate.');
|
|
360
|
+
log_1.logger.info(' • Add SceneDelegate.swift (an empty UIWindowSceneDelegate) to the App target, and keep or add');
|
|
361
|
+
log_1.logger.info(' configurationForConnecting in AppDelegate naming it as the scene delegate class.');
|
|
172
362
|
log_1.logger.info(' • Reference AppDelegate from App.swift with @UIApplicationDelegateAdaptor.');
|
|
363
|
+
log_1.logger.info(' • Forward application(_:supportedInterfaceOrientationsFor:) in AppDelegate to');
|
|
364
|
+
log_1.logger.info(' ApplicationDelegateProxy.shared, so the bridge orientations (ScreenOrientation.lock()) apply.');
|
|
173
365
|
log_1.logger.info(' • Reduce UIApplicationSceneManifest to UIApplicationSupportsMultipleScenes only.');
|
|
174
366
|
log_1.logger.info(' • Remove UIMainStoryboardFile from Info.plist.');
|
|
175
367
|
log_1.logger.info(' • Route URLs and universal links from the SwiftUI scene body through');
|
|
176
368
|
log_1.logger.info(' SceneDelegateProxy.shared.handle(openURL:) and .handle(userActivity:).');
|
|
177
|
-
log_1.logger.info(
|
|
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));
|
|
369
|
+
log_1.logger.info(` • Full guide: ${MIGRATION_GUIDE_URL}`);
|
|
185
370
|
}
|
|
186
371
|
async function loadTemplateAssets(config) {
|
|
187
372
|
const packageManager = await config.ios.packageManager;
|
|
@@ -191,13 +376,18 @@ async function loadTemplateAssets(config) {
|
|
|
191
376
|
try {
|
|
192
377
|
await (0, template_1.extractTemplate)(archivePath, tempDir);
|
|
193
378
|
const appPath = (0, path_1.join)(tempDir, 'App', 'App', 'App.swift');
|
|
379
|
+
const appDelegatePath = (0, path_1.join)(tempDir, 'App', 'App', 'AppDelegate.swift');
|
|
194
380
|
const capacitorViewPath = (0, path_1.join)(tempDir, 'App', 'App', 'CapacitorView.swift');
|
|
195
|
-
|
|
381
|
+
const sceneDelegatePath = (0, path_1.join)(tempDir, 'App', 'App', 'SceneDelegate.swift');
|
|
382
|
+
const paths = [appPath, appDelegatePath, capacitorViewPath, sceneDelegatePath];
|
|
383
|
+
if (paths.some((path) => !(0, fs_extra_1.existsSync)(path))) {
|
|
196
384
|
return null;
|
|
197
385
|
}
|
|
198
386
|
return {
|
|
199
387
|
app: (0, fs_extra_1.readFileSync)(appPath, 'utf-8'),
|
|
388
|
+
appDelegate: (0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8'),
|
|
200
389
|
capacitorView: (0, fs_extra_1.readFileSync)(capacitorViewPath, 'utf-8'),
|
|
390
|
+
sceneDelegate: (0, fs_extra_1.readFileSync)(sceneDelegatePath, 'utf-8'),
|
|
201
391
|
};
|
|
202
392
|
}
|
|
203
393
|
finally {
|
|
@@ -245,11 +435,93 @@ function rewriteAppDelegateForAdaptor(source) {
|
|
|
245
435
|
let rewritten = stripMainAttribute(source);
|
|
246
436
|
rewritten = rewritten.replace(/(\bclass\s+AppDelegate\s*:\s*)UIResponder\b/, '$1NSObject');
|
|
247
437
|
rewritten = rewritten.replace(/^[ \t]*var\s+window\s*:\s*UIWindow\?[ \t]*\r?\n(?:[ \t]*\r?\n)?/m, '');
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
438
|
+
// UIKit stops calling these two once the app is scene-based, so leaving them behind
|
|
439
|
+
// would plant dead code a developer could mistake for live routing. Both bodies are
|
|
440
|
+
// known to hold nothing but ApplicationDelegateProxy calls by the guard above; App.swift
|
|
441
|
+
// re-routes them through SceneDelegateProxy in the same run.
|
|
442
|
+
const sigs = [OPEN_URL_SIG, CONTINUE_SIG];
|
|
443
|
+
const names = ['application(_:open:)', 'application(_:continue:)'];
|
|
444
|
+
let stripped = rewritten;
|
|
445
|
+
for (const [index, sig] of sigs.entries()) {
|
|
446
|
+
stripped = removeMethod(stripped, sig);
|
|
447
|
+
if (stripped === null) {
|
|
448
|
+
return { status: 'skipped', reason: `could not parse the ${names[index]} method in AppDelegate.swift.` };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const wired = ensureConfigurationForConnecting(stripped);
|
|
452
|
+
const forwarded = wired === null ? null : ensureSupportedInterfaceOrientations(wired);
|
|
453
|
+
if (forwarded === null) {
|
|
454
|
+
return { status: 'skipped', reason: 'could not find the end of the AppDelegate class in AppDelegate.swift.' };
|
|
455
|
+
}
|
|
456
|
+
return { status: 'rewritten', source: forwarded };
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Give the AppDelegate the scene configuration that names SceneDelegate.
|
|
460
|
+
*
|
|
461
|
+
* An 8.5 project already has one and keeps it — the class name it points at has not
|
|
462
|
+
* changed. Anything older gains the 9.0 template's version, because without it UIKit
|
|
463
|
+
* builds the scene with no delegate of ours and nothing can answer per-scene callbacks.
|
|
464
|
+
*/
|
|
465
|
+
function ensureConfigurationForConnecting(source) {
|
|
466
|
+
return ensureAppDelegateMethod(source, CONFIGURATION_FOR_CONNECTING_SIG, CONFIGURATION_FOR_CONNECTING_METHOD);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Forward the window's supported interface orientations to Capacitor.
|
|
470
|
+
*
|
|
471
|
+
* In the SwiftUI App-struct layout the bridge is not the window's root view controller, so
|
|
472
|
+
* UIKit only picks up its orientations, ScreenOrientation.lock() included, through the app
|
|
473
|
+
* delegate. A developer's own implementation is kept as it is.
|
|
474
|
+
*/
|
|
475
|
+
function ensureSupportedInterfaceOrientations(source) {
|
|
476
|
+
const forwarded = ensureAppDelegateMethod(source, SUPPORTED_INTERFACE_ORIENTATIONS_SIG, SUPPORTED_INTERFACE_ORIENTATIONS_METHOD);
|
|
477
|
+
if (forwarded === null || forwarded === source || /^import\s+Capacitor\b/m.test(forwarded)) {
|
|
478
|
+
return forwarded;
|
|
251
479
|
}
|
|
252
|
-
|
|
480
|
+
// ApplicationDelegateProxy lives in Capacitor, which a converted AppDelegate may not import yet.
|
|
481
|
+
const uiKitImport = /^import\s+UIKit\b.*$/m;
|
|
482
|
+
return uiKitImport.test(forwarded)
|
|
483
|
+
? forwarded.replace(uiKitImport, '$&\nimport Capacitor')
|
|
484
|
+
: `import Capacitor\n${forwarded}`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Append `method` at the end of the AppDelegate class, unless a method matching `sig` is
|
|
488
|
+
* already there. Returns null when the class body cannot be parsed.
|
|
489
|
+
*/
|
|
490
|
+
function ensureAppDelegateMethod(source, sig, method) {
|
|
491
|
+
// Located even when the method is already there: an unbalanced class body used to be
|
|
492
|
+
// caught by the strip this replaced, and reporting success on a file we cannot parse
|
|
493
|
+
// is worse than refusing it.
|
|
494
|
+
const match = source.match(/\bclass\s+AppDelegate\b/);
|
|
495
|
+
if (!match || match.index === undefined)
|
|
496
|
+
return null;
|
|
497
|
+
const openIdx = source.indexOf('{', match.index);
|
|
498
|
+
if (openIdx === -1)
|
|
499
|
+
return null;
|
|
500
|
+
const closeIdx = findMatchingBrace(source, openIdx);
|
|
501
|
+
if (closeIdx === null)
|
|
502
|
+
return null;
|
|
503
|
+
if (sig.test(source)) {
|
|
504
|
+
return source;
|
|
505
|
+
}
|
|
506
|
+
// Trailing blank lines before the closing brace would otherwise stack up with the separator.
|
|
507
|
+
const head = source.slice(0, closeIdx).replace(/\s*$/, '');
|
|
508
|
+
return `${head}\n\n${method}\n${source.slice(closeIdx)}`;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Projects migrated before the orientation forward existed are already on the SwiftUI layout,
|
|
512
|
+
* so the rest of this migration skips them; give them the forward on their next run.
|
|
513
|
+
*/
|
|
514
|
+
async function forwardSupportedInterfaceOrientations(config) {
|
|
515
|
+
const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
|
|
516
|
+
if (!(0, fs_extra_1.existsSync)(appDelegatePath))
|
|
517
|
+
return;
|
|
518
|
+
const source = (0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8');
|
|
519
|
+
const forwarded = ensureSupportedInterfaceOrientations(source);
|
|
520
|
+
if (forwarded === null || forwarded === source)
|
|
521
|
+
return;
|
|
522
|
+
await (0, common_1.runTask)('Forwarding supported interface orientations from AppDelegate.swift to Capacitor.', async () => {
|
|
523
|
+
(0, fs_extra_1.writeFileSync)(appDelegatePath, forwarded);
|
|
524
|
+
});
|
|
253
525
|
}
|
|
254
526
|
function stripMainAttribute(source) {
|
|
255
527
|
return source.replace(/@(?:main|UIApplicationMain)[ \t]*(?:\r?\n[ \t]*)?(?=class\s+AppDelegate\b)/, '');
|
|
@@ -299,37 +571,55 @@ function readDetectionSignals(config) {
|
|
|
299
571
|
const appStructPath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'App.swift');
|
|
300
572
|
const capacitorViewPath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'CapacitorView.swift');
|
|
301
573
|
const appDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'AppDelegate.swift');
|
|
574
|
+
const sceneDelegatePath = (0, path_1.join)(config.ios.nativeTargetDirAbs, 'SceneDelegate.swift');
|
|
302
575
|
return {
|
|
303
576
|
hasSwiftUIManifest: (0, spm_1.hasSwiftUISceneManifest)(config),
|
|
304
577
|
hasAppStruct: (0, fs_extra_1.existsSync)(appStructPath) && /struct\s+\w+\s*:\s*App\b/.test((0, fs_extra_1.readFileSync)(appStructPath, 'utf-8')),
|
|
305
578
|
hasCapacitorView: (0, fs_extra_1.existsSync)(capacitorViewPath),
|
|
306
579
|
hasDelegateAdaptorShape: (0, fs_extra_1.existsSync)(appDelegatePath) && !/@(?:main|UIApplicationMain)\b/.test((0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8')),
|
|
580
|
+
// A project migrated before the scene delegate landed has every other marker but no
|
|
581
|
+
// SceneDelegate.swift, so it reads as eligible and the next run completes it.
|
|
582
|
+
hasSceneDelegate: (0, fs_extra_1.existsSync)(sceneDelegatePath) &&
|
|
583
|
+
(0, fs_extra_1.existsSync)(appDelegatePath) &&
|
|
584
|
+
CONFIGURATION_FOR_CONNECTING_SIG.test((0, fs_extra_1.readFileSync)(appDelegatePath, 'utf-8')),
|
|
307
585
|
};
|
|
308
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Anything short of all five signals is eligible, not refused: every step below is
|
|
589
|
+
* individually idempotent, so a run interrupted part-way is finished by running again.
|
|
590
|
+
*/
|
|
309
591
|
function classify(signals) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return
|
|
592
|
+
return signalValues(signals).every(Boolean) ? 'already-migrated' : 'eligible';
|
|
593
|
+
}
|
|
594
|
+
function hasAnySignal(signals) {
|
|
595
|
+
return signalValues(signals).some(Boolean);
|
|
596
|
+
}
|
|
597
|
+
function signalValues({ hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape, hasSceneDelegate, }) {
|
|
598
|
+
return [hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape, hasSceneDelegate];
|
|
317
599
|
}
|
|
318
|
-
function describeSignals({ hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape, }) {
|
|
600
|
+
function describeSignals({ hasSwiftUIManifest, hasAppStruct, hasCapacitorView, hasDelegateAdaptorShape, hasSceneDelegate, }) {
|
|
319
601
|
const present = [];
|
|
320
602
|
const missing = [];
|
|
321
603
|
(hasSwiftUIManifest ? present : missing).push('SwiftUI UIApplicationSceneManifest');
|
|
322
604
|
(hasAppStruct ? present : missing).push('App.swift');
|
|
323
605
|
(hasCapacitorView ? present : missing).push('CapacitorView.swift');
|
|
324
606
|
(hasDelegateAdaptorShape ? present : missing).push('AppDelegate without @main');
|
|
607
|
+
(hasSceneDelegate ? present : missing).push('SceneDelegate.swift');
|
|
325
608
|
return `present: [${present.join(', ')}]; missing: [${missing.join(', ')}]`;
|
|
326
609
|
}
|
|
327
610
|
// Exported for tests.
|
|
328
611
|
exports.__testables = {
|
|
612
|
+
CONFIGURATION_FOR_CONNECTING_METHOD,
|
|
613
|
+
SUPPORTED_INTERFACE_ORIENTATIONS_METHOD,
|
|
329
614
|
classify,
|
|
330
615
|
describeSignals,
|
|
616
|
+
ensureSupportedInterfaceOrientations,
|
|
331
617
|
hasCustomDelegateBody,
|
|
332
618
|
hasCustomWindowSetup,
|
|
619
|
+
isStockAppDelegate,
|
|
620
|
+
isStockSceneDelegate,
|
|
621
|
+
removeLeftoverStoryboard,
|
|
333
622
|
rewriteAppDelegateForAdaptor,
|
|
334
623
|
scanAndWarn,
|
|
624
|
+
writeSceneDelegate,
|
|
335
625
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrateToModernProject = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const log_1 = require("../log");
|
|
6
|
+
const xcode_1 = require("../util/xcode");
|
|
7
|
+
async function migrateToModernProject(config) {
|
|
8
|
+
var _a;
|
|
9
|
+
const pbxprojPath = (0, path_1.join)(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
|
|
10
|
+
const settingsToAdd = {
|
|
11
|
+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS: 'YES',
|
|
12
|
+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF: 'YES',
|
|
13
|
+
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: 'YES',
|
|
14
|
+
STRING_CATALOG_GENERATE_SYMBOLS: 'YES',
|
|
15
|
+
};
|
|
16
|
+
if ((await config.ios.packageManager) === 'SPM') {
|
|
17
|
+
settingsToAdd['ENABLE_USER_SCRIPT_SANDBOXING'] = 'YES';
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
(0, xcode_1.addMultipleBuildSettings)(pbxprojPath, settingsToAdd);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
log_1.logger.warn(`Could not update the project ${pbxprojPath} to latest apple recomendations: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}. ` +
|
|
24
|
+
`You can manually do so in Xcode`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.migrateToModernProject = migrateToModernProject;
|