@bacons/apple-targets 0.1.19 → 0.2.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.
@@ -38,6 +38,9 @@ const withTargetsDir = (config, _props) => {
38
38
  else if (typeof targetConfig !== "object") {
39
39
  throw new Error(`Expected target config to be an object or function that returns an object, but got ${typeof targetConfig}`);
40
40
  }
41
+ if (!evaluatedTargetConfigObject.type) {
42
+ throw new Error(`Expected target config to have a 'type' property denoting the type of target it is, e.g. 'widget'`);
43
+ }
41
44
  config = (0, withWidget_1.default)(config, {
42
45
  appleTeamId,
43
46
  ...evaluatedTargetConfigObject,
@@ -45,30 +45,32 @@ function createLogQueue() {
45
45
  }
46
46
  // Queue up logs so they only run when prebuild is actually running and not during standard config reads.
47
47
  const prebuildLogQueue = createLogQueue();
48
- function kebabToCamelCase(str) {
49
- return str.replace(/-([a-z])/g, function (g) {
50
- return g[1].toUpperCase();
51
- });
52
- }
53
48
  const withWidget = (config, props) => {
54
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
49
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
55
50
  prebuildLogQueue.add(() => warnOnce((0, chalk_1.default) `\nUsing experimental Config Plugin {bold @bacons/apple-targets} that is subject to breaking changes.`));
56
51
  // TODO: Magically based on the top-level folders in the `ios-widgets/` folder
57
52
  if (props.icon && !/https?:\/\//.test(props.icon)) {
58
53
  props.icon = path_1.default.join(props.directory, props.icon);
59
54
  }
60
- const widgetDir = path_1.default
61
- .basename(props.directory)
62
- .replace(/\/+$/, "")
63
- .replace(/^\/+/, "");
64
- const widget = kebabToCamelCase(widgetDir);
65
- const widgetFolderAbsolutePath = path_1.default.join((_b = (_a = config._internal) === null || _a === void 0 ? void 0 : _a.projectRoot) !== null && _b !== void 0 ? _b : "", props.directory);
55
+ // This value should be used for the target name and other internal uses.
56
+ const targetDirName = path_1.default.basename(path_1.default.dirname(props.configPath));
57
+ // Sanitized for general usage. This name just needs to resemble the input value since it shouldn't be used for user-facing values such as the home screen or app store.
58
+ const productName = sanitizeNameForNonDisplayUse(props.name || targetDirName) ||
59
+ sanitizeNameForNonDisplayUse(targetDirName) ||
60
+ sanitizeNameForNonDisplayUse(props.type);
61
+ // This should never happen.
62
+ if (!productName) {
63
+ throw new Error(`[bacons/apple-targets][${props.type}] Target name does not contain any valid characters: ${targetDirName}`);
64
+ }
65
+ // TODO: Are there characters that aren't allowed in `CFBundleDisplayName`?
66
+ const targetDisplayName = (_a = props.name) !== null && _a !== void 0 ? _a : productName;
67
+ const targetDirAbsolutePath = path_1.default.join((_c = (_b = config._internal) === null || _b === void 0 ? void 0 : _b.projectRoot) !== null && _c !== void 0 ? _c : "", props.directory);
66
68
  const entitlementsFiles = (0, glob_1.sync)("*.entitlements", {
67
69
  absolute: true,
68
- cwd: widgetFolderAbsolutePath,
70
+ cwd: targetDirAbsolutePath,
69
71
  });
70
72
  if (entitlementsFiles.length > 1) {
71
- throw new Error(`[bacons/apple-targets][${props.type}] Found more than one '*.entitlements' file in ${widgetFolderAbsolutePath}`);
73
+ throw new Error(`[bacons/apple-targets][${props.type}] Found more than one '*.entitlements' file in ${targetDirAbsolutePath}`);
72
74
  }
73
75
  let entitlementsJson = props.entitlements;
74
76
  if (entitlementsJson) {
@@ -87,7 +89,7 @@ const withWidget = (config, props) => {
87
89
  if (!associatedDomains ||
88
90
  !Array.isArray(associatedDomains) ||
89
91
  associatedDomains.length === 0) {
90
- warnOnce((0, chalk_1.default) `{yellow [${widget}]} Apple App Clip may require the associated domains entitlement but none were found in the Expo config.\nExample:\n${JSON.stringify({
92
+ warnOnce((0, chalk_1.default) `{yellow [${targetDirName}]} Apple App Clip may require the associated domains entitlement but none were found in the Expo config.\nExample:\n${JSON.stringify({
91
93
  ios: {
92
94
  associatedDomains: [`applinks:placeholder.expo.app`],
93
95
  },
@@ -110,7 +112,7 @@ const withWidget = (config, props) => {
110
112
  .filter(Boolean);
111
113
  const unique = [...new Set(sanitizedUrls)];
112
114
  if (unique.length) {
113
- warnOnce((0, chalk_1.default) `{gray [${widget}]} Apple App Clip expo-target.config.js missing associated domains entitlements in the target config. Using the following defaults:\n${JSON.stringify({
115
+ warnOnce((0, chalk_1.default) `{gray [${targetDirName}]} Apple App Clip expo-target.config.js missing associated domains entitlements in the target config. Using the following defaults:\n${JSON.stringify({
114
116
  entitlements: {
115
117
  [associatedDomainsKey]: [
116
118
  `appclips:${unique[0] || "mywebsite.expo.app"}`,
@@ -137,13 +139,13 @@ const withWidget = (config, props) => {
137
139
  // Then set the target app groups to match the main app.
138
140
  entitlements[APP_GROUP_KEY] = mainAppGroups;
139
141
  prebuildLogQueue.add(() => {
140
- logOnce((0, chalk_1.default) `[${widget}] Syncing app groups with main app. {dim Define entitlements[${JSON.stringify(APP_GROUP_KEY)}] in the {bold expo-target.config} file to override.}`);
142
+ logOnce((0, chalk_1.default) `[${targetDirName}] Syncing app groups with main app. {dim Define entitlements[${JSON.stringify(APP_GROUP_KEY)}] in the {bold expo-target.config} file to override.}`);
141
143
  });
142
144
  }
143
145
  else {
144
146
  prebuildLogQueue.add(() => {
145
147
  var _a, _b;
146
- return warnOnce((0, chalk_1.default) `{yellow [${widget}]} Apple target may require the App Groups entitlement but none were found in the Expo config.\nExample:\n${JSON.stringify({
148
+ return warnOnce((0, chalk_1.default) `{yellow [${targetDirName}]} Apple target may require the App Groups entitlement but none were found in the Expo config.\nExample:\n${JSON.stringify({
147
149
  ios: {
148
150
  entitlements: {
149
151
  [APP_GROUP_KEY]: [
@@ -168,11 +170,11 @@ const withWidget = (config, props) => {
168
170
  const GENERATED_ENTITLEMENTS_FILE_NAME = "generated.entitlements";
169
171
  const entitlementsFilePath = (_a = entitlementsFiles[0]) !== null && _a !== void 0 ? _a :
170
172
  // Use the name `generated` to help indicate that this file should be in sync with the config
171
- path_1.default.join(widgetFolderAbsolutePath, GENERATED_ENTITLEMENTS_FILE_NAME);
173
+ path_1.default.join(targetDirAbsolutePath, GENERATED_ENTITLEMENTS_FILE_NAME);
172
174
  if (entitlementsFiles[0]) {
173
- const relativeName = path_1.default.relative(widgetFolderAbsolutePath, entitlementsFiles[0]);
175
+ const relativeName = path_1.default.relative(targetDirAbsolutePath, entitlementsFiles[0]);
174
176
  if (relativeName !== GENERATED_ENTITLEMENTS_FILE_NAME) {
175
- console.log(`[${widget}] Replacing ${path_1.default.relative(widgetFolderAbsolutePath, entitlementsFiles[0])} with entitlements JSON from config`);
177
+ console.log(`[${targetDirName}] Replacing ${path_1.default.relative(targetDirAbsolutePath, entitlementsFiles[0])} with entitlements JSON from config`);
176
178
  }
177
179
  }
178
180
  fs_1.default.writeFileSync(entitlementsFilePath, plist_1.default.build(entitlementsJson));
@@ -190,7 +192,7 @@ const withWidget = (config, props) => {
190
192
  "ios",
191
193
  async (config) => {
192
194
  prebuildLogQueue.flush();
193
- fs_1.default.mkdirSync(widgetFolderAbsolutePath, { recursive: true });
195
+ fs_1.default.mkdirSync(targetDirAbsolutePath, { recursive: true });
194
196
  const files = [
195
197
  ["Info.plist", (0, target_1.getTargetInfoPlistForType)(props.type)],
196
198
  ];
@@ -208,7 +210,7 @@ const withWidget = (config, props) => {
208
210
  // );
209
211
  // }
210
212
  files.forEach(([filename, content]) => {
211
- const filePath = path_1.default.join(widgetFolderAbsolutePath, filename);
213
+ const filePath = path_1.default.join(targetDirAbsolutePath, filename);
212
214
  if (!fs_1.default.existsSync(filePath)) {
213
215
  fs_1.default.writeFileSync(filePath, content);
214
216
  }
@@ -216,41 +218,54 @@ const withWidget = (config, props) => {
216
218
  return config;
217
219
  },
218
220
  ]);
219
- const targetName = (_c = props.name) !== null && _c !== void 0 ? _c : widget;
220
221
  const mainAppBundleId = config.ios.bundleIdentifier;
221
- const bundleId = ((_d = props.bundleIdentifier) === null || _d === void 0 ? void 0 : _d.startsWith("."))
222
- ? mainAppBundleId + props.bundleIdentifier
223
- : (_e = props.bundleIdentifier) !== null && _e !== void 0 ? _e : `${mainAppBundleId}.${props.type === "clip"
224
- ? // Use a more standardized bundle identifier for App Clips.
225
- "clip"
226
- : getSanitizedBundleIdentifier(targetName)}`;
227
- const deviceFamilies = ((_f = config.ios) === null || _f === void 0 ? void 0 : _f.isTabletOnly)
222
+ const bundleId = (() => {
223
+ var _a;
224
+ // Support the bundle identifier being appended to the main app's bundle identifier.
225
+ if ((_a = props.bundleIdentifier) === null || _a === void 0 ? void 0 : _a.startsWith(".")) {
226
+ return mainAppBundleId + props.bundleIdentifier;
227
+ }
228
+ else if (props.bundleIdentifier) {
229
+ return props.bundleIdentifier;
230
+ }
231
+ if (props.type === "clip") {
232
+ // Use a more standardized bundle identifier for App Clips.
233
+ return mainAppBundleId + ".clip";
234
+ }
235
+ let bundleId = mainAppBundleId;
236
+ bundleId += ".";
237
+ // Generate the bundle identifier. This logic needs to remain generally stable since it's used for a permanent value.
238
+ // Key here is simplicity and predictability since it's already appended to the main app's bundle identifier.
239
+ return mainAppBundleId + "." + getSanitizedBundleIdentifier(props.type);
240
+ })();
241
+ const deviceFamilies = ((_d = config.ios) === null || _d === void 0 ? void 0 : _d.isTabletOnly)
228
242
  ? ["tablet"]
229
- : ((_g = config.ios) === null || _g === void 0 ? void 0 : _g.supportsTablet)
243
+ : ((_e = config.ios) === null || _e === void 0 ? void 0 : _e.supportsTablet)
230
244
  ? ["phone", "tablet"]
231
245
  : ["phone"];
232
246
  (0, withXcodeChanges_1.withXcodeChanges)(config, {
247
+ productName,
233
248
  configPath: props.configPath,
234
- name: targetName,
249
+ name: targetDisplayName,
235
250
  cwd: "../" +
236
251
  path_1.default.relative(config._internal.projectRoot, path_1.default.resolve(props.directory)),
237
- deploymentTarget: (_h = props.deploymentTarget) !== null && _h !== void 0 ? _h : DEFAULT_DEPLOYMENT_TARGET,
252
+ deploymentTarget: (_f = props.deploymentTarget) !== null && _f !== void 0 ? _f : DEFAULT_DEPLOYMENT_TARGET,
238
253
  bundleId,
239
254
  icon: props.icon,
240
255
  orientation: config.orientation,
241
- hasAccentColor: !!((_j = props.colors) === null || _j === void 0 ? void 0 : _j.$accent),
256
+ hasAccentColor: !!((_g = props.colors) === null || _g === void 0 ? void 0 : _g.$accent),
242
257
  deviceFamilies,
243
258
  // @ts-expect-error: who cares
244
- currentProjectVersion: ((_k = config.ios) === null || _k === void 0 ? void 0 : _k.buildNumber) || 1,
259
+ currentProjectVersion: ((_h = config.ios) === null || _h === void 0 ? void 0 : _h.buildNumber) || 1,
245
260
  frameworks: (0, target_1.getFrameworksForType)(props.type).concat(props.frameworks || []),
246
261
  type: props.type,
247
262
  teamId: props.appleTeamId,
248
- exportJs: (_l = props.exportJs) !== null && _l !== void 0 ? _l :
263
+ exportJs: (_j = props.exportJs) !== null && _j !== void 0 ? _j :
249
264
  // Assume App Clips are used for React Native.
250
265
  props.type === "clip",
251
266
  });
252
267
  config = (0, withEasCredentials_1.withEASTargets)(config, {
253
- targetName,
268
+ targetName: productName,
254
269
  bundleIdentifier: bundleId,
255
270
  entitlements: entitlementsJson,
256
271
  });
@@ -306,3 +321,9 @@ function getSanitizedBundleIdentifier(value) {
306
321
  // Can have empty segments (e.g. com.example..app).
307
322
  return value.replace(/(^[^a-zA-Z.-]|[^a-zA-Z0-9-.])/g, "-");
308
323
  }
324
+ function sanitizeNameForNonDisplayUse(name) {
325
+ return name
326
+ .replace(/[\W_]+/g, "")
327
+ .normalize("NFD")
328
+ .replace(/[\u0300-\u036f]/g, "");
329
+ }
@@ -2,6 +2,8 @@ import { ConfigPlugin } from "@expo/config-plugins";
2
2
  import { ExtensionType } from "./target";
3
3
  export type XcodeSettings = {
4
4
  name: string;
5
+ /** Name used for internal purposes. This has more strict rules and should be generated. */
6
+ productName: string;
5
7
  /** Directory relative to the project root, (i.e. outside of the `ios` directory) where the widget code should live. */
6
8
  cwd: string;
7
9
  bundleId: string;
@@ -684,7 +684,7 @@ async function applyXcodeChanges(config, project, props) {
684
684
  });
685
685
  }
686
686
  const targets = getExtensionTargets();
687
- const productName = props.name;
687
+ const productName = props.productName;
688
688
  let targetToUpdate = (_a = targets.find((target) => target.props.productName === productName)) !== null && _a !== void 0 ? _a : targets[0];
689
689
  if (targetToUpdate) {
690
690
  console.log(`Target "${targetToUpdate.props.productName}" already exists, updating instead of creating a new one`);
@@ -833,7 +833,7 @@ async function applyXcodeChanges(config, project, props) {
833
833
  ? "wrapper.extensionkit-extension"
834
834
  : "wrapper.app-extension",
835
835
  includeInIndex: 0,
836
- path: productName + (isExtension ? ".appex" : ".app"),
836
+ path: props.name + (isExtension ? ".appex" : ".app"),
837
837
  sourceTree: "BUILT_PRODUCTS_DIR",
838
838
  }),
839
839
  settings: {
@@ -845,7 +845,7 @@ async function applyXcodeChanges(config, project, props) {
845
845
  appExtensionBuildFile.props.fileRef);
846
846
  targetToUpdate = project.rootObject.createNativeTarget({
847
847
  buildConfigurationList: createConfigurationListForType(project, props),
848
- name: productName,
848
+ name: props.name,
849
849
  productName,
850
850
  // @ts-expect-error
851
851
  productReference: appExtensionBuildFile.props.fileRef /* alphaExtension.appex */,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacons/apple-targets",
3
- "version": "0.1.19",
3
+ "version": "0.2.0",
4
4
  "description": "Generate Apple Targets with Expo Prebuild",
5
5
  "main": "build/ExtensionStorage.js",
6
6
  "types": "build/ExtensionStorage.d.ts",