Files
musicland/plugins/withXcode27Compatibility.js
Thomas Demirdjian c8656d6273 heygen
2026-08-04 10:59:27 +02:00

226 lines
7.2 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const {
createRunOncePlugin,
withAppDelegate,
withDangerousMod,
withInfoPlist,
} = require("@expo/config-plugins");
const SCENE_MARKER = "Xcode 27 UIScene lifecycle compatibility";
const DEPLOYMENT_TARGET_MARKER = "Xcode 27 minimum deployment target compatibility";
const SCENE_INTERFACE = `// ${SCENE_MARKER}
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (nonatomic, strong) UIWindow *window;
@end`;
const SCENE_CONFIGURATION = `- (UISceneConfiguration *)application:(UIApplication *)application
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
options:(UISceneConnectionOptions *)options
{
UISceneConfiguration *configuration =
[[UISceneConfiguration alloc] initWithName:@"Default Configuration"
sessionRole:connectingSceneSession.role];
configuration.delegateClass = [SceneDelegate class];
return configuration;
}
`;
const SCENE_IMPLEMENTATION = `@implementation SceneDelegate
- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions
{
UIWindowScene *windowScene = (UIWindowScene *)scene;
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
self.window = appDelegate.window;
self.window.windowScene = windowScene;
[self.window makeKeyAndVisible];
if (connectionOptions.URLContexts.count > 0) {
[self scene:scene openURLContexts:connectionOptions.URLContexts];
}
for (NSUserActivity *userActivity in connectionOptions.userActivities) {
[self scene:scene continueUserActivity:userActivity];
}
}
- (void)sceneDidBecomeActive:(UIScene *)scene
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate applicationDidBecomeActive:UIApplication.sharedApplication];
}
- (void)sceneWillResignActive:(UIScene *)scene
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate applicationWillResignActive:UIApplication.sharedApplication];
}
- (void)sceneWillEnterForeground:(UIScene *)scene
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate applicationWillEnterForeground:UIApplication.sharedApplication];
}
- (void)sceneDidEnterBackground:(UIScene *)scene
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate applicationDidEnterBackground:UIApplication.sharedApplication];
}
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
for (UIOpenURLContext *urlContext in URLContexts) {
NSMutableDictionary<UIApplicationOpenURLOptionsKey, id> *options = [NSMutableDictionary dictionary];
options[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(urlContext.options.openInPlace);
if (urlContext.options.sourceApplication != nil) {
options[UIApplicationOpenURLOptionsSourceApplicationKey] = urlContext.options.sourceApplication;
}
if (urlContext.options.annotation != nil) {
options[UIApplicationOpenURLOptionsAnnotationKey] = urlContext.options.annotation;
}
[appDelegate application:UIApplication.sharedApplication openURL:urlContext.URL options:options];
}
}
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate application:UIApplication.sharedApplication
continueUserActivity:userActivity
restorationHandler:^(NSArray<id<UIUserActivityRestoring>> *restorableObjects) {
}];
}
@end`;
const DEPLOYMENT_TARGET_BLOCK = ` # ${DEPLOYMENT_TARGET_MARKER}
installer.pods_project.targets.each do |target|
target.build_configurations.each do |build_configuration|
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ios_deployment_target
end
end`;
function applySceneLifecycle(contents) {
if (contents.includes(SCENE_MARKER)) {
return contents;
}
const importAnchor = "#import <React/RCTLinkingManager.h>";
const configurationAnchor = "- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge";
if (!contents.includes(importAnchor) || !contents.includes(configurationAnchor)) {
throw new Error(
"withXcode27Compatibility: could not find the Objective-C AppDelegate anchors."
);
}
const withInterface = contents.replace(
importAnchor,
`${importAnchor}\n\n${SCENE_INTERFACE}`
);
const withConfiguration = withInterface.replace(
configurationAnchor,
`${SCENE_CONFIGURATION}\n${configurationAnchor}`
);
return `${withConfiguration.trimEnd()}\n\n${SCENE_IMPLEMENTATION}\n`;
}
function applyDeploymentTarget(contents) {
let patched = contents;
if (!patched.includes("ios_deployment_target =")) {
const platformLine =
"platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'";
if (!patched.includes(platformLine)) {
throw new Error(
"withXcode27Compatibility: could not find the iOS platform declaration."
);
}
patched = patched.replace(
platformLine,
"ios_deployment_target = podfile_properties['ios.deploymentTarget'] || '15.1'\n" +
"platform :ios, ios_deployment_target"
);
}
if (!patched.includes(DEPLOYMENT_TARGET_MARKER)) {
const anchor =
" # This is necessary for Xcode 14, because it signs resource bundles by default";
if (!patched.includes(anchor)) {
throw new Error(
"withXcode27Compatibility: could not find the Podfile post-install anchor."
);
}
patched = patched.replace(
anchor,
`${DEPLOYMENT_TARGET_BLOCK}\n\n${anchor}`
);
}
return patched;
}
const withXcode27Compatibility = (config) => {
config = withInfoPlist(config, (config) => {
config.modResults.UIApplicationSceneManifest = {
UIApplicationSupportsMultipleScenes: false,
UISceneConfigurations: {
UIWindowSceneSessionRoleApplication: [
{
UISceneConfigurationName: "Default Configuration",
UISceneDelegateClassName: "SceneDelegate",
},
],
},
};
return config;
});
config = withAppDelegate(config, (config) => {
if (!["objc", "objcpp"].includes(config.modResults.language)) {
throw new Error(
"withXcode27Compatibility: expected an Objective-C AppDelegate."
);
}
config.modResults.contents = applySceneLifecycle(config.modResults.contents);
return config;
});
config = withDangerousMod(config, [
"ios",
async (config) => {
const podfilePath = path.join(
config.modRequest.platformProjectRoot,
"Podfile"
);
const podfile = fs.readFileSync(podfilePath, "utf8");
const patched = applyDeploymentTarget(podfile);
if (patched !== podfile) {
fs.writeFileSync(podfilePath, patched);
}
return config;
},
]);
return config;
};
module.exports = createRunOncePlugin(
withXcode27Compatibility,
"with-xcode-27-compatibility",
"1.0.0"
);