diff --git a/.eslintrc.js b/.eslintrc.js
index fe91d4c8b6..f6c832469f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,4 +1,5 @@
module.exports = {
+ root: true,
plugins: ['import', 'unicorn', '@tanstack/query', 'sonarjs'],
extends: [
'@internxt/eslint-config-internxt',
diff --git a/.github/workflows/check-pr-size.yml b/.github/workflows/check-pr-size.yml
index d77733b2df..d4f93bd2f8 100644
--- a/.github/workflows/check-pr-size.yml
+++ b/.github/workflows/check-pr-size.yml
@@ -7,8 +7,9 @@ on:
jobs:
check_pr_size:
# Ignore PRs with name merge-X-X-X-release
- if: |
- !(startsWith(github.head_ref, 'merge-') && endsWith(github.head_ref, '-release') && github.base_ref == 'main')
+ if: false
+ # if: |
+ # !(startsWith(github.head_ref, 'merge-') && endsWith(github.head_ref, '-release') && github.base_ref == 'main')
runs-on: ubuntu-latest
timeout-minutes: 1
diff --git a/.github/workflows/core-find-deadcode.yml b/.github/workflows/core-find-deadcode.yml
new file mode 100644
index 0000000000..be2afeaf2d
--- /dev/null
+++ b/.github/workflows/core-find-deadcode.yml
@@ -0,0 +1,31 @@
+name: Core find deadcode
+
+on:
+ pull_request:
+ paths: ['packages/core/**']
+ types: [opened, synchronize, reopened]
+
+jobs:
+ find_deadcode:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ defaults:
+ run:
+ working-directory: packages/core
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Find deadcode
+ run: npm run find-deadcode
diff --git a/.github/workflows/core-publish.yml b/.github/workflows/core-publish.yml
new file mode 100644
index 0000000000..d1e8f8e0f1
--- /dev/null
+++ b/.github/workflows/core-publish.yml
@@ -0,0 +1,49 @@
+name: Publish
+
+on:
+ workflow_dispatch:
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ timeout-minutes: 1
+ defaults:
+ run:
+ working-directory: packages/core
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build
+ run: npm run build
+
+ - name: Create .npmrc file
+ shell: bash
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "@internxt:registry=https://npm.pkg.github.com" > .npmrc
+ echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+
+ - name: Publish package to github
+ run: npm publish --scope=@internxt --access public
+
+ - name: Create .npmrc file
+ shell: bash
+ env:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ run: |
+ echo "registry=https://registry.npmjs.org/" > .npmrc
+ echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
+
+ - name: Publish package to npm
+ run: npm publish --scope=@internxt --access public
diff --git a/.github/workflows/core-pull-request.yml b/.github/workflows/core-pull-request.yml
new file mode 100644
index 0000000000..ee6bc709de
--- /dev/null
+++ b/.github/workflows/core-pull-request.yml
@@ -0,0 +1,37 @@
+name: Core pull request
+
+on:
+ pull_request:
+ paths: ['packages/core/**']
+ types: [opened, synchronize, reopened]
+
+jobs:
+ pull_request:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ defaults:
+ run:
+ working-directory: packages/core
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run TypeScript compiler
+ run: npm run type-check
+
+ - name: Run Prettier
+ run: npm run format
+
+ - name: Run Eslint
+ run: npm run lint
diff --git a/.github/workflows/core-sonar-analysis.yml b/.github/workflows/core-sonar-analysis.yml
new file mode 100644
index 0000000000..1b5bb65b24
--- /dev/null
+++ b/.github/workflows/core-sonar-analysis.yml
@@ -0,0 +1,39 @@
+name: Core sonar analysis
+
+on:
+ pull_request:
+ paths: ['packages/core/**']
+ types: [opened, synchronize, reopened]
+ push:
+ paths: ['packages/core/**']
+ branches: ['master']
+
+jobs:
+ sonar_analysis:
+ runs-on: windows-latest
+ timeout-minutes: 10
+ defaults:
+ run:
+ working-directory: packages/core
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run tests
+ run: npm run test:cov
+
+ # - name: SonarQube Scan
+ # uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1
+ # env:
+ # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..ea8d238cdb
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,182 @@
+# Contributing
+
+## Table of Contents
+
+- [Architecture](#architecture)
+- [Code](#code)
+ - [File template](#file-template)
+ - [Testing](#testing)
+ - [Imports](#imports)
+ - [Use function instead of arrow functions by default](#use-function-instead-of-arrow-functions-by-default)
+ - [Use object props instead of multiple props](#use-object-props-instead-of-multiple-props)
+ - [Never use return types if the function infers the type correctly](#never-use-return-types-if-the-function-infers-the-type-correctly)
+ - [Logger](#logger)
+ - [Comments](#comments)
+ - [Frontend](#frontend)
+
+## Architecture
+
+```
+π backend
+ π core
+ π logger
+ π utils
+ π infra
+ π drive-server-wip
+ π sqlite
+ π sqlite.module.ts
+ π services
+ π function1.ts
+ π function2.ts
+ π features
+ π backups
+ π sync
+ π sync.module.ts
+ π services
+ π function1.ts
+ π function2.ts
+π frontend
+ π core
+ π api
+```
+
+## Code
+
+### File template
+
+```ts
+type Props = { prop1: A; prop2: B };
+
+export function fn({ prop1, prop2 }: Props) {}
+```
+
+### Testing
+
+See [docs/TESTING.md](https://github.com/internxt/drive-desktop-core/blob/master/docs/TESTING.md).
+
+### Imports
+
+Always import the function used, not the module. This is to be consistent and import everything in the same way.
+
+```ts
+// bad
+import fs from 'node:fs';
+// good
+import { stat } from 'node:fs';
+```
+
+### Use function instead of arrow functions by default
+
+---
+
+We recommend always creating functions using the `function` keyword because:
+
+- We use the eslint `no-use-before-define` rule and we need to skip checking functions because they are hoisted (we cannot do this with arrow functions).
+
+We only use arrow functions when we want to define a function using a type.
+
+```ts
+// bad
+const connect = () => {}
+// good
+function connect() {}
+// good
+type func = () => void;
+const connect: func = () => {}
+```
+
+### Use object props instead of multiple props
+
+---
+
+https://github.com/internxt/drive-desktop/issues/545
+
+```ts
+// bad
+function connect(host: string, port: number) {}
+// good
+function connect({ host, port }: { host: string; port: number }) {}
+// default parameters
+function connect({ host, port = 5432 }: { host: string; port?: number }) {}
+```
+
+### Never use return types if the function infers the type correctly
+
+---
+
+We believe that using return types presents more problems than advantages:
+
+- Naming return types.
+- Maintaining return types.
+
+However, using return types has one advantage: if a function is supposed to return a `boolean` value and we forget to add a return value, it will infer an `undefined` value and we might start checking that function's return value using the wrong `undefined`. To solve this, we use the TypeScript rule `noImplicitReturns` to ensure that we don't forget to return a value in all branches of a function and that the function doesn't return `undefined` without explicitly defining it.
+
+```ts
+// bad
+function getNumber(): number {
+ return 8;
+}
+// good
+function getNumber() {
+ return 8;
+}
+```
+
+### Logger
+
+---
+
+Use logger.error for errors that should be logged in `drive-important.log` and logger.warn for all other errors. Almost all errors should be logged with logger.error. Do not concatenate strings in msg, otherwise it's more difficult to extend a log and also we won't have multiple colors for each prop.
+
+```ts
+logger.debug({
+ tag: 'TAG',
+ msg: 'Some message',
+ prop1,
+ prop2,
+});
+```
+
+### Comments
+
+---
+
+```ts
+/**
+ * vX.X.X Author
+ * Explain why it's done that way, not what it does.
+ * We should be able to understand what it does by reading the code, but not why we did it that way; that's the point of the comment.
+ * Also, don't delete these comments. The plan is for it to function as an Architecture Decision Record.
+ * Whenever we change something, we should retain the comments from the previous version to see the history of the decision.
+ */
+```
+
+### Frontend
+
+We'll follow a structure similar to Angular's with services and components. The service will be a hook that manages all the logic. Both the service and the component will be stored in the same file.
+
+```ts
+export function useComponent() {
+ const { t } = useI18n();
+ const { data, status } = useCustomHook();
+
+ const value = useMemo(() => {
+ switch (status) {
+ case 'loading':
+ return t('loading');
+ case 'error':
+ return '';
+ case 'success': {
+ return data;
+ }
+ }
+ }, [status]);
+
+ return { value };
+}
+
+export function Component() {
+ const { value } = useComponent();
+ return
{value}
;
+}
+```
diff --git a/README.md b/README.md
index 6cc3d1f139..9413b093cd 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,10 @@
[](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [](https://sonarcloud.io/summary/new_code?id=internxt_node-win)
+## Core
+
+[](https://nodejs.org/download/release/latest-iron/) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core)
+
# Setup
## Windows
diff --git a/docs/TESTING.md b/docs/TESTING.md
new file mode 100644
index 0000000000..a3bc98e0f9
--- /dev/null
+++ b/docs/TESTING.md
@@ -0,0 +1,146 @@
+# Testing
+
+## Table of Contents
+
+- [Types](#types)
+- [Describe](#describe)
+- [Mocks](#mocks)
+- [Assert](#assert)
+- [Structure](#structure)
+- [Frontend](#frontend)
+
+## Types
+
+- Unit tests (`service.test.ts`). It just tests the service function inside the file and mocks all other functions.
+- Infra tests (`anything.infra.test.ts`). It tests multiple functions and not only a service function or it is a long test. We want to keep these tests in a separate runner so as not to block the main runner with slow tests.
+
+## Describe
+
+Use `name-of-file` in describe. Why?
+
+- The file can have more that one function.
+- In an infra test maybe we are not calling a function directly; for example, we may use the watcher.
+
+```ts
+describe('name-of-file', () => {});
+```
+
+## Mocks
+
+By default use `partialSpyOn` in all mocks. Node modules are the only ones that need `vi.mock` and `deepMocked`.
+
+```ts
+import * as depModule from 'module';
+
+describe('name-of-file', () => {
+ const depMock = partialSpyOn(depModule, 'dep');
+
+ beforeEach(() => {
+ depMock.mockReturnValue('value');
+ });
+});
+```
+
+```ts
+import { dep } from 'node-module';
+
+vi.mock(import('node-module'));
+
+describe('name-of-file', () => {
+ const depMock = deepMocked(dep);
+
+ beforeEach(() => {
+ depMock.mockReturnValue('value');
+ });
+});
+```
+
+## Assert
+
+To check the calls of a depMock we use `call` (just one call) or `calls` (0 or more calls). We use only `toHaveLength`, `toBe`, `toMatchObject` and `toStrictEqual` for assertions.
+
+```ts
+import * as depModule from 'module';
+
+describe('name-of-file', () => {
+ const depMock = partialSpyOn(depModule, 'dep');
+
+ it('should do x when y', () => {
+ // When
+ const res = fn();
+ // Then
+ expect(res).toHaveLength();
+ expect(res).toBe();
+ expect(res).toMatchObject();
+ expect(res).toStrictEqual();
+
+ call(depMock).toBe();
+ call(depMock).toMatchObject();
+ call(depMock).toStrictEqual();
+
+ calls(depMock).toHaveLength();
+ calls(depMock).toMatchObject();
+ calls(depMock).toStrictEqual();
+ });
+});
+```
+
+## Structure
+
+```ts
+import * as dep1Module from 'module';
+import * as dep2Module from 'module';
+
+describe('name-of-file', () => {
+ const dep1Mock = partialSpyOn(dep1Module, 'dep');
+ const dep2Mock = partialSpyOn(dep2Module, 'dep');
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ dep1Mock.mockReturnValue('value1');
+ dep2Mock.mockReturnValue('value1');
+
+ props = mockProps({ prop: 'prop1' });
+ });
+
+ it('should do x when y', () => {
+ // Given
+ dep1Mock.mockReturnValue('value2');
+ props.prop = 'prop2';
+ // When
+ const res = fn(props);
+ // Then
+ expect(res).toMatchObject([{ res: 'value2' }, { res: 'value1' }]);
+ call(dep1Mock).toStrictEqual([{ prop: 'prop2' }]);
+ });
+});
+```
+
+## Frontend
+
+Testing the frontend is more complicated due to all the possible interactions we have. Therefore, to keep it as testable as possible and with as little code as possible, we'll only test the component logic (service).
+
+```ts
+import { renderHook } from '@testing-library/react-hooks';
+import * as depModule from 'module';
+
+describe('name-of-file', () => {
+ const depMock = partialSpyOn(depModule, 'dep');
+
+ it('should do x when y', () => {
+ // Given
+ depMock.mockReturnValue('value1');
+ // When
+ const { result, rerender } = renderHook(() => useComponent());
+ // Then
+ expect(result.current.value).toBe('value1');
+ // Given
+ depMock.mockReturnValue('value2');
+ // When
+ rerender();
+ // Then
+ expect(result.current.value).toBe('value2');
+ });
+});
+```
diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js
new file mode 100644
index 0000000000..47b87a98e8
--- /dev/null
+++ b/packages/core/.eslintrc.js
@@ -0,0 +1,71 @@
+module.exports = {
+ root: true,
+ plugins: ['@typescript-eslint', 'import', 'unicorn', '@tanstack/query', 'sonarjs'],
+ extends: [
+ 'plugin:prettier/recommended',
+ 'plugin:@typescript-eslint/recommended-type-checked',
+ 'plugin:@tanstack/eslint-plugin-query/recommended',
+ 'plugin:sonarjs/recommended-legacy',
+ ],
+ parser: '@typescript-eslint/parser',
+ ignorePatterns: ['node_modules', 'build', 'coverage'],
+ overrides: [
+ {
+ files: ['*.ts', '*.tsx'],
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: './tsconfig.json',
+ },
+ },
+ {
+ files: ['*.test.ts', '*.test.tsx'],
+ rules: {
+ '@typescript-eslint/unbound-method': 'off',
+ 'sonarjs/os-command': 'off',
+ },
+ },
+ ],
+ rules: {
+ '@typescript-eslint/await-thenable': 'error',
+ '@typescript-eslint/ban-ts-comment': 'error',
+ '@typescript-eslint/ban-types': 'off',
+ '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
+ '@typescript-eslint/no-empty-function': 'off',
+ '@typescript-eslint/no-empty-interface': 'off',
+ '@typescript-eslint/no-empty-object-type': 'off',
+ '@typescript-eslint/no-floating-promises': 'error',
+ '@typescript-eslint/no-unsafe-assignment': 'off',
+ '@typescript-eslint/no-unsafe-member-access': 'off',
+ '@typescript-eslint/no-unsafe-return': 'off',
+ '@typescript-eslint/no-unused-expressions': 'error',
+ '@typescript-eslint/prefer-promise-reject-errors': 'off',
+ '@typescript-eslint/restrict-template-expressions': 'off',
+ 'array-callback-return': 'error',
+ 'import/no-default-export': 'error',
+ 'max-len': ['error', { code: 140, ignoreStrings: true, ignoreTemplateLiterals: true }],
+ 'no-async-promise-executor': 'error',
+ 'no-await-in-loop': 'off',
+ 'no-console': 'error',
+ 'no-empty': 'off',
+ 'no-throw-literal': 'error',
+ 'no-unused-expressions': 'off',
+ 'no-use-before-define': ['error', { functions: false }],
+ 'object-shorthand': 'error',
+ 'require-await': 'error',
+ 'sonarjs/no-commented-code': 'off',
+ 'sonarjs/no-empty-test-file': 'off',
+ 'sonarjs/no-redundant-optional': 'off',
+ 'sonarjs/todo-tag': 'off',
+ 'unicorn/catch-error-name': 'error',
+ 'unicorn/filename-case': ['error', { case: 'kebabCase' }],
+ 'unicorn/no-array-for-each': 'error',
+ 'unicorn/prefer-node-protocol': 'error',
+ 'padding-line-between-statements': [
+ 'error',
+ { blankLine: 'always', prev: '*', next: 'block' },
+ { blankLine: 'always', prev: '*', next: 'class' },
+ { blankLine: 'always', prev: '*', next: 'function' },
+ { blankLine: 'always', prev: 'multiline-expression', next: 'multiline-expression' },
+ ],
+ },
+};
diff --git a/packages/core/.gitignore b/packages/core/.gitignore
new file mode 100644
index 0000000000..98c30168f6
--- /dev/null
+++ b/packages/core/.gitignore
@@ -0,0 +1,4 @@
+/build
+/coverage
+/node_modules
+/test-files
diff --git a/packages/core/.prettierrc.js b/packages/core/.prettierrc.js
new file mode 100644
index 0000000000..d2de3a5221
--- /dev/null
+++ b/packages/core/.prettierrc.js
@@ -0,0 +1,17 @@
+module.exports = {
+ arrowParens: 'always',
+ bracketSameLine: true,
+ bracketSpacing: true,
+ endOfLine: 'lf',
+ importOrder: ['^@/(.*)$', '^[./]'],
+ importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
+ importOrderSeparation: true,
+ plugins: [require.resolve('@trivago/prettier-plugin-sort-imports'), require.resolve('prettier-plugin-tailwindcss')],
+ printWidth: 140,
+ proseWrap: 'never',
+ semi: true,
+ singleQuote: true,
+ tabWidth: 2,
+ trailingComma: 'all',
+ useTabs: false,
+};
diff --git a/packages/core/nodemon.json b/packages/core/nodemon.json
new file mode 100644
index 0000000000..c455887bfd
--- /dev/null
+++ b/packages/core/nodemon.json
@@ -0,0 +1,7 @@
+{
+ "exec": "npm run build",
+ "ext": "ts,tsx",
+ "ignore": ["**/*.test.ts"],
+ "verbose": true,
+ "watch": ["src"]
+}
diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json
new file mode 100644
index 0000000000..ff2afde058
--- /dev/null
+++ b/packages/core/package-lock.json
@@ -0,0 +1,9188 @@
+{
+ "name": "@internxt/drive-desktop-core",
+ "version": "0.1.13",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@internxt/drive-desktop-core",
+ "version": "0.1.13",
+ "license": "MIT",
+ "dependencies": {
+ "@internxt/sdk": "^1.11.10",
+ "@phosphor-icons/react": "2.0.9",
+ "@tanstack/react-virtual": "^3.13.12",
+ "check-disk-space": "^3.4.0",
+ "electron-log": "^5.4.1",
+ "react": "^17.0.2",
+ "uuid": "^13.0.0"
+ },
+ "devDependencies": {
+ "@tanstack/eslint-plugin-query": "^5.81.2",
+ "@trivago/prettier-plugin-sort-imports": "^5.2.2",
+ "@types/node": "^20.19.9",
+ "@types/react": "17.0.38",
+ "@typescript-eslint/eslint-plugin": "^8.37.0",
+ "@typescript-eslint/parser": "^8.37.0",
+ "@vitest/coverage-v8": "^3.2.4",
+ "depcheck": "^1.4.7",
+ "electron": "^37.2.3",
+ "eslint": "^8.57.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-prettier": "^5.5.3",
+ "eslint-plugin-sonarjs": "^3.0.5",
+ "eslint-plugin-unicorn": "^56.0.1",
+ "nodemon": "^3.1.10",
+ "prettier": "^3.6.2",
+ "prettier-plugin-tailwindcss": "^0.7.0",
+ "rimraf": "^6.0.1",
+ "ts-essentials": "^10.1.1",
+ "ts-prune": "^0.10.3",
+ "tsc-alias": "^1.8.16",
+ "typescript": "^5.8.3",
+ "vite": "^6.3.5",
+ "vite-tsconfig-paths": "^5.1.4",
+ "vitest": "^3.2.4",
+ "vitest-mock-extended": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18.20.8",
+ "npm": ">=10.0.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
+ "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/@electron/get/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
+ "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
+ "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
+ "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
+ "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
+ "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
+ "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
+ "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
+ "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
+ "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
+ "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
+ "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
+ "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
+ "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
+ "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
+ "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
+ "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
+ "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
+ "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
+ "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
+ "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
+ "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@internxt/sdk": {
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/@internxt/sdk/-/sdk-1.11.10.tgz",
+ "integrity": "sha512-wiOOIIy1VqXySCIPbz1Rv8D6uTXWaP1o8URudEpIzrejxCNFM1M505MLDPStZdEmBW3ESJQa64LouCXUBQWKQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.12.2",
+ "uuid": "11.1.0"
+ }
+ },
+ "node_modules/@internxt/sdk/node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
+ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
+ "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@phosphor-icons/react": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.9.tgz",
+ "integrity": "sha512-/dtQ0M9MXAr35wy8zPlwF684EvYRvGWZPAv+Bd0BR4vzIhjzfLBdHSovFxSP1rj3UOHvVR08qgRL04Kv90oqHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8",
+ "react-dom": ">= 16.8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.1.tgz",
+ "integrity": "sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.1.tgz",
+ "integrity": "sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.1.tgz",
+ "integrity": "sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.1.tgz",
+ "integrity": "sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.1.tgz",
+ "integrity": "sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.1.tgz",
+ "integrity": "sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.1.tgz",
+ "integrity": "sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.1.tgz",
+ "integrity": "sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.1.tgz",
+ "integrity": "sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.1.tgz",
+ "integrity": "sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.1.tgz",
+ "integrity": "sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.1.tgz",
+ "integrity": "sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.1.tgz",
+ "integrity": "sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.1.tgz",
+ "integrity": "sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.1.tgz",
+ "integrity": "sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.1.tgz",
+ "integrity": "sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.1.tgz",
+ "integrity": "sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.1.tgz",
+ "integrity": "sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.1.tgz",
+ "integrity": "sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.1.tgz",
+ "integrity": "sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query": {
+ "version": "5.81.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.81.2.tgz",
+ "integrity": "sha512-h4k6P6fm5VhKP5NkK+0TTVpGGyKQdx6tk7NYYG7J7PkSu7ClpLgBihw7yzK8N3n5zPaF3IMyErxfoNiXWH/3/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.18.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
+ "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
+ "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@trivago/prettier-plugin-sort-imports": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
+ "integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@babel/generator": "^7.26.5",
+ "@babel/parser": "^7.26.7",
+ "@babel/traverse": "^7.26.7",
+ "@babel/types": "^7.26.7",
+ "javascript-natural-sort": "^0.7.1",
+ "lodash": "^4.17.21"
+ },
+ "engines": {
+ "node": ">18.12"
+ },
+ "peerDependencies": {
+ "@vue/compiler-sfc": "3.x",
+ "prettier": "2.x - 3.x",
+ "prettier-plugin-svelte": "3.x",
+ "svelte": "4.x || 5.x"
+ },
+ "peerDependenciesMeta": {
+ "@vue/compiler-sfc": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ts-morph/common": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.12.3.tgz",
+ "integrity": "sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "^3.2.7",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^1.0.4",
+ "path-browserify": "^1.0.1"
+ }
+ },
+ "node_modules/@ts-morph/common/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@ts-morph/common/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/minimatch": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
+ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
+ "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "17.0.38",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz",
+ "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+ "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
+ "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.38.0",
+ "@typescript-eslint/type-utils": "8.38.0",
+ "@typescript-eslint/utils": "8.38.0",
+ "@typescript-eslint/visitor-keys": "8.38.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.38.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
+ "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.38.0",
+ "@typescript-eslint/types": "8.38.0",
+ "@typescript-eslint/typescript-estree": "8.38.0",
+ "@typescript-eslint/visitor-keys": "8.38.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
+ "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.38.0",
+ "@typescript-eslint/types": "^8.38.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
+ "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.38.0",
+ "@typescript-eslint/visitor-keys": "8.38.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
+ "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
+ "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.38.0",
+ "@typescript-eslint/typescript-estree": "8.38.0",
+ "@typescript-eslint/utils": "8.38.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
+ "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
+ "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.38.0",
+ "@typescript-eslint/tsconfig-utils": "8.38.0",
+ "@typescript-eslint/types": "8.38.0",
+ "@typescript-eslint/visitor-keys": "8.38.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
+ "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.38.0",
+ "@typescript-eslint/types": "8.38.0",
+ "@typescript-eslint/typescript-estree": "8.38.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
+ "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.38.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitest/coverage-v8": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
+ "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@bcoe/v8-coverage": "^1.0.2",
+ "ast-v8-to-istanbul": "^0.3.3",
+ "debug": "^4.4.1",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-lib-source-maps": "^5.0.6",
+ "istanbul-reports": "^3.1.7",
+ "magic-string": "^0.30.17",
+ "magicast": "^0.3.5",
+ "std-env": "^3.9.0",
+ "test-exclude": "^7.0.1",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "3.2.4",
+ "vitest": "3.2.4"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
+ "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@vue/shared": "3.5.18",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
+ "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.18",
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz",
+ "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@vue/compiler-core": "3.5.18",
+ "@vue/compiler-dom": "3.5.18",
+ "@vue/compiler-ssr": "3.5.18",
+ "@vue/shared": "3.5.18",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz",
+ "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.18",
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz",
+ "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-differ": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
+ "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ast-v8-to-istanbul": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz",
+ "integrity": "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "estree-walker": "^3.0.3",
+ "js-tokens": "^9.0.1"
+ }
+ },
+ "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/builtin-modules": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsite": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+ "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001727",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
+ "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz",
+ "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-disk-space": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz",
+ "integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz",
+ "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clean-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
+ "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/clean-regexp/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/code-block-writer": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz",
+ "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.44.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz",
+ "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depcheck": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-1.4.7.tgz",
+ "integrity": "sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@babel/traverse": "^7.23.2",
+ "@vue/compiler-sfc": "^3.3.4",
+ "callsite": "^1.0.0",
+ "camelcase": "^6.3.0",
+ "cosmiconfig": "^7.1.0",
+ "debug": "^4.3.4",
+ "deps-regex": "^0.2.0",
+ "findup-sync": "^5.0.0",
+ "ignore": "^5.2.4",
+ "is-core-module": "^2.12.0",
+ "js-yaml": "^3.14.1",
+ "json5": "^2.2.3",
+ "lodash": "^4.17.21",
+ "minimatch": "^7.4.6",
+ "multimatch": "^5.0.0",
+ "please-upgrade-node": "^3.2.0",
+ "readdirp": "^3.6.0",
+ "require-package-name": "^2.0.1",
+ "resolve": "^1.22.3",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.5.4",
+ "yargs": "^16.2.0"
+ },
+ "bin": {
+ "depcheck": "bin/depcheck.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/depcheck/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/depcheck/node_modules/minimatch": {
+ "version": "7.4.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
+ "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/deps-regex": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.2.0.tgz",
+ "integrity": "sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron": {
+ "version": "37.2.4",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-37.2.4.tgz",
+ "integrity": "sha512-F1WDDvY60TpFwGyW+evNB5q0Em8PamcDTVIKB2NaiaKEbNC2Fabn8Wyxy5g+Anirr1K40eKGjfSJhWEUbI1TOw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/get": "^2.0.0",
+ "@types/node": "^22.7.7",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js"
+ },
+ "engines": {
+ "node": ">= 12.20.55"
+ }
+ },
+ "node_modules/electron-log": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.1.tgz",
+ "integrity": "sha512-QvisA18Z++8E3Th0zmhUelys9dEv7aIeXJlbFw3UrxCc8H9qSRW0j8/ooTef/EtHui8tVmbKSL+EIQzP9GoRLg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.191",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz",
+ "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/electron/node_modules/@types/node": {
+ "version": "22.16.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz",
+ "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
+ "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
+ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.8",
+ "@esbuild/android-arm": "0.25.8",
+ "@esbuild/android-arm64": "0.25.8",
+ "@esbuild/android-x64": "0.25.8",
+ "@esbuild/darwin-arm64": "0.25.8",
+ "@esbuild/darwin-x64": "0.25.8",
+ "@esbuild/freebsd-arm64": "0.25.8",
+ "@esbuild/freebsd-x64": "0.25.8",
+ "@esbuild/linux-arm": "0.25.8",
+ "@esbuild/linux-arm64": "0.25.8",
+ "@esbuild/linux-ia32": "0.25.8",
+ "@esbuild/linux-loong64": "0.25.8",
+ "@esbuild/linux-mips64el": "0.25.8",
+ "@esbuild/linux-ppc64": "0.25.8",
+ "@esbuild/linux-riscv64": "0.25.8",
+ "@esbuild/linux-s390x": "0.25.8",
+ "@esbuild/linux-x64": "0.25.8",
+ "@esbuild/netbsd-arm64": "0.25.8",
+ "@esbuild/netbsd-x64": "0.25.8",
+ "@esbuild/openbsd-arm64": "0.25.8",
+ "@esbuild/openbsd-x64": "0.25.8",
+ "@esbuild/openharmony-arm64": "0.25.8",
+ "@esbuild/sunos-x64": "0.25.8",
+ "@esbuild/win32-arm64": "0.25.8",
+ "@esbuild/win32-ia32": "0.25.8",
+ "@esbuild/win32-x64": "0.25.8"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.1",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.16.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.1",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.9",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz",
+ "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.7"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-sonarjs": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.5.tgz",
+ "integrity": "sha512-dI62Ff3zMezUToi161hs2i1HX1ie8Ia2hO0jtNBfdgRBicAG4ydy2WPt0rMTrAe3ZrlqhpAO3w1jcQEdneYoFA==",
+ "dev": true,
+ "license": "LGPL-3.0-only",
+ "dependencies": {
+ "@eslint-community/regexpp": "4.12.1",
+ "builtin-modules": "3.3.0",
+ "bytes": "3.1.2",
+ "functional-red-black-tree": "1.0.1",
+ "jsx-ast-utils-x": "0.1.0",
+ "lodash.merge": "4.6.2",
+ "minimatch": "9.0.5",
+ "scslre": "0.3.0",
+ "semver": "7.7.2",
+ "typescript": ">=5"
+ },
+ "peerDependencies": {
+ "eslint": "^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-unicorn": {
+ "version": "56.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-56.0.1.tgz",
+ "integrity": "sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "ci-info": "^4.0.0",
+ "clean-regexp": "^1.0.0",
+ "core-js-compat": "^3.38.1",
+ "esquery": "^1.6.0",
+ "globals": "^15.9.0",
+ "indent-string": "^4.0.0",
+ "is-builtin-module": "^3.2.1",
+ "jsesc": "^3.0.2",
+ "pluralize": "^8.0.0",
+ "read-pkg-up": "^7.0.1",
+ "regexp-tree": "^0.1.27",
+ "regjsparser": "^0.10.0",
+ "semver": "^7.6.3",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.56.0"
+ }
+ },
+ "node_modules/eslint-plugin-unicorn/node_modules/globals": {
+ "version": "15.15.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
+ "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "homedir-polyfill": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/findup-sync": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz",
+ "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.4",
+ "resolve-dir": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flat-cache/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/flat-cache/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/flat-cache/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/flat-cache/node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
+ "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
+ "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.3.1",
+ "jackspeak": "^4.1.1",
+ "minimatch": "^10.0.3",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^2.0.0"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
+ "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/brace-expansion": "^5.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/global-prefix/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/globrex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
+ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-passwd": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-builtin-module": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+ "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "builtin-modules": "^3.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
+ "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/javascript-natural-sort": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
+ "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsx-ast-utils-x": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz",
+ "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz",
+ "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
+ "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/multimatch": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
+ "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "^3.0.3",
+ "array-differ": "^3.0.0",
+ "array-union": "^2.1.0",
+ "arrify": "^2.0.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/multimatch/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/multimatch/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mylas": {
+ "version": "2.1.13",
+ "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz",
+ "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/raouldeheer"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
+ "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/please-upgrade-node": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver-compare": "^1.0.0"
+ }
+ },
+ "node_modules/plimit-lit": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
+ "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "queue-lit": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.0.tgz",
+ "integrity": "sha512-zpRZhkfwq1cNmbKhmKzXKuKFdkgXZXlf6p+KttD75v6pGz1FxmcKMc4RKdw97GYBKBbout4113HSLaBJAomFDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-hermes": "*",
+ "@prettier/plugin-oxc": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-hermes": {
+ "optional": true
+ },
+ "@prettier/plugin-oxc": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-lit": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz",
+ "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/react": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
+ "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
+ "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.2"
+ },
+ "peerDependencies": {
+ "react": "17.0.2"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/refa": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz",
+ "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0"
+ },
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexp-ast-analysis": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz",
+ "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0",
+ "refa": "^0.12.1"
+ },
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/regexp-tree": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "regexp-tree": "bin/regexp-tree"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regjsparser": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz",
+ "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~0.5.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/regjsparser/node_modules/jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-package-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
+ "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
+ "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^11.0.0",
+ "package-json-from-dist": "^1.0.0"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/roarr/node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/rollup": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.1.tgz",
+ "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.46.1",
+ "@rollup/rollup-android-arm64": "4.46.1",
+ "@rollup/rollup-darwin-arm64": "4.46.1",
+ "@rollup/rollup-darwin-x64": "4.46.1",
+ "@rollup/rollup-freebsd-arm64": "4.46.1",
+ "@rollup/rollup-freebsd-x64": "4.46.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.46.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.46.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.46.1",
+ "@rollup/rollup-linux-arm64-musl": "4.46.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.46.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.46.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.46.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.46.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.46.1",
+ "@rollup/rollup-linux-x64-gnu": "4.46.1",
+ "@rollup/rollup-linux-x64-musl": "4.46.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.46.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.46.1",
+ "@rollup/rollup-win32-x64-msvc": "4.46.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/scslre": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
+ "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0",
+ "refa": "^0.12.0",
+ "regexp-ast-analysis": "^0.7.0"
+ },
+ "engines": {
+ "node": "^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/serialize-error/node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.21",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
+ "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
+ "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
+ "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^10.4.1",
+ "minimatch": "^9.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/test-exclude/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/test-exclude/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/test-exclude/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
+ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/true-myth": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/true-myth/-/true-myth-4.1.1.tgz",
+ "integrity": "sha512-rqy30BSpxPznbbTcAcci90oZ1YR4DqvKcNXNerG5gQBU2v4jk0cygheiul5J6ExIMrgDVuanv/MkGfqZbKrNNg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "10.* || >= 12.*"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-essentials": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.1.1.tgz",
+ "integrity": "sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=4.5.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-morph": {
+ "version": "13.0.3",
+ "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-13.0.3.tgz",
+ "integrity": "sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ts-morph/common": "~0.12.3",
+ "code-block-writer": "^11.0.0"
+ }
+ },
+ "node_modules/ts-prune": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/ts-prune/-/ts-prune-0.10.3.tgz",
+ "integrity": "sha512-iS47YTbdIcvN8Nh/1BFyziyUqmjXz7GVzWu02RaZXqb+e/3Qe1B7IQ4860krOeCGUeJmterAlaM2FRH0Ue0hjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^6.2.1",
+ "cosmiconfig": "^7.0.1",
+ "json5": "^2.1.3",
+ "lodash": "^4.17.21",
+ "true-myth": "^4.1.0",
+ "ts-morph": "^13.0.1"
+ },
+ "bin": {
+ "ts-prune": "lib/index.js"
+ }
+ },
+ "node_modules/ts-prune/node_modules/commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/tsc-alias": {
+ "version": "1.8.16",
+ "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz",
+ "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "commander": "^9.0.0",
+ "get-tsconfig": "^4.10.0",
+ "globby": "^11.0.4",
+ "mylas": "^2.1.9",
+ "normalize-path": "^3.0.0",
+ "plimit-lit": "^1.2.6"
+ },
+ "bin": {
+ "tsc-alias": "dist/bin/index.js"
+ },
+ "engines": {
+ "node": ">=16.20.2"
+ }
+ },
+ "node_modules/tsconfck": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
+ "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tsconfck": "bin/tsconfck.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-tsconfig-paths": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
+ "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "globrex": "^0.1.2",
+ "tsconfck": "^3.0.3"
+ },
+ "peerDependencies": {
+ "vite": "*"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest-mock-extended": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-3.1.0.tgz",
+ "integrity": "sha512-vCM0VkuocOUBwwqwV7JB7YStw07pqeKvEIrZnR8l3PtwYi6rAAJAyJACeC1UYNfbQWi85nz7EdiXWBFI5hll2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ts-essentials": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "typescript": "3.x || 4.x || 5.x",
+ "vitest": ">=3.0.0"
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000000..8ec543313f
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "@internxt/drive-desktop-core",
+ "version": "0.1.13",
+ "author": "Internxt ",
+ "license": "MIT",
+ "files": [
+ "build"
+ ],
+ "scripts": {
+ "build": "rimraf build && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
+ "start": "nodemon",
+ "init:dev": "npm install && node node_modules/electron/install.js",
+ "========== Code style ==========": "",
+ "lint": "eslint . --ext .ts,.tsx",
+ "lint:fix": "npm run lint -- --fix",
+ "format": "prettier src tests --check",
+ "format:fix": "prettier src tests --write",
+ "type-check": "tsc",
+ "find-deadcode": "ts-prune -i index.ts -e",
+ "========== Testing ==========": "",
+ "test": "vitest",
+ "test:once": "npm run test -- --run",
+ "test:one": "npm run test -- related x",
+ "test:cov": "npm run test:once -- --coverage",
+ "========== Other ==========": "",
+ "depcheck": "depcheck"
+ },
+ "devDependencies": {
+ "@tanstack/eslint-plugin-query": "^5.81.2",
+ "@trivago/prettier-plugin-sort-imports": "^5.2.2",
+ "@types/node": "^20.19.9",
+ "@types/react": "17.0.38",
+ "@typescript-eslint/eslint-plugin": "^8.37.0",
+ "@typescript-eslint/parser": "^8.37.0",
+ "@vitest/coverage-v8": "^3.2.4",
+ "depcheck": "^1.4.7",
+ "electron": "^37.2.3",
+ "eslint": "^8.57.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-prettier": "^5.5.3",
+ "eslint-plugin-sonarjs": "^3.0.5",
+ "eslint-plugin-unicorn": "^56.0.1",
+ "nodemon": "^3.1.10",
+ "prettier": "^3.6.2",
+ "prettier-plugin-tailwindcss": "^0.7.0",
+ "rimraf": "^6.0.1",
+ "ts-essentials": "^10.1.1",
+ "ts-prune": "^0.10.3",
+ "tsc-alias": "^1.8.16",
+ "typescript": "^5.8.3",
+ "vite": "^6.3.5",
+ "vite-tsconfig-paths": "^5.1.4",
+ "vitest": "^3.2.4",
+ "vitest-mock-extended": "^3.1.0"
+ },
+ "dependencies": {
+ "@internxt/sdk": "^1.11.10",
+ "@phosphor-icons/react": "2.0.9",
+ "@tanstack/react-virtual": "^3.13.12",
+ "check-disk-space": "^3.4.0",
+ "electron-log": "^5.4.1",
+ "react": "^17.0.2",
+ "uuid": "^13.0.0"
+ },
+ "engines": {
+ "node": ">=18.20.8",
+ "npm": ">=10.0.0"
+ }
+}
diff --git a/packages/core/sonar-project.properties b/packages/core/sonar-project.properties
new file mode 100644
index 0000000000..c067b6c435
--- /dev/null
+++ b/packages/core/sonar-project.properties
@@ -0,0 +1,5 @@
+sonar.projectKey=internxt_drive-desktop-core
+sonar.organization=internxt
+sonar.javascript.lcov.reportPaths=./coverage/lcov.info
+sonar.exclusions=**/*.test.ts,**/i18n/locales/*.ts
+sonar.coverage.exclusions=**/*.module.ts,./src/backend/index.ts
diff --git a/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.test.ts b/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.test.ts
new file mode 100644
index 0000000000..666b958a2b
--- /dev/null
+++ b/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.test.ts
@@ -0,0 +1,26 @@
+import { convertUTCDateToGMT2 } from './convert-utc-date-to-gmt2';
+
+describe('convertUTCDateToGMT2', () => {
+ it('should return a string in the format YYYY-MM-DD HH:mm:SS.SSS', () => {
+ const result = convertUTCDateToGMT2(new Date());
+ expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/);
+ });
+
+ it('should correctly convert a specific UTC date to GMT+2', () => {
+ const input = new Date('2025-04-17T12:00:00.000Z'); // UTC
+ const result = convertUTCDateToGMT2(input);
+ expect(result).toBe('2025-04-17 14:00:00.000'); // +2h
+ });
+
+ it('should correctly convert and preserve milliseconds', () => {
+ const input = new Date('2025-04-17T23:59:59.987Z');
+ const result = convertUTCDateToGMT2(input);
+ expect(result).toBe('2025-04-18 01:59:59.987');
+ });
+
+ it('should correctly handle dates near midnight UTC', () => {
+ const date = new Date('2025-04-17T23:30:00.000Z'); // UTC
+ const result = convertUTCDateToGMT2(date);
+ expect(result).toBe('2025-04-18 01:30:00.000'); // next day in GMT+2
+ });
+});
diff --git a/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.ts b/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.ts
new file mode 100644
index 0000000000..b0a0b50827
--- /dev/null
+++ b/packages/core/src/backend/core/logger/convert-utc-date-to-gmt2.ts
@@ -0,0 +1,20 @@
+function pad(n: number) {
+ return n.toString().padStart(2, '0');
+}
+
+function padMs(n: number) {
+ return n.toString().padStart(3, '0');
+}
+
+function formatDate(date: Date) {
+ return (
+ `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ` +
+ `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}.` +
+ `${padMs(date.getUTCMilliseconds())}`
+ );
+}
+
+export function convertUTCDateToGMT2(date: Date) {
+ const gmt2Date = new Date(date.getTime() + 2 * 60 * 60 * 1000);
+ return formatDate(gmt2Date);
+}
diff --git a/packages/core/src/backend/core/logger/log-formatter.test.ts b/packages/core/src/backend/core/logger/log-formatter.test.ts
new file mode 100644
index 0000000000..22d7005676
--- /dev/null
+++ b/packages/core/src/backend/core/logger/log-formatter.test.ts
@@ -0,0 +1,40 @@
+import { FormatParams } from 'electron-log';
+
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { logFormatter } from './log-formatter';
+
+describe('logFormatter', () => {
+ it('should include GMT+2 formatted timestamp and message data', () => {
+ const formatParams: FormatParams = mockProps({
+ data: ['App started'],
+ level: 'info',
+ message: {
+ data: ['App started'],
+ level: 'info',
+ date: new Date('2025-04-17T12:00:00.000Z'),
+ },
+ });
+ const result = logFormatter(formatParams);
+
+ expect(result[0]).toBe('[2025-04-17 14:00:00.000]');
+ expect(result.slice(1)).toStrictEqual(['App started']);
+ });
+
+ it('should include all original data arguments after the timestamp', () => {
+ const formatParams: FormatParams = mockProps({
+ data: ['User', 42, 'logged in'],
+ level: 'info',
+ message: {
+ data: ['User', 42, 'logged in'],
+ level: 'info',
+ date: new Date('2025-04-17T10:00:00.000Z'),
+ },
+ });
+
+ const result = logFormatter(formatParams);
+
+ expect(result[0]).toBe('[2025-04-17 12:00:00.000]');
+ expect(result.slice(1)).toStrictEqual(['User', 42, 'logged in']);
+ });
+});
diff --git a/packages/core/src/backend/core/logger/log-formatter.ts b/packages/core/src/backend/core/logger/log-formatter.ts
new file mode 100644
index 0000000000..43a7ce31a3
--- /dev/null
+++ b/packages/core/src/backend/core/logger/log-formatter.ts
@@ -0,0 +1,7 @@
+import { FormatParams } from 'electron-log';
+
+import { convertUTCDateToGMT2 } from './convert-utc-date-to-gmt2';
+
+export function logFormatter(message: FormatParams) {
+ return [`[${convertUTCDateToGMT2(message.message.date)}]`, ...message.data];
+}
diff --git a/packages/core/src/backend/core/logger/logger.ts b/packages/core/src/backend/core/logger/logger.ts
new file mode 100644
index 0000000000..37cba31092
--- /dev/null
+++ b/packages/core/src/backend/core/logger/logger.ts
@@ -0,0 +1,105 @@
+import ElectronLog from 'electron-log';
+import { inspect } from 'node:util';
+
+type TTag = 'AUTH' | 'BACKUPS' | 'SYNC-ENGINE' | 'ANTIVIRUS' | 'NODE-WIN' | 'PRODUCTS' | 'CLEANER';
+type TLevel = 'debug' | 'warn' | 'error';
+
+export type TLoggerBody = {
+ tag?: TTag;
+ msg: string;
+ workspaceId?: string;
+ context?: Record;
+ [key: string]: unknown;
+};
+
+function getLevelStr(level: TLevel): string {
+ switch (level) {
+ case 'debug':
+ return ' ';
+ case 'warn':
+ return 'w';
+ case 'error':
+ return 'E';
+ }
+}
+
+function getProcessStr(): string {
+ switch (process.type) {
+ case 'browser':
+ return 'b';
+ case 'renderer':
+ return 'r';
+ case 'worker':
+ return 'w';
+ case 'utility':
+ return 'u';
+ case 'service-worker':
+ return 's';
+ }
+}
+
+function getTagStr(tag?: TTag): string {
+ switch (tag) {
+ case 'AUTH':
+ return 'auth';
+ case 'BACKUPS':
+ return 'back';
+ case 'SYNC-ENGINE':
+ return 'sync';
+ case 'ANTIVIRUS':
+ return 'anti';
+ case 'NODE-WIN':
+ return 'sync';
+ case 'CLEANER':
+ return 'clea';
+ case 'PRODUCTS':
+ return 'prod';
+ case undefined:
+ return ' ';
+ }
+}
+
+function prepareBody(level: TLevel, rawBody: TLoggerBody) {
+ const { tag, msg, workspaceId, ...rest } = rawBody;
+
+ const header = `${getLevelStr(level)} - ${getProcessStr()} - ${getTagStr(tag)}`;
+
+ rawBody = {
+ header,
+ msg,
+ ...(workspaceId && { workspaceId }),
+ ...rest,
+ };
+
+ const body = inspect(rawBody, { depth: Infinity, breakLength: Infinity });
+ const coloredBody = inspect(rawBody, { depth: Infinity, breakLength: Infinity, colors: true });
+ return { body, coloredBody };
+}
+
+function debug(rawBody: TLoggerBody) {
+ const { body, coloredBody } = prepareBody('debug', rawBody);
+ ElectronLog.silly(coloredBody);
+ ElectronLog.debug(body);
+ return new Error(rawBody.msg, { cause: rawBody.exc });
+}
+
+function warn(rawBody: TLoggerBody) {
+ const { body, coloredBody } = prepareBody('warn', rawBody);
+ ElectronLog.silly(coloredBody);
+ ElectronLog.debug(body);
+ return new Error(rawBody.msg, { cause: rawBody.exc });
+}
+
+function error(rawBody: TLoggerBody) {
+ const { body, coloredBody } = prepareBody('error', rawBody);
+ ElectronLog.silly(coloredBody);
+ ElectronLog.debug(body);
+ ElectronLog.info(body);
+ return new Error(rawBody.msg, { cause: rawBody.exc });
+}
+
+export const logger = {
+ debug,
+ warn,
+ error,
+};
diff --git a/packages/core/src/backend/core/logger/setup-electron-log.ts b/packages/core/src/backend/core/logger/setup-electron-log.ts
new file mode 100644
index 0000000000..74a5aa3ce7
--- /dev/null
+++ b/packages/core/src/backend/core/logger/setup-electron-log.ts
@@ -0,0 +1,48 @@
+import { ipcMain, shell } from 'electron';
+import ElectronLog from 'electron-log';
+import { join } from 'node:path';
+
+import { logFormatter } from './log-formatter';
+
+type Props = {
+ logsPath: string;
+};
+
+export function setupElectronLog({ logsPath }: Props) {
+ ElectronLog.initialize();
+
+ const defaultLogs = join(logsPath, 'drive.log');
+ const importantLogs = join(logsPath, 'drive-important.log');
+
+ ElectronLog.transports.file.resolvePathFn = (_, message) => {
+ if (message?.level === 'info') {
+ return importantLogs;
+ } else {
+ return defaultLogs;
+ }
+ };
+
+ /**
+ * v2.5.4 Daniel JimΓ©nez
+ * Based on this ticket we set the maximum to 1GB.
+ * https://inxt.atlassian.net/browse/BR-1244
+ */
+ ElectronLog.transports.file.maxSize = 1024 * 1024 * 1024;
+ ElectronLog.transports.file.format = logFormatter;
+ /**
+ * v2.5.6 Daniel JimΓ©nez
+ * Levels: silly < debug < verbose < info < log < warn < error
+ */
+ ElectronLog.transports.file.level = 'debug';
+ ElectronLog.transports.console.format = (message) => [...message.data];
+ ElectronLog.transports.console.writeFn = ({ message }) => {
+ if (message.level === 'silly') {
+ // eslint-disable-next-line no-console
+ console.log(`${message.data}`);
+ }
+ };
+
+ ipcMain.on('open-logs', () => {
+ void shell.openPath(logsPath);
+ });
+}
diff --git a/packages/core/src/backend/core/utils/brand.types.ts b/packages/core/src/backend/core/utils/brand.types.ts
new file mode 100644
index 0000000000..1491efc6f5
--- /dev/null
+++ b/packages/core/src/backend/core/utils/brand.types.ts
@@ -0,0 +1 @@
+export type Brand = T & { __brand: U };
diff --git a/packages/core/src/backend/core/utils/throw-wrapper.ts b/packages/core/src/backend/core/utils/throw-wrapper.ts
new file mode 100644
index 0000000000..ebf7cd724d
--- /dev/null
+++ b/packages/core/src/backend/core/utils/throw-wrapper.ts
@@ -0,0 +1,9 @@
+type Result = { data: NonNullable; error: undefined } | { data: undefined; error: Error };
+
+export function throwWrapper(fn: (...args: Args) => Promise>) {
+ return async (...args: Args) => {
+ const { data, error } = await fn(...args);
+ if (error) throw error;
+ return data;
+ };
+}
diff --git a/packages/core/src/backend/features/cleaner/cleaner.module.ts b/packages/core/src/backend/features/cleaner/cleaner.module.ts
new file mode 100644
index 0000000000..be4221e553
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/cleaner.module.ts
@@ -0,0 +1,35 @@
+import { appCacheFileFilter } from './filters/app-cache/app-cache-filter';
+import { isDirectoryWebBrowserRelated } from './filters/app-cache/is-directory-web-browser-related';
+import { logFileFilter } from './filters/logs/log-file-filter';
+import { scanFirefoxCacheProfiles } from './scan/firefox-web-cache/scan-firefox-cache-profiles';
+import { scanFirefoxProfiles } from './scan/firefox-web-storage/scan-firefox-profiles';
+import { processDirent } from './scan/process-dirent';
+import { scanDirectory } from './scan/scan-directory';
+import { scanSingleFile } from './scan/scan-single-file';
+import { scanSubDirectory } from './scan/scan-subdirectory';
+import { startCleanup } from './services/start-cleanup';
+import { stopCleanup } from './services/stop-cleanup';
+import { getAllItemsToDelete } from './utils/get-all-items-to-delete';
+import { getDiskSpace } from './utils/get-disk-space';
+import { getSelectedItemsForSection } from './utils/get-selected-items-for-section';
+import { isInternxtRelated } from './utils/is-file-internxt-related';
+import { isSafeWebBrowserFile } from './utils/is-safe-web-browser-file';
+
+export const CleanerModule = {
+ getDiskSpace,
+ isInternxtRelated,
+ getAllItemsToDelete,
+ getSelectedItemsForSection,
+ processDirent,
+ scanDirectory,
+ scanSingleFile,
+ scanSubDirectory,
+ startCleanup,
+ stopCleanup,
+ appCacheFileFilter,
+ isDirectoryWebBrowserRelated,
+ logFileFilter,
+ isSafeWebBrowserFile,
+ scanFirefoxCacheProfiles,
+ scanFirefoxProfiles,
+};
diff --git a/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.test.ts b/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.test.ts
new file mode 100644
index 0000000000..bf311768bc
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.test.ts
@@ -0,0 +1,34 @@
+import { CleanerContext } from '../../types/cleaner.types';
+import { appCacheFileFilter } from './app-cache-filter';
+
+describe('appCacheFileFilter', () => {
+ const ctx = {
+ appCache: {
+ criticalExtensions: ['.lock', '.pid', '.db', '.sqlite', '.sqlite3', '.sock', '.socket'],
+ criticalKeywords: ['session', 'state', 'preferences'],
+ },
+ } as unknown as CleanerContext;
+
+ it.each(['.lock', '.pid', '.db', '.sqlite', '.sqlite3', '.sock', '.socket'])('should return false for %s files', (extension) => {
+ const fileName = `test${extension}`;
+ expect(appCacheFileFilter({ ctx, fileName })).toBe(false);
+ });
+
+ it.each(['.LOCK', '.DB', '.PID'])('should handle uppercase extensions: %s', (extension) => {
+ expect(appCacheFileFilter({ ctx, fileName: `test${extension}` })).toBe(false);
+ });
+
+ it.each(['session', 'state', 'preferences'])('should return false for files containing %s', (keyword) => {
+ expect(appCacheFileFilter({ ctx, fileName: `app-${keyword}-config` })).toBe(false);
+ });
+
+ it.each(['SESSION', 'STATE', 'PREFERENCES'])('should handle uppercase keywords: %s', (keyword) => {
+ expect(appCacheFileFilter({ ctx, fileName: `test.${keyword}.xml` })).toBe(false);
+ });
+
+ it('should handle multiple dots', () => {
+ expect(appCacheFileFilter({ ctx, fileName: 'app.session.backup.db' })).toBe(false);
+ expect(appCacheFileFilter({ ctx, fileName: 'user-preferences-old.txt' })).toBe(false);
+ expect(appCacheFileFilter({ ctx, fileName: 'data.v1.2.3.cache' })).toBe(true);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.ts b/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.ts
new file mode 100644
index 0000000000..b7bf82084c
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/app-cache/app-cache-filter.ts
@@ -0,0 +1,18 @@
+import { extname } from 'node:path';
+
+import { CleanerContext } from '../../types/cleaner.types';
+
+type Props = {
+ ctx: CleanerContext;
+ fileName: string;
+};
+
+export function appCacheFileFilter({ ctx, fileName }: Props) {
+ const ext = extname(fileName).toLowerCase();
+ const lowerName = fileName.toLowerCase();
+
+ const excludeCriticalExtensions = ctx.appCache.criticalExtensions.includes(ext);
+ const excludeCriticalKeywords = ctx.appCache.criticalKeywords.some((keyword) => lowerName.includes(keyword));
+
+ return !excludeCriticalExtensions && !excludeCriticalKeywords;
+}
diff --git a/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.test.ts b/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.test.ts
new file mode 100644
index 0000000000..c512566d02
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.test.ts
@@ -0,0 +1,23 @@
+import { isDirectoryWebBrowserRelated } from './is-directory-web-browser-related';
+
+describe('isDirectoryWebBrowserRelated', () => {
+ it.each(['google', 'chromium'])('should return true for exact browser folder names: %s', (folderName) => {
+ expect(isDirectoryWebBrowserRelated({ folderName })).toBe(true);
+ });
+
+ it.each(['Google', 'CHROMIUM'])('should return true for browser names with case variations: %s', (folderName) => {
+ expect(isDirectoryWebBrowserRelated({ folderName })).toBe(true);
+ });
+
+ it.each(['google-stable', 'chromium-browser'])('should return true for folders containing browser names: %s', (folderName) => {
+ expect(isDirectoryWebBrowserRelated({ folderName })).toBe(true);
+ });
+
+ it('should return false for non-browser folder names', () => {
+ expect(isDirectoryWebBrowserRelated({ folderName: 'vscode' })).toBe(false);
+ });
+
+ it('should return false for empty strings', () => {
+ expect(isDirectoryWebBrowserRelated({ folderName: '' })).toBe(false);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.ts b/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.ts
new file mode 100644
index 0000000000..5ca95ddf27
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/app-cache/is-directory-web-browser-related.ts
@@ -0,0 +1,6 @@
+const WEB_BROWSER_DIRECTORIES = ['google', 'chromium', 'firefox', 'opera', 'brave', 'chrome', 'mozilla', 'edge'];
+
+export function isDirectoryWebBrowserRelated({ folderName }: { folderName: string }) {
+ const lowerName = folderName.toLowerCase();
+ return WEB_BROWSER_DIRECTORIES.some((browserName) => lowerName.includes(browserName));
+}
diff --git a/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.test.ts b/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.test.ts
new file mode 100644
index 0000000000..082c9d5396
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.test.ts
@@ -0,0 +1,34 @@
+import { CleanerContext } from '../../types/cleaner.types';
+import { logFileFilter } from './log-file-filter';
+
+describe('log-file-filter', () => {
+ const ctx = {
+ logFiles: {
+ safeExtensions: ['.log', '.txt', '.out', '.gz', '.bz2', '.xz'],
+ },
+ } as unknown as CleanerContext;
+
+ it.each(['application.log', 'debug.txt', 'output.out'])('should return true for safe log file extensions', (fileName) => {
+ expect(logFileFilter({ ctx, fileName })).toBe(true);
+ });
+
+ it.each(['application.log.gz', 'system.log.bz2', 'debug.log.xz'])('should return true for compressed log files', (fileName) => {
+ expect(logFileFilter({ ctx, fileName })).toBe(true);
+ });
+
+ it.each(['app.LOG', 'debug.TXT', 'error.OUT'])('should return true for case insensitive extensions', (fileName) => {
+ expect(logFileFilter({ ctx, fileName })).toBe(true);
+ });
+
+ it.each(['config.db', 'process.pid', 'app.lock'])('should return false for unsafe file extensions', (fileName) => {
+ expect(logFileFilter({ ctx, fileName })).toBe(false);
+ });
+
+ it('should return false for files without extensions', () => {
+ expect(logFileFilter({ ctx, fileName: 'logfile' })).toBe(false);
+ });
+
+ it('should return true for rotated log files matching regex pattern', () => {
+ expect(logFileFilter({ ctx, fileName: 'app.log.1' })).toBe(true);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.ts b/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.ts
new file mode 100644
index 0000000000..ff240e82e9
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/filters/logs/log-file-filter.ts
@@ -0,0 +1,17 @@
+import { CleanerContext } from '../../types/cleaner.types';
+
+type Props = { ctx: CleanerContext; fileName: string };
+
+export function logFileFilter({ ctx, fileName }: Props) {
+ const lowerName = fileName.toLowerCase();
+
+ const includeSafeExtensions = ctx.logFiles.safeExtensions.some((ext) => lowerName.endsWith(ext));
+ /**
+ * v0.1.1 Esteban Galvis
+ * Remove also files that match the pattern of rotated logs
+ * e.g. app.log.1, app.log.2, etc.
+ */
+ const checkRotatedLog = /\.log(\.\d+)?$/.test(lowerName);
+
+ return includeSafeExtensions || checkRotatedLog;
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.test.ts b/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.test.ts
new file mode 100644
index 0000000000..4414088e1e
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.test.ts
@@ -0,0 +1,76 @@
+import { Dirent } from 'node:fs';
+import { readdir } from 'node:fs/promises';
+
+import { mockProps, partialSpyOn, deepMocked } from '@/tests/vitest/utils.helper.test';
+
+import * as scanDirectoryModule from '../../scan/scan-directory';
+import { CleanableItem } from '../../types/cleaner.types';
+import * as isFirefoxProfileDirectoryModule from '../../utils/is-firefox-profile-directory';
+import { scanFirefoxCacheProfiles } from './scan-firefox-cache-profiles';
+
+vi.mock(import('node:fs/promises'));
+
+describe('scanFirefoxCacheProfiles', () => {
+ const firefoxCacheDir = '/home/user/.cache/mozilla/firefox';
+ const mockedScanDirectory = partialSpyOn(scanDirectoryModule, 'scanDirectory');
+ const mockedIsFirefoxProfileDirectory = partialSpyOn(isFirefoxProfileDirectoryModule, 'isFirefoxProfileDirectory');
+ const readdirMock = deepMocked(readdir);
+
+ const createMockDirent = (name: string, isDirectory = true) =>
+ ({
+ name,
+ isDirectory: () => isDirectory,
+ isFile: () => !isDirectory,
+ }) as unknown as Dirent;
+
+ const createMockItem = (fileName: string, size: number, basePath: string): CleanableItem => ({
+ fullPath: `${basePath}/${fileName}`,
+ fileName,
+ sizeInBytes: size,
+ });
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ mockedScanDirectory.mockResolvedValue([]);
+ mockedIsFirefoxProfileDirectory.mockReturnValue(false);
+ readdirMock.mockResolvedValue([]);
+ props = mockProps({ firefoxCacheDir });
+ });
+
+ it('should return empty array when no entries found in cache directory', async () => {
+ // Given/When
+ const result = await scanFirefoxCacheProfiles(props);
+ // Then
+ expect(result).toEqual([]);
+ expect(mockedIsFirefoxProfileDirectory).not.toBeCalled();
+ expect(mockedScanDirectory).not.toBeCalled();
+ });
+
+ it('should scan valid Firefox profile cache directories', async () => {
+ // Given
+ const profileEntries = [createMockDirent('rwt14re6.default'), createMockDirent('abc123.test-profile')];
+ const cacheItems = [
+ createMockItem('cache-file1.dat', 1024, '/home/user/.cache/mozilla/firefox/rwt14re6.default/cache2'),
+ createMockItem('thumbnail1.png', 512, '/home/user/.cache/mozilla/firefox/rwt14re6.default/thumbnails'),
+ createMockItem('startup1.bin', 256, '/home/user/.cache/mozilla/firefox/rwt14re6.default/startupCache'),
+ ];
+ readdirMock.mockResolvedValue(profileEntries);
+ mockedIsFirefoxProfileDirectory
+ .mockReturnValueOnce(true)
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(false);
+
+ mockedScanDirectory
+ .mockResolvedValueOnce([cacheItems[0]])
+ .mockResolvedValueOnce([cacheItems[1]])
+ .mockResolvedValueOnce([cacheItems[2]]);
+ // When
+ const result = await scanFirefoxCacheProfiles(props);
+ // Then
+ expect(result).toStrictEqual(cacheItems);
+ expect(mockedIsFirefoxProfileDirectory).toBeCalledTimes(2);
+ expect(mockedScanDirectory).toBeCalledTimes(3);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.ts b/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.ts
new file mode 100644
index 0000000000..4cdb2ddab3
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/firefox-web-cache/scan-firefox-cache-profiles.ts
@@ -0,0 +1,65 @@
+import { Dirent } from 'node:fs';
+import { readdir } from 'node:fs/promises';
+import { join } from 'node:path/posix';
+
+import { scanDirectory } from '../../scan/scan-directory';
+import { CleanableItem, CleanerContext } from '../../types/cleaner.types';
+import { isFirefoxProfileDirectory } from '../../utils/is-firefox-profile-directory';
+import { isSafeWebBrowserFile } from '../../utils/is-safe-web-browser-file';
+
+type Props = {
+ ctx: CleanerContext;
+ firefoxCacheDir: string;
+};
+
+export async function scanFirefoxCacheProfiles({ ctx, firefoxCacheDir }: Props) {
+ let entries: Dirent[];
+ try {
+ entries = await readdir(firefoxCacheDir, { withFileTypes: true });
+ } catch {
+ return [];
+ }
+
+ const profileDirsChecks = entries.map((entry) => {
+ const isProfileDir = isFirefoxProfileDirectory({ entry, parentPath: firefoxCacheDir });
+ return { entry, isProfileDir };
+ });
+
+ const profileDirs = profileDirsChecks.filter((result) => result.isProfileDir).map((result) => result.entry.name);
+
+ const scanPromises: Promise[] = [];
+
+ for (const profileDir of profileDirs) {
+ const profileCachePath = join(firefoxCacheDir, profileDir);
+
+ const cache2Path = join(profileCachePath, 'cache2');
+ scanPromises.push(
+ scanDirectory({
+ ctx,
+ dirPath: cache2Path,
+ customFileFilter: isSafeWebBrowserFile,
+ }),
+ );
+
+ const thumbnailsPath = join(profileCachePath, 'thumbnails');
+ scanPromises.push(
+ scanDirectory({
+ ctx,
+ dirPath: thumbnailsPath,
+ customFileFilter: isSafeWebBrowserFile,
+ }),
+ );
+
+ const startupCachePath = join(profileCachePath, 'startupCache');
+ scanPromises.push(
+ scanDirectory({
+ ctx,
+ dirPath: startupCachePath,
+ customFileFilter: isSafeWebBrowserFile,
+ }),
+ );
+ }
+
+ const results = await Promise.allSettled(scanPromises);
+ return results.filter((result) => result.status === 'fulfilled').flatMap((result) => result.value);
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.test.ts b/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.test.ts
new file mode 100644
index 0000000000..d2f7175321
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.test.ts
@@ -0,0 +1,91 @@
+import { Dirent } from 'node:fs';
+import { readdir, stat } from 'node:fs/promises';
+
+import { mockProps, partialSpyOn, deepMocked } from '@/tests/vitest/utils.helper.test';
+
+import * as isFirefoxProfileDirectoryModule from '../../utils/is-firefox-profile-directory';
+import * as wasAccessedWithinLastHourModule from '../../utils/was-accessed-within-last-hour';
+import { scanFirefoxProfiles } from './scan-firefox-profiles';
+
+vi.mock(import('node:fs/promises'));
+
+describe('scanFirefoxProfiles', () => {
+ const firefoxProfilesDir = '/home/user/.mozilla/firefox';
+ const mockedIsFirefoxProfileDirectory = partialSpyOn(isFirefoxProfileDirectoryModule, 'isFirefoxProfileDirectory');
+ const mockedWasAccessedWithinLastHour = partialSpyOn(wasAccessedWithinLastHourModule, 'wasAccessedWithinLastHour');
+ const readdirMock = deepMocked(readdir);
+ const statMock = deepMocked(stat);
+
+ const createMockDirent = (name: string, isDirectory = true) =>
+ ({
+ name,
+ isDirectory: () => isDirectory,
+ isFile: () => !isDirectory,
+ }) as unknown as Dirent;
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ mockedIsFirefoxProfileDirectory.mockReturnValue(false);
+ readdirMock.mockResolvedValue([]);
+
+ props = mockProps({
+ firefoxProfilesDir,
+ ctx: {
+ browser: {
+ criticalExtensions: [],
+ criticalFilenames: [],
+ },
+ },
+ });
+ });
+
+ it('should return empty array when no entries found in profiles directory', async () => {
+ // Given
+ readdirMock.mockResolvedValue([]);
+ // When
+ const result = await scanFirefoxProfiles(props);
+ // Then
+ expect(result).toEqual([]);
+ expect(mockedIsFirefoxProfileDirectory).not.toBeCalled();
+ });
+
+ it('should scan valid Firefox profile directories and filter files', async () => {
+ // Given
+ props.ctx.browser.criticalExtensions = ['.sqlite', '.sqlite3', '.db'];
+ props.ctx.browser.criticalFilenames = ['cookies', 'webappsstore', 'chromeappsstore'];
+ const profileEntries = [createMockDirent('profile.default')];
+ const profileFiles = [
+ createMockDirent('cookies.sqlite', false),
+ createMockDirent('webappsstore.sqlite3', false),
+ createMockDirent('chromeappsstore.db', false),
+ createMockDirent('regular-file.txt', false),
+ createMockDirent('prefs.js', false),
+ createMockDirent('bookmarks.html', false),
+ ];
+ statMock.mockResolvedValue({ isFile: () => true, size: 2048 });
+ readdirMock.mockResolvedValueOnce(profileEntries).mockResolvedValueOnce(profileFiles);
+ mockedIsFirefoxProfileDirectory.mockReturnValue(true);
+ mockedWasAccessedWithinLastHour.mockReturnValue(false);
+ // When
+ const result = await scanFirefoxProfiles(props);
+ // Then
+ expect(result).toMatchObject([
+ {
+ fullPath: '/home/user/.mozilla/firefox/profile.default/regular-file.txt',
+ fileName: 'regular-file.txt',
+ sizeInBytes: 2048,
+ },
+ {
+ fullPath: '/home/user/.mozilla/firefox/profile.default/prefs.js',
+ fileName: 'prefs.js',
+ sizeInBytes: 2048,
+ },
+ {
+ fullPath: '/home/user/.mozilla/firefox/profile.default/bookmarks.html',
+ fileName: 'bookmarks.html',
+ sizeInBytes: 2048,
+ },
+ ]);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.ts b/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.ts
new file mode 100644
index 0000000000..b47baec920
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/firefox-web-storage/scan-firefox-profiles.ts
@@ -0,0 +1,46 @@
+import { Dirent } from 'node:fs';
+import { readdir } from 'node:fs/promises';
+import { join } from 'node:path/posix';
+
+import { scanDirectory } from '../../scan/scan-directory';
+import { CleanableItem, CleanerContext } from '../../types/cleaner.types';
+import { isFirefoxProfileDirectory } from '../../utils/is-firefox-profile-directory';
+import { isSafeWebBrowserFile } from '../../utils/is-safe-web-browser-file';
+
+type Props = {
+ ctx: CleanerContext;
+ firefoxProfilesDir: string;
+};
+
+export async function scanFirefoxProfiles({ ctx, firefoxProfilesDir }: Props) {
+ let entries: Dirent[];
+ try {
+ entries = await readdir(firefoxProfilesDir, { withFileTypes: true });
+ } catch {
+ return [];
+ }
+
+ const profileDirsChecks = entries.map((entry) => {
+ const isProfileDir = isFirefoxProfileDirectory({ entry, parentPath: firefoxProfilesDir });
+ return { entry, isProfileDir };
+ });
+
+ const profileDirs = profileDirsChecks.filter((result) => result.isProfileDir).map((result) => result.entry.name);
+
+ const scanPromises: Promise[] = [];
+
+ for (const profileDir of profileDirs) {
+ const profilePath = join(firefoxProfilesDir, profileDir);
+
+ scanPromises.push(
+ scanDirectory({
+ ctx,
+ dirPath: profilePath,
+ customFileFilter: isSafeWebBrowserFile,
+ }),
+ );
+ }
+
+ const results = await Promise.allSettled(scanPromises);
+ return results.filter((result) => result.status === 'fulfilled').flatMap((result) => result.value);
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/process-dirent.test.ts b/packages/core/src/backend/features/cleaner/scan/process-dirent.test.ts
new file mode 100644
index 0000000000..d53fe8003c
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/process-dirent.test.ts
@@ -0,0 +1,116 @@
+import { Stats } from 'node:fs';
+import { stat } from 'node:fs/promises';
+
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { mockProps, partialSpyOn, deepMocked, calls } from '@/tests/vitest/utils.helper.test';
+
+import * as createCleanableItemMocule from '../utils/create-cleanable-item';
+import * as wasAccessedWithinLastHourModule from '../utils/was-accessed-within-last-hour';
+import { processDirent } from './process-dirent';
+import * as scanDirectoryModule from './scan-directory';
+
+vi.mock(import('node:fs/promises'));
+
+describe('processDirent', () => {
+ const statMock = deepMocked(stat);
+ const wasAccessedWithinLastHourMock = partialSpyOn(wasAccessedWithinLastHourModule, 'wasAccessedWithinLastHour');
+ const createCleanableItemMock = partialSpyOn(createCleanableItemMocule, 'createCleanableItem');
+ const scanDirectoryMock = partialSpyOn(scanDirectoryModule, 'scanDirectory');
+
+ const fullPath = '/test/test.txt';
+ const name = 'test.txt';
+ const mockCleanableItem = {
+ fullPath,
+ fileName: name,
+ sizeInBytes: 1024,
+ };
+
+ let props: Parameters[0];
+
+ const createMockStats = (isFile = true) => ({ isDirectory: () => !isFile, isFile: () => isFile }) as unknown as Stats;
+
+ beforeEach(() => {
+ statMock.mockResolvedValue(createMockStats());
+ wasAccessedWithinLastHourMock.mockReturnValue(false);
+ props = mockProps({ entry: { name }, fullPath });
+ });
+
+ describe('for files', () => {
+ beforeEach(() => {
+ props.entry.isFile = vi.fn().mockReturnValue(true);
+ });
+
+ it('should process file and return CleanableItem when file is safe to delete', async () => {
+ // Given
+ props.customFileFilter = vi.fn().mockReturnValue(true);
+ createCleanableItemMock.mockReturnValue(mockCleanableItem);
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([mockCleanableItem]);
+ expect(wasAccessedWithinLastHourMock).toBeCalledWith({ fileStats: expect.any(Object) });
+ expect(createCleanableItemMock).toBeCalledWith({ filePath: fullPath, stat: expect.any(Object) });
+ });
+
+ it('should return empty array when file was accessed within last hour', async () => {
+ // Given
+ wasAccessedWithinLastHourMock.mockReturnValue(true);
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(createCleanableItemMock).not.toHaveBeenCalled();
+ });
+
+ it('should return empty array when custom filter excludes file', async () => {
+ // Given
+ props.customFileFilter = vi.fn().mockReturnValue(false);
+ wasAccessedWithinLastHourMock.mockReturnValue(false);
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(props.customFileFilter).toBeCalledWith({ fileName: name });
+ expect(createCleanableItemMock).not.toHaveBeenCalled();
+ });
+
+ it('should handle errors gracefully and log warning', async () => {
+ // Given
+ statMock.mockRejectedValue(new Error('Permission denied'));
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ calls(loggerMock.warn).toHaveLength(1);
+ });
+ });
+
+ describe('for folders', () => {
+ beforeEach(() => {
+ props.entry.isFile = vi.fn().mockReturnValue(false);
+ props.entry.isDirectory = vi.fn().mockReturnValue(true);
+ });
+
+ it('should process directory by calling scanDirectory', async () => {
+ // Given
+ scanDirectoryMock.mockResolvedValue([mockCleanableItem]);
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([mockCleanableItem]);
+ expect(scanDirectoryMock).toBeCalledTimes(1);
+ expect(wasAccessedWithinLastHourMock).not.toHaveBeenCalled();
+ });
+
+ it('should return empty array when custom filter excludes folder', async () => {
+ // Given
+ props.customDirectoryFilter = vi.fn().mockReturnValue(true);
+ // When
+ const result = await processDirent(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(props.customDirectoryFilter).toBeCalledWith({ folderName: name });
+ expect(createCleanableItemMock).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/process-dirent.ts b/packages/core/src/backend/features/cleaner/scan/process-dirent.ts
new file mode 100644
index 0000000000..6f58c096b8
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/process-dirent.ts
@@ -0,0 +1,53 @@
+import { Dirent } from 'node:fs';
+import { stat } from 'node:fs/promises';
+
+import { logger } from '@/backend/core/logger/logger';
+
+import { CleanerContext } from '../types/cleaner.types';
+import { createCleanableItem } from '../utils/create-cleanable-item';
+import { wasAccessedWithinLastHour } from '../utils/was-accessed-within-last-hour';
+import { scanDirectory } from './scan-directory';
+
+type Props = {
+ ctx: CleanerContext;
+ entry: Dirent;
+ fullPath: string;
+ customDirectoryFilter?: ({ folderName }: { folderName: string }) => boolean;
+ customFileFilter?: ({ ctx, fileName }: { ctx: CleanerContext; fileName: string }) => boolean;
+};
+
+export async function processDirent({ ctx, entry, fullPath, customFileFilter, customDirectoryFilter }: Props) {
+ try {
+ if (entry.isFile()) {
+ const isIncluded = customFileFilter?.({ ctx, fileName: entry.name }) ?? true;
+ if (!isIncluded) return [];
+
+ const fileStats = await stat(fullPath);
+ const wasAccessed = wasAccessedWithinLastHour({ fileStats });
+ if (wasAccessed) return [];
+
+ const item = createCleanableItem({ filePath: fullPath, stat: fileStats });
+ return [item];
+ }
+
+ if (entry.isDirectory()) {
+ const isExcluded = customDirectoryFilter?.({ folderName: entry.name });
+ if (isExcluded) return [];
+
+ return await scanDirectory({
+ ctx,
+ dirPath: fullPath,
+ customFileFilter,
+ customDirectoryFilter,
+ });
+ }
+ } catch {
+ logger.warn({
+ tag: 'CLEANER',
+ msg: 'File or folder cannot be accessed, skipping',
+ fullPath,
+ });
+ }
+
+ return [];
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-directory.test.ts b/packages/core/src/backend/features/cleaner/scan/scan-directory.test.ts
new file mode 100644
index 0000000000..8ba00b09e5
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-directory.test.ts
@@ -0,0 +1,151 @@
+import { Dirent } from 'node:fs';
+import { readdir } from 'node:fs/promises';
+
+import { deepMocked, mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
+
+import { CleanableItem } from '../types/cleaner.types';
+import * as isInternxtRelatedModule from '../utils/is-file-internxt-related';
+import * as processDirentModule from './process-dirent';
+import { scanDirectory } from './scan-directory';
+
+vi.mock(import('node:fs/promises'));
+
+describe('scanDirectory', () => {
+ const readdirMock = deepMocked(readdir);
+ const mockBasePath = '/test/path';
+ const isInternxtRelatedMock = partialSpyOn(isInternxtRelatedModule, 'isInternxtRelated');
+ const processDirentMock = partialSpyOn(processDirentModule, 'processDirent');
+
+ const createMockDirent = (name: string, isFile = true) =>
+ ({
+ name,
+ isFile: () => isFile,
+ isDirectory: () => !isFile,
+ }) as unknown as Dirent;
+
+ const createCleanableItemMock = (fileName: string, size: number, basePath = mockBasePath) =>
+ ({
+ fullPath: `${basePath}/${fileName}`,
+ fileName,
+ sizeInBytes: size,
+ }) as CleanableItem;
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ isInternxtRelatedMock.mockReturnValue(false);
+ props = mockProps({ dirPath: mockBasePath });
+ });
+
+ it('should scan files in directory correctly', async () => {
+ readdirMock.mockResolvedValue([createMockDirent('file1.txt', true)]);
+
+ const expectedItem = createCleanableItemMock('file1.txt', 2048);
+ processDirentMock.mockResolvedValue([expectedItem]);
+ const result = await scanDirectory(props);
+
+ expect(readdirMock).toHaveBeenCalled();
+ expect(processDirentMock).toHaveBeenCalled();
+ expect(result).toStrictEqual([expectedItem]);
+ expect(processDirentMock).toBeCalledWith(
+ expect.objectContaining({
+ entry: expect.objectContaining({ name: 'file1.txt' }),
+ fullPath: '/test/path/file1.txt',
+ customFileFilter: undefined,
+ }),
+ );
+ });
+
+ it('should skip Internxt-related files and directories', async () => {
+ readdirMock.mockResolvedValue([createMockDirent('internxt-app', false), createMockDirent('regular-file.txt', true)]);
+
+ isInternxtRelatedMock.mockReturnValueOnce(true).mockReturnValueOnce(false);
+
+ const expectedItem = createCleanableItemMock('regular-file.txt', 1024);
+ processDirentMock.mockResolvedValue([expectedItem]);
+ const result = await scanDirectory(props);
+
+ expect(result).toStrictEqual([expectedItem]);
+ expect(isInternxtRelatedMock).toBeCalledWith({ name: '/test/path/internxt-app' });
+ expect(isInternxtRelatedMock).toBeCalledWith({ name: '/test/path/regular-file.txt' });
+ expect(processDirentMock).toBeCalledTimes(1);
+ expect(processDirentMock).toBeCalledWith(
+ expect.objectContaining({
+ entry: expect.objectContaining({ name: 'regular-file.txt' }),
+ fullPath: '/test/path/regular-file.txt',
+ customFileFilter: undefined,
+ }),
+ );
+ });
+
+ it('should recursively scan subdirectories', async () => {
+ const dirent = createMockDirent('subdir', false);
+ readdirMock.mockResolvedValue([dirent]);
+
+ const expectedItem = [createCleanableItemMock('nested-file.txt', 512, '/test/path/subdir')];
+ processDirentMock.mockResolvedValue(expectedItem);
+
+ const result = await scanDirectory(props);
+
+ expect(result).toStrictEqual(expectedItem);
+ expect(readdirMock).toBeCalledWith(mockBasePath, {
+ withFileTypes: true,
+ });
+
+ expect(processDirentMock).toBeCalledWith(
+ expect.objectContaining({
+ entry: dirent,
+ fullPath: '/test/path/subdir',
+ customFileFilter: undefined,
+ }),
+ );
+ });
+
+ it('should handle mixed files and directories', async () => {
+ readdirMock.mockResolvedValue([
+ createMockDirent('file1.txt', true),
+ createMockDirent('subdir', false),
+ createMockDirent('file2.log', true),
+ ]);
+
+ const file1Item = createCleanableItemMock('file1.txt', 100);
+ const file2Item = createCleanableItemMock('file2.log', 300);
+ const subdirItem = createCleanableItemMock('nested.txt', 200, '/test/path/subdir');
+ processDirentMock.mockResolvedValueOnce([file1Item]).mockResolvedValueOnce([subdirItem]).mockResolvedValueOnce([file2Item]);
+
+ const result = await scanDirectory(props);
+
+ expect(result).toStrictEqual([file1Item, subdirItem, file2Item]);
+ expect(processDirentMock).toBeCalledTimes(3);
+ });
+
+ it('should skip files that cannot be accessed due to permissions', async () => {
+ readdirMock.mockResolvedValue([createMockDirent('accessible-file.txt', true), createMockDirent('restricted-file.txt', true)]);
+
+ const accessibleItem = [createCleanableItemMock('accessible-file.txt', 1024)];
+ processDirentMock.mockResolvedValueOnce(accessibleItem).mockResolvedValueOnce([]);
+ const result = await scanDirectory(props);
+
+ expect(result).toStrictEqual(accessibleItem);
+ expect(processDirentMock).toBeCalledTimes(2);
+ });
+
+ it('should handle empty directories', async () => {
+ // Given
+ readdirMock.mockResolvedValue([]);
+ // When
+ const result = await scanDirectory(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(processDirentMock).toBeCalledTimes(0);
+ });
+
+ it('should handle readdir errors gracefully', async () => {
+ // Given
+ readdirMock.mockRejectedValue(new Error('Cannot read directory'));
+ // When
+ const result = await scanDirectory(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-directory.ts b/packages/core/src/backend/features/cleaner/scan/scan-directory.ts
new file mode 100644
index 0000000000..7da6ce69e1
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-directory.ts
@@ -0,0 +1,51 @@
+import { join } from 'node:path/posix';
+
+import { logger } from '@/backend/core/logger/logger';
+import { FileSystemModule } from '@/backend/infra/file-system/file-system.module';
+
+import { CleanableItem, CleanerContext } from '../types/cleaner.types';
+import { isInternxtRelated } from '../utils/is-file-internxt-related';
+import { processDirent } from './process-dirent';
+
+type Props = {
+ ctx: CleanerContext;
+ dirPath: string;
+ customFileFilter?: ({ ctx, fileName }: { ctx: CleanerContext; fileName: string }) => boolean;
+ customDirectoryFilter?: ({ folderName }: { folderName: string }) => boolean;
+};
+
+export async function scanDirectory({ ctx, dirPath, customFileFilter, customDirectoryFilter }: Props) {
+ const { data: dirents, error } = await FileSystemModule.readdir({ absolutePath: dirPath });
+
+ if (error) {
+ if (error.code !== 'NON_EXISTS') {
+ logger.warn({
+ tag: 'CLEANER',
+ msg: 'Folder cannot be accessed, skipping',
+ dirPath,
+ error: error.code === 'UNKNOWN' ? error : error.code,
+ });
+ }
+
+ return [];
+ }
+
+ const items: CleanableItem[] = [];
+
+ for (const dirent of dirents) {
+ const fullPath = join(dirPath, dirent.name);
+ if (isInternxtRelated({ name: fullPath })) continue;
+
+ const cleanableItems = await processDirent({
+ ctx,
+ entry: dirent,
+ fullPath,
+ customFileFilter,
+ customDirectoryFilter,
+ });
+
+ items.push(...cleanableItems);
+ }
+
+ return items;
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-single-file.test.ts b/packages/core/src/backend/features/cleaner/scan/scan-single-file.test.ts
new file mode 100644
index 0000000000..68772f544d
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-single-file.test.ts
@@ -0,0 +1,73 @@
+import { promises as fs, Stats } from 'node:fs';
+
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { partialSpyOn, deepMocked, calls } from '@/tests/vitest/utils.helper.test';
+
+import * as createCleanableItemModule from '../utils/create-cleanable-item';
+import * as wasAccessedWithinLastHourModule from '../utils/was-accessed-within-last-hour';
+import { scanSingleFile } from './scan-single-file';
+
+vi.mock(import('node:fs'));
+
+describe('scanSingleFile', () => {
+ const statMock = deepMocked(fs.stat);
+ const wasAccessedWithinLastHourMock = partialSpyOn(wasAccessedWithinLastHourModule, 'wasAccessedWithinLastHour');
+ const createCleanableItemMock = partialSpyOn(createCleanableItemModule, 'createCleanableItem');
+
+ const mockFilePath = '/home/user/.xsession-errors';
+ const mockCleanableItem = {
+ fullPath: mockFilePath,
+ fileName: '.xsession-errors',
+ sizeInBytes: 2048,
+ };
+
+ const createMockStats = (isFile = true) => ({ isDirectory: () => !isFile, isFile: () => isFile }) as unknown as Stats;
+
+ beforeEach(() => {
+ statMock.mockResolvedValue(createMockStats());
+ wasAccessedWithinLastHourMock.mockReturnValue(false);
+ });
+
+ it('should return CleanableItem array when file is safe to delete', async () => {
+ // Given
+ createCleanableItemMock.mockReturnValue(mockCleanableItem);
+ // When
+ const result = await scanSingleFile({ filePath: mockFilePath });
+ // Then
+ expect(result).toStrictEqual([mockCleanableItem]);
+ expect(statMock).toBeCalledWith(mockFilePath);
+ expect(wasAccessedWithinLastHourMock).toBeCalledWith({ fileStats: expect.any(Object) });
+ expect(createCleanableItemMock).toBeCalledWith({ filePath: mockFilePath, stat: expect.any(Object) });
+ });
+
+ it('should return empty array when path is not a file', async () => {
+ // Given
+ statMock.mockResolvedValue(createMockStats(false));
+ // When
+ const result = await scanSingleFile({ filePath: mockFilePath });
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(wasAccessedWithinLastHourMock).not.toHaveBeenCalled();
+ expect(createCleanableItemMock).not.toHaveBeenCalled();
+ });
+
+ it('should return empty array when file was accessed within last hour', async () => {
+ // Given
+ wasAccessedWithinLastHourMock.mockReturnValue(true);
+ // When
+ const result = await scanSingleFile({ filePath: mockFilePath });
+ // Then
+ expect(result).toStrictEqual([]);
+ expect(createCleanableItemMock).not.toHaveBeenCalled();
+ });
+
+ it('should handle file access errors gracefully and log warning', async () => {
+ // Given
+ statMock.mockRejectedValue(new Error('File not found'));
+ // When
+ const result = await scanSingleFile({ filePath: mockFilePath });
+ // Then
+ expect(result).toStrictEqual([]);
+ calls(loggerMock.warn).toHaveLength(1);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-single-file.ts b/packages/core/src/backend/features/cleaner/scan/scan-single-file.ts
new file mode 100644
index 0000000000..ebb7fc094d
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-single-file.ts
@@ -0,0 +1,27 @@
+import { promises as fs } from 'node:fs';
+
+import { logger } from '@/backend/core/logger/logger';
+
+import { createCleanableItem } from '../utils/create-cleanable-item';
+import { wasAccessedWithinLastHour } from '../utils/was-accessed-within-last-hour';
+
+export async function scanSingleFile({ filePath }: { filePath: string }) {
+ try {
+ const fileStats = await fs.stat(filePath);
+
+ if (!fileStats.isFile() || wasAccessedWithinLastHour({ fileStats })) {
+ return [];
+ }
+
+ const item = createCleanableItem({ filePath, stat: fileStats });
+ return [item];
+ } catch {
+ logger.warn({
+ tag: 'CLEANER',
+ msg: `Single file cannot be accessed, skipping`,
+ filePath,
+ });
+ }
+
+ return [];
+}
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.test.ts b/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.test.ts
new file mode 100644
index 0000000000..964cc8b405
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.test.ts
@@ -0,0 +1,73 @@
+import { Dirent } from 'node:fs';
+
+import { mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
+
+import { CleanableItem } from '../types/cleaner.types';
+import * as getFilteredDirectoriesModule from '../utils/get-filtered-directories';
+import * as isFileInternextRelatedModule from '../utils/is-file-internxt-related';
+import * as scanDirectoryModule from './scan-directory';
+import { scanSubDirectory } from './scan-subdirectory';
+
+describe('scanSubDirectory', () => {
+ const mockedGetFilteredDirectories = partialSpyOn(getFilteredDirectoriesModule, 'getFilteredDirectories');
+ const mockedIsInternxtRelated = partialSpyOn(isFileInternextRelatedModule, 'isInternxtRelated');
+ const mockedScanDirectory = partialSpyOn(scanDirectoryModule, 'scanDirectory');
+
+ const createMockDirent = (name: string, isDirectory = true) =>
+ ({
+ name,
+ isDirectory: () => isDirectory,
+ isFile: () => !isDirectory,
+ }) as unknown as Dirent;
+
+ const mockBaseDir = '/home/user/.cache';
+ const mockSubDir = 'cache';
+
+ const createCleanableItemMock = (appName: string, fileName: string, size: number, basePath = mockBaseDir) =>
+ ({
+ fullPath: `${basePath}/${appName}/${fileName}`,
+ fileName,
+ sizeInBytes: size,
+ }) as CleanableItem;
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ mockedIsInternxtRelated.mockReturnValue(false);
+ props = mockProps({
+ baseDir: mockBaseDir,
+ subPath: mockSubDir,
+ });
+ });
+
+ it('should scan directories given a certain subPath', async () => {
+ // Given
+ const mockBaseDir = '/home/user/.local/share';
+ props.baseDir = mockBaseDir;
+ const mockDirents = [createMockDirent('app1'), createMockDirent('app2')];
+ const mockApp1Items = [createCleanableItemMock('app1', 'file1.cache', 1024, mockBaseDir)];
+ const mockApp2Items = [createCleanableItemMock('app2', 'file2.cache', 2048, mockBaseDir)];
+ mockedGetFilteredDirectories.mockResolvedValue(mockDirents);
+ mockedScanDirectory.mockResolvedValueOnce(mockApp1Items).mockResolvedValueOnce(mockApp2Items);
+ // When
+ const result = await scanSubDirectory(props);
+ // Then
+ expect(result).toStrictEqual([...mockApp1Items, ...mockApp2Items]);
+ expect(mockedGetFilteredDirectories).toBeCalledWith({ baseDir: mockBaseDir, customDirectoryFilter: undefined });
+ expect(mockedScanDirectory).toBeCalledWith(expect.objectContaining({ dirPath: `${mockBaseDir}/app1/${mockSubDir}` }));
+ expect(mockedScanDirectory).toBeCalledWith(expect.objectContaining({ dirPath: `${mockBaseDir}/app2/${mockSubDir}` }));
+ });
+
+ it('should handle scanDirectory errors gracefully', async () => {
+ // Given
+ const mockDirents = [createMockDirent('app1'), createMockDirent('app2')];
+ const app2Items = [createCleanableItemMock('app2', 'cache.tmp', 1024)];
+ mockedGetFilteredDirectories.mockResolvedValue(mockDirents);
+ mockedScanDirectory.mockRejectedValueOnce(new Error('Permission denied')).mockResolvedValueOnce(app2Items);
+ // When
+ const result = await scanSubDirectory(props);
+ // Then
+ expect(result).toStrictEqual(app2Items);
+ expect(mockedScanDirectory).toBeCalledTimes(2);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.ts b/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.ts
new file mode 100644
index 0000000000..731a9e1fe1
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/scan/scan-subdirectory.ts
@@ -0,0 +1,43 @@
+import { join } from 'node:path/posix';
+
+import { logger } from '@/backend/core/logger/logger';
+
+import { CleanerContext } from '../types/cleaner.types';
+import { getFilteredDirectories } from '../utils/get-filtered-directories';
+import { scanDirectory } from './scan-directory';
+
+type Props = {
+ ctx: CleanerContext;
+ baseDir: string;
+ subPath: string;
+ customDirectoryFilter?: ({ folderName }: { folderName: string }) => boolean;
+ customFileFilter?: ({ ctx, fileName }: { ctx: CleanerContext; fileName: string }) => boolean;
+};
+
+export async function scanSubDirectory({ ctx, baseDir, subPath, customDirectoryFilter, customFileFilter }: Props) {
+ try {
+ const directories = await getFilteredDirectories({ baseDir, customDirectoryFilter });
+
+ const scanPromises = directories.map((directory) => {
+ const dirPath = join(baseDir, directory.name, subPath);
+ return scanDirectory({
+ ctx,
+ dirPath,
+ customFileFilter,
+ });
+ });
+
+ const results = await Promise.allSettled(scanPromises);
+
+ return results.filter((result) => result.status === 'fulfilled').flatMap((result) => result.value);
+ } catch (error) {
+ logger.warn({
+ tag: 'CLEANER',
+ msg: `Directory might not exist or be accesible, skipping it`,
+ baseDir,
+ subPath,
+ error,
+ });
+ return [];
+ }
+}
diff --git a/packages/core/src/backend/features/cleaner/services/delete-file-saftly.test.ts b/packages/core/src/backend/features/cleaner/services/delete-file-saftly.test.ts
new file mode 100644
index 0000000000..6ece7e0ce5
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/delete-file-saftly.test.ts
@@ -0,0 +1,78 @@
+import { unlink } from 'node:fs/promises';
+
+import { FileSystemModule } from '@/backend/infra/file-system/file-system.module';
+import type { AbsolutePath } from '@/backend/infra/file-system/file-system.types';
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { call, deepMocked, partialSpyOn } from '@/tests/vitest/utils.helper.test';
+
+import { cleanerStore } from '../stores/cleaner.store';
+import { deleteFileSafely } from './delete-file-saftly';
+
+vi.mock(import('node:fs/promises'));
+
+const mockedUnlink = deepMocked(unlink);
+const mockedStatThrow = partialSpyOn(FileSystemModule, 'statThrow');
+
+describe('deleteFileSafely', () => {
+ const testFilePath = '/test/path/file.txt' as AbsolutePath;
+
+ beforeEach(() => {
+ cleanerStore.reset();
+ });
+
+ it('should delete file successfully and update store', async () => {
+ // Given
+ mockedStatThrow.mockResolvedValue({ size: 1024 });
+ mockedUnlink.mockResolvedValue(undefined);
+ // When
+ await deleteFileSafely({ absolutePath: testFilePath });
+ // Then
+ call(mockedStatThrow).toMatchObject({ absolutePath: testFilePath });
+ call(mockedUnlink).toMatchObject(testFilePath);
+ expect(cleanerStore.state.deletedFilesCount).toBe(1);
+ expect(cleanerStore.state.totalSpaceGained).toBe(1024);
+ expect(loggerMock.warn).not.toBeCalled();
+ });
+
+ it('should handle stat error gracefully', async () => {
+ // Given
+ mockedStatThrow.mockRejectedValue(new Error('File not found'));
+ // When
+ await deleteFileSafely({ absolutePath: testFilePath });
+ // Then
+ expect(cleanerStore.state.deletedFilesCount).toBe(0);
+ expect(cleanerStore.state.totalSpaceGained).toBe(0);
+ expect(mockedUnlink).not.toBeCalled();
+ call(mockedStatThrow).toMatchObject({ absolutePath: testFilePath });
+ call(loggerMock.warn).toMatchObject({
+ tag: 'CLEANER',
+ msg: 'Failed to delete file, continuing with next file',
+ absolutePath: testFilePath,
+ error: {
+ message: 'File not found',
+ },
+ });
+ });
+
+ it('should handle unlink error gracefully', async () => {
+ // Given
+ mockedStatThrow.mockResolvedValue({ size: 512 });
+ const unlinkError = new Error('Permission denied');
+ mockedUnlink.mockRejectedValue(unlinkError);
+ // When
+ await deleteFileSafely({ absolutePath: testFilePath });
+ // Then
+ expect(cleanerStore.state.deletedFilesCount).toBe(0);
+ expect(cleanerStore.state.totalSpaceGained).toBe(0);
+ call(mockedStatThrow).toMatchObject({ absolutePath: testFilePath });
+ call(mockedUnlink).toMatchObject(testFilePath);
+ call(loggerMock.warn).toMatchObject({
+ tag: 'CLEANER',
+ msg: 'Failed to delete file, continuing with next file',
+ absolutePath: testFilePath,
+ error: {
+ message: 'Permission denied',
+ },
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/services/delete-file-saftly.ts b/packages/core/src/backend/features/cleaner/services/delete-file-saftly.ts
new file mode 100644
index 0000000000..05528c32a3
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/delete-file-saftly.ts
@@ -0,0 +1,28 @@
+import { unlink } from 'node:fs/promises';
+
+import { logger } from '@/backend/core/logger/logger';
+import { FileSystemModule } from '@/backend/infra/file-system/file-system.module';
+
+import { cleanerStore } from '../stores/cleaner.store';
+
+type Props = {
+ absolutePath: string;
+};
+
+export async function deleteFileSafely({ absolutePath }: Props) {
+ try {
+ const { size } = await FileSystemModule.statThrow({ absolutePath });
+
+ await unlink(absolutePath);
+
+ cleanerStore.state.deletedFilesCount++;
+ cleanerStore.state.totalSpaceGained += size;
+ } catch (error) {
+ logger.warn({
+ tag: 'CLEANER',
+ msg: 'Failed to delete file, continuing with next file',
+ absolutePath,
+ error,
+ });
+ }
+}
diff --git a/packages/core/src/backend/features/cleaner/services/start-cleanup.test.ts b/packages/core/src/backend/features/cleaner/services/start-cleanup.test.ts
new file mode 100644
index 0000000000..f8f84d1a0f
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/start-cleanup.test.ts
@@ -0,0 +1,181 @@
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { calls, mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
+
+import { cleanerStore } from '../stores/cleaner.store';
+import * as getAllItemsToDeleteModule from '../utils/get-all-items-to-delete';
+import * as deleteFileSafelyModule from './delete-file-saftly';
+import { startCleanup } from './start-cleanup';
+
+const mockedGetAllItemsToDelete = partialSpyOn(getAllItemsToDeleteModule, 'getAllItemsToDelete');
+const mockedDeleteFileSafely = partialSpyOn(deleteFileSafelyModule, 'deleteFileSafely');
+
+describe('startCleanup', () => {
+ const mockEmitProgress = vi.fn();
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ cleanerStore.reset();
+
+ props = mockProps({
+ cleanerSectionKeys: ['appCache', 'logFiles'],
+ viewModel: {
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ },
+ storedCleanerReport: {
+ appCache: {
+ totalSizeInBytes: 1000,
+ items: [{ fullPath: '/cache/file1.tmp', fileName: 'file1.tmp', sizeInBytes: 400 }],
+ },
+ logFiles: {
+ totalSizeInBytes: 500,
+ items: [{ fullPath: '/logs/app.log', fileName: 'app.log', sizeInBytes: 300 }],
+ },
+ },
+ emitProgress: mockEmitProgress,
+ });
+ });
+
+ it('should complete cleanup process successfully', async () => {
+ // Given
+ const mockItemsToDelete = [
+ { fullPath: '/cache/file1.tmp', fileName: 'file1.tmp', sizeInBytes: 400, absolutePath: '/cache/file1.tmp' },
+ { fullPath: '/logs/app.log', fileName: 'app.log', sizeInBytes: 300, absolutePath: '/logs/app.log' },
+ ];
+ mockedGetAllItemsToDelete.mockReturnValue(mockItemsToDelete);
+ mockedDeleteFileSafely
+ .mockImplementationOnce(async () => {
+ await Promise.resolve();
+ cleanerStore.state.deletedFilesCount++;
+ cleanerStore.state.totalSpaceGained += 400;
+ })
+ .mockImplementationOnce(async () => {
+ await Promise.resolve();
+ cleanerStore.state.deletedFilesCount++;
+ cleanerStore.state.totalSpaceGained += 300;
+ });
+ // When
+ await startCleanup(props);
+ // Then
+ expect(mockedGetAllItemsToDelete).toHaveBeenCalledWith({
+ viewModel: props.viewModel,
+ report: props.storedCleanerReport,
+ cleanerSectionKeys: props.cleanerSectionKeys,
+ });
+ calls(mockedDeleteFileSafely).toHaveLength(2);
+ expect(mockedDeleteFileSafely).toHaveBeenCalledWith({ absolutePath: '/cache/file1.tmp' });
+ expect(mockedDeleteFileSafely).toHaveBeenCalledWith({ absolutePath: '/logs/app.log' });
+ calls(mockEmitProgress).toMatchObject([
+ {
+ currentCleaningPath: '',
+ progress: 0,
+ deletedFiles: 0,
+ spaceGained: 0,
+ cleaning: true,
+ cleaningCompleted: false,
+ },
+ {
+ currentCleaningPath: 'file1.tmp',
+ progress: 50,
+ deletedFiles: 1,
+ spaceGained: 400,
+ cleaning: true,
+ cleaningCompleted: false,
+ },
+ {
+ currentCleaningPath: 'app.log',
+ progress: 100,
+ deletedFiles: 2,
+ spaceGained: 700,
+ cleaning: true,
+ cleaningCompleted: false,
+ },
+ {
+ currentCleaningPath: '',
+ progress: 100,
+ deletedFiles: 2,
+ spaceGained: 700,
+ cleaning: false,
+ cleaningCompleted: true,
+ },
+ ]);
+
+ calls(loggerMock.debug).toMatchObject([
+ {
+ tag: 'CLEANER',
+ msg: 'Starting cleanup process',
+ totalFiles: 2,
+ },
+ {
+ tag: 'CLEANER',
+ msg: 'Cleanup process finished',
+ deletedFiles: 2,
+ totalFiles: 2,
+ },
+ ]);
+ });
+
+ it('should handle partial deletion failures', async () => {
+ // Given
+ const mockItemsToDelete = [
+ { fullPath: '/cache/file1.tmp', fileName: 'file1.tmp', sizeInBytes: 400, absolutePath: '/cache/file1.tmp' },
+ { fullPath: '/logs/app.log', fileName: 'app.log', sizeInBytes: 300, absolutePath: '/logs/app.log' },
+ ];
+ mockedGetAllItemsToDelete.mockReturnValue(mockItemsToDelete);
+ mockedDeleteFileSafely.mockImplementationOnce(async () => {
+ await Promise.resolve();
+ cleanerStore.state.deletedFilesCount++;
+ cleanerStore.state.totalSpaceGained += 400;
+ });
+ // When
+ await startCleanup(props);
+ // Then
+ calls(mockedDeleteFileSafely).toHaveLength(2);
+ expect(mockEmitProgress).toBeCalledWith({
+ currentCleaningPath: '',
+ progress: 100,
+ deletedFiles: 1,
+ spaceGained: 400,
+ cleaning: false,
+ cleaningCompleted: true,
+ });
+ });
+
+ it('should handle empty items to delete list', async () => {
+ // Given
+ mockedGetAllItemsToDelete.mockReturnValue([]);
+ // When
+ await startCleanup(props);
+ // Then
+ expect(mockedDeleteFileSafely).not.toBeCalled();
+ expect(mockEmitProgress).toBeCalledWith({
+ currentCleaningPath: '',
+ progress: 100,
+ deletedFiles: 0,
+ spaceGained: 0,
+ cleaning: false,
+ cleaningCompleted: true,
+ });
+ });
+
+ it('should prevent concurrent cleanup processes', async () => {
+ // Given
+ const mockItemsToDelete = [{ fullPath: '/cache/file1.tmp', fileName: 'file1.tmp', sizeInBytes: 400, absolutePath: '/cache/file1.tmp' }];
+ mockedGetAllItemsToDelete.mockReturnValue(mockItemsToDelete);
+ mockedDeleteFileSafely.mockImplementation(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ cleanerStore.state.deletedFilesCount++;
+ cleanerStore.state.totalSpaceGained += 400;
+ });
+ const newCleanup = startCleanup(props);
+ // When
+ await startCleanup(props);
+ await newCleanup;
+ // Then
+ expect(loggerMock.warn).toHaveBeenCalledWith({
+ tag: 'CLEANER',
+ msg: 'Cleanup already in progress, ignoring new request',
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/services/start-cleanup.ts b/packages/core/src/backend/features/cleaner/services/start-cleanup.ts
new file mode 100644
index 0000000000..187d868632
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/start-cleanup.ts
@@ -0,0 +1,82 @@
+import { logger } from '@/backend/core/logger/logger';
+
+import { cleanerStore } from '../stores/cleaner.store';
+import { CleanerViewModel, CleanupProgress, CleanerSectionKey, CleanerReport } from '../types/cleaner.types';
+import { getAllItemsToDelete } from '../utils/get-all-items-to-delete';
+import { deleteFileSafely } from './delete-file-saftly';
+
+type Props = {
+ viewModel: CleanerViewModel;
+ storedCleanerReport: CleanerReport | null;
+ emitProgress: (progress: CleanupProgress) => void;
+ cleanerSectionKeys: CleanerSectionKey[];
+};
+
+export async function startCleanup({ viewModel, storedCleanerReport, emitProgress, cleanerSectionKeys }: Props) {
+ if (cleanerStore.state.isCleanupInProgress) {
+ logger.warn({ tag: 'CLEANER', msg: 'Cleanup already in progress, ignoring new request' });
+ return;
+ }
+
+ if (!storedCleanerReport) {
+ logger.error({ tag: 'CLEANER', msg: 'No cleaner report available. Generate a report first.' });
+ return;
+ }
+
+ const itemsToDelete = getAllItemsToDelete({ viewModel, report: storedCleanerReport, cleanerSectionKeys });
+ cleanerStore.state.totalFilesToDelete = itemsToDelete.length;
+ cleanerStore.state.isCleanupInProgress = true;
+
+ logger.debug({
+ tag: 'CLEANER',
+ msg: 'Starting cleanup process',
+ totalFiles: cleanerStore.state.totalFilesToDelete,
+ });
+
+ emitProgress({
+ currentCleaningPath: '',
+ progress: 0,
+ deletedFiles: 0,
+ spaceGained: 0,
+ cleaning: true,
+ cleaningCompleted: false,
+ });
+
+ for (const [i, item] of itemsToDelete.entries()) {
+ if (cleanerStore.state.currentAbortController?.signal.aborted) {
+ logger.debug({ tag: 'CLEANER', msg: 'Cleanup process was aborted' });
+ break;
+ }
+
+ if (!item) return;
+ await deleteFileSafely({ absolutePath: item.fullPath });
+
+ const progress = Math.round(((i + 1) / cleanerStore.state.totalFilesToDelete) * 100);
+ emitProgress({
+ currentCleaningPath: item.fileName,
+ progress,
+ deletedFiles: cleanerStore.state.deletedFilesCount,
+ spaceGained: cleanerStore.state.totalSpaceGained,
+ cleaning: true,
+ cleaningCompleted: false,
+ });
+ }
+
+ emitProgress({
+ currentCleaningPath: '',
+ progress: 100,
+ deletedFiles: cleanerStore.state.deletedFilesCount,
+ spaceGained: cleanerStore.state.totalSpaceGained,
+ cleaning: false,
+ cleaningCompleted: true,
+ });
+
+ logger.debug({
+ tag: 'CLEANER',
+ msg: 'Cleanup process finished',
+ deletedFiles: cleanerStore.state.deletedFilesCount,
+ totalFiles: cleanerStore.state.totalFilesToDelete,
+ });
+
+ cleanerStore.reset();
+}
diff --git a/packages/core/src/backend/features/cleaner/services/stop-cleanup.test.ts b/packages/core/src/backend/features/cleaner/services/stop-cleanup.test.ts
new file mode 100644
index 0000000000..51d965c66e
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/stop-cleanup.test.ts
@@ -0,0 +1,34 @@
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { call } from '@/tests/vitest/utils.helper.test';
+
+import { cleanerStore } from '../stores/cleaner.store';
+import { stopCleanup } from './stop-cleanup';
+
+describe('stopCleanup', () => {
+ beforeEach(() => {
+ cleanerStore.reset();
+ });
+
+ it('should stop running cleanup process', () => {
+ // Given
+ cleanerStore.state.isCleanupInProgress = true;
+ const abortController = cleanerStore.state.currentAbortController;
+ // When
+ stopCleanup();
+ // Then
+ expect(abortController.signal.aborted).toBe(true);
+ call(loggerMock.debug).toMatchObject({
+ msg: 'Stopping cleanup process',
+ });
+ expect(cleanerStore.state.isCleanupInProgress).toBe(false);
+ });
+
+ it('should handle stop when no cleanup is running', () => {
+ // When
+ stopCleanup();
+ // Then
+ call(loggerMock.warn).toMatchObject({
+ msg: 'No cleanup process to stop',
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/services/stop-cleanup.ts b/packages/core/src/backend/features/cleaner/services/stop-cleanup.ts
new file mode 100644
index 0000000000..76b87fc139
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/services/stop-cleanup.ts
@@ -0,0 +1,14 @@
+import { logger } from '@/backend/core/logger/logger';
+
+import { cleanerStore } from '../stores/cleaner.store';
+
+export function stopCleanup() {
+ if (!cleanerStore.state.isCleanupInProgress) {
+ logger.warn({ tag: 'CLEANER', msg: 'No cleanup process to stop' });
+ return;
+ }
+
+ logger.debug({ tag: 'CLEANER', msg: 'Stopping cleanup process' });
+ cleanerStore.state.currentAbortController.abort();
+ cleanerStore.reset();
+}
diff --git a/packages/core/src/backend/features/cleaner/stores/cleaner.store.test.ts b/packages/core/src/backend/features/cleaner/stores/cleaner.store.test.ts
new file mode 100644
index 0000000000..0c876561fe
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/stores/cleaner.store.test.ts
@@ -0,0 +1,31 @@
+import { cleanerStore } from './cleaner.store';
+
+describe('cleanerStore', () => {
+ it('should have correct initial state', () => {
+ expect(cleanerStore.state).toStrictEqual({
+ currentAbortController: new AbortController(),
+ totalFilesToDelete: 0,
+ deletedFilesCount: 0,
+ totalSpaceGained: 0,
+ isCleanupInProgress: false,
+ });
+ });
+
+ it('should reset all state to initial values', () => {
+ // Given
+ cleanerStore.state.totalFilesToDelete = 10;
+ cleanerStore.state.deletedFilesCount = 5;
+ cleanerStore.state.totalSpaceGained = 1024;
+ cleanerStore.state.isCleanupInProgress = true;
+ // When
+ cleanerStore.reset();
+ // Then
+ expect(cleanerStore.state).toStrictEqual({
+ currentAbortController: new AbortController(),
+ totalFilesToDelete: 0,
+ deletedFilesCount: 0,
+ totalSpaceGained: 0,
+ isCleanupInProgress: false,
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/stores/cleaner.store.ts b/packages/core/src/backend/features/cleaner/stores/cleaner.store.ts
new file mode 100644
index 0000000000..226c2772a7
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/stores/cleaner.store.ts
@@ -0,0 +1,29 @@
+type CleanerState = {
+ currentAbortController: AbortController;
+ totalFilesToDelete: number;
+ deletedFilesCount: number;
+ totalSpaceGained: number;
+ isCleanupInProgress: boolean;
+};
+
+function createInitialState(): CleanerState {
+ return {
+ currentAbortController: new AbortController(),
+ totalFilesToDelete: 0,
+ deletedFilesCount: 0,
+ totalSpaceGained: 0,
+ isCleanupInProgress: false,
+ };
+}
+
+const state = createInitialState();
+
+function reset() {
+ const newState = createInitialState();
+ Object.assign(state, newState);
+}
+
+export const cleanerStore = {
+ state,
+ reset,
+} as const;
diff --git a/packages/core/src/backend/features/cleaner/types/cleaner.types.ts b/packages/core/src/backend/features/cleaner/types/cleaner.types.ts
new file mode 100644
index 0000000000..0a7dc4ffe2
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/types/cleaner.types.ts
@@ -0,0 +1,48 @@
+export type CleanableItem = {
+ fullPath: string;
+ fileName: string;
+ sizeInBytes: number;
+};
+
+export type CleanerSection = {
+ totalSizeInBytes: number;
+ items: CleanableItem[];
+};
+
+export type CleanerSectionViewModel = {
+ selectedAll: boolean;
+ exceptions: string[];
+};
+
+export type CleanerSectionKey = 'appCache' | 'logFiles' | 'trash' | 'webStorage' | 'webCache' | 'platformSpecific';
+export type CleanerReport = Record;
+export type CleanerViewModel = Record;
+
+export type CleanupProgress = {
+ currentCleaningPath: string;
+ progress: number;
+ deletedFiles: number;
+ spaceGained: number;
+ cleaning: boolean;
+ cleaningCompleted: boolean;
+};
+
+type BrowserContext = {
+ criticalExtensions: string[];
+ criticalFilenames: string[];
+};
+
+type AppCacheContext = {
+ criticalExtensions: string[];
+ criticalKeywords: string[];
+};
+
+type LogFilesContext = {
+ safeExtensions: string[];
+};
+
+export type CleanerContext = {
+ browser: BrowserContext;
+ appCache: AppCacheContext;
+ logFiles: LogFilesContext;
+};
diff --git a/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.test.ts b/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.test.ts
new file mode 100644
index 0000000000..14f9daf513
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.test.ts
@@ -0,0 +1,27 @@
+import { Stats } from 'node:fs';
+import path from 'node:path';
+
+import { deepMocked } from '@/tests/vitest/utils.helper.test';
+
+import { createCleanableItem } from './create-cleanable-item';
+
+vi.mock(import('node:path'));
+
+describe('createCleanableItem', () => {
+ const mockedBasename = deepMocked(path.basename);
+
+ it('should create a CleanableItem with correct properties', () => {
+ // Given
+ const mockStat = { size: 1024 } as Stats;
+ const filePath = '/mock/path/example.txt';
+ mockedBasename.mockReturnValue('example.txt');
+ // When
+ const result = createCleanableItem({ filePath, stat: mockStat });
+ // Then
+ expect(result).toStrictEqual({
+ fullPath: filePath,
+ fileName: 'example.txt',
+ sizeInBytes: 1024,
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.ts b/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.ts
new file mode 100644
index 0000000000..0bfa5373e2
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/create-cleanable-item.ts
@@ -0,0 +1,12 @@
+import { Stats } from 'node:fs';
+import { basename } from 'node:path/posix';
+
+import { CleanableItem } from '../types/cleaner.types';
+
+export function createCleanableItem({ filePath, stat }: { filePath: string; stat: Stats }) {
+ return {
+ fullPath: filePath,
+ fileName: basename(filePath),
+ sizeInBytes: stat.size,
+ } as CleanableItem;
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.test.ts b/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.test.ts
new file mode 100644
index 0000000000..015f7dcc3e
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.test.ts
@@ -0,0 +1,88 @@
+import { mockProps, updateProps } from '@/tests/vitest/utils.helper.test';
+
+import { CleanableItem } from '../types/cleaner.types';
+import { getAllItemsToDelete } from './get-all-items-to-delete';
+
+describe('getAllItemsToDelete', () => {
+ const mockItems1: Partial[] = [{ fullPath: '/cache/file1.txt' }, { fullPath: '/cache/file2.txt' }];
+ const mockItems2: Partial[] = [{ fullPath: '/logs/log1.txt' }, { fullPath: '/logs/log2.txt' }];
+ const mockItems3: Partial[] = [{ fullPath: '/trash/deleted1.txt' }];
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ props = mockProps({
+ cleanerSectionKeys: ['appCache', 'logFiles', 'trash'],
+ report: {
+ appCache: { totalSizeInBytes: 3072, items: mockItems1 },
+ logFiles: { totalSizeInBytes: 768, items: mockItems2 },
+ trash: { totalSizeInBytes: 4096, items: mockItems3 },
+ },
+ });
+ });
+
+ it('should return all selected items from all sections', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getAllItemsToDelete(props);
+ // Then
+ expect(result).toMatchObject([...mockItems1, ...mockItems2, ...mockItems3]);
+ });
+
+ it('should respect exceptions when selectedAll is true', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: ['/cache/file1.txt'] },
+ logFiles: { selectedAll: true, exceptions: ['/logs/log2.txt'] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getAllItemsToDelete(props);
+ // Then
+ expect(result).toMatchObject([{ fullPath: '/cache/file2.txt' }, { fullPath: '/logs/log1.txt' }, { fullPath: '/trash/deleted1.txt' }]);
+ });
+
+ it('should return only explicitly selected items when selectedAll is false', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: false, exceptions: ['/cache/file1.txt'] },
+ logFiles: { selectedAll: false, exceptions: ['/logs/log2.txt'] },
+ trash: { selectedAll: false, exceptions: [] },
+ });
+ // When
+ const result = getAllItemsToDelete(props);
+ // Then
+ expect(result).toMatchObject([{ fullPath: '/cache/file1.txt' }, { fullPath: '/logs/log2.txt' }]);
+ });
+
+ it('should return empty array when no sections are selected', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: false, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ trash: { selectedAll: false, exceptions: [] },
+ });
+ // When
+ const result = getAllItemsToDelete(props);
+ // Then
+ expect(result).toStrictEqual([]);
+ });
+
+ it('should handle mixed selection states across different sections', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: ['/cache/file2.txt'] },
+ logFiles: { selectedAll: false, exceptions: ['/logs/log1.txt'] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getAllItemsToDelete(props);
+ // Then
+ expect(result).toMatchObject([{ fullPath: '/cache/file1.txt' }, { fullPath: '/logs/log1.txt' }, { fullPath: '/trash/deleted1.txt' }]);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.ts b/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.ts
new file mode 100644
index 0000000000..12b571e663
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-all-items-to-delete.ts
@@ -0,0 +1,24 @@
+import { CleanableItem, CleanerSectionKey, CleanerViewModel, CleanerReport } from '../types/cleaner.types';
+import { getSelectedItemsForSection } from './get-selected-items-for-section';
+
+type Props = {
+ viewModel: CleanerViewModel;
+ report: CleanerReport;
+ cleanerSectionKeys: CleanerSectionKey[];
+};
+
+export function getAllItemsToDelete({ viewModel, report, cleanerSectionKeys }: Props) {
+ const itemsToDelete: CleanableItem[] = [];
+
+ for (const sectionKey of cleanerSectionKeys) {
+ const section = report[sectionKey];
+ const sectionViewModel = viewModel[sectionKey];
+
+ if (sectionViewModel) {
+ const selectedItems = getSelectedItemsForSection({ sectionViewModel, sectionItems: section.items });
+ itemsToDelete.push(...selectedItems);
+ }
+ }
+
+ return itemsToDelete;
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/get-disk-space.test.ts b/packages/core/src/backend/features/cleaner/utils/get-disk-space.test.ts
new file mode 100644
index 0000000000..b001d9179b
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-disk-space.test.ts
@@ -0,0 +1,32 @@
+import checkDiskSpace from 'check-disk-space';
+
+import { loggerMock } from '@/tests/vitest/mocks.helper.test';
+import { calls, deepMocked } from '@/tests/vitest/utils.helper.test';
+
+import { getDiskSpace } from './get-disk-space';
+
+vi.mock(import('check-disk-space'));
+
+describe('getDiskSpace', () => {
+ const checkDiskSpaceMock = deepMocked(checkDiskSpace);
+
+ it('should return the disk size for the base path', async () => {
+ // Given
+ checkDiskSpaceMock.mockResolvedValue({ size: 5000000000 });
+ // When
+ const result = await getDiskSpace({ mainPath: 'C:\\' });
+ // Then
+ expect(result).toBe(5000000000);
+ });
+
+ it('should return 0 and log an error if checkDiskSpace fails', async () => {
+ // Given
+ const mockError = new Error('Disk check failed');
+ checkDiskSpaceMock.mockRejectedValue(mockError);
+ // When
+ const result = await getDiskSpace({ mainPath: '/' });
+ // Then
+ expect(result).toBe(0);
+ calls(loggerMock.error).toMatchObject([{ msg: 'Failed to get disk space', error: mockError }]);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/get-disk-space.ts b/packages/core/src/backend/features/cleaner/utils/get-disk-space.ts
new file mode 100644
index 0000000000..a05115c97f
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-disk-space.ts
@@ -0,0 +1,13 @@
+import checkDiskSpace from 'check-disk-space';
+
+import { logger } from '@/backend/core/logger/logger';
+
+export async function getDiskSpace({ mainPath }: { mainPath: string }) {
+ try {
+ const { size } = await checkDiskSpace(mainPath);
+ return size;
+ } catch (error) {
+ logger.error({ msg: 'Failed to get disk space', error });
+ return 0;
+ }
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.test.ts b/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.test.ts
new file mode 100644
index 0000000000..018e260b10
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.test.ts
@@ -0,0 +1,70 @@
+import { promises as fs, Dirent } from 'node:fs';
+
+import { deepMocked } from '@/tests/vitest/utils.helper.test';
+
+import { getFilteredDirectories } from './get-filtered-directories';
+
+vi.mock(import('node:fs'));
+
+describe('getFilteredDirectories', () => {
+ const mockReaddir = deepMocked(fs.readdir);
+
+ const createMockDirent = (name: string, isDirectory = true) =>
+ ({
+ name,
+ isDirectory: () => isDirectory,
+ }) as unknown as Dirent;
+
+ it('should filter out Internxt-related directories and return only regular directories', async () => {
+ // Given
+ const mockDirents = [
+ createMockDirent('Documents', true),
+ createMockDirent('internxt-folder', true),
+ createMockDirent('drive-desktop-cache', true),
+ createMockDirent('Pictures', true),
+ createMockDirent('file.txt', false),
+ createMockDirent('Videos', true),
+ ];
+ mockReaddir.mockResolvedValue(mockDirents);
+ // When
+ const result = await getFilteredDirectories({ baseDir: '/test/path' });
+ // Then
+ expect(result.map((d) => d.name)).toStrictEqual(['Documents', 'Pictures', 'Videos']);
+ });
+
+ it('should apply custom directory filter when provided', async () => {
+ // Given
+ const mockDirents = [
+ createMockDirent('Documents', true),
+ createMockDirent('Pictures', true),
+ createMockDirent('Videos', true),
+ createMockDirent('TempFolder', true),
+ createMockDirent('file.txt', false),
+ ];
+ const customFilter = ({ folderName }: { folderName: string }) => folderName === 'TempFolder';
+ mockReaddir.mockResolvedValue(mockDirents);
+ // When
+ const result = await getFilteredDirectories({
+ baseDir: '/test/path',
+ customDirectoryFilter: customFilter,
+ });
+ // Then
+ expect(result.map((d) => d.name)).toStrictEqual(['Documents', 'Pictures', 'Videos']);
+ });
+
+ it('should return empty array when no directories match the filters', async () => {
+ // Given
+ const mockDirents = [
+ createMockDirent('internxt-data', true),
+ createMockDirent('INTERNXT-Cache', true),
+ createMockDirent('drive-desktop-logs', true),
+ createMockDirent('file1.txt', false),
+ createMockDirent('file2.pdf', false),
+ ];
+ mockReaddir.mockResolvedValue(mockDirents);
+ // When
+ const result = await getFilteredDirectories({ baseDir: '/test/path' });
+ // Then
+ expect(result).toHaveLength(0);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.ts b/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.ts
new file mode 100644
index 0000000000..af0c1714d8
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-filtered-directories.ts
@@ -0,0 +1,16 @@
+import { promises as fs } from 'node:fs';
+
+import { isInternxtRelated } from '../utils/is-file-internxt-related';
+
+type Props = {
+ baseDir: string;
+ customDirectoryFilter?: ({ folderName }: { folderName: string }) => boolean;
+};
+
+export async function getFilteredDirectories({ baseDir, customDirectoryFilter }: Props) {
+ const dirents = await fs.readdir(baseDir, { withFileTypes: true });
+ return dirents.filter((dirent) => {
+ const isExcluded = customDirectoryFilter?.({ folderName: dirent.name });
+ return dirent.isDirectory() && !isInternxtRelated({ name: dirent.name }) && !isExcluded;
+ });
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.test.ts b/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.test.ts
new file mode 100644
index 0000000000..4c1533ce18
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.test.ts
@@ -0,0 +1,77 @@
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { CleanableItem } from '../types/cleaner.types';
+import { getSelectedItemsForSection } from './get-selected-items-for-section';
+
+describe('getSelectedItemsForSection', () => {
+ const mockItems: Partial[] = [
+ { fullPath: '/path/to/file1.txt' },
+ { fullPath: '/path/to/file2.txt' },
+ { fullPath: '/path/to/file3.txt' },
+ { fullPath: '/path/to/file4.txt' },
+ ];
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ props = mockProps({
+ sectionItems: mockItems,
+ sectionViewModel: {
+ exceptions: [],
+ },
+ });
+ });
+
+ describe('when selectedAll is true', () => {
+ beforeEach(() => {
+ props.sectionViewModel.selectedAll = true;
+ });
+
+ it('should return all items when there are no exceptions', () => {
+ // When
+ const result = getSelectedItemsForSection(props);
+ // Then
+ expect(result).toStrictEqual(mockItems);
+ });
+
+ it('should exclude items that are in exceptions', () => {
+ // Given
+ props.sectionViewModel.exceptions = ['/path/to/file2.txt', '/path/to/file4.txt'];
+ // When
+ const result = getSelectedItemsForSection(props);
+ // Then
+ expect(result).toStrictEqual([mockItems[0], mockItems[2]]);
+ });
+ });
+
+ describe('when selectedAll is false', () => {
+ beforeEach(() => {
+ props.sectionViewModel.selectedAll = false;
+ });
+
+ it('should return empty array when there are no exceptions', () => {
+ // When
+ const result = getSelectedItemsForSection(props);
+ // Then
+ expect(result).toHaveLength(0);
+ });
+
+ it('should return only items that are in exceptions', () => {
+ // Given
+ props.sectionViewModel.exceptions = ['/path/to/file1.txt', '/path/to/file3.txt'];
+ // When
+ const result = getSelectedItemsForSection(props);
+ // Then
+ expect(result).toStrictEqual([mockItems[0], mockItems[2]]);
+ });
+
+ it('should return all items when all paths are in exceptions', () => {
+ // Given
+ props.sectionViewModel.exceptions = ['/path/to/file1.txt', '/path/to/file2.txt', '/path/to/file3.txt', '/path/to/file4.txt'];
+ // When
+ const result = getSelectedItemsForSection(props);
+ // Then
+ expect(result).toStrictEqual(mockItems);
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.ts b/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.ts
new file mode 100644
index 0000000000..15ed5e1076
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/get-selected-items-for-section.ts
@@ -0,0 +1,14 @@
+import { CleanableItem, CleanerSectionViewModel } from '../types/cleaner.types';
+
+type Props = {
+ sectionViewModel: CleanerSectionViewModel;
+ sectionItems: CleanableItem[];
+};
+
+export function getSelectedItemsForSection({ sectionViewModel, sectionItems }: Props) {
+ if (sectionViewModel.selectedAll) {
+ return sectionItems.filter((item) => !sectionViewModel.exceptions.includes(item.fullPath));
+ } else {
+ return sectionItems.filter((item) => sectionViewModel.exceptions.includes(item.fullPath));
+ }
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.test.ts b/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.test.ts
new file mode 100644
index 0000000000..94d0ec5f45
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.test.ts
@@ -0,0 +1,40 @@
+import { isInternxtRelated } from './is-file-internxt-related';
+
+describe('isInternxtRelated', () => {
+ it.each([
+ 'internxt',
+ 'Internxt',
+ 'INTERNXT',
+ 'internxt-app',
+ 'my-internxt-folder',
+ '/home/user/.cache/internxt',
+ 'internxt-drive',
+ 'internxtlike',
+ 'notinternxt',
+ '/home/user/.local/share/internxt/cache',
+ '~/.cache/drive-desktop/logs',
+ '/var/log/internxt-desktop.log',
+ './internxt-temp-files',
+ '/home/user/.config/internxt/logs',
+ ])('should return true for internxt pattern: "%s"', (name) => {
+ expect(isInternxtRelated({ name })).toBe(true);
+ });
+
+ it.each(['drive-desktop', 'Drive-Desktop', 'DRIVE-DESKTOP', 'drive-desktop-linux', 'my-drive-desktop-app', '/var/log/drive-desktop.log'])(
+ 'should return true for drive-desktop pattern: "%s"',
+ (name) => {
+ expect(isInternxtRelated({ name })).toBe(true);
+ },
+ );
+
+ it.each(['google-chrome', 'firefox', 'application.log', 'temp-file.txt', 'system-cache', '/random/random-file'])(
+ 'should return false for non-internxt related name: "%s"',
+ (name) => {
+ expect(isInternxtRelated({ name })).toBe(false);
+ },
+ );
+
+ it('should return false for empty string', () => {
+ expect(isInternxtRelated({ name: '' })).toBe(false);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.ts b/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.ts
new file mode 100644
index 0000000000..becbd88f42
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-file-internxt-related.ts
@@ -0,0 +1,5 @@
+export function isInternxtRelated({ name }: { name: string }) {
+ const internxtPatterns = [/internxt/i, /drive-desktop/i];
+
+ return internxtPatterns.some((pattern) => pattern.test(name));
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.test.ts b/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.test.ts
new file mode 100644
index 0000000000..dcafc0f434
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.test.ts
@@ -0,0 +1,68 @@
+import { Dirent } from 'node:fs';
+
+import { isFirefoxProfileDirectory } from './is-firefox-profile-directory';
+
+describe('isFirefoxProfileDirectory', () => {
+ const createMockDirent = (name: string, isDirectory = true): Dirent =>
+ ({
+ name,
+ isDirectory: () => isDirectory,
+ isFile: () => !isDirectory,
+ }) as Dirent;
+
+ it.each([
+ ['abc123.default', '/home/user/firefox/profiles'],
+ ['xyz789.default-esr', '/home/user/.mozilla/firefox/profiles'],
+ ['profile.default-release', '/Users/john/Library/Application Support/Firefox/Profiles'],
+ ['test123.default', '/path/to/firefox/profiles/subfolder'],
+ ['a1b2c3.default-dev', '/PROFILES/firefox'],
+ ['123.default', '/home/user/firefox/profiles'],
+ ])('should return true for valid Firefox profile directory: "%s" in path "%s"', (entryName, parentPath) => {
+ // Given
+ const entry = createMockDirent(entryName, true);
+ // When
+ const result = isFirefoxProfileDirectory({ entry, parentPath });
+ // Then
+ expect(result).toBe(true);
+ });
+
+ it.each([
+ ['not-a-profile', '/home/user/firefox/profiles'],
+ ['profile', '/home/user/firefox/profiles'],
+ ['profile.', '/home/user/firefox/profiles'],
+ ['profile.default.extra', '/home/user/firefox/profiles'],
+ ['profile-default', '/home/user/firefox/profiles'],
+ ['.default', '/home/user/firefox/profiles'],
+ ['profile.default-', '/home/user/firefox/profiles'],
+ ])('should return false for invalid Firefox profile directory name: "%s"', (entryName, parentPath) => {
+ // Given
+ const entry = createMockDirent(entryName, true);
+ // When
+ const result = isFirefoxProfileDirectory({ entry, parentPath });
+ // Then
+ expect(result).toBe(false);
+ });
+
+ it.each([
+ ['abc123.default', '/home/user/firefox'],
+ ['xyz789.default-esr', '/home/user/documents'],
+ ['profile.default-release', '/Users/john/Library'],
+ ['test123.default', '/random/path'],
+ ])('should return false when parent path does not contain "profiles": "%s" in path "%s"', (entryName, parentPath) => {
+ // Given
+ const entry = createMockDirent(entryName, true);
+ // When
+ const result = isFirefoxProfileDirectory({ entry, parentPath });
+ // Then
+ expect(result).toBe(false);
+ });
+
+ it('should return false when entry is not a directory', () => {
+ // Given
+ const entry = createMockDirent('abc123.default', false);
+ // When
+ const result = isFirefoxProfileDirectory({ entry, parentPath: '/home/user/firefox/profiles' });
+ // Then
+ expect(result).toBe(false);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.ts b/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.ts
new file mode 100644
index 0000000000..5752b73273
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-firefox-profile-directory.ts
@@ -0,0 +1,14 @@
+import { Dirent } from 'node:fs';
+
+type Props = {
+ entry: Dirent;
+ parentPath: string;
+};
+
+export function isFirefoxProfileDirectory({ entry, parentPath }: Props) {
+ if (!entry.isDirectory()) return false;
+ if (!parentPath.toLowerCase().includes('profiles')) return false;
+
+ const profileRegex = /^[a-z0-9]+\.default(-[a-z]+)?$/i;
+ return profileRegex.test(entry.name);
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.test.ts b/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.test.ts
new file mode 100644
index 0000000000..40e18acc6d
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.test.ts
@@ -0,0 +1,35 @@
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { isSafeWebBrowserFile } from './is-safe-web-browser-file';
+
+describe('isSafeWebBrowserFile', () => {
+ let props: Parameters[0];
+ beforeEach(() => {
+ props = mockProps({
+ fileName: '',
+ ctx: {
+ browser: {
+ criticalExtensions: ['.sqlite', '.db', '.log'],
+ criticalFilenames: ['lock', 'prefs.js', 'local state'],
+ },
+ },
+ });
+ });
+
+ describe('Cross-platform critical patterns', () => {
+ it.each(['file.sqlite', 'data.db', 'config.log'])('should reject files with critical extensions: "%s"', (file) => {
+ props.fileName = file;
+ expect(isSafeWebBrowserFile(props)).toBe(false);
+ });
+
+ it.each(['lock', 'prefs.js', 'local state'])('should reject files with critical filenames: "%s"', (file) => {
+ props.fileName = file;
+ expect(isSafeWebBrowserFile(props)).toBe(false);
+ });
+
+ it.each(['document.txt', 'image.png', 'video.mp4'])('should allow safe file: "%s"', (file) => {
+ props.fileName = file;
+ expect(isSafeWebBrowserFile(props)).toBe(true);
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.ts b/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.ts
new file mode 100644
index 0000000000..83fd1e026c
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/is-safe-web-browser-file.ts
@@ -0,0 +1,12 @@
+import { CleanerContext } from '../types/cleaner.types';
+
+type Props = {
+ ctx: CleanerContext;
+ fileName: string;
+};
+
+export function isSafeWebBrowserFile({ ctx, fileName }: Props) {
+ const lowerName = fileName.toLowerCase();
+
+ return !(ctx.browser.criticalExtensions.some((ext) => lowerName.endsWith(ext)) || ctx.browser.criticalFilenames.includes(lowerName));
+}
diff --git a/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.test.ts b/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.test.ts
new file mode 100644
index 0000000000..2a3a16d943
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.test.ts
@@ -0,0 +1,48 @@
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { wasAccessedWithinLastHour } from './was-accessed-within-last-hour';
+
+describe('wasAccessedWithinLastHour', () => {
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2025-09-19T12:00:00Z'));
+
+ props = mockProps({
+ fileStats: {
+ atimeMs: new Date('2025-09-19T10:59:00Z').getTime(),
+ mtimeMs: new Date('2025-09-19T10:59:00Z').getTime(),
+ },
+ });
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('should return true when file was modified within last hour', () => {
+ // Given
+ props.fileStats.mtimeMs = new Date('2025-09-19T11:00:00Z').getTime();
+ // When
+ const result = wasAccessedWithinLastHour(props);
+ // Then
+ expect(result).toBe(true);
+ });
+
+ it('should return true when file was accessed within last hour', () => {
+ // Given
+ props.fileStats.atimeMs = new Date('2025-09-19T11:00:00Z').getTime();
+ // When
+ const result = wasAccessedWithinLastHour(props);
+ // Then
+ expect(result).toBe(true);
+ });
+
+ it('should return false when file was accessed or modified more than an hour ago', () => {
+ // When
+ const result = wasAccessedWithinLastHour(props);
+ // Then
+ expect(result).toBe(false);
+ });
+});
diff --git a/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.ts b/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.ts
new file mode 100644
index 0000000000..f49fb07525
--- /dev/null
+++ b/packages/core/src/backend/features/cleaner/utils/was-accessed-within-last-hour.ts
@@ -0,0 +1,11 @@
+import { Stats } from 'node:fs';
+
+type Props = {
+ fileStats: Stats;
+};
+
+export function wasAccessedWithinLastHour({ fileStats }: Props) {
+ const lastAccessTime = Math.max(fileStats.atimeMs, fileStats.mtimeMs);
+ const hoursSinceAccess = (Date.now() - lastAccessTime) / (1000 * 60 * 60);
+ return hoursSinceAccess <= 1;
+}
diff --git a/packages/core/src/backend/features/payments/payments.module.ts b/packages/core/src/backend/features/payments/payments.module.ts
new file mode 100644
index 0000000000..59931f613b
--- /dev/null
+++ b/packages/core/src/backend/features/payments/payments.module.ts
@@ -0,0 +1,6 @@
+import { getUserAvailableProducts } from './services/get-user-available-products';
+
+export type { UserAvailableProducts } from './payments.types';
+export const PaymentsModule = {
+ getUserAvailableProducts,
+};
diff --git a/packages/core/src/backend/features/payments/payments.types.ts b/packages/core/src/backend/features/payments/payments.types.ts
new file mode 100644
index 0000000000..eb9c9c3a11
--- /dev/null
+++ b/packages/core/src/backend/features/payments/payments.types.ts
@@ -0,0 +1,5 @@
+export type UserAvailableProducts = {
+ antivirus: boolean;
+ backups: boolean;
+ cleaner: boolean;
+};
diff --git a/packages/core/src/backend/features/payments/services/get-payments-client.ts b/packages/core/src/backend/features/payments/services/get-payments-client.ts
new file mode 100644
index 0000000000..1fde261809
--- /dev/null
+++ b/packages/core/src/backend/features/payments/services/get-payments-client.ts
@@ -0,0 +1,32 @@
+import { Payments } from '@internxt/sdk/dist/drive';
+
+export type PaymentsClientConfig = {
+ paymentsUrl: string;
+ desktopHeader: string;
+ clientName: string;
+ clientVersion: string;
+ token: string;
+ unauthorizedCallback: () => void;
+};
+
+export function getPaymentsClient({
+ paymentsUrl,
+ desktopHeader,
+ clientName,
+ clientVersion,
+ token,
+ unauthorizedCallback,
+}: PaymentsClientConfig) {
+ return Payments.client(
+ paymentsUrl,
+ {
+ clientName,
+ clientVersion,
+ desktopHeader,
+ },
+ {
+ unauthorizedCallback,
+ token,
+ },
+ );
+}
diff --git a/packages/core/src/backend/features/payments/services/get-user-available-products.test.ts b/packages/core/src/backend/features/payments/services/get-user-available-products.test.ts
new file mode 100644
index 0000000000..495d8ebf60
--- /dev/null
+++ b/packages/core/src/backend/features/payments/services/get-user-available-products.test.ts
@@ -0,0 +1,68 @@
+import { Payments } from '@internxt/sdk/dist/drive';
+import { Tier } from '@internxt/sdk/dist/drive/payments/types/tiers';
+import { mockDeep } from 'vitest-mock-extended';
+
+import { logger } from '@/backend/core/logger/logger';
+import { partialSpyOn, mockProps } from '@/tests/vitest/utils.helper.test';
+
+import * as userAvailableProductsMapperFile from '../user-available-products.mapper';
+import * as getPaymentsClientFile from './get-payments-client';
+import { getUserAvailableProducts } from './get-user-available-products';
+
+describe('getUserAvailableProducts', () => {
+ const userAvailableProductsMapperMock = partialSpyOn(userAvailableProductsMapperFile, 'userAvailableProductsMapper');
+ const getPaymentsClientMock = partialSpyOn(getPaymentsClientFile, 'getPaymentsClient');
+ const loggerErrorMock = partialSpyOn(logger, 'error');
+ const paymentsClientMock = mockDeep();
+ const props = mockProps({
+ paymentsClientConfig: {},
+ });
+
+ beforeEach(() => {
+ getPaymentsClientMock.mockReturnValue(paymentsClientMock);
+ });
+
+ it('should properly fetch for the user available products and map the result to the object domain', async () => {
+ const getUserTierResponseMock = {
+ featuresPerService: {
+ backups: { enabled: true },
+ antivirus: { enabled: true },
+ cleaner: { enabled: true },
+ },
+ } as Tier;
+
+ paymentsClientMock.getUserTier.mockResolvedValue(getUserTierResponseMock);
+
+ const mappedResult = {
+ backups: true,
+ antivirus: false,
+ cleaner: true,
+ };
+
+ userAvailableProductsMapperMock.mockReturnValue(mappedResult);
+
+ const result = await getUserAvailableProducts(props);
+
+ expect(getPaymentsClientMock).toHaveBeenCalledWith(props.paymentsClientConfig);
+ expect(paymentsClientMock.getUserTier).toHaveBeenCalledTimes(1);
+ expect(userAvailableProductsMapperMock).toHaveBeenCalledWith(getUserTierResponseMock.featuresPerService);
+ expect(result).toStrictEqual(mappedResult);
+ });
+
+ it('should handle errors from paymentsClient.getUserTier and log them', async () => {
+ const mockError = new Error('API Error');
+ paymentsClientMock.getUserTier.mockRejectedValue(mockError);
+
+ const result = await getUserAvailableProducts(props);
+
+ expect(getPaymentsClientMock).toHaveBeenCalledWith(props.paymentsClientConfig);
+ expect(paymentsClientMock.getUserTier).toHaveBeenCalledTimes(1);
+ expect(userAvailableProductsMapperMock).not.toHaveBeenCalled();
+ expect(loggerErrorMock).toHaveBeenCalledWith({
+ tag: 'PRODUCTS',
+ msg: 'Failed to get user available products with error:',
+ error: mockError,
+ });
+ expect(result).toBeUndefined();
+ });
+});
diff --git a/packages/core/src/backend/features/payments/services/get-user-available-products.ts b/packages/core/src/backend/features/payments/services/get-user-available-products.ts
new file mode 100644
index 0000000000..ca6f7efc3b
--- /dev/null
+++ b/packages/core/src/backend/features/payments/services/get-user-available-products.ts
@@ -0,0 +1,19 @@
+import { logger } from '@/backend/core/logger/logger';
+
+import { userAvailableProductsMapper } from '../user-available-products.mapper';
+import { getPaymentsClient, PaymentsClientConfig } from './get-payments-client';
+
+export async function getUserAvailableProducts({ paymentsClientConfig }: { paymentsClientConfig: PaymentsClientConfig }) {
+ try {
+ const paymentsClient = getPaymentsClient(paymentsClientConfig);
+ const userProductsInfo = await paymentsClient.getUserTier();
+ return userAvailableProductsMapper(userProductsInfo.featuresPerService);
+ } catch (error) {
+ logger.error({
+ tag: 'PRODUCTS',
+ msg: 'Failed to get user available products with error:',
+ error,
+ });
+ return undefined;
+ }
+}
diff --git a/packages/core/src/backend/features/payments/user-available-products.mapper.test.ts b/packages/core/src/backend/features/payments/user-available-products.mapper.test.ts
new file mode 100644
index 0000000000..edb087b96f
--- /dev/null
+++ b/packages/core/src/backend/features/payments/user-available-products.mapper.test.ts
@@ -0,0 +1,38 @@
+import { Tier } from '@internxt/sdk/dist/drive/payments/types/tiers';
+
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { userAvailableProductsMapper } from './user-available-products.mapper';
+
+describe('userAvailableProductsMapper', () => {
+ it('should correctly map an object of Tier["featuresPerService"] into the proper domain object', () => {
+ const props = mockProps({
+ backups: { enabled: true },
+ antivirus: { enabled: false },
+ cleaner: { enabled: true },
+ });
+
+ const result = userAvailableProductsMapper(props);
+
+ expect(result).toStrictEqual({
+ backups: true,
+ antivirus: false,
+ cleaner: true,
+ });
+ });
+
+ it('should correctly map into the proper domain object even though we recieve incorrect properties', () => {
+ const props = mockProps({
+ antivirus: { enabled: null } as unknown as Tier['featuresPerService']['antivirus'],
+ backups: { enabled: true },
+ });
+
+ const result = userAvailableProductsMapper(props);
+
+ expect(result).toStrictEqual({
+ backups: true,
+ antivirus: false,
+ cleaner: false,
+ });
+ });
+});
diff --git a/packages/core/src/backend/features/payments/user-available-products.mapper.ts b/packages/core/src/backend/features/payments/user-available-products.mapper.ts
new file mode 100644
index 0000000000..f7adbae426
--- /dev/null
+++ b/packages/core/src/backend/features/payments/user-available-products.mapper.ts
@@ -0,0 +1,9 @@
+import { Tier } from '@internxt/sdk/dist/drive/payments/types/tiers';
+
+export function userAvailableProductsMapper(featuresPerService: Tier['featuresPerService']) {
+ return {
+ backups: !!featuresPerService['backups']?.enabled,
+ antivirus: !!featuresPerService['antivirus']?.enabled,
+ cleaner: !!featuresPerService['cleaner']?.enabled,
+ };
+}
diff --git a/packages/core/src/backend/features/sync/index.tsx b/packages/core/src/backend/features/sync/index.tsx
new file mode 100644
index 0000000000..abf6cbae62
--- /dev/null
+++ b/packages/core/src/backend/features/sync/index.tsx
@@ -0,0 +1,3 @@
+export const SyncModule = {
+ MAX_FILE_SIZE: 40 * 1024 * 1024 * 1024,
+};
diff --git a/packages/core/src/backend/index.ts b/packages/core/src/backend/index.ts
new file mode 100644
index 0000000000..8465881ba2
--- /dev/null
+++ b/packages/core/src/backend/index.ts
@@ -0,0 +1,8 @@
+export { logger, TLoggerBody } from './core/logger/logger';
+export { setupElectronLog } from './core/logger/setup-electron-log';
+export { throwWrapper } from './core/utils/throw-wrapper';
+
+export { FileSystemModule, AbsolutePath, RelativePath } from './infra/file-system/file-system.module';
+export { PaymentsModule, UserAvailableProducts } from './features/payments/payments.module';
+export { CleanerModule } from './features/cleaner/cleaner.module';
+export { SyncModule } from './features/sync';
diff --git a/packages/core/src/backend/infra/file-system/file-system.module.ts b/packages/core/src/backend/infra/file-system/file-system.module.ts
new file mode 100644
index 0000000000..549ad4a27e
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/file-system.module.ts
@@ -0,0 +1,12 @@
+import { throwWrapper } from '@/backend/core/utils/throw-wrapper';
+
+import { readdir } from './services/readdir';
+import { stat } from './services/stat';
+
+export type { AbsolutePath, RelativePath } from './file-system.types';
+export const FileSystemModule = {
+ stat,
+ statThrow: throwWrapper(stat),
+ readdir,
+ readdirThrow: throwWrapper(readdir),
+};
diff --git a/packages/core/src/backend/infra/file-system/file-system.types.ts b/packages/core/src/backend/infra/file-system/file-system.types.ts
new file mode 100644
index 0000000000..5bb2cddb27
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/file-system.types.ts
@@ -0,0 +1,4 @@
+import { Brand } from '@/backend/core/utils/brand.types';
+
+export type AbsolutePath = Brand;
+export type RelativePath = Brand;
diff --git a/packages/core/src/backend/infra/file-system/services/readdir.test.ts b/packages/core/src/backend/infra/file-system/services/readdir.test.ts
new file mode 100644
index 0000000000..4ccd4eca27
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/services/readdir.test.ts
@@ -0,0 +1,36 @@
+import { execSync } from 'node:child_process';
+import { mkdir } from 'node:fs/promises';
+import { join } from 'node:path';
+import { cwd } from 'node:process';
+import { v4 } from 'uuid';
+
+import { TEST_FILES } from '@/tests/vitest/mocks.helper.test';
+
+import { readdir } from './readdir';
+
+describe('readdir', () => {
+ it('If folder exists', async () => {
+ // When
+ const { data } = await readdir({ absolutePath: cwd() });
+ // Then
+ expect(data).toBeTruthy();
+ });
+
+ it('If folder does not exist (ENOENT)', async () => {
+ // When
+ const { error } = await readdir({ absolutePath: 'non_existing_folder' });
+ // Then
+ expect(error?.code).toEqual('NON_EXISTS');
+ });
+
+ it('If folder access is denied (EPERM)', async () => {
+ // Given
+ const folder = join(TEST_FILES, v4());
+ await mkdir(folder);
+ execSync(`icacls "${folder}" /deny "${process.env.USERNAME}":F`);
+ // When
+ const { error } = await readdir({ absolutePath: folder });
+ // Then
+ expect(error?.code).toEqual('NO_ACCESS');
+ });
+});
diff --git a/packages/core/src/backend/infra/file-system/services/readdir.ts b/packages/core/src/backend/infra/file-system/services/readdir.ts
new file mode 100644
index 0000000000..cc75c50816
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/services/readdir.ts
@@ -0,0 +1,37 @@
+import fs from 'node:fs/promises';
+
+class ReaddirError extends Error {
+ constructor(
+ public readonly code: 'NON_EXISTS' | 'NO_ACCESS' | 'UNKNOWN',
+ cause?: unknown,
+ ) {
+ super(code, { cause });
+ }
+}
+
+type Props = { absolutePath: string };
+
+export async function readdir({ absolutePath }: Props) {
+ try {
+ const readdir = await fs.readdir(absolutePath, { withFileTypes: true });
+
+ return { data: readdir };
+ } catch (error) {
+ if (error instanceof Error) {
+ if (error.message.includes('ENOENT')) {
+ return { error: new ReaddirError('NON_EXISTS', error) };
+ }
+
+ /**
+ * v0.1.4 Daniel JimΓ©nez
+ * TODO: EACCES has not been reproduced in windows
+ * https://stackoverflow.com/questions/59428844/listen-eacces-permission-denied-in-windows
+ */
+ if (error.message.includes('EPERM') || error.message.includes('EACCES')) {
+ return { error: new ReaddirError('NO_ACCESS', error) };
+ }
+ }
+
+ return { error: new ReaddirError('UNKNOWN', error) };
+ }
+}
diff --git a/packages/core/src/backend/infra/file-system/services/stat.test.ts b/packages/core/src/backend/infra/file-system/services/stat.test.ts
new file mode 100644
index 0000000000..a447ac55b9
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/services/stat.test.ts
@@ -0,0 +1,40 @@
+import { execSync } from 'node:child_process';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+import { cwd } from 'node:process';
+import { v4 } from 'uuid';
+
+import { TEST_FILES } from '@/tests/vitest/mocks.helper.test';
+
+import { stat } from './stat';
+
+describe('stat', () => {
+ it('If file exists', async () => {
+ // When
+ const { data } = await stat({ absolutePath: join(cwd(), 'package.json') });
+ // Then
+ expect(data).toBeTruthy();
+ });
+
+ it('If file does not exist (ENOENT)', async () => {
+ // When
+ const { error } = await stat({ absolutePath: 'non_existing_file' });
+ // Then
+ expect(error?.code).toEqual('NON_EXISTS');
+ });
+
+ it('If file access is denied (EPERM)', async () => {
+ // Given
+ const folder = join(TEST_FILES, v4());
+ const file = join(folder, 'file.txt');
+
+ await mkdir(folder);
+ await writeFile(file, 'content');
+
+ execSync(`icacls "${file}" /deny "${process.env.USERNAME}":F`);
+ // When
+ const { error } = await stat({ absolutePath: file });
+ // Then
+ expect(error?.code).toEqual('NO_ACCESS');
+ });
+});
diff --git a/packages/core/src/backend/infra/file-system/services/stat.ts b/packages/core/src/backend/infra/file-system/services/stat.ts
new file mode 100644
index 0000000000..7a4931b164
--- /dev/null
+++ b/packages/core/src/backend/infra/file-system/services/stat.ts
@@ -0,0 +1,37 @@
+import fs from 'node:fs/promises';
+
+class StatError extends Error {
+ constructor(
+ public readonly code: 'NON_EXISTS' | 'NO_ACCESS' | 'UNKNOWN',
+ cause?: unknown,
+ ) {
+ super(code, { cause });
+ }
+}
+
+type Props = { absolutePath: string };
+
+export async function stat({ absolutePath }: Props) {
+ try {
+ const stat = await fs.stat(absolutePath);
+
+ return { data: stat };
+ } catch (error) {
+ if (error instanceof Error) {
+ if (error.message.includes('ENOENT')) {
+ return { error: new StatError('NON_EXISTS', error) };
+ }
+
+ /**
+ * v0.1.1 Daniel JimΓ©nez
+ * TODO: EACCES has not been reproduced in windows
+ * https://stackoverflow.com/questions/59428844/listen-eacces-permission-denied-in-windows
+ */
+ if (error.message.includes('EPERM') || error.message.includes('EACCES')) {
+ return { error: new StatError('NO_ACCESS', error) };
+ }
+ }
+
+ return { error: new StatError('UNKNOWN', error) };
+ }
+}
diff --git a/packages/core/src/frontend/components/button.tsx b/packages/core/src/frontend/components/button.tsx
new file mode 100644
index 0000000000..c4edafc40c
--- /dev/null
+++ b/packages/core/src/frontend/components/button.tsx
@@ -0,0 +1,38 @@
+const sizes = {
+ sm: 'h-7 px-3 rounded-md text-sm',
+ md: 'h-8 px-[14px] rounded-lg text-base',
+ lg: 'h-10 px-5 rounded-lg text-base',
+ xl: 'h-11 px-5 rounded-lg text-base',
+ '2xl': 'h-12 px-5 rounded-lg text-lg',
+};
+
+type Props = {
+ variant?: 'primary' | 'danger' | 'secondary' | 'primaryLight' | 'dangerLight';
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
+} & React.ButtonHTMLAttributes;
+
+export function Button({ variant = 'primary', size = 'md', type = 'button', className, disabled = false, children, ...props }: Props) {
+ const variants = {
+ primary: disabled ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' : 'bg-primary active:bg-primary-dark text-white',
+ primaryLight: disabled
+ ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30'
+ : 'border border-primary bg-surface text-primary hover:cursor-pointer',
+ secondary: disabled
+ ? 'bg-surface text-gray-40 border border-gray-5 dark:bg-gray-5 dark:text-gray-40'
+ : 'bg-surface active:bg-gray-1 text-highlight border border-gray-20 dark:bg-gray-5 dark:active:bg-gray-10 dark:active:border-gray-30',
+ danger: disabled ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' : 'bg-red active:bg-red-dark text-white',
+ dangerLight: disabled
+ ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30'
+ : 'border border-red-dark bg-surface text-red-dark hover:cursor-pointer',
+ };
+
+ const styles = `whitespace-nowrap shadow-sm outline-none transition-all duration-75 ease-in-out focus-visible:outline-none ${
+ variants[variant]
+ } ${sizes[size]} ${className}`;
+
+ return (
+
+ );
+}
diff --git a/packages/core/src/frontend/components/checkbox.tsx b/packages/core/src/frontend/components/checkbox.tsx
new file mode 100644
index 0000000000..4456001cf3
--- /dev/null
+++ b/packages/core/src/frontend/components/checkbox.tsx
@@ -0,0 +1,54 @@
+type Props = {
+ onClick: () => void;
+ label: string;
+ customClassName?: string;
+} & React.InputHTMLAttributes;
+
+export function Checkbox({ disabled = false, checked, label, customClassName = '', onClick }: Props) {
+ const checkedClasses = checked
+ ? 'border-primary-dark bg-primary group-active:bg-primary-dark'
+ : 'bg-surface group-active:bg-gray-1 dark:bg-gray-5';
+
+ return (
+
+ );
+}
+
+function Check() {
+ return (
+
+ );
+}
diff --git a/packages/core/src/frontend/components/section-spinner.tsx b/packages/core/src/frontend/components/section-spinner.tsx
new file mode 100644
index 0000000000..2e0180eef5
--- /dev/null
+++ b/packages/core/src/frontend/components/section-spinner.tsx
@@ -0,0 +1,9 @@
+import { Spinner } from '@phosphor-icons/react';
+
+export function SectionSpinner() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/components/spinner.tsx b/packages/core/src/frontend/components/spinner.tsx
new file mode 100644
index 0000000000..8b5162e24e
--- /dev/null
+++ b/packages/core/src/frontend/components/spinner.tsx
@@ -0,0 +1,14 @@
+type Props = {
+ className?: string;
+};
+
+export function Spinner({ className }: Readonly) {
+ return (
+
+ );
+}
diff --git a/packages/core/src/frontend/core/i18n/i18n.types.ts b/packages/core/src/frontend/core/i18n/i18n.types.ts
new file mode 100644
index 0000000000..7df053c121
--- /dev/null
+++ b/packages/core/src/frontend/core/i18n/i18n.types.ts
@@ -0,0 +1,22 @@
+import { en } from './locales/en';
+
+export type Language = 'es' | 'en' | 'fr';
+export type Translation = typeof en;
+
+/**
+ * v2.6.0 Daniel JimΓ©nez
+ * This type generates all possible paths from the translations.
+ * We set a maximum depth of 10 for a translation path.
+ */
+type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+type Paths = [D] extends [never]
+ ? never
+ : T extends object
+ ? {
+ [K in keyof T]-?: K extends string | number ? (T[K] extends object ? `${K}.${Paths & string}` : `${K}`) : never;
+ }[keyof T]
+ : never;
+
+export type TranslationPath = Paths;
+export type TranslationArgs = Record;
+export type TranslationFn = (path: TranslationPath, args?: Record) => string;
diff --git a/packages/core/src/frontend/core/i18n/index.ts b/packages/core/src/frontend/core/i18n/index.ts
new file mode 100644
index 0000000000..2f41f30de8
--- /dev/null
+++ b/packages/core/src/frontend/core/i18n/index.ts
@@ -0,0 +1,11 @@
+import { en } from './locales/en';
+import { es } from './locales/es';
+import { fr } from './locales/fr';
+
+export { Language, TranslationFn, TranslationPath, TranslationArgs } from './i18n.types';
+
+export const I18nModule = {
+ en,
+ es,
+ fr,
+};
diff --git a/packages/core/src/frontend/core/i18n/locales/en.ts b/packages/core/src/frontend/core/i18n/locales/en.ts
new file mode 100644
index 0000000000..70f1b940fb
--- /dev/null
+++ b/packages/core/src/frontend/core/i18n/locales/en.ts
@@ -0,0 +1,406 @@
+export const en = {
+ login: {
+ signInBrowser: 'Log in with browser',
+ createAccount: 'Create account',
+ welcome: 'Welcome to Internxt',
+ noAccount: "Don't have an account?",
+ },
+ onboarding: {
+ slides: {
+ welcome: {
+ title: 'Internxt Desktop',
+ description:
+ 'Welcome to Internxt!\n\nBack up your files with Drive, secure against malware with Antivirus, and optimize performance with Cleaner β all while keeping your privacy protected.',
+ 'take-tour': 'Take a tour',
+ },
+ drive: {
+ title: 'Drive',
+ description:
+ 'Access all your files from the Internxt Drive folder in your {{platform_app}} sidebar.\n\nChoose to save space with online-only files, or keep essentials available offline β everything stays secure and in sync across your devices.',
+ },
+ antivirus: {
+ title: 'Antivirus',
+ description:
+ 'Protect your device from malware and online threats.\n\nInternxt Antivirus keeps you safe with real-time scans and privacy-first security.',
+ },
+ backups: {
+ title: 'Backup',
+ description:
+ "With Internxt's upgraded backup feature, you can now safely backup folders on the cloud in order to free up space locally. You can also adjust the backup frequency as you need.",
+ },
+ cleaner: {
+ title: 'Cleaner',
+ description:
+ 'Free up space locally and optimize your deviceβs performance.\n\nOur cleaner finds and removes unnecessary files, so your device runs smoothly.',
+ },
+ 'onboarding-completed': {
+ title: "You're all set, enjoy your privacy!",
+ 'desktop-ready': {
+ title: 'Internxt is ready',
+ description: 'Access your stored files from your {{platform_phrase}}βs sidebar',
+ },
+ },
+ },
+ common: {
+ 'onboarding-progress': '{{current_slide}} of {{total_slides}}',
+ continue: 'Continue',
+ skip: 'Skip',
+ 'open-drive': 'Open Internxt',
+ 'platform-phrase': {
+ windows: 'file explorer',
+ },
+ new: 'New',
+ },
+ },
+ widget: {
+ header: {
+ usage: {
+ of: 'of',
+ upgrade: 'Upgrade',
+ },
+ dropdown: {
+ new: 'New',
+ preferences: 'Preferences',
+ sync: 'Sync',
+ issues: 'Issues',
+ support: 'Support',
+ antivirus: 'Antivirus',
+ logout: 'Log out',
+ quit: 'Quit',
+ 'logout-confirmation': {
+ title: 'Log out from this device?',
+ message: 'Internxt Drive will not show up without an account logged in.',
+ confirm: 'Log out',
+ cancel: 'Cancel',
+ },
+ cleaner: 'Cleaner',
+ },
+ },
+ body: {
+ activity: {
+ operation: {
+ DELETE_ERROR: 'Delete error',
+ DELETED: 'Moved to trash',
+ DOWNLOAD_CANCEL: 'Downloaded Cancel',
+ DOWNLOAD_ERROR: 'Download error',
+ DOWNLOADED: 'Downloaded',
+ DOWNLOADING: 'Downloading',
+ MODIFIED: 'Modified',
+ MODIFY_ERROR: 'Modify error',
+ MOVE_ERROR: 'Move error',
+ MOVED: 'Moved',
+ RENAME_ERROR: 'Rename error',
+ RENAMED: 'Renamed',
+ UPLOAD_ERROR: 'Upload error',
+ UPLOADED: 'Uploaded',
+ UPLOADING: 'Uploading',
+ },
+ },
+ upToDate: {
+ title: 'Your files are up to date',
+ subtitle: 'Sync activity will show up here',
+ },
+ },
+ footer: {
+ 'action-description': {
+ syncing: 'Syncing your files',
+ updated: 'Fully synced',
+ failed: 'Sync failed',
+ 'sync-pending': 'Sync pending',
+ },
+ errors: {
+ lock: 'Sync locked by other device',
+ offline: 'Not connected to the internet',
+ },
+ },
+ 'sync-error': {
+ title: "Can't get remote content",
+ message: 'We are having issues retrieving your content from the cloud, please try again',
+ button: 'Try again',
+ },
+ banners: {
+ 'discover-backups': {
+ title: 'INTERNXT BACKUPS',
+ body: 'Keep a lifesaver copy of your most important folders and files.',
+ action: 'Backup this device',
+ },
+ },
+ },
+ settings: {
+ header: {
+ section: {
+ GENERAL: 'general',
+ ACCOUNT: 'account',
+ BACKUPS: 'backups',
+ ANTIVIRUS: 'Antivirus',
+ CLEANER: 'Cleaner',
+ },
+ },
+ general: {
+ language: {
+ label: 'Language',
+ options: {
+ es: 'EspaΓ±ol (Spanish)',
+ en: 'English (English)',
+ fr: 'FranΓ§ais (French)',
+ },
+ },
+ theme: {
+ label: 'Appearance',
+ options: {
+ system: 'System',
+ light: 'Light',
+ dark: 'Dark',
+ },
+ },
+
+ device: {
+ section: 'Device name',
+ action: {
+ edit: 'Edit',
+ cancel: 'Cancel',
+ save: 'Save',
+ },
+ },
+ 'auto-startup': 'Start Internxt on system startup',
+ sync: {
+ folder: 'Virtual Drive location',
+ changeLocation: 'Change location',
+ },
+ 'app-info': {
+ 'open-logs': 'Open logs',
+ more: 'Learn more about Internxt',
+ },
+ },
+ account: {
+ logout: 'Log out',
+ usage: {
+ display: 'Used {{used}} of {{total}}',
+ upgrade: 'Upgrade',
+ change: 'Change',
+ plan: 'Current plan',
+ free: 'Free',
+ loadError: {
+ title: "Couldn't fetch your usage details",
+ action: 'Retry',
+ },
+ current: {
+ used: 'Used',
+ of: 'of',
+ 'in-use': 'in use',
+ },
+ full: {
+ title: 'Your storage is full',
+ subtitle: "You can't upload, sync, or backup files. Upgrade now your plan or remove files to save up space.",
+ },
+ },
+ },
+ backups: {
+ title: 'Backup folders',
+ 'selected-folder_one': '{{count}} folder',
+ 'selected-folder_other': '{{count}} folders',
+ 'add-folders': 'Click + to select the folders\n you want to back up',
+ activate: 'Back up your folders and files',
+ 'view-backups': 'Browse Backups',
+ 'selected-folders-title': 'Selected folders',
+ 'select-folders': 'Change folders',
+ 'last-backup-had-issues': 'Last backup had some issues',
+ 'see-issues': 'See issues',
+ 'backing-up': 'Backing up...',
+ enable: {
+ message: 'Save a copy of your most important files on the cloud automatically',
+ action: 'Backup now',
+ },
+ action: {
+ start: 'Backup now',
+ stop: 'Stop backup',
+ 'last-run': 'Last updated',
+ download: 'Download',
+ stopDownload: 'Stop download',
+ downloading: 'Downloading backup',
+ },
+ frequency: {
+ title: 'Upload frequency',
+ options: {
+ '1h': 'Every hour',
+ '6h': 'Every 6 hours',
+ '12h': 'Every 12 hours',
+ '24h': 'Every day',
+ manually: 'Backup manually',
+ },
+ warning: "Folders won't automatically backup until you click 'Backup now'. This mode is not recommended.",
+ },
+ folders: {
+ 'no-folders': 'empty folder, no folder available',
+ 'no-folders-to-download': 'No folders available for download',
+ save: 'Save',
+ cancel: 'Cancel',
+ error: 'We could not load your backups',
+ },
+ delete: {
+ title: 'Delete backup',
+ explanation: 'This backup will be removed from the cloud, all folders and files will remain in this computer',
+ action: 'Delete backup',
+ 'deletion-modal': {
+ title: 'Delete backup?',
+ explanation: 'This backup will be removed from the cloud permanently, all folders and files will remain in this computer.',
+ 'explanation-2': 'This action cannot be undone.',
+ confirm: 'Yes, delete',
+ cancel: 'Cancel',
+ },
+ },
+ stop: {
+ modal: {
+ title: 'Stop ongoing backup',
+ explanation: 'There are still files that have not yet been uploaded. Stop backup anyway?',
+ 'explanation-2': '',
+ confirm: 'Stop backup',
+ cancel: 'Cancel',
+ },
+ },
+ },
+ antivirus: {
+ featureLocked: {
+ title: 'Feature locked',
+ subtitle: 'Please upgrade your plan to use this feature.',
+ action: 'Upgrade',
+ },
+ errorState: {
+ title: 'Something went wrong while scanning the directory',
+ button: 'Try again',
+ },
+ scanOptions: {
+ stopScan: 'Stop scan',
+ systemScan: {
+ text: 'Antivirus system scan',
+ action: 'Start scan',
+ },
+ customScan: {
+ text: 'Antivirus custom scan',
+ action: 'Choose',
+ selector: {
+ files: 'Files',
+ folders: 'Folders',
+ },
+ },
+ removeMalware: {
+ actions: {
+ cancel: 'Cancel',
+ remove: 'Remove',
+ },
+ actionRequired: {
+ title: 'Action required',
+ description:
+ 'Removing the malware will also permanently delete the folder from your storage to protect your device. This action cannot be undone.',
+ confirmToContinue: 'Please confirm to continue.',
+ },
+ securityWarning: {
+ title: 'Security warning',
+ description: 'Malware is still present, and your device is at risk.',
+ confirmToCancel: 'Are you sure you want to cancel?',
+ },
+ },
+ },
+ scanProcess: {
+ countingFiles: 'Counting files...',
+ scanning: 'Scanning...',
+ scannedFiles: 'Scanned files',
+ detectedFiles: 'Detected files',
+ errorWhileScanning: 'An error occurred while scanning the items. Please try again.',
+ noFilesFound: {
+ title: 'No threats were found',
+ subtitle: 'No further actions are necessary',
+ },
+ malwareFound: {
+ title: 'Malware detected',
+ subtitle: 'Please review and remove threats.',
+ action: 'Remove malware',
+ },
+ scanAgain: 'Scan again',
+ },
+ filesContainingMalwareModal: {
+ title: 'Files containing malware',
+ selectedItems: 'Selected {{selectedFiles}} out of {{totalFiles}}',
+ selectAll: 'Select all',
+ actions: {
+ cancel: 'Cancel',
+ remove: 'Remove',
+ },
+ },
+ },
+ cleaner: {
+ selectAllCheckbox: 'Select all',
+ mainView: {
+ cleanup: 'Clean Up',
+ },
+ generateReportView: {
+ title: 'No scan yet',
+ description: 'Scan your system to find files you can safely remove and free up space.',
+ generateReport: 'Run scan',
+ },
+ loadingView: {
+ title: 'Please wait a moment.',
+ description: 'We are generating your report...',
+ },
+ sizeIndicatorView: {
+ selectCategory: 'Select a category to',
+ previewContent: 'preview content',
+ saveUpTo: 'Save up to',
+ ofYourSpace: 'of your space',
+ },
+ cleanupConfirmDialogView: {
+ title: 'Confirm cleanup',
+ description:
+ 'This action will permanently delete the selected files from your device. This cannot be undone. Please, confirm to continue.',
+ cancelButton: 'Cancel',
+ confirmButton: 'Delete files',
+ },
+ cleaningView: {
+ cleaningProcess: {
+ title: 'Cleaning...',
+ stopCleanButton: 'Stop clean',
+ deletedFiles: 'Deleted files',
+ freeSpaceGained: 'Free space gained',
+ },
+ cleaningFinished: {
+ title: 'Your device is clean',
+ subtitle: 'No further actions are necessary',
+ finish: 'Finish',
+ },
+ },
+ },
+ },
+ issues: {
+ title: 'Issues',
+ tabs: {
+ sync: 'Sync',
+ backups: 'Backups',
+ general: 'General',
+ },
+ 'no-issues': 'No issues found',
+ actions: {
+ 'find-folder': 'Find folder',
+ },
+ errors: {
+ ABORTED: 'Aborted',
+ CANNOT_REGISTER_VIRTUAL_DRIVE: 'Cannot register virtual drive',
+ CREATE_FOLDER_FAILED: 'Failed to create folder',
+ DELETE_ERROR: 'Cannot delete item',
+ FILE_MODIFIED: 'File modified while uploading',
+ FILE_SIZE_TOO_BIG: 'File size too big (max 40GB)',
+ FOLDER_ACCESS_DENIED: 'The app does not have permission to access this folder',
+ FOLDER_DOES_NOT_EXIST: 'Folder does not exist',
+ INVALID_WINDOWS_NAME: String.raw`Windows does not allow names that include the characters \ / : * ? " < > |`,
+ NETWORK_CONNECTIVITY_ERROR: 'Network connectivity error.',
+ NOT_ENOUGH_SPACE: 'You have not enough space to complete the operation',
+ PARENT_FOLDER_DOES_NOT_EXIST: 'Parent folder does not exist',
+ ROOT_FOLDER_DOES_NOT_EXIST: 'Root folder does not exist',
+ SERVER_INTERNAL_ERROR: 'Server internal error.',
+ UNKNOWN_DEVICE_NAME: "Could not retrieve your device's name",
+ WEBSOCKET_CONNECTION_ERROR: 'WebSocket connection error',
+ },
+ },
+ common: {
+ cancel: 'Cancel',
+ },
+};
diff --git a/packages/core/src/frontend/core/i18n/locales/es.ts b/packages/core/src/frontend/core/i18n/locales/es.ts
new file mode 100644
index 0000000000..5e16f7f363
--- /dev/null
+++ b/packages/core/src/frontend/core/i18n/locales/es.ts
@@ -0,0 +1,410 @@
+import { Translation } from '../i18n.types';
+
+export const es: Translation = {
+ login: {
+ signInBrowser: 'Iniciar sesiΓ³n en el navegador',
+ createAccount: 'Crear cuenta',
+ welcome: 'Bienvenido a Internxt',
+ noAccount: 'ΒΏNo tienes cuenta?',
+ },
+ onboarding: {
+ slides: {
+ welcome: {
+ title: 'Internxt Desktop',
+ description:
+ 'Β‘Bienvenido a Internxt!\n\nHaz copias de seguridad de tus archivos con Drive, protΓ©gelos contra malware con Antivirus y optimiza el rendimiento con Cleaner β todo mientras mantienes tu privacidad protegida.',
+ 'take-tour': 'Hacer recorrido',
+ },
+ drive: {
+ title: 'Drive',
+ description:
+ 'Accede a todos tus archivos desde la carpeta de Internxt Drive en la barra lateral de {{platform_app}}.\n\nElige ahorrar espacio con archivos disponibles solo en lΓnea o mantener los esenciales sin conexiΓ³n β todo permanece seguro y sincronizado en todos tus dispositivos.',
+ },
+ antivirus: {
+ title: 'Antivirus',
+ description:
+ 'Protege tu dispositivo contra el malware y las amenazas en lΓnea.\n\nInternxt Antivirus te mantiene seguro con anΓ‘lisis en tiempo real y una seguridad que prioriza tu privacidad.',
+ },
+ backups: {
+ title: 'Backup',
+ description:
+ 'Con la funciΓ³n de copia de seguridad mejorada de Internxt, ahora puedes hacer copias seguras de tus carpetas en la nube para liberar espacio localmente.\n\nTambiΓ©n puedes ajustar la frecuencia de las copias de seguridad segΓΊn tus necesidades.',
+ },
+ cleaner: {
+ title: 'Cleaner',
+ description:
+ 'Libera espacio localmente y optimiza el rendimiento de tu dispositivo.\n\nNuestro limpiador detecta y elimina archivos innecesarios para que tu dispositivo funcione sin problemas.',
+ },
+ 'onboarding-completed': {
+ title: 'Ya estΓ‘ todo listo, Β‘disfruta de tu privacidad!',
+ 'desktop-ready': {
+ title: 'Internxt estΓ‘ listo',
+ description: 'Accede a tus archivos almacenados desde la barra lateral de tu {{platform_phrase}}.',
+ },
+ },
+ },
+ common: {
+ 'onboarding-progress': '{{current_slide}} de {{total_slides}}',
+ continue: 'Continuar',
+ 'open-drive': 'Abrir Internxt',
+ skip: 'Saltar',
+ 'platform-phrase': {
+ windows: 'explorador de archivos',
+ },
+ new: 'Nuevo',
+ },
+ },
+ widget: {
+ header: {
+ usage: {
+ of: 'de',
+ upgrade: 'Comprar espacio',
+ },
+ dropdown: {
+ new: 'Nuevo',
+ preferences: 'Preferencias',
+ sync: 'Sincronizar',
+ issues: 'Lista de errores',
+ support: 'Ayuda',
+ antivirus: 'Antivirus',
+ logout: 'Cerrar sesiΓ³n',
+ quit: 'Salir',
+ 'logout-confirmation': {
+ title: 'ΒΏCerrar sesiΓ³n en este dispositivo?',
+ message: 'Internxt no se mostrarΓ‘ sin una cuenta iniciada.',
+ confirm: 'Cerrar sesiΓ³n',
+ cancel: 'Cancelar',
+ },
+ cleaner: 'Cleaner',
+ },
+ },
+ body: {
+ activity: {
+ operation: {
+ DELETE_ERROR: 'Error de eliminaciΓ³n',
+ DELETED: 'Movido a la papelera',
+ DOWNLOAD_CANCEL: 'Descarga cancelada',
+ DOWNLOAD_ERROR: 'Error de descarga',
+ DOWNLOADED: 'Descargado',
+ DOWNLOADING: 'Descargando',
+ MODIFIED: 'Modificado',
+ MODIFY_ERROR: 'Error de modificaciΓ³n',
+ MOVE_ERROR: 'Error de movimiento',
+ MOVED: 'Movido',
+ RENAME_ERROR: 'Error de renombrado',
+ RENAMED: 'Renombrado',
+ UPLOAD_ERROR: 'Error de subida',
+ UPLOADED: 'Subido',
+ UPLOADING: 'Subiendo',
+ },
+ },
+ upToDate: {
+ title: 'Tus archivos estΓ‘n actualizados',
+ subtitle: 'La actividad de sincronizaciΓ³n se mostrarΓ‘ aquΓ',
+ },
+ },
+ footer: {
+ 'action-description': {
+ syncing: 'Sincronizando tus archivos',
+ updated: 'Sincronizado',
+ failed: 'SincronizaciΓ³n fallida',
+ 'sync-pending': 'SincronizaciΓ³n pendiente',
+ },
+ errors: {
+ lock: 'SincronizaciΓ³n bloqueada por otro dispositivo',
+ offline: 'No hay conexiΓ³n a internet',
+ },
+ },
+ 'sync-error': {
+ title: 'No se puede acceder al contenido',
+ message: 'Parece que hay problemas para obtener el contenido de la nube, por favor intΓ©ntalo de nuevo',
+ button: 'Reintentar',
+ },
+ banners: {
+ 'discover-backups': {
+ title: ' COPIAS DE SEGURIDAD DE INTERNXT',
+ body: 'MantΓ©n una copia de seguridad de tus carpetas y archivos mΓ‘s importantes.',
+ action: 'Hacer copia',
+ },
+ },
+ },
+ settings: {
+ header: {
+ section: {
+ GENERAL: 'General',
+ ACCOUNT: 'Cuenta',
+ BACKUPS: 'Backups',
+ ANTIVIRUS: 'Antivirus',
+ CLEANER: 'Cleaner',
+ },
+ },
+ general: {
+ language: {
+ label: 'Idioma',
+ options: {
+ es: 'EspaΓ±ol (EspaΓ±ol)',
+ en: 'English (InglΓ©s)',
+ fr: 'FranΓ§ais (FrancΓ©s)',
+ },
+ },
+ theme: {
+ label: 'Apariencia',
+ options: {
+ system: 'Sistema',
+ light: 'Claro',
+ dark: 'Oscuro',
+ },
+ },
+ device: {
+ section: 'Nombre del dispositivo',
+ action: {
+ edit: 'Editar',
+ cancel: 'Cancelar',
+ save: 'Guardar',
+ },
+ },
+ 'auto-startup': 'Iniciar Internxt al arrancar el sistema',
+ sync: {
+ folder: 'UbicaciΓ³n Drive virtual',
+ changeLocation: 'Cambiar ubicaciΓ³n',
+ },
+ 'app-info': {
+ 'open-logs': 'Abrir registros',
+ more: 'MΓ‘s informaciΓ³n sobre Internxt',
+ },
+ },
+ account: {
+ logout: 'Cerrar sesiΓ³n',
+ usage: {
+ display: 'Usado {{used}} de {{total}}',
+ upgrade: 'Comprar espacio',
+ change: 'Cambiar',
+ plan: 'Plan actual',
+ free: 'Gratis',
+ loadError: {
+ title: 'No se han podido obtener tus datos de uso',
+ action: 'Reintentar',
+ },
+ current: {
+ used: 'usado',
+ of: 'de',
+ 'in-use': 'usado',
+ },
+ full: {
+ title: 'Tu almacenamiento estΓ‘ lleno',
+ subtitle:
+ 'No puedes subir, sincronizar ni hacer copias de seguridad de archivos. AmplΓa ahora tu plan o elimina archivos para ahorrar espacio.',
+ },
+ },
+ },
+ backups: {
+ title: 'Carpetas de copia de seguridad',
+ 'add-folders': 'Haz clic en + para hacer una copia de seguridad de tus carpetas',
+ 'selected-folder_one': '{{count}} carpeta',
+ 'selected-folder_other': '{{count}} carpetas',
+ activate: 'Hacer copia de seguridad de tus carpetas',
+ 'view-backups': 'Explorar backups',
+ 'selected-folders-title': 'Carpetas seleccionadas',
+ 'select-folders': 'Cambiar carpetas',
+ 'last-backup-had-issues': 'La ΓΊltima copia de seguridad tuvo algunos problemas',
+ 'see-issues': 'Ver problemas',
+ 'backing-up': 'Haciendo la copia',
+ enable: {
+ message: 'Guarda una copia de tus archivos mΓ‘s importantes en la nube automΓ‘ticamente',
+ action: 'Haz una copia de seguridad ahora',
+ },
+ action: {
+ start: 'Hacer copia',
+ stop: 'Stop backup',
+ 'last-run': 'Γltima ejecuciΓ³n',
+ download: 'Descargar',
+ stopDownload: 'Detener descarga',
+ downloading: 'Descargando copia de seguridad',
+ },
+ frequency: {
+ title: 'Frecuencia de subida',
+ options: {
+ '1h': 'Cada hora',
+ '6h': 'Cada 6 horas',
+ '12h': 'Cada 12 horas',
+ '24h': 'Cada dΓa',
+ manually: 'Manual',
+ },
+ warning:
+ "Las carpetas no se respaldarΓ‘n automΓ‘ticamente hasta que haga clic en 'Copia de seguridad ahora'. Este modo no se recomienda.",
+ },
+ folders: {
+ 'no-folders': 'No hay carpetas seleccionadas',
+ 'no-folders-to-download': 'No hay carpetas para descargar',
+ save: 'Guardar',
+ cancel: 'Cancelar',
+ error: 'No pudimos encontrar la carpeta seleccionada',
+ },
+ delete: {
+ title: 'Eliminar copia de seguridad',
+ explanation: 'Esta copia de seguridad se eliminarΓ‘ de la nube, todas las carpetas y archivos permanecerΓ‘n en este ordenador',
+ action: 'Eliminar copia de seguridad',
+ 'deletion-modal': {
+ title: 'Borrar copia de seguridad?',
+ explanation:
+ 'Esta copia de seguridad se eliminarΓ‘ permanentemente de la nube, todas las carpetas y archivos permanecerΓ‘n en este ordenador.',
+ 'explanation-2': 'Esta acciΓ³n no se puede deshacer.',
+ confirm: 'SΓ, eliminar',
+ cancel: 'Cancelar',
+ },
+ },
+ stop: {
+ modal: {
+ title: 'Detener copia de seguridad en curso',
+ explanation: 'TodavΓa hay archivos que no se han subido. ΒΏDetener la copia de seguridad de todos modos?',
+ 'explanation-2': '',
+ confirm: 'Detener',
+ cancel: 'Cancelar',
+ },
+ },
+ },
+ antivirus: {
+ featureLocked: {
+ title: 'FunciΓ³n bloqueada',
+ subtitle: 'Por favor, actualiza tu plan para usar esta funciΓ³n.',
+ action: 'Actualizar',
+ },
+ errorState: {
+ title: 'Algo saliΓ³ mal al escanear el directorio',
+ button: 'Intentar de nuevo',
+ },
+ scanOptions: {
+ stopScan: 'Detener escaneo',
+ systemScan: {
+ text: 'Escaneo del sistema antivirus',
+ action: 'Iniciar escaneo',
+ },
+ customScan: {
+ text: 'Escaneo personalizado del antivirus',
+ action: 'Elegir',
+ selector: {
+ files: 'Archivos',
+ folders: 'Carpetas',
+ },
+ },
+ removeMalware: {
+ actions: {
+ cancel: 'Cancelar',
+ remove: 'Eliminar',
+ },
+ actionRequired: {
+ title: 'AcciΓ³n requerida',
+ description:
+ 'Al eliminar el malware, tambiΓ©n se eliminarΓ‘ permanentemente la carpeta de tu almacenamiento para proteger tu dispositivo. Esta acciΓ³n no se puede deshacer.',
+ confirmToContinue: 'Confirma para continuar.',
+ },
+ securityWarning: {
+ title: 'Advertencia de seguridad',
+ description: 'El malware sigue presente y tu dispositivo estΓ‘ en riesgo.',
+ confirmToCancel: 'ΒΏEstΓ‘s seguro de que deseas cancelar?',
+ },
+ },
+ },
+ scanProcess: {
+ countingFiles: 'Contando archivos...',
+ scanning: 'Escaneando...',
+ scannedFiles: 'Archivos escaneados',
+ detectedFiles: 'Archivos detectados',
+ errorWhileScanning: 'OcurriΓ³ un error al escanear los elementos. Por favor, intenta nuevamente.',
+ noFilesFound: {
+ title: 'No se encontraron amenazas',
+ subtitle: 'No es necesario realizar mΓ‘s acciones',
+ },
+ malwareFound: {
+ title: 'Malware detectado',
+ subtitle: 'Por favor, revisa y elimina las amenazas.',
+ action: 'Eliminar malware',
+ },
+ scanAgain: 'Escanear nuevamente',
+ },
+ filesContainingMalwareModal: {
+ title: 'Archivos que contienen malware',
+ selectedItems: 'Seleccionados {{selectedFiles}} de {{totalFiles}}',
+ selectAll: 'Seleccionar todo',
+ actions: {
+ cancel: 'Cancelar',
+ remove: 'Eliminar',
+ },
+ },
+ },
+ cleaner: {
+ selectAllCheckbox: 'Seleccionar todo',
+ mainView: {
+ cleanup: 'Limpiar',
+ },
+ generateReportView: {
+ title: 'NingΓΊn escaneo todavΓa',
+ description: 'Escanee su sistema para encontrar archivos que pueda eliminar de forma segura y liberar espacio.',
+ generateReport: 'Ejecutar escaneo',
+ },
+ loadingView: {
+ title: 'Espera un momento',
+ description: 'Estamos generando tu informe...',
+ },
+ sizeIndicatorView: {
+ selectCategory: 'Seleccione una categorΓa para',
+ previewContent: 'obtener una vista previa del contenido',
+ saveUpTo: 'Ahorra hasta',
+ ofYourSpace: 'de tu espacio',
+ },
+ cleanupConfirmDialogView: {
+ title: 'Confirmar borrado',
+ description:
+ 'Esta acciΓ³n eliminarΓ‘ permanentemente los archivos seleccionados de tu dispositivo. Esta acciΓ³n no se puede deshacer. Confirme para continuar.',
+ cancelButton: 'Cancelar',
+ confirmButton: 'Eliminar archivos',
+ },
+ cleaningView: {
+ cleaningProcess: {
+ title: 'Limpiando...',
+ stopCleanButton: 'Detener limpieza',
+ deletedFiles: 'Archivos eliminados',
+ freeSpaceGained: 'Espacio libre ganado',
+ },
+ cleaningFinished: {
+ title: 'Tu dispositivo estΓ‘ limpio',
+ subtitle: 'No es necesario realizar ninguna otra acciΓ³n',
+ finish: 'Finalizar',
+ },
+ },
+ },
+ },
+ issues: {
+ title: 'Lista de errores',
+ tabs: {
+ sync: 'SincronizaciΓ³n',
+ backups: 'Copias de seguridad',
+ general: 'General',
+ },
+ 'no-issues': 'No se han encontrado errores',
+ actions: {
+ 'find-folder': 'Buscar la carpeta',
+ },
+ errors: {
+ ABORTED: 'Abortado',
+ CANNOT_REGISTER_VIRTUAL_DRIVE: 'No se puede registrar el drive virtual',
+ CREATE_FOLDER_FAILED: 'Error al crear la carpeta',
+ DELETE_ERROR: 'No se pudo eliminar el elemento',
+ FILE_MODIFIED: 'Archivo modificado durante la subida',
+ FILE_SIZE_TOO_BIG: 'Archivo es demasiado grande (mΓ‘ximo 40GB)',
+ FOLDER_ACCESS_DENIED: 'La app no tiene permiso para acceder a esta carpeta',
+ FOLDER_DOES_NOT_EXIST: 'Carpeta no existe',
+ INVALID_WINDOWS_NAME: String.raw`Windows no permite nombres que incluyen los caracteres \ / : * ? " < > |`,
+ NETWORK_CONNECTIVITY_ERROR: 'Error de conectividad de red',
+ NOT_ENOUGH_SPACE: 'No tienes suficiente espacio para completar la operaciΓ³n',
+ PARENT_FOLDER_DOES_NOT_EXIST: 'Carpeta padre no existe',
+ ROOT_FOLDER_DOES_NOT_EXIST: 'Carpeta raΓz no existe',
+ SERVER_INTERNAL_ERROR: 'Error interno del servidor',
+ UNKNOWN_DEVICE_NAME: 'No se pudo obtener el nombre de tu dispositivo',
+ WEBSOCKET_CONNECTION_ERROR: 'Error de conexiΓ³n WebSocket',
+ },
+ },
+ common: {
+ cancel: 'Cancelar',
+ },
+};
diff --git a/packages/core/src/frontend/core/i18n/locales/fr.ts b/packages/core/src/frontend/core/i18n/locales/fr.ts
new file mode 100644
index 0000000000..715b4b8073
--- /dev/null
+++ b/packages/core/src/frontend/core/i18n/locales/fr.ts
@@ -0,0 +1,411 @@
+import { Translation } from '../i18n.types';
+
+export const fr: Translation = {
+ login: {
+ signInBrowser: 'Se connecter avec le navigateur',
+ createAccount: 'CrΓ©er un compte',
+ welcome: 'Bienvenue chez Internxt',
+ noAccount: "Vous n'avez pas de compte ?",
+ },
+ onboarding: {
+ slides: {
+ welcome: {
+ title: 'Internxt Desktop',
+ description:
+ 'Bienvenue dans Internxt!\n\nSauvegardez vos fichiers avec Drive, protΓ©gez-les contre les logiciels malveillants avec Antivirus et optimisez les performances avec Cleaner β tout en prΓ©servant votre vie privΓ©e.',
+ 'take-tour': 'Visite guidΓ©e',
+ },
+ drive: {
+ title: 'Drive',
+ description:
+ 'AccΓ©dez Γ tous vos fichiers depuis le dossier Internxt Drive dans la barre latΓ©rale de {{platform_app}}.\n\nChoisissez dβΓ©conomiser de lβespace avec des fichiers disponibles uniquement en ligne, ou gardez lβessentiel accessible hors ligne β tout reste sΓ©curisΓ© et synchronisΓ© sur tous vos appareils.',
+ },
+ antivirus: {
+ title: 'Antivirus',
+ description:
+ 'Protégez votre appareil contre les logiciels malveillants et les menaces en ligne.\n\nInternxt Antivirus vous protège grÒce à des analyses en temps réel et une sécurité axée sur la confidentialité.',
+ },
+ backups: {
+ title: 'Backup',
+ description:
+ 'Avec la fonction de sauvegarde amΓ©liorΓ©e dβInternxt, vous pouvez dΓ©sormais sauvegarder vos dossiers en toute sΓ©curitΓ© sur le cloud afin de libΓ©rer de lβespace localement.\n\nVous pouvez Γ©galement ajuster la frΓ©quence des sauvegardes selon vos besoins.',
+ },
+ cleaner: {
+ title: 'Cleaner',
+ description:
+ 'LibΓ©rez de lβespace localement et optimisez les performances de votre appareil.\n\nNotre nettoyeur dΓ©tecte et supprime les fichiers inutiles afin que votre appareil fonctionne sans encombre.',
+ },
+ 'onboarding-completed': {
+ title: 'Vous Γͺtes prΓͺt, profitez de votre vie privΓ©e !',
+ 'desktop-ready': {
+ title: 'Internxt est prΓͺt',
+ description: 'AccΓ©dez Γ vos fichiers stockΓ©s depuis la barre latΓ©rale de votre {{platform_phrase}}.',
+ },
+ },
+ },
+ common: {
+ 'onboarding-progress': '{{current_slide}} de {{total_slides}}',
+ 'open-drive': 'Ouvrir Internxt',
+ continue: 'Continuer',
+ skip: 'Sauter',
+ 'platform-phrase': {
+ windows: 'navigateur de fichiers',
+ },
+ new: 'Nouveau',
+ },
+ },
+ widget: {
+ header: {
+ usage: {
+ of: 'de',
+ upgrade: 'Acheter maintenant',
+ },
+ dropdown: {
+ new: 'Nouveau',
+ preferences: 'PrΓ©fΓ©rences',
+ sync: 'Synchroniser',
+ issues: "Liste d'erreurs",
+ support: 'Aide',
+ antivirus: 'Antivirus',
+ logout: 'DΓ©connecter',
+ quit: 'Fermer',
+ 'logout-confirmation': {
+ title: 'Se dΓ©connecter de cet appareil?',
+ message: "Internxt ne s'affichera pas sans un compte connectΓ©.",
+ confirm: 'Se dΓ©connecter',
+ cancel: 'Annuler',
+ },
+ cleaner: 'Cleaner',
+ },
+ },
+ body: {
+ activity: {
+ operation: {
+ DELETE_ERROR: 'Erreur de suppression',
+ DELETED: 'DΓ©placΓ© vers la corbeille',
+ DOWNLOAD_CANCEL: 'TΓ©lΓ©chargement annulΓ©',
+ DOWNLOAD_ERROR: 'Erreur de tΓ©lΓ©chargement',
+ DOWNLOADED: 'TΓ©lΓ©chargΓ©',
+ DOWNLOADING: 'TΓ©lΓ©chargement en cours',
+ MODIFIED: 'ModifiΓ©',
+ MODIFY_ERROR: 'Erreur de modification',
+ MOVE_ERROR: 'Erreur de dΓ©placement',
+ MOVED: 'DΓ©placΓ©',
+ RENAME_ERROR: 'Erreur de renommage',
+ RENAMED: 'RenommΓ©',
+ UPLOAD_ERROR: 'Erreur de tΓ©lΓ©versement',
+ UPLOADED: 'TΓ©lΓ©versΓ©',
+ UPLOADING: 'TΓ©lΓ©versement en cours',
+ },
+ },
+ upToDate: {
+ title: 'Vos fichiers sont Γ jour',
+ subtitle: "L'activitΓ© de synchronisation s'affichera ici",
+ },
+ },
+ footer: {
+ 'action-description': {
+ syncing: 'Synchronisation de vos fichiers',
+ updated: 'Synchronisation complète',
+ failed: 'Γchec de la synchronisation',
+ 'sync-pending': 'Synchronisation en attente',
+ },
+ errors: {
+ lock: 'Synchronisation bloquΓ©e par un autre dispositif',
+ offline: 'Non connectΓ© Γ Internet',
+ },
+ },
+ 'sync-error': {
+ title: "Impossible d'obtenir le contenu Γ distance",
+ message: 'Nous avons des problèmes pour récupérer votre contenu depuis le nuage, veuillez réessayer ',
+ button: 'Essayez Γ nouveau ',
+ },
+ banners: {
+ 'discover-backups': {
+ title: 'INTERNXT SAUVEGARDES',
+ body: 'Gardez une copie de secours de vos dossiers et fichiers les plus importants.',
+ action: 'Sauvegarder',
+ },
+ },
+ },
+ settings: {
+ header: {
+ section: {
+ GENERAL: 'GΓ©nΓ©ral',
+ ACCOUNT: 'Compte',
+ BACKUPS: 'Sauvegardes',
+ ANTIVIRUS: 'Antivirus',
+ CLEANER: 'Cleaner',
+ },
+ },
+ general: {
+ language: {
+ label: 'Langue',
+ options: {
+ es: 'EspaΓ±ol (Espagnole)',
+ en: 'English (Anglaise)',
+ fr: 'FranΓ§ais (FranΓ§ais)',
+ },
+ },
+ theme: {
+ label: 'Apparence',
+ options: {
+ system: 'Système',
+ light: 'Lumière',
+ dark: 'Sombre',
+ },
+ },
+ device: {
+ section: 'Nom du dispositif',
+ action: {
+ edit: 'Editer',
+ cancel: 'Annuler',
+ save: 'Enregistrer',
+ },
+ },
+ 'auto-startup': 'Démarrer Internxt au démarrage du système',
+ sync: {
+ folder: 'Emplacement Drive virtuel',
+ changeLocation: "Changer d'emplacement",
+ },
+ 'app-info': {
+ 'open-logs': 'Ouvrir les registres',
+ more: "Plus d'informations sur Internxt",
+ },
+ },
+ account: {
+ logout: 'DΓ©connecter',
+ usage: {
+ display: 'UtilisΓ© {{used}} sur {{total}}',
+ upgrade: 'Acheter',
+ change: 'Changement',
+ plan: 'Plan actuel',
+ free: 'Gratuit',
+ loadError: {
+ title: "Impossible d'obtenir les dΓ©tails de votre utilisation",
+ action: 'RΓ©essayer',
+ },
+ current: {
+ used: 'utilisΓ©s',
+ of: 'de',
+ 'in-use': 'utilisΓ©',
+ },
+ full: {
+ title: 'Votre espace de stockage est plein',
+ subtitle:
+ "Vous ne pouvez pas tΓ©lΓ©charger, synchroniser ou sauvegarder des fichiers. Mettez votre forfait Γ niveau ou supprimez des fichiers pour Γ©conomiser de l'espace.",
+ },
+ },
+ },
+ backups: {
+ title: 'Dossiers de sauvegarde',
+ 'add-folders': 'Cliquez sur + pour sΓ©lectionner les dossiers que vous souhaitez sauvegarder',
+ 'selected-folder_one': '{{count}} dossier',
+ 'selected-folder_other': '{{count}} dossiers',
+ activate: 'Sauvegarder vos dossiers',
+ 'view-backups': 'Parcourir les backups',
+ 'selected-folders-title': 'Dossiers sΓ©lectionnΓ©s',
+ 'select-folders': 'Changer les dossiers',
+ 'last-backup-had-issues': 'La dernière sauvegarde a rencontré quelques problèmes',
+ 'see-issues': 'Voir des problèmes',
+ 'backing-up': 'Sauvegarde...',
+ enable: {
+ message: 'Enregistrez automatiquement une copie de vos fichiers les plus importants dans le cloud',
+ action: 'Faites une sauvegarde maintenant',
+ },
+ action: {
+ start: 'Faire une copie ',
+ stop: 'ArrΓͺter la sauvegarde',
+ 'last-run': 'Dernière exécution',
+ download: 'TΓ©lΓ©charger',
+ stopDownload: 'ArrΓͺter le tΓ©lΓ©chargement',
+ downloading: 'TΓ©lΓ©chargement de la sauvegarde',
+ },
+ frequency: {
+ title: 'FrΓ©quence de tΓ©lΓ©chargement',
+ options: {
+ '1h': 'Toutes les heures',
+ '6h': 'Toutes les 6 heures',
+ '12h': 'Toutes les 12 heures',
+ '24h': 'Tous les jours',
+ manually: 'Manuellement',
+ },
+ warning:
+ 'Les dossiers ne seront pas automatiquement sauvegardΓ©s tant que vous nβaurez pas cliquΓ© sur « Sauvegarder maintenantΒ Β». Ce mode nβest pas recommandΓ©.',
+ },
+ folders: {
+ 'no-folders': 'Pas encore de sauvegardes',
+ 'no-folders-to-download': 'Pas de dossiers Γ tΓ©lΓ©charger',
+ save: 'Sauvegarder',
+ cancel: 'Annuler',
+ error: 'Nous nβavons pas trouvΓ© le dossier sΓ©lectionnΓ©',
+ },
+ delete: {
+ title: 'Supprimer la sauvegarde',
+ explanation: 'Cette sauvegarde sera supprimΓ©e du cloud, tous les dossiers et fichiers resteront sur cet ordinateur',
+ action: 'Supprimer la sauvegarde',
+ 'deletion-modal': {
+ title: 'ArrΓͺter la sauvegarde de ',
+ explanation:
+ 'Cette sauvegarde sera supprimΓ©e dΓ©finitivement du cloud, tous les dossiers et fichiers resteront sur cet ordinateur.',
+ 'explanation-2': 'Cette action est irrΓ©versible.',
+ confirm: 'Oui, supprimer',
+ cancel: 'Annuler',
+ },
+ },
+ stop: {
+ modal: {
+ title: 'ArrΓͺter la sauvegarde en cours',
+ explanation: "Il reste encore des fichiers qui n'ont pas Γ©tΓ© tΓ©lΓ©chargΓ©s. ArrΓͺter la sauvegarde quand mΓͺme?",
+ 'explanation-2': '',
+ confirm: 'ArrΓͺter la sauvegarde',
+ cancel: 'Annuler',
+ },
+ },
+ },
+ antivirus: {
+ featureLocked: {
+ title: 'Fonction verrouillΓ©e',
+ subtitle: 'Veuillez mettre Γ niveau votre plan pour utiliser cette fonctionnalitΓ©.',
+ action: 'Mettre Γ niveau',
+ },
+ errorState: {
+ title: "Une erreur s'est produite lors de l'analyse du rΓ©pertoire",
+ button: 'RΓ©essayer',
+ },
+ scanOptions: {
+ stopScan: "ArrΓͺter l'analyse",
+ systemScan: {
+ text: 'Analyse du système antivirus',
+ action: "DΓ©marrer l'analyse",
+ },
+ customScan: {
+ text: "Analyse personnalisΓ©e de l'antivirus",
+ action: 'Choisir',
+ selector: {
+ files: 'Fichiers',
+ folders: 'Dossiers',
+ },
+ },
+ removeMalware: {
+ actions: {
+ cancel: 'Annuler',
+ remove: 'Supprimer',
+ },
+ actionRequired: {
+ title: 'Action requise',
+ description:
+ 'La suppression du logiciel malveillant supprimera Γ©galement dΓ©finitivement le dossier de votre espace de stockage afin de protΓ©ger votre appareil. Cette action est irrΓ©versible.',
+ confirmToContinue: 'Veuillez confirmer pour continuer.',
+ },
+ securityWarning: {
+ title: 'Avertissement de sΓ©curitΓ©',
+ description: 'Le logiciel malveillant est toujours prΓ©sent et votre appareil est menacΓ©.',
+ confirmToCancel: 'Γtes-vous sΓ»r de vouloir annuler?',
+ },
+ },
+ },
+ scanProcess: {
+ countingFiles: 'Comptage des fichiers...',
+ scanning: 'Analyse en cours...',
+ scannedFiles: 'Fichiers analysΓ©s',
+ detectedFiles: 'Fichiers dΓ©tectΓ©s',
+ errorWhileScanning: "Une erreur s'est produite lors de l'analyse des Γ©lΓ©ments. Veuillez rΓ©essayer.",
+ noFilesFound: {
+ title: 'Aucune menace dΓ©tectΓ©e',
+ subtitle: 'Aucune action supplΓ©mentaire requise',
+ },
+ malwareFound: {
+ title: 'Malware dΓ©tectΓ©',
+ subtitle: 'Veuillez examiner et supprimer les menaces.',
+ action: 'Supprimer le malware',
+ },
+ scanAgain: 'Analyser Γ nouveau',
+ },
+ filesContainingMalwareModal: {
+ title: 'Fichiers contenant des malwares',
+ selectedItems: 'SΓ©lectionnΓ© {{selectedFiles}} sur {{totalFiles}}',
+ selectAll: 'Tout sΓ©lectionner',
+ actions: {
+ cancel: 'Annuler',
+ remove: 'Supprimer',
+ },
+ },
+ },
+ cleaner: {
+ selectAllCheckbox: 'Tout sΓ©lectionner',
+ mainView: {
+ cleanup: 'Nettoyer',
+ },
+ generateReportView: {
+ title: 'Pas encore de scans',
+ description:
+ "Analysez votre système pour trouver les fichiers que vous pouvez supprimer en toute sécurité pour libérer de l'espace.",
+ generateReport: "ExΓ©cuter l'analyse",
+ },
+ loadingView: {
+ title: 'Attendez un instant.',
+ description: 'Nous gΓ©nΓ©rons votre rapport...',
+ },
+ sizeIndicatorView: {
+ selectCategory: 'SΓ©lectionnez une catΓ©gorie pour',
+ previewContent: 'aperΓ§u du contenu',
+ saveUpTo: "Γconomisez jusqu'Γ ",
+ ofYourSpace: 'de votre espace',
+ },
+ cleanupConfirmDialogView: {
+ title: 'Confirmer le nettoyage',
+ description:
+ 'Cette action supprimera dΓ©finitivement les fichiers sΓ©lectionnΓ©s de votre appareil. Cette action ne peut pas Γͺtre annulΓ©e. Veuillez confirmer pour continuer.',
+ cancelButton: 'Annuler',
+ confirmButton: 'Supprimer les fichiers ',
+ },
+ cleaningView: {
+ cleaningProcess: {
+ title: 'Nettoyage...',
+ stopCleanButton: 'ArrΓͺter le nettoyage',
+ deletedFiles: 'Fichiers supprimΓ©s',
+ freeSpaceGained: 'Espace libre gagnΓ©',
+ },
+ cleaningFinished: {
+ title: 'Votre appareil est propre',
+ subtitle: "Aucune autre action n'est nΓ©cessaire",
+ finish: 'Terminer',
+ },
+ },
+ },
+ },
+ issues: {
+ title: "Liste d'erreurs",
+ tabs: {
+ sync: 'Synchronisation',
+ backups: 'Sauvegardes',
+ general: 'GΓ©nΓ©ral',
+ },
+ 'no-issues': 'Aucune erreur trouvΓ©e',
+ actions: {
+ 'find-folder': 'Trouver un dossier',
+ },
+ errors: {
+ ABORTED: 'AvortΓ©',
+ CANNOT_REGISTER_VIRTUAL_DRIVE: 'Le lecteur virtuel ne peut pas Γͺtre enregistrΓ©',
+ CREATE_FOLDER_FAILED: 'Erreur lors de la crΓ©ation de la dossier',
+ DELETE_ERROR: "Impossible de supprimer l'Γ©lΓ©ment",
+ FILE_MODIFIED: 'Fichier modifiΓ© lors du tΓ©lΓ©chargement',
+ FILE_SIZE_TOO_BIG: 'Le fichier est trop grand (max 40GB)',
+ FOLDER_ACCESS_DENIED: "L'app n'a pas le droit d'accΓ©der Γ cette dossier",
+ FOLDER_DOES_NOT_EXIST: 'Dossier non existant',
+ INVALID_WINDOWS_NAME: String.raw`Windows ne permet pas les noms contenant les caractères \ / : * ? " < > |`,
+ NETWORK_CONNECTIVITY_ERROR: 'Erreur de connectivitΓ© rΓ©seau',
+ NOT_ENOUGH_SPACE: "Vous n'avez pas assez d'espace pour complΓ©ter l'opΓ©ration",
+ PARENT_FOLDER_DOES_NOT_EXIST: 'Dossier parent non existant',
+ ROOT_FOLDER_DOES_NOT_EXIST: 'Dossier racine non existant',
+ SERVER_INTERNAL_ERROR: 'Erreur de serveur interne',
+ UNKNOWN_DEVICE_NAME: "Impossible d'obtenir le nom de votre appareil",
+ WEBSOCKET_CONNECTION_ERROR: 'Erreur de connexion WebSocket',
+ },
+ },
+ common: {
+ cancel: 'Annuler',
+ },
+};
diff --git a/packages/core/src/frontend/features/cleaner/cleaner-section.tsx b/packages/core/src/frontend/features/cleaner/cleaner-section.tsx
new file mode 100644
index 0000000000..8461e4a1e7
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/cleaner-section.tsx
@@ -0,0 +1,113 @@
+import { useState } from 'react';
+
+import { Button } from '@/frontend/components/button';
+import { SectionSpinner } from '@/frontend/components/section-spinner';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { CleanerContextType, SectionConfig } from './cleaner.types';
+import { CleanupConfirmDialog } from './components/cleanup-confirm-dialog';
+import { useCleanerViewModel } from './use-cleaner-view-model';
+import { CleanerView } from './views/cleaner-view';
+import { CleaningView } from './views/cleaning-view';
+import { GenerateReportView } from './views/generate-report-view';
+import { LoadingView } from './views/loading-view';
+import { LockedState } from './views/locked-view';
+
+type Props = {
+ active: boolean;
+ sectionConfig: SectionConfig;
+ useCleaner: () => CleanerContextType;
+ useTranslationContext: () => LocalContextProps;
+ openUrl: (url: string) => Promise;
+ isSectionLoading: boolean;
+ isAvailable: boolean;
+};
+
+export function CleanerSection({
+ active,
+ sectionConfig,
+ useCleaner,
+ useTranslationContext,
+ openUrl,
+ isSectionLoading,
+ isAvailable,
+}: Readonly) {
+ const { translate } = useTranslationContext();
+ const { cleaningState, sectionKeys, loading, report, diskSpace, generateReport, startCleanup } = useCleaner();
+ const useCleanerViewModelHook = useCleanerViewModel(sectionKeys);
+ const [showConfirmDialog, setShowConfirmDialog] = useState(false);
+
+ function handleCleanupClick() {
+ setShowConfirmDialog(true);
+ }
+
+ function confirmCleanup() {
+ if (report) {
+ void startCleanup(useCleanerViewModelHook.viewModel);
+ }
+ setShowConfirmDialog(false);
+ }
+
+ function cancelCleanup() {
+ setShowConfirmDialog(false);
+ }
+
+ function handleGenerateReport() {
+ void generateReport();
+ }
+
+ function renderContent() {
+ if (isSectionLoading) return ;
+
+ if (!isAvailable) {
+ return ;
+ }
+
+ if (cleaningState.cleaning || cleaningState.cleaningCompleted) {
+ return ;
+ }
+
+ return (
+
+ {!report && !loading && (
+
+ )}
+ {loading &&
}
+ {report && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/cleaner.module.ts b/packages/core/src/frontend/features/cleaner/cleaner.module.ts
new file mode 100644
index 0000000000..9291911a97
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/cleaner.module.ts
@@ -0,0 +1,7 @@
+import { CleanerSection } from './cleaner-section';
+import { formatFileSize } from './service/format-file-size';
+
+export const CleanerModule = {
+ formatFileSize,
+ CleanerSection,
+};
diff --git a/packages/core/src/frontend/features/cleaner/cleaner.types.ts b/packages/core/src/frontend/features/cleaner/cleaner.types.ts
new file mode 100644
index 0000000000..d1f3e35dec
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/cleaner.types.ts
@@ -0,0 +1,22 @@
+import { CleanerSectionKey, CleanerViewModel, CleanerReport } from '@/backend/features/cleaner/types/cleaner.types';
+
+export type SectionConfig = Record;
+
+export type CleanerContextType = {
+ report: CleanerReport | null;
+ loading: boolean;
+ cleaningState: {
+ cleaning: boolean;
+ cleaningCompleted: boolean;
+ currentCleaningPath: string;
+ progress: number;
+ deletedFiles: number;
+ spaceGained: string;
+ };
+ diskSpace: number;
+ sectionKeys: CleanerSectionKey[];
+ generateReport: (force?: boolean) => Promise;
+ startCleanup: (viewModel: CleanerViewModel) => Promise;
+ stopCleanup: () => void;
+ setInitialCleaningState: () => void;
+};
diff --git a/packages/core/src/frontend/features/cleaner/components/cleaned-files-container.tsx b/packages/core/src/frontend/features/cleaner/components/cleaned-files-container.tsx
new file mode 100644
index 0000000000..84b5361ae0
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/cleaned-files-container.tsx
@@ -0,0 +1,27 @@
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ deletedFiles: number;
+ freeSpaceGained: string;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function CleanedFilesContainer({ deletedFiles, freeSpaceGained, useTranslationContext }: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+
+
+
{deletedFiles}
+
{translate('settings.cleaner.cleaningView.cleaningProcess.deletedFiles')}
+
+
+
+
{freeSpaceGained}
+
{translate('settings.cleaner.cleaningView.cleaningProcess.freeSpaceGained')}
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/cleaning-finished.tsx b/packages/core/src/frontend/features/cleaner/components/cleaning-finished.tsx
new file mode 100644
index 0000000000..271dff0943
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/cleaning-finished.tsx
@@ -0,0 +1,33 @@
+import { Sparkle } from '@phosphor-icons/react';
+
+import { Button } from '@/frontend/components/button';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { CleanedFilesContainer } from './cleaned-files-container';
+
+type Props = {
+ deletedFiles: number;
+ freeSpaceGained: string;
+ onFinish: () => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function CleaningFinished({ deletedFiles, freeSpaceGained, onFinish, useTranslationContext }: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+
+
+
+
+
{translate('settings.cleaner.cleaningView.cleaningFinished.title')}
+
{translate('settings.cleaner.cleaningView.cleaningFinished.subtitle')}
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/cleaning-process.tsx b/packages/core/src/frontend/features/cleaner/components/cleaning-process.tsx
new file mode 100644
index 0000000000..c3aca0ad3d
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/cleaning-process.tsx
@@ -0,0 +1,39 @@
+import { Button } from '@/frontend/components/button';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { CleanedFilesContainer } from './cleaned-files-container';
+import { ProgresBar } from './progress-bar';
+
+type Props = {
+ currentCleaningPath: string;
+ cleanedProgress: number;
+ deletedFiles: number;
+ freeSpaceGained: string;
+ onStopCleaning: () => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function CleaningProcess({
+ currentCleaningPath,
+ cleanedProgress,
+ deletedFiles,
+ freeSpaceGained,
+ onStopCleaning,
+ useTranslationContext,
+}: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+
+
{translate('settings.cleaner.cleaningView.cleaningProcess.title')}
+
{currentCleaningPath}
+
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/cleanup-confirm-dialog.tsx b/packages/core/src/frontend/features/cleaner/components/cleanup-confirm-dialog.tsx
new file mode 100644
index 0000000000..7afae58dbb
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/cleanup-confirm-dialog.tsx
@@ -0,0 +1,54 @@
+import { X } from '@phosphor-icons/react';
+
+import { Button } from '@/frontend/components/button';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ isVisible: boolean;
+ onConfirm: () => void;
+ onCancel: () => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function CleanupConfirmDialog({ isVisible, onConfirm, onCancel, useTranslationContext }: Readonly) {
+ if (!isVisible) return null;
+
+ const { translate } = useTranslationContext();
+
+ return (
+
+ {/* Backdrop */}
+
+
+ {/* Dialog */}
+
+ {/* Header */}
+
+
+
+ {translate('settings.cleaner.cleanupConfirmDialogView.title')}
+
+
+
+
+
+ {/* Content */}
+
+
{translate('settings.cleaner.cleanupConfirmDialogView.description')}
+
+
+ {/* Actions */}
+
+
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/cleanup-size-indicator.tsx b/packages/core/src/frontend/features/cleaner/components/cleanup-size-indicator.tsx
new file mode 100644
index 0000000000..d3b8f120e9
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/cleanup-size-indicator.tsx
@@ -0,0 +1,94 @@
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { formatFileSize } from '../service/format-file-size';
+
+type Props = {
+ selectedSize: number;
+ totalSize: number;
+ segmentDetails: Array<{ color: string; percentage: number; size: number }>;
+ useTranslationContext: () => LocalContextProps;
+};
+export function CleanupSizeIndicator({ selectedSize, totalSize, segmentDetails, useTranslationContext }: Readonly) {
+ const { translate } = useTranslationContext();
+ return (
+
+
+
{translate('settings.cleaner.sizeIndicatorView.selectCategory')}
+
{translate('settings.cleaner.sizeIndicatorView.previewContent')}
+
+
+
+
+
+
+
{formatFileSize({ bytes: selectedSize })}
+
+
+
+
+ );
+}
+
+function SavedSpaceIndicator({
+ totalSize,
+ selectedSize,
+ useTranslationContext,
+}: Readonly<{
+ totalSize: number;
+ selectedSize: number;
+ useTranslationContext: () => LocalContextProps;
+}>) {
+ const { translate } = useTranslationContext();
+ const savedSpacePercentage = totalSize > 0 ? (selectedSize / totalSize) * 100 : 0;
+
+ return (
+
+ {translate('settings.cleaner.sizeIndicatorView.saveUpTo')} {savedSpacePercentage.toFixed(2)}%
+
+ {translate('settings.cleaner.sizeIndicatorView.ofYourSpace')}
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/progress-bar.tsx b/packages/core/src/frontend/features/cleaner/components/progress-bar.tsx
new file mode 100644
index 0000000000..5eaa7dca94
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/progress-bar.tsx
@@ -0,0 +1,17 @@
+type Props = { progress: number };
+
+export function ProgresBar({ progress }: Readonly) {
+ return (
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/section-detail-header.tsx b/packages/core/src/frontend/features/cleaner/components/section-detail-header.tsx
new file mode 100644
index 0000000000..3bbe7b4c02
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/section-detail-header.tsx
@@ -0,0 +1,47 @@
+import { CaretDoubleRight } from '@phosphor-icons/react';
+
+import { CleanerSectionKey } from '@/backend/features/cleaner/types/cleaner.types';
+import { Checkbox } from '@/frontend/components/checkbox';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { SectionConfig } from '../cleaner.types';
+
+type Props = {
+ sectionName: CleanerSectionKey;
+ isAllSelected: boolean;
+ isPartiallySelected: boolean;
+ sectionConfig: SectionConfig;
+ isEmpty?: boolean;
+ onClose: () => void;
+ onSelectAll: () => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function SectionDetailHeader({
+ sectionName,
+ isAllSelected,
+ isPartiallySelected,
+ sectionConfig,
+ isEmpty = false,
+ onClose,
+ onSelectAll,
+ useTranslationContext,
+}: Readonly) {
+ const { translate } = useTranslationContext();
+ return (
+
+
+
+
+
+
{sectionConfig[sectionName].name}
+
+
!isEmpty && onSelectAll()}
+ />
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/section-detail-menu-item.tsx b/packages/core/src/frontend/features/cleaner/components/section-detail-menu-item.tsx
new file mode 100644
index 0000000000..18b6a592f7
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/section-detail-menu-item.tsx
@@ -0,0 +1,36 @@
+import { CleanableItem, CleanerSectionKey } from '@/backend/features/cleaner/types/cleaner.types';
+import { Checkbox } from '@/frontend/components/checkbox';
+
+import { formatFileSize } from '../service/format-file-size';
+import { Separator } from './separator';
+
+type Props = {
+ item: CleanableItem;
+ sectionName: CleanerSectionKey;
+ showSeparatorOnTop: boolean;
+ isSelected: boolean;
+ onToggleItem: (sectionKey: CleanerSectionKey, itemPath: string) => void;
+};
+
+export function SectionDetailMenuItem({ item, sectionName, showSeparatorOnTop, isSelected, onToggleItem }: Readonly) {
+ return (
+
+ {showSeparatorOnTop &&
}
+
+
+
+ onToggleItem(sectionName, item.fullPath)}
+ />
+
+
+
+ {formatFileSize({ bytes: item.sizeInBytes })}
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/section-detail-menu.tsx b/packages/core/src/frontend/features/cleaner/components/section-detail-menu.tsx
new file mode 100644
index 0000000000..7946b3d734
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/section-detail-menu.tsx
@@ -0,0 +1,119 @@
+import { useVirtualizer } from '@tanstack/react-virtual';
+import { useRef } from 'react';
+
+import { CleanerReport, CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { SectionConfig } from '../cleaner.types';
+import { getSectionStats } from '../service/get-section-stats';
+import { isItemSelected } from '../service/is-item-selected';
+import { SectionDetailHeader } from './section-detail-header';
+import { SectionDetailMenuItem } from './section-detail-menu-item';
+import { Separator } from './separator';
+
+type Props = {
+ sectionName: CleanerSectionKey;
+ report: CleanerReport;
+ viewModel: CleanerViewModel;
+ sectionConfig: SectionConfig;
+ onClose: () => void;
+ onToggleSection: (sectionKey: CleanerSectionKey) => void;
+ onToggleItem: (sectionKey: CleanerSectionKey, itemPath: string) => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function SectionDetailMenu({
+ sectionName,
+ report,
+ viewModel,
+ sectionConfig,
+ onClose,
+ onToggleSection,
+ onToggleItem,
+ useTranslationContext,
+}: Readonly) {
+ const parentRef = useRef(null);
+
+ const sectionData = report[sectionName];
+ const sectionViewModel = viewModel[sectionName];
+ const items = sectionData?.items || [];
+
+ const virtualizer = useVirtualizer({
+ count: items.length,
+ getScrollElement: () => parentRef.current,
+ estimateSize: () => 80,
+ overscan: 10,
+ });
+
+ const stats = getSectionStats({ viewModel: sectionViewModel, allItems: sectionData.items });
+
+ const isAllSelected = stats.selected === 'all';
+ const isPartiallySelected = stats.selected === 'partial';
+ const isEmpty = stats.totalCount === 0;
+
+ function handleSelectAll() {
+ if (!isEmpty) {
+ onToggleSection(sectionName);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ {virtualizer.getVirtualItems().map((virtualItem) => {
+ const item = items[virtualItem.index];
+ if (!item) return <>>;
+ const isSelected = isItemSelected({ viewModel: sectionViewModel, itemPath: item.fullPath });
+
+ return (
+
+ 0}
+ isSelected={isSelected}
+ onToggleItem={onToggleItem}
+ />
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/section-item.tsx b/packages/core/src/frontend/features/cleaner/components/section-item.tsx
new file mode 100644
index 0000000000..cc58f039b6
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/section-item.tsx
@@ -0,0 +1,69 @@
+import { CaretRight } from '@phosphor-icons/react';
+
+import { CleanerSection, CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+import { Checkbox } from '@/frontend/components/checkbox';
+
+import { SectionConfig } from '../cleaner.types';
+import { formatFileSize } from '../service/format-file-size';
+import { getSectionStats } from '../service/get-section-stats';
+import { Separator } from './separator';
+
+type Props = {
+ sectionName: CleanerSectionKey;
+ section: CleanerSection;
+ showSeparatorOnTop: boolean;
+ viewModel: CleanerViewModel;
+ sectionConfig: SectionConfig;
+ onToggleSection: (sectionName: CleanerSectionKey) => void;
+ onToggleSectionExpansion: (sectionName: CleanerSectionKey) => void;
+};
+
+export function SectionItem({
+ sectionName,
+ section,
+ showSeparatorOnTop,
+ viewModel,
+ sectionConfig,
+ onToggleSection,
+ onToggleSectionExpansion,
+}: Readonly) {
+ const config = sectionConfig[sectionName];
+ const sectionViewModel = viewModel[sectionName];
+
+ if (!config || !sectionViewModel) return null;
+
+ const stats = getSectionStats({ viewModel: sectionViewModel, allItems: section.items });
+
+ const isSectionAllSelected = stats.selected === 'all';
+ const isSectionPartiallySelected = stats.selected === 'partial';
+ const isEmpty = stats.totalCount === 0;
+
+ return (
+
+ {showSeparatorOnTop &&
}
+
+
+
!isEmpty && onToggleSection(sectionName)}
+ />
+
+ {
+ e.stopPropagation();
+ onToggleSectionExpansion(sectionName);
+ }}>
+
+ {formatFileSize({ bytes: section.totalSizeInBytes })}
+
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/sections-list-header-type.tsx b/packages/core/src/frontend/features/cleaner/components/sections-list-header-type.tsx
new file mode 100644
index 0000000000..058a89e707
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/sections-list-header-type.tsx
@@ -0,0 +1,31 @@
+import { Checkbox } from '@/frontend/components/checkbox';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ isAllSelected: boolean;
+ isPartiallySelected: boolean;
+ hasAnyItems: boolean;
+ useTranslationContext: () => LocalContextProps;
+ onSelectAll: () => void;
+};
+
+export function SectionsListHeadertype({
+ isAllSelected,
+ isPartiallySelected,
+ hasAnyItems,
+ useTranslationContext,
+ onSelectAll,
+}: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+ hasAnyItems && onSelectAll()}
+ />
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/sections-list.tsx b/packages/core/src/frontend/features/cleaner/components/sections-list.tsx
new file mode 100644
index 0000000000..8e33fd45ae
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/sections-list.tsx
@@ -0,0 +1,61 @@
+import { CleanerViewModel, CleanerSectionKey, CleanerReport } from '@/backend/features/cleaner/types/cleaner.types';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { SectionConfig } from '../cleaner.types';
+import { SectionItem } from './section-item';
+import { SectionsListHeadertype } from './sections-list-header-type';
+import { Separator } from './separator';
+
+type Props = {
+ report: CleanerReport;
+ viewModel: CleanerViewModel;
+ isAllSelected: boolean;
+ isPartiallySelected: boolean;
+ sectionConfig: SectionConfig;
+ useTranslationContext: () => LocalContextProps;
+ onSelectAll: () => void;
+ onToggleSection: (sectionName: CleanerSectionKey) => void;
+ onToggleSectionExpansion: (sectionName: CleanerSectionKey) => void;
+};
+
+export function SectionsList({
+ report,
+ viewModel,
+ isAllSelected,
+ isPartiallySelected,
+ sectionConfig,
+ useTranslationContext,
+ onSelectAll,
+ onToggleSection,
+ onToggleSectionExpansion,
+}: Readonly) {
+ const hasAnyItems = Object.values(report).some((section) => section.items.length > 0);
+
+ return (
+
+
+
+
+
+ {Object.entries(report).map(([sectionName, section], index) => (
+ 0}
+ viewModel={viewModel}
+ sectionConfig={sectionConfig}
+ onToggleSection={onToggleSection}
+ onToggleSectionExpansion={onToggleSectionExpansion}
+ />
+ ))}
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/components/separator.tsx b/packages/core/src/frontend/features/cleaner/components/separator.tsx
new file mode 100644
index 0000000000..d1d8893326
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/components/separator.tsx
@@ -0,0 +1,13 @@
+type Props = {
+ size?: 'normal' | 'small';
+ classname?: string;
+};
+
+export function Separator({ classname = '', size = 'normal' }: Readonly) {
+ const sizeClasses = {
+ normal: 'w-full',
+ small: 'ml-10',
+ };
+
+ return ;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.test.ts b/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.test.ts
new file mode 100644
index 0000000000..949d75a709
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.test.ts
@@ -0,0 +1,59 @@
+import { mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
+
+import { calculateChartSegments } from './calculate-chart-segments';
+import * as calculateSectionSizeModule from './calculate-section-size';
+
+describe('calculate-chart-segments', () => {
+ const getSectionSelectionStats = vi.fn();
+ const calculateSectionSizeMock = partialSpyOn(calculateSectionSizeModule, 'calculateSectionSize');
+
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ getSectionSelectionStats.mockReturnValue(1);
+
+ props = mockProps({
+ totalSize: 4500,
+ getSectionSelectionStats,
+ report: { appCache: {}, logFiles: {}, webCache: {} },
+ viewModel: { appCache: {}, logFiles: {}, webCache: {} },
+ sectionConfig: {
+ appCache: { name: 'App Cache', color: '#FF6B6B' },
+ logFiles: { name: 'Log Files', color: '#4ECDC4' },
+ webCache: { name: 'Web Cache', color: '#45B7D1' },
+ },
+ });
+ });
+
+ it('should calculate segments correctly with no exceptions', () => {
+ // Given
+ calculateSectionSizeMock.mockReturnValueOnce(1000).mockReturnValueOnce(2000).mockReturnValueOnce(1500);
+ // When
+ const result = calculateChartSegments(props);
+ // Then
+ expect(result).toStrictEqual([
+ { color: '#FF6B6B', percentage: (1000 / 4500) * 100, size: 1000 },
+ { color: '#4ECDC4', percentage: (2000 / 4500) * 100, size: 2000 },
+ { color: '#45B7D1', percentage: (1500 / 4500) * 100, size: 1500 },
+ ]);
+ });
+
+ it('should skip sections with no selected items', () => {
+ // Given
+ calculateSectionSizeMock.mockReturnValueOnce(0).mockReturnValueOnce(2000).mockReturnValueOnce(0);
+ // When
+ const result = calculateChartSegments(props);
+ // Then
+ expect(result).toStrictEqual([{ color: '#4ECDC4', percentage: (2000 / 4500) * 100, size: 2000 }]);
+ });
+
+ it('should handle zero totalSize correctly', () => {
+ // Given
+ calculateSectionSizeMock.mockReturnValueOnce(1000);
+ props.totalSize = 0;
+ // When
+ const result = calculateChartSegments(props);
+ // Then
+ expect(result).toStrictEqual([{ color: '#FF6B6B', percentage: 0, size: 1000 }]);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.ts b/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.ts
new file mode 100644
index 0000000000..7d9b29dc37
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/calculate-chart-segments.ts
@@ -0,0 +1,40 @@
+import { CleanerReport, CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+import { SectionConfig } from '../cleaner.types';
+import { calculateSectionSize } from './calculate-section-size';
+import { getSectionStats } from './get-section-stats';
+
+type Props = {
+ viewModel: CleanerViewModel;
+ report: CleanerReport;
+ totalSize: number;
+ getSectionSelectionStats: (sectionKey: CleanerSectionKey, report: CleanerReport) => ReturnType;
+ sectionConfig: SectionConfig;
+};
+
+export function calculateChartSegments({ viewModel, report, totalSize, getSectionSelectionStats, sectionConfig }: Props) {
+ const segments: Array<{ color: string; percentage: number; size: number }> = [];
+
+ for (const [rawSectionKey, section] of Object.entries(report)) {
+ const sectionKey = rawSectionKey as CleanerSectionKey;
+ const sectionStats = getSectionSelectionStats(sectionKey, report);
+ const sectionViewModel = viewModel[sectionKey];
+
+ if (!sectionViewModel || sectionStats.selectedCount === 0) {
+ continue;
+ }
+
+ const sectionSelectedSize = calculateSectionSize({ section, sectionViewModel });
+
+ if (sectionSelectedSize > 0) {
+ const config = sectionConfig[sectionKey];
+ segments.push({
+ color: config.color,
+ percentage: totalSize > 0 ? (sectionSelectedSize / totalSize) * 100 : 0,
+ size: sectionSelectedSize,
+ });
+ }
+ }
+
+ return segments;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/calculate-section-size.ts b/packages/core/src/frontend/features/cleaner/service/calculate-section-size.ts
new file mode 100644
index 0000000000..4dbfeaae37
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/calculate-section-size.ts
@@ -0,0 +1,20 @@
+import { CleanerSection, CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+type Props = { section: CleanerSection; sectionViewModel: CleanerSectionViewModel };
+
+export function calculateSectionSize({ section, sectionViewModel }: Props) {
+ let size = 0;
+
+ for (const exceptionPath of sectionViewModel.exceptions) {
+ const item = section.items.find((item) => item.fullPath === exceptionPath);
+ if (item) {
+ size += item.sizeInBytes;
+ }
+ }
+
+ if (sectionViewModel.selectedAll) {
+ return section.totalSizeInBytes - size;
+ }
+
+ return size;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.test.ts b/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.test.ts
new file mode 100644
index 0000000000..3574c4d592
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.test.ts
@@ -0,0 +1,80 @@
+import { describe, it, expect } from 'vitest';
+
+import { CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { calculateSelectedSize } from './calculate-selected-size';
+
+describe('calculate-selected-size', () => {
+ const props = mockProps({
+ report: {
+ appCache: {
+ totalSizeInBytes: 1000,
+ items: [
+ { fullPath: '/cache/file1.tmp', sizeInBytes: 400 },
+ { fullPath: '/cache/file2.tmp', sizeInBytes: 300 },
+ { fullPath: '/cache/file3.tmp', sizeInBytes: 300 },
+ ],
+ },
+ logFiles: {
+ totalSizeInBytes: 2000,
+ items: [
+ { fullPath: '/logs/app.log', sizeInBytes: 800 },
+ { fullPath: '/logs/error.log', sizeInBytes: 1200 },
+ ],
+ },
+ webCache: {
+ totalSizeInBytes: 1500,
+ items: [{ fullPath: '/web/cache1', sizeInBytes: 1500 }],
+ },
+ },
+ });
+
+ it('should return total size of all sections when no exceptions', () => {
+ // Given
+ props.viewModel = {
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ webCache: { selectedAll: true, exceptions: [] },
+ } as Partial as CleanerViewModel;
+ // When
+ const result = calculateSelectedSize(props);
+ // Then
+ expect(result).toBe(4500);
+ });
+
+ it('should subtract exception items from total size', () => {
+ // Given
+ props.viewModel = {
+ appCache: { selectedAll: false, exceptions: ['/cache/file1.tmp', '/cache/file2.tmp'] },
+ logFiles: { selectedAll: false, exceptions: ['/logs/error.log'] },
+ webCache: { selectedAll: true, exceptions: [] },
+ } as Partial as CleanerViewModel;
+ // When
+ const result = calculateSelectedSize(props);
+ // Then
+ expect(result).toBe(3400);
+ });
+
+ it('should return 0 when no exceptions and selectedAll is false', () => {
+ // Given
+ props.viewModel = {
+ appCache: { selectedAll: false, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ webCache: { selectedAll: false, exceptions: [] },
+ } as Partial as CleanerViewModel;
+ // When
+ const result = calculateSelectedSize(props);
+ // Then
+ expect(result).toBe(0);
+ });
+
+ it('should handle empty report', () => {
+ // Given
+ props.viewModel = {} as Partial as CleanerViewModel;
+ // When
+ const result = calculateSelectedSize(props);
+ // Then
+ expect(result).toBe(0);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.ts b/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.ts
new file mode 100644
index 0000000000..0e5f40c4d3
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/calculate-selected-size.ts
@@ -0,0 +1,18 @@
+import { CleanerReport, CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+import { calculateSectionSize } from './calculate-section-size';
+
+type Props = { viewModel: CleanerViewModel; report: CleanerReport };
+
+export function calculateSelectedSize({ viewModel, report }: Props) {
+ let totalSize = 0;
+
+ for (const [sectionKey, sectionViewModel] of Object.entries(viewModel)) {
+ const section = report[sectionKey as CleanerSectionKey];
+ if (section) {
+ totalSize += calculateSectionSize({ section, sectionViewModel });
+ }
+ }
+
+ return totalSize;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.test.ts b/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.test.ts
new file mode 100644
index 0000000000..c06fed46a4
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.test.ts
@@ -0,0 +1,35 @@
+import { createInitialViewModel } from './create-initial-view-model';
+
+describe('createInitialViewModel', () => {
+ let props: Parameters[0];
+ beforeEach(() => {
+ props = {
+ cleanerSectionKeys: ['appCache', 'logFiles', 'trash'],
+ selectedAll: true,
+ };
+ });
+
+ it('should create a view model with selectedAll set to true by default', () => {
+ // When
+ const result = createInitialViewModel(props);
+ // Then
+ expect(result).toMatchObject({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ });
+
+ it('should create a view model with selectedAll set to false when specified', () => {
+ // Given
+ props.selectedAll = false;
+ // When
+ const result = createInitialViewModel(props);
+ // Then
+ expect(result).toMatchObject({
+ appCache: { selectedAll: false, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ trash: { selectedAll: false, exceptions: [] },
+ });
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.ts b/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.ts
new file mode 100644
index 0000000000..a70089eff1
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/create-initial-view-model.ts
@@ -0,0 +1,19 @@
+import { CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+type Props = {
+ cleanerSectionKeys: CleanerSectionKey[];
+ selectedAll?: boolean;
+};
+
+export function createInitialViewModel({ cleanerSectionKeys, selectedAll = true }: Props) {
+ const viewModel = {} as unknown as CleanerViewModel;
+
+ for (const sectionKey of cleanerSectionKeys) {
+ viewModel[sectionKey] = {
+ selectedAll,
+ exceptions: [],
+ };
+ }
+
+ return viewModel;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/format-file-size.test.ts b/packages/core/src/frontend/features/cleaner/service/format-file-size.test.ts
new file mode 100644
index 0000000000..6a7cc37241
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/format-file-size.test.ts
@@ -0,0 +1,55 @@
+import { describe, it, expect } from 'vitest';
+
+import { formatFileSize } from './format-file-size';
+
+describe('formatFileSize', () => {
+ it('should return "0 B" for zero bytes', () => {
+ const result = formatFileSize({ bytes: 0 });
+ expect(result).toBe('0 B');
+ });
+
+ it('should format bytes correctly', () => {
+ const result = formatFileSize({ bytes: 512 });
+ expect(result).toBe('512 B');
+ });
+
+ it('should format kilobytes correctly', () => {
+ const result = formatFileSize({ bytes: 1024 });
+ expect(result).toBe('1 KB');
+ });
+
+ it('should format kilobytes with decimal correctly', () => {
+ const result = formatFileSize({ bytes: 1536 });
+ expect(result).toBe('1.5 KB');
+ });
+
+ it('should format megabytes correctly', () => {
+ const result = formatFileSize({ bytes: 1048576 });
+ expect(result).toBe('1 MB');
+ });
+
+ it('should format megabytes with decimal correctly', () => {
+ const result = formatFileSize({ bytes: 2621440 });
+ expect(result).toBe('2.5 MB');
+ });
+
+ it('should format gigabytes correctly', () => {
+ const result = formatFileSize({ bytes: 1073741824 });
+ expect(result).toBe('1 GB');
+ });
+
+ it('should format terabytes correctly', () => {
+ const result = formatFileSize({ bytes: 1099511627776 });
+ expect(result).toBe('1 TB');
+ });
+
+ it('should handle large numbers correctly', () => {
+ const result = formatFileSize({ bytes: 5497558138880 });
+ expect(result).toBe('5 TB');
+ });
+
+ it('should round to one decimal place', () => {
+ const result = formatFileSize({ bytes: 1234567 });
+ expect(result).toBe('1.2 MB');
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/format-file-size.ts b/packages/core/src/frontend/features/cleaner/service/format-file-size.ts
new file mode 100644
index 0000000000..71b5ccce6c
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/format-file-size.ts
@@ -0,0 +1,7 @@
+export function formatFileSize({ bytes }: { bytes: number }): string {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/get-global-stats.test.ts b/packages/core/src/frontend/features/cleaner/service/get-global-stats.test.ts
new file mode 100644
index 0000000000..b6c2ff4cf0
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-global-stats.test.ts
@@ -0,0 +1,108 @@
+import { mockProps, updateProps } from '@/tests/vitest/utils.helper.test';
+
+import { getGlobalStats } from './get-global-stats';
+
+describe('getGlobalStats', () => {
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ props = mockProps({
+ sectionKeys: ['appCache', 'logFiles', 'trash'],
+ report: {
+ appCache: { items: [{ fullPath: '/cache/file1.txt' }, { fullPath: '/cache/file2.txt' }] },
+ logFiles: { items: [{ fullPath: '/logs/log1.txt' }] },
+ trash: { items: [] },
+ },
+ });
+ });
+
+ it('should return all selected when all non-empty sections are fully selected', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('all');
+ });
+
+ it('should return none selected when all non-empty sections have nothing selected', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: false, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ trash: { selectedAll: false, exceptions: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('none');
+ });
+
+ it('should return partially selected when sections have mixed selection states', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('partial');
+ });
+
+ it('should return partially selected when at least one section is partially selected', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: ['/cache/file1.txt'] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('partial');
+ });
+
+ it('should return none selected when all sections are empty', () => {
+ // Given
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: true, exceptions: [] },
+ trash: { selectedAll: true, exceptions: [] },
+ });
+
+ props.report = updateProps({
+ appCache: { totalSizeInBytes: 0, items: [] },
+ logFiles: { totalSizeInBytes: 0, items: [] },
+ trash: { totalSizeInBytes: 0, items: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('none');
+ });
+
+ it('should ignore empty sections when calculating global stats', () => {
+ // Given
+ props.report = updateProps({
+ appCache: { items: [{ fullPath: '/cache/file1.txt' }] },
+ logFiles: { items: [] },
+ trash: { items: [] },
+ });
+
+ props.viewModel = updateProps({
+ appCache: { selectedAll: true, exceptions: [] },
+ logFiles: { selectedAll: false, exceptions: [] },
+ trash: { selectedAll: false, exceptions: [] },
+ });
+ // When
+ const result = getGlobalStats(props);
+ // Then
+ expect(result).toBe('all');
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/get-global-stats.ts b/packages/core/src/frontend/features/cleaner/service/get-global-stats.ts
new file mode 100644
index 0000000000..4a0291884e
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-global-stats.ts
@@ -0,0 +1,23 @@
+import { CleanerReport, CleanerSectionKey, CleanerViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+import { getSectionStats, Selected } from './get-section-stats';
+
+type Props = {
+ viewModel: CleanerViewModel;
+ report: CleanerReport;
+ sectionKeys: CleanerSectionKey[];
+};
+
+export function getGlobalStats({ viewModel, report, sectionKeys }: Props): Selected {
+ const allSectionStats = sectionKeys.map((sectionKey) => {
+ const section = report[sectionKey];
+ return getSectionStats({ viewModel: viewModel[sectionKey], allItems: section.items });
+ });
+
+ const nonEmptySectionStats = allSectionStats.filter((stats) => stats.totalCount > 0);
+
+ if (nonEmptySectionStats.length === 0) return 'none';
+ if (nonEmptySectionStats.every((stats) => stats.selected === 'all')) return 'all';
+ if (nonEmptySectionStats.every((stats) => stats.selected === 'none')) return 'none';
+ return 'partial';
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/get-section-stats.test.ts b/packages/core/src/frontend/features/cleaner/service/get-section-stats.test.ts
new file mode 100644
index 0000000000..6f84f37d3e
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-section-stats.test.ts
@@ -0,0 +1,57 @@
+import { describe, it, expect } from 'vitest';
+
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { getSectionStats } from './get-section-stats';
+
+describe('get-section-stats', () => {
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ props = mockProps({
+ viewModel: { selectedAll: true },
+ allItems: [
+ { fullPath: '/path/to/file1.txt' },
+ { fullPath: '/path/to/file2.txt' },
+ { fullPath: '/path/to/file3.txt' },
+ { fullPath: '/path/to/file4.txt' },
+ ],
+ });
+ });
+
+ it('should return stats indicating no items', () => {
+ // Given
+ props.allItems = [];
+ // When
+ const result = getSectionStats(props);
+ // Then
+ expect(result).toMatchObject({ selectedCount: 0, totalCount: 0, selected: 'none' });
+ });
+
+ it('should return all selected when no exceptions', () => {
+ // Given
+ props.viewModel.exceptions = [];
+ // When
+ const result = getSectionStats(props);
+ // Then
+ expect(result).toMatchObject({ selectedCount: 4, totalCount: 4, selected: 'all' });
+ });
+
+ it('should return partially selected when some exceptions exist', () => {
+ // Given
+ props.viewModel.exceptions = ['/path/to/file2.txt', '/path/to/file4.txt'];
+ // When
+ const result = getSectionStats(props);
+ // Then
+ expect(result).toMatchObject({ selectedCount: 2, totalCount: 4, selected: 'partial' });
+ });
+
+ it('should return none selected when all items are exceptions', () => {
+ // Given
+ props.viewModel.exceptions = ['/path/to/file1.txt', '/path/to/file2.txt', '/path/to/file3.txt', '/path/to/file4.txt'];
+ // When
+ const result = getSectionStats(props);
+ // Then
+ expect(result).toMatchObject({ selectedCount: 0, totalCount: 4, selected: 'none' });
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/get-section-stats.ts b/packages/core/src/frontend/features/cleaner/service/get-section-stats.ts
new file mode 100644
index 0000000000..d73ed8e546
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-section-stats.ts
@@ -0,0 +1,22 @@
+import { CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+import { getSelectedItems } from './get-selected-items';
+
+export type Selected = 'none' | 'partial' | 'all';
+type Props = { viewModel: CleanerSectionViewModel; allItems: Array<{ fullPath: string }> };
+
+export function getSectionStats({ viewModel, allItems }: Props) {
+ const selectedItems = getSelectedItems({ viewModel, allItems });
+ const selectedCount = selectedItems.length;
+ const totalCount = allItems.length;
+
+ let selected: Selected = 'partial';
+ if (selectedCount === 0) selected = 'none';
+ else if (selectedCount === totalCount) selected = 'all';
+
+ return {
+ selectedCount,
+ totalCount,
+ selected,
+ };
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/get-selected-items.test.ts b/packages/core/src/frontend/features/cleaner/service/get-selected-items.test.ts
new file mode 100644
index 0000000000..0959d50494
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-selected-items.test.ts
@@ -0,0 +1,64 @@
+import { mockProps } from '@/tests/vitest/utils.helper.test';
+
+import { getSelectedItems } from './get-selected-items';
+
+describe('get-selected-items', () => {
+ let props: Parameters[0];
+
+ beforeEach(() => {
+ props = mockProps({
+ viewModel: {
+ selectedAll: false,
+ exceptions: [],
+ },
+ allItems: [{ fullPath: '/path/to/file1' }, { fullPath: '/path/to/file2' }, { fullPath: '/path/to/file3' }],
+ });
+ });
+
+ it('should return all items except exceptions when selectedAll is true', () => {
+ // Given
+ props.viewModel.selectedAll = true;
+ props.viewModel.exceptions = ['/path/to/file2'];
+ // When
+ const res = getSelectedItems(props);
+ // Then
+ expect(res).toStrictEqual(['/path/to/file1', '/path/to/file3']);
+ });
+
+ it('should return all items when selectedAll is true and no exceptions', () => {
+ // Given
+ props.viewModel.selectedAll = true;
+ props.viewModel.exceptions = [];
+ // When
+ const res = getSelectedItems(props);
+ // Then
+ expect(res).toStrictEqual(['/path/to/file1', '/path/to/file2', '/path/to/file3']);
+ });
+
+ it('should return only exceptions when selectedAll is false', () => {
+ // Given
+ props.viewModel.exceptions = ['/path/to/file1', '/path/to/file3'];
+ // When
+ const res = getSelectedItems(props);
+ // Then
+ expect(res).toStrictEqual(['/path/to/file1', '/path/to/file3']);
+ });
+
+ it('should return empty array when selectedAll is false and no exceptions', () => {
+ // Given
+ props.viewModel.exceptions = [];
+ // When
+ const res = getSelectedItems(props);
+ // Then
+ expect(res).toStrictEqual([]);
+ });
+
+ it('should filter out exceptions that are not in allItems when selectedAll is false', () => {
+ // Given
+ props.viewModel.exceptions = ['/path/to/file1', '/path/not/in/items'];
+ // When
+ const res = getSelectedItems(props);
+ // Then
+ expect(res).toStrictEqual(['/path/to/file1']);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/get-selected-items.ts b/packages/core/src/frontend/features/cleaner/service/get-selected-items.ts
new file mode 100644
index 0000000000..b4b86a64db
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/get-selected-items.ts
@@ -0,0 +1,9 @@
+import { CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+export function getSelectedItems({ viewModel, allItems }: { viewModel: CleanerSectionViewModel; allItems: Array<{ fullPath: string }> }) {
+ if (viewModel.selectedAll) {
+ return allItems.map((item) => item.fullPath).filter((path) => !viewModel.exceptions.includes(path));
+ } else {
+ return viewModel.exceptions.filter((path) => allItems.some((item) => item.fullPath === path));
+ }
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/is-item-selected.test.ts b/packages/core/src/frontend/features/cleaner/service/is-item-selected.test.ts
new file mode 100644
index 0000000000..330bb77650
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/is-item-selected.test.ts
@@ -0,0 +1,32 @@
+import { isItemSelected } from './is-item-selected';
+
+describe('isItemSelected', () => {
+ let props: Parameters[0];
+ beforeEach(() => {
+ props = {
+ viewModel: {
+ selectedAll: true,
+ exceptions: ['/path/to/item1.txt'],
+ },
+ itemPath: '/path/to/item2.txt',
+ };
+ });
+
+ it('should return true for items not in exceptions when selectedAll is true', () => {
+ // Given
+ props.viewModel.selectedAll = true;
+ // When
+ const result = isItemSelected(props);
+ // Then
+ expect(result).toBe(true);
+ });
+
+ it('should return false for items not in exceptions when selectedAll is false', () => {
+ // Given
+ props.viewModel.selectedAll = false;
+ // When
+ const result = isItemSelected(props);
+ // Then
+ expect(result).toBe(false);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/is-item-selected.ts b/packages/core/src/frontend/features/cleaner/service/is-item-selected.ts
new file mode 100644
index 0000000000..b9453c3bd0
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/is-item-selected.ts
@@ -0,0 +1,8 @@
+import { CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+type Props = { viewModel: CleanerSectionViewModel; itemPath: string };
+
+export function isItemSelected({ viewModel, itemPath }: Props) {
+ const isException = viewModel.exceptions.includes(itemPath);
+ return viewModel.selectedAll ? !isException : isException;
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/toggle-item.test.ts b/packages/core/src/frontend/features/cleaner/service/toggle-item.test.ts
new file mode 100644
index 0000000000..e9713da5ce
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/toggle-item.test.ts
@@ -0,0 +1,32 @@
+import { toggleItem } from './toggle-item';
+
+describe('toggleItem', () => {
+ let props: Parameters[0];
+ beforeEach(() => {
+ props = {
+ viewModel: {
+ selectedAll: true,
+ exceptions: ['/path/to/file1.txt'],
+ },
+ itemPath: '/path/to/file2.txt',
+ };
+ });
+
+ it('should add item to exceptions when it is not present', () => {
+ // When
+ const result = toggleItem(props);
+ // Then
+ expect(result.exceptions).toStrictEqual(['/path/to/file1.txt', '/path/to/file2.txt']);
+ });
+
+ it('should remove item from exceptions when it is present', () => {
+ // Given
+ props.viewModel.selectedAll = false;
+ props.viewModel.exceptions = ['/path/to/file1.txt', '/path/to/file2.txt', '/path/to/file3.txt'];
+ // When
+ const result = toggleItem(props);
+ // Then
+ expect(result.exceptions).toStrictEqual(['/path/to/file1.txt', '/path/to/file3.txt']);
+ expect(result.selectedAll).toBe(false);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/toggle-item.ts b/packages/core/src/frontend/features/cleaner/service/toggle-item.ts
new file mode 100644
index 0000000000..f2278dd367
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/toggle-item.ts
@@ -0,0 +1,19 @@
+import { CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+type Props = { viewModel: CleanerSectionViewModel; itemPath: string };
+
+export function toggleItem({ viewModel, itemPath }: Props) {
+ const exceptions = [...viewModel.exceptions];
+ const exceptionIndex = exceptions.indexOf(itemPath);
+
+ if (exceptionIndex >= 0) {
+ exceptions.splice(exceptionIndex, 1);
+ } else {
+ exceptions.push(itemPath);
+ }
+
+ return {
+ ...viewModel,
+ exceptions,
+ };
+}
diff --git a/packages/core/src/frontend/features/cleaner/service/toggle-select-all.test.ts b/packages/core/src/frontend/features/cleaner/service/toggle-select-all.test.ts
new file mode 100644
index 0000000000..bf749ac68a
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/toggle-select-all.test.ts
@@ -0,0 +1,36 @@
+import { toggleSelectAll } from './toggle-select-all';
+
+describe('toggle-select-all', () => {
+ let viewModel = { selectedAll: false, exceptions: ['/path/file1', '/path/file2'] };
+
+ it('should toggle selectedAll from false to true and clear exceptions', () => {
+ // When
+ const result = toggleSelectAll({ viewModel });
+ // Then
+ expect(result).toStrictEqual({
+ selectedAll: true,
+ exceptions: [],
+ });
+ });
+
+ it('should toggle selectedAll from true to false and clear exceptions', () => {
+ // Given
+ viewModel.selectedAll = true;
+ // When
+ const result = toggleSelectAll({ viewModel });
+ // Then
+ expect(result).toStrictEqual({
+ selectedAll: false,
+ exceptions: [],
+ });
+ });
+
+ it('should clear exceptions regardless of selectedAll state', () => {
+ // Given
+ viewModel = { selectedAll: false, exceptions: ['/path/file1', '/path/file2', '/path/file3'] };
+ // When
+ const result = toggleSelectAll({ viewModel });
+ // Then
+ expect(result.exceptions).toHaveLength(0);
+ });
+});
diff --git a/packages/core/src/frontend/features/cleaner/service/toggle-select-all.ts b/packages/core/src/frontend/features/cleaner/service/toggle-select-all.ts
new file mode 100644
index 0000000000..cee6807976
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/service/toggle-select-all.ts
@@ -0,0 +1,8 @@
+import { CleanerSectionViewModel } from '@/backend/features/cleaner/types/cleaner.types';
+
+export function toggleSelectAll({ viewModel }: { viewModel: CleanerSectionViewModel }) {
+ return {
+ selectedAll: !viewModel.selectedAll,
+ exceptions: [],
+ };
+}
diff --git a/packages/core/src/frontend/features/cleaner/use-cleaner-view-model.ts b/packages/core/src/frontend/features/cleaner/use-cleaner-view-model.ts
new file mode 100644
index 0000000000..fa74b218b0
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/use-cleaner-view-model.ts
@@ -0,0 +1,97 @@
+import { useCallback, useState } from 'react';
+
+import { CleanerSectionKey, CleanerViewModel, CleanerReport } from '@/backend/features/cleaner/types/cleaner.types';
+
+import { calculateSelectedSize } from './service/calculate-selected-size';
+import { createInitialViewModel } from './service/create-initial-view-model';
+import { getGlobalStats } from './service/get-global-stats';
+import { getSectionStats } from './service/get-section-stats';
+import { getSelectedItems } from './service/get-selected-items';
+import { isItemSelected } from './service/is-item-selected';
+import { toggleItem } from './service/toggle-item';
+import { toggleSelectAll } from './service/toggle-select-all';
+
+export function useCleanerViewModel(sectionKeys: CleanerSectionKey[]) {
+ const keys = sectionKeys;
+ const [viewModel, setViewModel] = useState(createInitialViewModel({ cleanerSectionKeys: keys }));
+
+ const toggleSection = useCallback((sectionKey: CleanerSectionKey) => {
+ setViewModel((prev) => ({
+ ...prev,
+ [sectionKey]: toggleSelectAll({ viewModel: prev[sectionKey] }),
+ }));
+ }, []);
+
+ const toggleItemSelection = useCallback((sectionKey: CleanerSectionKey, itemPath: string) => {
+ setViewModel((prev) => ({
+ ...prev,
+ [sectionKey]: toggleItem({ viewModel: prev[sectionKey], itemPath }),
+ }));
+ }, []);
+
+ const selectAllSections = useCallback(() => {
+ setViewModel(createInitialViewModel({ cleanerSectionKeys: keys }));
+ }, []);
+
+ const deselectAllSections = useCallback(() => {
+ setViewModel(createInitialViewModel({ cleanerSectionKeys: keys, selectedAll: false }));
+ }, []);
+
+ const isItemSelectedInSection = useCallback(
+ (sectionKey: CleanerSectionKey, itemPath: string) => {
+ return isItemSelected({ viewModel: viewModel[sectionKey], itemPath });
+ },
+ [viewModel],
+ );
+
+ const getSelectedItemsForSection = useCallback(
+ (sectionKey: CleanerSectionKey, report: CleanerReport) => {
+ const section = report[sectionKey];
+ return section ? getSelectedItems({ viewModel: viewModel[sectionKey], allItems: section.items }) : [];
+ },
+ [viewModel],
+ );
+
+ const getSectionSelectionStats = useCallback(
+ (sectionKey: CleanerSectionKey, report: CleanerReport) => {
+ const section = report[sectionKey];
+ return section
+ ? getSectionStats({ viewModel: viewModel[sectionKey], allItems: section.items })
+ : {
+ selectedCount: 0,
+ totalCount: 0,
+ selected: 'none' as const,
+ };
+ },
+ [viewModel],
+ );
+
+ const getTotalSelectedSize = useCallback(
+ (report: CleanerReport) => {
+ return calculateSelectedSize({ viewModel, report });
+ },
+ [viewModel],
+ );
+
+ const getGlobalSelectionStats = useCallback(
+ (report: CleanerReport) => {
+ return getGlobalStats({ viewModel, report, sectionKeys: keys });
+ },
+ [viewModel, keys],
+ );
+
+ return {
+ viewModel,
+ toggleSection,
+ toggleItemSelection,
+ selectAllSections,
+ deselectAllSections,
+ isItemSelectedInSection,
+ getSelectedItemsForSection,
+ getSectionSelectionStats,
+ getTotalSelectedSize,
+ getGlobalSelectionStats,
+ };
+}
+
+export type CleanerViewModelHook = ReturnType;
diff --git a/packages/core/src/frontend/features/cleaner/views/cleaner-view.tsx b/packages/core/src/frontend/features/cleaner/views/cleaner-view.tsx
new file mode 100644
index 0000000000..2b1dae0b94
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/views/cleaner-view.tsx
@@ -0,0 +1,103 @@
+import { useMemo, useState } from 'react';
+
+import { CleanerReport, CleanerSectionKey } from '@/backend/features/cleaner/types/cleaner.types';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { SectionConfig } from '../cleaner.types';
+import { CleanupSizeIndicator } from '../components/cleanup-size-indicator';
+import { SectionDetailMenu } from '../components/section-detail-menu';
+import { SectionsList } from '../components/sections-list';
+import { calculateChartSegments } from '../service/calculate-chart-segments';
+import { CleanerViewModelHook } from '../use-cleaner-view-model';
+
+type CleanerViewProps = {
+ report: CleanerReport;
+ diskSpace: number;
+ sectionConfig: SectionConfig;
+ useTranslationContext: () => LocalContextProps;
+} & CleanerViewModelHook;
+
+export function CleanerView({
+ report,
+ viewModel,
+ diskSpace,
+ sectionConfig,
+ useTranslationContext,
+ toggleSection,
+ toggleItemSelection,
+ selectAllSections,
+ deselectAllSections,
+ getSectionSelectionStats,
+ getTotalSelectedSize,
+ getGlobalSelectionStats,
+}: CleanerViewProps) {
+ const [sectionDetailMenu, setSectionDetailMenu] = useState(null);
+
+ const totalSize = useMemo(() => {
+ return Object.values(report).reduce((sum, section) => sum + section.totalSizeInBytes, 0);
+ }, [report]);
+
+ const selectedSize = useMemo(() => {
+ return getTotalSelectedSize(report);
+ }, [getTotalSelectedSize, report]);
+
+ const toggleSectionExpansion = (sectionKey: CleanerSectionKey) => {
+ setSectionDetailMenu((prev) => (prev === sectionKey ? null : sectionKey));
+ };
+
+ const globalStats = useMemo(() => {
+ return getGlobalSelectionStats(report);
+ }, [getGlobalSelectionStats, report]);
+
+ const selectAll = () => {
+ if (globalStats === 'all') {
+ deselectAllSections();
+ } else {
+ selectAllSections();
+ }
+ };
+
+ const segmentDetails = useMemo(() => {
+ return calculateChartSegments({ viewModel, report, totalSize, getSectionSelectionStats, sectionConfig });
+ }, [viewModel, report, totalSize, getSectionSelectionStats]);
+
+ return (
+
+ {/* Main View */}
+
+ {/* Left Panel */}
+
+ {/* Right Panel */}
+
+
+ {/* Section Detail Menu */}
+ {sectionDetailMenu && (
+
setSectionDetailMenu(null)}
+ onToggleSection={toggleSection}
+ onToggleItem={toggleItemSelection}
+ useTranslationContext={useTranslationContext}
+ />
+ )}
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/views/cleaning-view.tsx b/packages/core/src/frontend/features/cleaner/views/cleaning-view.tsx
new file mode 100644
index 0000000000..06e50ad4d0
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/views/cleaning-view.tsx
@@ -0,0 +1,48 @@
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+import { CleanerContextType } from '../cleaner.types';
+import { CleaningFinished } from '../components/cleaning-finished';
+import { CleaningProcess } from '../components/cleaning-process';
+
+type Props = {
+ useCleaner: () => CleanerContextType;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function CleaningView({ useCleaner, useTranslationContext }: Readonly) {
+ const { cleaningState, generateReport, stopCleanup, setInitialCleaningState } = useCleaner();
+
+ function handleStopCleaning() {
+ stopCleanup();
+ }
+
+ function handleFinishView() {
+ setInitialCleaningState();
+ void generateReport(true);
+ }
+
+ return (
+
+
+ {cleaningState.cleaning && (
+
+ )}
+ {cleaningState.cleaningCompleted && !cleaningState.cleaning && (
+
+ )}
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/views/generate-report-view.tsx b/packages/core/src/frontend/features/cleaner/views/generate-report-view.tsx
new file mode 100644
index 0000000000..799f2190a2
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/views/generate-report-view.tsx
@@ -0,0 +1,23 @@
+import { Button } from '@/frontend/components/button';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ onGenerateReport: () => void;
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function GenerateReportView({ onGenerateReport, useTranslationContext }: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+
+
{translate('settings.cleaner.generateReportView.title')}
+
{translate('settings.cleaner.generateReportView.description')}
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/views/loading-view.tsx b/packages/core/src/frontend/features/cleaner/views/loading-view.tsx
new file mode 100644
index 0000000000..33be9bf076
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/views/loading-view.tsx
@@ -0,0 +1,22 @@
+import { Spinner } from '@/frontend/components/spinner';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ useTranslationContext: () => LocalContextProps;
+};
+
+export function LoadingView({ useTranslationContext }: Readonly) {
+ const { translate } = useTranslationContext();
+
+ return (
+
+
+
{translate('settings.cleaner.loadingView.title')}
+
{translate('settings.cleaner.loadingView.description')}
+
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/features/cleaner/views/locked-view.tsx b/packages/core/src/frontend/features/cleaner/views/locked-view.tsx
new file mode 100644
index 0000000000..b98a667017
--- /dev/null
+++ b/packages/core/src/frontend/features/cleaner/views/locked-view.tsx
@@ -0,0 +1,37 @@
+import { Button } from '@/frontend/components/button';
+import { LocalContextProps } from '@/frontend/frontend.types';
+
+type Props = {
+ useTranslationContext: () => LocalContextProps;
+ openUrl: (url: string) => Promise;
+};
+
+export function LockedState({ useTranslationContext, openUrl }: Readonly) {
+ const { translate } = useTranslationContext();
+
+ async function handleOpenPricingPage() {
+ try {
+ await openUrl('https://internxt.com/pricing');
+ } catch (error) {
+ reportError(error);
+ }
+ }
+
+ return (
+
+
+
+
{translate('settings.antivirus.featureLocked.title')}
+
{translate('settings.antivirus.featureLocked.subtitle')}
+
+
+
+
+ );
+}
diff --git a/packages/core/src/frontend/frontend.types.ts b/packages/core/src/frontend/frontend.types.ts
new file mode 100644
index 0000000000..004b66d3ed
--- /dev/null
+++ b/packages/core/src/frontend/frontend.types.ts
@@ -0,0 +1,6 @@
+import { Language, TranslationFn } from './core/i18n/i18n.types';
+
+export type LocalContextProps = {
+ translate: TranslationFn;
+ language: Language;
+};
diff --git a/packages/core/src/frontend/index.ts b/packages/core/src/frontend/index.ts
new file mode 100644
index 0000000000..9ac95c345e
--- /dev/null
+++ b/packages/core/src/frontend/index.ts
@@ -0,0 +1,2 @@
+export { CleanerModule } from './features/cleaner/cleaner.module';
+export { SectionSpinner } from './components/section-spinner';
diff --git a/packages/core/tailwind.config.js b/packages/core/tailwind.config.js
new file mode 100644
index 0000000000..f053ebf797
--- /dev/null
+++ b/packages/core/tailwind.config.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/packages/core/tests/vitest/mocks.helper.test.ts b/packages/core/tests/vitest/mocks.helper.test.ts
new file mode 100644
index 0000000000..2d8b6f1f78
--- /dev/null
+++ b/packages/core/tests/vitest/mocks.helper.test.ts
@@ -0,0 +1,7 @@
+import { join } from 'node:path';
+import { cwd } from 'node:process';
+
+import { logger } from '@/backend/core/logger/logger';
+
+export const TEST_FILES = join(cwd(), 'test-files');
+export const loggerMock = vi.mocked(logger);
diff --git a/packages/core/tests/vitest/setup.helper.test.ts b/packages/core/tests/vitest/setup.helper.test.ts
new file mode 100644
index 0000000000..cd7918bc2f
--- /dev/null
+++ b/packages/core/tests/vitest/setup.helper.test.ts
@@ -0,0 +1,9 @@
+import { mkdirSync } from 'node:fs';
+import { vi } from 'vitest';
+
+import { TEST_FILES } from './mocks.helper.test';
+
+// We do not want to log anything
+vi.mock(import('@/backend/core/logger/logger'));
+
+mkdirSync(TEST_FILES, { recursive: true });
diff --git a/packages/core/tests/vitest/utils.helper.test.ts b/packages/core/tests/vitest/utils.helper.test.ts
new file mode 100644
index 0000000000..28df0b8339
--- /dev/null
+++ b/packages/core/tests/vitest/utils.helper.test.ts
@@ -0,0 +1,43 @@
+/* eslint-disable @typescript-eslint/no-unsafe-call */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { DeepPartial } from 'ts-essentials';
+import { MockedFunction, MockInstance } from 'vitest';
+
+export function mockProps unknown>(props: DeepPartial[0]>) {
+ return props as Parameters[0];
+}
+
+export function updateProps(props: DeepPartial) {
+ return props as T;
+}
+
+export function deepMocked unknown>(fn: T) {
+ return vi.mocked(fn) as MockedFunction<(...args: Parameters) => DeepPartial>>;
+}
+
+export function calls(object: any) {
+ return expect(object.mock.calls.map((call: any) => call[0]));
+}
+
+export function call(object: any) {
+ const calls = object.mock.calls.map((call: any) => call[0]);
+ if (calls.length !== 1) throw new Error(`Invalid length: ${calls.length} calls`);
+ return expect(calls[0]);
+}
+
+/**
+ * v2.5.6 Daniel JimΓ©nez
+ * Code extracted from vitest
+ * https://github.com/vitest-dev/vitest/blob/c1f78d2adc78ef08ef8b61b0dd6a925fb08f20b6/packages/spy/src/index.ts#L464
+ */
+type Procedure = (...args: any[]) => any;
+type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K] };
+type Classes = { [K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never }[keyof T] & (string | symbol);
+export function partialSpyOn> | Methods>>(obj: T, methodName: M, mock = true) {
+ type Fn = Required[M] extends (...args: any[]) => any ? Required[M] : never;
+ const objSpy = vi.spyOn(obj as Required, methodName);
+ // @ts-expect-error by default we want to remove always the real implementation
+ // se we don't run unexpected code
+ if (mock) objSpy.mockImplementation(() => {});
+ return objSpy as MockInstance<(...args: Parameters) => DeepPartial>>;
+}
diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json
new file mode 100644
index 0000000000..909faf5e6a
--- /dev/null
+++ b/packages/core/tsconfig.build.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "noEmit": false,
+ "outDir": "build"
+ },
+ "exclude": ["**/*.test.ts"]
+}
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 0000000000..23a03cf15c
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "target": "ESNext",
+ "module": "commonjs",
+ "lib": ["dom", "esnext"],
+ "declaration": true,
+ "declarationMap": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "pretty": true,
+ "sourceMap": true,
+ "rootDir": ".",
+ "noImplicitReturns": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["vitest/globals"],
+ "paths": {
+ "@/*": ["./src/*"],
+ "@/tests/*": ["./tests/*"]
+ }
+ },
+ "include": ["src", "tests"]
+}
diff --git a/packages/core/vitest.config.mts b/packages/core/vitest.config.mts
new file mode 100644
index 0000000000..3e0bba6187
--- /dev/null
+++ b/packages/core/vitest.config.mts
@@ -0,0 +1,21 @@
+import tsconfigPaths from 'vite-tsconfig-paths';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ plugins: [tsconfigPaths()],
+ test: {
+ coverage: {
+ provider: 'v8',
+ reporter: ['lcov', 'json-summary'],
+ reportOnFailure: true,
+ },
+ clearMocks: true,
+ exclude: ['**/*.helper.test.ts', '**/*.infra.test.ts', '**/node_modules'],
+ globals: true,
+ reporters: ['verbose'],
+ root: './',
+ setupFiles: './tests/vitest/setup.helper.test.ts',
+ testTimeout: 5000,
+ watch: true,
+ },
+});