@independo/capacitor-inderun 0.2.0-dev.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.
- package/LICENSE +21 -0
- package/Package.swift +48 -0
- package/README.md +115 -0
- package/android/build.gradle.kts +38 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +3 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +82 -0
- package/android/settings.gradle +21 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunCapacitorPlugin.kt +111 -0
- package/android/src/main/kotlin/app/independo/inderun/capacitor/IndeRunSerializer.kt +263 -0
- package/android/src/test/kotlin/app/independo/inderun/capacitor/IndeRunSerializerTest.kt +180 -0
- package/dist/definitions.d.ts +17 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/web.d.ts +10 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +52 -0
- package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorBridge.swift +105 -0
- package/ios/Sources/IndeRunCapacitorPlugin/IndeRunCapacitorPlugin.swift +51 -0
- package/ios/Tests/IndeRunCapacitorTests/IndeRunCapacitorBridgeTests.swift +123 -0
- package/package.json +77 -0
package/dist/web.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { WebPlugin } from "@capacitor/core";
|
|
2
|
+
import { createIndeRunWeb, createUnavailable, toIndeRunException } from "@independo/inderun-web";
|
|
3
|
+
export class IndeRunWeb extends WebPlugin {
|
|
4
|
+
engine = null;
|
|
5
|
+
async configure(options) {
|
|
6
|
+
if (!options?.openAI) {
|
|
7
|
+
throw createUnavailable("Capacitor web execution requires OpenAI provider registration. Configure with openAI bootstrap options before calling run(request).").toContractError();
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const webOptions = {
|
|
11
|
+
openAI: compactOpenAIOptions(options)
|
|
12
|
+
};
|
|
13
|
+
if (options.allowDirectOpenAIEndpoint !== undefined) {
|
|
14
|
+
webOptions.allowDirectOpenAIEndpoint = options.allowDirectOpenAIEndpoint;
|
|
15
|
+
}
|
|
16
|
+
this.engine = createIndeRunWeb(webOptions);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw toIndeRunException(error).toContractError();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async run(request) {
|
|
23
|
+
if (!this.engine) {
|
|
24
|
+
throw createUnavailable("Capacitor IndeRun has not been configured. Configure providers before calling run(request).").toContractError();
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await this.engine.run(request);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw toIndeRunException(error).toContractError();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function compactOpenAIOptions(options) {
|
|
35
|
+
const openAI = options.openAI;
|
|
36
|
+
const result = {
|
|
37
|
+
model: openAI.model
|
|
38
|
+
};
|
|
39
|
+
if (openAI.endpointUrl !== undefined) {
|
|
40
|
+
result.endpointUrl = openAI.endpointUrl;
|
|
41
|
+
}
|
|
42
|
+
if (openAI.auth !== undefined) {
|
|
43
|
+
result.auth = openAI.auth;
|
|
44
|
+
}
|
|
45
|
+
if (openAI.authContextRef !== undefined) {
|
|
46
|
+
result.authContextRef = openAI.authContextRef;
|
|
47
|
+
}
|
|
48
|
+
if (openAI.timeoutMs !== undefined) {
|
|
49
|
+
result.timeoutMs = openAI.timeoutMs;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#if canImport(Capacitor)
|
|
2
|
+
import Capacitor
|
|
3
|
+
#else
|
|
4
|
+
typealias JSObject = [String: Any]
|
|
5
|
+
#endif
|
|
6
|
+
import Foundation
|
|
7
|
+
import IndeRunAppleProviders
|
|
8
|
+
import IndeRunCore
|
|
9
|
+
import IndeRunContracts
|
|
10
|
+
import IndeRunOpenAIProviders
|
|
11
|
+
import IndeRunSwift
|
|
12
|
+
|
|
13
|
+
struct OpenAIProviderBootstrapOptions: Codable {
|
|
14
|
+
let model: String
|
|
15
|
+
let endpointURL: String?
|
|
16
|
+
let auth: String?
|
|
17
|
+
let authContextRef: String?
|
|
18
|
+
let timeoutMs: Int?
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct CapacitorRunOptions: Codable {
|
|
22
|
+
let openAI: OpenAIProviderBootstrapOptions?
|
|
23
|
+
let allowDirectOpenAIEndpoint: Bool? // web-only, no-op on native
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
final class IndeRunCapacitorBridge {
|
|
27
|
+
private var configuredRegistry: ProviderRegistry?
|
|
28
|
+
private var configuredHostServices: HostServices?
|
|
29
|
+
|
|
30
|
+
func configure(options: JSObject) throws {
|
|
31
|
+
let runOptions = try decodeConfigureOptions(from: options)
|
|
32
|
+
configuredRegistry = try makeRegistry(openAI: runOptions.openAI)
|
|
33
|
+
configuredHostServices = DefaultHostServices.make()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func run(requestObject: JSObject) async throws -> JSObject {
|
|
37
|
+
guard let registry = configuredRegistry, let hostServices = configuredHostServices else {
|
|
38
|
+
throw createUnavailable(message: "Capacitor IndeRun has not been configured. Configure providers before calling run(request).")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let request = try decodeRequest(from: requestObject)
|
|
42
|
+
// IndeRun is a stateless coordinator; new per call is intentional — registry is cached above.
|
|
43
|
+
let engine = IndeRun(registry: registry, hostServices: hostServices)
|
|
44
|
+
let result = try await engine.run(request: request)
|
|
45
|
+
return try encode(result)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func encode(error: IndeRunError) throws -> JSObject {
|
|
49
|
+
try encodeObject(error)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private func makeRegistry(openAI: OpenAIProviderBootstrapOptions?) throws -> ProviderRegistry {
|
|
53
|
+
let registry = ProviderRegistry()
|
|
54
|
+
try registry.register(AppleFoundationModelsProvider())
|
|
55
|
+
|
|
56
|
+
if let openAI {
|
|
57
|
+
try registry.register(
|
|
58
|
+
OpenAIProvider(
|
|
59
|
+
options: OpenAIProviderOptions(
|
|
60
|
+
id: "openai",
|
|
61
|
+
model: openAI.model,
|
|
62
|
+
endpointURL: openAI.endpointURL ?? defaultOpenAIResponsesEndpoint,
|
|
63
|
+
auth: mapAuthMode(openAI.auth),
|
|
64
|
+
authContextRef: openAI.authContextRef,
|
|
65
|
+
timeoutMs: openAI.timeoutMs
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return registry
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func decodeConfigureOptions(from object: JSObject) throws -> CapacitorRunOptions {
|
|
75
|
+
let data = try JSONSerialization.data(withJSONObject: object, options: [])
|
|
76
|
+
return try JSONDecoder().decode(CapacitorRunOptions.self, from: data)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func decodeRequest(from object: JSObject) throws -> TaskRequest {
|
|
80
|
+
let data = try JSONSerialization.data(withJSONObject: object, options: [])
|
|
81
|
+
return try JSONDecoder().decode(TaskRequest.self, from: data)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private func encode(_ result: TaskResult) throws -> JSObject {
|
|
85
|
+
try encodeObject(result)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private func encodeObject<T: Encodable>(_ value: T) throws -> JSObject {
|
|
89
|
+
let data = try JSONEncoder().encode(value)
|
|
90
|
+
let object = try JSONSerialization.jsonObject(with: data, options: [])
|
|
91
|
+
guard let dictionary = object as? JSObject else {
|
|
92
|
+
throw createInternal(message: "Capacitor bridge failed to encode a JSON object.")
|
|
93
|
+
}
|
|
94
|
+
return dictionary
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func mapAuthMode(_ value: String?) -> OpenAIAuthMode {
|
|
98
|
+
switch value {
|
|
99
|
+
case "none":
|
|
100
|
+
return .none
|
|
101
|
+
default:
|
|
102
|
+
return .authContextRef
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#if canImport(Capacitor)
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Foundation
|
|
4
|
+
import IndeRunCore
|
|
5
|
+
|
|
6
|
+
@objc(IndeRunCapacitorPlugin)
|
|
7
|
+
public final class IndeRunCapacitorPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
8
|
+
public let identifier = "IndeRunCapacitorPlugin"
|
|
9
|
+
public let jsName = "IndeRunCapacitor"
|
|
10
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
11
|
+
CAPPluginMethod(name: "configure", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "run", returnType: CAPPluginReturnPromise)
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
private let implementation = IndeRunCapacitorBridge()
|
|
16
|
+
|
|
17
|
+
@objc func configure(_ call: CAPPluginCall) {
|
|
18
|
+
do {
|
|
19
|
+
try implementation.configure(options: call.options)
|
|
20
|
+
call.resolve()
|
|
21
|
+
} catch let error as IndeRunException {
|
|
22
|
+
let contractError = error.toContractError()
|
|
23
|
+
let details = try? implementation.encode(error: contractError)
|
|
24
|
+
call.reject(contractError.message, contractError.errorClass.rawValue, error, details)
|
|
25
|
+
} catch {
|
|
26
|
+
let normalized = toIndeRunException(error)
|
|
27
|
+
let contractError = normalized.toContractError()
|
|
28
|
+
let details = try? implementation.encode(error: contractError)
|
|
29
|
+
call.reject(contractError.message, contractError.errorClass.rawValue, normalized, details)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@objc func run(_ call: CAPPluginCall) {
|
|
34
|
+
Task {
|
|
35
|
+
do {
|
|
36
|
+
let result = try await implementation.run(requestObject: call.options)
|
|
37
|
+
call.resolve(result)
|
|
38
|
+
} catch let error as IndeRunException {
|
|
39
|
+
let contractError = error.toContractError()
|
|
40
|
+
let details = try? implementation.encode(error: contractError)
|
|
41
|
+
call.reject(contractError.message, contractError.errorClass.rawValue, error, details)
|
|
42
|
+
} catch {
|
|
43
|
+
let normalized = toIndeRunException(error)
|
|
44
|
+
let contractError = normalized.toContractError()
|
|
45
|
+
let details = try? implementation.encode(error: contractError)
|
|
46
|
+
call.reject(contractError.message, contractError.errorClass.rawValue, normalized, details)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
#endif
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import Foundation
|
|
3
|
+
import IndeRunContracts
|
|
4
|
+
@testable import IndeRunCapacitorPlugin
|
|
5
|
+
|
|
6
|
+
final class IndeRunCapacitorBridgeTests: XCTestCase {
|
|
7
|
+
|
|
8
|
+
// MARK: - CapacitorRunOptions decoding
|
|
9
|
+
|
|
10
|
+
func testDecodesConfigureOptionsWithOpenAI() throws {
|
|
11
|
+
let json: [String: Any] = [
|
|
12
|
+
"openAI": [
|
|
13
|
+
"model": "gpt-5.2",
|
|
14
|
+
"endpointURL": "/api/inderun/openai-responses",
|
|
15
|
+
"auth": "none",
|
|
16
|
+
"authContextRef": "openai/main",
|
|
17
|
+
"timeoutMs": 30_000
|
|
18
|
+
]
|
|
19
|
+
]
|
|
20
|
+
let data = try JSONSerialization.data(withJSONObject: json)
|
|
21
|
+
let options = try JSONDecoder().decode(CapacitorRunOptions.self, from: data)
|
|
22
|
+
|
|
23
|
+
XCTAssertEqual(options.openAI?.model, "gpt-5.2")
|
|
24
|
+
XCTAssertEqual(options.openAI?.endpointURL, "/api/inderun/openai-responses")
|
|
25
|
+
XCTAssertEqual(options.openAI?.auth, "none")
|
|
26
|
+
XCTAssertEqual(options.openAI?.authContextRef, "openai/main")
|
|
27
|
+
XCTAssertEqual(options.openAI?.timeoutMs, 30_000)
|
|
28
|
+
XCTAssertNil(options.allowDirectOpenAIEndpoint)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func testDecodesConfigureOptionsWithAllOptionalFieldsAbsent() throws {
|
|
32
|
+
let data = try JSONSerialization.data(withJSONObject: [String: Any]())
|
|
33
|
+
let options = try JSONDecoder().decode(CapacitorRunOptions.self, from: data)
|
|
34
|
+
|
|
35
|
+
XCTAssertNil(options.openAI)
|
|
36
|
+
XCTAssertNil(options.allowDirectOpenAIEndpoint)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func testDecodesOpenAIOptionsWithOnlyRequiredFields() throws {
|
|
40
|
+
let json: [String: Any] = ["openAI": ["model": "gpt-4"]]
|
|
41
|
+
let data = try JSONSerialization.data(withJSONObject: json)
|
|
42
|
+
let options = try JSONDecoder().decode(CapacitorRunOptions.self, from: data)
|
|
43
|
+
|
|
44
|
+
XCTAssertEqual(options.openAI?.model, "gpt-4")
|
|
45
|
+
XCTAssertNil(options.openAI?.endpointURL)
|
|
46
|
+
XCTAssertNil(options.openAI?.auth)
|
|
47
|
+
XCTAssertNil(options.openAI?.authContextRef)
|
|
48
|
+
XCTAssertNil(options.openAI?.timeoutMs)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func testDecodesAllowDirectOpenAIEndpoint() throws {
|
|
52
|
+
let json: [String: Any] = ["allowDirectOpenAIEndpoint": true]
|
|
53
|
+
let data = try JSONSerialization.data(withJSONObject: json)
|
|
54
|
+
let options = try JSONDecoder().decode(CapacitorRunOptions.self, from: data)
|
|
55
|
+
|
|
56
|
+
XCTAssertEqual(options.allowDirectOpenAIEndpoint, true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: - encode(error:)
|
|
60
|
+
|
|
61
|
+
func testEncodesIndeRunErrorRequiredFieldsOnly() throws {
|
|
62
|
+
let bridge = IndeRunCapacitorBridge()
|
|
63
|
+
let error = IndeRunError(
|
|
64
|
+
details: nil,
|
|
65
|
+
errorClass: .Unavailable,
|
|
66
|
+
message: "Not configured.",
|
|
67
|
+
providerId: nil,
|
|
68
|
+
retryable: nil,
|
|
69
|
+
retryAfterMs: nil,
|
|
70
|
+
runId: nil,
|
|
71
|
+
schemaVersion: .the10
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
let encoded = try bridge.encode(error: error)
|
|
75
|
+
|
|
76
|
+
XCTAssertEqual(encoded["schemaVersion"] as? String, "1.0")
|
|
77
|
+
XCTAssertEqual(encoded["errorClass"] as? String, "Unavailable")
|
|
78
|
+
XCTAssertEqual(encoded["message"] as? String, "Not configured.")
|
|
79
|
+
XCTAssertNil(encoded["providerId"])
|
|
80
|
+
XCTAssertNil(encoded["retryable"])
|
|
81
|
+
XCTAssertNil(encoded["retryAfterMs"])
|
|
82
|
+
XCTAssertNil(encoded["runId"])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func testEncodesIndeRunErrorWithAllOptionalFields() throws {
|
|
86
|
+
let bridge = IndeRunCapacitorBridge()
|
|
87
|
+
let error = IndeRunError(
|
|
88
|
+
details: nil,
|
|
89
|
+
errorClass: .RateLimited,
|
|
90
|
+
message: "Too many requests.",
|
|
91
|
+
providerId: "openai",
|
|
92
|
+
retryable: true,
|
|
93
|
+
retryAfterMs: 2_000,
|
|
94
|
+
runId: "run_abc",
|
|
95
|
+
schemaVersion: .the10
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
let encoded = try bridge.encode(error: error)
|
|
99
|
+
|
|
100
|
+
XCTAssertEqual(encoded["errorClass"] as? String, "RateLimited")
|
|
101
|
+
XCTAssertEqual(encoded["providerId"] as? String, "openai")
|
|
102
|
+
XCTAssertEqual(encoded["retryable"] as? Bool, true)
|
|
103
|
+
XCTAssertEqual(encoded["retryAfterMs"] as? Int, 2_000)
|
|
104
|
+
XCTAssertEqual(encoded["runId"] as? String, "run_abc")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - mapAuthMode
|
|
108
|
+
|
|
109
|
+
func testMapAuthModeNoneString() {
|
|
110
|
+
let bridge = IndeRunCapacitorBridge()
|
|
111
|
+
XCTAssertEqual(bridge.mapAuthMode("none"), .none)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func testMapAuthModeUnknownStringDefaultsToAuthContextRef() {
|
|
115
|
+
let bridge = IndeRunCapacitorBridge()
|
|
116
|
+
XCTAssertEqual(bridge.mapAuthMode("some_future_value"), .authContextRef)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func testMapAuthModeNilDefaultsToAuthContextRef() {
|
|
120
|
+
let bridge = IndeRunCapacitorBridge()
|
|
121
|
+
XCTAssertEqual(bridge.mapAuthMode(nil), .authContextRef)
|
|
122
|
+
}
|
|
123
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@independo/capacitor-inderun",
|
|
3
|
+
"version": "0.2.0-dev.0",
|
|
4
|
+
"description": "Thin Capacitor bridge for IndeRun across web, iOS, and Android.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/independo-gmbh/capacitor-inderun#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/independo-gmbh/capacitor-inderun.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/independo-gmbh/capacitor-inderun/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"capacitor",
|
|
16
|
+
"plugin",
|
|
17
|
+
"inderun",
|
|
18
|
+
"ai"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=24"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"android",
|
|
35
|
+
"ios",
|
|
36
|
+
"Package.swift",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"capacitor": {
|
|
40
|
+
"ios": {
|
|
41
|
+
"src": "ios"
|
|
42
|
+
},
|
|
43
|
+
"android": {
|
|
44
|
+
"src": "android"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"prepack": "pnpm run build",
|
|
50
|
+
"lint": "tsc --noEmit -p tsconfig.json",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:web": "vitest run",
|
|
53
|
+
"test:android": "cd android && ./gradlew :testDebugUnitTest",
|
|
54
|
+
"test:ios": "swift test",
|
|
55
|
+
"verify:web": "tsc -p tsconfig.json",
|
|
56
|
+
"verify:android": "cd android && ./gradlew assembleDebug",
|
|
57
|
+
"verify:ios": "swift build"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@capacitor/core": "^8.0.0",
|
|
61
|
+
"@independo/inderun-contracts": "^0.2.0",
|
|
62
|
+
"@independo/inderun-web": "^0.2.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
|
66
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
67
|
+
"@semantic-release/git": "^10.0.1",
|
|
68
|
+
"@types/node": "^24.13.2",
|
|
69
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
70
|
+
"semantic-release": "^25.0.5",
|
|
71
|
+
"typescript": "^6.0.3",
|
|
72
|
+
"vitest": "^3.2.6"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@capacitor/core": "^8.0.0"
|
|
76
|
+
}
|
|
77
|
+
}
|