diff --git a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt
index b3a57f262d..7f1e73fc46 100644
--- a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt
+++ b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt
@@ -6,9 +6,9 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.swmansion.rnexecutorch.utils.ArrayUtils
import com.swmansion.rnexecutorch.utils.ETError
-import com.swmansion.rnexecutorch.utils.Fetcher
import com.swmansion.rnexecutorch.utils.TensorUtils
import org.pytorch.executorch.Module
+import java.net.URL
class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(reactContext) {
private lateinit var module: Module
@@ -18,19 +18,8 @@ class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(react
}
override fun loadModule(modelSource: String, promise: Promise) {
- Fetcher.downloadModel(
- reactApplicationContext,
- modelSource,
- ) { path, error ->
- if (error != null) {
- promise.reject(error.message!!, ETError.InvalidModelSource.toString())
- return@downloadModel
- }
-
- module = Module.load(path)
- promise.resolve(0)
- return@downloadModel
- }
+ module = Module.load(URL(modelSource).path)
+ promise.resolve(0)
}
override fun loadMethod(methodName: String, promise: Promise) {
diff --git a/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt b/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt
index 393468b8f1..4ca13ca51c 100644
--- a/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt
+++ b/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt
@@ -3,14 +3,12 @@ package com.swmansion.rnexecutorch
import android.util.Log
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
-import com.swmansion.rnexecutorch.utils.Fetcher
-import com.swmansion.rnexecutorch.utils.ProgressResponseBody
-import com.swmansion.rnexecutorch.utils.ResourceType
import com.swmansion.rnexecutorch.utils.llms.ChatRole
import com.swmansion.rnexecutorch.utils.llms.ConversationManager
import com.swmansion.rnexecutorch.utils.llms.END_OF_TEXT_TOKEN
import org.pytorch.executorch.LlamaCallback
import org.pytorch.executorch.LlamaModule
+import java.net.URL
class LLM(reactContext: ReactApplicationContext) :
NativeLLMSpec(reactContext), LlamaCallback {
@@ -18,7 +16,6 @@ class LLM(reactContext: ReactApplicationContext) :
private var llamaModule: LlamaModule? = null
private var tempLlamaResponse = StringBuilder()
private lateinit var conversationManager: ConversationManager
- private var isFetching = false
override fun getName(): String {
return NAME
@@ -37,37 +34,6 @@ class LLM(reactContext: ReactApplicationContext) :
Log.d("rn_executorch", "TPS: $tps")
}
- private fun updateDownloadProgress(progress: Float) {
- emitOnDownloadProgress((progress / 100).toDouble())
- }
-
- private fun downloadResource(
- url: String,
- resourceType: ResourceType,
- isLargeFile: Boolean = false,
- callback: (path: String?, error: Exception?) -> Unit,
- ) {
- Fetcher.downloadResource(
- reactApplicationContext, url, resourceType, isLargeFile,
- { path, error -> callback(path, error) },
- object : ProgressResponseBody.ProgressListener {
- override fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean) {
- val progress = (bytesRead * 100 / contentLength).toFloat()
- updateDownloadProgress(progress)
- if (done) {
- isFetching = false
- }
- }
- })
- }
-
- private fun initializeLlamaModule(modelPath: String, tokenizerPath: String, promise: Promise) {
- llamaModule = LlamaModule(1, modelPath, tokenizerPath, 0.7f)
- isFetching = false
- this.tempLlamaResponse.clear()
- promise.resolve("Model loaded successfully")
- }
-
override fun loadLLM(
modelSource: String,
tokenizerSource: String,
@@ -75,46 +41,13 @@ class LLM(reactContext: ReactApplicationContext) :
contextWindowLength: Double,
promise: Promise
) {
- if (isFetching) {
- promise.reject("Model is fetching", "Model is fetching")
- return
- }
-
try {
this.conversationManager = ConversationManager(contextWindowLength.toInt(), systemPrompt)
-
- isFetching = true
-
- downloadResource(
- tokenizerSource,
- ResourceType.TOKENIZER
- ) tokenizerDownload@{ tokenizerPath, error ->
- if (error != null) {
- promise.reject("Download Error", "Tokenizer download failed: ${error.message}")
- isFetching = false
- return@tokenizerDownload
- }
-
- downloadResource(
- modelSource,
- ResourceType.MODEL,
- isLargeFile = true
- ) modelDownload@{ modelPath, modelError ->
- if (modelError != null) {
- promise.reject(
- "Download Error",
- "Model download failed: ${modelError.message}"
- )
- isFetching = false
- return@modelDownload
- }
-
- initializeLlamaModule(modelPath!!, tokenizerPath!!, promise)
- }
- }
+ llamaModule = LlamaModule(1, URL(modelSource).path, URL(tokenizerSource).path, 0.7f)
+ this.tempLlamaResponse.clear()
+ promise.resolve("Model loaded successfully")
} catch (e: Exception) {
- promise.reject("Download Error", e.message)
- isFetching = false
+ promise.reject("Model loading failed", e.message)
}
}
diff --git a/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt b/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt
index 4ce67bcdef..ae3e8b9f3a 100644
--- a/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt
+++ b/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt
@@ -2,26 +2,16 @@ package com.swmansion.rnexecutorch.models
import android.content.Context
import com.swmansion.rnexecutorch.utils.ETError
-import com.swmansion.rnexecutorch.utils.Fetcher
import org.pytorch.executorch.EValue
import org.pytorch.executorch.Module
-import org.pytorch.executorch.Tensor
+import java.net.URL
abstract class BaseModel(val context: Context) {
protected lateinit var module: Module
fun loadModel(modelSource: String) {
- Fetcher.downloadModel(
- context,
- modelSource
- ) { path, error ->
- if (error != null) {
- throw Error(error.message!!)
- }
-
- module = Module.load(path)
- }
+ module = Module.load(URL(modelSource).path)
}
protected fun forward(input: EValue): Array {
diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt
deleted file mode 100644
index deaf787e36..0000000000
--- a/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt
+++ /dev/null
@@ -1,295 +0,0 @@
-package com.swmansion.rnexecutorch.utils
-
-import android.content.Context
-import okhttp3.Call
-import okhttp3.Callback
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.RequestBody
-import okhttp3.Response
-import okio.IOException
-import java.io.File
-import java.io.FileOutputStream
-import java.net.URL
-
-enum class ResourceType {
- TOKENIZER,
- MODEL,
-}
-
-class Fetcher {
- companion object {
- private fun saveResponseToFile(
- response: Response,
- directory: File,
- fileName: String,
- ): File {
- val file = File(directory.path, fileName)
- file.outputStream().use { outputStream ->
- response.body?.byteStream()?.copyTo(outputStream)
- }
- return file
- }
-
- private fun getValidExtension(resourceType: ResourceType): String {
- return when (resourceType) {
- ResourceType.TOKENIZER -> {
- "bin"
- }
-
- ResourceType.MODEL -> {
- "pte"
- }
- }
- }
-
- private fun extractFileName(url: URL): String {
- if (url.path == "/assets/") {
- val pathSegments = url.toString().split('/')
- return pathSegments[pathSegments.size - 1].split("?")[0]
- }
-
- return url.path.substringAfterLast('/')
- }
-
- private fun fetchModel(
- file: File,
- validFile: File,
- client: OkHttpClient,
- url: URL,
- onComplete: (String?, Exception?) -> Unit,
- listener: ProgressResponseBody.ProgressListener? = null,
- ) {
- val request = Request.Builder().url(url).build()
- client.newCall(request).enqueue(object : Callback {
- override fun onFailure(call: Call, e: IOException) {
- onComplete(null, e)
- }
-
- override fun onResponse(call: Call, response: Response) {
- if (!response.isSuccessful) {
- onComplete(null, Exception("download_error"))
- }
-
- response.body?.let { body ->
- val progressBody = listener?.let { ProgressResponseBody(body, it) }
- val inputStream = progressBody?.source()?.inputStream()
- inputStream?.use { input ->
- FileOutputStream(file).use { output ->
- val buffer = ByteArray(2048)
- var bytesRead: Int
- while (input.read(buffer).also { bytesRead = it } != -1) {
- output.write(buffer, 0, bytesRead)
- }
- }
- }
-
- if (file.renameTo(validFile)) {
- onComplete(validFile.absolutePath, null)
- } else {
- onComplete(null, Exception("Failed to move the file to the valid location"))
- }
- }
- }
- })
- }
-
- private fun isUrlPointingToHfRepo(url: URL): Boolean {
- val expectedHost = "huggingface.co"
- val expectedPathPrefix = "/software-mansion/"
- if (url.host != expectedHost) {
- return false
- }
- return url.path.startsWith(expectedPathPrefix)
- }
-
- private fun resolveConfigUrlFromModelUrl(modelUrl: URL): URL {
- // Create a new URL using the base URL and append the desired path
- val baseUrl =
- modelUrl.protocol + "://" + modelUrl.host + modelUrl.path.substringBefore("resolve/")
- return URL(baseUrl + "resolve/main/config.json")
- }
-
- private fun sendRequestToUrl(
- url: URL,
- method: String,
- body: RequestBody?,
- client: OkHttpClient
- ): Response {
- val request = Request.Builder()
- .url(url)
- .method(method, body)
- .build()
- val response = client.newCall(request).execute()
- return response
- }
-
- private fun getIdOfResource(
- context: Context,
- resourceName: String,
- defType: String = "raw"
- ): Int {
- return context.resources.getIdentifier(resourceName, defType, context.packageName)
- }
-
- private fun getResourceFromAssets(
- context: Context,
- url: String,
- resourceType: ResourceType,
- onComplete: (String?, Exception?) -> Unit
- ) {
- if (!url.contains("://")) {
- //The provided file is from react-native assets folder in release mode
- val resId = getIdOfResource(context, url)
- val resName = context.resources.getResourceEntryName(resId)
- val fileExtension = getValidExtension(resourceType)
- context.resources.openRawResource(resId).use { inputStream ->
- val file = File(
- context.filesDir,
- "$resName.$fileExtension"
- )
- file.outputStream().use { outputStream ->
- inputStream.copyTo(outputStream)
- }
- onComplete(file.absolutePath, null)
- return
- }
- }
- }
-
- private fun getLocalFile(
- url: URL,
- resourceType: ResourceType,
- onComplete: (String?, Exception?) -> Unit
- ) {
- // The provided file is a local file, get rid of the file:// prefix and return path
- if (url.protocol == "file") {
- val localPath = url.path
- if (getValidExtension(resourceType) != localPath.takeLast(3)) {
- throw Exception("invalid_extension")
- }
-
- val file = File(localPath)
- if (file.exists()) {
- onComplete(localPath, null)
- return
- }
-
- throw Exception("file_not_found")
- }
- }
-
- private fun getRemoteFile(
- context: Context,
- url: URL,
- resourceType: ResourceType,
- isLargeFile: Boolean,
- onComplete: (String?, Exception?) -> Unit,
- listener: ProgressResponseBody.ProgressListener?
- ) {
- val client = OkHttpClientSingleton.instance
- val fileName = extractFileName(url)
-
- if (getValidExtension(resourceType) != fileName.takeLast(3)) {
- throw Exception("invalid_extension")
- }
-
- val modelsDirectory = File(context.filesDir, "models").apply {
- if (!exists()) {
- mkdirs()
- }
- }
-
- var validFile = File(modelsDirectory, fileName)
- if (validFile.exists()) {
- onComplete(validFile.absolutePath, null)
- return
- }
-
- // If the url is a Software Mansion HuggingFace repo, we want to send a HEAD
- // request to the config.json file, this increments HF download counter
- // https://huggingface.co/docs/hub/models-download-stats
- if (isUrlPointingToHfRepo(url)) {
- val configUrl = resolveConfigUrlFromModelUrl(url)
- sendRequestToUrl(configUrl, "HEAD", null, client)
- }
-
- if (!isLargeFile) {
- val request = Request.Builder().url(url).build()
- val response = client.newCall(request).execute()
-
- if (!response.isSuccessful) {
- throw Exception("download_error")
- }
-
- validFile = saveResponseToFile(response, modelsDirectory, fileName)
- onComplete(validFile.absolutePath, null)
- return
- }
-
- val tempFile = File(context.filesDir, fileName)
- if (tempFile.exists()) {
- tempFile.delete()
- }
-
- fetchModel(tempFile, validFile, client, url, onComplete, listener)
- }
-
- fun downloadModel(context: Context, url: String, callback: (String?, Exception?) -> Unit) {
- downloadResource(context,
- url,
- ResourceType.MODEL,
- false,
- { path, error -> callback(path, error) })
- }
-
- fun downloadResource(
- context: Context,
- url: String,
- resourceType: ResourceType,
- isLargeFile: Boolean,
- onComplete: (String?, Exception?) -> Unit,
- listener: ProgressResponseBody.ProgressListener? = null,
- ) {
- /*
- Fetching model and tokenizer file
- 1. Check if the provided file is a bundled local file
- a. Check if it exists
- b. Check if it has valid extension
- c. Copy the file and return the path
- 2. Check if the provided file is a path to a local file
- a. Check if it exists
- b. Check if it has valid extension
- c. Return the path
- 3. The provided file is a remote file
- a. Check if it has valid extension
- b. Check if it's a large file
- i. Create temporary file to store it at download time
- ii. Move it to the models directory and return the path
- c. If it's not a large file download it and return the path
- */
-
- try {
- getResourceFromAssets(context, url, resourceType, onComplete)
-
- val resUrl = URL(url)
- /*
- The provided file is either a remote file or a local file
- - local file: file:///path/to/file
- - remote file: https://path/to/file || http://10.0.2.2:8080/path/to/file
- */
- getLocalFile(resUrl, resourceType, onComplete)
-
- /*
- The provided file is a remote file, if it's a large file
- create temporary file to store it at download time and later
- move it to the models directory
- */
- getRemoteFile(context, resUrl, resourceType, isLargeFile, onComplete, listener)
- } catch (e: Exception) {
- onComplete(null, e)
- return
- }
- }
- }
-}
diff --git a/examples/computer-vision/screens/ClassificationScreen.tsx b/examples/computer-vision/screens/ClassificationScreen.tsx
index 5c3b0c3c94..5f6b31b0ab 100644
--- a/examples/computer-vision/screens/ClassificationScreen.tsx
+++ b/examples/computer-vision/screens/ClassificationScreen.tsx
@@ -46,7 +46,10 @@ export const ClassificationScreen = ({
if (!model.isReady) {
return (
-
+
);
}
diff --git a/examples/computer-vision/screens/ObjectDetectionScreen.tsx b/examples/computer-vision/screens/ObjectDetectionScreen.tsx
index ea82dfd493..eea471d1fe 100644
--- a/examples/computer-vision/screens/ObjectDetectionScreen.tsx
+++ b/examples/computer-vision/screens/ObjectDetectionScreen.tsx
@@ -56,7 +56,7 @@ export const ObjectDetectionScreen = ({
return (
);
}
diff --git a/examples/computer-vision/screens/StyleTransferScreen.tsx b/examples/computer-vision/screens/StyleTransferScreen.tsx
index 8ccd0a8deb..0f6b5c9a52 100644
--- a/examples/computer-vision/screens/StyleTransferScreen.tsx
+++ b/examples/computer-vision/screens/StyleTransferScreen.tsx
@@ -39,7 +39,10 @@ export const StyleTransferScreen = ({
if (!model.isReady) {
return (
-
+
);
}
diff --git a/ios/RnExecutorch/Classification.mm b/ios/RnExecutorch/Classification.mm
index 96f510c748..0688a1803f 100644
--- a/ios/RnExecutorch/Classification.mm
+++ b/ios/RnExecutorch/Classification.mm
@@ -1,15 +1,14 @@
#import "Classification.h"
-#import "utils/Fetcher.h"
+#import "ImageProcessor.h"
#import "models/BaseModel.h"
+#import "models/classification/ClassificationModel.h"
+#import "opencv2/opencv.hpp"
#import "utils/ETError.h"
-#import "ImageProcessor.h"
#import
#import
-#import "models/classification/ClassificationModel.h"
-#import "opencv2/opencv.hpp"
@implementation Classification {
- ClassificationModel* model;
+ ClassificationModel *model;
}
RCT_EXPORT_MODULE()
@@ -18,16 +17,19 @@ - (void)loadModule:(NSString *)modelSource
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
model = [[ClassificationModel alloc] init];
- [model loadModel: [NSURL URLWithString:modelSource] completion:^(BOOL success, NSNumber *errorCode){
- if(success){
- resolve(errorCode);
- return;
- }
-
- reject(@"init_module_error", [NSString
- stringWithFormat:@"%ld", (long)[errorCode longValue]], nil);
- return;
- }];
+ [model
+ loadModel:[NSURL URLWithString:modelSource]
+ completion:^(BOOL success, NSNumber *errorCode) {
+ if (success) {
+ resolve(errorCode);
+ return;
+ }
+
+ reject(@"init_module_error",
+ [NSString stringWithFormat:@"%ld", (long)[errorCode longValue]],
+ nil);
+ return;
+ }];
}
- (void)forward:(NSString *)input
@@ -36,20 +38,19 @@ - (void)forward:(NSString *)input
@try {
cv::Mat image = [ImageProcessor readImage:input];
NSDictionary *result = [model runModel:image];
-
+
resolve(result);
return;
} @catch (NSException *exception) {
NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason);
- reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason],
- nil);
+ reject(@"forward_error",
+ [NSString stringWithFormat:@"%@", exception.reason], nil);
return;
}
}
-
- (std::shared_ptr)getTurboModule:
-(const facebook::react::ObjCTurboModule::InitParams &)params {
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared(params);
}
diff --git a/ios/RnExecutorch/ETModule.mm b/ios/RnExecutorch/ETModule.mm
index e04dec88d1..abd202796b 100644
--- a/ios/RnExecutorch/ETModule.mm
+++ b/ios/RnExecutorch/ETModule.mm
@@ -1,5 +1,4 @@
#import "ETModule.h"
-#import "utils/Fetcher.h"
#import
#import
#include
@@ -16,30 +15,25 @@ - (void)loadModule:(NSString *)modelSource
if (!module) {
module = [[ETModel alloc] init];
}
-
- [Fetcher fetchResource:[NSURL URLWithString:modelSource]
- resourceType:ResourceType::MODEL
- completionHandler:^(NSString *filePath, NSError *error) {
- if (error) {
- reject(@"init_module_error", @"-1", nil);
- return;
- }
-
- NSNumber *result = [self->module loadModel:filePath];
- if ([result isEqualToNumber:@(0)]) {
- resolve(result);
- } else {
- NSError *error = [NSError
- errorWithDomain:@"ETModuleErrorDomain"
- code:[result intValue]
+
+ NSURL *modelURL = [NSURL URLWithString:modelSource];
+
+ NSNumber *result = [self->module loadModel:modelURL.path];
+
+ if ([result intValue] != 0) {
+ NSError *error =
+ [NSError errorWithDomain:@"ETModuleErrorDomain"
+ code:[result intValue]
userInfo:@{
- NSLocalizedDescriptionKey : [NSString
- stringWithFormat:@"%ld", (long)[result longValue]]
- }];
-
- reject(@"init_module_error", error.localizedDescription, error);
- }
- }];
+ NSLocalizedDescriptionKey : [NSString
+ stringWithFormat:@"%ld", (long)[result longValue]]
+ }];
+
+ reject(@"init_module_error", error.localizedDescription, error);
+ return;
+ }
+
+ resolve(result);
}
- (void)forward:(NSArray *)input
@@ -48,7 +42,9 @@ - (void)forward:(NSArray *)input
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
@try {
- NSArray *result = [module forward:input shape:shape inputType:[NSNumber numberWithInt:inputType]];
+ NSArray *result = [module forward:input
+ shape:shape
+ inputType:[NSNumber numberWithInt:inputType]];
resolve(result);
} @catch (NSException *exception) {
NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason);
@@ -69,7 +65,7 @@ - (void)loadMethod:(NSString *)methodName
}
- (std::shared_ptr)getTurboModule:
-(const facebook::react::ObjCTurboModule::InitParams &)params {
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared(params);
}
diff --git a/ios/RnExecutorch/LLM.mm b/ios/RnExecutorch/LLM.mm
index 8b6957ff4f..462c114279 100644
--- a/ios/RnExecutorch/LLM.mm
+++ b/ios/RnExecutorch/LLM.mm
@@ -1,34 +1,29 @@
#import "LLM.h"
-#import
-#import "utils/llms/ConversationManager.h"
#import "utils/llms/Constants.h"
-#import "utils/Fetcher.h"
-#import "utils/LargeFileFetcher.h"
-#import
-#import
+#import "utils/llms/ConversationManager.h"
+#import
#import
#import
#import
#import
#import
#import
+#import
#import
-
+#import
@implementation LLM {
LLaMARunner *runner;
ConversationManager *conversationManager;
NSMutableString *tempLlamaResponse;
- BOOL isFetching;
}
- (instancetype)init {
self = [super init];
- if(self) {
- isFetching = NO;
+ if (self) {
tempLlamaResponse = [[NSMutableString alloc] init];
}
-
+
return self;
}
@@ -38,99 +33,84 @@ - (void)onResult:(NSString *)token prompt:(NSString *)prompt {
if ([token isEqualToString:prompt]) {
return;
}
-
+
dispatch_async(dispatch_get_main_queue(), ^{
[self emitOnToken:token];
[self->tempLlamaResponse appendString:token];
});
}
-- (void)updateDownloadProgress:(NSNumber *)progress {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self emitOnDownloadProgress:progress];
- });
-}
-
-- (void)loadLLM:(NSString *)modelSource tokenizerSource:(NSString *)tokenizerSource systemPrompt:(NSString *)systemPrompt contextWindowLength:(double)contextWindowLength resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
+- (void)loadLLM:(NSString *)modelSource
+ tokenizerSource:(NSString *)tokenizerSource
+ systemPrompt:(NSString *)systemPrompt
+ contextWindowLength:(double)contextWindowLength
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject {
NSURL *modelURL = [NSURL URLWithString:modelSource];
NSURL *tokenizerURL = [NSURL URLWithString:tokenizerSource];
-
- if(isFetching){
- reject(@"model_is_fetching", @"Model is fetching", nil);
+ @try {
+ self->runner = [[LLaMARunner alloc] initWithModelPath:modelURL.path
+ tokenizerPath:tokenizerURL.path];
+ NSUInteger contextWindowLengthUInt = (NSUInteger)round(contextWindowLength);
+
+ self->conversationManager = [[ConversationManager alloc]
+ initWithNumMessagesContextWindow:contextWindowLengthUInt
+ systemPrompt:systemPrompt];
+
+ self->tempLlamaResponse = [NSMutableString string];
+ resolve(@"Model and tokenizer loaded successfully");
+ return;
+ } @catch (NSException *exception) {
+ reject(@"Model or tokenizer loading failed", exception.reason, nil);
return;
}
-
- isFetching = YES;
- [Fetcher fetchResource:tokenizerURL resourceType:ResourceType::TOKENIZER completionHandler:^(NSString *tokenizerFilePath, NSError *error) {
- if(error){
- reject(@"download_error", error.localizedDescription, nil);
- return;
- }
- LargeFileFetcher *modelFetcher = [[LargeFileFetcher alloc] init];
- modelFetcher.onProgress = ^(NSNumber *progress) {
- [self updateDownloadProgress:progress];
- };
-
- modelFetcher.onFailure = ^(NSError *error){
- reject(@"download_error", error.localizedDescription, nil);
- return;
- };
-
- modelFetcher.onFinish = ^(NSString *modelFilePath) {
- self->runner = [[LLaMARunner alloc] initWithModelPath:modelFilePath tokenizerPath:tokenizerFilePath];
- NSUInteger contextWindowLengthUInt = (NSUInteger)round(contextWindowLength);
-
- self->conversationManager = [[ConversationManager alloc] initWithNumMessagesContextWindow: contextWindowLengthUInt systemPrompt: systemPrompt];
- self->isFetching = NO;
- self->tempLlamaResponse = [NSMutableString string];
- resolve(@"Model and tokenizer loaded successfully");
- return;
- };
-
- [modelFetcher startDownloadingFileFromURL:modelURL];
- }];
}
-
-- (void) runInference:(NSString *)input resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
+- (void)runInference:(NSString *)input
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject {
[conversationManager addResponse:input senderRole:ChatRole::USER];
NSString *prompt = [conversationManager getConversation];
-
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- NSError *error = nil;
- [self->runner generate:prompt withTokenCallback:^(NSString *token) {
- [self onResult:token prompt:prompt];
- } error:&error];
-
- // make sure to add eot token once generation is done
- if (![self->tempLlamaResponse hasSuffix:END_OF_TEXT_TOKEN_NS]) {
- [self onResult:END_OF_TEXT_TOKEN_NS prompt:prompt];
- }
-
- if (self->tempLlamaResponse) {
- [self->conversationManager addResponse:self->tempLlamaResponse senderRole:ChatRole::ASSISTANT];
- self->tempLlamaResponse = [NSMutableString string];
- }
-
- if (error) {
- reject(@"error_in_generation", error.localizedDescription, nil);
- return;
- }
- resolve(@"Inference completed successfully");
- return;
- });
+
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ NSError *error = nil;
+ [self->runner generate:prompt
+ withTokenCallback:^(NSString *token) {
+ [self onResult:token prompt:prompt];
+ }
+ error:&error];
+
+ // make sure to add eot token once generation is done
+ if (![self->tempLlamaResponse hasSuffix:END_OF_TEXT_TOKEN_NS]) {
+ [self onResult:END_OF_TEXT_TOKEN_NS prompt:prompt];
+ }
+
+ if (self->tempLlamaResponse) {
+ [self->conversationManager addResponse:self->tempLlamaResponse
+ senderRole:ChatRole::ASSISTANT];
+ self->tempLlamaResponse = [NSMutableString string];
+ }
+
+ if (error) {
+ reject(@"error_in_generation", error.localizedDescription, nil);
+ return;
+ }
+ resolve(@"Inference completed successfully");
+ return;
+ });
}
--(void)interrupt {
+- (void)interrupt {
[self->runner stop];
}
--(void)deleteModule {
+- (void)deleteModule {
self->runner = nil;
}
-- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
-{
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared(params);
}
diff --git a/ios/RnExecutorch/ObjectDetection.mm b/ios/RnExecutorch/ObjectDetection.mm
index 785d07b7fa..2324db8ac0 100644
--- a/ios/RnExecutorch/ObjectDetection.mm
+++ b/ios/RnExecutorch/ObjectDetection.mm
@@ -1,8 +1,8 @@
#import "ObjectDetection.h"
#import "models/object_detection/SSDLiteLargeModel.hpp"
+#import "utils/ImageProcessor.h"
#import
#import
-#import "utils/ImageProcessor.h"
@implementation ObjectDetection {
SSDLiteLargeModel *model;
@@ -42,8 +42,8 @@ - (void)forward:(NSString *)input
NSArray *result = [model runModel:image];
resolve(result);
} @catch (NSException *exception) {
- reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason],
- nil);
+ reject(@"forward_error",
+ [NSString stringWithFormat:@"%@", exception.reason], nil);
}
}
diff --git a/ios/RnExecutorch/StyleTransfer.mm b/ios/RnExecutorch/StyleTransfer.mm
index 6b3bba8c3a..08e8d4a34a 100644
--- a/ios/RnExecutorch/StyleTransfer.mm
+++ b/ios/RnExecutorch/StyleTransfer.mm
@@ -1,15 +1,14 @@
#import "StyleTransfer.h"
-#import "utils/Fetcher.h"
+#import "ImageProcessor.h"
#import "models/BaseModel.h"
+#import "models/StyleTransferModel.h"
#import "utils/ETError.h"
-#import "ImageProcessor.h"
#import
#import
-#import "models/StyleTransferModel.h"
#import
@implementation StyleTransfer {
- StyleTransferModel* model;
+ StyleTransferModel *model;
}
RCT_EXPORT_MODULE()
@@ -18,16 +17,19 @@ - (void)loadModule:(NSString *)modelSource
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
model = [[StyleTransferModel alloc] init];
- [model loadModel: [NSURL URLWithString:modelSource] completion:^(BOOL success, NSNumber *errorCode){
- if(success){
- resolve(errorCode);
- return;
- }
-
- reject(@"init_module_error", [NSString
- stringWithFormat:@"%ld", (long)[errorCode longValue]], nil);
- return;
- }];
+ [model
+ loadModel:[NSURL URLWithString:modelSource]
+ completion:^(BOOL success, NSNumber *errorCode) {
+ if (success) {
+ resolve(errorCode);
+ return;
+ }
+
+ reject(@"init_module_error",
+ [NSString stringWithFormat:@"%ld", (long)[errorCode longValue]],
+ nil);
+ return;
+ }];
}
- (void)forward:(NSString *)input
@@ -36,21 +38,20 @@ - (void)forward:(NSString *)input
@try {
cv::Mat image = [ImageProcessor readImage:input];
cv::Mat resultImage = [model runModel:image];
-
- NSString* tempFilePath = [ImageProcessor saveToTempFile:resultImage];
+
+ NSString *tempFilePath = [ImageProcessor saveToTempFile:resultImage];
resolve(tempFilePath);
return;
} @catch (NSException *exception) {
NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason);
- reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason],
- nil);
+ reject(@"forward_error",
+ [NSString stringWithFormat:@"%@", exception.reason], nil);
return;
}
}
-
- (std::shared_ptr)getTurboModule:
-(const facebook::react::ObjCTurboModule::InitParams &)params {
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared(params);
}
diff --git a/ios/RnExecutorch/models/BaseModel.h b/ios/RnExecutorch/models/BaseModel.h
index d5259e6b1f..147215b687 100644
--- a/ios/RnExecutorch/models/BaseModel.h
+++ b/ios/RnExecutorch/models/BaseModel.h
@@ -1,14 +1,14 @@
+#import "ExecutorchLib/ETModel.h"
#import
#import
-#import "ExecutorchLib/ETModel.h"
-@interface BaseModel : NSObject
-{
+@interface BaseModel : NSObject {
@protected
ETModel *module;
}
- (NSArray *)forward:(NSArray *)input;
-- (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber *code))completion;
+- (void)loadModel:(NSURL *)modelURL
+ completion:(void (^)(BOOL success, NSNumber *code))completion;
@end
diff --git a/ios/RnExecutorch/models/BaseModel.mm b/ios/RnExecutorch/models/BaseModel.mm
index b06a3530f6..587eddf68b 100644
--- a/ios/RnExecutorch/models/BaseModel.mm
+++ b/ios/RnExecutorch/models/BaseModel.mm
@@ -1,30 +1,27 @@
#import "BaseModel.h"
#import "../utils/ETError.h"
-#import "../utils/Fetcher.h"
@implementation BaseModel
- (NSArray *)forward:(NSArray *)input {
- NSArray *result = [module forward:input shape:[module getInputShape:@0] inputType:[module getInputType: @0]];
+ NSArray *result = [module forward:input
+ shape:[module getInputShape:@0]
+ inputType:[module getInputType:@0]];
return result;
}
-- (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber* code))completion {
+- (void)loadModel:(NSURL *)modelURL
+ completion:(void (^)(BOOL success, NSNumber *code))completion {
module = [[ETModel alloc] init];
- [Fetcher fetchResource:modelURL resourceType:ResourceType::MODEL completionHandler:^(NSString *filePath, NSError *error) {
- if (error) {
- completion(NO, @(InvalidModelSource));
- return;
- }
- NSNumber *result = [self->module loadModel: filePath];
- if([result intValue] != 0){
- completion(NO, result);
- return;
- }
-
- completion(YES, result);
+
+ NSNumber *result = [self->module loadModel:modelURL.path];
+ if ([result intValue] != 0) {
+ completion(NO, result);
return;
- }];
+ }
+
+ completion(YES, result);
+ return;
}
@end
diff --git a/ios/RnExecutorch/utils/Fetcher.h b/ios/RnExecutorch/utils/Fetcher.h
deleted file mode 100644
index 9d75a5740b..0000000000
--- a/ios/RnExecutorch/utils/Fetcher.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#import
-#import
-
-enum class ResourceType
-{
- MODEL,
- TOKENIZER
-};
-
-inline constexpr unsigned int STATUS_OK = 200;
-
-inline constexpr unsigned int INVALID_URL_CODE = 1;
-inline constexpr unsigned int INVALID_FILE_TYPE = 2;
-inline constexpr unsigned int DOWNLOAD_FAILED = 3;
-inline constexpr unsigned int WRITE_FAILED = 4;
-inline constexpr unsigned int NO_DATA = 5;
-
-NSString *const FETCHER_ERROR_DOMAIN = @"com.swmansion.fetcher";
-
-@interface Fetcher : NSObject
-
-@property(nonatomic, copy) void (^onProgress)(NSNumber *);
-
-+ (void)fetchResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType completionHandler:(void (^)(NSString *filePath, NSError *error))completionHandler;
-+ (BOOL)isValidURL:(NSURL *)url;
-+ (BOOL)isLocalFilePath:(NSURL *)url;
-+ (BOOL)hasValidExtension:(NSString *)fileName resourceType:(ResourceType)resourceType;
-+ (BOOL)fileExistsAtPath:(NSString *)filePath;
-+ (NSString *)extractFilePathFromAssetsURL:(NSURL *)resourceURL;
-+ (NSError *)buildError:(NSString *)description code:(NSInteger)code;
-+ (NSURL *)createDirectoryInDocuments:(NSFileManager *)fileManager;
-+ (NSString *)prepareFilePathForResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType error:(NSError **)error;
-
-@end
diff --git a/ios/RnExecutorch/utils/Fetcher.mm b/ios/RnExecutorch/utils/Fetcher.mm
deleted file mode 100644
index 27ae238e70..0000000000
--- a/ios/RnExecutorch/utils/Fetcher.mm
+++ /dev/null
@@ -1,167 +0,0 @@
-#import
-#import "Fetcher.h"
-
-@implementation Fetcher
-
-+ (NSError *) buildError:(NSString *)description code: (NSInteger)code {
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(description, nil)};
- NSError *error = [NSError errorWithDomain:FETCHER_ERROR_DOMAIN code:code userInfo:userInfo];
- return error;
-}
-
-+ (NSString *) extractFilePathFromAssetsURL: (NSURL *)resourceURL {
- NSString *fileName = nil;
-
- NSURLComponents *components = [NSURLComponents componentsWithURL:resourceURL resolvingAgainstBaseURL:NO];
- NSArray *queryItems = components.queryItems;
-
- for (NSURLQueryItem *item in queryItems) {
- if ([item.name isEqualToString:@"unstable_path"]) {
- NSString *unstablePathValue = item.value;
-
- if (unstablePathValue) {
- NSString *decodedPath = [unstablePathValue stringByRemovingPercentEncoding];
- NSArray *pathComponents = [decodedPath componentsSeparatedByString:@"?"];
-
- fileName = [[pathComponents firstObject] lastPathComponent];
- }
- break;
- }
- }
-
- return fileName ? fileName : @"";
-}
-
-+ (BOOL) isValidURL:(NSURL *) url {
- return url && url.scheme && url.host;
-}
-
-+ (BOOL) isLocalFilePath:(NSURL *) url {
- return [url.scheme isEqualToString:@"file"];
-}
-
-+ (BOOL) hasValidExtension:(NSString *)fileName resourceType:(ResourceType)resourceType{
- switch (resourceType) {
- case ResourceType::TOKENIZER:
- return [fileName hasSuffix:@".bin"];
- case ResourceType::MODEL:
- return [fileName hasSuffix:@".pte"];
- default:
- return NO;
- }
-}
-
-+(BOOL) fileExistsAtPath:(NSString *)filePath {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- return [fileManager fileExistsAtPath:filePath];
-}
-
-+ (NSURL *) createDirectoryInDocuments:(NSFileManager *)fileManager{
- NSArray *documentsDirectoryURLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
- NSURL *documentsDirectoryURL = [documentsDirectoryURLs firstObject];
- NSURL *modelsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:@"models" isDirectory:YES];
-
- NSError *directoryError = nil;
- if (![fileManager fileExistsAtPath:modelsDirectoryURL.path]) {
- [fileManager createDirectoryAtURL:modelsDirectoryURL withIntermediateDirectories:YES attributes:nil error:&directoryError];
- if (directoryError) {
- return [NSURL URLWithString: @""];
- }
- }
-
- return modelsDirectoryURL;
-}
-
-+ (NSString *)prepareFilePathForResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType error:(NSError **)error {
- NSFileManager *fileManager = [NSFileManager defaultManager];
-
- //If fileName is equal to assets it means that the model is included in bundle
- NSString *fileName = [resourceURL lastPathComponent];
-
- // If fileName is "assets", extract the file path
- if ([fileName isEqualToString:@"assets"]) {
- fileName = [self extractFilePathFromAssetsURL:resourceURL];
- }
-
- //Check if the file extension matches type of resource
- if (![Fetcher hasValidExtension:fileName resourceType:resourceType]) {
- if (error) {
- *error = [self buildError:@"Invalid file type" code:INVALID_FILE_TYPE];
- }
- return nil;
- }
-
- //Create models directory in app's documents folder
- NSURL *modelsDirectoryURL = [self createDirectoryInDocuments:fileManager];
- if ([@"" isEqualToString: modelsDirectoryURL.path]) {
- if (error) {
- *error = [self buildError:@"Couldn't create models directory" code:WRITE_FAILED];
- }
- return nil;
- }
-
- // Construct the full file path
- NSString *filePath = [modelsDirectoryURL.path stringByAppendingPathComponent:fileName];
-
- //Check if file with extracted file name already exists in models directory if so, return path to it
- if ([fileManager fileExistsAtPath:filePath]) {
- return filePath;
- }
-
- return filePath;
-}
-
-+ (void)fetchResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType completionHandler:(void (^)(NSString *filePath, NSError *error))completionHandler {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error = nil;
- if (![self isValidURL:resourceURL]) {
- if ([self isLocalFilePath:resourceURL] && [self fileExistsAtPath:resourceURL.path]) {
- completionHandler(resourceURL.path, nil);
- return;
- }
- completionHandler(nil, [self buildError:@"The provided URL is invalid" code:INVALID_URL_CODE]);
- return;
- }
-
- NSString *filePath = [self prepareFilePathForResource:resourceURL resourceType:resourceType error:&error];
- if (error) {
- completionHandler(nil, error);
- return;
- }
-
- if ([fileManager fileExistsAtPath:filePath]) {
- completionHandler(filePath, nil);
- return;
- }
-
- NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
- NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
-
- NSURLSessionDataTask *downloadTask = [session dataTaskWithURL:resourceURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *sessionTaskError) {
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
- NSInteger statusCode = httpResponse.statusCode;
-
- if (statusCode != STATUS_OK) {
- completionHandler(nil, [self buildError:@"Couldn't download file" code:DOWNLOAD_FAILED]);
- return;
- }
- if (sessionTaskError) {
- completionHandler(nil, sessionTaskError);
- return;
- }
- if (data) {
- NSError *writeError = nil;
- BOOL success = [data writeToFile:filePath options:NSDataWritingAtomic error:&writeError];
- if (success) {
- completionHandler(filePath, nil);
- } else {
- completionHandler(nil, [self buildError:writeError.localizedDescription code:WRITE_FAILED]);
- }
- } else {
- completionHandler(nil, [self buildError:@"No data received from server" code:NO_DATA]);
- }
- }];
-
- [downloadTask resume];
-}
-@end
diff --git a/ios/RnExecutorch/utils/LargeFileFetcher.h b/ios/RnExecutorch/utils/LargeFileFetcher.h
deleted file mode 100644
index ae2cf100c8..0000000000
--- a/ios/RnExecutorch/utils/LargeFileFetcher.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// Downloader.h
-
-#import
-
-@interface LargeFileFetcher : NSObject
-
-@property(nonatomic, copy) void (^onProgress)(NSNumber *);
-@property(nonatomic, copy) void (^onFinish)(NSString *);
-@property(nonatomic, copy) void (^onFailure)(NSError *);
-
-- (void)startDownloadingFileFromURL:(NSURL *)url;
-
-@end
diff --git a/ios/RnExecutorch/utils/LargeFileFetcher.mm b/ios/RnExecutorch/utils/LargeFileFetcher.mm
deleted file mode 100644
index 48cc39b94a..0000000000
--- a/ios/RnExecutorch/utils/LargeFileFetcher.mm
+++ /dev/null
@@ -1,127 +0,0 @@
-#import "LargeFileFetcher.h"
-#import "Fetcher.h"
-#import
-
-@implementation LargeFileFetcher {
- NSURLSession *_session;
- NSURL *_fileURL;
- NSString *_fileName;
- NSString *_destination;
-}
-
-- (instancetype)init {
- self = [super init];
- if (self) {
- NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:@"com.swmansion.rnexecutorch.%@", [[NSUUID UUID] UUIDString]]];
- _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
- }
- return self;
-}
-
-- (void)executeCompletionWithSuccess:(NSString *)filePath {
- if (self.onFinish) {
- dispatch_async(dispatch_get_main_queue(), ^{
- self.onFinish(filePath);
- });
- }
-}
-
-- (void)executeFailureWithMessage:(NSString *)message code:(NSInteger)code {
- if (self.onFailure) {
- NSError *error = [Fetcher buildError:message code:code];
- dispatch_async(dispatch_get_main_queue(), ^{
- self.onFailure(error);
- });
- }
-}
-
-- (void)cancelUnfinishedTasks {
- [_session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
-
- for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
- [downloadTask cancel];
- }
- }];
-}
-
-- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
- double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
-
- if(self.onProgress){
- dispatch_async(dispatch_get_main_queue(), ^{
- self.onProgress(@(progress));
- });
- }
-}
-
-- (void)sendHeadRequestToURL:(NSURL *)url {
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
- [request setHTTPMethod:@"HEAD"];
- NSURLSessionDataTask *dataTask = [_session dataTaskWithRequest:request];
- [dataTask resume];
-}
-
-- (void)startDownloadingFileFromURL:(NSURL *)url {
- //Check if file is a valid url, if not check if it's path to local file
- if (![Fetcher isValidURL:url]) {
- if([Fetcher isLocalFilePath:url] && [Fetcher fileExistsAtPath:url.path]){
- [self executeCompletionWithSuccess:url.path];
- return;
- }
- [self executeFailureWithMessage:@"The provided URL is invalid" code:INVALID_URL_CODE];
- return;
- }
-
- //Check if file with extracted file name already exists in models directory if so, return path to it
- NSError *error;
- NSString *filePath = [Fetcher prepareFilePathForResource:url resourceType:ResourceType::MODEL error:&error];
- if (error) {
- [self executeFailureWithMessage:error.localizedDescription code:error.code];
- return;
- }
-
- if([[NSFileManager defaultManager] fileExistsAtPath: filePath]){
- [self executeCompletionWithSuccess:filePath];
- return;
- }
-
- // If the url is a Software Mansion HuggingFace repo, we want to send a HEAD
- // request to the config.json file, this increments HF download counter
- // https://huggingface.co/docs/hub/models-download-stats
- NSString *huggingFaceOrgNSString = @"https://huggingface.co/software-mansion/";
- NSString *modelURLNSString = [url absoluteString];
-
- if ([modelURLNSString hasPrefix:huggingFaceOrgNSString]) {
- NSRange resolveRange = [modelURLNSString rangeOfString:@"resolve"];
- if (resolveRange.location != NSNotFound) {
- NSString *configURLNSString = [modelURLNSString substringToIndex:resolveRange.location + resolveRange.length];
- configURLNSString = [configURLNSString stringByAppendingString:@"/main/config.json"];
- NSURL *configNSURL = [NSURL URLWithString:configURLNSString];
- [self sendHeadRequestToURL:configNSURL];
- }
- }
-
- //Cancel all running background download tasks and start new one
- _destination = filePath;
- [self cancelUnfinishedTasks];
- NSURLRequest *request = [NSURLRequest requestWithURL:url];
- NSURLSessionDownloadTask *downloadTask = [_session downloadTaskWithRequest:request];
- [downloadTask resume];
-}
-
-- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
- NSFileManager *fileManager = [NSFileManager defaultManager];
-
- [fileManager removeItemAtPath:_destination error:nil];
-
- NSError *error;
- if ([fileManager moveItemAtPath:location.path toPath:_destination error:&error]) {
- [self executeCompletionWithSuccess:_destination];
- return;
- } else {
- [self executeFailureWithMessage:@"Failed to write file to disk" code:WRITE_FAILED];
- return;
- }
-}
-
-@end
diff --git a/package.json b/package.json
index 9947d4cb95..23f5a0b349 100644
--- a/package.json
+++ b/package.json
@@ -191,5 +191,9 @@
"android": {
"javaPackageName": "com.swmansion.rnexecutorch"
}
+ },
+ "dependencies": {
+ "expo-asset": "^11.0.3",
+ "expo-file-system": "^18.0.10"
}
}
diff --git a/src/constants/directories.ts b/src/constants/directories.ts
new file mode 100644
index 0000000000..ac20d04d8f
--- /dev/null
+++ b/src/constants/directories.ts
@@ -0,0 +1,3 @@
+import { documentDirectory } from 'expo-file-system';
+
+export const RNEDirectory = `${documentDirectory}react-native-executorch/`;
diff --git a/src/hooks/computer_vision/useClassification.ts b/src/hooks/computer_vision/useClassification.ts
index 836a0fc938..0cc8a1dccd 100644
--- a/src/hooks/computer_vision/useClassification.ts
+++ b/src/hooks/computer_vision/useClassification.ts
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { _ClassificationModule } from '../../native/RnExecutorchModules';
-import { useModule } from '../../useModule';
+import { useModule } from '../useModule';
interface Props {
modelSource: string | number;
@@ -12,6 +12,7 @@ export const useClassification = ({
error: string | null;
isReady: boolean;
isGenerating: boolean;
+ downloadProgress: number;
forward: (input: string) => Promise<{ [category: string]: number }>;
} => {
const [module, _] = useState(() => new _ClassificationModule());
@@ -19,11 +20,12 @@ export const useClassification = ({
error,
isReady,
isGenerating,
+ downloadProgress,
forwardImage: forward,
} = useModule({
modelSource,
module,
});
- return { error, isReady, isGenerating, forward };
+ return { error, isReady, isGenerating, downloadProgress, forward };
};
diff --git a/src/hooks/computer_vision/useObjectDetection.ts b/src/hooks/computer_vision/useObjectDetection.ts
index 8456ee338a..b5082540a2 100644
--- a/src/hooks/computer_vision/useObjectDetection.ts
+++ b/src/hooks/computer_vision/useObjectDetection.ts
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { _ObjectDetectionModule } from '../../native/RnExecutorchModules';
-import { useModule } from '../../useModule';
+import { useModule } from '../useModule';
import { Detection } from '../../types/object_detection';
interface Props {
@@ -13,6 +13,7 @@ export const useObjectDetection = ({
error: string | null;
isReady: boolean;
isGenerating: boolean;
+ downloadProgress: number;
forward: (input: string) => Promise;
} => {
const [module, _] = useState(() => new _ObjectDetectionModule());
@@ -20,11 +21,12 @@ export const useObjectDetection = ({
error,
isReady,
isGenerating,
+ downloadProgress,
forwardImage: forward,
} = useModule({
modelSource,
module,
});
- return { error, isReady, isGenerating, forward };
+ return { error, isReady, isGenerating, downloadProgress, forward };
};
diff --git a/src/hooks/computer_vision/useStyleTransfer.ts b/src/hooks/computer_vision/useStyleTransfer.ts
index 20c400b41e..b7dcbaca0d 100644
--- a/src/hooks/computer_vision/useStyleTransfer.ts
+++ b/src/hooks/computer_vision/useStyleTransfer.ts
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { _StyleTransferModule } from '../../native/RnExecutorchModules';
-import { useModule } from '../../useModule';
+import { useModule } from '../useModule';
interface Props {
modelSource: string | number;
@@ -12,6 +12,7 @@ export const useStyleTransfer = ({
error: string | null;
isReady: boolean;
isGenerating: boolean;
+ downloadProgress: number;
forward: (input: string) => Promise;
} => {
const [module, _] = useState(() => new _StyleTransferModule());
@@ -19,11 +20,12 @@ export const useStyleTransfer = ({
error,
isReady,
isGenerating,
+ downloadProgress,
forwardImage: forward,
} = useModule({
modelSource,
module,
});
- return { error, isReady, isGenerating, forward };
+ return { error, isReady, isGenerating, downloadProgress, forward };
};
diff --git a/src/hooks/general/useExecutorchModule.ts b/src/hooks/general/useExecutorchModule.ts
index 5a180fdff0..99e2a2fcf8 100644
--- a/src/hooks/general/useExecutorchModule.ts
+++ b/src/hooks/general/useExecutorchModule.ts
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { _ETModule } from '../../native/RnExecutorchModules';
-import { useModule } from '../../useModule';
+import { useModule } from '../useModule';
import { ETInput } from '../../types/common';
import { getError } from '../../Error';
@@ -14,6 +14,7 @@ export const useExecutorchModule = ({
error: string | null;
isReady: boolean;
isGenerating: boolean;
+ downloadProgress: number;
forward: (input: ETInput, shape: number[]) => Promise;
loadMethod: (methodName: string) => Promise;
loadForward: () => Promise;
@@ -23,6 +24,7 @@ export const useExecutorchModule = ({
error,
isReady,
isGenerating,
+ downloadProgress,
forwardETInput: forward,
} = useModule({
modelSource,
@@ -45,6 +47,7 @@ export const useExecutorchModule = ({
error,
isReady,
isGenerating,
+ downloadProgress,
forward,
loadMethod,
loadForward,
diff --git a/src/hooks/natural_language_processing/useLLM.ts b/src/hooks/natural_language_processing/useLLM.ts
index 386750c459..5864d40bc4 100644
--- a/src/hooks/natural_language_processing/useLLM.ts
+++ b/src/hooks/natural_language_processing/useLLM.ts
@@ -1,12 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react';
-import { EventSubscription, Image } from 'react-native';
+import { EventSubscription } from 'react-native';
+import { LLM } from '../../native/RnExecutorchModules';
+import { fetchResource } from '../../utils/fetchResource';
import { ResourceSource, Model } from '../../types/common';
import {
DEFAULT_CONTEXT_WINDOW_LENGTH,
DEFAULT_SYSTEM_PROMPT,
EOT_TOKEN,
} from '../../constants/llamaDefaults';
-import { LLM } from '../../native/RnExecutorchModules';
const interrupt = () => {
LLM.interrupt();
@@ -28,33 +29,22 @@ export const useLLM = ({
const [isGenerating, setIsGenerating] = useState(false);
const [response, setResponse] = useState('');
const [downloadProgress, setDownloadProgress] = useState(0);
- const downloadProgressListener = useRef(null);
const tokenGeneratedListener = useRef(null);
useEffect(() => {
const loadModel = async () => {
try {
- let modelUrl = modelSource;
- let tokenizerUrl = tokenizerSource;
- if (typeof modelSource === 'number') {
- modelUrl = Image.resolveAssetSource(modelSource).uri;
- }
- if (typeof tokenizerSource === 'number') {
- tokenizerUrl = Image.resolveAssetSource(tokenizerSource).uri;
- }
+ setIsReady(false);
- downloadProgressListener.current = LLM.onDownloadProgress(
- (data: number) => {
- if (data) {
- setDownloadProgress(data);
- }
- }
+ const tokenizerFileUri = await fetchResource(tokenizerSource);
+ const modelFileUri = await fetchResource(
+ modelSource,
+ setDownloadProgress
);
- setIsReady(false);
await LLM.loadLLM(
- modelUrl as string,
- tokenizerUrl as string,
+ modelFileUri,
+ tokenizerFileUri,
systemPrompt,
contextWindowLength
);
@@ -85,8 +75,6 @@ export const useLLM = ({
loadModel();
return () => {
- downloadProgressListener.current?.remove();
- downloadProgressListener.current = null;
tokenGeneratedListener.current?.remove();
tokenGeneratedListener.current = null;
LLM.deleteModule();
diff --git a/src/useModule.ts b/src/hooks/useModule.ts
similarity index 79%
rename from src/useModule.ts
rename to src/hooks/useModule.ts
index e4080a55f9..bba9638b70 100644
--- a/src/useModule.ts
+++ b/src/hooks/useModule.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
-import { Image } from 'react-native';
-import { ETError, getError } from './Error';
-import { ETInput, Module, getTypeIdentifier } from './types/common';
+import { fetchResource } from '../utils/fetchResource';
+import { ETError, getError } from '../Error';
+import { ETInput, Module, getTypeIdentifier } from '../types/common';
interface Props {
modelSource: string | number;
@@ -12,6 +12,7 @@ interface _Module {
error: string | null;
isReady: boolean;
isGenerating: boolean;
+ downloadProgress: number;
forwardETInput: (input: ETInput, shape: number[]) => Promise;
forwardImage: (input: string) => Promise;
}
@@ -20,19 +21,16 @@ export const useModule = ({ modelSource, module }: Props): _Module => {
const [error, setError] = useState(null);
const [isReady, setIsReady] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
+ const [downloadProgress, setDownloadProgress] = useState(0);
useEffect(() => {
const loadModel = async () => {
if (!modelSource) return;
- let path = modelSource;
-
- if (typeof modelSource === 'number') {
- path = Image.resolveAssetSource(modelSource).uri;
- }
try {
setIsReady(false);
- await module.loadModule(path);
+ const fileUri = await fetchResource(modelSource, setDownloadProgress);
+ await module.loadModule(fileUri);
setIsReady(true);
} catch (e) {
setError(getError(e));
@@ -86,5 +84,12 @@ export const useModule = ({ modelSource, module }: Props): _Module => {
}
};
- return { error, isReady, isGenerating, forwardETInput, forwardImage };
+ return {
+ error,
+ isReady,
+ isGenerating,
+ downloadProgress,
+ forwardETInput,
+ forwardImage,
+ };
};
diff --git a/src/index.tsx b/src/index.tsx
index 1c21b25e08..ec7dd8c128 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -16,6 +16,9 @@ export * from './modules/natural_language_processing/LLMModule';
export * from './modules/general/ExecutorchModule';
+// utils
+export * from './utils/listDownloadedResources';
+
// types
export * from './types/object_detection';
diff --git a/src/modules/BaseModule.ts b/src/modules/BaseModule.ts
index a93591cee1..e977836f77 100644
--- a/src/modules/BaseModule.ts
+++ b/src/modules/BaseModule.ts
@@ -1,10 +1,10 @@
-import { Image } from 'react-native';
import {
_StyleTransferModule,
_ObjectDetectionModule,
_ClassificationModule,
_ETModule,
} from '../native/RnExecutorchModules';
+import { fetchResource } from '../utils/fetchResource';
import { ResourceSource } from '../types/common';
import { getError } from '../Error';
@@ -15,16 +15,17 @@ export class BaseModule {
| _ClassificationModule
| _ETModule;
+ static onDownloadProgressCallback = (_downloadProgress: number) => {};
+
static async load(modelSource: ResourceSource) {
if (!modelSource) return;
- let path =
- typeof modelSource === 'number'
- ? Image.resolveAssetSource(modelSource).uri
- : modelSource;
-
try {
- await this.module.loadModule(path);
+ const fileUri = await fetchResource(
+ modelSource,
+ this.onDownloadProgressCallback
+ );
+ await this.module.loadModule(fileUri);
} catch (e) {
throw new Error(getError(e));
}
@@ -33,4 +34,8 @@ export class BaseModule {
static async forward(..._: any[]): Promise {
throw new Error('The forward method is not implemented.');
}
+
+ static onDownloadProgress(callback: (downloadProgress: number) => void) {
+ this.onDownloadProgressCallback = callback;
+ }
}
diff --git a/src/modules/natural_language_processing/LLMModule.ts b/src/modules/natural_language_processing/LLMModule.ts
index 20914eddd9..7b44e8a90f 100644
--- a/src/modules/natural_language_processing/LLMModule.ts
+++ b/src/modules/natural_language_processing/LLMModule.ts
@@ -1,5 +1,5 @@
import { LLM } from '../../native/RnExecutorchModules';
-import { Image } from 'react-native';
+import { fetchResource } from '../../utils/fetchResource';
import {
DEFAULT_CONTEXT_WINDOW_LENGTH,
DEFAULT_SYSTEM_PROMPT,
@@ -7,6 +7,8 @@ import {
import { ResourceSource } from '../../types/common';
export class LLMModule {
+ static onDownloadProgressCallback = (_downloadProgress: number) => {};
+
static async load(
modelSource: ResourceSource,
tokenizerSource: ResourceSource,
@@ -14,19 +16,15 @@ export class LLMModule {
contextWindowLength = DEFAULT_CONTEXT_WINDOW_LENGTH
) {
try {
- let modelUrl =
- typeof modelSource === 'number'
- ? Image.resolveAssetSource(modelSource).uri
- : modelSource;
-
- let tokenizerUrl =
- typeof tokenizerSource === 'number'
- ? Image.resolveAssetSource(tokenizerSource).uri
- : tokenizerSource;
+ const tokenizerFileUri = await fetchResource(tokenizerSource);
+ const modelFileUri = await fetchResource(
+ modelSource,
+ this.onDownloadProgressCallback
+ );
await LLM.loadLLM(
- modelUrl,
- tokenizerUrl,
+ modelFileUri,
+ tokenizerFileUri,
systemPrompt,
contextWindowLength
);
@@ -43,8 +41,8 @@ export class LLMModule {
}
}
- static onDownloadProgress(callback: (data: number) => void) {
- return LLM.onDownloadProgress(callback);
+ static onDownloadProgress(callback: (downloadProgress: number) => void) {
+ this.onDownloadProgressCallback = callback;
}
static onToken(callback: (data: string | undefined) => void) {
diff --git a/src/native/NativeLLM.ts b/src/native/NativeLLM.ts
index 35d2a42087..b610e30e39 100644
--- a/src/native/NativeLLM.ts
+++ b/src/native/NativeLLM.ts
@@ -14,7 +14,6 @@ export interface Spec extends TurboModule {
deleteModule(): void;
readonly onToken: EventEmitter;
- readonly onDownloadProgress: EventEmitter;
}
export default TurboModuleRegistry.get('LLM');
diff --git a/src/utils/fetchResource.ts b/src/utils/fetchResource.ts
new file mode 100644
index 0000000000..9885758ee5
--- /dev/null
+++ b/src/utils/fetchResource.ts
@@ -0,0 +1,82 @@
+import {
+ cacheDirectory,
+ createDownloadResumable,
+ getInfoAsync,
+ makeDirectoryAsync,
+ moveAsync,
+ FileSystemSessionType,
+} from 'expo-file-system';
+import { Asset } from 'expo-asset';
+import { RNEDirectory } from '../constants/directories';
+
+const getFilenameFromUri = (uri: string) => {
+ const filename = uri.split('/').pop()?.split('?')[0];
+ if (!filename) {
+ throw new Error('Cannot derive filename from URI');
+ }
+ return filename;
+};
+
+/**
+ * Increments the Hugging Face download counter if the URI points to a Software Mansion Hugging Face repo.
+ * More information: https://huggingface.co/docs/hub/models-download-stats
+ */
+const triggerHuggingFaceDownloadCounter = (uri: string) => {
+ const url = new URL(uri);
+ if (
+ url.host === 'huggingface.co' &&
+ url.pathname.startsWith('/software-mansion/')
+ ) {
+ const baseUrl = `${url.protocol}//${url.host}${url.pathname.split('resolve')[0]}`;
+ fetch(`${baseUrl}resolve/main/config.json`, { method: 'HEAD' });
+ }
+};
+
+export const fetchResource = async (
+ source: string | number,
+ callback: (downloadProgress: number) => void = () => {}
+) => {
+ const uri =
+ typeof source === 'number' ? Asset.fromModule(source).uri : source;
+ const filename = getFilenameFromUri(uri);
+ const fileUri = `${RNEDirectory}${filename}`;
+
+ // Check if the file already exists
+ if ((await getInfoAsync(fileUri)).exists) {
+ return fileUri;
+ }
+
+ // Create the RNEDirectory if it doesn't exist
+ if (!(await getInfoAsync(RNEDirectory)).exists) {
+ await makeDirectoryAsync(RNEDirectory, { intermediates: true });
+ }
+
+ // Handle local asset files in release mode
+ if (!uri.includes('://')) {
+ const asset = Asset.fromModule(source);
+ const fileUriWithType = `${fileUri}.${asset.type}`;
+ await asset.downloadAsync();
+ await moveAsync({ from: asset.localUri!, to: fileUriWithType });
+ return fileUriWithType;
+ }
+
+ // Handle remote file download
+ const cacheFileUri = `${cacheDirectory}${filename}`;
+ const downloadResumable = createDownloadResumable(
+ uri,
+ cacheFileUri,
+ { sessionType: FileSystemSessionType.BACKGROUND },
+ ({ totalBytesWritten, totalBytesExpectedToWrite }) => {
+ callback(totalBytesWritten / totalBytesExpectedToWrite);
+ }
+ );
+ const result = await downloadResumable.downloadAsync();
+ if (!result || result.status !== 200) {
+ throw new Error(`Failed to fetch resource from '${uri}'`);
+ }
+ await moveAsync({ from: cacheFileUri, to: fileUri });
+
+ triggerHuggingFaceDownloadCounter(uri);
+
+ return fileUri;
+};
diff --git a/src/utils/listDownloadedResources.ts b/src/utils/listDownloadedResources.ts
new file mode 100644
index 0000000000..44cb74f1f0
--- /dev/null
+++ b/src/utils/listDownloadedResources.ts
@@ -0,0 +1,12 @@
+import { readDirectoryAsync } from 'expo-file-system';
+import { RNEDirectory } from '../constants/directories';
+
+export const listDownloadedFiles = async () => {
+ const files = await readDirectoryAsync(RNEDirectory);
+ return files.map((file) => `${RNEDirectory}${file}`);
+};
+
+export const listDownloadedModels = async () => {
+ const files = await listDownloadedFiles();
+ return files.filter((file) => file.endsWith('.pte'));
+};
diff --git a/yarn.lock b/yarn.lock
index fa968b1bdf..77c1e49cbb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -26,6 +26,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/code-frame@npm:~7.10.4":
+ version: 7.10.4
+ resolution: "@babel/code-frame@npm:7.10.4"
+ dependencies:
+ "@babel/highlight": ^7.10.4
+ checksum: feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019
+ languageName: node
+ linkType: hard
+
"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.9, @babel/compat-data@npm:^7.26.0":
version: 7.26.2
resolution: "@babel/compat-data@npm:7.26.2"
@@ -306,6 +315,18 @@ __metadata:
languageName: node
linkType: hard
+"@babel/highlight@npm:^7.10.4":
+ version: 7.25.9
+ resolution: "@babel/highlight@npm:7.25.9"
+ dependencies:
+ "@babel/helper-validator-identifier": ^7.25.9
+ chalk: ^2.4.2
+ js-tokens: ^4.0.0
+ picocolors: ^1.0.0
+ checksum: a6e0ac0a1c4bef7401915ca3442ab2b7ae4adf360262ca96b91396bfb9578abb28c316abf5e34460b780696db833b550238d9256bdaca60fade4ba7a67645064
+ languageName: node
+ linkType: hard
+
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2":
version: 7.26.2
resolution: "@babel/parser@npm:7.26.2"
@@ -1904,6 +1925,125 @@ __metadata:
languageName: node
linkType: hard
+"@expo/config-plugins@npm:~9.0.15":
+ version: 9.0.15
+ resolution: "@expo/config-plugins@npm:9.0.15"
+ dependencies:
+ "@expo/config-types": ^52.0.4
+ "@expo/json-file": ~9.0.1
+ "@expo/plist": ^0.2.1
+ "@expo/sdk-runtime-versions": ^1.0.0
+ chalk: ^4.1.2
+ debug: ^4.3.5
+ getenv: ^1.0.0
+ glob: ^10.4.2
+ resolve-from: ^5.0.0
+ semver: ^7.5.4
+ slash: ^3.0.0
+ slugify: ^1.6.6
+ xcode: ^3.0.1
+ xml2js: 0.6.0
+ checksum: ac45abd0b23048eb1e38eb1dc8c543c713c0066cc5c05a0685267ce4d3f769731ac39dd249fa88b5263a8c02aea920ab3e79c4ad2ac23e05f5b1d77ba1e47ef7
+ languageName: node
+ linkType: hard
+
+"@expo/config-types@npm:^52.0.4":
+ version: 52.0.4
+ resolution: "@expo/config-types@npm:52.0.4"
+ checksum: d9489d0bf5c619d68938673b6fbfa5f4ebe626316e95763f0f396c9e09bff1adc2d00cc814088e2e0039c0b344b57df580439e38bef16ed43d2d5d40c87d453f
+ languageName: node
+ linkType: hard
+
+"@expo/config@npm:~10.0.9":
+ version: 10.0.10
+ resolution: "@expo/config@npm:10.0.10"
+ dependencies:
+ "@babel/code-frame": ~7.10.4
+ "@expo/config-plugins": ~9.0.15
+ "@expo/config-types": ^52.0.4
+ "@expo/json-file": ^9.0.2
+ deepmerge: ^4.3.1
+ getenv: ^1.0.0
+ glob: ^10.4.2
+ require-from-string: ^2.0.2
+ resolve-from: ^5.0.0
+ resolve-workspace-root: ^2.0.0
+ semver: ^7.6.0
+ slugify: ^1.3.4
+ sucrase: 3.35.0
+ checksum: ba9c4a4eaa714824ecc88d27df09bef532268abcad25fd06cc79dfbb8f592e591d3d3afd3288041535be94155536eb5f0c65ab718f661f2d16cbcb881b003a71
+ languageName: node
+ linkType: hard
+
+"@expo/env@npm:~0.4.1":
+ version: 0.4.2
+ resolution: "@expo/env@npm:0.4.2"
+ dependencies:
+ chalk: ^4.0.0
+ debug: ^4.3.4
+ dotenv: ~16.4.5
+ dotenv-expand: ~11.0.6
+ getenv: ^1.0.0
+ checksum: cc9264e50faf5f38e6253b5c97e775bc8cb29bf8ca37bcd427cbb67dd773a4e62a2bdb030904565bac4644eac89e10fc61206d5aa42e5b1f26acf5ca1f6b9ce9
+ languageName: node
+ linkType: hard
+
+"@expo/image-utils@npm:^0.6.4":
+ version: 0.6.5
+ resolution: "@expo/image-utils@npm:0.6.5"
+ dependencies:
+ "@expo/spawn-async": ^1.7.2
+ chalk: ^4.0.0
+ fs-extra: 9.0.0
+ getenv: ^1.0.0
+ jimp-compact: 0.16.1
+ parse-png: ^2.1.0
+ resolve-from: ^5.0.0
+ semver: ^7.6.0
+ temp-dir: ~2.0.0
+ unique-string: ~2.0.0
+ checksum: f6fe5efd518d84463d767a4fb8a920d8b70779c8d93ba07ef407e0f016452324e3da6cff8292d0e2b436facdaef0073b8d527881e73ff5ba0288b4c942cdb539
+ languageName: node
+ linkType: hard
+
+"@expo/json-file@npm:^9.0.2, @expo/json-file@npm:~9.0.1":
+ version: 9.0.2
+ resolution: "@expo/json-file@npm:9.0.2"
+ dependencies:
+ "@babel/code-frame": ~7.10.4
+ json5: ^2.2.3
+ write-file-atomic: ^2.3.0
+ checksum: 665fb72028e403adcb3ff9d7763ff6fab0ce16eaa1485a6b502daaab709608a9953599cce2f5c46e91b4791bd2380c87eb911deef4161b9d1f3a7631c2630366
+ languageName: node
+ linkType: hard
+
+"@expo/plist@npm:^0.2.1":
+ version: 0.2.2
+ resolution: "@expo/plist@npm:0.2.2"
+ dependencies:
+ "@xmldom/xmldom": ~0.7.7
+ base64-js: ^1.2.3
+ xmlbuilder: ^14.0.0
+ checksum: ccc8256f07352e327092132d885c3e2291f14b3ef6060065eb11080f130a575012cfff7ae92c579b5e04cc6b2587930caed70e277c2f1f5b63591e39366e659a
+ languageName: node
+ linkType: hard
+
+"@expo/sdk-runtime-versions@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "@expo/sdk-runtime-versions@npm:1.0.0"
+ checksum: 0942d5a356f590e8dc795761456cc48b3e2d6a38ad2a02d6774efcdc5a70424e05623b4e3e5d2fec0cdc30f40dde05c14391c781607eed3971bf8676518bfd9d
+ languageName: node
+ linkType: hard
+
+"@expo/spawn-async@npm:^1.7.2":
+ version: 1.7.2
+ resolution: "@expo/spawn-async@npm:1.7.2"
+ dependencies:
+ cross-spawn: ^7.0.3
+ checksum: d99e5ff6d303ec9b0105f97c4fa6c65bca526c7d4d0987997c35cc745fa8224adf009942d01808192ebb9fa30619a53316641958631e85cf17b773d9eeda2597
+ languageName: node
+ linkType: hard
+
"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0":
version: 9.3.0
resolution: "@hapi/hoek@npm:9.3.0"
@@ -2245,6 +2385,17 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/gen-mapping@npm:^0.3.2":
+ version: 0.3.8
+ resolution: "@jridgewell/gen-mapping@npm:0.3.8"
+ dependencies:
+ "@jridgewell/set-array": ^1.2.1
+ "@jridgewell/sourcemap-codec": ^1.4.10
+ "@jridgewell/trace-mapping": ^0.3.24
+ checksum: c0687b5227461717aa537fe71a42e356bcd1c43293b3353796a148bf3b0d6f59109def46c22f05b60e29a46f19b2e4676d027959a7c53a6c92b9d5b0d87d0420
+ languageName: node
+ linkType: hard
+
"@jridgewell/gen-mapping@npm:^0.3.5":
version: 0.3.5
resolution: "@jridgewell/gen-mapping@npm:0.3.5"
@@ -3195,6 +3346,20 @@ __metadata:
languageName: node
linkType: hard
+"@xmldom/xmldom@npm:^0.8.8":
+ version: 0.8.10
+ resolution: "@xmldom/xmldom@npm:0.8.10"
+ checksum: 4c136aec31fb3b49aaa53b6fcbfe524d02a1dc0d8e17ee35bd3bf35e9ce1344560481cd1efd086ad1a4821541482528672306d5e37cdbd187f33d7fadd3e2cf0
+ languageName: node
+ linkType: hard
+
+"@xmldom/xmldom@npm:~0.7.7":
+ version: 0.7.13
+ resolution: "@xmldom/xmldom@npm:0.7.13"
+ checksum: b4054078530e5fa8ede9677425deff0fce6d965f4c477ca73f8490d8a089e60b8498a15560425a1335f5ff99ecb851ed2c734b0a9a879299a5694302f212f37a
+ languageName: node
+ linkType: hard
+
"JSONStream@npm:^1.0.4, JSONStream@npm:^1.3.5":
version: 1.3.5
resolution: "JSONStream@npm:1.3.5"
@@ -3368,7 +3533,7 @@ __metadata:
languageName: node
linkType: hard
-"ansi-styles@npm:^3.2.0":
+"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1":
version: 3.2.1
resolution: "ansi-styles@npm:3.2.1"
dependencies:
@@ -3400,6 +3565,13 @@ __metadata:
languageName: node
linkType: hard
+"any-promise@npm:^1.0.0":
+ version: 1.3.0
+ resolution: "any-promise@npm:1.3.0"
+ checksum: 0ee8a9bdbe882c90464d75d1f55cf027f5458650c4bd1f0467e65aec38ccccda07ca5844969ee77ed46d04e7dded3eaceb027e8d32f385688523fe305fa7e1de
+ languageName: node
+ linkType: hard
+
"anymatch@npm:^3.0.3":
version: 3.1.3
resolution: "anymatch@npm:3.1.3"
@@ -3582,6 +3754,13 @@ __metadata:
languageName: node
linkType: hard
+"at-least-node@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "at-least-node@npm:1.0.0"
+ checksum: 463e2f8e43384f1afb54bc68485c436d7622acec08b6fad269b421cb1d29cebb5af751426793d0961ed243146fe4dc983402f6d5a51b720b277818dbf6f2e49e
+ languageName: node
+ linkType: hard
+
"available-typed-arrays@npm:^1.0.7":
version: 1.0.7
resolution: "available-typed-arrays@npm:1.0.7"
@@ -3762,13 +3941,20 @@ __metadata:
languageName: node
linkType: hard
-"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
+"base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005
languageName: node
linkType: hard
+"big-integer@npm:1.6.x":
+ version: 1.6.52
+ resolution: "big-integer@npm:1.6.52"
+ checksum: 6e86885787a20fed96521958ae9086960e4e4b5e74d04f3ef7513d4d0ad631a9f3bde2730fc8aaa4b00419fc865f6ec573e5320234531ef37505da7da192c40b
+ languageName: node
+ linkType: hard
+
"bl@npm:^4.1.0":
version: 4.1.0
resolution: "bl@npm:4.1.0"
@@ -3780,6 +3966,24 @@ __metadata:
languageName: node
linkType: hard
+"bplist-creator@npm:0.1.1":
+ version: 0.1.1
+ resolution: "bplist-creator@npm:0.1.1"
+ dependencies:
+ stream-buffers: 2.2.x
+ checksum: b0d40d1d1623f1afdbb575cfc8075d742d2c4f0eb458574be809e3857752d1042a39553b3943d2d7f505dde92bcd43e1d7bdac61c9cd44475d696deb79f897ce
+ languageName: node
+ linkType: hard
+
+"bplist-parser@npm:0.3.2":
+ version: 0.3.2
+ resolution: "bplist-parser@npm:0.3.2"
+ dependencies:
+ big-integer: 1.6.x
+ checksum: fad0f6eb155a9b636b4096a1725ce972a0386490d7d38df7be11a3a5645372446b7c44aacbc6626d24d2c17d8b837765361520ebf2960aeffcaf56765811620e
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
@@ -3831,6 +4035,30 @@ __metadata:
languageName: node
linkType: hard
+"buffer-alloc-unsafe@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "buffer-alloc-unsafe@npm:1.1.0"
+ checksum: c5e18bf51f67754ec843c9af3d4c005051aac5008a3992938dda1344e5cfec77c4b02b4ca303644d1e9a6e281765155ce6356d85c6f5ccc5cd21afc868def396
+ languageName: node
+ linkType: hard
+
+"buffer-alloc@npm:^1.1.0":
+ version: 1.2.0
+ resolution: "buffer-alloc@npm:1.2.0"
+ dependencies:
+ buffer-alloc-unsafe: ^1.1.0
+ buffer-fill: ^1.0.0
+ checksum: 560cd27f3cbe73c614867da373407d4506309c62fe18de45a1ce191f3785ec6ca2488d802ff82065798542422980ca25f903db078c57822218182c37c3576df5
+ languageName: node
+ linkType: hard
+
+"buffer-fill@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "buffer-fill@npm:1.0.0"
+ checksum: c29b4723ddeab01e74b5d3b982a0c6828f2ded49cef049ddca3dac661c874ecdbcecb5dd8380cf0f4adbeb8cff90a7de724126750a1f1e5ebd4eb6c59a1315b1
+ languageName: node
+ linkType: hard
+
"buffer-from@npm:^1.0.0":
version: 1.1.2
resolution: "buffer-from@npm:1.1.2"
@@ -3964,6 +4192,17 @@ __metadata:
languageName: node
linkType: hard
+"chalk@npm:^2.4.2":
+ version: 2.4.2
+ resolution: "chalk@npm:2.4.2"
+ dependencies:
+ ansi-styles: ^3.2.1
+ escape-string-regexp: ^1.0.5
+ supports-color: ^5.3.0
+ checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2
+ languageName: node
+ linkType: hard
+
"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
@@ -4194,6 +4433,13 @@ __metadata:
languageName: node
linkType: hard
+"commander@npm:^4.0.0":
+ version: 4.1.1
+ resolution: "commander@npm:4.1.1"
+ checksum: d7b9913ff92cae20cb577a4ac6fcc121bd6223319e54a40f51a14740a681ad5c574fd29a57da478a5f234a6fa6c52cbf0b7c641353e03c648b1ae85ba670b977
+ languageName: node
+ linkType: hard
+
"commander@npm:^9.4.1":
version: 9.5.0
resolution: "commander@npm:9.5.0"
@@ -4629,6 +4875,13 @@ __metadata:
languageName: node
linkType: hard
+"crypto-random-string@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "crypto-random-string@npm:2.0.0"
+ checksum: 0283879f55e7c16fdceacc181f87a0a65c53bc16ffe1d58b9d19a6277adcd71900d02bb2c4843dd55e78c51e30e89b0fec618a7f170ebcc95b33182c28f05fd6
+ languageName: node
+ linkType: hard
+
"csstype@npm:^3.0.2":
version: 3.1.3
resolution: "csstype@npm:3.1.3"
@@ -4711,6 +4964,18 @@ __metadata:
languageName: node
linkType: hard
+"debug@npm:^4.3.5":
+ version: 4.4.0
+ resolution: "debug@npm:4.4.0"
+ dependencies:
+ ms: ^2.1.3
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479
+ languageName: node
+ linkType: hard
+
"decamelize-keys@npm:^1.1.0":
version: 1.1.1
resolution: "decamelize-keys@npm:1.1.1"
@@ -4761,7 +5026,7 @@ __metadata:
languageName: node
linkType: hard
-"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0":
+"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0, deepmerge@npm:^4.3.1":
version: 4.3.1
resolution: "deepmerge@npm:4.3.1"
checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052
@@ -4922,6 +5187,22 @@ __metadata:
languageName: node
linkType: hard
+"dotenv-expand@npm:~11.0.6":
+ version: 11.0.7
+ resolution: "dotenv-expand@npm:11.0.7"
+ dependencies:
+ dotenv: ^16.4.5
+ checksum: 58455ad9ffedbf6180b49f8f35596da54f10b02efcaabcba5400363f432e1da057113eee39b42365535da41df1e794d54a4aa67b22b37c41686c3dce4e6a28c5
+ languageName: node
+ linkType: hard
+
+"dotenv@npm:^16.4.5, dotenv@npm:~16.4.5":
+ version: 16.4.7
+ resolution: "dotenv@npm:16.4.7"
+ checksum: c27419b5875a44addcc56cc69b7dc5b0e6587826ca85d5b355da9303c6fc317fc9989f1f18366a16378c9fdd9532d14117a1abe6029cc719cdbbef6eaef2cea4
+ languageName: node
+ linkType: hard
+
"eastasianwidth@npm:^0.2.0":
version: 0.2.0
resolution: "eastasianwidth@npm:0.2.0"
@@ -5586,6 +5867,47 @@ __metadata:
languageName: node
linkType: hard
+"expo-asset@npm:^11.0.3":
+ version: 11.0.3
+ resolution: "expo-asset@npm:11.0.3"
+ dependencies:
+ "@expo/image-utils": ^0.6.4
+ expo-constants: ~17.0.5
+ invariant: ^2.2.4
+ md5-file: ^3.2.3
+ peerDependencies:
+ expo: "*"
+ react: "*"
+ react-native: "*"
+ checksum: e22714d0277dd7c3f8ece9ebc6e6475fb4fd96aa54c4ff42878837aef62f2d334232cd7a5afded0b5c55f18a7d4fa2737bce7920666525112c5ceee1d7e5c4fd
+ languageName: node
+ linkType: hard
+
+"expo-constants@npm:~17.0.5":
+ version: 17.0.6
+ resolution: "expo-constants@npm:17.0.6"
+ dependencies:
+ "@expo/config": ~10.0.9
+ "@expo/env": ~0.4.1
+ peerDependencies:
+ expo: "*"
+ react-native: "*"
+ checksum: c4719a9c2eaa2c3aaa447efee0a3a50320a68f1106b5cf581139ad0d64585edfaad082218f9cb506239958a7fb96d261e95dcbeb8fd23aa5941fc2e169314a00
+ languageName: node
+ linkType: hard
+
+"expo-file-system@npm:^18.0.10":
+ version: 18.0.10
+ resolution: "expo-file-system@npm:18.0.10"
+ dependencies:
+ web-streams-polyfill: ^3.3.2
+ peerDependencies:
+ expo: "*"
+ react-native: "*"
+ checksum: 86884a5046267c4a58ee8f283fe2e6f4b8dec6666f31c3db2f7460d4ebc792f18d42ef7d33c9f5da047d626343538fec707598983f15e7452775767ff3e72035
+ languageName: node
+ linkType: hard
+
"exponential-backoff@npm:^3.1.1":
version: 3.1.1
resolution: "exponential-backoff@npm:3.1.1"
@@ -5819,6 +6141,18 @@ __metadata:
languageName: node
linkType: hard
+"fs-extra@npm:9.0.0":
+ version: 9.0.0
+ resolution: "fs-extra@npm:9.0.0"
+ dependencies:
+ at-least-node: ^1.0.0
+ graceful-fs: ^4.2.0
+ jsonfile: ^6.0.1
+ universalify: ^1.0.0
+ checksum: c4269fbfd8d8d2a1edca4257fa28545caf7e5ad218d264f723c338a154d3624d2ef098c19915b9436d3186b7ac45d5b032371a2004008ec0cd4072512e853aa8
+ languageName: node
+ linkType: hard
+
"fs-extra@npm:^10.1.0":
version: 10.1.0
resolution: "fs-extra@npm:10.1.0"
@@ -5997,6 +6331,13 @@ __metadata:
languageName: node
linkType: hard
+"getenv@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "getenv@npm:1.0.0"
+ checksum: 19ae5cad603a1cf1bcb8fa3bed48e00d062eb0572a4404c02334b67f3b3499f238383082b064bb42515e9e25c2b08aef1a3e3d2b6852347721aa8b174825bd56
+ languageName: node
+ linkType: hard
+
"git-raw-commits@npm:^2.0.11, git-raw-commits@npm:^2.0.8":
version: 2.0.11
resolution: "git-raw-commits@npm:2.0.11"
@@ -6061,7 +6402,7 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:^10.2.2, glob@npm:^10.3.10":
+"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.2":
version: 10.4.5
resolution: "glob@npm:10.4.5"
dependencies:
@@ -6233,6 +6574,13 @@ __metadata:
languageName: node
linkType: hard
+"has-flag@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "has-flag@npm:3.0.0"
+ checksum: 4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b
+ languageName: node
+ linkType: hard
+
"has-flag@npm:^4.0.0":
version: 4.0.0
resolution: "has-flag@npm:4.0.0"
@@ -7530,6 +7878,13 @@ __metadata:
languageName: node
linkType: hard
+"jimp-compact@npm:0.16.1":
+ version: 0.16.1
+ resolution: "jimp-compact@npm:0.16.1"
+ checksum: 5a1c62d70881b31f79ea65fecfe03617be0eb56139bc451f37e8972365c99ac3b52c5176c446ff27144c98ab664a99107ae08d347044e94e1de637f165b41a57
+ languageName: node
+ linkType: hard
+
"joi@npm:^17.2.1":
version: 17.13.3
resolution: "joi@npm:17.13.3"
@@ -8083,6 +8438,17 @@ __metadata:
languageName: node
linkType: hard
+"md5-file@npm:^3.2.3":
+ version: 3.2.3
+ resolution: "md5-file@npm:3.2.3"
+ dependencies:
+ buffer-alloc: ^1.1.0
+ bin:
+ md5-file: cli.js
+ checksum: a3738274ee0c5ce21e7c14a4b60e5de6b298740f8a37eeb502bb97a056e3f19ea0871418b4dd45ca9c70d2f1d6c79a19e9a320fba1c129b196cdf671e544c450
+ languageName: node
+ linkType: hard
+
"memoize-one@npm:^5.0.0":
version: 5.2.1
resolution: "memoize-one@npm:5.2.1"
@@ -8908,6 +9274,17 @@ __metadata:
languageName: node
linkType: hard
+"mz@npm:^2.7.0":
+ version: 2.7.0
+ resolution: "mz@npm:2.7.0"
+ dependencies:
+ any-promise: ^1.0.0
+ object-assign: ^4.0.1
+ thenify-all: ^1.0.0
+ checksum: 8427de0ece99a07e9faed3c0c6778820d7543e3776f9a84d22cf0ec0a8eb65f6e9aee9c9d353ff9a105ff62d33a9463c6ca638974cc652ee8140cd1e35951c87
+ languageName: node
+ linkType: hard
+
"natural-compare-lite@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare-lite@npm:1.4.0"
@@ -9104,7 +9481,7 @@ __metadata:
languageName: node
linkType: hard
-"object-assign@npm:^4.1.1":
+"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1":
version: 4.1.1
resolution: "object-assign@npm:4.1.1"
checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f
@@ -9397,6 +9774,15 @@ __metadata:
languageName: node
linkType: hard
+"parse-png@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "parse-png@npm:2.1.0"
+ dependencies:
+ pngjs: ^3.3.0
+ checksum: 0c6b6c42c8830cd16f6f9e9aedafd53111c0ad2ff350ba79c629996887567558f5639ad0c95764f96f7acd1f9ff63d4ac73737e80efa3911a6de9839ee520c96
+ languageName: node
+ linkType: hard
+
"parseurl@npm:~1.3.3":
version: 1.3.3
resolution: "parseurl@npm:1.3.3"
@@ -9500,7 +9886,7 @@ __metadata:
languageName: node
linkType: hard
-"pirates@npm:^4.0.4, pirates@npm:^4.0.6":
+"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6":
version: 4.0.6
resolution: "pirates@npm:4.0.6"
checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6
@@ -9534,6 +9920,24 @@ __metadata:
languageName: node
linkType: hard
+"plist@npm:^3.0.5":
+ version: 3.1.0
+ resolution: "plist@npm:3.1.0"
+ dependencies:
+ "@xmldom/xmldom": ^0.8.8
+ base64-js: ^1.5.1
+ xmlbuilder: ^15.1.1
+ checksum: c8ea013da8646d4c50dff82f9be39488054621cc229957621bb00add42b5d4ce3657cf58d4b10c50f7dea1a81118f825838f838baeb4e6f17fab453ecf91d424
+ languageName: node
+ linkType: hard
+
+"pngjs@npm:^3.3.0":
+ version: 3.4.0
+ resolution: "pngjs@npm:3.4.0"
+ checksum: 8bd40bd698abd16b72c97b85cb858c80894fbedc76277ce72a784aa441e14795d45d9856e97333ca469b34b67528860ffc8a7317ca6beea349b645366df00bcd
+ languageName: node
+ linkType: hard
+
"possible-typed-array-names@npm:^1.0.0":
version: 1.0.0
resolution: "possible-typed-array-names@npm:1.0.0"
@@ -9790,6 +10194,8 @@ __metadata:
eslint: ^8.51.0
eslint-config-prettier: ^9.0.0
eslint-plugin-prettier: ^5.0.1
+ expo-asset: ^11.0.3
+ expo-file-system: ^18.0.10
jest: ^29.7.0
metro-react-native-babel-preset: ^0.77.0
prettier: ^3.0.3
@@ -10178,6 +10584,13 @@ __metadata:
languageName: node
linkType: hard
+"resolve-workspace-root@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "resolve-workspace-root@npm:2.0.0"
+ checksum: c7222391a35ecb3514fa04d753334a86f984d8ffe06ce87506582c4c5671ac608273b8f5e6faa2055be6e196785bf751ede9a48d484de53889d721b814c097ab
+ languageName: node
+ linkType: hard
+
"resolve.exports@npm:^2.0.0":
version: 2.0.2
resolution: "resolve.exports@npm:2.0.2"
@@ -10336,6 +10749,13 @@ __metadata:
languageName: node
linkType: hard
+"sax@npm:>=0.6.0":
+ version: 1.4.1
+ resolution: "sax@npm:1.4.1"
+ checksum: 3ad64df16b743f0f2eb7c38ced9692a6d924f1cd07bbe45c39576c2cf50de8290d9d04e7b2228f924c7d05fecc4ec5cf651423278e0c7b63d260c387ef3af84a
+ languageName: node
+ linkType: hard
+
"scheduler@npm:0.24.0-canary-efb381bbf-20230505":
version: 0.24.0-canary-efb381bbf-20230505
resolution: "scheduler@npm:0.24.0-canary-efb381bbf-20230505"
@@ -10404,6 +10824,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.6.0":
+ version: 7.7.1
+ resolution: "semver@npm:7.7.1"
+ bin:
+ semver: bin/semver.js
+ checksum: 586b825d36874007c9382d9e1ad8f93888d8670040add24a28e06a910aeebd673a2eb9e3bf169c6679d9245e66efb9057e0852e70d9daa6c27372aab1dda7104
+ languageName: node
+ linkType: hard
+
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
@@ -10542,6 +10971,17 @@ __metadata:
languageName: node
linkType: hard
+"simple-plist@npm:^1.1.0":
+ version: 1.4.0
+ resolution: "simple-plist@npm:1.4.0"
+ dependencies:
+ bplist-creator: 0.1.1
+ bplist-parser: 0.3.2
+ plist: ^3.0.5
+ checksum: fa8086f6b781c289f1abad21306481dda4af6373b32a5d998a70e53c2b7218a1d21ebb5ae3e736baae704c21d311d3d39d01d0e6a2387eda01b4020b9ebd909e
+ languageName: node
+ linkType: hard
+
"sisteransi@npm:^1.0.5":
version: 1.0.5
resolution: "sisteransi@npm:1.0.5"
@@ -10574,6 +11014,13 @@ __metadata:
languageName: node
linkType: hard
+"slugify@npm:^1.3.4, slugify@npm:^1.6.6":
+ version: 1.6.6
+ resolution: "slugify@npm:1.6.6"
+ checksum: 04773c2d3b7aea8d2a61fa47cc7e5d29ce04e1a96cbaec409da57139df906acb3a449fac30b167d203212c806e73690abd4ff94fbad0a9a7b7ea109a2a638ae9
+ languageName: node
+ linkType: hard
+
"smart-buffer@npm:^4.2.0":
version: 4.2.0
resolution: "smart-buffer@npm:4.2.0"
@@ -10750,6 +11197,13 @@ __metadata:
languageName: node
linkType: hard
+"stream-buffers@npm:2.2.x":
+ version: 2.2.0
+ resolution: "stream-buffers@npm:2.2.0"
+ checksum: 4587d9e8f050d689fb38b4295e73408401b16de8edecc12026c6f4ae92956705ecfd995ae3845d7fa3ebf19502d5754df9143d91447fd881d86e518f43882c1c
+ languageName: node
+ linkType: hard
+
"string-length@npm:^4.0.1":
version: 4.0.2
resolution: "string-length@npm:4.0.2"
@@ -10951,6 +11405,24 @@ __metadata:
languageName: node
linkType: hard
+"sucrase@npm:3.35.0":
+ version: 3.35.0
+ resolution: "sucrase@npm:3.35.0"
+ dependencies:
+ "@jridgewell/gen-mapping": ^0.3.2
+ commander: ^4.0.0
+ glob: ^10.3.10
+ lines-and-columns: ^1.1.6
+ mz: ^2.7.0
+ pirates: ^4.0.1
+ ts-interface-checker: ^0.1.9
+ bin:
+ sucrase: bin/sucrase
+ sucrase-node: bin/sucrase-node
+ checksum: 9fc5792a9ab8a14dcf9c47dcb704431d35c1cdff1d17d55d382a31c2e8e3063870ad32ce120a80915498486246d612e30cda44f1624d9d9a10423e1a43487ad1
+ languageName: node
+ linkType: hard
+
"sudo-prompt@npm:^9.0.0":
version: 9.2.1
resolution: "sudo-prompt@npm:9.2.1"
@@ -10958,6 +11430,15 @@ __metadata:
languageName: node
linkType: hard
+"supports-color@npm:^5.3.0":
+ version: 5.5.0
+ resolution: "supports-color@npm:5.5.0"
+ dependencies:
+ has-flag: ^3.0.0
+ checksum: 95f6f4ba5afdf92f495b5a912d4abee8dcba766ae719b975c56c084f5004845f6f5a5f7769f52d53f40e21952a6d87411bafe34af4a01e65f9926002e38e1dac
+ languageName: node
+ linkType: hard
+
"supports-color@npm:^7.1.0":
version: 7.2.0
resolution: "supports-color@npm:7.2.0"
@@ -11007,6 +11488,13 @@ __metadata:
languageName: node
linkType: hard
+"temp-dir@npm:~2.0.0":
+ version: 2.0.0
+ resolution: "temp-dir@npm:2.0.0"
+ checksum: cc4f0404bf8d6ae1a166e0e64f3f409b423f4d1274d8c02814a59a5529f07db6cd070a749664141b992b2c1af337fa9bb451a460a43bb9bcddc49f235d3115aa
+ languageName: node
+ linkType: hard
+
"temp@npm:^0.8.4":
version: 0.8.4
resolution: "temp@npm:0.8.4"
@@ -11055,6 +11543,24 @@ __metadata:
languageName: node
linkType: hard
+"thenify-all@npm:^1.0.0":
+ version: 1.6.0
+ resolution: "thenify-all@npm:1.6.0"
+ dependencies:
+ thenify: ">= 3.1.0 < 4"
+ checksum: dba7cc8a23a154cdcb6acb7f51d61511c37a6b077ec5ab5da6e8b874272015937788402fd271fdfc5f187f8cb0948e38d0a42dcc89d554d731652ab458f5343e
+ languageName: node
+ linkType: hard
+
+"thenify@npm:>= 3.1.0 < 4":
+ version: 3.3.1
+ resolution: "thenify@npm:3.3.1"
+ dependencies:
+ any-promise: ^1.0.0
+ checksum: 84e1b804bfec49f3531215f17b4a6e50fd4397b5f7c1bccc427b9c656e1ecfb13ea79d899930184f78bc2f57285c54d9a50a590c8868f4f0cef5c1d9f898b05e
+ languageName: node
+ linkType: hard
+
"throat@npm:^5.0.0":
version: 5.0.0
resolution: "throat@npm:5.0.0"
@@ -11132,6 +11638,13 @@ __metadata:
languageName: node
linkType: hard
+"ts-interface-checker@npm:^0.1.9":
+ version: 0.1.13
+ resolution: "ts-interface-checker@npm:0.1.13"
+ checksum: 20c29189c2dd6067a8775e07823ddf8d59a33e2ffc47a1bd59a5cb28bb0121a2969a816d5e77eda2ed85b18171aa5d1c4005a6b88ae8499ec7cc49f78571cb5e
+ languageName: node
+ linkType: hard
+
"ts-node@npm:^10.8.1":
version: 10.9.2
resolution: "ts-node@npm:10.9.2"
@@ -11494,6 +12007,15 @@ __metadata:
languageName: node
linkType: hard
+"unique-string@npm:~2.0.0":
+ version: 2.0.0
+ resolution: "unique-string@npm:2.0.0"
+ dependencies:
+ crypto-random-string: ^2.0.0
+ checksum: ef68f639136bcfe040cf7e3cd7a8dff076a665288122855148a6f7134092e6ed33bf83a7f3a9185e46c98dddc445a0da6ac25612afa1a7c38b8b654d6c02498e
+ languageName: node
+ linkType: hard
+
"universalify@npm:^0.1.0":
version: 0.1.2
resolution: "universalify@npm:0.1.2"
@@ -11501,6 +12023,13 @@ __metadata:
languageName: node
linkType: hard
+"universalify@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "universalify@npm:1.0.0"
+ checksum: 095a808f2b915e3b89d29b6f3b4ee4163962b02fa5b7cb686970b8d0439f4ca789bc43f319b7cbb1ce552ae724e631d148e5aee9ce04c4f46a7fe0c5bbfd2b9e
+ languageName: node
+ linkType: hard
+
"universalify@npm:^2.0.0":
version: 2.0.1
resolution: "universalify@npm:2.0.1"
@@ -11552,6 +12081,15 @@ __metadata:
languageName: node
linkType: hard
+"uuid@npm:^7.0.3":
+ version: 7.0.3
+ resolution: "uuid@npm:7.0.3"
+ bin:
+ uuid: dist/bin/uuid
+ checksum: f5b7b5cc28accac68d5c083fd51cca64896639ebd4cca88c6cfb363801aaa83aa439c86dfc8446ea250a7a98d17afd2ad9e88d9d4958c79a412eccb93bae29de
+ languageName: node
+ linkType: hard
+
"v8-compile-cache-lib@npm:^3.0.1":
version: 3.0.1
resolution: "v8-compile-cache-lib@npm:3.0.1"
@@ -11612,6 +12150,13 @@ __metadata:
languageName: node
linkType: hard
+"web-streams-polyfill@npm:^3.3.2":
+ version: 3.3.3
+ resolution: "web-streams-polyfill@npm:3.3.3"
+ checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb
+ languageName: node
+ linkType: hard
+
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
@@ -11822,6 +12367,47 @@ __metadata:
languageName: node
linkType: hard
+"xcode@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "xcode@npm:3.0.1"
+ dependencies:
+ simple-plist: ^1.1.0
+ uuid: ^7.0.3
+ checksum: 908ff85851f81aec6e36ca24427db092e1cc068f052716e14de5e762196858039efabbe053a1abe8920184622501049e74a93618e8692b982f7604a9847db108
+ languageName: node
+ linkType: hard
+
+"xml2js@npm:0.6.0":
+ version: 0.6.0
+ resolution: "xml2js@npm:0.6.0"
+ dependencies:
+ sax: ">=0.6.0"
+ xmlbuilder: ~11.0.0
+ checksum: 437f353fd66d367bf158e9555a0625df9965d944e499728a5c6bc92a54a2763179b144f14b7e1c725040f56bbd22b0fa6cfcb09ec4faf39c45ce01efe631f40b
+ languageName: node
+ linkType: hard
+
+"xmlbuilder@npm:^14.0.0":
+ version: 14.0.0
+ resolution: "xmlbuilder@npm:14.0.0"
+ checksum: 9e93d3c73957dbb21acde63afa5d241b19057bdbdca9d53534d8351e70f1d5c9db154e3ca19bd3e9ea84c082539ab6e7845591c8778a663e8b5d3470d5427a8b
+ languageName: node
+ linkType: hard
+
+"xmlbuilder@npm:^15.1.1":
+ version: 15.1.1
+ resolution: "xmlbuilder@npm:15.1.1"
+ checksum: 14f7302402e28d1f32823583d121594a9dca36408d40320b33f598bd589ca5163a352d076489c9c64d2dc1da19a790926a07bf4191275330d4de2b0d85bb1843
+ languageName: node
+ linkType: hard
+
+"xmlbuilder@npm:~11.0.0":
+ version: 11.0.1
+ resolution: "xmlbuilder@npm:11.0.1"
+ checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0
+ languageName: node
+ linkType: hard
+
"xtend@npm:~4.0.1":
version: 4.0.2
resolution: "xtend@npm:4.0.2"