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 @@ [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=coverage)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=internxt_node-win&metric=bugs)](https://sonarcloud.io/summary/new_code?id=internxt_node-win) +## Core + +[![node](https://img.shields.io/badge/node-18-iron)](https://nodejs.org/download/release/latest-iron/) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=coverage)](https://sonarcloud.io/summary/new_code?id=internxt_drive-desktop-core) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=internxt_drive-desktop-core&metric=bugs)](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 ( +
+ {renderContent()} + +
+ ); +} 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 */} + + ); +} 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')}

+
+ +
+ + + + {segmentDetails.length > 0 && ( + <> + { + segmentDetails.reduce( + (acc, segment, index) => { + const radius = 80; + const semiCircumference = Math.PI * radius; + const strokeLength = (segment.percentage / 100) * semiCircumference; + const strokeDasharray = `${strokeLength} ${semiCircumference}`; + const strokeDashoffset = -acc.offset; + + acc.elements.push( + , + ); + + acc.offset += strokeLength; + return acc; + }, + { elements: [] as React.ReactNode[], offset: 0 }, + ).elements + } + + )} + + +
+
{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 ( +
+
+
+
+

{progress}%

+
+ ); +} 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)} + /> + + +
+
+ ); +} 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, + }, +});