@smileid/usesmileid_vision_document 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 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 document detection module for `usesmileid`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/usesmileid_vision_document",
3
- "version": "12.0.0",
3
+ "version": "12.0.2",
4
4
  "description": "Apple Vision document 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/usesmileid_bridge": "12.0.0",
52
- "@smileid/usesmileid_platform_interface": "12.0.0"
51
+ "@smileid/usesmileid_platform_interface": "12.0.2",
52
+ "@smileid/usesmileid_bridge": "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"
@@ -1,3 +1,3 @@
1
- import { ConfigPlugin } from "@expo/config-plugins";
1
+ import { ConfigPlugin } from "expo/config-plugins";
2
2
  declare const withVisionDocumentSpm: ConfigPlugin;
3
3
  export default withVisionDocumentSpm;
@@ -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("@expo/config-plugins");
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,13 +62,20 @@ const SMILE_ID_PACKAGE = {
59
62
  url: "https://github.com/smileidentity/ios-spm",
60
63
  requirement: {
61
64
  kind: "exactVersion",
62
- version: "12.0.0",
65
+ version: "12.0.2",
63
66
  },
64
67
  };
65
68
  const POD_TARGET = "UseSmileIDVisionDocumentExpo";
66
69
  const SPM_PRODUCTS = ["UseSmileIDVisionDocument", "UseSmileIDBridge"];
67
70
  const PODFILE_MARKER = "usesmileid_vision_document";
