@smileid/usesmileid_vision_face 12.0.0 → 12.0.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/CHANGELOG.md +8 -0
- package/package.json +4 -4
- package/plugin/build/index.d.ts +1 -1
- package/plugin/build/index.js +20 -3
- package/plugin/build/index.js.map +1 -1
- package/plugin/build/lib/deployment_target.d.ts +5 -0
- package/plugin/build/lib/deployment_target.js +39 -0
- package/plugin/build/lib/deployment_target.js.map +1 -0
- package/plugin/build/lib/podfile.d.ts +2 -0
- package/plugin/build/lib/podfile.js +54 -13
- package/plugin/build/lib/podfile.js.map +1 -1
- package/plugin/build/lib/spm.d.ts +1 -1
- package/plugin/src/__tests__/deployment_target.test.ts +41 -0
- package/plugin/src/__tests__/podfile.test.ts +136 -0
- package/plugin/src/index.ts +23 -3
- package/plugin/src/lib/deployment_target.ts +46 -0
- package/plugin/src/lib/podfile.ts +59 -14
- package/plugin/src/lib/spm.ts +1 -1
- package/plugin/tsconfig.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 12.0.2
|
|
2
|
+
|
|
3
|
+
See the repository CHANGELOG for the 12.0.2 release notes.
|
|
4
|
+
|
|
5
|
+
## 12.0.1
|
|
6
|
+
|
|
7
|
+
See the repository CHANGELOG for the 12.0.1 release notes.
|
|
8
|
+
|
|
1
9
|
## 12.0.0
|
|
2
10
|
|
|
3
11
|
First stable release of the Smile ID v12 SDK. Provides the iOS Apple Vision face detection module for `usesmileid`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/usesmileid_vision_face",
|
|
3
|
-
"version": "12.0.
|
|
3
|
+
"version": "12.0.2",
|
|
4
4
|
"description": "Apple Vision face detection implementation for the UseSmileID React Native (Expo) SDK.",
|
|
5
5
|
"homepage": "https://usesmileid.com",
|
|
6
6
|
"repository": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"CHANGELOG.md"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@smileid/
|
|
52
|
-
"@smileid/
|
|
51
|
+
"@smileid/usesmileid_bridge": "12.0.2",
|
|
52
|
+
"@smileid/usesmileid_platform_interface": "12.0.2"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"expo": ">=56.0.0",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"react-native": ">=0.83.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@expo/config-plugins": "^56.0.8",
|
|
61
60
|
"@types/node": "^25.9.1",
|
|
61
|
+
"expo": "^56.0.9",
|
|
62
62
|
"expo-modules-core": "^56.0.15",
|
|
63
63
|
"react-native": "^0.85.3",
|
|
64
64
|
"typescript": "^6.0.3"
|
package/plugin/build/index.d.ts
CHANGED
package/plugin/build/index.js
CHANGED
|
@@ -46,11 +46,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
46
46
|
};
|
|
47
47
|
})();
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
-
const config_plugins_1 = require("
|
|
49
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
50
50
|
const fs = __importStar(require("fs"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const deployment_target_1 = require("./lib/deployment_target");
|
|
52
53
|
const podfile_1 = require("./lib/podfile");
|
|
53
54
|
const spm_1 = require("./lib/spm");
|
|
55
|
+
// The lowest the Expo iOS template and the pods alongside it still compile at.
|
|
56
|
+
const IOS_DEPLOYMENT_TARGET = "16.4";
|
|
54
57
|
// Pinned to the exact `12.0.0` release tag — `from: 12.0.0`
|
|
55
58
|
// would not resolve because `ios-spm` publishes pre-release tags (e.g.
|
|
56
59
|
// `12.0.0`), and SPM excludes pre-release tags from open-ended
|
|
@@ -59,7 +62,7 @@ const SMILE_ID_PACKAGE = {
|
|
|
59
62
|
url: "https://github.com/smileidentity/ios-spm",
|
|
60
63
|
requirement: {
|
|
61
64
|
kind: "exactVersion",
|
|
62
|
-
version: "12.0.
|
|
65
|
+
version: "12.0.2",
|
|
63
66
|
},
|
|
64
67
|
};
|
|
65
68
|
const POD_TARGET = "UseSmileIDVisionFaceExpo";
|
|
@@ -73,7 +76,14 @@ const POD_TARGET = "UseSmileIDVisionFaceExpo";
|
|
|
73
76
|
// benign "Conflicting parseable interfaces" warning at link time.
|
|
74
77
|
const SPM_PRODUCTS = ["UseSmileIDVisionFace", "UseSmileIDBridge"];
|
|
75
78
|
const PODFILE_MARKER = "usesmileid_vision_face";
|
|
79
|
+
// Nothing else links the bridge's own pod target, so without this block its
|
|
80
|
+
// `import UseSmileIDBridge` fails. Both vision plugins emit it; the injection is
|
|
81
|
+
// idempotent per marker, so installing both is harmless.
|
|
82
|
+
const BRIDGE_POD_TARGET = "UseSmileIDBridgeExpo";
|
|
83
|
+
const BRIDGE_SPM_PRODUCTS = ["UseSmileIDBridge"];
|
|
84
|
+
const BRIDGE_PODFILE_MARKER = "usesmileid_bridge";
|
|
76
85
|
const withVisionFaceSpm = (config) => {
|
|
86
|
+
config = (0, deployment_target_1.withIosDeploymentTargetFloor)(config, IOS_DEPLOYMENT_TARGET);
|
|
77
87
|
// 1. Add the package + products to the host app's Xcode project so
|
|
78
88
|
// Xcode resolves the binary xcframeworks and embeds them in the
|
|
79
89
|
// .app at link time.
|
|
@@ -97,12 +107,19 @@ const withVisionFaceSpm = (config) => {
|
|
|
97
107
|
(config) => {
|
|
98
108
|
const podfilePath = path.join(config.modRequest.platformProjectRoot, "Podfile");
|
|
99
109
|
const original = fs.readFileSync(podfilePath, "utf-8");
|
|
100
|
-
|
|
110
|
+
let next = (0, podfile_1.injectPodfileSpmBlock)(original, {
|
|
101
111
|
marker: PODFILE_MARKER,
|
|
102
112
|
package: SMILE_ID_PACKAGE,
|
|
103
113
|
podTarget: POD_TARGET,
|
|
104
114
|
products: [...SPM_PRODUCTS],
|
|
105
115
|
});
|
|
116
|
+
next = (0, podfile_1.injectPodfileSpmBlock)(next, {
|
|
117
|
+
marker: BRIDGE_PODFILE_MARKER,
|
|
118
|
+
package: SMILE_ID_PACKAGE,
|
|
119
|
+
podTarget: BRIDGE_POD_TARGET,
|
|
120
|
+
products: [...BRIDGE_SPM_PRODUCTS],
|
|
121
|
+
required: true,
|
|
122
|
+
});
|
|
106
123
|
if (next !== original) {
|
|
107
124
|
fs.writeFileSync(podfilePath, next);
|
|
108
125
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iEAAiE;AACjE,0DAA0D;AAC1D,qEAAqE;AACrE,2CAA2C;AAC3C,mEAAmE;AACnE,oEAAoE;AACpE,uEAAuE;AACvE,EAAE;AACF,oDAAoD;AACpD,oEAAoE;AACpE,sEAAsE;AACtE,oEAAoE;AACpE,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE9B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
/** Sets `ios.deploymentTarget` to `minimum` unless the project already asks for a higher one. */
|
|
3
|
+
export declare const withIosDeploymentTargetFloor: ConfigPlugin<string>;
|
|
4
|
+
/** Pure form of the edit the plugin makes to the Podfile properties. */
|
|
5
|
+
export declare function raiseDeploymentTarget(properties: Readonly<Record<string, string>>, minimum: string): Record<string, string>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The Podfile prebuild generates reads `ios.deploymentTarget` from
|
|
3
|
+
// `ios/Podfile.properties.json`, which nothing the SDK publishes used to set.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.withIosDeploymentTargetFloor = void 0;
|
|
6
|
+
exports.raiseDeploymentTarget = raiseDeploymentTarget;
|
|
7
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
8
|
+
/** Sets `ios.deploymentTarget` to `minimum` unless the project already asks for a higher one. */
|
|
9
|
+
const withIosDeploymentTargetFloor = (config, minimum) => (0, config_plugins_1.withPodfileProperties)(config, (config) => {
|
|
10
|
+
config.modResults = raiseDeploymentTarget(config.modResults, minimum);
|
|
11
|
+
return config;
|
|
12
|
+
});
|
|
13
|
+
exports.withIosDeploymentTargetFloor = withIosDeploymentTargetFloor;
|
|
14
|
+
/** Pure form of the edit the plugin makes to the Podfile properties. */
|
|
15
|
+
function raiseDeploymentTarget(properties, minimum) {
|
|
16
|
+
const current = properties["ios.deploymentTarget"];
|
|
17
|
+
if (current !== undefined && compareVersions(current, minimum) >= 0) {
|
|
18
|
+
return { ...properties };
|
|
19
|
+
}
|
|
20
|
+
return { ...properties, "ios.deploymentTarget": minimum };
|
|
21
|
+
}
|
|
22
|
+
// An unparseable component sorts as 0 so a bad value in a partner's project is
|
|
23
|
+
// overwritten rather than crashing prebuild.
|
|
24
|
+
function compareVersions(left, right) {
|
|
25
|
+
const leftParts = left.split(".");
|
|
26
|
+
const rightParts = right.split(".");
|
|
27
|
+
for (let i = 0; i < Math.max(leftParts.length, rightParts.length); i++) {
|
|
28
|
+
const difference = toNumber(leftParts[i] ?? "0") - toNumber(rightParts[i] ?? "0");
|
|
29
|
+
if (difference !== 0) {
|
|
30
|
+
return difference;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
function toNumber(part) {
|
|
36
|
+
const parsed = Number.parseInt(part, 10);
|
|
37
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=deployment_target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment_target.js","sourceRoot":"","sources":["../../src/lib/deployment_target.ts"],"names":[],"mappings":";AAAA,mEAAmE;AACnE,8EAA8E;;;AAe9E,sDASC;AAtBD,wDAA+E;AAE/E,iGAAiG;AAC1F,MAAM,4BAA4B,GAAyB,CAChE,MAAM,EACN,OAAO,EACP,EAAE,CACF,IAAA,sCAAqB,EAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;IACvC,MAAM,CAAC,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC,CAAC;AAPQ,QAAA,4BAA4B,gCAOpC;AAEL,wEAAwE;AACxE,SAAgB,qBAAqB,CACnC,UAA4C,EAC5C,OAAe;IAEf,MAAM,OAAO,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,SAAS,IAAI,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,EAAE,GAAG,UAAU,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC;AAC5D,CAAC;AAED,+EAA+E;AAC/E,6CAA6C;AAC7C,SAAS,eAAe,CAAC,IAAY,EAAE,KAAa;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvE,MAAM,UAAU,GACd,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QACjE,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC"}
|
|
@@ -7,6 +7,8 @@ export interface PodfileLinkOptions {
|
|
|
7
7
|
podTarget: string;
|
|
8
8
|
/** SPM library product names to link to the pod target. */
|
|
9
9
|
products: string[];
|
|
10
|
+
/** Fail `pod install` when the target is absent — a warning there reads as success. */
|
|
11
|
+
required?: boolean;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Idempotently injects an SPM-linking Ruby block into the Podfile's
|
|
@@ -19,9 +19,13 @@ function injectPodfileSpmBlock(podfile, options) {
|
|
|
19
19
|
const block = renderBlock(options);
|
|
20
20
|
const beginMarker = `${BEGIN_PREFIX} ${options.marker} ===`;
|
|
21
21
|
const endMarker = `${END_PREFIX} ${options.marker} ===`;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// The indentation is captured so a rewrite can restore it; dropping it would
|
|
23
|
+
// make every prebuild rewrite the Podfile.
|
|
24
|
+
const blockRegex = new RegExp(`([ \\t]*)${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`, "m");
|
|
25
|
+
const existing = podfile.match(blockRegex);
|
|
26
|
+
if (existing) {
|
|
27
|
+
// A replacer function, so `$` in the Ruby block is never a replacement pattern.
|
|
28
|
+
return podfile.replace(blockRegex, () => indentBlock(block, existing[1] ?? ""));
|
|
25
29
|
}
|
|
26
30
|
// Inject at the *start* of the post_install body — right after
|
|
27
31
|
// `post_install do |installer|`. We tried matching the closing
|
|
@@ -42,16 +46,19 @@ function injectPodfileSpmBlock(podfile, options) {
|
|
|
42
46
|
"or wire the SPM block in manually.");
|
|
43
47
|
}
|
|
44
48
|
const insertAt = headerMatch.index + headerMatch[0].length;
|
|
45
|
-
const indented = block
|
|
46
|
-
.split("\n")
|
|
47
|
-
.map((line) => (line.length > 0 ? ` ${line}` : line))
|
|
48
|
-
.join("\n") + "\n";
|
|
49
|
+
const indented = `${indentBlock(block, " ")}\n`;
|
|
49
50
|
return podfile.slice(0, insertAt) + indented + podfile.slice(insertAt);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
function indentBlock(block, prefix) {
|
|
53
|
+
return block
|
|
54
|
+
.split("\n")
|
|
55
|
+
.map((line) => (line.length > 0 ? `${prefix}${line}` : line))
|
|
56
|
+
.join("\n");
|
|
57
|
+
}
|
|
58
|
+
// Injected strings land in Ruby literals, so a typo here could otherwise become
|
|
59
|
+
// code that runs during `pod install`. `#` and `"` are rejected alongside `'`,
|
|
60
|
+
// `\` and control chars because the diagnostics are double-quoted, where Ruby
|
|
61
|
+
// evaluates `#{...}`.
|
|
55
62
|
function validateRubySafe(options) {
|
|
56
63
|
const fields = [
|
|
57
64
|
["marker", options.marker],
|
|
@@ -77,7 +84,7 @@ function validateRubySafe(options) {
|
|
|
77
84
|
break;
|
|
78
85
|
}
|
|
79
86
|
for (const [name, value] of fields) {
|
|
80
|
-
if (/['
|
|
87
|
+
if (/['"#\\\n\r\t]/.test(value)) {
|
|
81
88
|
throw new Error(`[smileid-spm] Refusing to inject Podfile block: '${name}' contains an unsafe character (got: ${JSON.stringify(value)}).`);
|
|
82
89
|
}
|
|
83
90
|
}
|
|
@@ -87,6 +94,9 @@ function renderBlock(options) {
|
|
|
87
94
|
const endMarker = `${END_PREFIX} ${options.marker} ===`;
|
|
88
95
|
const requirement = renderRequirement(options.package.requirement);
|
|
89
96
|
const productList = options.products.map((p) => `'${p}'`).join(", ");
|
|
97
|
+
const missingTarget = options.required
|
|
98
|
+
? `raise "[smileid-spm] Pod target #{target_name} is missing from Pods.xcodeproj, so #{product_names.join(', ')} cannot be linked and the Smile ID bridge will not compile. Check that '@smileid/usesmileid_bridge' is installed and that its npm package ships its 'ios' directory."`
|
|
99
|
+
: `Pod::UI.warn "[smileid-spm] Pod target #{target_name} not found; skipping SPM link."`;
|
|
90
100
|
return `${beginMarker}
|
|
91
101
|
# Wires ${options.package.url} into Pods.xcodeproj and links its
|
|
92
102
|
# products to the '${options.podTarget}' pod target so the Swift
|
|
@@ -132,7 +142,7 @@ begin
|
|
|
132
142
|
|
|
133
143
|
target = pods_project.targets.find { |t| t.name == target_name }
|
|
134
144
|
if target.nil?
|
|
135
|
-
|
|
145
|
+
${missingTarget}
|
|
136
146
|
else
|
|
137
147
|
product_names.each do |product_name|
|
|
138
148
|
already = target.package_product_dependencies.any? do |d|
|
|
@@ -153,6 +163,37 @@ begin
|
|
|
153
163
|
bf.product_ref = dep
|
|
154
164
|
target.frameworks_build_phase.files << bf
|
|
155
165
|
end
|
|
166
|
+
|
|
167
|
+
# Every linking static pod absorbs ios-spm's source-target objects and the
|
|
168
|
+
# app's -ObjC link then sees duplicates — the app keeps the single copy.
|
|
169
|
+
strip_name = '[smileid-spm] Strip absorbed SPM source objects'
|
|
170
|
+
unless target.shell_script_build_phases.any? { |p| p.name == strip_name }
|
|
171
|
+
phase = target.new_shell_script_build_phase(strip_name)
|
|
172
|
+
phase.shell_script = <<~'STRIPSH'
|
|
173
|
+
set -u
|
|
174
|
+
A="\${TARGET_BUILD_DIR}/\${EXECUTABLE_PATH}"
|
|
175
|
+
[ -f "$A" ] || exit 0
|
|
176
|
+
strip_obj() {
|
|
177
|
+
ar -t "$1" 2>/dev/null | grep -q "^$2$" || return 0
|
|
178
|
+
ar -d "$1" "$2" && ranlib "$1" 2>/dev/null || true
|
|
179
|
+
}
|
|
180
|
+
ARCHS_IN=$(lipo -archs "$A" 2>/dev/null || true)
|
|
181
|
+
for OBJ in UseSmileIDSentrySupport.o UseSmileIDLottieSupport.o; do
|
|
182
|
+
if [ "$(echo "$ARCHS_IN" | wc -w)" -gt 1 ]; then
|
|
183
|
+
THIN=""
|
|
184
|
+
for ARCH in $ARCHS_IN; do
|
|
185
|
+
lipo -thin "$ARCH" "$A" -output "$A.$ARCH"
|
|
186
|
+
strip_obj "$A.$ARCH" "$OBJ"
|
|
187
|
+
THIN="$THIN $A.$ARCH"
|
|
188
|
+
done
|
|
189
|
+
lipo -create $THIN -output "$A"
|
|
190
|
+
rm -f $THIN
|
|
191
|
+
else
|
|
192
|
+
strip_obj "$A" "$OBJ"
|
|
193
|
+
fi
|
|
194
|
+
done
|
|
195
|
+
STRIPSH
|
|
196
|
+
end
|
|
156
197
|
end
|
|
157
198
|
|
|
158
199
|
pods_project.save
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"podfile.js","sourceRoot":"","sources":["../../src/lib/podfile.ts"],"names":[],"mappings":";AAAA,qEAAqE;AACrE,sEAAsE;AACtE,mEAAmE;AACnE,mEAAmE;AACnE,iEAAiE;;
|
|
1
|
+
{"version":3,"file":"podfile.js","sourceRoot":"","sources":["../../src/lib/podfile.ts"],"names":[],"mappings":";AAAA,qEAAqE;AACrE,sEAAsE;AACtE,mEAAmE;AACnE,mEAAmE;AACnE,iEAAiE;;AAyBjE,sDA+CC;AApED,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAChD,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAc5C;;;;;GAKG;AACH,SAAgB,qBAAqB,CACnC,OAAe,EACf,OAA2B;IAE3B,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE1B,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,WAAW,GAAG,GAAG,YAAY,IAAI,OAAO,CAAC,MAAM,MAAM,CAAC;IAC5D,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,MAAM,MAAM,CAAC;IAExD,6EAA6E;IAC7E,2CAA2C;IAC3C,MAAM,UAAU,GAAG,IAAI,MAAM,CAC3B,YAAY,YAAY,CAAC,WAAW,CAAC,aAAa,YAAY,CAAC,SAAS,CAAC,EAAE,EAC3E,GAAG,CACJ,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACb,gFAAgF;QAChF,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,EAAE,CACtC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CACtC,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,+DAA+D;IAC/D,+DAA+D;IAC/D,4DAA4D;IAC5D,4DAA4D;IAC5D,8DAA8D;IAC9D,6DAA6D;IAC7D,iEAAiE;IACjE,gEAAgE;IAChE,mDAAmD;IACnD,MAAM,WAAW,GAAG,gDAAgD,CAAC;IACrE,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/C,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,uGAAuG;YACvG,oGAAoG;YACpG,oCAAoC,CACvC,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3D,MAAM,QAAQ,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC;IACnD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,MAAc;IAChD,OAAO,KAAK;SACT,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC5D,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,sBAAsB;AACtB,SAAS,gBAAgB,CAAC,OAA2B;IACnD,MAAM,MAAM,GAA4B;QACtC,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC;QAC1B,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QACpC,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC;KACjC,CAAC;IACF,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,QAAQ,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACzC,KAAK,sBAAsB,CAAC;QAC5B,KAAK,sBAAsB;YACzB,MAAM,CAAC,IAAI,CAAC;gBACV,4BAA4B;gBAC5B,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc;aAC3C,CAAC,CAAC;YACH,MAAM;QACR,KAAK,cAAc;YACjB,MAAM,CAAC,IAAI,CAAC,CAAC,qBAAqB,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1E,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAoB,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;YACxE,MAAM;IACV,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,oDAAoD,IAAI,wCAAwC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAC1H,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,OAA2B;IAC9C,MAAM,WAAW,GAAG,GAAG,YAAY,IAAI,OAAO,CAAC,MAAM,MAAM,CAAC;IAC5D,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,MAAM,MAAM,CAAC;IACxD,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ;QACpC,CAAC,CAAC,oRAAoR;QACtR,CAAC,CAAC,sFAAsF,CAAC;IAE3F,OAAO,GAAG,WAAW;UACb,OAAO,CAAC,OAAO,CAAC,GAAG;qBACR,OAAO,CAAC,SAAS;;;;;;;;;;;;;;;;;eAiBvB,OAAO,CAAC,OAAO,CAAC,GAAG;sBACZ,WAAW;qBACZ,WAAW;mBACb,OAAO,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;MAuB9B,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gEAwD6C,OAAO,CAAC,SAAS;;;EAG/E,SAAS,EAAE,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAmB;IAC5C,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,sBAAsB;YACzB,OAAO,oDAAoD,GAAG,CAAC,cAAc,KAAK,CAAC;QACrF,KAAK,sBAAsB;YACzB,OAAO,oDAAoD,GAAG,CAAC,cAAc,KAAK,CAAC;QACrF,KAAK,cAAc;YACjB,OAAO,qCAAqC,GAAG,CAAC,OAAO,KAAK,CAAC;QAC/D,KAAK,QAAQ;YACX,OAAO,8BAA8B,GAAG,CAAC,MAAM,KAAK,CAAC;IACzD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { raiseDeploymentTarget } from "../lib/deployment_target";
|
|
4
|
+
|
|
5
|
+
describe("raiseDeploymentTarget", () => {
|
|
6
|
+
it("sets the floor when the project declares nothing", () => {
|
|
7
|
+
expect(raiseDeploymentTarget({}, "16.4")).toEqual({
|
|
8
|
+
"ios.deploymentTarget": "16.4",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("raises a target below the floor", () => {
|
|
13
|
+
expect(
|
|
14
|
+
raiseDeploymentTarget({ "ios.deploymentTarget": "15.0" }, "16.4"),
|
|
15
|
+
).toEqual({ "ios.deploymentTarget": "16.4" });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("leaves a higher target the partner chose alone", () => {
|
|
19
|
+
expect(
|
|
20
|
+
raiseDeploymentTarget({ "ios.deploymentTarget": "18.0" }, "16.4"),
|
|
21
|
+
).toEqual({ "ios.deploymentTarget": "18.0" });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("compares components numerically rather than as text", () => {
|
|
25
|
+
expect(
|
|
26
|
+
raiseDeploymentTarget({ "ios.deploymentTarget": "16.10" }, "16.4"),
|
|
27
|
+
).toEqual({ "ios.deploymentTarget": "16.10" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("overwrites a value it cannot parse", () => {
|
|
31
|
+
expect(
|
|
32
|
+
raiseDeploymentTarget({ "ios.deploymentTarget": "latest" }, "16.4"),
|
|
33
|
+
).toEqual({ "ios.deploymentTarget": "16.4" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("preserves unrelated Podfile properties", () => {
|
|
37
|
+
expect(
|
|
38
|
+
raiseDeploymentTarget({ "expo.jsEngine": "hermes" }, "16.4"),
|
|
39
|
+
).toEqual({ "expo.jsEngine": "hermes", "ios.deploymentTarget": "16.4" });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { injectPodfileSpmBlock } from "../lib/podfile";
|
|
4
|
+
import type { SpmPackage } from "../lib/spm";
|
|
5
|
+
|
|
6
|
+
const PACKAGE: SpmPackage = {
|
|
7
|
+
url: "https://github.com/smileidentity/ios-spm",
|
|
8
|
+
requirement: { kind: "exactVersion", version: "12.0.1" },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PODFILE = `platform :ios, podfile_properties['ios.deploymentTarget']
|
|
12
|
+
|
|
13
|
+
target 'sample' do
|
|
14
|
+
use_expo_modules!
|
|
15
|
+
config = use_native_modules!
|
|
16
|
+
|
|
17
|
+
post_install do |installer|
|
|
18
|
+
react_native_post_install(
|
|
19
|
+
installer,
|
|
20
|
+
config[:reactNativePath],
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
function injectProvider(podfile: string): string {
|
|
27
|
+
return injectPodfileSpmBlock(podfile, {
|
|
28
|
+
marker: "usesmileid_vision_face",
|
|
29
|
+
package: PACKAGE,
|
|
30
|
+
podTarget: "UseSmileIDVisionFaceExpo",
|
|
31
|
+
products: ["UseSmileIDVisionFace", "UseSmileIDBridge"],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function injectBridge(podfile: string): string {
|
|
36
|
+
return injectPodfileSpmBlock(podfile, {
|
|
37
|
+
marker: "usesmileid_bridge",
|
|
38
|
+
package: PACKAGE,
|
|
39
|
+
podTarget: "UseSmileIDBridgeExpo",
|
|
40
|
+
products: ["UseSmileIDBridge"],
|
|
41
|
+
required: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("injectPodfileSpmBlock", () => {
|
|
46
|
+
it("keeps the provider and bridge blocks as two separate regions", () => {
|
|
47
|
+
const result = injectBridge(injectProvider(PODFILE));
|
|
48
|
+
|
|
49
|
+
expect(result).toContain("# === BEGIN smileid-spm: usesmileid_vision_face");
|
|
50
|
+
expect(result).toContain("# === BEGIN smileid-spm: usesmileid_bridge");
|
|
51
|
+
expect(result).toContain("target_name = 'UseSmileIDVisionFaceExpo'");
|
|
52
|
+
expect(result).toContain("target_name = 'UseSmileIDBridgeExpo'");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("nests neither block inside the other", () => {
|
|
56
|
+
const result = injectBridge(injectProvider(PODFILE));
|
|
57
|
+
|
|
58
|
+
const order = [
|
|
59
|
+
result.indexOf("# === BEGIN smileid-spm: usesmileid_bridge"),
|
|
60
|
+
result.indexOf("# === END smileid-spm: usesmileid_bridge"),
|
|
61
|
+
result.indexOf("# === BEGIN smileid-spm: usesmileid_vision_face"),
|
|
62
|
+
result.indexOf("# === END smileid-spm: usesmileid_vision_face"),
|
|
63
|
+
];
|
|
64
|
+
expect(order.every((index) => index >= 0)).toBe(true);
|
|
65
|
+
expect(order[1]).toBeLessThan(order[2]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("leaves the React Native post-install call in place", () => {
|
|
69
|
+
const result = injectBridge(injectProvider(PODFILE));
|
|
70
|
+
|
|
71
|
+
expect(result).toContain("react_native_post_install(");
|
|
72
|
+
expect(result.match(/post_install do \|installer\|/g)).toHaveLength(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("is idempotent for both blocks", () => {
|
|
76
|
+
const once = injectBridge(injectProvider(PODFILE));
|
|
77
|
+
|
|
78
|
+
expect(injectBridge(injectProvider(once))).toBe(once);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("fails pod install when a required pod target is missing", () => {
|
|
82
|
+
const result = injectBridge(PODFILE);
|
|
83
|
+
|
|
84
|
+
const bridgeBlock = blockFor(result, "usesmileid_bridge");
|
|
85
|
+
expect(bridgeBlock).toContain('raise "[smileid-spm] Pod target');
|
|
86
|
+
expect(bridgeBlock).not.toContain("skipping SPM link");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("only warns when a non-required pod target is missing", () => {
|
|
90
|
+
const result = injectProvider(PODFILE);
|
|
91
|
+
|
|
92
|
+
const providerBlock = blockFor(result, "usesmileid_vision_face");
|
|
93
|
+
expect(providerBlock).toContain("skipping SPM link");
|
|
94
|
+
expect(providerBlock).not.toContain('raise "[smileid-spm] Pod target');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("refuses to inject without a post_install hook to anchor on", () => {
|
|
98
|
+
expect(() => injectProvider("target 'sample' do\nend\n")).toThrow(
|
|
99
|
+
/post_install/,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("rejects a pod target that could break out of the Ruby literal", () => {
|
|
104
|
+
expect(() =>
|
|
105
|
+
injectPodfileSpmBlock(PODFILE, {
|
|
106
|
+
marker: "usesmileid_bridge",
|
|
107
|
+
package: PACKAGE,
|
|
108
|
+
podTarget: "Bridge'; system('rm -rf /') #",
|
|
109
|
+
products: ["UseSmileIDBridge"],
|
|
110
|
+
}),
|
|
111
|
+
).toThrow(/unsafe character/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// The diagnostics are double-quoted Ruby, where `#{...}` evaluates.
|
|
115
|
+
it.each([
|
|
116
|
+
["interpolation", "Bridge#{`rm -rf /`}"],
|
|
117
|
+
["double quote", 'Bridge"'],
|
|
118
|
+
])("rejects a pod target carrying a Ruby %s", (_label, podTarget) => {
|
|
119
|
+
expect(() =>
|
|
120
|
+
injectPodfileSpmBlock(PODFILE, {
|
|
121
|
+
marker: "usesmileid_bridge",
|
|
122
|
+
package: PACKAGE,
|
|
123
|
+
podTarget,
|
|
124
|
+
products: ["UseSmileIDBridge"],
|
|
125
|
+
}),
|
|
126
|
+
).toThrow(/unsafe character/);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function blockFor(podfile: string, marker: string): string {
|
|
131
|
+
const begin = podfile.indexOf(`# === BEGIN smileid-spm: ${marker} ===`);
|
|
132
|
+
const end = podfile.indexOf(`# === END smileid-spm: ${marker} ===`);
|
|
133
|
+
expect(begin).toBeGreaterThanOrEqual(0);
|
|
134
|
+
expect(end).toBeGreaterThan(begin);
|
|
135
|
+
return podfile.slice(begin, end);
|
|
136
|
+
}
|
package/plugin/src/index.ts
CHANGED
|
@@ -16,13 +16,17 @@ import {
|
|
|
16
16
|
ConfigPlugin,
|
|
17
17
|
withDangerousMod,
|
|
18
18
|
withXcodeProject,
|
|
19
|
-
} from "
|
|
19
|
+
} from "expo/config-plugins";
|
|
20
20
|
import * as fs from "fs";
|
|
21
21
|
import * as path from "path";
|
|
22
22
|
|
|
23
|
+
import { withIosDeploymentTargetFloor } from "./lib/deployment_target";
|
|
23
24
|
import { injectPodfileSpmBlock } from "./lib/podfile";
|
|
24
25
|
import { linkSpmPackage, SpmPackage } from "./lib/spm";
|
|
25
26
|
|
|
27
|
+
// The lowest the Expo iOS template and the pods alongside it still compile at.
|
|
28
|
+
const IOS_DEPLOYMENT_TARGET = "16.4";
|
|
29
|
+
|
|
26
30
|
// Pinned to the exact `12.0.0` release tag — `from: 12.0.0`
|
|
27
31
|
// would not resolve because `ios-spm` publishes pre-release tags (e.g.
|
|
28
32
|
// `12.0.0`), and SPM excludes pre-release tags from open-ended
|
|
@@ -31,7 +35,7 @@ const SMILE_ID_PACKAGE: SpmPackage = {
|
|
|
31
35
|
url: "https://github.com/smileidentity/ios-spm",
|
|
32
36
|
requirement: {
|
|
33
37
|
kind: "exactVersion",
|
|
34
|
-
version: "12.0.
|
|
38
|
+
version: "12.0.2",
|
|
35
39
|
},
|
|
36
40
|
};
|
|
37
41
|
|
|
@@ -47,7 +51,16 @@ const POD_TARGET = "UseSmileIDVisionFaceExpo";
|
|
|
47
51
|
const SPM_PRODUCTS = ["UseSmileIDVisionFace", "UseSmileIDBridge"] as const;
|
|
48
52
|
const PODFILE_MARKER = "usesmileid_vision_face";
|
|
49
53
|
|
|
54
|
+
// Nothing else links the bridge's own pod target, so without this block its
|
|
55
|
+
// `import UseSmileIDBridge` fails. Both vision plugins emit it; the injection is
|
|
56
|
+
// idempotent per marker, so installing both is harmless.
|
|
57
|
+
const BRIDGE_POD_TARGET = "UseSmileIDBridgeExpo";
|
|
58
|
+
const BRIDGE_SPM_PRODUCTS = ["UseSmileIDBridge"] as const;
|
|
59
|
+
const BRIDGE_PODFILE_MARKER = "usesmileid_bridge";
|
|
60
|
+
|
|
50
61
|
const withVisionFaceSpm: ConfigPlugin = (config) => {
|
|
62
|
+
config = withIosDeploymentTargetFloor(config, IOS_DEPLOYMENT_TARGET);
|
|
63
|
+
|
|
51
64
|
// 1. Add the package + products to the host app's Xcode project so
|
|
52
65
|
// Xcode resolves the binary xcframeworks and embeds them in the
|
|
53
66
|
// .app at link time.
|
|
@@ -77,12 +90,19 @@ const withVisionFaceSpm: ConfigPlugin = (config) => {
|
|
|
77
90
|
"Podfile",
|
|
78
91
|
);
|
|
79
92
|
const original = fs.readFileSync(podfilePath, "utf-8");
|
|
80
|
-
|
|
93
|
+
let next = injectPodfileSpmBlock(original, {
|
|
81
94
|
marker: PODFILE_MARKER,
|
|
82
95
|
package: SMILE_ID_PACKAGE,
|
|
83
96
|
podTarget: POD_TARGET,
|
|
84
97
|
products: [...SPM_PRODUCTS],
|
|
85
98
|
});
|
|
99
|
+
next = injectPodfileSpmBlock(next, {
|
|
100
|
+
marker: BRIDGE_PODFILE_MARKER,
|
|
101
|
+
package: SMILE_ID_PACKAGE,
|
|
102
|
+
podTarget: BRIDGE_POD_TARGET,
|
|
103
|
+
products: [...BRIDGE_SPM_PRODUCTS],
|
|
104
|
+
required: true,
|
|
105
|
+
});
|
|
86
106
|
if (next !== original) {
|
|
87
107
|
fs.writeFileSync(podfilePath, next);
|
|
88
108
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The Podfile prebuild generates reads `ios.deploymentTarget` from
|
|
2
|
+
// `ios/Podfile.properties.json`, which nothing the SDK publishes used to set.
|
|
3
|
+
|
|
4
|
+
import { type ConfigPlugin, withPodfileProperties } from "expo/config-plugins";
|
|
5
|
+
|
|
6
|
+
/** Sets `ios.deploymentTarget` to `minimum` unless the project already asks for a higher one. */
|
|
7
|
+
export const withIosDeploymentTargetFloor: ConfigPlugin<string> = (
|
|
8
|
+
config,
|
|
9
|
+
minimum,
|
|
10
|
+
) =>
|
|
11
|
+
withPodfileProperties(config, (config) => {
|
|
12
|
+
config.modResults = raiseDeploymentTarget(config.modResults, minimum);
|
|
13
|
+
return config;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Pure form of the edit the plugin makes to the Podfile properties. */
|
|
17
|
+
export function raiseDeploymentTarget(
|
|
18
|
+
properties: Readonly<Record<string, string>>,
|
|
19
|
+
minimum: string,
|
|
20
|
+
): Record<string, string> {
|
|
21
|
+
const current = properties["ios.deploymentTarget"];
|
|
22
|
+
if (current !== undefined && compareVersions(current, minimum) >= 0) {
|
|
23
|
+
return { ...properties };
|
|
24
|
+
}
|
|
25
|
+
return { ...properties, "ios.deploymentTarget": minimum };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// An unparseable component sorts as 0 so a bad value in a partner's project is
|
|
29
|
+
// overwritten rather than crashing prebuild.
|
|
30
|
+
function compareVersions(left: string, right: string): number {
|
|
31
|
+
const leftParts = left.split(".");
|
|
32
|
+
const rightParts = right.split(".");
|
|
33
|
+
for (let i = 0; i < Math.max(leftParts.length, rightParts.length); i++) {
|
|
34
|
+
const difference =
|
|
35
|
+
toNumber(leftParts[i] ?? "0") - toNumber(rightParts[i] ?? "0");
|
|
36
|
+
if (difference !== 0) {
|
|
37
|
+
return difference;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function toNumber(part: string): number {
|
|
44
|
+
const parsed = Number.parseInt(part, 10);
|
|
45
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
46
|
+
}
|
|
@@ -17,6 +17,8 @@ export interface PodfileLinkOptions {
|
|
|
17
17
|
podTarget: string;
|
|
18
18
|
/** SPM library product names to link to the pod target. */
|
|
19
19
|
products: string[];
|
|
20
|
+
/** Fail `pod install` when the target is absent — a warning there reads as success. */
|
|
21
|
+
required?: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -35,12 +37,18 @@ export function injectPodfileSpmBlock(
|
|
|
35
37
|
const beginMarker = `${BEGIN_PREFIX} ${options.marker} ===`;
|
|
36
38
|
const endMarker = `${END_PREFIX} ${options.marker} ===`;
|
|
37
39
|
|
|
40
|
+
// The indentation is captured so a rewrite can restore it; dropping it would
|
|
41
|
+
// make every prebuild rewrite the Podfile.
|
|
38
42
|
const blockRegex = new RegExp(
|
|
39
|
-
|
|
43
|
+
`([ \\t]*)${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`,
|
|
40
44
|
"m",
|
|
41
45
|
);
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
const existing = podfile.match(blockRegex);
|
|
47
|
+
if (existing) {
|
|
48
|
+
// A replacer function, so `$` in the Ruby block is never a replacement pattern.
|
|
49
|
+
return podfile.replace(blockRegex, () =>
|
|
50
|
+
indentBlock(block, existing[1] ?? ""),
|
|
51
|
+
);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
// Inject at the *start* of the post_install body — right after
|
|
@@ -64,18 +72,21 @@ export function injectPodfileSpmBlock(
|
|
|
64
72
|
);
|
|
65
73
|
}
|
|
66
74
|
const insertAt = headerMatch.index + headerMatch[0].length;
|
|
67
|
-
const indented =
|
|
68
|
-
block
|
|
69
|
-
.split("\n")
|
|
70
|
-
.map((line) => (line.length > 0 ? ` ${line}` : line))
|
|
71
|
-
.join("\n") + "\n";
|
|
75
|
+
const indented = `${indentBlock(block, " ")}\n`;
|
|
72
76
|
return podfile.slice(0, insertAt) + indented + podfile.slice(insertAt);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
function indentBlock(block: string, prefix: string): string {
|
|
80
|
+
return block
|
|
81
|
+
.split("\n")
|
|
82
|
+
.map((line) => (line.length > 0 ? `${prefix}${line}` : line))
|
|
83
|
+
.join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Injected strings land in Ruby literals, so a typo here could otherwise become
|
|
87
|
+
// code that runs during `pod install`. `#` and `"` are rejected alongside `'`,
|
|
88
|
+
// `\` and control chars because the diagnostics are double-quoted, where Ruby
|
|
89
|
+
// evaluates `#{...}`.
|
|
79
90
|
function validateRubySafe(options: PodfileLinkOptions): void {
|
|
80
91
|
const fields: Array<[string, string]> = [
|
|
81
92
|
["marker", options.marker],
|
|
@@ -101,7 +112,7 @@ function validateRubySafe(options: PodfileLinkOptions): void {
|
|
|
101
112
|
break;
|
|
102
113
|
}
|
|
103
114
|
for (const [name, value] of fields) {
|
|
104
|
-
if (/['
|
|
115
|
+
if (/['"#\\\n\r\t]/.test(value)) {
|
|
105
116
|
throw new Error(
|
|
106
117
|
`[smileid-spm] Refusing to inject Podfile block: '${name}' contains an unsafe character (got: ${JSON.stringify(value)}).`,
|
|
107
118
|
);
|
|
@@ -114,6 +125,9 @@ function renderBlock(options: PodfileLinkOptions): string {
|
|
|
114
125
|
const endMarker = `${END_PREFIX} ${options.marker} ===`;
|
|
115
126
|
const requirement = renderRequirement(options.package.requirement);
|
|
116
127
|
const productList = options.products.map((p) => `'${p}'`).join(", ");
|
|
128
|
+
const missingTarget = options.required
|
|
129
|
+
? `raise "[smileid-spm] Pod target #{target_name} is missing from Pods.xcodeproj, so #{product_names.join(', ')} cannot be linked and the Smile ID bridge will not compile. Check that '@smileid/usesmileid_bridge' is installed and that its npm package ships its 'ios' directory."`
|
|
130
|
+
: `Pod::UI.warn "[smileid-spm] Pod target #{target_name} not found; skipping SPM link."`;
|
|
117
131
|
|
|
118
132
|
return `${beginMarker}
|
|
119
133
|
# Wires ${options.package.url} into Pods.xcodeproj and links its
|
|
@@ -160,7 +174,7 @@ begin
|
|
|
160
174
|
|
|
161
175
|
target = pods_project.targets.find { |t| t.name == target_name }
|
|
162
176
|
if target.nil?
|
|
163
|
-
|
|
177
|
+
${missingTarget}
|
|
164
178
|
else
|
|
165
179
|
product_names.each do |product_name|
|
|
166
180
|
already = target.package_product_dependencies.any? do |d|
|
|
@@ -181,6 +195,37 @@ begin
|
|
|
181
195
|
bf.product_ref = dep
|
|
182
196
|
target.frameworks_build_phase.files << bf
|
|
183
197
|
end
|
|
198
|
+
|
|
199
|
+
# Every linking static pod absorbs ios-spm's source-target objects and the
|
|
200
|
+
# app's -ObjC link then sees duplicates — the app keeps the single copy.
|
|
201
|
+
strip_name = '[smileid-spm] Strip absorbed SPM source objects'
|
|
202
|
+
unless target.shell_script_build_phases.any? { |p| p.name == strip_name }
|
|
203
|
+
phase = target.new_shell_script_build_phase(strip_name)
|
|
204
|
+
phase.shell_script = <<~'STRIPSH'
|
|
205
|
+
set -u
|
|
206
|
+
A="\${TARGET_BUILD_DIR}/\${EXECUTABLE_PATH}"
|
|
207
|
+
[ -f "$A" ] || exit 0
|
|
208
|
+
strip_obj() {
|
|
209
|
+
ar -t "$1" 2>/dev/null | grep -q "^$2$" || return 0
|
|
210
|
+
ar -d "$1" "$2" && ranlib "$1" 2>/dev/null || true
|
|
211
|
+
}
|
|
212
|
+
ARCHS_IN=$(lipo -archs "$A" 2>/dev/null || true)
|
|
213
|
+
for OBJ in UseSmileIDSentrySupport.o UseSmileIDLottieSupport.o; do
|
|
214
|
+
if [ "$(echo "$ARCHS_IN" | wc -w)" -gt 1 ]; then
|
|
215
|
+
THIN=""
|
|
216
|
+
for ARCH in $ARCHS_IN; do
|
|
217
|
+
lipo -thin "$ARCH" "$A" -output "$A.$ARCH"
|
|
218
|
+
strip_obj "$A.$ARCH" "$OBJ"
|
|
219
|
+
THIN="$THIN $A.$ARCH"
|
|
220
|
+
done
|
|
221
|
+
lipo -create $THIN -output "$A"
|
|
222
|
+
rm -f $THIN
|
|
223
|
+
else
|
|
224
|
+
strip_obj "$A" "$OBJ"
|
|
225
|
+
fi
|
|
226
|
+
done
|
|
227
|
+
STRIPSH
|
|
228
|
+
end
|
|
184
229
|
end
|
|
185
230
|
|
|
186
231
|
pods_project.save
|
package/plugin/src/lib/spm.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// field, so listing SPM products in `expo-module.config.json` is a
|
|
8
8
|
// silent no-op. This module fills that gap.
|
|
9
9
|
|
|
10
|
-
import { XcodeProject } from "
|
|
10
|
+
import { XcodeProject } from "expo/config-plugins";
|
|
11
11
|
|
|
12
12
|
export interface SpmPackage {
|
|
13
13
|
/** Remote git URL of the Swift Package, e.g. `https://github.com/smileidentity/ios-spm`. */
|