@smileid/usesmileid_mlkit_face 12.0.2 → 12.1.0

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +7 -3
  3. package/android/build.gradle +21 -21
  4. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceKameraAnalysisTick.kt +42 -0
  5. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceModule.kt +6 -28
  6. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceSessionHolder.kt +2 -3
  7. package/android/src/main/java/com/usesmileid/rn/analyzer/mlkitface/UseSmileIDAnalyzerExecutor.kt +28 -0
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.js +1 -38
  10. package/package.json +12 -13
  11. package/plugin/build/index.js +1 -27
  12. package/plugin/build/lib/mlkit_manifest.js +1 -54
  13. package/plugin/build/lib/root_project.d.ts +11 -3
  14. package/plugin/build/lib/root_project.js +4 -109
  15. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceFrameProcessorPlugin.kt +0 -79
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/index.js.map +0 -1
  18. package/plugin/build/index.js.map +0 -1
  19. package/plugin/build/lib/mlkit_manifest.js.map +0 -1
  20. package/plugin/build/lib/root_project.js.map +0 -1
  21. package/plugin/src/__tests__/mirrored_sources.test.ts +0 -57
  22. package/plugin/src/__tests__/mlkit_manifest.test.ts +0 -117
  23. package/plugin/src/__tests__/root_project.test.ts +0 -195
  24. package/plugin/src/index.ts +0 -47
  25. package/plugin/src/lib/mlkit_manifest.ts +0 -88
  26. package/plugin/src/lib/root_project.ts +0 -160
  27. package/plugin/tsconfig.json +0 -24
  28. package/src/__tests__/use_smile_id_mlkit_face.test.ts +0 -101
  29. package/src/index.ts +0 -46
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ Full release notes: [Smile ID docs](https://docs.usesmileid.com/developer-resources/sdks/mobile/release-notes/react-native-expo).
2
+
3
+ ## 12.1.0
4
+
5
+ See the repository CHANGELOG for the 12.1.0 release notes.
6
+
7
+
1
8
  ## 12.0.2
2
9
 
3
10
  See the repository CHANGELOG for the 12.0.2 release notes.
package/README.md CHANGED
@@ -30,15 +30,15 @@ package to `expo.plugins` in `app.json` and run `npx expo prebuild --clean`:
30
30
  "plugins": [
31
31
  [
32
32
  "@smileid/usesmileid_mlkit_face",
33
- { "additionalModelDependencies": ["barcode_ui"] }
33
+ { "kspVersion": "2.3.6", "additionalModelDependencies": ["barcode_ui"] }
34
34
  ]
35
35
  ]
36
36
  }
37
37
  }
38
38
  ```
39
39
 
40
- The plugin pins `compileSdk`, the Kotlin/KSP versions the AAR was compiled with,
41
- and the `androidx.core` version that resolves under Expo's Android Gradle Plugin.
40
+ The plugin pins the Kotlin the AAR was compiled with (2.3.20) and the
41
+ `androidx.core` version that resolves under Expo's Android Gradle Plugin.
42
42
  It also claims the `com.google.mlkit.vision.DEPENDENCIES` meta-data on your
43
43
  `<application>` with `tools:replace`: this AAR declares that key as `face`, so
44
44
  without the override a second ML Kit consumer declaring its own value (for
@@ -47,6 +47,10 @@ Android manifest merger. `additionalModelDependencies` is optional — name the
47
47
  hints your other consumers declare so their models are prefetched at install
48
48
  time rather than downloaded on first use.
49
49
 
50
+ Expo's own KSP lookup carries no entry for that Kotlin and falls back to a KSP
51
+ the compiler cannot load, so pass `kspVersion` as shown — the samples use
52
+ `2.3.6`. `kotlinVersion` overrides the Kotlin pin.
53
+
50
54
  ## Usage
51
55
 
52
56
  ```ts
@@ -17,7 +17,7 @@ plugins {
17
17
  }
18
18
 
19
19
  group = 'com.usesmileid.mlkit.face'
20
- version = '12.0.2'
20
+ version = '12.1.0'
21
21
 
