@therealitycrafters/react-native-evskit 0.1.0 → 0.1.1
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/app.plugin.js +7 -0
- package/package.json +3 -1
- package/plugin/withEverysightBundleResources.js +110 -0
- package/plugin/withEverysightSPM.js +276 -0
package/app.plugin.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const withEverysightBundleResources = require('./plugin/withEverysightBundleResources');
|
|
2
|
+
|
|
3
|
+
module.exports = (config = {}, { bundleResources = [] } = {}) => {
|
|
4
|
+
let result = config;
|
|
5
|
+
result = withEverysightBundleResources(result, { bundleResources });
|
|
6
|
+
return result;
|
|
7
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@therealitycrafters/react-native-evskit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "React Native / Expo bridge for Everysight's Maverick SDK (EvsKit + NativeEvsKit)",
|
|
5
5
|
"author": "Patrick Catanzariti",
|
|
6
6
|
"homepage": "https://therealitycrafters.com",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"src",
|
|
18
18
|
"android",
|
|
19
19
|
"ios",
|
|
20
|
+
"plugin",
|
|
21
|
+
"app.plugin.js",
|
|
20
22
|
"expo-module.config.json"
|
|
21
23
|
],
|
|
22
24
|
"scripts": {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const { withXcodeProject } = require("@expo/config-plugins");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Config plugin that adds files (e.g. sdk.key, app.key) as Xcode bundle
|
|
6
|
+
* resources so they are included in the app binary.
|
|
7
|
+
*
|
|
8
|
+
* Usage in app.config.ts:
|
|
9
|
+
* plugins: [
|
|
10
|
+
* ['react-native-evskit', { bundleResources: ['./sdk.key'] }],
|
|
11
|
+
* ]
|
|
12
|
+
*
|
|
13
|
+
* Adds PBXBuildFile + PBXFileReference entries directly to the pbxproj
|
|
14
|
+
* objects, avoiding the pbxFile constructor which produces undefined values
|
|
15
|
+
* that CocoaPods cannot parse.
|
|
16
|
+
*/
|
|
17
|
+
function withEverysightBundleResources(config, { bundleResources = [] } = {}) {
|
|
18
|
+
if (!bundleResources.length) {
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return withXcodeProject(config, (config) => {
|
|
23
|
+
const project = config.modResults;
|
|
24
|
+
const objects = project.hash.project.objects;
|
|
25
|
+
|
|
26
|
+
// Initialize required object sections
|
|
27
|
+
objects.PBXBuildFile = objects.PBXBuildFile || {};
|
|
28
|
+
objects.PBXFileReference = objects.PBXFileReference || {};
|
|
29
|
+
|
|
30
|
+
for (const relativePath of bundleResources) {
|
|
31
|
+
const resolvedPath = path.join(
|
|
32
|
+
config.modRequest.projectRoot,
|
|
33
|
+
relativePath,
|
|
34
|
+
);
|
|
35
|
+
const fileName = path.basename(resolvedPath);
|
|
36
|
+
|
|
37
|
+
// Skip if already exists
|
|
38
|
+
const existingRef = Object.keys(objects.PBXFileReference).find(
|
|
39
|
+
(key) =>
|
|
40
|
+
!key.endsWith("_comment") &&
|
|
41
|
+
objects.PBXFileReference[key].name === fileName,
|
|
42
|
+
);
|
|
43
|
+
if (existingRef) continue;
|
|
44
|
+
|
|
45
|
+
// The Xcode project lives in <projectRoot>/ios, so the file reference
|
|
46
|
+
// path must be relative to that directory (e.g. "../sdk.key").
|
|
47
|
+
const iosDir = path.join(config.modRequest.projectRoot, "ios");
|
|
48
|
+
const relativeToIos = path.relative(iosDir, resolvedPath);
|
|
49
|
+
|
|
50
|
+
// Create PBXFileReference
|
|
51
|
+
const fileRefUuid = project.generateUuid();
|
|
52
|
+
objects.PBXFileReference[fileRefUuid] = {
|
|
53
|
+
isa: "PBXFileReference",
|
|
54
|
+
name: `"${fileName}"`,
|
|
55
|
+
path: `"${relativeToIos}"`,
|
|
56
|
+
// Quotes must be embedded in the string: the `xcode` lib's pbxWriter
|
|
57
|
+
// does not add quotes itself, and angle brackets (<>) are invalid
|
|
58
|
+
// unquoted in the pbxproj format (e.g. `sourceTree = "<group>";`).
|
|
59
|
+
sourceTree: '"<group>"',
|
|
60
|
+
};
|
|
61
|
+
objects.PBXFileReference[`${fileRefUuid}_comment`] = fileName;
|
|
62
|
+
|
|
63
|
+
// Create PBXBuildFile
|
|
64
|
+
const buildFileUuid = project.generateUuid();
|
|
65
|
+
objects.PBXBuildFile[buildFileUuid] = {
|
|
66
|
+
isa: "PBXBuildFile",
|
|
67
|
+
fileRef: fileRefUuid,
|
|
68
|
+
};
|
|
69
|
+
objects.PBXBuildFile[`${buildFileUuid}_comment`] =
|
|
70
|
+
`${fileName} in Resources`;
|
|
71
|
+
|
|
72
|
+
// Add to Resources build phase
|
|
73
|
+
const target = project.getFirstTarget();
|
|
74
|
+
const nativeTarget = objects.PBXNativeTarget[target.uuid];
|
|
75
|
+
const resourcesPhaseUuid = nativeTarget.buildPhases.find((phase) =>
|
|
76
|
+
/Resources/i.test(phase.comment || ""),
|
|
77
|
+
)?.value;
|
|
78
|
+
if (resourcesPhaseUuid && objects.PBXResourcesBuildPhase) {
|
|
79
|
+
const resourcesPhase =
|
|
80
|
+
objects.PBXResourcesBuildPhase[resourcesPhaseUuid];
|
|
81
|
+
if (resourcesPhase) {
|
|
82
|
+
resourcesPhase.files = resourcesPhase.files || [];
|
|
83
|
+
resourcesPhase.files.push({
|
|
84
|
+
value: buildFileUuid,
|
|
85
|
+
comment: `${fileName} in Resources`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add to Resources group
|
|
91
|
+
const resourcesGroupKey = Object.keys(objects.PBXGroup || {}).find(
|
|
92
|
+
(key) =>
|
|
93
|
+
!key.endsWith("_comment") &&
|
|
94
|
+
objects.PBXGroup[key].name === "Resources",
|
|
95
|
+
);
|
|
96
|
+
if (resourcesGroupKey) {
|
|
97
|
+
objects.PBXGroup[resourcesGroupKey].children =
|
|
98
|
+
objects.PBXGroup[resourcesGroupKey].children || [];
|
|
99
|
+
objects.PBXGroup[resourcesGroupKey].children.push({
|
|
100
|
+
value: fileRefUuid,
|
|
101
|
+
comment: fileName,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return config;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = withEverysightBundleResources;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const { withXcodeProject } = require("@expo/config-plugins");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adds Everysight's Maverick SDK (EvsKit + NativeEvsKit) as a Swift Package
|
|
7
|
+
* Manager dependency of the iOS app target during `expo prebuild`.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists: CocoaPods can't consume SPM packages, and the SDK is
|
|
10
|
+
* only distributed via SPM (github.com/everysight-maverick/m1-ios-spm),
|
|
11
|
+
* which itself is just a `Package.swift` pointing at two prebuilt
|
|
12
|
+
* `EvsKit.xcframework.zip` / `NativeEvsKit.xcframework.zip` binary targets
|
|
13
|
+
* (confirmed by reading that repo directly — there's no source in it).
|
|
14
|
+
* Without this plugin you'd have to re-add the package by hand in Xcode
|
|
15
|
+
* every time you run a clean prebuild, which defeats the point of managed
|
|
16
|
+
* config.
|
|
17
|
+
*
|
|
18
|
+
* Implementation note: `@expo/config-plugins` / the underlying `xcode` npm
|
|
19
|
+
* package has no first-class API for SPM package references (it predates
|
|
20
|
+
* SPM support landing in most versions of that library), so this writes the
|
|
21
|
+
* `XCRemoteSwiftPackageReference` / `XCSwiftPackageProductDependency`
|
|
22
|
+
* pbxproj objects directly. That's inherently more fragile than a proper
|
|
23
|
+
* API — it's been structured to be idempotent (safe to run on every
|
|
24
|
+
* prebuild) and defensive about missing sections, but if a future Xcode
|
|
25
|
+
* project format changes the relevant object shapes, this may need
|
|
26
|
+
* updating. If it ever misbehaves, the fallback is adding the package by
|
|
27
|
+
* hand via Xcode's File > Add Packages... using the same URL/version below,
|
|
28
|
+
* then removing this plugin.
|
|
29
|
+
*
|
|
30
|
+
* @param {import('@expo/config-plugins').ExportedConfig} config
|
|
31
|
+
* @param {{ version?: string }} [options]
|
|
32
|
+
*/
|
|
33
|
+
function withEverysightSPM(config, options = {}) {
|
|
34
|
+
const packageURL = "https://github.com/everysight-maverick/m1-ios-spm.git";
|
|
35
|
+
const versionRequirement = options.version ?? "2.6.1";
|
|
36
|
+
const products = ["EvsKit", "NativeEvsKit"];
|
|
37
|
+
|
|
38
|
+
config = withXcodeProject(config, (config) => {
|
|
39
|
+
const project = config.modResults;
|
|
40
|
+
addSwiftPackage(project, packageURL, versionRequirement, products);
|
|
41
|
+
addModuleSourcesToAppTarget(project, config.modRequest.projectRoot);
|
|
42
|
+
return config;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Registers a remote Swift package (with an exact-version requirement) and
|
|
50
|
+
* links the given product names against the app's first native target.
|
|
51
|
+
* Safe to call repeatedly — checks for an existing reference/dependency
|
|
52
|
+
* before adding a duplicate.
|
|
53
|
+
*/
|
|
54
|
+
function addSwiftPackage(project, repositoryURL, exactVersion, productNames) {
|
|
55
|
+
const objects = project.hash.project.objects;
|
|
56
|
+
objects.XCRemoteSwiftPackageReference =
|
|
57
|
+
objects.XCRemoteSwiftPackageReference || {};
|
|
58
|
+
objects.XCSwiftPackageProductDependency =
|
|
59
|
+
objects.XCSwiftPackageProductDependency || {};
|
|
60
|
+
|
|
61
|
+
// --- 1. Find or create the XCRemoteSwiftPackageReference ------------------
|
|
62
|
+
let packageRefUuid = Object.keys(objects.XCRemoteSwiftPackageReference).find(
|
|
63
|
+
(key) =>
|
|
64
|
+
!key.endsWith("_comment") &&
|
|
65
|
+
objects.XCRemoteSwiftPackageReference[key].repositoryURL ===
|
|
66
|
+
JSON.stringify(repositoryURL),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!packageRefUuid) {
|
|
70
|
+
packageRefUuid = project.generateUuid();
|
|
71
|
+
objects.XCRemoteSwiftPackageReference[packageRefUuid] = {
|
|
72
|
+
isa: "XCRemoteSwiftPackageReference",
|
|
73
|
+
repositoryURL: JSON.stringify(repositoryURL),
|
|
74
|
+
requirement: {
|
|
75
|
+
kind: "exactVersion",
|
|
76
|
+
version: exactVersion,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
objects.XCRemoteSwiftPackageReference[`${packageRefUuid}_comment`] =
|
|
80
|
+
'XCRemoteSwiftPackageReference "m1-ios-spm"';
|
|
81
|
+
|
|
82
|
+
// Register the reference on the project object itself
|
|
83
|
+
// (Xcode reads packageReferences off PBXProject, not just the target).
|
|
84
|
+
const rootObjectUuid = project.hash.project.rootObject;
|
|
85
|
+
const rootObject = objects.PBXProject[rootObjectUuid];
|
|
86
|
+
rootObject.packageReferences = rootObject.packageReferences || [];
|
|
87
|
+
if (
|
|
88
|
+
!rootObject.packageReferences.some((ref) => ref.value === packageRefUuid)
|
|
89
|
+
) {
|
|
90
|
+
rootObject.packageReferences.push({
|
|
91
|
+
value: packageRefUuid,
|
|
92
|
+
comment: 'XCRemoteSwiftPackageReference "m1-ios-spm"',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- 2. Find or create a XCSwiftPackageProductDependency per product ------
|
|
98
|
+
const target = project.getFirstTarget();
|
|
99
|
+
const nativeTarget = objects.PBXNativeTarget[target.uuid];
|
|
100
|
+
nativeTarget.packageProductDependencies =
|
|
101
|
+
nativeTarget.packageProductDependencies || [];
|
|
102
|
+
|
|
103
|
+
// Also link SPM products to the pod target (RNEvsKit) so that the Swift
|
|
104
|
+
// module can import EvsKit and NativeEvsKit. The pod is compiled as a
|
|
105
|
+
// separate target and needs the SPM products linked to it.
|
|
106
|
+
const podTargetUuid = Object.keys(objects.PBXNativeTarget).find(
|
|
107
|
+
(key) =>
|
|
108
|
+
!key.endsWith("_comment") &&
|
|
109
|
+
objects.PBXNativeTarget[key].productReference &&
|
|
110
|
+
objects.PBXNativeTarget[key].buildConfigurationList,
|
|
111
|
+
);
|
|
112
|
+
const podTarget = podTargetUuid
|
|
113
|
+
? objects.PBXNativeTarget[podTargetUuid]
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
const frameworksBuildPhaseUuid = nativeTarget.buildPhases.find((phase) =>
|
|
117
|
+
/Frameworks/i.test(phase.comment || ""),
|
|
118
|
+
)?.value;
|
|
119
|
+
const frameworksPhase = frameworksBuildPhaseUuid
|
|
120
|
+
? objects.PBXFrameworksBuildPhase[frameworksBuildPhaseUuid]
|
|
121
|
+
: undefined;
|
|
122
|
+
|
|
123
|
+
for (const productName of productNames) {
|
|
124
|
+
let productDepUuid = Object.keys(
|
|
125
|
+
objects.XCSwiftPackageProductDependency,
|
|
126
|
+
).find(
|
|
127
|
+
(key) =>
|
|
128
|
+
!key.endsWith("_comment") &&
|
|
129
|
+
objects.XCSwiftPackageProductDependency[key].productName ===
|
|
130
|
+
productName,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (!productDepUuid) {
|
|
134
|
+
productDepUuid = project.generateUuid();
|
|
135
|
+
objects.XCSwiftPackageProductDependency[productDepUuid] = {
|
|
136
|
+
isa: "XCSwiftPackageProductDependency",
|
|
137
|
+
package: packageRefUuid,
|
|
138
|
+
productName,
|
|
139
|
+
};
|
|
140
|
+
objects.XCSwiftPackageProductDependency[`${productDepUuid}_comment`] =
|
|
141
|
+
productName;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Add to app target
|
|
145
|
+
if (
|
|
146
|
+
!nativeTarget.packageProductDependencies.some(
|
|
147
|
+
(dep) => dep.value === productDepUuid,
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
nativeTarget.packageProductDependencies.push({
|
|
151
|
+
value: productDepUuid,
|
|
152
|
+
comment: productName,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Also add to pod target (RNEvsKit) so the Swift module can import these
|
|
157
|
+
if (podTarget) {
|
|
158
|
+
podTarget.packageProductDependencies =
|
|
159
|
+
podTarget.packageProductDependencies || [];
|
|
160
|
+
if (
|
|
161
|
+
!podTarget.packageProductDependencies.some(
|
|
162
|
+
(dep) => dep.value === productDepUuid,
|
|
163
|
+
)
|
|
164
|
+
) {
|
|
165
|
+
podTarget.packageProductDependencies.push({
|
|
166
|
+
value: productDepUuid,
|
|
167
|
+
comment: productName,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Link the product into the app binary via the Frameworks build phase,
|
|
173
|
+
// same as Xcode would if you added it through the UI.
|
|
174
|
+
if (frameworksPhase) {
|
|
175
|
+
objects.PBXBuildFile = objects.PBXBuildFile || {};
|
|
176
|
+
const alreadyLinked = Object.keys(objects.PBXBuildFile).some(
|
|
177
|
+
(key) =>
|
|
178
|
+
!key.endsWith("_comment") &&
|
|
179
|
+
objects.PBXBuildFile[key].productRef === productDepUuid,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (!alreadyLinked) {
|
|
183
|
+
const buildFileUuid = project.generateUuid();
|
|
184
|
+
objects.PBXBuildFile[buildFileUuid] = {
|
|
185
|
+
isa: "PBXBuildFile",
|
|
186
|
+
productRef: productDepUuid,
|
|
187
|
+
productRef_comment: productName,
|
|
188
|
+
};
|
|
189
|
+
objects.PBXBuildFile[`${buildFileUuid}_comment`] =
|
|
190
|
+
`${productName} in Frameworks`;
|
|
191
|
+
|
|
192
|
+
frameworksPhase.files = frameworksPhase.files || [];
|
|
193
|
+
frameworksPhase.files.push({
|
|
194
|
+
value: buildFileUuid,
|
|
195
|
+
comment: `${productName} in Frameworks`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Adds the EvsKitModule.swift source files directly to the app target's
|
|
204
|
+
* Sources build phase so they can import SPM products (EvsKit, NativeEvsKit).
|
|
205
|
+
* CocoaPods targets cannot consume SPM packages, so the podspec has empty
|
|
206
|
+
* source_files and the actual sources are compiled via the app target instead.
|
|
207
|
+
*/
|
|
208
|
+
function addModuleSourcesToAppTarget(project, projectRoot) {
|
|
209
|
+
const objects = project.hash.project.objects;
|
|
210
|
+
const target = project.getFirstTarget();
|
|
211
|
+
const nativeTarget = objects.PBXNativeTarget[target.uuid];
|
|
212
|
+
|
|
213
|
+
// Find the Sources build phase
|
|
214
|
+
const sourcesPhaseUuid = nativeTarget.buildPhases.find((phase) =>
|
|
215
|
+
/Sources/i.test(phase.comment || ""),
|
|
216
|
+
)?.value;
|
|
217
|
+
const sourcesPhase = sourcesPhaseUuid
|
|
218
|
+
? objects.PBXSourcesBuildPhase[sourcesPhaseUuid]
|
|
219
|
+
: undefined;
|
|
220
|
+
if (!sourcesPhase) return;
|
|
221
|
+
|
|
222
|
+
const moduleDir = path.join(path.dirname(__dirname), "ios");
|
|
223
|
+
const sourceFiles = fs
|
|
224
|
+
.readdirSync(moduleDir)
|
|
225
|
+
.filter((f) => f.endsWith(".swift"));
|
|
226
|
+
|
|
227
|
+
// The Xcode project lives in <projectRoot>/ios, so the file reference path
|
|
228
|
+
// must be relative to that directory (e.g. "../../react-native-evskit/ios").
|
|
229
|
+
const iosDir = path.join(projectRoot, "ios");
|
|
230
|
+
const moduleDirRelativeToIos = path.relative(iosDir, moduleDir);
|
|
231
|
+
|
|
232
|
+
for (const fileName of sourceFiles) {
|
|
233
|
+
// Check if already added
|
|
234
|
+
const alreadyAdded = Object.keys(objects.PBXBuildFile || {}).some(
|
|
235
|
+
(key) =>
|
|
236
|
+
!key.endsWith("_comment") &&
|
|
237
|
+
objects.PBXBuildFile[key].fileRef &&
|
|
238
|
+
objects.PBXBuildFile[key].fileRef_comment === fileName,
|
|
239
|
+
);
|
|
240
|
+
if (alreadyAdded) continue;
|
|
241
|
+
|
|
242
|
+
// Add file reference
|
|
243
|
+
const fileRefUuid = project.generateUuid();
|
|
244
|
+
objects.PBXFileReference = objects.PBXFileReference || {};
|
|
245
|
+
objects.PBXFileReference[fileRefUuid] = {
|
|
246
|
+
isa: "PBXFileReference",
|
|
247
|
+
explicitFileType: "sourcecode.swift",
|
|
248
|
+
fileEncoding: 4,
|
|
249
|
+
name: fileName,
|
|
250
|
+
path: path.join(moduleDirRelativeToIos, fileName),
|
|
251
|
+
// Quotes must be embedded in the string: the `xcode` lib's pbxWriter
|
|
252
|
+
// does not add quotes itself, and angle brackets (<>) are invalid
|
|
253
|
+
// unquoted in the pbxproj format (e.g. `sourceTree = "<group>";`).
|
|
254
|
+
sourceTree: '"<group>"',
|
|
255
|
+
};
|
|
256
|
+
objects.PBXFileReference[`${fileRefUuid}_comment`] = fileName;
|
|
257
|
+
|
|
258
|
+
// Add build file
|
|
259
|
+
const buildFileUuid = project.generateUuid();
|
|
260
|
+
objects.PBXBuildFile = objects.PBXBuildFile || {};
|
|
261
|
+
objects.PBXBuildFile[buildFileUuid] = {
|
|
262
|
+
isa: "PBXBuildFile",
|
|
263
|
+
fileRef: fileRefUuid,
|
|
264
|
+
};
|
|
265
|
+
objects.PBXBuildFile[`${buildFileUuid}_comment`] = `${fileName} in Sources`;
|
|
266
|
+
|
|
267
|
+
// Add to Sources build phase
|
|
268
|
+
sourcesPhase.files = sourcesPhase.files || [];
|
|
269
|
+
sourcesPhase.files.push({
|
|
270
|
+
value: buildFileUuid,
|
|
271
|
+
comment: `${fileName} in Sources`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = withEverysightSPM;
|