@bacons/apple-targets 0.1.16 → 0.1.17

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.
@@ -20,7 +20,7 @@ const withTargetsDir = (config, _props) => {
20
20
  hasWarned = true;
21
21
  console.warn((0, chalk_1.default) `{yellow [bacons/apple-targets]} Expo config is missing required {cyan ios.appleTeamId} property. Find this in Xcode and add to the Expo Config to correct. iOS builds may fail until this is corrected.`);
22
22
  }
23
- const targets = (0, glob_1.sync)(`${root}/${match}/expo-target.config.@(json|js)`, {
23
+ const targets = (0, glob_1.globSync)(`${root}/${match}/expo-target.config.@(json|js)`, {
24
24
  // const targets = globSync(`./targets/action/expo-target.config.@(json|js)`, {
25
25
  cwd: projectRoot,
26
26
  absolute: true,
package/build/config.d.ts CHANGED
@@ -3,6 +3,17 @@ export type DynamicColor = {
3
3
  light: string;
4
4
  dark?: string;
5
5
  };
6
+ export type AppIntent = {
7
+ /** Main text to show in the control */
8
+ label: string;
9
+ /** SF Symbol name */
10
+ icon: string;
11
+ displayName: string;
12
+ description: string;
13
+ kind?: string;
14
+ /** URL to open when the intent is triggered. Should be a universal link. */
15
+ url: string;
16
+ };
6
17
  export type Entitlements = Partial<{
7
18
  "com.apple.developer.healthkit": boolean;
8
19
  "com.apple.developer.healthkit.access": string[];
@@ -108,5 +119,7 @@ export type Config = {
108
119
  }>;
109
120
  /** Should the release build export the JS bundle and embed. Intended for App Clips and Share Extensions where you may want to use React Native. */
110
121
  exportJs?: boolean;
122
+ /** App Intents to generate. */
123
+ intents?: AppIntent[];
111
124
  };
112
125
  export type ConfigFunction = (config: import("expo/config").ExpoConfig) => Config;
@@ -27,7 +27,7 @@ exports.generateWatchIconsInternalAsync = exports.generateIconsInternalAsync = e
27
27
  const config_plugins_1 = require("@expo/config-plugins");
28
28
  const image_utils_1 = require("@expo/image-utils");
29
29
  const AssetContents_1 = require("@expo/prebuild-config/build/plugins/icons/AssetContents");
30
- const fs = __importStar(require("fs-extra"));
30
+ const fs = __importStar(require("fs"));
31
31
  const path_1 = __importStar(require("path"));
32
32
  const withImageAsset = (config, { cwd, name, image }) => {
33
33
  return (0, config_plugins_1.withDangerousMod)(config, [
@@ -37,7 +37,9 @@ const withImageAsset = (config, { cwd, name, image }) => {
37
37
  const iosNamedProjectRoot = (0, path_1.join)(projectRoot, cwd);
38
38
  const imgPath = `Assets.xcassets/${name}.imageset`;
39
39
  // Ensure the Images.xcassets/AppIcon.appiconset path exists
40
- await fs.ensureDir((0, path_1.join)(iosNamedProjectRoot, imgPath));
40
+ await fs.promises.mkdir((0, path_1.join)(iosNamedProjectRoot, imgPath), {
41
+ recursive: true,
42
+ });
41
43
  const userDefinedIcon = typeof image === "string"
42
44
  ? { "1x": image, "2x": undefined, "3x": undefined }
43
45
  : image;
@@ -120,7 +122,9 @@ exports.ICON_CONTENTS = [
120
122
  ];
121
123
  async function setIconsAsync(icon, projectRoot, iosNamedProjectRoot, cacheComponent) {
122
124
  // Ensure the Images.xcassets/AppIcon.appiconset path exists
123
- await fs.ensureDir((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH));
125
+ await fs.promises.mkdir((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH), {
126
+ recursive: true,
127
+ });
124
128
  // Finally, write the Config.json
125
129
  await (0, AssetContents_1.writeContentsJsonAsync)((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH), {
126
130
  images: await generateIconsInternalAsync(icon, projectRoot, iosNamedProjectRoot, cacheComponent),
@@ -154,7 +158,7 @@ async function generateResizedImageAsync(icon, name, projectRoot, iosNamedProjec
154
158
  });
155
159
  // Write image buffer to the file system.
156
160
  const assetPath = (0, path_1.join)(iosNamedProjectRoot, `Assets.xcassets/${name}.imageset`, filename);
157
- await fs.writeFile(assetPath, source);
161
+ await fs.promises.writeFile(assetPath, source);
158
162
  if (filename) {
159
163
  imgEntry.filename = filename;
160
164
  }
@@ -196,7 +200,7 @@ async function generateIconsInternalAsync(icon, projectRoot, iosNamedProjectRoot
196
200
  });
197
201
  // Write image buffer to the file system.
198
202
  const assetPath = (0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH, filename);
199
- await fs.writeFile(assetPath, source);
203
+ await fs.promises.writeFile(assetPath, source);
200
204
  // Save a reference to the generated image so we don't create a duplicate.
201
205
  generatedIcons[filename] = true;
202
206
  }
@@ -234,7 +238,7 @@ async function generateWatchIconsInternalAsync(icon, projectRoot, iosNamedProjec
234
238
  });
235
239
  // Write image buffer to the file system.
236
240
  const assetPath = (0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH, filename);
237
- await fs.writeFile(assetPath, source);
241
+ await fs.promises.writeFile(assetPath, source);
238
242
  imagesJson.push({
239
243
  filename: getAppleIconName(size, 1),
240
244
  idiom: "universal",
@@ -27,7 +27,7 @@ exports.generateWatchIconsInternalAsync = exports.generateIconsInternalAsync = e
27
27
  const config_plugins_1 = require("@expo/config-plugins");
28
28
  const image_utils_1 = require("@expo/image-utils");
29
29
  const AssetContents_1 = require("@expo/prebuild-config/build/plugins/icons/AssetContents");
30
- const fs = __importStar(require("fs-extra"));
30
+ const fs = __importStar(require("fs"));
31
31
  const path_1 = require("path");
32
32
  // TODO: support dark, tinted, and universal icons for widgets.
33
33
  const withIosIcon = (config, { cwd, type, iconFilePath, isTransparent = false }) => {
@@ -38,7 +38,9 @@ const withIosIcon = (config, { cwd, type, iconFilePath, isTransparent = false })
38
38
  const namedProjectRoot = (0, path_1.join)(projectRoot, cwd);
39
39
  if (type === "watch") {
40
40
  // Ensure the Images.xcassets/AppIcon.appiconset path exists
41
- await fs.ensureDir((0, path_1.join)(namedProjectRoot, IMAGESET_PATH));
41
+ await fs.promises.mkdir((0, path_1.join)(namedProjectRoot, IMAGESET_PATH), {
42
+ recursive: true,
43
+ });
42
44
  // Finally, write the Config.json
43
45
  await (0, AssetContents_1.writeContentsJsonAsync)((0, path_1.join)(namedProjectRoot, IMAGESET_PATH), {
44
46
  images: await generateWatchIconsInternalAsync(iconFilePath, projectRoot, namedProjectRoot, cwd, isTransparent),
@@ -119,7 +121,9 @@ exports.ICON_CONTENTS = [
119
121
  ];
120
122
  async function setIconsAsync(icon, projectRoot, iosNamedProjectRoot, cacheComponent, isTransparent) {
121
123
  // Ensure the Images.xcassets/AppIcon.appiconset path exists
122
- await fs.ensureDir((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH));
124
+ await fs.promises.mkdir((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH), {
125
+ recursive: true,
126
+ });
123
127
  // Finally, write the Config.json
124
128
  await (0, AssetContents_1.writeContentsJsonAsync)((0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH), {
125
129
  images: await generateIconsInternalAsync(icon, projectRoot, iosNamedProjectRoot, cacheComponent, isTransparent),
@@ -158,7 +162,7 @@ async function generateIconsInternalAsync(icon, projectRoot, iosNamedProjectRoot
158
162
  });
159
163
  // Write image buffer to the file system.
160
164
  const assetPath = (0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH, filename);
161
- await fs.writeFile(assetPath, source);
165
+ await fs.promises.writeFile(assetPath, source);
162
166
  // Save a reference to the generated image so we don't create a duplicate.
163
167
  generatedIcons[filename] = true;
164
168
  }
@@ -196,7 +200,7 @@ async function generateWatchIconsInternalAsync(icon, projectRoot, iosNamedProjec
196
200
  });
197
201
  // Write image buffer to the file system.
198
202
  const assetPath = (0, path_1.join)(iosNamedProjectRoot, IMAGESET_PATH, filename);
199
- await fs.writeFile(assetPath, source);
203
+ await fs.promises.writeFile(assetPath, source);
200
204
  imagesJson.push({
201
205
  filename: getAppleIconName(size, 1),
202
206
  idiom: "universal",
@@ -0,0 +1,7 @@
1
+ import { ConfigPlugin } from "expo/config-plugins";
2
+ import { AppIntent } from "./config";
3
+ declare const withLinkAppIntent: ConfigPlugin<{
4
+ intents: AppIntent[];
5
+ targetRoot: string;
6
+ }>;
7
+ export default withLinkAppIntent;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_plugins_1 = require("expo/config-plugins");
7
+ function generateSwiftModule(config) {
8
+ const { intents } = config;
9
+ const widgetBundleBody = intents
10
+ .map((intent, index) => `// widgetControl${index}()`)
11
+ .join("\n");
12
+ const widgetDefinitions = intents
13
+ .map((intent, index) => {
14
+ return `
15
+ @available(iOS 18.0, *)
16
+ struct widgetControl${index}: ControlWidget {
17
+ static let kind: String = "${intent.kind}"
18
+ var body: some ControlWidgetConfiguration {
19
+ StaticControlConfiguration(kind: Self.kind) {
20
+ ControlWidgetButton(action: OpenAppIntent${index}()) {
21
+ Label("${intent.label}", ${
22
+ // If the icon is generated, use the image system
23
+ intent.icon.startsWith("generated_expo_intent_icon_")
24
+ ? "image"
25
+ : "systemImage"}: "${intent.icon}")
26
+ }
27
+ }
28
+ .displayName("${intent.displayName}")
29
+ .description("${intent.description}")
30
+ }
31
+ }
32
+
33
+ // This must be in both targets when \`openAppWhenRun = true\`
34
+ // https://developer.apple.com/forums/thread/763851
35
+ @available(iOS 18.0, *)
36
+ struct OpenAppIntent${index}: ControlConfigurationIntent {
37
+ static let title: LocalizedStringResource = "${intent.displayName}"
38
+ static let description = IntentDescription(stringLiteral: "${intent.description}")
39
+ static let isDiscoverable = true
40
+ static let openAppWhenRun: Bool = true
41
+
42
+ @MainActor
43
+ func perform() async throws -> some IntentResult & OpensIntent {
44
+ return .result(opensIntent: OpenURLIntent(URL(string: "${intent.url}")!))
45
+ }
46
+ }`;
47
+ })
48
+ .join("\n");
49
+ return `
50
+ // Generated by @bacons/apple-targets via the appIntents option in the config file.
51
+ import AppIntents
52
+ import SwiftUI
53
+ import WidgetKit
54
+
55
+ // TODO: These must be added to the WidgetBundle manually. They need to be linked outside of the _shared folder.
56
+ // @main
57
+ // struct exportWidgets: WidgetBundle {
58
+ // var body: some Widget {
59
+ ${widgetBundleBody}
60
+ // }
61
+ // }
62
+ ${widgetDefinitions}
63
+ `;
64
+ }
65
+ const fs_1 = __importDefault(require("fs"));
66
+ const withLinkAppIntent = (config, { targetRoot, intents }) => {
67
+ (0, config_plugins_1.withDangerousMod)(config, [
68
+ "ios",
69
+ async (config) => {
70
+ const sharedFile = `${targetRoot}/_shared/generated-intents.swift`;
71
+ await fs_1.default.promises.mkdir(`${targetRoot}/_shared`, { recursive: true });
72
+ await fs_1.default.promises.writeFile(sharedFile, generateSwiftModule({
73
+ intents: intents.map((intent, index) => {
74
+ var _a;
75
+ return ({
76
+ ...intent,
77
+ kind: (_a = intent.kind) !== null && _a !== void 0 ? _a : `${config.ios.bundleIdentifier}.${index}`,
78
+ });
79
+ }),
80
+ }), "utf-8");
81
+ return config;
82
+ },
83
+ ]);
84
+ return config;
85
+ };
86
+ exports.default = withLinkAppIntent;
@@ -15,6 +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 withLinkAppIntent_1 = __importDefault(require("./withLinkAppIntent"));
18
19
  const DEFAULT_DEPLOYMENT_TARGET = "18.0";
19
20
  function memoize(fn) {
20
21
  const cache = new Map();
@@ -63,7 +64,7 @@ const withWidget = (config, props) => {
63
64
  .replace(/^\/+/, "");
64
65
  const widget = kebabToCamelCase(widgetDir);
65
66
  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);
66
- const entitlementsFiles = (0, glob_1.sync)("*.entitlements", {
67
+ const entitlementsFiles = (0, glob_1.globSync)("*.entitlements", {
67
68
  absolute: true,
68
69
  cwd: widgetFolderAbsolutePath,
69
70
  });
@@ -116,6 +117,29 @@ const withWidget = (config, props) => {
116
117
  };
117
118
  entitlementsJson = applyDefaultEntitlements(entitlementsJson);
118
119
  }
120
+ if (props.intents) {
121
+ // Add local images:
122
+ // TODO: Apple only supports custom SF Symbols for intents, so we'll need to create a system to generate these from an SVG or accept .SFSymbol.svg files.
123
+ // props.intents.forEach((intent, index) => {
124
+ // // Is local image?
125
+ // if (intent.icon.match(/^\./)) {
126
+ // props.images ??= {};
127
+ // const intentIconName = "generated_expo_intent_icon_" + index;
128
+ // props.images[intentIconName] = intent.icon;
129
+ // props.intents![index].icon = intentIconName;
130
+ // }
131
+ // });
132
+ props.intents.forEach((intent) => {
133
+ if (intent.icon.match(/^\./)) {
134
+ throw new Error("Local images are not supported for intents. Use an SF Symbol name. From: " +
135
+ intent.icon);
136
+ }
137
+ });
138
+ (0, withLinkAppIntent_1.default)(config, {
139
+ intents: props.intents,
140
+ targetRoot: widgetFolderAbsolutePath,
141
+ });
142
+ }
119
143
  // If the user defined entitlements, then overwrite any existing entitlements file
120
144
  if (entitlementsJson) {
121
145
  (0, config_plugins_1.withDangerousMod)(config, [
@@ -697,7 +697,7 @@ async function applyXcodeChanges(config, project, props) {
697
697
  }
698
698
  }
699
699
  function configureTargetWithEntitlements(target) {
700
- const entitlements = (0, glob_1.sync)("*.entitlements", {
700
+ const entitlements = (0, glob_1.globSync)("*.entitlements", {
701
701
  absolute: false,
702
702
  cwd: magicCwd,
703
703
  });
@@ -719,7 +719,7 @@ async function applyXcodeChanges(config, project, props) {
719
719
  });
720
720
  }
721
721
  function configureTargetWithPreview(target) {
722
- const assets = (0, glob_1.sync)("preview/*.xcassets", {
722
+ const assets = (0, glob_1.globSync)("preview/*.xcassets", {
723
723
  absolute: true,
724
724
  cwd: magicCwd,
725
725
  })[0];
@@ -839,7 +839,7 @@ async function applyXcodeChanges(config, project, props) {
839
839
  fs_1.default.statSync(path_1.default.join(assetsDir, file)).isDirectory())
840
840
  .map((file) => path_1.default.join("assets", file));
841
841
  const protectedGroup = ensureProtectedGroup(project, path_1.default.dirname(props.cwd));
842
- const sharedAssets = (0, glob_1.sync)("_shared/*", {
842
+ const sharedAssets = (0, glob_1.globSync)("_shared/*", {
843
843
  absolute: false,
844
844
  cwd: magicCwd,
845
845
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacons/apple-targets",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Generate Apple Targets with Expo Prebuild",
5
5
  "main": "build/ExtensionStorage.js",
6
6
  "types": "build/ExtensionStorage.d.ts",
@@ -33,15 +33,13 @@
33
33
  "author": "Evan Bacon",
34
34
  "license": "MIT",
35
35
  "dependencies": {
36
- "@react-native/normalize-colors": "^0.76.1",
37
- "glob": "^10.2.6",
38
36
  "@bacons/xcode": "1.0.0-alpha.24",
39
- "fs-extra": "^11.2.0",
37
+ "@react-native/normalize-colors": "^0.76.1",
38
+ "glob": "^10.4.2",
40
39
  "debug": "^4.3.4"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@types/debug": "^4.1.7",
44
- "@types/fs-extra": "^11.0.4",
45
43
  "@types/glob": "^8.1.0",
46
44
  "@expo/babel-preset-cli": "^0.3.1",
47
45
  "chalk": "^4.0.0",