@bacons/apple-targets 0.1.15 → 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.
- package/README.md +53 -0
- package/build/config-plugin.js +1 -1
- package/build/config.d.ts +13 -0
- package/build/icon/withImageAsset.js +10 -6
- package/build/icon/withIosIcon.js +9 -5
- package/build/withEasCredentials.js +13 -15
- package/build/withLinkAppIntent.d.ts +7 -0
- package/build/withLinkAppIntent.js +86 -0
- package/build/withWidget.js +25 -1
- package/build/withXcodeChanges.js +41 -11
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -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-plugin.js
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -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.
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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",
|
|
@@ -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;
|
package/build/withWidget.js
CHANGED
|
@@ -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.
|
|
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, [
|
|
@@ -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)) {
|
|
@@ -695,7 +697,7 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
695
697
|
}
|
|
696
698
|
}
|
|
697
699
|
function configureTargetWithEntitlements(target) {
|
|
698
|
-
const entitlements = (0, glob_1.
|
|
700
|
+
const entitlements = (0, glob_1.globSync)("*.entitlements", {
|
|
699
701
|
absolute: false,
|
|
700
702
|
cwd: magicCwd,
|
|
701
703
|
});
|
|
@@ -717,7 +719,7 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
717
719
|
});
|
|
718
720
|
}
|
|
719
721
|
function configureTargetWithPreview(target) {
|
|
720
|
-
const assets = (0, glob_1.
|
|
722
|
+
const assets = (0, glob_1.globSync)("preview/*.xcassets", {
|
|
721
723
|
absolute: true,
|
|
722
724
|
cwd: magicCwd,
|
|
723
725
|
})[0];
|
|
@@ -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
|
-
|
|
745
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
835
|
-
|
|
842
|
+
const sharedAssets = (0, glob_1.globSync)("_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.
|
|
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,16 @@
|
|
|
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
|
-
"
|
|
37
|
+
"@react-native/normalize-colors": "^0.76.1",
|
|
38
|
+
"glob": "^10.4.2",
|
|
39
|
+
"debug": "^4.3.4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/
|
|
42
|
+
"@types/debug": "^4.1.7",
|
|
43
43
|
"@types/glob": "^8.1.0",
|
|
44
44
|
"@expo/babel-preset-cli": "^0.3.1",
|
|
45
|
+
"chalk": "^4.0.0",
|
|
45
46
|
"jest-environment-node": "^26"
|
|
46
47
|
}
|
|
47
48
|
}
|