@bacons/apple-targets 0.1.0 → 0.1.2
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 +26 -20
- package/build/config.d.ts +5 -0
- package/build/target.d.ts +2 -2
- package/build/target.js +9 -0
- package/build/withWidget.js +9 -6
- package/build/withXcodeChanges.js +86 -9
- package/package.json +4 -3
- package/prebuild-blank.tgz +0 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ You can change the root directory from `./targets` to something else with `root:
|
|
|
36
36
|
|
|
37
37
|
## Using React Native in Targets
|
|
38
38
|
|
|
39
|
-
I'm not sure, that's not the purpose of this plugin. I built this so I could easily build iOS widgets and other minor targets with SwiftUI. I imagine it would be straightforward to use React Native in share, notification, iMessage, Safari, and photo editing extensions, you can build that on top of this plugin if you want.
|
|
39
|
+
I'm not sure, that's not the purpose of this plugin. I built this so I could easily build iOS widgets and other minor targets with SwiftUI. I imagine it would be straightforward to use React Native in share, notification, iMessage, Safari, and photo editing extensions, you can build that on top of this plugin if you want. Look at the App Clip example for a starting point.
|
|
40
40
|
|
|
41
41
|
## `expo-target.config.json`
|
|
42
42
|
|
|
@@ -69,7 +69,11 @@ This file can have the following properties:
|
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
// The iOS version fot the target.
|
|
72
|
-
"deploymentTarget": "13.4"
|
|
72
|
+
"deploymentTarget": "13.4",
|
|
73
|
+
|
|
74
|
+
// Optional bundle identifier for the target. Will default to a sanitized version of the root project bundle id + target name.
|
|
75
|
+
// If the specified bundle identifier is prefixed with a dot (.), the bundle identifier will be appended to the main app's bundle identifier.
|
|
76
|
+
"bundleIdentifier": ".mywidget"
|
|
73
77
|
}
|
|
74
78
|
```
|
|
75
79
|
|
|
@@ -242,24 +246,26 @@ module.exports = {
|
|
|
242
246
|
|
|
243
247
|
Ideally, this would be generated automatically based on a fully qualified Xcode project, but for now it's a manual process. The currently supported types are based on static analysis of the most commonly used targets in the iOS App Store. I haven't tested all of these and they may not work.
|
|
244
248
|
|
|
245
|
-
| Type
|
|
246
|
-
|
|
|
247
|
-
| action
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
| notification-
|
|
255
|
-
|
|
|
256
|
-
| intent
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
249
|
+
| Type | Description |
|
|
250
|
+
| ----------------------- | ---------------------------------- |
|
|
251
|
+
| action | Share Action |
|
|
252
|
+
| app-intent | App Intent Extension |
|
|
253
|
+
| widget | Widget / Live Activity |
|
|
254
|
+
| watch | Watch App (with companion iOS App) |
|
|
255
|
+
| clip | App Clip |
|
|
256
|
+
| safari | Safari Extension |
|
|
257
|
+
| share | Share Extension |
|
|
258
|
+
| notification-content | Notification Content Extension |
|
|
259
|
+
| notification-service | Notification Service Extension |
|
|
260
|
+
| intent | Siri Intent Extension |
|
|
261
|
+
| intent-ui | Siri Intent UI Extension |
|
|
262
|
+
| spotlight | Spotlight Index Extension |
|
|
263
|
+
| bg-download | Background Download Extension |
|
|
264
|
+
| quicklook-thumbnail | Quick Look Thumbnail Extension |
|
|
265
|
+
| location-push | Location Push Service Extension |
|
|
266
|
+
| credentials-provider | Credentials Provider Extension |
|
|
267
|
+
| account-auth | Account Authentication Extension |
|
|
268
|
+
| device-activity-monitor | Device Activity Monitor Extension |
|
|
263
269
|
|
|
264
270
|
<!-- | imessage | iMessage Extension | -->
|
|
265
271
|
|
package/build/config.d.ts
CHANGED
|
@@ -68,6 +68,11 @@ export type Config = {
|
|
|
68
68
|
type: ExtensionType;
|
|
69
69
|
/** Name of the target. Will default to a sanitized version of the directory name. */
|
|
70
70
|
name?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Bundle identifier for the target. Will default to a sanitized version of the root project + name.
|
|
73
|
+
* If the specified bundle identifier is prefixed with a dot (.), the bundle identifier will be appended to the main app's bundle identifier.
|
|
74
|
+
**/
|
|
75
|
+
bundleIdentifier?: string;
|
|
71
76
|
/**
|
|
72
77
|
* A local file path or URL to an image asset.
|
|
73
78
|
* @example "./assets/icon.png"
|
package/build/target.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PBXNativeTarget, XcodeProject } from "@bacons/xcode";
|
|
2
|
-
export type ExtensionType = "widget" | "notification-content" | "notification-service" | "share" | "intent" | "bg-download" | "intent-ui" | "spotlight" | "matter" | "quicklook-thumbnail" | "imessage" | "clip" | "watch" | "location-push" | "credentials-provider" | "account-auth" | "action" | "safari";
|
|
2
|
+
export type ExtensionType = "widget" | "notification-content" | "notification-service" | "share" | "intent" | "bg-download" | "intent-ui" | "spotlight" | "matter" | "quicklook-thumbnail" | "imessage" | "clip" | "watch" | "location-push" | "credentials-provider" | "account-auth" | "action" | "safari" | "app-intent" | "device-activity-monitor";
|
|
3
3
|
export declare const KNOWN_EXTENSION_POINT_IDENTIFIERS: Record<string, ExtensionType>;
|
|
4
4
|
export declare function getTargetInfoPlistForType(type: ExtensionType): string;
|
|
5
|
-
export declare function productTypeForType(type: ExtensionType): "com.apple.product-type.application.on-demand-install-capable" | "com.apple.product-type.application" | "com.apple.product-type.app-extension";
|
|
5
|
+
export declare function productTypeForType(type: ExtensionType): "com.apple.product-type.application.on-demand-install-capable" | "com.apple.product-type.application" | "com.apple.product-type.extensionkit-extension" | "com.apple.product-type.app-extension";
|
|
6
6
|
export declare function needsEmbeddedSwift(type: ExtensionType): boolean;
|
|
7
7
|
export declare function getFrameworksForType(type: ExtensionType): string[];
|
|
8
8
|
export declare function isNativeTargetOfType(target: PBXNativeTarget, type: ExtensionType): boolean;
|
package/build/target.js
CHANGED
|
@@ -22,6 +22,7 @@ exports.KNOWN_EXTENSION_POINT_IDENTIFIERS = {
|
|
|
22
22
|
"com.apple.authentication-services-credential-provider-ui": "credentials-provider",
|
|
23
23
|
"com.apple.authentication-services-account-authentication-modification-ui": "account-auth",
|
|
24
24
|
"com.apple.services": "action",
|
|
25
|
+
"com.apple.appintents-extension": "app-intent",
|
|
25
26
|
// "com.apple.intents-service": "intents",
|
|
26
27
|
};
|
|
27
28
|
// TODO: Maybe we can replace `NSExtensionPrincipalClass` with the `@main` annotation that newer extensions use?
|
|
@@ -232,6 +233,8 @@ function productTypeForType(type) {
|
|
|
232
233
|
return "com.apple.product-type.application.on-demand-install-capable";
|
|
233
234
|
case "watch":
|
|
234
235
|
return "com.apple.product-type.application";
|
|
236
|
+
case "app-intent":
|
|
237
|
+
return "com.apple.product-type.extensionkit-extension";
|
|
235
238
|
default:
|
|
236
239
|
return "com.apple.product-type.app-extension";
|
|
237
240
|
}
|
|
@@ -272,6 +275,12 @@ function getFrameworksForType(type) {
|
|
|
272
275
|
else if (type === "notification-content") {
|
|
273
276
|
return ["UserNotifications", "UserNotificationsUI"];
|
|
274
277
|
}
|
|
278
|
+
else if (type === "app-intent") {
|
|
279
|
+
return ["AppIntents"];
|
|
280
|
+
}
|
|
281
|
+
else if (type === "device-activity-monitor") {
|
|
282
|
+
return ["DeviceActivity"];
|
|
283
|
+
}
|
|
275
284
|
else if (type === "action") {
|
|
276
285
|
return [
|
|
277
286
|
// "UniformTypeIdentifiers"
|
package/build/withWidget.js
CHANGED
|
@@ -22,7 +22,7 @@ function kebabToCamelCase(str) {
|
|
|
22
22
|
}
|
|
23
23
|
const withWidget = (config, props) => {
|
|
24
24
|
// TODO: Magically based on the top-level folders in the `ios-widgets/` folder
|
|
25
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
25
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
26
26
|
if (props.icon && !/https?:\/\//.test(props.icon)) {
|
|
27
27
|
props.icon = path_1.default.join(props.directory, props.icon);
|
|
28
28
|
}
|
|
@@ -111,22 +111,25 @@ const withWidget = (config, props) => {
|
|
|
111
111
|
},
|
|
112
112
|
]);
|
|
113
113
|
const targetName = (_c = props.name) !== null && _c !== void 0 ? _c : widget;
|
|
114
|
-
const
|
|
114
|
+
const mainAppBundleId = config.ios.bundleIdentifier;
|
|
115
|
+
const bundleId = ((_d = props.bundleIdentifier) === null || _d === void 0 ? void 0 : _d.startsWith("."))
|
|
116
|
+
? mainAppBundleId + props.bundleIdentifier
|
|
117
|
+
: (_e = props.bundleIdentifier) !== null && _e !== void 0 ? _e : `${mainAppBundleId}.${targetName}`;
|
|
115
118
|
(0, withXcodeChanges_1.withXcodeChanges)(config, {
|
|
116
119
|
configPath: props.configPath,
|
|
117
120
|
name: targetName,
|
|
118
121
|
cwd: "../" +
|
|
119
122
|
path_1.default.relative(config._internal.projectRoot, path_1.default.resolve(props.directory)),
|
|
120
|
-
deploymentTarget: (
|
|
123
|
+
deploymentTarget: (_f = props.deploymentTarget) !== null && _f !== void 0 ? _f : "16.4",
|
|
121
124
|
bundleId,
|
|
122
125
|
icon: props.icon,
|
|
123
|
-
hasAccentColor: !!((
|
|
126
|
+
hasAccentColor: !!((_g = props.colors) === null || _g === void 0 ? void 0 : _g.$accent),
|
|
124
127
|
// @ts-expect-error: who cares
|
|
125
|
-
currentProjectVersion: ((
|
|
128
|
+
currentProjectVersion: ((_h = config.ios) === null || _h === void 0 ? void 0 : _h.buildNumber) || 1,
|
|
126
129
|
frameworks: (0, target_1.getFrameworksForType)(props.type).concat(props.frameworks || []),
|
|
127
130
|
type: props.type,
|
|
128
131
|
teamId: props.appleTeamId,
|
|
129
|
-
exportJs: (
|
|
132
|
+
exportJs: (_j = props.exportJs) !== null && _j !== void 0 ? _j :
|
|
130
133
|
// Assume App Clips are used for React Native.
|
|
131
134
|
props.type === "clip",
|
|
132
135
|
});
|
|
@@ -121,6 +121,68 @@ extensionType, { name, cwd, bundleId, deploymentTarget, currentProjectVersion, i
|
|
|
121
121
|
});
|
|
122
122
|
return configurationList;
|
|
123
123
|
}
|
|
124
|
+
function createAppIntentConfigurationList(project, { name, cwd, bundleId }) {
|
|
125
|
+
const commonBuildSettings = {
|
|
126
|
+
// @ts-expect-error
|
|
127
|
+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: "YES",
|
|
128
|
+
CLANG_ANALYZER_NONNULL: "YES",
|
|
129
|
+
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION: "YES_AGGRESSIVE",
|
|
130
|
+
CLANG_CXX_LANGUAGE_STANDARD: "gnu++20",
|
|
131
|
+
CLANG_ENABLE_OBJC_WEAK: "YES",
|
|
132
|
+
CLANG_WARN_DOCUMENTATION_COMMENTS: "YES",
|
|
133
|
+
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: "YES",
|
|
134
|
+
CLANG_WARN_UNGUARDED_AVAILABILITY: "YES_AGGRESSIVE",
|
|
135
|
+
CODE_SIGN_STYLE: "Automatic",
|
|
136
|
+
CURRENT_PROJECT_VERSION: "1",
|
|
137
|
+
DEBUG_INFORMATION_FORMAT: "dwarf",
|
|
138
|
+
ENABLE_USER_SCRIPT_SANDBOXING: "YES",
|
|
139
|
+
GCC_C_LANGUAGE_STANDARD: "gnu17",
|
|
140
|
+
GENERATE_INFOPLIST_FILE: "YES",
|
|
141
|
+
INFOPLIST_FILE: cwd + "/Info.plist",
|
|
142
|
+
INFOPLIST_KEY_CFBundleDisplayName: name,
|
|
143
|
+
INFOPLIST_KEY_NSHumanReadableCopyright: "",
|
|
144
|
+
IPHONEOS_DEPLOYMENT_TARGET: "17.0",
|
|
145
|
+
LD_RUNPATH_SEARCH_PATHS: [
|
|
146
|
+
"$(inherited)",
|
|
147
|
+
"@executable_path/Frameworks",
|
|
148
|
+
"@executable_path/../../Frameworks",
|
|
149
|
+
],
|
|
150
|
+
LOCALIZATION_PREFERS_STRING_CATALOGS: "YES",
|
|
151
|
+
MARKETING_VERSION: "1.0",
|
|
152
|
+
MTL_FAST_MATH: "YES",
|
|
153
|
+
PRODUCT_BUNDLE_IDENTIFIER: bundleId,
|
|
154
|
+
PRODUCT_NAME: "$(TARGET_NAME)",
|
|
155
|
+
SKIP_INSTALL: "YES",
|
|
156
|
+
SWIFT_EMIT_LOC_STRINGS: "YES",
|
|
157
|
+
SWIFT_VERSION: "5.0",
|
|
158
|
+
TARGETED_DEVICE_FAMILY: "1,2",
|
|
159
|
+
};
|
|
160
|
+
const debugBuildConfig = xcode_1.XCBuildConfiguration.create(project, {
|
|
161
|
+
name: "Debug",
|
|
162
|
+
buildSettings: {
|
|
163
|
+
...commonBuildSettings,
|
|
164
|
+
GCC_PREPROCESSOR_DEFINITIONS: ["DEBUG=1", "$(inherited)"],
|
|
165
|
+
MTL_ENABLE_DEBUG_INFO: "INCLUDE_SOURCE",
|
|
166
|
+
SWIFT_ACTIVE_COMPILATION_CONDITIONS: "DEBUG $(inherited)",
|
|
167
|
+
SWIFT_OPTIMIZATION_LEVEL: "-Onone",
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const releaseBuildConfig = xcode_1.XCBuildConfiguration.create(project, {
|
|
171
|
+
name: "Release",
|
|
172
|
+
buildSettings: {
|
|
173
|
+
...commonBuildSettings,
|
|
174
|
+
COPY_PHASE_STRIP: "NO",
|
|
175
|
+
DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym",
|
|
176
|
+
...{ SWIFT_COMPILATION_MODE: "wholemodule" },
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const configurationList = xcode_1.XCConfigurationList.create(project, {
|
|
180
|
+
buildConfigurations: [debugBuildConfig, releaseBuildConfig],
|
|
181
|
+
defaultConfigurationIsVisible: 0,
|
|
182
|
+
defaultConfigurationName: "Release",
|
|
183
|
+
});
|
|
184
|
+
return configurationList;
|
|
185
|
+
}
|
|
124
186
|
function createShareConfigurationList(project, { name, cwd, bundleId, deploymentTarget, currentProjectVersion, }) {
|
|
125
187
|
const common = {
|
|
126
188
|
CLANG_ANALYZER_NONNULL: "YES",
|
|
@@ -563,6 +625,9 @@ function createConfigurationListForType(project, props) {
|
|
|
563
625
|
else if (props.type === "watch") {
|
|
564
626
|
return createWatchAppConfigurationList(project, props);
|
|
565
627
|
}
|
|
628
|
+
else if (props.type === "app-intent") {
|
|
629
|
+
return createAppIntentConfigurationList(project, props);
|
|
630
|
+
}
|
|
566
631
|
else {
|
|
567
632
|
// TODO: More
|
|
568
633
|
return createNotificationContentConfigurationList(project, props);
|
|
@@ -722,11 +787,16 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
722
787
|
syncMarketingVersions();
|
|
723
788
|
return project;
|
|
724
789
|
}
|
|
790
|
+
const productType = (0, target_1.productTypeForType)(props.type);
|
|
791
|
+
const isExtension = productType === "com.apple.product-type.app-extension";
|
|
792
|
+
const isExtensionKit = productType === "com.apple.product-type.extensionkit-extension";
|
|
725
793
|
const appExtensionBuildFile = xcode_1.PBXBuildFile.create(project, {
|
|
726
794
|
fileRef: xcode_1.PBXFileReference.create(project, {
|
|
727
|
-
explicitFileType:
|
|
795
|
+
explicitFileType: isExtensionKit
|
|
796
|
+
? "wrapper.extensionkit-extension"
|
|
797
|
+
: "wrapper.app-extension",
|
|
728
798
|
includeInIndex: 0,
|
|
729
|
-
path: productName + ".appex",
|
|
799
|
+
path: productName + (isExtension ? ".appex" : ".app"),
|
|
730
800
|
sourceTree: "BUILT_PRODUCTS_DIR",
|
|
731
801
|
}),
|
|
732
802
|
settings: {
|
|
@@ -742,7 +812,7 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
742
812
|
productName,
|
|
743
813
|
// @ts-expect-error
|
|
744
814
|
productReference: appExtensionBuildFile.props.fileRef /* alphaExtension.appex */,
|
|
745
|
-
productType:
|
|
815
|
+
productType: productType,
|
|
746
816
|
});
|
|
747
817
|
configureTargetWithKnownSettings(extensionTarget);
|
|
748
818
|
configureTargetWithEntitlements(extensionTarget);
|
|
@@ -763,11 +833,18 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
763
833
|
});
|
|
764
834
|
// Add the target dependency to the main app, should be only one.
|
|
765
835
|
mainAppTarget.props.dependencies.push(targetDependency);
|
|
766
|
-
const WELL_KNOWN_COPY_EXTENSIONS_NAME =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
836
|
+
const WELL_KNOWN_COPY_EXTENSIONS_NAME = (() => {
|
|
837
|
+
switch (props.type) {
|
|
838
|
+
case "clip":
|
|
839
|
+
return "Embed App Clips";
|
|
840
|
+
case "watch":
|
|
841
|
+
return "Embed Watch Content";
|
|
842
|
+
case "app-intent":
|
|
843
|
+
return "Embed ExtensionKit Extensions";
|
|
844
|
+
default:
|
|
845
|
+
return "Embed Foundation Extensions";
|
|
846
|
+
}
|
|
847
|
+
})();
|
|
771
848
|
// Could exist from a Share Extension
|
|
772
849
|
const copyFilesBuildPhase = mainAppTarget.props.buildPhases.find((phase) => {
|
|
773
850
|
if (xcode_1.PBXCopyFilesBuildPhase.is(phase)) {
|
|
@@ -791,7 +868,7 @@ async function applyXcodeChanges(config, project, props) {
|
|
|
791
868
|
"Info.plist",
|
|
792
869
|
// Exclude the config path
|
|
793
870
|
path_1.default.relative(magicCwd, props.configPath),
|
|
794
|
-
],
|
|
871
|
+
].sort(),
|
|
795
872
|
});
|
|
796
873
|
const assetsDir = path_1.default.join(magicCwd, "assets");
|
|
797
874
|
// TODO: Maybe just limit this to Safari extensions?
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacons/apple-targets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Generate Apple Targets with Expo Prebuild",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"app.plugin.js",
|
|
8
|
-
"build"
|
|
8
|
+
"build",
|
|
9
|
+
"prebuild-blank.tgz"
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "expo-module build",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@react-native/normalize-colors": "^0.76.1",
|
|
32
33
|
"glob": "^10.2.6",
|
|
33
|
-
"@bacons/xcode": "^1.0.0-alpha.
|
|
34
|
+
"@bacons/xcode": "^1.0.0-alpha.22",
|
|
34
35
|
"fs-extra": "^11.2.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
Binary file
|