71
+ // Nothing else links the bridge's own pod target, so without this block its
72
+ // `import UseSmileIDBridge` fails. Both vision plugins emit it; the injection is
73
+ // idempotent per marker, so installing both is harmless.
74
+ const BRIDGE_POD_TARGET = "UseSmileIDBridgeExpo";
75
+ const BRIDGE_SPM_PRODUCTS = ["UseSmileIDBridge"];
76
+ const BRIDGE_PODFILE_MARKER = "usesmileid_bridge";
68
77
  const withVisionDocumentSpm = (config) => {
78
+ config = (0, deployment_target_1.withIosDeploymentTargetFloor)(config, IOS_DEPLOYMENT_TARGET);
69
79
  // 1. Add the package + products to the host app's Xcode project so
70
80
  // Xcode resolves the binary xcframeworks and embeds them in the
71
81
  // .app at link time.
@@ -89,12 +99,19 @@ const withVisionDocumentSpm = (config) => {
89
99
  (config) => {
90
100
  const podfilePath = path.join(config.modRequest.platformProjectRoot, "Podfile");
91
101
  const original = fs.readFileSync(podfilePath, "utf-8");
92
- const next = (0, podfile_1.injectPodfileSpmBlock)(original, {
102
+ let next = (0, podfile_1.injectPodfileSpmBlock)(original, {
93
103
  marker: PODFILE_MARKER,
94
104
  package: SMILE_ID_PACKAGE,
95
105
  podTarget: POD_TARGET,
96
106
  products: [...SPM_PRODUCTS],
97
107
  });
108
+ next = (0, podfile_1.injectPodfileSpmBlock)(next, {
109
+ marker: BRIDGE_PODFILE_MARKER,
110
+ package: SMILE_ID_PACKAGE,
111
+ podTarget: BRIDGE_POD_TARGET,
112
+ products: [...BRIDGE_SPM_PRODUCTS],
113
+ required: true,
114
+ });
98
115
  if (next !== original) {
99
116
  fs.writeFileSync(podfilePath, next);
100
117
  }
@@ -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,wEAAwE;AACxE,2EAA2E;AAC3E,EAAE;AACF,oDAAoD;AACpD,oEAAoE;AACpE,sEAAsE;AACtE,oEAAoE;AACpE,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE9B,yDAI8B;AAC9B,uCAAyB;AACzB,2CAA6B;AAE7B,2CAAsD;AACtD,mCAAuD;AAEvD,4DAA4D;AAC5D,uEAAuE;AACvE,+DAA+D;AAC/D,8BAA8B;AAC9B,MAAM,gBAAgB,GAAe;IACnC,GAAG,EAAE,0CAA0C;IAC/C,WAAW,EAAE;QACX,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,QAAQ;KAClB;CACF,CAAC;AAEF,MAAM,UAAU,GAAG,8BAA8B,CAAC;AAClD,MAAM,YAAY,GAAG,CAAC,0BAA0B,EAAE,kBAAkB,CAAU,CAAC;AAC/E,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAEpD,MAAM,qBAAqB,GAAiB,CAAC,MAAM,EAAE,EAAE;IACrD,mEAAmE;IACnE,mEAAmE;IACnE,wBAAwB;IACxB,MAAM,GAAG,IAAA,iCAAgB,EAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;QAChD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;QACJ,CAAC;QACD,IAAA,oBAAc,EAAC,MAAM,CAAC,UAAU,EAAE,gBAAgB,EAAE;YAClD,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,CAAC,GAAG,YAAY,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,mEAAmE;IACnE,iEAAiE;IACjE,+DAA+D;IAC/D,oEAAoE;IACpE,MAAM,GAAG,IAAA,iCAAgB,EAAC,MAAM,EAAE;QAChC,KAAK;QACL,CAAC,MAAM,EAAE,EAAE;YACT,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAC3B,MAAM,CAAC,UAAU,CAAC,mBAAmB,EACrC,SAAS,CACV,CAAC;YACF,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,IAAA,+BAAqB,EAAC,QAAQ,EAAE;gBAC3C,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,gBAAgB;gBACzB,SAAS,EAAE,UAAU;gBACrB,QAAQ,EAAE,CAAC,GAAG,YAAY,CAAC;aAC5B,CAAC,CAAC;YACH,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,kBAAe,qBAAqB,CAAC"}
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
- const blockRegex = new RegExp(`${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`, "m");
23
- if (blockRegex.test(podfile)) {
24
- return podfile.replace(blockRegex, block);
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
- // All injected strings flow into single-quoted Ruby literals. We
52
- // reject characters that could break out of those literals so a
53
- // typo in the bridge plugin can't produce code that runs during
54
- // `pod install`. Rejected: single-quote, backslash, control chars.
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 (/['\\\n\r\t]/.test(value)) {
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
- Pod::UI.warn "[smileid-spm] Pod target #{target_name} not found; skipping SPM link."
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":"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"}
1
+ {"version":3,"file":"podfile.js","sourceRoot":"","sources":["../../src/lib/podfile.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { XcodeProject } from "@expo/config-plugins";
1
+ import { XcodeProject } from "expo/config-plugins";
2
2
  export interface SpmPackage {
3
3
  /** Remote git URL of the Swift Package, e.g. `https://github.com/smileidentity/ios-spm`. */
4
4
  url: string;
@@ -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,138 @@
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_document",
29
+ package: PACKAGE,
30
+ podTarget: "UseSmileIDVisionDocumentExpo",
31
+ products: ["UseSmileIDVisionDocument", "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(
50
+ "# === BEGIN smileid-spm: usesmileid_vision_document",
51
+ );
52
+ expect(result).toContain("# === BEGIN smileid-spm: usesmileid_bridge");
53
+ expect(result).toContain("target_name = 'UseSmileIDVisionDocumentExpo'");
54
+ expect(result).toContain("target_name = 'UseSmileIDBridgeExpo'");
55
+ });
56
+
57
+ it("nests neither block inside the other", () => {
58
+ const result = injectBridge(injectProvider(PODFILE));
59
+
60
+ const order = [
61
+ result.indexOf("# === BEGIN smileid-spm: usesmileid_bridge"),
62
+ result.indexOf("# === END smileid-spm: usesmileid_bridge"),
63
+ result.indexOf("# === BEGIN smileid-spm: usesmileid_vision_document"),
64
+ result.indexOf("# === END smileid-spm: usesmileid_vision_document"),
65
+ ];
66
+ expect(order.every((index) => index >= 0)).toBe(true);
67
+ expect(order[1]).toBeLessThan(order[2]);
68
+ });
69
+
70
+ it("leaves the React Native post-install call in place", () => {
71
+ const result = injectBridge(injectProvider(PODFILE));
72
+
73
+ expect(result).toContain("react_native_post_install(");
74
+ expect(result.match(/post_install do \|installer\|/g)).toHaveLength(1);
75
+ });
76
+
77
+ it("is idempotent for both blocks", () => {
78
+ const once = injectBridge(injectProvider(PODFILE));
79
+
80
+ expect(injectBridge(injectProvider(once))).toBe(once);
81
+ });
82
+
83
+ it("fails pod install when a required pod target is missing", () => {
84
+ const result = injectBridge(PODFILE);
85
+
86
+ const bridgeBlock = blockFor(result, "usesmileid_bridge");
87
+ expect(bridgeBlock).toContain('raise "[smileid-spm] Pod target');
88
+ expect(bridgeBlock).not.toContain("skipping SPM link");
89
+ });
90
+
91
+ it("only warns when a non-required pod target is missing", () => {
92
+ const result = injectProvider(PODFILE);
93
+
94
+ const providerBlock = blockFor(result, "usesmileid_vision_document");
95
+ expect(providerBlock).toContain("skipping SPM link");
96
+ expect(providerBlock).not.toContain('raise "[smileid-spm] Pod target');
97
+ });
98
+
99
+ it("refuses to inject without a post_install hook to anchor on", () => {
100
+ expect(() => injectProvider("target 'sample' do\nend\n")).toThrow(
101
+ /post_install/,
102
+ );
103
+ });
104
+
105
+ it("rejects a pod target that could break out of the Ruby literal", () => {
106
+ expect(() =>
107
+ injectPodfileSpmBlock(PODFILE, {
108
+ marker: "usesmileid_bridge",
109
+ package: PACKAGE,
110
+ podTarget: "Bridge'; system('rm -rf /') #",
111
+ products: ["UseSmileIDBridge"],
112
+ }),
113
+ ).toThrow(/unsafe character/);
114
+ });
115
+
116
+ // The diagnostics are double-quoted Ruby, where `#{...}` evaluates.
117
+ it.each([
118
+ ["interpolation", "Bridge#{`rm -rf /`}"],
119
+ ["double quote", 'Bridge"'],
120
+ ])("rejects a pod target carrying a Ruby %s", (_label, podTarget) => {
121
+ expect(() =>
122
+ injectPodfileSpmBlock(PODFILE, {
123
+ marker: "usesmileid_bridge",
124
+ package: PACKAGE,
125
+ podTarget,
126
+ products: ["UseSmileIDBridge"],
127
+ }),
128
+ ).toThrow(/unsafe character/);
129
+ });
130
+ });
131
+
132
+ function blockFor(podfile: string, marker: string): string {
133
+ const begin = podfile.indexOf(`# === BEGIN smileid-spm: ${marker} ===`);
134
+ const end = podfile.indexOf(`# === END smileid-spm: ${marker} ===`);
135
+ expect(begin).toBeGreaterThanOrEqual(0);
136
+ expect(end).toBeGreaterThan(begin);
137
+ return podfile.slice(begin, end);
138
+ }
@@ -16,13 +16,17 @@ import {
16
16
  ConfigPlugin,
17
17
  withDangerousMod,
18
18
  withXcodeProject,
19
- } from "@expo/config-plugins";
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.0",
38
+ version: "12.0.2",
35
39
  },
36
40
  };
37
41
 
@@ -39,7 +43,16 @@ const POD_TARGET = "UseSmileIDVisionDocumentExpo";
39
43
  const SPM_PRODUCTS = ["UseSmileIDVisionDocument", "UseSmileIDBridge"] as const;
40
44
  const PODFILE_MARKER = "usesmileid_vision_document";
41
45
 
46
+ // Nothing else links the bridge's own pod target, so without this block its
47
+ // `import UseSmileIDBridge` fails. Both vision plugins emit it; the injection is
48
+ // idempotent per marker, so installing both is harmless.
49
+ const BRIDGE_POD_TARGET = "UseSmileIDBridgeExpo";
50
+ const BRIDGE_SPM_PRODUCTS = ["UseSmileIDBridge"] as const;
51
+ const BRIDGE_PODFILE_MARKER = "usesmileid_bridge";
52
+
42
53
  const withVisionDocumentSpm: ConfigPlugin = (config) => {
54
+ config = withIosDeploymentTargetFloor(config, IOS_DEPLOYMENT_TARGET);
55
+
43
56
  // 1. Add the package + products to the host app's Xcode project so
44
57
  // Xcode resolves the binary xcframeworks and embeds them in the
45
58
  // .app at link time.
@@ -69,12 +82,19 @@ const withVisionDocumentSpm: ConfigPlugin = (config) => {
69
82
  "Podfile",
70
83
  );
71
84
  const original = fs.readFileSync(podfilePath, "utf-8");
72
- const next = injectPodfileSpmBlock(original, {
85
+ let next = injectPodfileSpmBlock(original, {
73
86
  marker: PODFILE_MARKER,
74
87
  package: SMILE_ID_PACKAGE,
75
88
  podTarget: POD_TARGET,
76
89
  products: [...SPM_PRODUCTS],
77
90
  });
91
+ next = injectPodfileSpmBlock(next, {
92
+ marker: BRIDGE_PODFILE_MARKER,
93
+ package: SMILE_ID_PACKAGE,
94
+ podTarget: BRIDGE_POD_TARGET,
95
+ products: [...BRIDGE_SPM_PRODUCTS],
96
+ required: true,
97
+ });
78
98
  if (next !== original) {
79
99
  fs.writeFileSync(podfilePath, next);
80
100
  }
@@ -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
- `${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`,
43
+ `([ \\t]*)${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`,
40
44
  "m",
41
45
  );
42
- if (blockRegex.test(podfile)) {
43
- return podfile.replace(blockRegex, block);
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
- // All injected strings flow into single-quoted Ruby literals. We
76
- // reject characters that could break out of those literals so a
77
- // typo in the bridge plugin can't produce code that runs during
78
- // `pod install`. Rejected: single-quote, backslash, control chars.
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 (/['\\\n\r\t]/.test(value)) {
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
- Pod::UI.warn "[smileid-spm] Pod target #{target_name} not found; skipping SPM link."
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
@@ -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 "@expo/config-plugins";
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`. */
@@ -15,5 +15,10 @@
15
15
  "composite": false
16
16
  },
17
17
  "include": ["src/**/*"],
18
- "exclude": ["build", "node_modules"]
18
+ "exclude": [
19
+ "src/**/*.test.ts",
20
+ "src/**/__tests__/**",
21
+ "build",
22
+ "node_modules"
23
+ ]
19
24
  }