22
22
  android {
23
23
  namespace 'com.usesmileid.mlkit.face'
@@ -29,7 +29,7 @@ android {
29
29
  // android/build.gradle and the rest of the @expo/* modules.
30
30
  defaultConfig {
31
31
  versionCode 1
32
- versionName '12.0.2'
32
+ versionName '12.1.0'
33
33
  // R8 keep-rules to bundle into partner apps that depend on this
34
34
  // module. Without these, R8 may rename the Expo module entry
35
35
  // point and the JS-bridged member functions, breaking autolinking.
@@ -38,26 +38,26 @@ android {
38
38
  }
39
39
 
40
40
  dependencies {
41
+ // kamera native core: the analyzer executor takes analysis frames from
42
+ // kamera's KameraFrameHub so pixels never cross the JS bridge. Resolves from
43
+ // Maven Central; a local publishToMavenLocal build wins (see kamera.md).
44
+ implementation 'com.usesmileid:kamera:1.0.5'
45
+
41
46
  // Existing Smile ID native AAR — the entire ML pipeline lives here.
42
47
  // This Expo Module is a thin Kotlin shim that constructs the
43
48
  // session and bridges its four methods to JS via Expo Modules.
44
- implementation 'com.usesmileid:mlkit-face:12.0.2'
45
-
46
- // VisionCamera frame-processor plugin classes. The host app provides
47
- // this dependency via RN autolinking — react-native-vision-camera >= 4.7.3
48
- // must be present in the partner app's package.json.
49
- implementation project(':react-native-vision-camera')
50
-
51
- // CameraX types (`ImageProxy`, `ExperimentalGetImage`) referenced by the
52
- // frame processor plugin. VisionCamera declares camera-core as
53
- // `implementation`, so it is NOT on this module's compile classpath
54
- // transitively — compileOnly pins the compile-time API while the host
55
- // app's VisionCamera provides the runtime classes (its camera-core is
56
- // 1.5.0-alpha03; the `ImageProxy` surface used here is stable since 1.0).
57
- compileOnly 'androidx.camera:camera-core:1.5.0-alpha03'
58
-
59
- // `FaceDetectorAnalyzer()` takes a defaulted `FaceDetector` argument, so the compiler
60
- // needs that type to emit the call. The AAR above declares ML Kit at runtime
61
- // scope only — enough to execute, not to compile against.
62
- compileOnly 'com.google.android.gms:play-services-mlkit-face-detection:17.1.0'
49
+ implementation 'com.usesmileid:mlkit-face:12.1.0'
50
+
51
+
52
+ }
53
+
54
+ // kamera resolves from Maven Central. mavenLocal stays injected — content-filtered to
55
+ // com.usesmileid — the same way kamera's own glue does it, so a locally published kamera
56
+ // build takes precedence during the kamera dev loop without host apps editing repos.
57
+ rootProject.allprojects {
58
+ repositories {
59
+ mavenLocal {
60
+ content { includeGroup 'com.usesmileid' }
61
+ }
62
+ }
63
63
  }
@@ -0,0 +1,42 @@
1
+ package com.usesmileid.mlkit.face
2
+
3
+ import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
4
+ import com.usesmileid.kamera.integration.KameraFrameHub
5
+ import com.usesmileid.rn.analyzer.mlkitface.toNativeYuvFrame
6
+ import kotlin.coroutines.cancellation.CancellationException
7
+ import kotlinx.coroutines.runBlocking
8
+ import kotlinx.coroutines.withContext
9
+
10
+ /**
11
+ * Scans the latest [KameraFrameHub] frame natively; no pending frame returns the `no_frame` wire error.
12
+ * The frame's rotationDegrees already uprights the buffer to the locked interface axis.
13
+ */
14
+ internal fun analyzeKameraFrameTick(arguments: Map<String, Any?>?): Any {
15
+ val frame = KameraFrameHub.take() ?: return mapOf("error" to "no_frame")
16
+ val yuvFrame = try {
17
+ frame.toNativeYuvFrame()
18
+ } finally {
19
+ // Closing releases the frame to the pipeline; the converted copy owns its own bytes.
20
+ frame.close()
21
+ }
22
+
23
+ // Device-tilt context (0 = upright portrait) for the validators.
24
+ val jsRotation = (arguments?.get("rotation") as? Number)?.toInt() ?: 0
25
+
26
+ return runBlocking {
27
+ withContext(UseSmileIDMlkitFaceSessionHolder.sessionDispatcher) {
28
+ val activeSession = UseSmileIDMlkitFaceSessionHolder.session
29
+ ?: return@withContext mapOf("error" to "session_not_configured")
30
+
31
+ try {
32
+ val scanResult = activeSession.scan(yuvFrame, jsRotation)
33
+ val events = activeSession.drainCaptureReadyEvents()
34
+ UseSmileIDMlkitFaceModule.buildWireResult(scanResult, events)
35
+ } catch (cancellation: CancellationException) {
36
+ throw cancellation
37
+ } catch (e: Exception) {
38
+ mapOf("error" to ("scan_failed: " + (e.message ?: e.javaClass.simpleName)))
39
+ }
40
+ }
41
+ }
42
+ }
@@ -14,7 +14,6 @@ import com.usesmileid.bridge.session.UseSmileIDFaceSession
14
14
  import com.usesmileid.bridge.session.UseSmileIDScanState
15
15
  import com.usesmileid.bridge.session.UseSmileIDScanStateType
16
16
  import com.usesmileid.bridge.storage.FrameStorage
17
- import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
18
17
  import com.usesmileid.rn.analyzer.mlkitface.UseSmileIDAnalyzerExecutor
19
18
  import expo.modules.kotlin.Promise
20
19
  import expo.modules.kotlin.modules.Module
@@ -31,10 +30,7 @@ import expo.modules.kotlin.modules.ModuleDefinition
31
30
  * subclass hooks (session construction, wire conversion) and forwards
32
31
  * the async functions through it.
33
32
  *
34
- * Per-frame work is handled by [UseSmileIDMlkitFaceFrameProcessorPlugin],
35
- * which is registered with VisionCamera's frame-processor runtime in
36
- * [OnCreate]. Partners call `VisionCameraProxy.initFrameProcessorPlugin`
37
- * from their JS worklet instead of the old `AsyncFunction("analyze")`.
33
+ * Frames reach `analyzeKameraFrame` through kamera's native hub; pixels never cross the JS bridge.
38
34
  *
39
35
  * Lifecycle: Expo instantiates exactly one `Module` per app process;
40
36
  * the executor is owned for the module's lifetime.
@@ -118,20 +114,17 @@ class UseSmileIDMlkitFaceModule : Module() {
118
114
  ?: throw IllegalStateException("Module not attached to a React context")
119
115
  executor.onAttach(ctx)
120
116
 
121
- // Guard against duplicate registration on dev-client hot reloads
122
- // where OnCreate can fire more than once in the same process.
123
- if (!pluginRegistered) {
124
- pluginRegistered = true
125
- FrameProcessorPluginRegistry.addFrameProcessorPlugin(
126
- FRAME_PROCESSOR_PLUGIN_NAME,
127
- ) { _, _ -> UseSmileIDMlkitFaceFrameProcessorPlugin() }
128
- }
129
117
  }
130
118
 
131
119
  AsyncFunction("configure") { config: Map<String, Any?> ->
132
120
  executor.handleConfigure(config)
133
121
  }
134
122
 
123
+ AsyncFunction("analyzeKameraFrame") { arguments: Map<String, Any?>? ->
124
+ // Blocks this module's async queue so only one scan is in flight at a time.
125
+ analyzeKameraFrameTick(arguments)
126
+ }
127
+
135
128
  AsyncFunction("reset") {
136
129
  executor.handleReset()
137
130
  }
@@ -158,21 +151,6 @@ class UseSmileIDMlkitFaceModule : Module() {
158
151
  /** Wire-format key partners pass via `FaceConfig.analyzerFactoryKey`. */
159
152
  const val KEY: String = "mlkit-face"
160
153
 
161
- /**
162
- * Must match the JS worklet's `VisionCameraProxy.initFrameProcessorPlugin(...)`
163
- * call exactly — drift is a runtime "plugin not found" error.
164
- */
165
- const val FRAME_PROCESSOR_PLUGIN_NAME: String = "UseSmileIDMlkitFaceFrameProcessor"
166
-
167
- /**
168
- * [FrameProcessorPluginRegistry.addFrameProcessorPlugin] asserts uniqueness
169
- * and crashes on duplicate registration; this guard prevents that on dev-client
170
- * hot reloads where [OnCreate] fires more than once per process.
171
- */
172
- @Volatile
173
- private var pluginRegistered: Boolean = false
174
-
175
- /** Wire-result builder shared with [UseSmileIDMlkitFaceFrameProcessorPlugin]. */
176
154
  internal fun buildWireResult(
177
155
  scanResult: UseSmileIDFaceScanResult,
178
156
  events: List<NativeCaptureReadyEvent>,
@@ -10,9 +10,8 @@ import kotlinx.coroutines.asCoroutineDispatcher
10
10
  *
11
11
  * The session's state machine is not thread-safe; all operations —
12
12
  * scan+drain as one atomic unit, flush, reset, close — are serialized
13
- * through [sessionDispatcher] (a single-thread executor). The FP plugin
14
- * blocks its thread via [runBlocking]; VisionCamera drops frames that
15
- * arrive while a scan is in flight rather than queuing them.
13
+ * through [sessionDispatcher] (a single-thread executor). The analysis tick blocks its
14
+ * caller via [runBlocking]; kamera's hub keeps only the latest frame, so frames never queue.
16
15
  */
17
16
  internal object UseSmileIDMlkitFaceSessionHolder {
18
17
 
@@ -12,6 +12,8 @@ import com.usesmileid.bridge.model.CaptureType
12
12
  import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
13
13
  import com.usesmileid.bridge.storage.DocumentSide
14
14
  import com.usesmileid.bridge.storage.FrameStorage
15
+ import com.usesmileid.kamera.frame.KameraFrame
16
+ import com.usesmileid.kamera.integration.KameraFrameHub
15
17
  import java.io.File
16
18
  import kotlinx.coroutines.CoroutineDispatcher
17
19
  import kotlinx.coroutines.CoroutineScope
@@ -171,6 +173,7 @@ abstract class UseSmileIDAnalyzerExecutor<
171
173
 
172
174
  // Shared implementation behind the Expo Module's async functions -----
173
175
 
176
+
174
177
  /**
175
178
  * Implementation behind `configure`. Closes any prior session,
176
179
  * allocates a fresh on-disk session directory, and delegates session
@@ -281,3 +284,28 @@ abstract class UseSmileIDAnalyzerExecutor<
281
284
  return dir
282
285
  }
283
286
  }
287
+
288
+ /** Copies the YUV planes so the frame can close at once; rotation stays metadata for the detector. */
289
+ internal fun KameraFrame.toNativeYuvFrame(): NativeYuvFrame {
290
+ val yPlane = planes[0]
291
+ val uPlane = planes[1]
292
+ val vPlane = planes[2]
293
+ fun copyBytes(buffer: java.nio.ByteBuffer): ByteArray {
294
+ val source = buffer.duplicate()
295
+ source.rewind()
296
+ return ByteArray(source.remaining()).also { source.get(it) }
297
+ }
298
+ return NativeYuvFrame(
299
+ planeY = copyBytes(yPlane.buffer),
300
+ planeU = copyBytes(uPlane.buffer),
301
+ planeV = copyBytes(vPlane.buffer),
302
+ yRowStride = yPlane.rowStride,
303
+ uRowStride = uPlane.rowStride,
304
+ vRowStride = vPlane.rowStride,
305
+ uPixelStride = uPlane.pixelStride,
306
+ vPixelStride = vPlane.pixelStride,
307
+ width = width,
308
+ height = height,
309
+ rotation = rotationDegrees,
310
+ )
311
+ }
package/dist/index.d.ts CHANGED
@@ -20,9 +20,10 @@ import type { FaceConfig, FaceScanResult } from "@smileid/usesmileid_bridge";
20
20
  * `FaceAnalyzer` for the canonical contract.
21
21
  */
22
22
  /**
23
- * Must match the Kotlin Expo Module's `Name("UseSmileIDMlkitFace")` and its
24
- * VisionCamera frame-processor-plugin registration — drift is a runtime
25
- * "module/plugin not found" error, not a compile error.
23
+ * Must match the Kotlin Expo Module's `Name("UseSmileIDMlkitFace")` — drift is a runtime
24
+ * "module not found" error, not a compile error.
25
+ *
26
+ * `frameProcessorPluginName` is inert; kept only for API compatibility.
26
27
  */
27
28
  export declare const UseSmileIDMlkitFaceIdentifiers: {
28
29
  readonly nativeModuleName: "UseSmileIDMlkitFace";
@@ -30,4 +31,3 @@ export declare const UseSmileIDMlkitFaceIdentifiers: {
30
31
  };
31
32
  export declare const useSmileIDMlkitFace: import("@smileid/usesmileid_platform_interface").FaceAnalyzer;
32
33
  export type { FaceConfig, FaceScanResult };
33
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,38 +1 @@
1
- import { AnalyzerFactoryKey } from "@smileid/usesmileid_bridge";
2
- import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
3
- /**
4
- * Google ML Kit face analyzer (Android-only).
5
- *
6
- * Thin shim around the existing native session
7
- * `com.usesmileid.bridge.session.UseSmileIDFaceSession(FaceDetectorAnalyzer())`
8
- * shipped in the `com.smileidentity:mlkit-face` AAR. The native side
9
- * runs the entire pipeline (analyzer + validator + transitioner +
10
- * state machine); this TS layer only routes calls and validates wire
11
- * payloads at the channel boundary.
12
- *
13
- * The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
14
- * `usesmileid_platform_interface`. This file only supplies the
15
- * provider-specific identity so all six provider modules share one set
16
- * of correctness properties by construction.
17
- *
18
- * Lifecycle: `configure()` → per-frame analysis via the native frame
19
- * processor plugin → `reset()`? → `close()`. Calling
20
- * `close()` twice is idempotent. See `usesmileid_platform_interface`
21
- * `FaceAnalyzer` for the canonical contract.
22
- */
23
- /**
24
- * Must match the Kotlin Expo Module's `Name("UseSmileIDMlkitFace")` and its
25
- * VisionCamera frame-processor-plugin registration — drift is a runtime
26
- * "module/plugin not found" error, not a compile error.
27
- */
28
- export const UseSmileIDMlkitFaceIdentifiers = {
29
- nativeModuleName: "UseSmileIDMlkitFace",
30
- frameProcessorPluginName: "UseSmileIDMlkitFaceFrameProcessor",
31
- };
32
- export const useSmileIDMlkitFace = useSmileIDAnalyzerExecutor({
33
- nativeModuleName: UseSmileIDMlkitFaceIdentifiers.nativeModuleName,
34
- frameProcessorPluginName: UseSmileIDMlkitFaceIdentifiers.frameProcessorPluginName,
35
- kind: "face",
36
- key: AnalyzerFactoryKey.MLKitFace,
37
- });
38
- //# sourceMappingURL=index.js.map
1
+ "use strict";import{AnalyzerFactoryKey as e}from"@smileid/usesmileid_bridge";import{useSmileIDAnalyzerExecutor as r}from"@smileid/usesmileid_platform_interface";export const UseSmileIDMlkitFaceIdentifiers={nativeModuleName:"UseSmileIDMlkitFace",frameProcessorPluginName:"UseSmileIDMlkitFaceFrameProcessor"},useSmileIDMlkitFace=r({nativeModuleName:UseSmileIDMlkitFaceIdentifiers.nativeModuleName,frameProcessorPluginName:UseSmileIDMlkitFaceIdentifiers.frameProcessorPluginName,kind:"face",key:e.MLKitFace});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/usesmileid_mlkit_face",
3
- "version": "12.0.2",
3
+ "version": "12.1.0",
4
4
  "description": "Android MLKit face detection implementation for the UseSmileID React Native (Expo) SDK.",
5
5
  "homepage": "https://usesmileid.com",
6
6
  "repository": {
@@ -36,11 +36,10 @@
36
36
  },
37
37
  "files": [
38
38
  "dist",
39
- "src",
40
- "android",
39
+ "android/build.gradle",
40
+ "android/consumer-rules.pro",
41
+ "android/src/main",
41
42
  "plugin/build",
42
- "plugin/src",
43
- "plugin/tsconfig.json",
44
43
  "app.plugin.js",
45
44
  "expo-module.config.json",
46
45
  "README.md",
@@ -48,8 +47,8 @@
48
47
  "CHANGELOG.md"
49
48
  ],
50
49
  "dependencies": {
51
- "@smileid/usesmileid_bridge": "12.0.2",
52
- "@smileid/usesmileid_platform_interface": "12.0.2"
50
+ "@smileid/usesmileid_platform_interface": "12.1.0",
51
+ "@smileid/usesmileid_bridge": "12.1.0"
53
52
  },
54
53
  "peerDependencies": {
55
54
  "expo": ">=56.0.0",
@@ -57,22 +56,22 @@
57
56
  "react-native": ">=0.83.0"
58
57
  },
59
58
  "devDependencies": {
60
- "@types/node": "^25.9.1",
61
- "expo": "^56.0.9",
62
- "expo-modules-core": "^56.0.15",
63
- "react-native": "^0.85.3",
59
+ "@types/node": "^26.4.1",
60
+ "expo": "~57.0.20",
61
+ "expo-modules-core": "^57.0.16",
62
+ "react-native": "^0.86.3",
64
63
  "typescript": "^6.0.3"
65
64
  },
66
65
  "publishConfig": {
67
66
  "access": "public"
68
67
  },
69
68
  "scripts": {
70
- "build": "pnpm run build:lib && pnpm run build:plugin",
69
+ "build": "pnpm run build:lib && pnpm run build:plugin && node ../Scripts/copy-assets.mjs",
71
70
  "build:lib": "tsc -p tsconfig.json",
72
71
  "build:plugin": "tsc -p plugin/tsconfig.json",
73
72
  "typecheck": "tsc -b && tsc -p plugin/tsconfig.json --noEmit",
74
73
  "test": "vitest run",
75
74
  "test:watch": "vitest",
76
- "clean": "rm -rf dist plugin/build .turbo coverage"
75
+ "clean": "rm -rf dist plugin/build .turbo coverage tsconfig.tsbuildinfo"
77
76
  }
78
77
  }
@@ -1,27 +1 @@
1
- "use strict";
2
- // The module is autolinked as a Gradle subproject, so it inherits the host's
3
- // Kotlin compiler and `compileSdk`; Expo's defaults for both are lower than the
4
- // AARs accept and a library cannot raise a setting the root project owns.
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const root_project_1 = require("./lib/root_project");
7
- const mlkit_manifest_1 = require("./lib/mlkit_manifest");
8
- // Lowest Kotlin that reads the AARs' 2.4.x metadata AND has Expo
9
- // compiler-plugin (pika) artifacts; higher pins break expo-modules-core.
10
- // KSP is left to Expo's own kotlin→ksp lookup table.
11
- const DEFAULT_KOTLIN_VERSION = "2.3.20";
12
- const COMPILE_SDK_VERSION = 37;
13
- const FORCED_DEPENDENCIES = [
14
- "androidx.core:core:1.18.0",
15
- "androidx.core:core-ktx:1.18.0",
16
- ];
17
- const withMlkitFace = (config, options) => {
18
- const { kotlinVersion, kspVersion, ...manifestOptions } = options ?? {};
19
- return (0, mlkit_manifest_1.withMlkitManifestMerge)((0, root_project_1.withAndroidRootProjectPins)(config, {
20
- kotlinVersion: kotlinVersion ?? DEFAULT_KOTLIN_VERSION,
21
- ...(kspVersion !== undefined ? { kspVersion } : {}),
22
- compileSdkVersion: COMPILE_SDK_VERSION,
23
- forcedDependencies: FORCED_DEPENDENCIES,
24
- }), manifestOptions);
25
- };
26
- exports.default = withMlkitFace;
27
- //# sourceMappingURL=index.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const root_project_1=require("./lib/root_project"),mlkit_manifest_1=require("./lib/mlkit_manifest"),DEFAULT_KOTLIN_VERSION="2.3.20",FORCED_DEPENDENCIES=["androidx.core:core:1.18.0","androidx.core:core-ktx:1.18.0"],withMlkitFace=(t,o)=>{const{kotlinVersion:i,kspVersion:e,...r}=o??{};return(0,mlkit_manifest_1.withMlkitManifestMerge)((0,root_project_1.withAndroidRootProjectPins)(t,{kotlinVersion:i??DEFAULT_KOTLIN_VERSION,...e!==void 0?{kspVersion:e}:{},forcedDependencies:FORCED_DEPENDENCIES}),r)};exports.default=withMlkitFace;
@@ -1,54 +1 @@
1
- "use strict";
2
- // The AAR declares this meta-data without `tools:replace`, so only the host `<application>` can claim it for every ML Kit consumer at once.
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.withMlkitManifestMerge = void 0;
5
- exports.mergedModelDependencies = mergedModelDependencies;
6
- exports.applyMlkitManifestMerge = applyMlkitManifestMerge;
7
- const config_plugins_1 = require("expo/config-plugins");
8
- const META_DATA_NAME = "com.google.mlkit.vision.DEPENDENCIES";
9
- const TOOLS_NAMESPACE = "http://schemas.android.com/tools";
10
- /** The hint this package's AAR declares; always part of the merged value. */
11
- const PROVIDER_MODEL_DEPENDENCY = "face";
12
- // ML Kit's own hint vocabulary — lowercase words and underscores.
13
- const MODEL_DEPENDENCY = /^[a-z0-9_]+$/;
14
- /** Provider hint first, then the caller's, deduplicated and order-stable. */
15
- function mergedModelDependencies(additional) {
16
- for (const dependency of additional) {
17
- if (!MODEL_DEPENDENCY.test(dependency)) {
18
- throw new Error(`[smileid-android] '${dependency}' is not a valid ML Kit model dependency. ` +
19
- "Expected a lowercase hint such as 'barcode_ui', as printed in the manifest-merger error.");
20
- }
21
- }
22
- return [...new Set([PROVIDER_MODEL_DEPENDENCY, ...additional])].join(",");
23
- }
24
- /** Declares the merged `com.google.mlkit.vision.DEPENDENCIES` on the host `<application>` with `tools:replace`. */
25
- const withMlkitManifestMerge = (config, options) => (0, config_plugins_1.withAndroidManifest)(config, (config) => {
26
- config.modResults = applyMlkitManifestMerge(config.modResults, mergedModelDependencies(options?.additionalModelDependencies ?? []));
27
- return config;
28
- });
29
- exports.withMlkitManifestMerge = withMlkitManifestMerge;
30
- /** Pure form of the edit. Idempotent; a re-run replaces a stale value. */
31
- function applyMlkitManifestMerge(manifest, value) {
32
- const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
33
- application["meta-data"] ??= [];
34
- const entries = application["meta-data"];
35
- const existing = entries.find((entry) => entry?.$?.["android:name"] === META_DATA_NAME);
36
- if (existing) {
37
- existing.$["android:value"] = value;
38
- existing.$["tools:replace"] = "android:value";
39
- }
40
- else {
41
- entries.push({
42
- $: {
43
- "android:name": META_DATA_NAME,
44
- "android:value": value,
45
- "tools:replace": "android:value",
46
- },
47
- });
48
- }
49
- // Without the namespace on <manifest>, `tools:replace` merges as an unknown attribute.
50
- manifest.manifest.$ ??= {};
51
- manifest.manifest.$["xmlns:tools"] ??= TOOLS_NAMESPACE;
52
- return manifest;
53
- }
54
- //# sourceMappingURL=mlkit_manifest.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.withMlkitManifestMerge=void 0,exports.mergedModelDependencies=mergedModelDependencies,exports.applyMlkitManifestMerge=applyMlkitManifestMerge;const config_plugins_1=require("expo/config-plugins"),META_DATA_NAME="com.google.mlkit.vision.DEPENDENCIES",TOOLS_NAMESPACE="http://schemas.android.com/tools",PROVIDER_MODEL_DEPENDENCY="face",MODEL_DEPENDENCY=/^[a-z0-9_]+$/;function mergedModelDependencies(e){for(const t of e)if(!MODEL_DEPENDENCY.test(t))throw new Error(`[smileid-android] '${t}' is not a valid ML Kit model dependency. Expected a lowercase hint such as 'barcode_ui', as printed in the manifest-merger error.`);return[...new Set([PROVIDER_MODEL_DEPENDENCY,...e])].join(",")}const withMlkitManifestMerge=(e,t)=>(0,config_plugins_1.withAndroidManifest)(e,n=>(n.modResults=applyMlkitManifestMerge(n.modResults,mergedModelDependencies(t?.additionalModelDependencies??[])),n));exports.withMlkitManifestMerge=withMlkitManifestMerge;function applyMlkitManifestMerge(e,t){const n=config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(e);n["meta-data"]??=[];const i=n["meta-data"],o=i.find(s=>s?.$?.["android:name"]===META_DATA_NAME);return o?(o.$["android:value"]=t,o.$["tools:replace"]="android:value"):i.push({$:{"android:name":META_DATA_NAME,"android:value":t,"tools:replace":"android:value"}}),e.manifest.$??={},e.manifest.$["xmlns:tools"]??=TOOLS_NAMESPACE,e}
@@ -1,4 +1,5 @@
1
- import { type ConfigPlugin } from "expo/config-plugins";
1
+ import { type AndroidConfig, type ConfigPlugin } from "expo/config-plugins";
2
+ type PropertiesItem = AndroidConfig.Properties.PropertiesItem;
2
3
  export interface AndroidRootProjectPins {
3
4
  /**
4
5
  * Opt-in: only for hosts whose Kotlin predates the AARs' compiler. Current
@@ -8,12 +9,19 @@ export interface AndroidRootProjectPins {
8
9
  kotlinVersion?: string;
9
10
  /** Only alongside `kotlinVersion` — Expo's own lookup table may carry no entry for it. */
10
11
  kspVersion?: string;
11
- /** The AARs declare it as a consumer minimum. Independent of `minSdk`, so device support is unchanged. */
12
- compileSdkVersion: number;
12
+ /** Opt-in: only for a host that must compile above React Native's default. The AARs declare their own consumer floor. */
13
+ compileSdkVersion?: number;
13
14
  /** `group:name:version` coordinates forced everywhere, for deps whose newest release the host toolchain cannot consume. */
14
15
  forcedDependencies: readonly string[];
15
16
  }
17
+ /**
18
+ * AGP matches this against the resolved platform, which may or may not carry a minor
19
+ * (`android-37` vs `android-37.0`), so both forms are listed.
20
+ */
21
+ export declare function suppressUnsupportedCompileSdkValue(compileSdkVersion: number): string;
22
+ export declare function suppressAdvisoryProperties(items: PropertiesItem[], compileSdkVersion: number | undefined): PropertiesItem[];
16
23
  /** Declares the pins ahead of `apply plugin: "expo-root-project"`. */
17
24
  export declare const withAndroidRootProjectPins: ConfigPlugin<AndroidRootProjectPins>;
18
25
  /** Pure form of the edit. Idempotent; a re-run replaces stale values rather than appending. */
19
26
  export declare function applyAndroidRootProjectPins(contents: string, pins: AndroidRootProjectPins): string;
27
+ export {};
@@ -1,109 +1,4 @@
1
- "use strict";
2
- // `expo-root-project` resolves these through `extra.setIfNotExist`, so they only
3
- // take effect when declared before that plugin applies.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.withAndroidRootProjectPins = void 0;
6
- exports.applyAndroidRootProjectPins = applyAndroidRootProjectPins;
7
- const config_plugins_1 = require("expo/config-plugins");
8
- const BEGIN_MARKER = "// === BEGIN smileid root-project pins ===";
9
- const END_MARKER = "// === END smileid root-project pins ===";
10
- const APPLY_ROOT_PROJECT = /^apply\s+plugin:\s*["']expo-root-project["']/m;
11
- // Bumping `ext.kotlinVersion` moves the dependency catalog but not the compiler, so
12
- // the classpath line needs pinning too. Version and parentheses are optional so an
13
- // already-pinned line and Groovy's space-call form still match.
14
- const KGP_CLASSPATH = /classpath\s*(?:\(\s*)?['"]org\.jetbrains\.kotlin:kotlin-gradle-plugin(?::[^'"]*)?['"](?:\s*\))?/;
15
- /** Declares the pins ahead of `apply plugin: "expo-root-project"`. */
16
- const withAndroidRootProjectPins = (config, pins) => (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
17
- if (config.modResults.language !== "groovy") {
18
- throw new Error("[smileid-android] Expected android/build.gradle to be Groovy DSL; refusing to edit " +
19
- `a ${config.modResults.language} build script.`);
20
- }
21
- config.modResults.contents = applyAndroidRootProjectPins(config.modResults.contents, pins);
22
- return config;
23
- });
24
- exports.withAndroidRootProjectPins = withAndroidRootProjectPins;
25
- /** Pure form of the edit. Idempotent; a re-run replaces stale values rather than appending. */
26
- function applyAndroidRootProjectPins(contents, pins) {
27
- if (pins.kspVersion !== undefined && pins.kotlinVersion === undefined) {
28
- throw new Error("[smileid-android] `kspVersion` only makes sense alongside `kotlinVersion`.");
29
- }
30
- validateGroovySafe(pins);
31
- const pinned = pins.kotlinVersion === undefined
32
- ? contents
33
- : pinKotlinGradlePlugin(contents, pins.kotlinVersion);
34
- return upsertPinBlock(pinned, renderPinBlock(pins));
35
- }
36
- // Every value lands in a single-quoted Groovy literal; reject anything that could close one.
37
- function validateGroovySafe(pins) {
38
- const values = [
39
- pins.kotlinVersion,
40
- pins.kspVersion,
41
- ...pins.forcedDependencies,
42
- ].filter((value) => value !== undefined);
43
- for (const value of values) {
44
- if (/['\\\n\r\t$]/.test(value)) {
45
- throw new Error(`[smileid-android] Refusing to edit android/build.gradle: '${value}' contains an unsafe character.`);
46
- }
47
- }
48
- }
49
- function pinKotlinGradlePlugin(contents, kotlinVersion) {
50
- const pinned = `classpath('org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}')`;
51
- if (!KGP_CLASSPATH.test(contents)) {
52
- throw new Error("[smileid-android] Could not locate the kotlin-gradle-plugin classpath line in " +
53
- "android/build.gradle. Refusing to silently no-op — the Smile ID analyzer modules do not " +
54
- `compile unless the Kotlin compiler is pinned to ${kotlinVersion}.`);
55
- }
56
- return contents.replace(KGP_CLASSPATH, pinned);
57
- }
58
- // Adding a further root-project pin means adding an entry here.
59
- function renderPinBlock(pins) {
60
- const extProperties = [];
61
- if (pins.kotlinVersion !== undefined) {
62
- extProperties.push(["kotlinVersion", `'${pins.kotlinVersion}'`]);
63
- }
64
- if (pins.kspVersion !== undefined) {
65
- extProperties.push(["kspVersion", `'${pins.kspVersion}'`]);
66
- }
67
- extProperties.push(["compileSdkVersion", String(pins.compileSdkVersion)]);
68
- const lines = extProperties.map(([name, value]) => `ext.${name} = ${value}`);
69
- return [
70
- BEGIN_MARKER,
71
- ...lines,
72
- ...renderForcedDependencies(pins.forcedDependencies),
73
- END_MARKER,
74
- ].join("\n");
75
- }
76
- // Its own `allprojects` block, which Gradle merges — editing the template's would
77
- // break the moment its shape changes.
78
- function renderForcedDependencies(coordinates) {
79
- if (coordinates.length === 0) {
80
- return [];
81
- }
82
- return [
83
- "",
84
- "// Forced to the newest release that resolves under the AGP Expo generates.",
85
- "allprojects {",
86
- " configurations.all {",
87
- " resolutionStrategy {",
88
- ...coordinates.map((coordinate) => ` force '${coordinate}'`),
89
- " }",
90
- " }",
91
- "}",
92
- ];
93
- }
94
- function upsertPinBlock(contents, block) {
95
- const existing = new RegExp(`${escapeRegExp(BEGIN_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`, "m");
96
- if (existing.test(contents)) {
97
- return contents.replace(existing, block);
98
- }
99
- if (!APPLY_ROOT_PROJECT.test(contents)) {
100
- throw new Error('[smileid-android] Could not locate `apply plugin: "expo-root-project"` in ' +
101
- "android/build.gradle. Refusing to silently no-op — these pins only take effect when " +
102
- "declared before that plugin applies.");
103
- }
104
- return contents.replace(APPLY_ROOT_PROJECT, (match) => `${block}\n\n${match}`);
105
- }
106
- function escapeRegExp(value) {
107
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
- }
109
- //# sourceMappingURL=root_project.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.withAndroidRootProjectPins=void 0,exports.suppressUnsupportedCompileSdkValue=suppressUnsupportedCompileSdkValue,exports.suppressAdvisoryProperties=suppressAdvisoryProperties,exports.applyAndroidRootProjectPins=applyAndroidRootProjectPins;const config_plugins_1=require("expo/config-plugins"),SUPPRESS_KEY="android.suppressUnsupportedCompileSdk",BEGIN_MARKER="// === BEGIN smileid root-project pins ===",END_MARKER="// === END smileid root-project pins ===",APPLY_ROOT_PROJECT=/^apply\s+plugin:\s*["']expo-root-project["']/m,KGP_CLASSPATH=/classpath\s*(?:\(\s*)?['"]org\.jetbrains\.kotlin:kotlin-gradle-plugin(?::[^'"]*)?['"](?:\s*\))?/;function suppressUnsupportedCompileSdkValue(e){return`${e},${e}.0`}function suppressAdvisoryProperties(e,o){if(o===void 0)return e;const r={type:"property",key:SUPPRESS_KEY,value:suppressUnsupportedCompileSdkValue(o)},n=e.findIndex(t=>t.type==="property"&&t.key===SUPPRESS_KEY);return n===-1?[...e,r]:e.map((t,i)=>i===n?r:t)}const withSuppressedCompileSdkAdvisory=(e,o)=>(0,config_plugins_1.withGradleProperties)(e,r=>(r.modResults=suppressAdvisoryProperties(r.modResults,o),r)),withAndroidRootProjectPins=(e,o)=>withSuppressedCompileSdkAdvisory((0,config_plugins_1.withProjectBuildGradle)(e,r=>{if(r.modResults.language!=="groovy")throw new Error(`[smileid-android] Expected android/build.gradle to be Groovy DSL; refusing to edit a ${r.modResults.language} build script.`);return r.modResults.contents=applyAndroidRootProjectPins(r.modResults.contents,o),r}),o.compileSdkVersion);exports.withAndroidRootProjectPins=withAndroidRootProjectPins;function applyAndroidRootProjectPins(e,o){if(o.kspVersion!==void 0&&o.kotlinVersion===void 0)throw new Error("[smileid-android] `kspVersion` only makes sense alongside `kotlinVersion`.");validateGroovySafe(o);const r=o.kotlinVersion===void 0?e:pinKotlinGradlePlugin(e,o.kotlinVersion);return upsertPinBlock(r,renderPinBlock(o))}function validateGroovySafe(e){const o=[e.kotlinVersion,e.kspVersion,...e.forcedDependencies].filter(r=>r!==void 0);for(const r of o)if(/['\\\n\r\t$]/.test(r))throw new Error(`[smileid-android] Refusing to edit android/build.gradle: '${r}' contains an unsafe character.`)}function pinKotlinGradlePlugin(e,o){const r=`classpath('org.jetbrains.kotlin:kotlin-gradle-plugin:${o}')`;if(!KGP_CLASSPATH.test(e))throw new Error(`[smileid-android] Could not locate the kotlin-gradle-plugin classpath line in android/build.gradle. Refusing to silently no-op \u2014 the Smile ID analyzer modules do not compile unless the Kotlin compiler is pinned to ${o}.`);return e.replace(KGP_CLASSPATH,r)}function renderPinBlock(e){const o=[];e.kotlinVersion!==void 0&&o.push(["kotlinVersion",`'${e.kotlinVersion}'`]),e.kspVersion!==void 0&&o.push(["kspVersion",`'${e.kspVersion}'`]),e.compileSdkVersion!==void 0&&o.push(["compileSdkVersion",String(e.compileSdkVersion)]);const r=o.map(([n,t])=>`ext.${n} = ${t}`);return[BEGIN_MARKER,...r,...renderForcedDependencies(e.forcedDependencies),END_MARKER].join(`
2
+ `)}function renderForcedDependencies(e){return e.length===0?[]:["","// Forced to the newest release that resolves under the AGP Expo generates.","allprojects {"," configurations.all {"," resolutionStrategy {",...e.map(o=>` force '${o}'`)," }"," }","}"]}function upsertPinBlock(e,o){const r=new RegExp(`${escapeRegExp(BEGIN_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`,"m");if(r.test(e))return e.replace(r,o);if(!APPLY_ROOT_PROJECT.test(e))throw new Error('[smileid-android] Could not locate `apply plugin: "expo-root-project"` in android/build.gradle. Refusing to silently no-op \u2014 these pins only take effect when declared before that plugin applies.');return e.replace(APPLY_ROOT_PROJECT,n=>`${o}
3
+
4
+ ${n}`)}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}