From 2c673db0886530ff150533056fa066ece39f5a75 Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:16:59 +0200 Subject: [PATCH 1/9] docs: explain iCloud file sync and completion --- apps/docs/content/docs/guides/icloud-sync.mdx | 47 +++++++++++++++ apps/docs/content/docs/guides/quick-start.mdx | 10 +++- .../docs/guides/using-multiple-providers.mdx | 2 +- apps/docs/content/docs/index.mdx | 6 +- .../installation/configure-google-drive.mdx | 4 +- .../src/cloud-storage.ts | 58 ++++++++++++------- .../src/types/main.ts | 2 +- 7 files changed, 99 insertions(+), 30 deletions(-) create mode 100644 apps/docs/content/docs/guides/icloud-sync.mdx diff --git a/apps/docs/content/docs/guides/icloud-sync.mdx b/apps/docs/content/docs/guides/icloud-sync.mdx new file mode 100644 index 0000000..f7a9f9b --- /dev/null +++ b/apps/docs/content/docs/guides/icloud-sync.mdx @@ -0,0 +1,47 @@ +--- +title: iCloud file synchronization +description: Understand local completion, download requests, and iCloud setup checks. +--- + +## Local completion is not server completion + +The iCloud file provider uses Foundation ubiquity APIs for iCloud Documents. It does not use CloudKit records or databases. iOS manages synchronization between the local ubiquity container and iCloud. + +- `writeFile()` and `appendFile()` resolve after the local write. +- `uploadFile()` resolves after it copies the source file into the local container. It does not wait for iCloud to receive the file. Google Drive resolves after its HTTP upload completes. +- `triggerSync()` requests a download of one iCloud file. It does not upload local changes, refresh a directory, or wait for download completion. It has no effect on Google Drive. +- `readdir()` combines local entries with an iCloud metadata query. `exists()` and reads also query metadata when the path is not known locally. These queries reflect the device's current view, which can lag behind another device. +- `readFile()` and `downloadFile(remotePath, localPath)` use coordinated reads. iOS can download file contents during that read. The promise resolves after the read or local copy, not just after requesting a download. + +Do not report an iCloud backup as uploaded based only on a resolved write or upload promise. The API provides no server-upload completion signal. Check the file from another device when testing cross-device delivery. + +For binary transfers, pass an absolute local filesystem path. On iOS, `file://` URLs also work, including percent-encoded filenames. `downloadFile(remotePath, localPath)` does not overwrite an existing local destination. + +## Refresh file content explicitly + +`useCloudFile` reads on mount and when its path, scope, or instance changes. Its own writes and removals update its content. It does not subscribe to remote file changes or poll for them. + +Call the hook's `read()` to refresh content. Its `sync()` only calls `triggerSync()`; it does not read content or wait for a download. Handle read errors while files are unavailable. A successful read is not a guarantee that it contains the latest server version. + +## Choose the scope for your data + +`CloudStorageScope.AppData` is a valid iCloud file scope. It uses the ubiquity container root for app-private files. `CloudStorageScope.Documents` uses that container's `Documents` directory for user-facing files. Switching to Documents does not fix a signing or synchronization problem. + +The `documentsMode: 'legacy_sandbox'` option reads and writes the local app sandbox instead. Use it only for [legacy data migration](/docs/guides/migrating-icloud-documents), not cloud synchronization. + +[`CloudKVStorage`](/docs/guides/key-value-storage) uses the separate `NSUbiquitousKeyValueStore` service on iOS. Working key-value synchronization does not prove that iCloud Documents is configured correctly. Key-value `sync()` does not synchronize files. + +## Check the installed build + +1. Enable iCloud Documents for the Apple App ID. Associate the intended container with that App ID and use a provisioning profile that permits it. +2. Inspect the **signed app**, not only the source entitlements file. For a built app, run: + + ```sh + codesign -d --entitlements :- /path/to/YourApp.app + ``` + + Confirm `com.apple.developer.icloud-services` includes `CloudDocuments`. Check that `com.apple.developer.ubiquity-container-identifiers` and `com.apple.developer.icloud-container-identifiers` contain the intended container. Both devices must use the same container. This library uses the default ubiquity container; check its selection if you configure multiple containers. +3. On both devices, sign in to the same iCloud account. Enable iCloud Drive and allow the app to use iCloud. Check available iCloud storage and network access. iOS controls transfer timing; reopening the app or requesting a download does not force a server upload. +4. Rebuild and reinstall after entitlement or container changes. For Expo, keep these settings in the app configuration and [plugin options](/docs/installation/expo). Do not rely on edits to generated native projects. + +`isCloudAvailable()` and `useIsCloudAvailable()` check for an iCloud identity. They do not check network reachability, access to the selected container, or synchronization progress. diff --git a/apps/docs/content/docs/guides/quick-start.mdx b/apps/docs/content/docs/guides/quick-start.mdx index 3904f64..abc8c6a 100644 --- a/apps/docs/content/docs/guides/quick-start.mdx +++ b/apps/docs/content/docs/guides/quick-start.mdx @@ -21,7 +21,7 @@ Then complete the native setup for your platform: ## Provide a Google Drive access token -iCloud works out of the box on iOS. Google Drive requires an access token that you obtain from the user with a library such as [`@react-native-google-signin/google-signin`](https://github.com/react-native-google-signin/google-signin), then hand to the library: +iCloud needs native configuration and a signed-in iCloud account on iOS, but no access token. Google Drive requires an access token that you obtain from the user with a library such as [`@react-native-google-signin/google-signin`](https://github.com/react-native-google-signin/google-signin), then hand to the library: ```ts import { CloudStorage, CloudStorageProvider } from 'react-native-cloud-storage'; @@ -60,7 +60,7 @@ if (await CloudStorage.exists('/user.json', CloudStorageScope.AppData)) { ## Use the React hook -Inside components, [`useCloudFile`](/docs/api/functions/useCloudFile) keeps a single file's content in sync and gives you helpers to write and remove it: +Inside components, [`useCloudFile`](/docs/api/functions/useCloudFile) reads a single file and gives you helpers to read, write, and remove it. It updates content after its own writes and removals. It does not subscribe to remote file changes or poll for them. ```tsx import { Button, Text, View } from 'react-native'; @@ -79,7 +79,11 @@ function Profile() { } ``` -To react to whether the cloud is reachable at all (for example, iCloud right after launch or before the user signs in), use [`useIsCloudAvailable`](/docs/api/functions/useIsCloudAvailable). +Call the hook's `read()` to refresh content. Its `sync()` only requests an iCloud download. It does not wait for completion or read the file. + +[`useIsCloudAvailable`](/docs/api/functions/useIsCloudAvailable) checks for an iCloud identity or a configured Google Drive token. It does not check network reachability or synchronization status. + +See [iCloud file synchronization](/docs/guides/icloud-sync) for completion semantics and device setup checks. ## Next steps diff --git a/apps/docs/content/docs/guides/using-multiple-providers.mdx b/apps/docs/content/docs/guides/using-multiple-providers.mdx index 86219cb..6edc719 100644 --- a/apps/docs/content/docs/guides/using-multiple-providers.mdx +++ b/apps/docs/content/docs/guides/using-multiple-providers.mdx @@ -3,7 +3,7 @@ title: Using multiple Cloud Storage Providers description: Override the default provider or create per-provider CloudStorage instances to back up to iCloud and Google Drive at the same time. --- -By default, the [`CloudStorage`](/docs/api/classes/CloudStorage) API will use a default storage provider based on the platform (CloudKit for iOS, Google Drive for all other platforms). +By default, the [`CloudStorage`](/docs/api/classes/CloudStorage) API will use a default storage provider based on the platform (iCloud for iOS, Google Drive for all other platforms). If you want to use _one specific provider_ in your app for all platforms, you can override the default provider used by the static default instance by calling [`CloudStorage.setProvider()`](/docs/api/classes/CloudStorage#setprovider) statically. diff --git a/apps/docs/content/docs/index.mdx b/apps/docs/content/docs/index.mdx index 89ccde2..6f761b3 100644 --- a/apps/docs/content/docs/index.mdx +++ b/apps/docs/content/docs/index.mdx @@ -9,8 +9,8 @@ React Native Cloud Storage allows you to use iCloud (iOS only) and Google Drive - **File storage with `fs`-like API** — `readFile`, `writeFile`, `appendFile`, `readdir`, `mkdir`, `stat`, `unlink`, and more, modeled on Node's `fs` so there's nothing new to learn. - **Key-value storage** — save preferences and small app state through native iCloud key-value storage or an emulated Google Drive store. -- **Two providers, one API** — [iCloud](/docs/api/enumerations/CloudStorageProvider) (via a native CloudKit module) and [Google Drive](/docs/installation/configure-google-drive) (via the REST API). Use the platform default or [switch between them at runtime](/docs/guides/using-multiple-providers). -- **React hooks** — [`useCloudFile`](/docs/api/functions/useCloudFile), [`useCloudKV`](/docs/api/functions/useCloudKV), and [`useIsCloudAvailable`](/docs/api/functions/useIsCloudAvailable) keep your components in sync with cloud state. +- **Two providers, one API** — [iCloud](/docs/api/enumerations/CloudStorageProvider) (via Foundation ubiquity APIs for iCloud Documents) and [Google Drive](/docs/installation/configure-google-drive) (via the REST API). Use the platform default or [switch between them at runtime](/docs/guides/using-multiple-providers). +- **React hooks** — [`useCloudFile`](/docs/api/functions/useCloudFile), [`useCloudKV`](/docs/api/functions/useCloudKV), and [`useIsCloudAvailable`](/docs/api/functions/useIsCloudAvailable) expose file content, key-value state, and provider availability. The file hook does not monitor remote changes. - **Scopes** — read and write in a hidden, app-private container ([`AppData`](/docs/api/enumerations/CloudStorageScope)) or the user-visible iCloud Drive / Google Drive [`Documents`](/docs/api/enumerations/CloudStorageScope) folder. - **Expo config plugin** — configures the native iCloud capability automatically, with no manual Xcode steps. - **Built for the New Architecture** — ships as a Turbo Module and is fully typed with TypeScript. @@ -38,7 +38,7 @@ Prefer hooks? [`useCloudFile`](/docs/api/functions/useCloudFile) and [`useCloudK | Provider | iOS | Android | Notes | | ------------ | :-: | :-----: | ------------------------------------------------------------- | -| iCloud | ✅ | — | Backed by a native CloudKit module; available out of the box. | +| iCloud | ✅ | — | Uses iCloud Documents; requires native setup and an iCloud account. | | Google Drive | ✅ | ✅ | Backed by the Drive REST API; you provide an access token. | By default, the library picks the right provider for each platform: iCloud on iOS, Google Drive everywhere else. You can override this or even use both providers at once. diff --git a/apps/docs/content/docs/installation/configure-google-drive.mdx b/apps/docs/content/docs/installation/configure-google-drive.mdx index 7357e93..8128269 100644 --- a/apps/docs/content/docs/installation/configure-google-drive.mdx +++ b/apps/docs/content/docs/installation/configure-google-drive.mdx @@ -8,10 +8,10 @@ Please note that filenames are not unique in Google Drive. There can be multiple -Be aware that all file operations on Google Drive will take severely more time than on iCloud. This is because iCloud is implemented using a direct native API that uses a local mirror of the cloud filesystem (CloudKit) while Google Drive is implemented using the HTTP REST API. A file read operation that might only take a split second on iCloud might take several seconds on Google Drive. +Google Drive operations use the HTTP REST API. iCloud file operations use Foundation ubiquity APIs and a local iCloud Documents container, not CloudKit. Local iCloud operations can finish before iOS synchronizes the changes. Reads can also need a download. See [iCloud file synchronization](/docs/guides/icloud-sync). -While iCloud for iOS devices works out of the box, Google Drive support requires some additional setup. Specifically, you will need to get and provide an access token for the Google Drive API. This module does **not** provide any way of acquiring such a token from the user, as it is out of scope. +iCloud needs native setup on iOS but no access token. For Google Drive, you must acquire and provide an access token. This module does **not** acquire tokens from the user. You therefore need to acquire the token with another library. A popular choice is [`@react-native-google-signin/google-signin`](https://github.com/react-native-google-signin/google-signin). Whatever you do, you will also need a Google OAuth client ID in order to make authentication requests. The linked Expo module has good documentation on this topic. When creating this client ID, make sure to request at least the `https://www.googleapis.com/auth/drive.appdata` scope. This will allow you to use the [`CloudStorageScope.AppData`](/docs/api/enumerations/CloudStorageScope) scope of this library. If you also want to access `CloudStorageScope.Documents`, you will also require the `https://www.googleapis.com/auth/drive` scope, which is a restricted Google API scope. This means your app needs to be audited in order to use it. For more documentation on this matter, consult the [Google documentation](https://developers.google.com/identity/protocols/oauth2/production-readiness/restricted-scope-verification). diff --git a/packages/react-native-cloud-storage/src/cloud-storage.ts b/packages/react-native-cloud-storage/src/cloud-storage.ts index 31d2c71..0f55fc8 100644 --- a/packages/react-native-cloud-storage/src/cloud-storage.ts +++ b/packages/react-native-cloud-storage/src/cloud-storage.ts @@ -13,6 +13,12 @@ import GoogleDrive from './storages/google-drive'; import { NativeCloudKit, NativeCloudKitModule, type NativeCloudStorageCloudKitTurboModule } from './storages/cloudkit'; import { DEFAULT_PROVIDER_OPTIONS, LINKING_ERROR } from './utils/constants'; +/** + * File storage for iCloud and Google Drive. + * iCloud uses Foundation ubiquity APIs (iCloud Documents), not CloudKit. + * iCloud writes and uploads complete locally; iOS synchronizes changes asynchronously. + * A successful operation does not confirm that another device has received the changes. + */ export default class RNCloudStorage { private static defaultInstance: RNCloudStorage; private provider: { @@ -220,9 +226,10 @@ export default class RNCloudStorage { //#region File system operations /** - * Tests whether or not the cloud storage is available. Always returns true for Google Drive. iCloud may be - * unavailable right after app launch or if the user is not logged in. - * @returns A promise that resolves to true if the cloud storage is available, false otherwise. + * Checks for an iCloud identity or a configured Google Drive access token. + * Does not check network reachability, token validity, container access, or synchronization status. + * iCloud may be unavailable right after app launch or if the user is not signed in. + * @returns A promise that resolves to true if an identity or token is present, false otherwise. */ isCloudAvailable(): Promise { return this.nativeStorage.isCloudAvailable(); @@ -241,6 +248,7 @@ export default class RNCloudStorage { /** * Tests whether or not the file at the given path exists. + * iCloud discovery reflects the device's current view, not a complete server listing. * @param path The path to test. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. * @returns A promise that resolves to true if the path exists, false otherwise. @@ -272,6 +280,7 @@ export default class RNCloudStorage { /** * Lists the contents of the directory at the given path. + * iCloud discovery reflects the device's current view, not a complete server listing. * @param path The directory to list. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. * @returns A promise that resolves to an array of file names, excluding '.' and '..'. @@ -291,10 +300,11 @@ export default class RNCloudStorage { } /** - * Triggers synchronization for the file at the given path. Does not have any effect on Google Drive. - * @param path The file to trigger synchronization for. + * Requests an iCloud file download. Does not have any effect on Google Drive. + * Does not upload changes, refresh directories, or wait for the download to complete. + * @param path The file to request a download for. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. - * @returns A promise that resolves once the synchronization has been triggered. + * @returns A promise that resolves once the download has been requested, not completed. * @provider icloud */ triggerSync(path: string, scope?: CloudStorageScope): Promise { @@ -307,7 +317,8 @@ export default class RNCloudStorage { * @param localPath The local path of the file to upload. * @param options The options for the upload. Must contain a `mimeType` property. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. - * @returns A promise that resolves when the file has been uploaded. + * @returns A promise that resolves after the local iCloud container copy or the Google Drive upload completes. + * iOS uploads the iCloud copy asynchronously; this promise does not confirm server delivery. */ uploadFile( remotePath: string, @@ -319,10 +330,11 @@ export default class RNCloudStorage { } /** - * Triggers synchronization for the file at the given path. Does not have any effect on Google Drive. - * @param path The file to trigger synchronization for. + * Requests an iCloud file download. Does not have any effect on Google Drive. + * Does not upload changes, refresh directories, or wait for the download to complete. + * @param path The file to request a download for. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. - * @returns A promise that resolves once the synchronization has been triggered. + * @returns A promise that resolves once the download has been requested, not completed. * @deprecated Use `triggerSync` instead. */ downloadFile(path: string, scope?: CloudStorageScope): Promise; @@ -425,6 +437,7 @@ export default class RNCloudStorage { /** * Tests whether or not the file at the given path exists in the provider of the default static instance. + * iCloud discovery reflects the device's current view, not a complete server listing. * @param path The path to test. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. * @returns A promise that resolves to true if the path exists, false otherwise. @@ -434,9 +447,10 @@ export default class RNCloudStorage { } /** - * Tests whether or not the cloud storage is available for the provider of the default static instance. Always returns true for Google Drive. iCloud may be - * unavailable right after app launch or if the user is not logged in. - * @returns A promise that resolves to true if the cloud storage is available, false otherwise. + * Checks for an iCloud identity or a configured Google Drive access token on the default instance. + * Does not check network reachability, token validity, container access, or synchronization status. + * iCloud may be unavailable right after app launch or if the user is not signed in. + * @returns A promise that resolves to true if an identity or token is present, false otherwise. */ static isCloudAvailable(): Promise { return RNCloudStorage.getDefaultInstance().isCloudAvailable(); @@ -476,6 +490,7 @@ export default class RNCloudStorage { /** * Lists the contents of the directory at the given path in the provider of the default static instance. + * iCloud discovery reflects the device's current view, not a complete server listing. * @param path The directory to list. * @param scope The directory scope the path is in. Defaults to the default scope set for the default static instance. * @returns A promise that resolves to an array of file names, excluding '.' and '..'. @@ -495,10 +510,11 @@ export default class RNCloudStorage { } /** - * Triggers synchronization for the file at the given path in the provider of the default static instance. Does not have any effect on Google Drive. - * @param path The file to trigger synchronization for. + * Requests an iCloud file download on the default instance. Does not have any effect on Google Drive. + * Does not upload changes, refresh directories, or wait for the download to complete. + * @param path The file to request a download for. * @param scope The directory scope the path is in. Defaults to the default scope set for the default static instance. - * @returns A promise that resolves once the synchronization has been triggered. + * @returns A promise that resolves once the download has been requested, not completed. * @provider icloud */ static triggerSync(path: string, scope?: CloudStorageScope): Promise { @@ -511,7 +527,8 @@ export default class RNCloudStorage { * @param localPath The local path of the file to upload. * @param options The options for the upload. Must contain a `mimeType` property. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. - * @returns A promise that resolves when the file has been uploaded. + * @returns A promise that resolves after the local iCloud container copy or the Google Drive upload completes. + * iOS uploads the iCloud copy asynchronously; this promise does not confirm server delivery. */ static uploadFile( remotePath: string, @@ -523,10 +540,11 @@ export default class RNCloudStorage { } /** - * Triggers synchronization for the file at the given path in the provider of the default static instance. Does not have any effect on Google Drive. - * @param path The file to trigger synchronization for. + * Requests an iCloud file download on the default instance. Does not have any effect on Google Drive. + * Does not upload changes, refresh directories, or wait for the download to complete. + * @param path The file to request a download for. * @param scope The directory scope the path is in. Defaults to set default scope set for the current provider. - * @returns A promise that resolves once the synchronization has been triggered. + * @returns A promise that resolves once the download has been requested, not completed. * @deprecated Use `triggerSync` instead. */ static downloadFile(path: string, scope?: CloudStorageScope): Promise; diff --git a/packages/react-native-cloud-storage/src/types/main.ts b/packages/react-native-cloud-storage/src/types/main.ts index 27f9383..e057337 100644 --- a/packages/react-native-cloud-storage/src/types/main.ts +++ b/packages/react-native-cloud-storage/src/types/main.ts @@ -74,7 +74,7 @@ export interface CloudStorageFileStat { export enum CloudStorageProvider { /** - * Apple iCloud, backed by CloudKit. + * Apple iCloud file storage, backed by Foundation ubiquity APIs (iCloud Documents), not CloudKit. * @platform ios */ ICloud = 'icloud', From 8d63b6385c78ca52a9144c52bb3f6f3fd9366aff Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:17:01 +0200 Subject: [PATCH 2/9] fix(example): report transfer progress correctly --- apps/example/src/screens/home/home-file-operations-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/example/src/screens/home/home-file-operations-card.tsx b/apps/example/src/screens/home/home-file-operations-card.tsx index 367b807..eaf2519 100644 --- a/apps/example/src/screens/home/home-file-operations-card.tsx +++ b/apps/example/src/screens/home/home-file-operations-card.tsx @@ -103,7 +103,7 @@ const HomeFileOperationsCard: React.FC = ({ mimeType: file.mimeType ?? 'application/octet-stream', }); setStats(await cloudStorage.stat(filePath)); - Alert.alert('File uploaded', 'File uploaded successfully.'); + Alert.alert('File saved', 'File saved. On iCloud, iOS uploads the local container copy asynchronously.'); } catch (error) { console.warn(error); } finally { @@ -154,7 +154,7 @@ const HomeFileOperationsCard: React.FC = ({ onLoadingChange(true); try { await cloudStorage.triggerSync(filePath); - Alert.alert('File download', 'File downloaded successfully.'); + Alert.alert('Download requested', 'iOS received the download request. This does not confirm completion.'); } catch (error) { console.warn(error); } finally { From 7a5b00aab283ea7968b29ee0674ead6ca9038048 Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:17:04 +0200 Subject: [PATCH 3/9] docs: keep example iCloud setup in Expo config --- apps/docs/content/docs/example.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/docs/example.mdx b/apps/docs/content/docs/example.mdx index f964081..f7c45ad 100644 --- a/apps/docs/content/docs/example.mdx +++ b/apps/docs/content/docs/example.mdx @@ -7,7 +7,16 @@ An example project is available within the [`apps/example` directory](https://gi ## iOS Setup -When setting up the example project for iOS, you will need to follow the [iOS installation steps](/docs/installation/react-native) in order to provide your own iCloud container. +Set `expo.ios.bundleIdentifier` in `apps/example/app.json` to an App ID owned by your Apple team. The existing `react-native-cloud-storage` plugin uses `iCloud.` as the container identifier. To use a different container, set `iCloudContainerIdentifier` in that plugin's options. + +Register the App ID and container for your team. Use a provisioning profile that supports them. See [Expo installation](/docs/installation/expo) for plugin options and [iCloud setup checks](/docs/guides/icloud-sync#check-the-installed-build) for signing and device settings. + +From the repository root, run: + +```sh +pnpm package build +pnpm example ios +``` ## Android Setup From d55ee10f24429e8f846b96537df09321f4aafe2c Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:17:51 +0200 Subject: [PATCH 4/9] fix(example): decode local file paths --- apps/example/src/screens/home/home-file-operations-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/example/src/screens/home/home-file-operations-card.tsx b/apps/example/src/screens/home/home-file-operations-card.tsx index eaf2519..7474dc4 100644 --- a/apps/example/src/screens/home/home-file-operations-card.tsx +++ b/apps/example/src/screens/home/home-file-operations-card.tsx @@ -99,7 +99,7 @@ const HomeFileOperationsCard: React.FC = ({ onLoadingChange(true); try { const file = result.assets[0]; - await cloudStorage.uploadFile(filePath, file.uri.replace(/^file:\/\//, ''), { + await cloudStorage.uploadFile(filePath, decodeURIComponent(file.uri.replace(/^file:\/\//, '')), { mimeType: file.mimeType ?? 'application/octet-stream', }); setStats(await cloudStorage.stat(filePath)); @@ -116,7 +116,7 @@ const HomeFileOperationsCard: React.FC = ({ try { const directory = FileSystem.cacheDirectory; if (!directory) throw new Error('Could not get cache directory'); - const newFilename = directory.replace(/^file:\/\//, '') + Crypto.randomUUID(); + const newFilename = decodeURIComponent(directory.replace(/^file:\/\//, '')) + Crypto.randomUUID(); await cloudStorage.downloadFile(filePath, newFilename); Alert.alert('File downloaded', `File downloaded to ${newFilename}`); } catch (error) { From d24852de759c697e416f56c9abc3c4015f83222a Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:28:02 +0200 Subject: [PATCH 5/9] fix(ios): keep local paths in upload errors --- .../ios/CloudStorageLocalFileSystem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-cloud-storage/ios/CloudStorageLocalFileSystem.swift b/packages/react-native-cloud-storage/ios/CloudStorageLocalFileSystem.swift index 5e4cb8c..007a596 100644 --- a/packages/react-native-cloud-storage/ios/CloudStorageLocalFileSystem.swift +++ b/packages/react-native-cloud-storage/ios/CloudStorageLocalFileSystem.swift @@ -179,7 +179,7 @@ public class CloudStorageLocalFileSystem: NSObject { guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else { let httpResponse = response as? HTTPURLResponse - let message = "Upload failed for path \(sanitizedPath) with status code: \(httpResponse?.statusCode ?? -1)" + let message = "Upload failed for path \(localPath) with status code: \(httpResponse?.statusCode ?? -1)" let cloudError = CloudStorageError.networkError(message: message) reject(cloudError.code, cloudError.message, nil) return From 4fea4237218557b3fd93283df09dea125b087c08 Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:28:04 +0200 Subject: [PATCH 6/9] fix(ios): resolve remote files before append and delete --- .../ios/CloudStorageCloudKit.swift | 11 ++++++++--- .../ios/Utils/CloudKitUtils.swift | 13 ++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/react-native-cloud-storage/ios/CloudStorageCloudKit.swift b/packages/react-native-cloud-storage/ios/CloudStorageCloudKit.swift index ee60e91..1832361 100644 --- a/packages/react-native-cloud-storage/ios/CloudStorageCloudKit.swift +++ b/packages/react-native-cloud-storage/ios/CloudStorageCloudKit.swift @@ -18,7 +18,12 @@ public class CloudStorageCloudKit: NSObject { @objc(appendToFile:withData:withScope:withResolver:withRejecter:) public func appendToFile(path: String, data: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { withBackgroundPromise(resolve: resolve, reject: reject) { - let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope) + let fileUrl: URL + do { + fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope, true) + } catch CloudStorageError.fileNotFound { + fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope) + } return try FileUtils.appendFile(fileUrl: fileUrl, content: data) } } @@ -66,7 +71,7 @@ public class CloudStorageCloudKit: NSObject { @objc(deleteFile:withScope:withResolver:withRejecter:) public func deleteFile(path: String, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { withBackgroundPromise(resolve: resolve, reject: reject) { - let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope) + let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope, true) return try FileUtils.deleteFileOrDirectory(fileUrl: fileUrl) } } @@ -74,7 +79,7 @@ public class CloudStorageCloudKit: NSObject { @objc(deleteDirectory:withRecursive:withScope:withResolver:withRejecter:) public func deleteDirectory(path: String, recursive _: Bool, scope: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { withBackgroundPromise(resolve: resolve, reject: reject) { - let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope) + let fileUrl = try CloudKitUtils.getFileURL(path: path, scope: scope, true) return try FileUtils.deleteFileOrDirectory(fileUrl: fileUrl) } } diff --git a/packages/react-native-cloud-storage/ios/Utils/CloudKitUtils.swift b/packages/react-native-cloud-storage/ios/Utils/CloudKitUtils.swift index 0957520..333a34e 100644 --- a/packages/react-native-cloud-storage/ios/Utils/CloudKitUtils.swift +++ b/packages/react-native-cloud-storage/ios/Utils/CloudKitUtils.swift @@ -70,18 +70,18 @@ enum CloudKitUtils { // append path to scope directory let fileUrl = directory.appendingPathComponent(FileUtils.sanitizePath(path: path)) - if shouldExist != nil { + if let shouldExist { var fileExists = try FileUtils.checkFileExists(fileUrl: fileUrl) if !fileExists, scope != .documentsLegacy { let urls = try ICloudMetadataQuery().gather() - if let discoveredUrl = urls.first(where: { canonicalPath($0) == canonicalPath(fileUrl) }), shouldExist == true { + if let discoveredUrl = urls.first(where: { canonicalPath($0) == canonicalPath(fileUrl) }), shouldExist { return discoveredUrl } fileExists = contains(fileUrl, in: urls) } - if shouldExist! && !fileExists { + if shouldExist && !fileExists { throw CloudStorageError.fileNotFound(path: path) - } else if !shouldExist! && fileExists { + } else if !shouldExist && fileExists { throw CloudStorageError.fileAlreadyExists(path: path) } } @@ -112,7 +112,10 @@ enum CloudKitUtils { static func contains(_ url: URL, in metadataURLs: [URL]) -> Bool { let path = canonicalPath(url) - return metadataURLs.contains { canonicalPath($0) == path || canonicalPath($0).hasPrefix(path + "/") } + return metadataURLs.contains { url in + let candidate = canonicalPath(url) + return candidate == path || candidate.hasPrefix(path + "/") + } } static func directoryEntries(at directoryUrl: URL, localNames: [String], metadataURLs: [URL]) -> [String] { From 2159d225d5dd709fab1f937347bf173541c9d8e7 Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:28:05 +0200 Subject: [PATCH 7/9] test: run iOS file checks in CI --- .github/workflows/build-ios.yml | 4 ++++ .../react-native-cloud-storage/scripts/swiftformat.sh | 2 +- .../react-native-cloud-storage/scripts/test-ios-utils.sh | 6 ++++-- .../src/__tests__/ios/main.swift | 9 +++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 8f382c4..04bf3c7 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -7,11 +7,13 @@ on: - '.github/workflows/build-ios.yml' - '**/ios/**' - '**/*.podspec' + - '**/scripts/test-ios-utils.sh' pull_request: paths: - '.github/workflows/build-ios.yml' - '**/ios/**' - '**/*.podspec' + - '**/scripts/test-ios-utils.sh' jobs: build: name: Build iOS example app @@ -20,6 +22,8 @@ jobs: - uses: actions/checkout@v7 - name: Setup uses: ./.github/actions/setup + - name: Test iOS file utilities + run: pnpm package test:ios - name: Install xcpretty run: gem install xcpretty - name: Build package diff --git a/packages/react-native-cloud-storage/scripts/swiftformat.sh b/packages/react-native-cloud-storage/scripts/swiftformat.sh index 2f0f52a..82397e5 100755 --- a/packages/react-native-cloud-storage/scripts/swiftformat.sh +++ b/packages/react-native-cloud-storage/scripts/swiftformat.sh @@ -1,7 +1,7 @@ #!/bin/bash if which swiftformat >/dev/null; then - cd ios && swiftformat "$@" . + cd ios && swiftformat "$@" . ../src/__tests__/ios --config .swiftformat --swift-version "$(<.swift-version)" else echo "error: SwiftFormat is not installed. Install with 'brew install swiftformat' or manually from https://github.com/nicklockwood/SwiftFormat" exit 1 diff --git a/packages/react-native-cloud-storage/scripts/test-ios-utils.sh b/packages/react-native-cloud-storage/scripts/test-ios-utils.sh index 4d0b2ad..a6ecb31 100644 --- a/packages/react-native-cloud-storage/scripts/test-ios-utils.sh +++ b/packages/react-native-cloud-storage/scripts/test-ios-utils.sh @@ -4,6 +4,8 @@ cd "$(dirname "$0")/.." directory=$(mktemp -d) trap 'rm -rf "$directory"' EXIT -swiftc ios/Utils/CloudStorageError.swift ios/Utils/Types.swift ios/Utils/FileUtils.swift \ - ios/Utils/CloudKitUtils.swift src/__tests__/ios/main.swift -o "$directory/test-ios-utils" +sources=(ios/Utils/CloudStorageError.swift ios/Utils/Types.swift ios/Utils/FileUtils.swift ios/Utils/CloudKitUtils.swift) +swiftc "${sources[@]}" src/__tests__/ios/main.swift -o "$directory/test-ios-utils" "$directory/test-ios-utils" +xcrun swiftc -typecheck -target arm64-apple-ios15.1-simulator \ + -sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)" "${sources[@]}" diff --git a/packages/react-native-cloud-storage/src/__tests__/ios/main.swift b/packages/react-native-cloud-storage/src/__tests__/ios/main.swift index 1b0f112..7f43613 100644 --- a/packages/react-native-cloud-storage/src/__tests__/ios/main.swift +++ b/packages/react-native-cloud-storage/src/__tests__/ios/main.swift @@ -72,6 +72,15 @@ expectError("ERR_FILE_EXISTS") { try FileUtils.writeFile(fileUrl: textFile, cont let textStat = try FileUtils.statFile(fileUrl: textFile) assert(textStat.isFile && textStat.size == 7) +let concurrentFile = directory.appendingPathComponent("concurrent.txt") +try FileUtils.writeFile(fileUrl: concurrentFile, content: "") +DispatchQueue.concurrentPerform(iterations: 20) { _ in + try! FileUtils.appendFile(fileUrl: concurrentFile, content: "x") +} + +let concurrentContent = try FileUtils.readFile(fileUrl: concurrentFile) +assert(concurrentContent == String(repeating: "x", count: 20)) + let destination = directory.appendingPathComponent("copy.zip") try FileUtils.copyFile(from: file, to: destination) expectError("ERR_FILE_EXISTS") { try FileUtils.copyFile(from: textFile, to: destination) } From b8c6d1b27ee431be91c745e6c562b0405995c1b3 Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Mon, 14 Sep 2026 20:35:50 +0200 Subject: [PATCH 8/9] ci: improve changelog generation --- packages/react-native-cloud-storage/package.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-native-cloud-storage/package.json b/packages/react-native-cloud-storage/package.json index 97af723..5de37ff 100644 --- a/packages/react-native-cloud-storage/package.json +++ b/packages/react-native-cloud-storage/package.json @@ -105,7 +105,21 @@ "plugins": { "@release-it/conventional-changelog": { "preset": { - "name": "angular" + "name": "conventionalcommits", + "types": [ + { + "type": "feat", + "section": "✨ Features" + }, + { + "type": "perf", + "section": "⚡ Performance Improvements" + }, + { + "type": "fix", + "section": "🐛 Bug Fixes" + } + ] }, "infile": "CHANGELOG.md" } From 31ac5783d780febfcd4f50b3744c51324dcf5135 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:39:10 +0000 Subject: [PATCH 9/9] chore(deps-dev): bump vitest from 4.1.10 to 5.0.0 Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.10 to 5.0.0. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.0/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 5.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../react-native-cloud-storage/package.json | 2 +- pnpm-lock.yaml | 337 ++++++++---------- 2 files changed, 142 insertions(+), 197 deletions(-) diff --git a/packages/react-native-cloud-storage/package.json b/packages/react-native-cloud-storage/package.json index 5de37ff..aaa56d5 100644 --- a/packages/react-native-cloud-storage/package.json +++ b/packages/react-native-cloud-storage/package.json @@ -75,7 +75,7 @@ "react-native-web": "^0.21.2", "release-it": "^15.0.0", "typescript": "~5.9.3", - "vitest": "^4.1.10" + "vitest": "^5.0.0" }, "peerDependencies": { "expo": ">=48.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4281154..304708d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3) fumadocs-mdx: specifier: 15.3.0 - version: 15.3.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3))(mdast-util-directive@3.1.0)(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(rolldown@1.2.5)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) + version: 15.3.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3))(mdast-util-directive@3.1.0)(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(rolldown@1.2.8)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) fumadocs-ui: specifier: 16.14.5 version: 16.14.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.3.3) @@ -183,8 +183,8 @@ importers: specifier: ~5.9.3 version: 5.9.3 vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@25.3.1)(jsdom@30.0.1)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) + specifier: ^5.0.0 + version: 5.0.0(@types/node@25.3.1)(jsdom@30.0.1)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) packages: @@ -1745,6 +1745,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -1875,8 +1878,8 @@ packages: '@octokit/types@9.3.2': resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} - '@oxc-project/types@0.146.0': - resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} + '@oxc-project/types@0.149.0': + resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} @@ -2596,98 +2599,98 @@ packages: peerDependencies: release-it: ^15.4.1 - '@rolldown/binding-android-arm-eabi@1.2.5': - resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} + '@rolldown/binding-android-arm-eabi@1.2.8': + resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.5': - resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} + '@rolldown/binding-android-arm64@1.2.8': + resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.5': - resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} + '@rolldown/binding-darwin-arm64@1.2.8': + resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.5': - resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} + '@rolldown/binding-darwin-x64@1.2.8': + resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.5': - resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} + '@rolldown/binding-freebsd-x64@1.2.8': + resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.5': - resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.5': - resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} + '@rolldown/binding-linux-arm64-gnu@1.2.8': + resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.5': - resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} + '@rolldown/binding-linux-arm64-musl@1.2.8': + resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.5': - resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.5': - resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.8': + resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.5': - resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} + '@rolldown/binding-linux-x64-gnu@1.2.8': + resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.5': - resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} + '@rolldown/binding-linux-x64-musl@1.2.8': + resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.5': - resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} + '@rolldown/binding-openharmony-arm64@1.2.8': + resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.5': - resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} + '@rolldown/binding-win32-arm64-msvc@1.2.8': + resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.5': - resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} + '@rolldown/binding-win32-x64-msvc@1.2.8': + resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2964,11 +2967,8 @@ packages: peerDependencies: '@urql/core': ^5.0.0 - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@vitest/mocker@5.0.0': + resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2978,20 +2978,8 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@vitest/spy@5.0.0': + resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} @@ -4074,8 +4062,8 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -5867,6 +5855,9 @@ packages: magic-string@1.2.2: resolution: {integrity: sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg==} + magic-string@1.3.1: + resolution: {integrity: sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==} + makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -6471,8 +6462,8 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} on-finished@2.3.0: @@ -6716,9 +6707,6 @@ packages: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -6730,14 +6718,14 @@ packages: resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} engines: {node: '>=10'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - picomatch@4.0.5: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7192,8 +7180,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rolldown@1.2.5: - resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} + rolldown@1.2.8: + resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7661,17 +7649,14 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.1.4: + resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + engines: {node: '>=20.0.0'} tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} - engines: {node: '>=18'} - tinyexec@1.3.0: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} @@ -7684,10 +7669,6 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} - engines: {node: '>=14.0.0'} - titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -8036,23 +8017,23 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.0: + resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.0 + '@vitest/browser-preview': 5.0.0 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.0 + '@vitest/coverage-v8': 5.0.0 + '@vitest/ui': 5.0.0 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -9724,7 +9705,7 @@ snapshots: node-forge: 1.3.3 npm-package-arg: 11.0.3 ora: 3.4.0 - picomatch: 4.0.5 + picomatch: 4.0.7 pretty-format: 29.7.0 progress: 2.0.3 prompts: 2.4.2 @@ -10004,7 +9985,7 @@ snapshots: hermes-parser: 0.32.1 jsc-safe-url: 0.2.4 lightningcss: 1.31.1 - picomatch: 4.0.5 + picomatch: 4.0.7 postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: @@ -10377,7 +10358,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -10394,10 +10375,12 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@mdx-js/mdx@3.1.1': dependencies: @@ -10548,7 +10531,7 @@ snapshots: dependencies: '@octokit/openapi-types': 18.1.1 - '@oxc-project/types@0.146.0': {} + '@oxc-project/types@0.149.0': {} '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -11255,49 +11238,49 @@ snapshots: release-it: 15.11.0 semver: 7.3.8 - '@rolldown/binding-android-arm-eabi@1.2.5': + '@rolldown/binding-android-arm-eabi@1.2.8': optional: true - '@rolldown/binding-android-arm64@1.2.5': + '@rolldown/binding-android-arm64@1.2.8': optional: true - '@rolldown/binding-darwin-arm64@1.2.5': + '@rolldown/binding-darwin-arm64@1.2.8': optional: true - '@rolldown/binding-darwin-x64@1.2.5': + '@rolldown/binding-darwin-x64@1.2.8': optional: true - '@rolldown/binding-freebsd-x64@1.2.5': + '@rolldown/binding-freebsd-x64@1.2.8': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.5': + '@rolldown/binding-linux-arm64-gnu@1.2.8': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.5': + '@rolldown/binding-linux-arm64-musl@1.2.8': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.8': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.5': + '@rolldown/binding-linux-s390x-gnu@1.2.8': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.5': + '@rolldown/binding-linux-x64-gnu@1.2.8': optional: true - '@rolldown/binding-linux-x64-musl@1.2.5': + '@rolldown/binding-linux-x64-musl@1.2.8': optional: true - '@rolldown/binding-openharmony-arm64@1.2.5': + '@rolldown/binding-openharmony-arm64@1.2.8': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.5': + '@rolldown/binding-win32-arm64-msvc@1.2.8': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.5': + '@rolldown/binding-win32-x64-msvc@1.2.8': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -11569,46 +11552,16 @@ snapshots: '@urql/core': 5.2.0 wonka: 6.3.5 - '@vitest/expect@4.1.10': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - chai: 6.2.2 - tinyrainbow: 3.1.1 - - '@vitest/mocker@4.1.10(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0))': + '@vitest/mocker@5.0.0(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.10 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.0 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.3.1 optionalDependencies: vite: 8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0) - '@vitest/pretty-format@4.1.10': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/runner@4.1.10': - dependencies: - '@vitest/utils': 4.1.10 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.10': {} - - '@vitest/utils@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@vitest/spy@5.0.0': {} '@xmldom/xmldom@0.8.11': {} @@ -12833,7 +12786,7 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 - es-module-lexer@2.3.1: {} + es-module-lexer@2.3.2: {} es-object-atoms@1.1.1: dependencies: @@ -13243,9 +13196,9 @@ snapshots: transitivePeerDependencies: - encoding - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 fetch-blob@3.2.0: dependencies: @@ -13382,7 +13335,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@15.3.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3))(mdast-util-directive@3.1.0)(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(rolldown@1.2.5)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)): + fumadocs-mdx@15.3.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.14.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.49.1)(lucide-react@0.575.0(react@19.2.3))(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.4.3))(mdast-util-directive@3.1.0)(next@16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(rolldown@1.2.8)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 @@ -13411,7 +13364,7 @@ snapshots: mdast-util-directive: 3.1.0 next: 16.3.1(@types/node@25.3.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 - rolldown: 1.2.5 + rolldown: 1.2.8 vite: 8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -14730,12 +14683,16 @@ snapshots: magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 magic-string@1.2.2: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@1.3.1: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + makeerror@1.0.12: dependencies: tmpl: 1.0.5 @@ -15812,7 +15769,7 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 - obug@2.1.4: {} + obug@2.2.1: {} on-finished@2.3.0: dependencies: @@ -16122,18 +16079,16 @@ snapshots: path-type@6.0.0: {} - pathe@2.0.3: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} picomatch@3.0.1: {} - picomatch@4.0.4: {} - picomatch@4.0.5: {} + picomatch@4.0.7: {} + pify@2.3.0: {} pify@3.0.0: {} @@ -16735,26 +16690,26 @@ snapshots: dependencies: glob: 7.2.3 - rolldown@1.2.5: + rolldown@1.2.8: dependencies: - '@oxc-project/types': 0.146.0 + '@oxc-project/types': 0.149.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.5 - '@rolldown/binding-android-arm64': 1.2.5 - '@rolldown/binding-darwin-arm64': 1.2.5 - '@rolldown/binding-darwin-x64': 1.2.5 - '@rolldown/binding-freebsd-x64': 1.2.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 - '@rolldown/binding-linux-arm64-gnu': 1.2.5 - '@rolldown/binding-linux-arm64-musl': 1.2.5 - '@rolldown/binding-linux-ppc64-gnu': 1.2.5 - '@rolldown/binding-linux-s390x-gnu': 1.2.5 - '@rolldown/binding-linux-x64-gnu': 1.2.5 - '@rolldown/binding-linux-x64-musl': 1.2.5 - '@rolldown/binding-openharmony-arm64': 1.2.5 - '@rolldown/binding-win32-arm64-msvc': 1.2.5 - '@rolldown/binding-win32-x64-msvc': 1.2.5 + '@rolldown/binding-android-arm-eabi': 1.2.8 + '@rolldown/binding-android-arm64': 1.2.8 + '@rolldown/binding-darwin-arm64': 1.2.8 + '@rolldown/binding-darwin-x64': 1.2.8 + '@rolldown/binding-freebsd-x64': 1.2.8 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 + '@rolldown/binding-linux-arm64-gnu': 1.2.8 + '@rolldown/binding-linux-arm64-musl': 1.2.8 + '@rolldown/binding-linux-ppc64-gnu': 1.2.8 + '@rolldown/binding-linux-s390x-gnu': 1.2.8 + '@rolldown/binding-linux-x64-gnu': 1.2.8 + '@rolldown/binding-linux-x64-musl': 1.2.8 + '@rolldown/binding-openharmony-arm64': 1.2.8 + '@rolldown/binding-win32-arm64-msvc': 1.2.8 + '@rolldown/binding-win32-x64-msvc': 1.2.8 run-applescript@5.0.0: dependencies: @@ -17259,23 +17214,19 @@ snapshots: through@2.3.8: {} - tinybench@2.9.0: {} + tinybench@6.1.4: {} tinyexec@1.0.2: {} - tinyexec@1.2.4: {} - tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinypool@2.1.0: {} - tinyrainbow@3.1.1: {} - titleize@3.0.0: {} tldts-core@7.4.10: {} @@ -17565,9 +17516,9 @@ snapshots: vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 - picomatch: 4.0.5 + picomatch: 4.0.7 postcss: 8.5.26 - rolldown: 1.2.5 + rolldown: 1.2.8 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.3.1 @@ -17577,26 +17528,20 @@ snapshots: terser: 5.46.0 yaml: 2.9.0 - vitest@4.1.10(@types/node@25.3.1)(jsdom@30.0.1)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)): + vitest@5.0.0(@types/node@25.3.1)(jsdom@30.0.1)(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.3.1 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.0(vite@8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0)) + chai: 6.2.2 + es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.4 - pathe: 2.0.3 - picomatch: 4.0.4 + magic-string: 1.3.1 + obug: 2.2.1 + picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 + tinybench: 6.1.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 vite: 8.2.1(@types/node@25.3.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: