1"use strict";
2
3// expo-sharing intentionally uses a fixed target name and also uses that
4// internal target name as CFBundleDisplayName. Brand the extension while
5// leaving its initially-empty signing team intact: Expo uses that state to
6// select ios.appleTeamId and enable first-time profile provisioning.
7//
8// ORDERING: list this plugin BEFORE expo-sharing. Expo runs same-type mods in
9// reverse registration order, so this executes after the extension exists.
10
11const fs = require("fs");
12const path = require("path");
13const { withDangerousMod, withXcodeProject } = require("expo/config-plugins");
14
15const TARGET_NAME = "expo-sharing-extension";
16
17function stripComments(section) {
18 return Object.fromEntries(
19 Object.entries(section ?? {}).filter(([key]) => !key.endsWith("_comment")),
20 );
21}
22
23function findByName(section, name) {
24 return Object.entries(stripComments(section)).find(([, value]) => value?.name === name) ?? null;
25}
26
27function escapeXml(value) {
28 return value
29 .replaceAll("&", "&")
30 .replaceAll("<", "&lt;")
31 .replaceAll(">", "&gt;")
32 .replaceAll('"', "&quot;")
33 .replaceAll("'", "&apos;");
34}
35
36function withDisplayNamePlist(config, displayName) {
37 return withDangerousMod(config, [
38 "ios",
39 (cfg) => {
40 const plistPath = path.join(cfg.modRequest.platformProjectRoot, TARGET_NAME, "Info.plist");
41 if (!fs.existsSync(plistPath)) {
42 throw new Error(
43 `${TARGET_NAME}/Info.plist was not generated. Register withShareExtensionDisplayName before expo-sharing.`,
44 );
45 }
46 const source = fs.readFileSync(plistPath, "utf8");
47 const displayNamePattern = /(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/;
48 if (!displayNamePattern.test(source)) {
49 throw new Error(`Could not update CFBundleDisplayName in ${plistPath}.`);
50 }
51 const next = source.replace(displayNamePattern, `$1${escapeXml(displayName)}$2`);
52 if (next !== source) {
53 fs.writeFileSync(plistPath, next);
54 }
55 return cfg;
56 },
57 ]);
58}
59
60function withExtensionDisplayNameBuildSetting(config, displayName) {
61 return withXcodeProject(config, (cfg) => {
62 const objects = cfg.modResults.hash.project.objects;
63 const targetEntry = findByName(objects.PBXNativeTarget, TARGET_NAME);
64 if (!targetEntry) {
65 throw new Error(
66 `${TARGET_NAME} Xcode target was not generated. Register withShareExtensionDisplayName before expo-sharing.`,
67 );
68 }
69 const target = targetEntry[1];
70 const configurationList = stripComments(objects.XCConfigurationList)[
71 target.buildConfigurationList
72 ];
73 if (!configurationList) {
74 throw new Error(`Could not find build configurations for ${TARGET_NAME}.`);
75 }
76 const buildConfigurations = stripComments(objects.XCBuildConfiguration);
77 for (const reference of configurationList.buildConfigurations ?? []) {
78 const buildConfiguration = buildConfigurations[reference.value];
79 if (buildConfiguration?.buildSettings) {
80 buildConfiguration.buildSettings.INFOPLIST_KEY_CFBundleDisplayName = `"${displayName}"`;
81 }
82 }
83 return cfg;
84 });
85}
86
87module.exports = function withShareExtensionDisplayName(config) {
88 const displayName = config.name;
89 return withExtensionDisplayNameBuildSetting(
90 withDisplayNamePlist(config, displayName),
91 displayName,
92 );
93};