@bacons/apple-targets 0.1.14 → 0.1.16

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.
package/README.md CHANGED
@@ -77,7 +77,7 @@ module.exports = {
77
77
  thing: "../assets/thing.png",
78
78
  },
79
79
 
80
- // The iOS version fot the target. Defaults to 18.1
80
+ // The iOS version fot the target. Defaults to 18.0
81
81
  deploymentTarget: "15.1",
82
82
 
83
83
  // Optional bundle identifier for the target. Will default to a sanitized version of the root project bundle id + target name.
@@ -174,6 +174,18 @@ end
174
174
 
175
175
  The name of the target must match the name of the target directory.
176
176
 
177
+ ## `_shared`
178
+
179
+ Some files are required to be linked to both your target and the main target. To support this, you can add a top-level `_shared` directory. Any file in this directory will be linked to both the main target and the sub-target. You'll need to re-run prebuild every time you add, rename, or remove a file in this directory.
180
+
181
+ This is especially useful for Live Activities and Intents.
182
+
183
+ ## `exportJs`
184
+
185
+ The `exportJs` option should be used when the target uses React Native (App Clip, Share extension). It works by linking the main target's `Bundle React Native code and images` build phase to the target. This will ensure that production builds (`Release`) bundle the main JS entry file with Metro, and embed the bundle/assets for offline use.
186
+
187
+ To detect which target is being built, you can read the bundle identifier using `expo-application`.
188
+
177
189
  ## Examples
178
190
 
179
191
  ### `widget`
@@ -392,3 +404,44 @@ let index = defaults?.string(forKey: "myKey")
392
404
  ## Xcode parsing
393
405
 
