@capacitor/cli 9.0.0-alpha.7 → 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.
Files changed (45) hide show
  1. package/assets/android-template.tar.gz +0 -0
  2. package/assets/capacitor-cordova-android-plugins.tar.gz +0 -0
  3. package/assets/capacitor-cordova-ios-plugins.tar.gz +0 -0
  4. package/assets/ios-pods-template.tar.gz +0 -0
  5. package/assets/ios-spm-template.tar.gz +0 -0
  6. package/dist/android/network-security-config.js +230 -0
  7. package/dist/android/update.js +5 -1
  8. package/dist/common.js +1 -1
  9. package/dist/config.js +4 -3
  10. package/dist/cordova.js +8 -17
  11. package/dist/declarations.d.ts +8 -41
  12. package/dist/index.js +4 -14
  13. package/dist/ios/common.js +1 -1
  14. package/dist/ios/update.js +9 -17
  15. package/dist/ipc.js +51 -11
  16. package/dist/plugin.js +1 -1
  17. package/dist/run-context/declared.js +54 -0
  18. package/dist/run-context/index.js +111 -0
  19. package/dist/run-context/registry.js +45 -0
  20. package/dist/run-context/signals.js +115 -0
  21. package/dist/run-context/types.js +2 -0
  22. package/dist/run-context/unknown.js +46 -0
  23. package/dist/run-context/version.js +74 -0
  24. package/dist/tasks/copy.js +3 -1
  25. package/dist/tasks/doctor.js +4 -1
  26. package/dist/tasks/list.js +1 -1
  27. package/dist/tasks/migrate-debug-xcconfig.js +60 -0
  28. package/dist/tasks/migrate-spm.js +0 -1
  29. package/dist/tasks/migrate-swiftui.js +344 -54
  30. package/dist/tasks/migrate-to-modern-project.js +27 -0
  31. package/dist/tasks/migrate.js +41 -4
  32. package/dist/tasks/run.js +8 -3
  33. package/dist/tasks/telemetry.js +8 -1
  34. package/dist/telemetry.js +303 -57
  35. package/dist/util/capacitor-package.js +95 -146
  36. package/dist/util/cordova-ios.js +13 -8
  37. package/dist/util/js.js +1 -1
  38. package/dist/util/monorepotools.js +2 -2
  39. package/dist/util/node.js +3 -3
  40. package/dist/util/npm.js +120 -0
  41. package/dist/util/spm.js +35 -25
  42. package/dist/util/subprocess.js +2 -2
  43. package/dist/util/xcode.js +193 -2
  44. package/dist/util/xml.js +1 -1
  45. 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
- 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;
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 CapacitorView.swift.', async () => {
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
- await (0, common_1.runTask)('Registering App.swift and CapacitorView.swift with the Xcode App target.', async () => {
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(config);
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 false;
320
+ return null;
132
321
  const openIdx = source.indexOf('{', match.index);
133
322
  if (openIdx === -1)
134
- return false;
323
+ return null;
135
324
  const closeIdx = findMatchingBrace(source, openIdx);
136
325
  if (closeIdx === null)
137
- return false;
138
- const body = source.slice(openIdx + 1, closeIdx);
139
- const codeLines = body
326
+ return null;
327
+ return source.slice(openIdx + 1, closeIdx);
328
+ }
329
+ function codeLines(body) {
330
+ return body
140
331
  .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'));
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(config) {
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
- 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');
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, the UIWindow property, and configurationForConnecting from AppDelegate.');
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(' • 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));
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
- if (!(0, fs_extra_1.existsSync)(appPath) || !(0, fs_extra_1.existsSync)(capacitorViewPath)) {
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
- 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.' };
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
- return { status: 'rewritten', source: withoutSceneConfiguration };
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
- 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';
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;