1"use strict";
2
3// Expo replaces the empty signing team with appleTeamId. Reverse-ordered mods run after extension creation.
4
5const fs = require("fs");
6const path = require("path");
7const { withDangerousMod, withXcodeProject } = require("expo/config-plugins");
8
9const TARGET_NAME = "expo-sharing-extension";
10
11function stripComments(section) {
12 return Object.fromEntries(
13 Object.entries(section ?? {}).filter(([key]) => !key.endsWith("_comment")),
14 );
15}
16
17function findByName(section, name) {
18 return Object.entries(stripComments(section)).find(([, value]) => value?.name === name) ?? null;
19}
20
21function escapeXml(value) {
22 return value
23 .replaceAll("&", "&")
24 .replaceAll("<", "<")
25 .replaceAll(">", ">")
26 .replaceAll('"', """)
27 .replaceAll("'", "'");
28}
29
30function withDisplayNamePlist(config, displayName) {
31 return withDangerousMod(config, [
32 "ios",
33 (cfg) => {
34 const plistPath = path.join(cfg.modRequest.platformProjectRoot, TARGET_NAME, "Info.plist");
35 if (!fs.existsSync(plistPath)) {
36 throw new Error(
37 `${TARGET_NAME}/Info.plist was not generated. Register withShareExtensionDisplayName before expo-sharing.`,
38 );
39 }
40 const source = fs.readFileSync(plistPath, "utf8");
41 const displayNamePattern = /(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/;
42 if (!displayNamePattern.test(source)) {
43 throw new Error(`Could not update CFBundleDisplayName in ${plistPath}.`);
44 }
45 const next = source.replace(displayNamePattern, `$1${escapeXml(displayName)}$2`);
46 if (next !== source) {
47 fs.writeFileSync(plistPath, next);
48 }
49 return cfg;
50 },
51 ]);
52}
53
54function withExtensionDisplayNameBuildSetting(config, displayName) {
55 return withXcodeProject(config, (cfg) => {
56 const objects = cfg.modResults.hash.project.objects;
57 const targetEntry = findByName(objects.PBXNativeTarget, TARGET_NAME);
58 if (!targetEntry) {
59 throw new Error(
60 `${TARGET_NAME} Xcode target was not generated. Register withShareExtensionDisplayName before expo-sharing.`,
61 );
62 }
63 const target = targetEntry[1];
64 const configurationList = stripComments(objects.XCConfigurationList)[
65 target.buildConfigurationList
66 ];
67 if (!configurationList) {
68 throw new Error(`Could not find build configurations for ${TARGET_NAME}.`);
69 }
70 const buildConfigurations = stripComments(objects.XCBuildConfiguration);
71 for (const reference of configurationList.buildConfigurations ?? []) {
72 const buildConfiguration = buildConfigurations[reference.value];
73 if (buildConfiguration?.buildSettings) {
74 buildConfiguration.buildSettings.INFOPLIST_KEY_CFBundleDisplayName = `"${displayName}"`;
75 }
76 }
77 return cfg;
78 });
79}
80
81module.exports = function withShareExtensionDisplayName(config) {
82 const displayName = config.name;
83 return withExtensionDisplayNameBuildSetting(
84 withDisplayNamePlist(config, displayName),
85 displayName,
86 );
87};