394
406
  This plugin makes use of my proprietary Xcode parsing library, [`@bacons/xcode`](https://github.com/evanbacon/xcode). It's mostly typed, very untested, and possibly full of bugs––however, it's still 10x nicer than the alternative.
407
+
408
+ ## Generated App Intents
409
+
410
+ You can make any type of App Intent that you'd like with this plugin. However, the majority of app intents simply launch a URL (specifically a universal link for the same app). Expo Router apps have automatic deep linking and websites, meaning you can get to universal links pretty easily. As such, I've built in support for generating link-opening app intents from JSON.
411
+
412
+ These actions will show up in the control center, siri suggestions, Shortcuts, and can be added to the bottom of the lock screen in iOS 18.
413
+
414
+ ```js
415
+ {
416
+ intents: [
417
+ {
418
+ // Label that will show up in the control center.
419
+ label: "Bacon",
420
+ // Name of an SF Symbol (no custom icons are supported).
421
+ icon: "laurel.leading",
422
+ // Universal link to open.
423
+ url: "https://pillarvalley.netlify.app/settings",
424
+ // Auxiliary data
425
+ displayName: "Open Bacon AI",
426
+ description: "Launch the Bacon AI app.",
427
+ },
428
+ ];
429
+ }
430
+ ```
431
+
432
+ You can add multiple intents, they'll be generated during prebuild to the `[target]/_shared/generated-intents.swift` file. This file will have a comment block in it like:
433
+
434
+ ```swift
435
+ // TODO: These must be added to the WidgetBundle manually. They need to be linked outside of the _shared folder.
436
+ // @main
437
+ // struct exportWidgets: WidgetBundle {
438
+ // var body: some Widget {
439
+ // widgetControl0()
440
+ // widgetControl1()
441
+ // }
442
+ // }
443
+ ```
444
+
445
+ You should copy the generated intents into your main `WidgetBundle` struct.
446
+
447
+ This system is subject to change with the default intended behavior to be that anywhere a SF Symbol can be used to launch a deep link should be populated by this feature.
package/build/config.d.ts CHANGED
@@ -84,7 +84,7 @@ export type Config = {
84
84
  * @example ["UserNotifications", "Intents"]
85
85
  */
86
86
  frameworks?: string[];
87
- /** Deployment iOS version for the target. Defaults to `18.1` */
87
+ /** Deployment iOS version for the target. Defaults to `18.0` */
88
88
  deploymentTarget?: string;
89
89
  /** Apple team ID to use for signing the target. Defaults to whatever is used in the main App target. */
90
90
  appleTeamId?: string;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEASCredentialsForXcodeProject = exports.withAutoEasExtensionCredentials = exports.withEASTargets = void 0;
4
4
  const target_1 = require("./target");
5
5
  const withXcparse_1 = require("./withXcparse");
6
+ const debug = require("debug")("expo:target:eas");
6
7
  function safeSet(obj, key, value) {
7
8
  const segments = key.split(".");
8
9
  const last = segments.pop();
@@ -17,27 +18,24 @@ function safeSet(obj, key, value) {
17
18
  }
18
19
  return obj;
19
20
  }
21
+ // TODO: This should all go into EAS instead.
20
22
  const withEASTargets = (config, { bundleIdentifier, targetName, entitlements }) => {
21
23
  // Extra EAS targets
22
24
  safeSet(config, "extra.eas.build.experimental.ios.appExtensions", []);
23
- const existing = config.extra.eas.build.experimental.ios.appExtensions.findIndex((ext) => ext.targetName === targetName);
25
+ const existing = config.extra.eas.build.experimental.ios.appExtensions.findIndex((ext) => ext.bundleIdentifier === bundleIdentifier);
26
+ const settings = {
27
+ bundleIdentifier,
28
+ targetName,
29
+ entitlements,
30
+ };
24
31
  if (existing > -1) {
25
- config.extra.eas.build.experimental.ios.appExtensions[existing] = {
26
- ...config.extra.eas.build.experimental.ios.appExtensions[existing],
27
- bundleIdentifier,
28
- entitlements: {
29
- ...config.extra.eas.build.experimental.ios.appExtensions[existing]
30
- .entitlements,
31
- ...entitlements,
32
- },
33
- };
32
+ debug("Found existing EAS target with bundle identifier: %s", bundleIdentifier);
33
+ debug("Using new settings: %o", settings);
34
+ config.extra.eas.build.experimental.ios.appExtensions[existing] = settings;
34
35
  }
35
36
  else {
36
- config.extra.eas.build.experimental.ios.appExtensions.push({
37
- bundleIdentifier,
38
- targetName,
39
- entitlements,
40
- });
37
+ debug("Adding new iOS target for code signing with EAS: %o", settings);
38
+ config.extra.eas.build.experimental.ios.appExtensions.push(settings);
41
39
  // "appExtensions": [
42
40
  // {
43
41
  // "targetName": "widgets",
@@ -15,7 +15,7 @@ const withIosIcon_1 = require("./icon/withIosIcon");
15
15
  const target_1 = require("./target");
16
16
  const withEasCredentials_1 = require("./withEasCredentials");
17
17
  const withXcodeChanges_1 = require("./withXcodeChanges");
18
- const DEFAULT_DEPLOYMENT_TARGET = "18.1";
18
+ const DEFAULT_DEPLOYMENT_TARGET = "18.0";
19
19
  function memoize(fn) {
20
20
  const cache = new Map();
21
21
  return ((...args) => {
@@ -12,6 +12,7 @@ const target_1 = require("./target");
12
12
  const XCBuildConfiguration_json_1 = __importDefault(require("./template/XCBuildConfiguration.json"));
13
13
  const TemplateBuildSettings = XCBuildConfiguration_json_1.default;
14
14
  const withXcparse_1 = require("./withXcparse");
15
+ const assert_1 = __importDefault(require("assert"));
15
16
  const withXcodeChanges = (config, props) => {
16
17
  return (0, withXcparse_1.withXcodeProjectBeta)(config, (config) => {
17
18
  // @ts-ignore
@@ -634,7 +635,8 @@ function createConfigurationListForType(project, props) {
634
635
  }
635
636
  }
636
637
  async function applyXcodeChanges(config, project, props) {
637
- var _a;
638
+ var _a, _b;
639
+ var _c;
638
640
  const mainAppTarget = (0, target_1.getMainAppTarget)(project);
639
641
  // Special setting for share extensions.
640
642
  if ((0, target_1.needsEmbeddedSwift)(props.type)) {
@@ -741,14 +743,20 @@ async function applyXcodeChanges(config, project, props) {
741
743
  const currentShellScript = target.props.buildPhases.find((phase) => xcode_1.PBXShellScriptBuildPhase.is(phase) &&
742
744
  phase.props.name === "Bundle React Native code and images");
743
745
  if (!currentShellScript) {
744
- target.createBuildPhase(xcode_1.PBXShellScriptBuildPhase, {
745
- ...shellScript.props,
746
- });
746
+ // Link the same build script across targets to simplify updates.
747
+ target.props.buildPhases.push(shellScript);
748
+ // Alternatively, create a duplicate.
749
+ // target.createBuildPhase(PBXShellScriptBuildPhase, {
750
+ // ...shellScript.props,
751
+ // });
747
752
  }
748
753
  else {
749
- for (const key in shellScript.props) {
750
- // @ts-expect-error
751
- currentShellScript.props[key] = shellScript.props[key];
754
+ // If there already is a bundler shell script and it's not the one from the main target, then update it.
755
+ if (currentShellScript.uuid !== shellScript.uuid) {
756
+ for (const key in shellScript.props) {
757
+ // @ts-expect-error
758
+ currentShellScript.props[key] = shellScript.props[key];
759
+ }
752
760
  }
753
761
  }
754
762
  }
@@ -831,8 +839,13 @@ async function applyXcodeChanges(config, project, props) {
831
839
  fs_1.default.statSync(path_1.default.join(assetsDir, file)).isDirectory())
832
840
  .map((file) => path_1.default.join("assets", file));
833
841
  const protectedGroup = ensureProtectedGroup(project, path_1.default.dirname(props.cwd));
834
- if (!protectedGroup.props.children.find((child) => child.props.path === path_1.default.basename(props.cwd))) {
835
- const syncRootGroup = xcode_1.PBXFileSystemSynchronizedRootGroup.create(project, {
842
+ const sharedAssets = (0, glob_1.sync)("_shared/*", {
843
+ absolute: false,
844
+ cwd: magicCwd,
845
+ });
846
+ let syncRootGroup = protectedGroup.props.children.find((child) => child.props.path === path_1.default.basename(props.cwd));
847
+ if (!syncRootGroup) {
848
+ syncRootGroup = xcode_1.PBXFileSystemSynchronizedRootGroup.create(project, {
836
849
  path: path_1.default.basename(props.cwd),
837
850
  exceptions: [
838
851
  xcode_1.PBXFileSystemSynchronizedBuildFileExceptionSet.create(project, {
@@ -859,6 +872,23 @@ async function applyXcodeChanges(config, project, props) {
859
872
  targetToUpdate.props.fileSystemSynchronizedGroups.push(syncRootGroup);
860
873
  protectedGroup.props.children.push(syncRootGroup);
861
874
  }
875
+ // If there's a `_shared` folder, create a PBXFileSystemSynchronizedBuildFileExceptionSet and set the `target` to the main app target. Then add exceptions to the new target's PBXFileSystemSynchronizedRootGroup's exceptions. Finally, ensure the relative paths for each file in the _shared folder are added to the `membershipExceptions` array.
876
+ (0, assert_1.default)(syncRootGroup instanceof xcode_1.PBXFileSystemSynchronizedRootGroup);
877
+ (_b = (_c = syncRootGroup.props).exceptions) !== null && _b !== void 0 ? _b : (_c.exceptions = []);
878
+ const existingExceptionSet = syncRootGroup.props.exceptions.find((exception) => exception instanceof xcode_1.PBXFileSystemSynchronizedBuildFileExceptionSet &&
879
+ exception.props.target === mainAppTarget);
880
+ if (sharedAssets.length) {
881
+ const exceptionSet = existingExceptionSet ||
882
+ xcode_1.PBXFileSystemSynchronizedBuildFileExceptionSet.create(project, {
883
+ target: mainAppTarget,
884
+ });
885
+ exceptionSet.props.membershipExceptions = sharedAssets.sort();
886
+ syncRootGroup.props.exceptions.push(exceptionSet);
887
+ }
888
+ else {
889
+ // Remove the exception set if there are no shared assets.
890
+ existingExceptionSet === null || existingExceptionSet === void 0 ? void 0 : existingExceptionSet.removeFromProject();
891
+ }
862
892
  applyDevelopmentTeamIdToTargets();
863
893
  syncMarketingVersions();
864
894
  return project;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacons/apple-targets",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Generate Apple Targets with Expo Prebuild",
5
5
  "main": "build/ExtensionStorage.js",
6
6
  "types": "build/ExtensionStorage.d.ts",
@@ -36,12 +36,15 @@
36
36
  "@react-native/normalize-colors": "^0.76.1",
37
37
  "glob": "^10.2.6",
38
38
  "@bacons/xcode": "1.0.0-alpha.24",
39
- "fs-extra": "^11.2.0"
39
+ "fs-extra": "^11.2.0",
40
+ "debug": "^4.3.4"
40
41
  },
41
42
  "devDependencies": {
43
+ "@types/debug": "^4.1.7",
42
44
  "@types/fs-extra": "^11.0.4",
43
45
  "@types/glob": "^8.1.0",
44
46
  "@expo/babel-preset-cli": "^0.3.1",
47
+ "chalk": "^4.0.0",
45
48
  "jest-environment-node": "^26"
46
49
  }
47
50
  }