diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..22d112b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + pull_request: + branches: [master] + +jobs: + ci: + name: Lint, Test & Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Test + run: pnpm test + + - name: Build + run: pnpm build diff --git a/README.md b/README.md index c0f54c2..27185c0 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ Requires Node.js 18 or later. ## Environment Variables -| Variable | Default | Description | -|---|---|---| -| `INSIGHTA_API_URL` | `http://localhost:3000` | Backend base URL | -| `GITHUB_CLIENT_ID` | — | **Required.** GitHub OAuth app client ID for the CLI | -| `INSIGHTA_CALLBACK_PORT` | `9876` | Local port for the OAuth callback server | +| Variable | Default | Description | +| ------------------------ | ----------------------- | ---------------------------------------------------- | +| `INSIGHTA_API_URL` | `http://localhost:3000` | Backend base URL | +| `GITHUB_CLIENT_ID` | — | **Required.** GitHub OAuth app client ID for the CLI | +| `INSIGHTA_CALLBACK_PORT` | `9876` | Local port for the OAuth callback server | Set these in your shell profile or a `.env` file before running the CLI. @@ -111,10 +111,10 @@ If the browser flow is not completed within 5 minutes, the CLI times out and exi The backend enforces all role restrictions. The CLI surfaces permission errors clearly. -| Role | Permitted commands | -|---|---| +| Role | Permitted commands | +| --------- | ----------------------------------------------------- | | `analyst` | `list`, `get`, `search`, `export`, `whoami`, `logout` | -| `admin` | All analyst commands + `create`, `delete` | +| `admin` | All analyst commands + `create`, `delete` | When a 403 is received, the error message includes the user's current role. The CLI never enforces roles locally. @@ -177,27 +177,27 @@ insighta profiles --help ## `profiles list` Options -| Flag | Type | Description | -|---|---|---| -| `--gender` | `male` \| `female` | Filter by gender | -| `--country` | string | ISO 3166-1 alpha-2 country code (e.g. `NG`, `US`) | -| `--age-group` | `child` \| `teenager` \| `adult` \| `senior` | Filter by age group | -| `--min-age` | number | Minimum age inclusive | -| `--max-age` | number | Maximum age inclusive | -| `--sort-by` | `age` \| `created_at` \| `gender_probability` | Sort field | -| `--order` | `asc` \| `desc` | Sort direction | -| `--page` | number | Page number (default: 1) | -| `--limit` | number | Results per page (default: 10, max: 50) | +| Flag | Type | Description | +| ------------- | --------------------------------------------- | ------------------------------------------------- | +| `--gender` | `male` \| `female` | Filter by gender | +| `--country` | string | ISO 3166-1 alpha-2 country code (e.g. `NG`, `US`) | +| `--age-group` | `child` \| `teenager` \| `adult` \| `senior` | Filter by age group | +| `--min-age` | number | Minimum age inclusive | +| `--max-age` | number | Maximum age inclusive | +| `--sort-by` | `age` \| `created_at` \| `gender_probability` | Sort field | +| `--order` | `asc` \| `desc` | Sort direction | +| `--page` | number | Page number (default: 1) | +| `--limit` | number | Results per page (default: 10, max: 50) | --- ## `profiles export` Options -| Flag | Type | Description | -|---|---|---| -| `--format` | `csv` | **Required.** Export format | -| `--gender` | `male` \| `female` | Filter by gender | -| `--country` | string | ISO 3166-1 alpha-2 country code | +| Flag | Type | Description | +| ----------- | ------------------ | ------------------------------- | +| `--format` | `csv` | **Required.** Export format | +| `--gender` | `male` \| `female` | Filter by gender | +| `--country` | string | ISO 3166-1 alpha-2 country code | The CSV file is saved to the current working directory with a timestamped filename. @@ -205,15 +205,15 @@ The CSV file is saved to the current working directory with a timestamped filena ## Error Handling -| Error | Cause | CLI output | -|---|---|---| -| `NotLoggedInError` | No credentials file | `Not logged in. Run insighta login to authenticate.` | -| `ForbiddenError` | 403 from backend | `Permission denied: ... Your current role is analyst.` | -| `NotFoundError` | 404 from backend | `No profile found with ID .` | -| `ValidationError` | 422 from backend | `Validation failed: ` | -| `RateLimitError` | 429 from backend | `Rate limited. Please wait N seconds before retrying.` | -| `NetworkError` | Fetch failed | `Network error: failed —
` | -| `TimeoutError` | OAuth not completed in 5 min | `Login timed out.` | +| Error | Cause | CLI output | +| ------------------ | ---------------------------- | ------------------------------------------------------ | +| `NotLoggedInError` | No credentials file | `Not logged in. Run insighta login to authenticate.` | +| `ForbiddenError` | 403 from backend | `Permission denied: ... Your current role is analyst.` | +| `NotFoundError` | 404 from backend | `No profile found with ID .` | +| `ValidationError` | 422 from backend | `Validation failed: ` | +| `RateLimitError` | 429 from backend | `Rate limited. Please wait N seconds before retrying.` | +| `NetworkError` | Fetch failed | `Network error: failed —
` | +| `TimeoutError` | OAuth not completed in 5 min | `Login timed out.` | --- diff --git a/bin/insighta.ts b/bin/insighta.ts index ff9e989..0142600 100644 --- a/bin/insighta.ts +++ b/bin/insighta.ts @@ -3,8 +3,8 @@ // Global error handler — must be registered before any async work. // In production: print only the message, no stack trace (Requirement 8.3). // In development: print the full error with stack. -process.on('uncaughtException', (err: Error) => { - if (process.env.NODE_ENV === 'development') { +process.on("uncaughtException", (err: Error) => { + if (process.env.NODE_ENV === "development") { console.error(err); } else { console.error(`Error: ${err.message}`); @@ -12,9 +12,9 @@ process.on('uncaughtException', (err: Error) => { process.exit(1); }); -process.on('unhandledRejection', (reason: unknown) => { +process.on("unhandledRejection", (reason: unknown) => { const err = reason instanceof Error ? reason : new Error(String(reason)); - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === "development") { console.error(err); } else { console.error(`Error: ${err.message}`); @@ -22,102 +22,111 @@ process.on('unhandledRejection', (reason: unknown) => { process.exit(1); }); -import { Command } from 'commander'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -import { login } from '../src/commands/login'; -import { logout } from '../src/commands/logout'; -import { whoami } from '../src/commands/whoami'; -import { listProfiles, type ListProfilesOptions } from '../src/commands/profiles/list'; -import { getProfile } from '../src/commands/profiles/get'; -import { createProfile } from '../src/commands/profiles/create'; -import { deleteProfile } from '../src/commands/profiles/delete'; -import { searchProfiles } from '../src/commands/profiles/search'; -import { exportProfiles, type ExportProfilesOptions } from '../src/commands/profiles/export'; +import { Command } from "commander"; +import { readFileSync } from "fs"; +import { join } from "path"; + +import { login } from "../src/commands/login"; +import { logout } from "../src/commands/logout"; +import { whoami } from "../src/commands/whoami"; +import { + listProfiles, + type ListProfilesOptions, +} from "../src/commands/profiles/list"; +import { getProfile } from "../src/commands/profiles/get"; +import { createProfile } from "../src/commands/profiles/create"; +import { deleteProfile } from "../src/commands/profiles/delete"; +import { searchProfiles } from "../src/commands/profiles/search"; +import { + exportProfiles, + type ExportProfilesOptions, +} from "../src/commands/profiles/export"; // Read version from package.json (dist/bin/ → ../../package.json) const pkg = JSON.parse( - readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8') + readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"), ) as { version: string }; const program = new Command(); program - .name('insighta') - .description('CLI for the Insighta Labs+ platform') - .version(pkg.version, '-v, --version'); + .name("insighta") + .description("CLI for the Insighta Labs+ platform") + .version(pkg.version, "-v, --version"); // ── Auth commands ──────────────────────────────────────────────────────────── program - .command('login') - .description('Log in with your GitHub account') + .command("login") + .description("Log in with your GitHub account") .action(login); program - .command('logout') - .description('Log out and revoke your session') + .command("logout") + .description("Log out and revoke your session") .action(logout); program - .command('whoami') - .description('Show the currently authenticated user') + .command("whoami") + .description("Show the currently authenticated user") .action(whoami); // ── Profiles subcommands ───────────────────────────────────────────────────── -const profiles = program - .command('profiles') - .description('Manage profiles'); +const profiles = program.command("profiles").description("Manage profiles"); profiles - .command('list') - .description('List profiles with optional filters') - .option('--gender ', 'Filter by gender (male|female)') - .option('--country ', 'Filter by country code (e.g. NG)') - .option('--age-group ', 'Filter by age group (adult|child|teenager|senior)') - .option('--min-age ', 'Filter by minimum age') - .option('--max-age ', 'Filter by maximum age') - .option('--sort-by ', 'Sort results by field (e.g. age)') - .option('--order ', 'Sort order (asc or desc)') - .option('--page ', 'Page number (positive integer)') - .option('--limit ', 'Results per page (positive integer)') + .command("list") + .description("List profiles with optional filters") + .option("--gender ", "Filter by gender (male|female)") + .option("--country ", "Filter by country code (e.g. NG)") + .option( + "--age-group ", + "Filter by age group (adult|child|teenager|senior)", + ) + .option("--min-age ", "Filter by minimum age") + .option("--max-age ", "Filter by maximum age") + .option("--sort-by ", "Sort results by field (e.g. age)") + .option("--order ", "Sort order (asc or desc)") + .option("--page ", "Page number (positive integer)") + .option("--limit ", "Results per page (positive integer)") .action((opts: ListProfilesOptions) => listProfiles(opts)); profiles - .command('get ') - .description('Get a profile by ID') + .command("get ") + .description("Get a profile by ID") .action((id: string) => getProfile(id)); profiles - .command('create') - .description('Create a new profile (admin only)') - .requiredOption('--name ', 'Name of the profile to create') + .command("create") + .description("Create a new profile (admin only)") + .requiredOption("--name ", "Name of the profile to create") .action((opts: { name: string }) => createProfile(opts)); profiles - .command('delete ') - .description('Delete a profile by ID (admin only)') + .command("delete ") + .description("Delete a profile by ID (admin only)") .action((id: string) => deleteProfile(id)); profiles - .command('search ') - .description('Search profiles using natural language') + .command("search ") + .description("Search profiles using natural language") .action((query: string) => searchProfiles(query)); profiles - .command('export') - .description('Export profiles to a file') - .requiredOption('--format ', 'Export format (csv)') - .option('--gender ', 'Filter by gender (male|female)') - .option('--country ', 'Filter by country code (e.g. NG)') + .command("export") + .description("Export profiles to a file") + .requiredOption("--format ", "Export format (csv)") + .option("--gender ", "Filter by gender (male|female)") + .option("--country ", "Filter by country code (e.g. NG)") .action((opts: ExportProfilesOptions) => exportProfiles(opts)); // ── Unrecognized commands ──────────────────────────────────────────────────── -program.on('command:*', (operands: string[]) => { - console.error(`Unknown command: \`${operands[0]}\`. Run \`insighta --help\` for available commands.`); +program.on("command:*", (operands: string[]) => { + console.error( + `Unknown command: \`${operands[0]}\`. Run \`insighta --help\` for available commands.`, + ); process.exit(1); }); diff --git a/eslint.config.js b/eslint.config.js index 60f5e1e..23e1de9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,23 +1,44 @@ -import tsParser from '@typescript-eslint/parser'; -import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from "@typescript-eslint/parser"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; + +const sharedRules = { + ...tsPlugin.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "warn", +}; export default [ + // Source files — use strict tsconfig (excludes test files) { - files: ['src/**/*.ts', 'bin/**/*.ts'], + files: ["src/**/*.ts", "bin/**/*.ts"], + ignores: ["src/**/*.test.ts", "src/**/*.spec.ts"], languageOptions: { parser: tsParser, parserOptions: { - project: './tsconfig.json', + project: "./tsconfig.json", }, }, plugins: { - '@typescript-eslint': tsPlugin, + "@typescript-eslint": tsPlugin, }, - rules: { - ...tsPlugin.configs.recommended.rules, - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'warn', + rules: sharedRules, + }, + // Test files — use tsconfig.test.json which includes test files + { + files: ["src/**/*.test.ts", "src/**/*.spec.ts"], + languageOptions: { + parser: tsParser, + parserOptions: { + project: "./tsconfig.test.json", + }, + }, + plugins: { + "@typescript-eslint": tsPlugin, }, + rules: sharedRules, }, ]; diff --git a/package.json b/package.json index c23dbc0..c66bd37 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,24 @@ "scripts": { "build": "tsc", "test": "vitest run", - "lint": "eslint src --ext .ts" + "lint": "eslint src --ext .ts", + "format": "prettier --write ." }, - "keywords": ["insighta", "cli"], + "keywords": [ + "insighta", + "cli" + ], "author": "", "license": "ISC", + "type": "module", "packageManager": "pnpm@10.24.0", "engines": { "node": ">=18" }, "dependencies": { "commander": "^12.1.0", - "open": "^10.1.0" + "open": "^10.1.0", + "prettier": "^3.8.3" }, "devDependencies": { "@fast-check/vitest": "^0.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51893ce..a12a4f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,11 +1,10 @@ -lockfileVersion: '9.0' +lockfileVersion: "9.0" settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: - .: dependencies: commander: @@ -14,17 +13,20 @@ importers: open: specifier: ^10.1.0 version: 10.2.0 + prettier: + specifier: ^3.8.3 + version: 3.8.3 devDependencies: - '@fast-check/vitest': + "@fast-check/vitest": specifier: ^0.1.5 version: 0.1.6(vitest@2.1.9(@types/node@20.19.39)) - '@types/node': + "@types/node": specifier: ^20.14.0 version: 20.19.39 - '@typescript-eslint/eslint-plugin': + "@typescript-eslint/eslint-plugin": specifier: ^8.0.0 version: 8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/parser': + "@typescript-eslint/parser": specifier: ^8.0.0 version: 8.59.1(eslint@9.39.4)(typescript@5.9.3) eslint: @@ -41,409 +43,645 @@ importers: version: 2.1.9(@types/node@20.19.39) packages: - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + "@esbuild/aix-ppc64@0.21.5": + resolution: + { + integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, + } + engines: { node: ">=12" } cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + "@esbuild/android-arm64@0.21.5": + resolution: + { + integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, + } + engines: { node: ">=12" } cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + "@esbuild/android-arm@0.21.5": + resolution: + { + integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, + } + engines: { node: ">=12" } cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + "@esbuild/android-x64@0.21.5": + resolution: + { + integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, + } + engines: { node: ">=12" } cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + "@esbuild/darwin-arm64@0.21.5": + resolution: + { + integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, + } + engines: { node: ">=12" } cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + "@esbuild/darwin-x64@0.21.5": + resolution: + { + integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, + } + engines: { node: ">=12" } cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + "@esbuild/freebsd-arm64@0.21.5": + resolution: + { + integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, + } + engines: { node: ">=12" } cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + "@esbuild/freebsd-x64@0.21.5": + resolution: + { + integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, + } + engines: { node: ">=12" } cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + "@esbuild/linux-arm64@0.21.5": + resolution: + { + integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, + } + engines: { node: ">=12" } cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + "@esbuild/linux-arm@0.21.5": + resolution: + { + integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, + } + engines: { node: ">=12" } cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + "@esbuild/linux-ia32@0.21.5": + resolution: + { + integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, + } + engines: { node: ">=12" } cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + "@esbuild/linux-loong64@0.21.5": + resolution: + { + integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, + } + engines: { node: ">=12" } cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + "@esbuild/linux-mips64el@0.21.5": + resolution: + { + integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, + } + engines: { node: ">=12" } cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + "@esbuild/linux-ppc64@0.21.5": + resolution: + { + integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, + } + engines: { node: ">=12" } cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + "@esbuild/linux-riscv64@0.21.5": + resolution: + { + integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, + } + engines: { node: ">=12" } cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + "@esbuild/linux-s390x@0.21.5": + resolution: + { + integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, + } + engines: { node: ">=12" } cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + "@esbuild/linux-x64@0.21.5": + resolution: + { + integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, + } + engines: { node: ">=12" } cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + "@esbuild/netbsd-x64@0.21.5": + resolution: + { + integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, + } + engines: { node: ">=12" } cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + "@esbuild/openbsd-x64@0.21.5": + resolution: + { + integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, + } + engines: { node: ">=12" } cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + "@esbuild/sunos-x64@0.21.5": + resolution: + { + integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, + } + engines: { node: ">=12" } cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + "@esbuild/win32-arm64@0.21.5": + resolution: + { + integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, + } + engines: { node: ">=12" } cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + "@esbuild/win32-ia32@0.21.5": + resolution: + { + integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, + } + engines: { node: ">=12" } cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + "@esbuild/win32-x64@0.21.5": + resolution: + { + integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, + } + engines: { node: ">=12" } cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + "@eslint-community/eslint-utils@4.9.1": + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@fast-check/vitest@0.1.6': - resolution: {integrity: sha512-/POA3YDsja15aIoXNqQ/vJrm0x7qTRPjL7oDTMLJcOYGqEBjlrmHTKWyRzxgSNa7xyh5B3q8Jib9ALObtMJiEA==} + "@eslint-community/regexpp@4.12.2": + resolution: + { + integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.21.2": + resolution: + { + integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/config-helpers@0.4.2": + resolution: + { + integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.17.0": + resolution: + { + integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/eslintrc@3.3.5": + resolution: + { + integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@9.39.4": + resolution: + { + integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.7": + resolution: + { + integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.4.1": + resolution: + { + integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@fast-check/vitest@0.1.6": + resolution: + { + integrity: sha512-/POA3YDsja15aIoXNqQ/vJrm0x7qTRPjL7oDTMLJcOYGqEBjlrmHTKWyRzxgSNa7xyh5B3q8Jib9ALObtMJiEA==, + } peerDependencies: - vitest: '>=0.28.1 <1.0.0 || ^1 || ^2 || ^3' - - '@humanfs/core@0.19.2': - resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.8': - resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} - engines: {node: '>=18.18.0'} - - '@humanfs/types@0.15.0': - resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@rollup/rollup-android-arm-eabi@4.60.2': - resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + vitest: ">=0.28.1 <1.0.0 || ^1 || ^2 || ^3" + + "@humanfs/core@0.19.2": + resolution: + { + integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.8": + resolution: + { + integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/types@0.15.0": + resolution: + { + integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==, + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: ">=18.18" } + + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + "@rollup/rollup-android-arm-eabi@4.60.2": + resolution: + { + integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==, + } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.2': - resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + "@rollup/rollup-android-arm64@4.60.2": + resolution: + { + integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==, + } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.2': - resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + "@rollup/rollup-darwin-arm64@4.60.2": + resolution: + { + integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==, + } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.2': - resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + "@rollup/rollup-darwin-x64@4.60.2": + resolution: + { + integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==, + } cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.2': - resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + "@rollup/rollup-freebsd-arm64@4.60.2": + resolution: + { + integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==, + } cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.2': - resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + "@rollup/rollup-freebsd-x64@4.60.2": + resolution: + { + integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==, + } cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + "@rollup/rollup-linux-arm-gnueabihf@4.60.2": + resolution: + { + integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + "@rollup/rollup-linux-arm-musleabihf@4.60.2": + resolution: + { + integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.60.2': - resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + "@rollup/rollup-linux-arm64-gnu@4.60.2": + resolution: + { + integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.2': - resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + "@rollup/rollup-linux-arm64-musl@4.60.2": + resolution: + { + integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.60.2': - resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + "@rollup/rollup-linux-loong64-gnu@4.60.2": + resolution: + { + integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==, + } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.60.2': - resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + "@rollup/rollup-linux-loong64-musl@4.60.2": + resolution: + { + integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==, + } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + "@rollup/rollup-linux-ppc64-gnu@4.60.2": + resolution: + { + integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==, + } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.60.2': - resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + "@rollup/rollup-linux-ppc64-musl@4.60.2": + resolution: + { + integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==, + } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + "@rollup/rollup-linux-riscv64-gnu@4.60.2": + resolution: + { + integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==, + } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.60.2': - resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + "@rollup/rollup-linux-riscv64-musl@4.60.2": + resolution: + { + integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==, + } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.60.2': - resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + "@rollup/rollup-linux-s390x-gnu@4.60.2": + resolution: + { + integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==, + } cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.2': - resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + "@rollup/rollup-linux-x64-gnu@4.60.2": + resolution: + { + integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==, + } cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.2': - resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + "@rollup/rollup-linux-x64-musl@4.60.2": + resolution: + { + integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==, + } cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.60.2': - resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + "@rollup/rollup-openbsd-x64@4.60.2": + resolution: + { + integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==, + } cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.2': - resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + "@rollup/rollup-openharmony-arm64@4.60.2": + resolution: + { + integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==, + } cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.2': - resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + "@rollup/rollup-win32-arm64-msvc@4.60.2": + resolution: + { + integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==, + } cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.2': - resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + "@rollup/rollup-win32-ia32-msvc@4.60.2": + resolution: + { + integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==, + } cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.2': - resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + "@rollup/rollup-win32-x64-gnu@4.60.2": + resolution: + { + integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==, + } cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.2': - resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + "@rollup/rollup-win32-x64-msvc@4.60.2": + resolution: + { + integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==, + } cpu: [x64] os: [win32] - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@20.19.39': - resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - - '@typescript-eslint/eslint-plugin@8.59.1': - resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/node@20.19.39": + resolution: + { + integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==, + } + + "@typescript-eslint/eslint-plugin@8.59.1": + resolution: + { + integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - '@typescript-eslint/parser': ^8.59.1 + "@typescript-eslint/parser": ^8.59.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/parser@8.59.1': - resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/parser@8.59.1": + resolution: + { + integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/project-service@8.59.1': - resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/project-service@8.59.1": + resolution: + { + integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/scope-manager@8.59.1': - resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.59.1': - resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/scope-manager@8.59.1": + resolution: + { + integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.59.1": + resolution: + { + integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/type-utils@8.59.1': - resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/type-utils@8.59.1": + resolution: + { + integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/types@8.59.1': - resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.59.1': - resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/types@8.59.1": + resolution: + { + integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.59.1": + resolution: + { + integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.59.1': - resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/utils@8.59.1": + resolution: + { + integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/visitor-keys@8.59.1': - resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} - - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/visitor-keys@8.59.1": + resolution: + { + integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@vitest/expect@2.1.9": + resolution: + { + integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==, + } + + "@vitest/mocker@2.1.9": + resolution: + { + integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==, + } peerDependencies: msw: ^2.4.9 vite: ^5.0.0 @@ -453,210 +691,366 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} - - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} - - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + "@vitest/pretty-format@2.1.9": + resolution: + { + integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==, + } + + "@vitest/runner@2.1.9": + resolution: + { + integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==, + } + + "@vitest/snapshot@2.1.9": + resolution: + { + integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==, + } + + "@vitest/spy@2.1.9": + resolution: + { + integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==, + } + + "@vitest/utils@2.1.9": + resolution: + { + integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==, + } acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} + resolution: + { + integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==, + } + engines: { node: ">=0.4.0" } hasBin: true ajv@6.15.0: - resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + resolution: + { + integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, + } ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: ">=12" } balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, + } + engines: { node: 18 || 20 || >=22 } brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + resolution: + { + integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==, + } brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==, + } + engines: { node: 18 || 20 || >=22 } bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==, + } + engines: { node: ">=18" } cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: ">=8" } callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: ">=6" } chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, + } + engines: { node: ">=18" } chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} + resolution: + { + integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==, + } + engines: { node: ">= 16" } color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==, + } + engines: { node: ">=18" } concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: ">=6.0" } peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: ">=6" } deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==, + } + engines: { node: ">=18" } default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==, + } + engines: { node: ">=18" } define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, + } + engines: { node: ">=12" } es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==, + } + engines: { node: ">=12" } hasBin: true escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: ">=10" } eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + resolution: + { + integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { + integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } hasBin: true peerDependencies: - jiti: '*' + jiti: "*" peerDependenciesMeta: jiti: optional: true espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} + resolution: + { + integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, + } + engines: { node: ">=0.10" } esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: ">=4.0" } estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: ">=4.0" } estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: ">=0.10.0" } expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} + resolution: + { + integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==, + } + engines: { node: ">=12.0.0" } fast-check@3.23.2: - resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} - engines: {node: '>=8.0.0'} + resolution: + { + integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==, + } + engines: { node: ">=8.0.0" } fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: ">=12.0.0" } peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -664,296 +1058,523 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: ">=16.0.0" } find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: ">=10" } flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: ">=16" } flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + resolution: + { + integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==, + } fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: ">=10.13.0" } globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: ">=18" } has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: ">= 4" } ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: ">= 4" } import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: ">=6" } imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: ">=0.8.19" } is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } hasBin: true is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: ">=0.10.0" } is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: ">=0.10.0" } is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, + } + engines: { node: ">=14.16" } hasBin: true is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, + } + engines: { node: ">=16" } isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } hasBin: true json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: ">= 0.8.0" } locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: ">=10" } lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + resolution: + { + integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, + } magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + resolution: + { + integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, + } minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} + resolution: + { + integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==, + } + engines: { node: 18 || 20 || >=22 } minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + resolution: + { + integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, + } ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==, + } + engines: { node: ">=18" } optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: ">= 0.8.0" } p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: ">=10" } p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: ">=10" } parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: ">=6" } path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: ">=8" } path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: ">=8" } pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + resolution: + { + integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==, + } pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, + } + engines: { node: ">= 14.16" } picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==, + } + engines: { node: ">=12" } postcss@8.5.12: - resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} - engines: {node: ^10 || ^12 || >=14} + resolution: + { + integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==, + } + engines: { node: ^10 || ^12 || >=14 } prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: ">= 0.8.0" } + + prettier@3.8.3: + resolution: + { + integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==, + } + engines: { node: ">=14" } + hasBin: true punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + resolution: + { + integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==, + } resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: ">=4" } rollup@4.60.2: - resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + resolution: + { + integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==, + } + engines: { node: ">=18" } semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: ">=10" } hasBin: true shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + resolution: + { + integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, + } strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: ">=8" } supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} + resolution: + { + integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==, + } + engines: { node: ">=12.0.0" } tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} + resolution: + { + integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==, + } + engines: { node: ">=14.0.0" } tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} + resolution: + { + integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==, + } + engines: { node: ">=14.0.0" } ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} + resolution: + { + integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, + } + engines: { node: ">=18.12" } peerDependencies: - typescript: '>=4.8.4' + typescript: ">=4.8.4" type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: ">= 0.8.0" } typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} + resolution: + { + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, + } + engines: { node: ">=14.17" } hasBin: true undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==, + } + engines: { node: ^18.0.0 || >=20.0.0 } hasBin: true vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==, + } + engines: { node: ^18.0.0 || >=20.0.0 } hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" terser: ^5.4.0 peerDependenciesMeta: - '@types/node': + "@types/node": optional: true less: optional: true @@ -971,24 +1592,27 @@ packages: optional: true vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==, + } + engines: { node: ^18.0.0 || >=20.0.0 } hasBin: true peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 - happy-dom: '*' - jsdom: '*' + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.1.9 + "@vitest/ui": 2.1.9 + happy-dom: "*" + jsdom: "*" peerDependenciesMeta: - '@edge-runtime/vm': + "@edge-runtime/vm": optional: true - '@types/node': + "@types/node": optional: true - '@vitest/browser': + "@vitest/browser": optional: true - '@vitest/ui': + "@vitest/ui": optional: true happy-dom: optional: true @@ -996,122 +1620,136 @@ packages: optional: true which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } hasBin: true why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: ">=8" } hasBin: true word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: ">=0.10.0" } wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==, + } + engines: { node: ">=18" } yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: ">=10" } snapshots: - - '@esbuild/aix-ppc64@0.21.5': + "@esbuild/aix-ppc64@0.21.5": optional: true - '@esbuild/android-arm64@0.21.5': + "@esbuild/android-arm64@0.21.5": optional: true - '@esbuild/android-arm@0.21.5': + "@esbuild/android-arm@0.21.5": optional: true - '@esbuild/android-x64@0.21.5': + "@esbuild/android-x64@0.21.5": optional: true - '@esbuild/darwin-arm64@0.21.5': + "@esbuild/darwin-arm64@0.21.5": optional: true - '@esbuild/darwin-x64@0.21.5': + "@esbuild/darwin-x64@0.21.5": optional: true - '@esbuild/freebsd-arm64@0.21.5': + "@esbuild/freebsd-arm64@0.21.5": optional: true - '@esbuild/freebsd-x64@0.21.5': + "@esbuild/freebsd-x64@0.21.5": optional: true - '@esbuild/linux-arm64@0.21.5': + "@esbuild/linux-arm64@0.21.5": optional: true - '@esbuild/linux-arm@0.21.5': + "@esbuild/linux-arm@0.21.5": optional: true - '@esbuild/linux-ia32@0.21.5': + "@esbuild/linux-ia32@0.21.5": optional: true - '@esbuild/linux-loong64@0.21.5': + "@esbuild/linux-loong64@0.21.5": optional: true - '@esbuild/linux-mips64el@0.21.5': + "@esbuild/linux-mips64el@0.21.5": optional: true - '@esbuild/linux-ppc64@0.21.5': + "@esbuild/linux-ppc64@0.21.5": optional: true - '@esbuild/linux-riscv64@0.21.5': + "@esbuild/linux-riscv64@0.21.5": optional: true - '@esbuild/linux-s390x@0.21.5': + "@esbuild/linux-s390x@0.21.5": optional: true - '@esbuild/linux-x64@0.21.5': + "@esbuild/linux-x64@0.21.5": optional: true - '@esbuild/netbsd-x64@0.21.5': + "@esbuild/netbsd-x64@0.21.5": optional: true - '@esbuild/openbsd-x64@0.21.5': + "@esbuild/openbsd-x64@0.21.5": optional: true - '@esbuild/sunos-x64@0.21.5': + "@esbuild/sunos-x64@0.21.5": optional: true - '@esbuild/win32-arm64@0.21.5': + "@esbuild/win32-arm64@0.21.5": optional: true - '@esbuild/win32-ia32@0.21.5': + "@esbuild/win32-ia32@0.21.5": optional: true - '@esbuild/win32-x64@0.21.5': + "@esbuild/win32-x64@0.21.5": optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + "@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)": dependencies: eslint: 9.39.4 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.2': {} + "@eslint-community/regexpp@4.12.2": {} - '@eslint/config-array@0.21.2': + "@eslint/config-array@0.21.2": dependencies: - '@eslint/object-schema': 2.1.7 + "@eslint/object-schema": 2.1.7 debug: 4.4.3 minimatch: 3.1.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + "@eslint/config-helpers@0.4.2": dependencies: - '@eslint/core': 0.17.0 + "@eslint/core": 0.17.0 - '@eslint/core@0.17.0': + "@eslint/core@0.17.0": dependencies: - '@types/json-schema': 7.0.15 + "@types/json-schema": 7.0.15 - '@eslint/eslintrc@3.3.5': + "@eslint/eslintrc@3.3.5": dependencies: ajv: 6.15.0 debug: 4.4.3 @@ -1125,129 +1763,129 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.4': {} + "@eslint/js@9.39.4": {} - '@eslint/object-schema@2.1.7': {} + "@eslint/object-schema@2.1.7": {} - '@eslint/plugin-kit@0.4.1': + "@eslint/plugin-kit@0.4.1": dependencies: - '@eslint/core': 0.17.0 + "@eslint/core": 0.17.0 levn: 0.4.1 - '@fast-check/vitest@0.1.6(vitest@2.1.9(@types/node@20.19.39))': + "@fast-check/vitest@0.1.6(vitest@2.1.9(@types/node@20.19.39))": dependencies: fast-check: 3.23.2 vitest: 2.1.9(@types/node@20.19.39) - '@humanfs/core@0.19.2': + "@humanfs/core@0.19.2": dependencies: - '@humanfs/types': 0.15.0 + "@humanfs/types": 0.15.0 - '@humanfs/node@0.16.8': + "@humanfs/node@0.16.8": dependencies: - '@humanfs/core': 0.19.2 - '@humanfs/types': 0.15.0 - '@humanwhocodes/retry': 0.4.3 + "@humanfs/core": 0.19.2 + "@humanfs/types": 0.15.0 + "@humanwhocodes/retry": 0.4.3 - '@humanfs/types@0.15.0': {} + "@humanfs/types@0.15.0": {} - '@humanwhocodes/module-importer@1.0.1': {} + "@humanwhocodes/module-importer@1.0.1": {} - '@humanwhocodes/retry@0.4.3': {} + "@humanwhocodes/retry@0.4.3": {} - '@jridgewell/sourcemap-codec@1.5.5': {} + "@jridgewell/sourcemap-codec@1.5.5": {} - '@rollup/rollup-android-arm-eabi@4.60.2': + "@rollup/rollup-android-arm-eabi@4.60.2": optional: true - '@rollup/rollup-android-arm64@4.60.2': + "@rollup/rollup-android-arm64@4.60.2": optional: true - '@rollup/rollup-darwin-arm64@4.60.2': + "@rollup/rollup-darwin-arm64@4.60.2": optional: true - '@rollup/rollup-darwin-x64@4.60.2': + "@rollup/rollup-darwin-x64@4.60.2": optional: true - '@rollup/rollup-freebsd-arm64@4.60.2': + "@rollup/rollup-freebsd-arm64@4.60.2": optional: true - '@rollup/rollup-freebsd-x64@4.60.2': + "@rollup/rollup-freebsd-x64@4.60.2": optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + "@rollup/rollup-linux-arm-gnueabihf@4.60.2": optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.2': + "@rollup/rollup-linux-arm-musleabihf@4.60.2": optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.2': + "@rollup/rollup-linux-arm64-gnu@4.60.2": optional: true - '@rollup/rollup-linux-arm64-musl@4.60.2': + "@rollup/rollup-linux-arm64-musl@4.60.2": optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.2': + "@rollup/rollup-linux-loong64-gnu@4.60.2": optional: true - '@rollup/rollup-linux-loong64-musl@4.60.2': + "@rollup/rollup-linux-loong64-musl@4.60.2": optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.2': + "@rollup/rollup-linux-ppc64-gnu@4.60.2": optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.2': + "@rollup/rollup-linux-ppc64-musl@4.60.2": optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.2': + "@rollup/rollup-linux-riscv64-gnu@4.60.2": optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.2': + "@rollup/rollup-linux-riscv64-musl@4.60.2": optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.2': + "@rollup/rollup-linux-s390x-gnu@4.60.2": optional: true - '@rollup/rollup-linux-x64-gnu@4.60.2': + "@rollup/rollup-linux-x64-gnu@4.60.2": optional: true - '@rollup/rollup-linux-x64-musl@4.60.2': + "@rollup/rollup-linux-x64-musl@4.60.2": optional: true - '@rollup/rollup-openbsd-x64@4.60.2': + "@rollup/rollup-openbsd-x64@4.60.2": optional: true - '@rollup/rollup-openharmony-arm64@4.60.2': + "@rollup/rollup-openharmony-arm64@4.60.2": optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.2': + "@rollup/rollup-win32-arm64-msvc@4.60.2": optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.2': + "@rollup/rollup-win32-ia32-msvc@4.60.2": optional: true - '@rollup/rollup-win32-x64-gnu@4.60.2': + "@rollup/rollup-win32-x64-gnu@4.60.2": optional: true - '@rollup/rollup-win32-x64-msvc@4.60.2': + "@rollup/rollup-win32-x64-msvc@4.60.2": optional: true - '@types/estree@1.0.8': {} + "@types/estree@1.0.8": {} - '@types/json-schema@7.0.15': {} + "@types/json-schema@7.0.15": {} - '@types/node@20.19.39': + "@types/node@20.19.39": dependencies: undici-types: 6.21.0 - '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + "@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)": dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/type-utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.1 + "@eslint-community/regexpp": 4.12.2 + "@typescript-eslint/parser": 8.59.1(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/scope-manager": 8.59.1 + "@typescript-eslint/type-utils": 8.59.1(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/utils": 8.59.1(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.59.1 eslint: 9.39.4 ignore: 7.0.5 natural-compare: 1.4.0 @@ -1256,41 +1894,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + "@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3)": dependencies: - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.1 + "@typescript-eslint/scope-manager": 8.59.1 + "@typescript-eslint/types": 8.59.1 + "@typescript-eslint/typescript-estree": 8.59.1(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.59.1 debug: 4.4.3 eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.1(typescript@5.9.3)': + "@typescript-eslint/project-service@8.59.1(typescript@5.9.3)": dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 + "@typescript-eslint/tsconfig-utils": 8.59.1(typescript@5.9.3) + "@typescript-eslint/types": 8.59.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.1': + "@typescript-eslint/scope-manager@8.59.1": dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + "@typescript-eslint/types": 8.59.1 + "@typescript-eslint/visitor-keys": 8.59.1 - '@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.9.3)': + "@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.9.3)": dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + "@typescript-eslint/type-utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)": dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/types": 8.59.1 + "@typescript-eslint/typescript-estree": 8.59.1(typescript@5.9.3) + "@typescript-eslint/utils": 8.59.1(eslint@9.39.4)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.4 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -1298,14 +1936,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.1': {} + "@typescript-eslint/types@8.59.1": {} - '@typescript-eslint/typescript-estree@8.59.1(typescript@5.9.3)': + "@typescript-eslint/typescript-estree@8.59.1(typescript@5.9.3)": dependencies: - '@typescript-eslint/project-service': 8.59.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + "@typescript-eslint/project-service": 8.59.1(typescript@5.9.3) + "@typescript-eslint/tsconfig-utils": 8.59.1(typescript@5.9.3) + "@typescript-eslint/types": 8.59.1 + "@typescript-eslint/visitor-keys": 8.59.1 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 @@ -1315,59 +1953,59 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + "@typescript-eslint/utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)": dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) + "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4) + "@typescript-eslint/scope-manager": 8.59.1 + "@typescript-eslint/types": 8.59.1 + "@typescript-eslint/typescript-estree": 8.59.1(typescript@5.9.3) eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.1': + "@typescript-eslint/visitor-keys@8.59.1": dependencies: - '@typescript-eslint/types': 8.59.1 + "@typescript-eslint/types": 8.59.1 eslint-visitor-keys: 5.0.1 - '@vitest/expect@2.1.9': + "@vitest/expect@2.1.9": dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + "@vitest/spy": 2.1.9 + "@vitest/utils": 2.1.9 chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.39))': + "@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.39))": dependencies: - '@vitest/spy': 2.1.9 + "@vitest/spy": 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 5.4.21(@types/node@20.19.39) - '@vitest/pretty-format@2.1.9': + "@vitest/pretty-format@2.1.9": dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.9': + "@vitest/runner@2.1.9": dependencies: - '@vitest/utils': 2.1.9 + "@vitest/utils": 2.1.9 pathe: 1.1.2 - '@vitest/snapshot@2.1.9': + "@vitest/snapshot@2.1.9": dependencies: - '@vitest/pretty-format': 2.1.9 + "@vitest/pretty-format": 2.1.9 magic-string: 0.30.21 pathe: 1.1.2 - '@vitest/spy@2.1.9': + "@vitest/spy@2.1.9": dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.9': + "@vitest/utils@2.1.9": dependencies: - '@vitest/pretty-format': 2.1.9 + "@vitest/pretty-format": 2.1.9 loupe: 3.2.1 tinyrainbow: 1.2.0 @@ -1465,29 +2103,29 @@ snapshots: esbuild@0.21.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + "@esbuild/aix-ppc64": 0.21.5 + "@esbuild/android-arm": 0.21.5 + "@esbuild/android-arm64": 0.21.5 + "@esbuild/android-x64": 0.21.5 + "@esbuild/darwin-arm64": 0.21.5 + "@esbuild/darwin-x64": 0.21.5 + "@esbuild/freebsd-arm64": 0.21.5 + "@esbuild/freebsd-x64": 0.21.5 + "@esbuild/linux-arm": 0.21.5 + "@esbuild/linux-arm64": 0.21.5 + "@esbuild/linux-ia32": 0.21.5 + "@esbuild/linux-loong64": 0.21.5 + "@esbuild/linux-mips64el": 0.21.5 + "@esbuild/linux-ppc64": 0.21.5 + "@esbuild/linux-riscv64": 0.21.5 + "@esbuild/linux-s390x": 0.21.5 + "@esbuild/linux-x64": 0.21.5 + "@esbuild/netbsd-x64": 0.21.5 + "@esbuild/openbsd-x64": 0.21.5 + "@esbuild/sunos-x64": 0.21.5 + "@esbuild/win32-arm64": 0.21.5 + "@esbuild/win32-ia32": 0.21.5 + "@esbuild/win32-x64": 0.21.5 escape-string-regexp@4.0.0: {} @@ -1504,18 +2142,18 @@ snapshots: eslint@9.39.4: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.8 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 + "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.21.2 + "@eslint/config-helpers": 0.4.2 + "@eslint/core": 0.17.0 + "@eslint/eslintrc": 3.3.5 + "@eslint/js": 9.39.4 + "@eslint/plugin-kit": 0.4.1 + "@humanfs/node": 0.16.8 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -1559,7 +2197,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + "@types/estree": 1.0.8 esutils@2.0.3: {} @@ -1664,7 +2302,7 @@ snapshots: magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + "@jridgewell/sourcemap-codec": 1.5.5 minimatch@10.2.5: dependencies: @@ -1728,6 +2366,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.8.3: {} + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -1736,33 +2376,33 @@ snapshots: rollup@4.60.2: dependencies: - '@types/estree': 1.0.8 + "@types/estree": 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.2 - '@rollup/rollup-android-arm64': 4.60.2 - '@rollup/rollup-darwin-arm64': 4.60.2 - '@rollup/rollup-darwin-x64': 4.60.2 - '@rollup/rollup-freebsd-arm64': 4.60.2 - '@rollup/rollup-freebsd-x64': 4.60.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 - '@rollup/rollup-linux-arm-musleabihf': 4.60.2 - '@rollup/rollup-linux-arm64-gnu': 4.60.2 - '@rollup/rollup-linux-arm64-musl': 4.60.2 - '@rollup/rollup-linux-loong64-gnu': 4.60.2 - '@rollup/rollup-linux-loong64-musl': 4.60.2 - '@rollup/rollup-linux-ppc64-gnu': 4.60.2 - '@rollup/rollup-linux-ppc64-musl': 4.60.2 - '@rollup/rollup-linux-riscv64-gnu': 4.60.2 - '@rollup/rollup-linux-riscv64-musl': 4.60.2 - '@rollup/rollup-linux-s390x-gnu': 4.60.2 - '@rollup/rollup-linux-x64-gnu': 4.60.2 - '@rollup/rollup-linux-x64-musl': 4.60.2 - '@rollup/rollup-openbsd-x64': 4.60.2 - '@rollup/rollup-openharmony-arm64': 4.60.2 - '@rollup/rollup-win32-arm64-msvc': 4.60.2 - '@rollup/rollup-win32-ia32-msvc': 4.60.2 - '@rollup/rollup-win32-x64-gnu': 4.60.2 - '@rollup/rollup-win32-x64-msvc': 4.60.2 + "@rollup/rollup-android-arm-eabi": 4.60.2 + "@rollup/rollup-android-arm64": 4.60.2 + "@rollup/rollup-darwin-arm64": 4.60.2 + "@rollup/rollup-darwin-x64": 4.60.2 + "@rollup/rollup-freebsd-arm64": 4.60.2 + "@rollup/rollup-freebsd-x64": 4.60.2 + "@rollup/rollup-linux-arm-gnueabihf": 4.60.2 + "@rollup/rollup-linux-arm-musleabihf": 4.60.2 + "@rollup/rollup-linux-arm64-gnu": 4.60.2 + "@rollup/rollup-linux-arm64-musl": 4.60.2 + "@rollup/rollup-linux-loong64-gnu": 4.60.2 + "@rollup/rollup-linux-loong64-musl": 4.60.2 + "@rollup/rollup-linux-ppc64-gnu": 4.60.2 + "@rollup/rollup-linux-ppc64-musl": 4.60.2 + "@rollup/rollup-linux-riscv64-gnu": 4.60.2 + "@rollup/rollup-linux-riscv64-musl": 4.60.2 + "@rollup/rollup-linux-s390x-gnu": 4.60.2 + "@rollup/rollup-linux-x64-gnu": 4.60.2 + "@rollup/rollup-linux-x64-musl": 4.60.2 + "@rollup/rollup-openbsd-x64": 4.60.2 + "@rollup/rollup-openharmony-arm64": 4.60.2 + "@rollup/rollup-win32-arm64-msvc": 4.60.2 + "@rollup/rollup-win32-ia32-msvc": 4.60.2 + "@rollup/rollup-win32-x64-gnu": 4.60.2 + "@rollup/rollup-win32-x64-msvc": 4.60.2 fsevents: 2.3.3 run-applescript@7.1.0: {} @@ -1828,7 +2468,7 @@ snapshots: pathe: 1.1.2 vite: 5.4.21(@types/node@20.19.39) transitivePeerDependencies: - - '@types/node' + - "@types/node" - less - lightningcss - sass @@ -1844,18 +2484,18 @@ snapshots: postcss: 8.5.12 rollup: 4.60.2 optionalDependencies: - '@types/node': 20.19.39 + "@types/node": 20.19.39 fsevents: 2.3.3 vitest@2.1.9(@types/node@20.19.39): dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.39)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + "@vitest/expect": 2.1.9 + "@vitest/mocker": 2.1.9(vite@5.4.21(@types/node@20.19.39)) + "@vitest/pretty-format": 2.1.9 + "@vitest/runner": 2.1.9 + "@vitest/snapshot": 2.1.9 + "@vitest/spy": 2.1.9 + "@vitest/utils": 2.1.9 chai: 5.3.3 debug: 4.4.3 expect-type: 1.3.0 @@ -1870,7 +2510,7 @@ snapshots: vite-node: 2.1.9(@types/node@20.19.39) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.19.39 + "@types/node": 20.19.39 transitivePeerDependencies: - less - lightningcss diff --git a/src/auth/callback-server.test.ts b/src/auth/callback-server.test.ts index 8c55172..745466d 100644 --- a/src/auth/callback-server.test.ts +++ b/src/auth/callback-server.test.ts @@ -1,17 +1,19 @@ -import { describe, it, expect } from 'vitest'; -import { test } from '@fast-check/vitest'; -import * as fc from 'fast-check'; -import * as http from 'http'; -import { startCallbackServer } from './callback-server'; -import { TimeoutError } from '../errors'; +import { describe, it, expect } from "vitest"; +import { test } from "@fast-check/vitest"; +import * as fc from "fast-check"; +import * as http from "http"; +import { startCallbackServer } from "./callback-server"; +import { TimeoutError } from "../errors"; // Helper: make a GET request and return the status code function httpGet(url: string): Promise { return new Promise((resolve, reject) => { - http.get(url, (res) => { - res.resume(); // drain the response - resolve(res.statusCode ?? 0); - }).on('error', reject); + http + .get(url, (res) => { + res.resume(); // drain the response + resolve(res.statusCode ?? 0); + }) + .on("error", reject); }); } @@ -19,15 +21,15 @@ function httpGet(url: string): Promise { * Property 3: Callback server resolves with code and state from GitHub * Validates: Requirements 2.4 */ -describe('startCallbackServer', () => { +describe("startCallbackServer", () => { test.prop( [ - fc.string({ minLength: 1 }), // code - fc.string({ minLength: 1 }), // state + fc.string({ minLength: 1 }), // code + fc.string({ minLength: 1 }), // state ], - { numRuns: 50 } + { numRuns: 50 }, )( - 'Property 3: resolves with code and state exactly as received in the callback', + "Property 3: resolves with code and state exactly as received in the callback", async (code, state) => { const { port, result } = await startCallbackServer(); @@ -41,31 +43,31 @@ describe('startCallbackServer', () => { expect(callbackResult.code).toBe(code); expect(callbackResult.state).toBe(state); - } + }, ); - it('returns 400 when code is missing from callback', async () => { + it("returns 400 when code is missing from callback", async () => { const { port, result } = await startCallbackServer(5_000); const status = await httpGet(`http://127.0.0.1:${port}/callback?state=abc`); expect(status).toBe(400); result.catch(() => {}); }); - it('returns 400 when state is missing from callback', async () => { + it("returns 400 when state is missing from callback", async () => { const { port, result } = await startCallbackServer(5_000); const status = await httpGet(`http://127.0.0.1:${port}/callback?code=xyz`); expect(status).toBe(400); result.catch(() => {}); }); - it('returns 404 for non-callback paths', async () => { + it("returns 404 for non-callback paths", async () => { const { port, result } = await startCallbackServer(5_000); const status = await httpGet(`http://127.0.0.1:${port}/other`); expect(status).toBe(404); result.catch(() => {}); }); - it('rejects with TimeoutError when the timeout elapses', async () => { + it("rejects with TimeoutError when the timeout elapses", async () => { const { result } = await startCallbackServer(50); await expect(result).rejects.toBeInstanceOf(TimeoutError); }); diff --git a/src/auth/callback-server.ts b/src/auth/callback-server.ts index 5f7b87b..4da741d 100644 --- a/src/auth/callback-server.ts +++ b/src/auth/callback-server.ts @@ -1,6 +1,6 @@ -import * as http from 'http'; -import { URL } from 'url'; -import { TimeoutError } from '../errors'; +import * as http from "http"; +import { URL } from "url"; +import { TimeoutError } from "../errors"; export interface OAuthCallbackResult { code: string; @@ -16,20 +16,23 @@ export interface OAuthCallbackResult { * * The server is always closed on resolve or reject. */ -export function startCallbackServer(timeoutMs = 300_000, fixedPort?: number): Promise<{ +export function startCallbackServer( + timeoutMs = 300_000, + fixedPort?: number, +): Promise<{ port: number; result: Promise; }> { return new Promise((resolveServer, rejectServer) => { const server = http.createServer(); - server.on('error', rejectServer); + server.on("error", rejectServer); - server.listen(fixedPort ?? 0, '127.0.0.1', () => { + server.listen(fixedPort ?? 0, "127.0.0.1", () => { const address = server.address(); - if (!address || typeof address === 'string') { + if (!address || typeof address === "string") { server.close(); - rejectServer(new Error('Failed to bind callback server to a port')); + rejectServer(new Error("Failed to bind callback server to a port")); return; } const port = address.port; @@ -48,33 +51,33 @@ export function startCallbackServer(timeoutMs = 300_000, fixedPort?: number): Pr cleanup(() => reject(new TimeoutError())); }, timeoutMs); - server.on('request', (req, res) => { + server.on("request", (req, res) => { if (settled) { res.writeHead(200); - res.end('OK'); + res.end("OK"); return; } try { - const reqUrl = new URL(req.url ?? '', `http://127.0.0.1:${port}`); + const reqUrl = new URL(req.url ?? "", `http://127.0.0.1:${port}`); - if (reqUrl.pathname !== '/callback') { + if (reqUrl.pathname !== "/callback") { res.writeHead(404); - res.end('Not found'); + res.end("Not found"); return; } - const code = reqUrl.searchParams.get('code'); - const state = reqUrl.searchParams.get('state'); + const code = reqUrl.searchParams.get("code"); + const state = reqUrl.searchParams.get("state"); if (!code || !state) { res.writeHead(400); - res.end('Missing required query parameters: code and state'); + res.end("Missing required query parameters: code and state"); return; } // Send response to browser BEFORE resolving — ensures browser gets the message - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(` Insighta CLI @@ -88,7 +91,7 @@ export function startCallbackServer(timeoutMs = 300_000, fixedPort?: number): Pr cleanup(() => resolve({ code, state })); } catch (err) { res.writeHead(500); - res.end('Internal error'); + res.end("Internal error"); clearTimeout(timer); cleanup(() => reject(err)); } diff --git a/src/auth/credentials.test.ts b/src/auth/credentials.test.ts index b6692e8..1c12655 100644 --- a/src/auth/credentials.test.ts +++ b/src/auth/credentials.test.ts @@ -1,28 +1,30 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { type Credentials, credentialsExist, deleteCredentials, readCredentials, writeCredentials, -} from './credentials'; +} from "./credentials"; -vi.mock('os', async (importOriginal) => { +vi.mock("os", async (importOriginal) => { const actual = await importOriginal(); return { ...actual }; }); const TEST_DIR = path.join(os.tmpdir(), `insighta-creds-test-${process.pid}`); -const TEST_CREDS_PATH = path.join(TEST_DIR, 'credentials.json'); +const _TEST_CREDS_PATH = path.join(TEST_DIR, "credentials.json"); beforeEach(() => { - vi.spyOn(os, 'homedir').mockReturnValue(path.join(TEST_DIR, '..', `home-${Date.now()}`)); + vi.spyOn(os, "homedir").mockReturnValue( + path.join(TEST_DIR, "..", `home-${Date.now()}`), + ); // Use a stable test dir per test const home = os.homedir(); - fs.mkdirSync(path.join(home, '.insighta'), { recursive: true }); + fs.mkdirSync(path.join(home, ".insighta"), { recursive: true }); }); afterEach(() => { @@ -32,56 +34,60 @@ afterEach(() => { }); function getTestCredPath(): string { - return path.join(os.homedir(), '.insighta', 'credentials.json'); + return path.join(os.homedir(), ".insighta", "credentials.json"); } const sampleCreds: Credentials = { - accessToken: 'gho_abc123', - refreshToken: 'ghr_xyz789', + accessToken: "gho_abc123", + refreshToken: "ghr_xyz789", expiresAt: 1720000000000, - username: 'octocat', - role: 'analyst', + username: "octocat", + role: "analyst", }; -describe('credentials store', () => { - it('readCredentials returns null when file does not exist', () => { +describe("credentials store", () => { + it("readCredentials returns null when file does not exist", () => { expect(readCredentials()).toBeNull(); }); - it('credentialsExist returns false when file does not exist', () => { + it("credentialsExist returns false when file does not exist", () => { expect(credentialsExist()).toBe(false); }); - it('writeCredentials creates the file and readCredentials returns the same object', () => { + it("writeCredentials creates the file and readCredentials returns the same object", () => { writeCredentials(sampleCreds); const result = readCredentials(); expect(result).toEqual(sampleCreds); }); - it('writeCredentials sets file permissions to 0600', () => { + it("writeCredentials sets file permissions to 0600", () => { writeCredentials(sampleCreds); const stat = fs.statSync(getTestCredPath()); expect(stat.mode & 0o777).toBe(0o600); }); - it('credentialsExist returns true after writing', () => { + it("credentialsExist returns true after writing", () => { writeCredentials(sampleCreds); expect(credentialsExist()).toBe(true); }); - it('deleteCredentials removes the file', () => { + it("deleteCredentials removes the file", () => { writeCredentials(sampleCreds); deleteCredentials(); expect(credentialsExist()).toBe(false); }); - it('deleteCredentials does not throw when file does not exist', () => { + it("deleteCredentials does not throw when file does not exist", () => { expect(() => deleteCredentials()).not.toThrow(); }); - it('writeCredentials overwrites existing credentials', () => { + it("writeCredentials overwrites existing credentials", () => { writeCredentials(sampleCreds); - const updated: Credentials = { ...sampleCreds, accessToken: 'gho_new', role: 'admin' }; + const updated: Credentials = { + ...sampleCreds, + accessToken: "gho_new", + role: "admin", + }; writeCredentials(updated); expect(readCredentials()).toEqual(updated); }); @@ -89,22 +95,22 @@ describe('credentials store', () => { // Feature: insighta-cli, Property 2: Credentials round-trip preserves all fields // Feature: insighta-cli, Property 3: Credentials file permissions are always 0600 -import fc from 'fast-check'; -import { test } from '@fast-check/vitest'; +import fc from "fast-check"; +import { test } from "@fast-check/vitest"; const credentialsArb = fc.record({ accessToken: fc.string({ minLength: 1 }), refreshToken: fc.string({ minLength: 1 }), expiresAt: fc.integer({ min: 0 }), username: fc.string({ minLength: 1 }), - role: fc.constantFrom('admin' as const, 'analyst' as const), + role: fc.constantFrom("admin" as const, "analyst" as const), }); -describe('property tests', () => { +describe("property tests", () => { // Property 2: Credentials round-trip preserves all fields // Validates: Requirements 1.2 test.prop([credentialsArb], { numRuns: 100 })( - 'round-trip write/read preserves all credential fields', + "round-trip write/read preserves all credential fields", (creds) => { writeCredentials(creds); const result = readCredentials(); @@ -115,7 +121,7 @@ describe('property tests', () => { // Property 3: Credentials file permissions are always 0600 // Validates: Requirements 1.2 test.prop([credentialsArb], { numRuns: 100 })( - 'credentials file permissions are always 0600', + "credentials file permissions are always 0600", (creds) => { writeCredentials(creds); const stat = fs.statSync(getTestCredPath()); diff --git a/src/auth/credentials.ts b/src/auth/credentials.ts index 5032820..b868bf9 100644 --- a/src/auth/credentials.ts +++ b/src/auth/credentials.ts @@ -1,26 +1,26 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; export interface Credentials { accessToken: string; refreshToken: string; expiresAt: number; // Unix timestamp (ms) username: string; - role: 'admin' | 'analyst'; + role: "admin" | "analyst"; } function getCredentialsPath(): string { - return path.join(os.homedir(), '.insighta', 'credentials.json'); + return path.join(os.homedir(), ".insighta", "credentials.json"); } export function readCredentials(): Credentials | null { const credPath = getCredentialsPath(); try { - const raw = fs.readFileSync(credPath, 'utf-8'); + const raw = fs.readFileSync(credPath, "utf-8"); return JSON.parse(raw) as Credentials; } catch (err: unknown) { - if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + if ((err as NodeJS.ErrnoException).code === "ENOENT") { return null; } throw err; @@ -31,7 +31,7 @@ export function writeCredentials(creds: Credentials): void { const credPath = getCredentialsPath(); const dir = path.dirname(credPath); fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), 'utf-8'); + fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), "utf-8"); fs.chmodSync(credPath, 0o600); } @@ -40,7 +40,7 @@ export function deleteCredentials(): void { try { fs.unlinkSync(credPath); } catch (err: unknown) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") { throw err; } } diff --git a/src/auth/pkce.ts b/src/auth/pkce.ts index d0352b8..3677ffb 100644 --- a/src/auth/pkce.ts +++ b/src/auth/pkce.ts @@ -1,4 +1,4 @@ -import crypto from 'crypto'; +import crypto from "crypto"; export interface PKCEParams { /** Cryptographically random hex nonce — used to validate the OAuth callback. */ @@ -14,12 +14,12 @@ export interface PKCEParams { * All values are cryptographically random and single-use. */ export function generatePKCEParams(): PKCEParams { - const state = crypto.randomBytes(16).toString('hex'); - const codeVerifier = crypto.randomBytes(32).toString('base64url'); + const state = crypto.randomBytes(16).toString("hex"); + const codeVerifier = crypto.randomBytes(32).toString("base64url"); const codeChallenge = crypto - .createHash('sha256') + .createHash("sha256") .update(codeVerifier) - .digest('base64url'); + .digest("base64url"); return { state, codeVerifier, codeChallenge }; } @@ -49,7 +49,7 @@ export function getCallbackPort(): number { export function buildGitHubAuthUrl(params: PKCEParams, port: number): string { const clientId = process.env.GITHUB_CLIENT_ID; if (!clientId) { - throw new Error('GITHUB_CLIENT_ID environment variable is not set.'); + throw new Error("GITHUB_CLIENT_ID environment variable is not set."); } const redirectUri = `http://127.0.0.1:${port}/callback`; @@ -57,11 +57,11 @@ export function buildGitHubAuthUrl(params: PKCEParams, port: number): string { const query = new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, - scope: 'read:user user:email', + scope: "read:user user:email", state: params.state, code_challenge: params.codeChallenge, - code_challenge_method: 'S256', - response_type: 'code', + code_challenge_method: "S256", + response_type: "code", }); return `https://github.com/login/oauth/authorize?${query.toString()}`; diff --git a/src/commands/login.ts b/src/commands/login.ts index 2147bc4..a601ee3 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -1,10 +1,14 @@ -import open from 'open'; -import { generatePKCEParams, buildGitHubAuthUrl, getCallbackPort } from '../auth/pkce'; -import { startCallbackServer } from '../auth/callback-server'; -import { writeCredentials } from '../auth/credentials'; -import { TimeoutError } from '../errors'; - -const BASE_URL = process.env.INSIGHTA_API_URL ?? 'http://localhost:3000'; +import open from "open"; +import { + generatePKCEParams, + buildGitHubAuthUrl, + getCallbackPort, +} from "../auth/pkce"; +import { startCallbackServer } from "../auth/callback-server"; +import { writeCredentials } from "../auth/credentials"; +import { TimeoutError } from "../errors"; + +const BASE_URL = process.env.INSIGHTA_API_URL ?? "http://localhost:3000"; interface ExchangeResponse { status: string; @@ -17,7 +21,7 @@ interface MeResponse { status: string; user: { username: string; - role: 'admin' | 'analyst'; + role: "admin" | "analyst"; }; } @@ -31,7 +35,9 @@ export async function login(): Promise { // Step 3: Construct GitHub OAuth URL and open in browser const authUrl = buildGitHubAuthUrl(pkceParams, port); - console.log(`Opening browser for GitHub login... (waiting for callback on port ${port})`); + console.log( + `Opening browser for GitHub login... (waiting for callback on port ${port})`, + ); await open(authUrl); let code: string; @@ -44,7 +50,9 @@ export async function login(): Promise { receivedState = callbackResult.state; } catch (err) { if (err instanceof TimeoutError) { - console.error('Login timed out. The browser flow was not completed within 5 minutes.'); + console.error( + "Login timed out. The browser flow was not completed within 5 minutes.", + ); process.exit(1); } const message = err instanceof Error ? err.message : String(err); @@ -54,35 +62,39 @@ export async function login(): Promise { // Step 5: Validate state to prevent CSRF if (receivedState !== pkceParams.state) { - console.error('Authentication failed: state mismatch. The OAuth callback may have been tampered with.'); + console.error( + "Authentication failed: state mismatch. The OAuth callback may have been tampered with.", + ); process.exit(1); } // Step 6: Exchange code + code_verifier with the backend // Backend CLI branch reads: req.query.code and req.query.state (used as code_verifier) // and requires x-client-type: cli header - const exchangeUrl = new URL('/auth/github/callback', BASE_URL); - exchangeUrl.searchParams.set('code', code); - exchangeUrl.searchParams.set('state', pkceParams.codeVerifier); // backend reads state as code_verifier for CLI + const exchangeUrl = new URL("/auth/github/callback", BASE_URL); + exchangeUrl.searchParams.set("code", code); + exchangeUrl.searchParams.set("state", pkceParams.codeVerifier); // backend reads state as code_verifier for CLI let exchangeData: ExchangeResponse; try { const exchangeRes = await fetch(exchangeUrl.toString(), { - method: 'GET', - headers: { 'x-client-type': 'cli' }, + method: "GET", + headers: { "x-client-type": "cli" }, }); if (!exchangeRes.ok) { let detail = exchangeRes.statusText; try { - const body = await exchangeRes.json() as { message?: string }; + const body = (await exchangeRes.json()) as { message?: string }; if (body.message) detail = body.message; - } catch { /* ignore */ } + } catch { + /* ignore */ + } console.error(`Authentication failed: ${detail}`); process.exit(1); } - exchangeData = await exchangeRes.json() as ExchangeResponse; + exchangeData = (await exchangeRes.json()) as ExchangeResponse; } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error(`Authentication failed: ${message}`); @@ -90,27 +102,35 @@ export async function login(): Promise { } // Step 7: Validate token fields - if (!exchangeData.access_token || !exchangeData.refresh_token || !exchangeData.expires_in) { - console.error('Authentication failed: server response is missing required token fields.'); + if ( + !exchangeData.access_token || + !exchangeData.refresh_token || + !exchangeData.expires_in + ) { + console.error( + "Authentication failed: server response is missing required token fields.", + ); process.exit(1); } // Step 8: Fetch user info with the new access token let meData: MeResponse; try { - const meRes = await fetch(new URL('/auth/me', BASE_URL).toString(), { + const meRes = await fetch(new URL("/auth/me", BASE_URL).toString(), { headers: { Authorization: `Bearer ${exchangeData.access_token}`, - 'x-client-type': 'cli', + "x-client-type": "cli", }, }); if (!meRes.ok) { - console.error('Authentication failed: could not retrieve user information.'); + console.error( + "Authentication failed: could not retrieve user information.", + ); process.exit(1); } - meData = await meRes.json() as MeResponse; + meData = (await meRes.json()) as MeResponse; } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error(`Authentication failed: ${message}`); diff --git a/src/commands/logout.ts b/src/commands/logout.ts index 12a7ba6..1221887 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -1,10 +1,14 @@ -import { credentialsExist, readCredentials, deleteCredentials } from '../auth/credentials'; -import { apiRequest } from '../http/client'; -import { NetworkError } from '../errors'; +import { + credentialsExist, + readCredentials, + deleteCredentials, +} from "../auth/credentials"; +import { apiRequest } from "../http/client"; +import { NetworkError } from "../errors"; export async function logout(): Promise { if (!credentialsExist()) { - console.log('You are already logged out.'); + console.log("You are already logged out."); return; } @@ -13,20 +17,22 @@ export async function logout(): Promise { if (creds) { try { await apiRequest({ - method: 'POST', - path: '/auth/logout', + method: "POST", + path: "/auth/logout", body: { refresh_token: creds.refreshToken }, accessToken: creds.accessToken, - operation: 'POST /auth/logout', + operation: "POST /auth/logout", }); } catch (err: unknown) { if (err instanceof NetworkError) { - console.warn(`Warning: could not reach server to revoke session — ${err.message}`); + console.warn( + `Warning: could not reach server to revoke session — ${err.message}`, + ); } // non-network errors are silently ignored; we still delete local credentials } } deleteCredentials(); - console.log('Logged out successfully.'); + console.log("Logged out successfully."); } diff --git a/src/commands/profiles/create.ts b/src/commands/profiles/create.ts index ff6a104..4255e66 100644 --- a/src/commands/profiles/create.ts +++ b/src/commands/profiles/create.ts @@ -1,10 +1,10 @@ -import { credentialsExist, readCredentials } from '../../auth/credentials'; -import { apiRequest } from '../../http/client'; -import { formatCreateSuccess, type Profile } from '../../output/formatter'; -import { startSpinner } from '../../output/spinner'; +import { credentialsExist, readCredentials } from "../../auth/credentials"; +import { apiRequest } from "../../http/client"; +import { formatCreateSuccess, type Profile } from "../../output/formatter"; +import { startSpinner } from "../../output/spinner"; interface CreateProfileResponse { - status: 'success' | 'error'; + status: "success" | "error"; message?: string; data: Profile; } @@ -18,21 +18,21 @@ interface CreateProfileResponse { */ export async function createProfile(options: { name: string }): Promise { if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } const creds = readCredentials()!; - const spinner = startSpinner('Creating profile...'); + const spinner = startSpinner("Creating profile..."); let response; try { response = await apiRequest({ - method: 'POST', - path: '/api/profiles', + method: "POST", + path: "/api/profiles", body: { name: options.name }, accessToken: creds.accessToken, - operation: 'POST /api/profiles', + operation: "POST /api/profiles", }); } catch (err) { spinner.stop(); @@ -44,7 +44,7 @@ export async function createProfile(options: { name: string }): Promise { const { status: httpStatus, data } = response; if (httpStatus === 200) { - console.log('Profile already exists.'); + console.log("Profile already exists."); } console.log(formatCreateSuccess(data.data)); diff --git a/src/commands/profiles/delete.ts b/src/commands/profiles/delete.ts index ab1b652..982ed7b 100644 --- a/src/commands/profiles/delete.ts +++ b/src/commands/profiles/delete.ts @@ -1,7 +1,7 @@ -import { credentialsExist, readCredentials } from '../../auth/credentials'; -import { apiRequest } from '../../http/client'; -import { formatDeleteConfirmation } from '../../output/formatter'; -import { startSpinner } from '../../output/spinner'; +import { credentialsExist, readCredentials } from "../../auth/credentials"; +import { apiRequest } from "../../http/client"; +import { formatDeleteConfirmation } from "../../output/formatter"; +import { startSpinner } from "../../output/spinner"; /** * Deletes a profile by ID. @@ -12,7 +12,7 @@ import { startSpinner } from '../../output/spinner'; */ export async function deleteProfile(id: string): Promise { if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } @@ -21,7 +21,7 @@ export async function deleteProfile(id: string): Promise { try { await apiRequest({ - method: 'DELETE', + method: "DELETE", path: `/api/profiles/${id}`, accessToken: creds.accessToken, operation: `DELETE /api/profiles/${id}`, diff --git a/src/commands/profiles/export.ts b/src/commands/profiles/export.ts index 573ae6a..1e7788e 100644 --- a/src/commands/profiles/export.ts +++ b/src/commands/profiles/export.ts @@ -1,11 +1,11 @@ -import { credentialsExist, readCredentials } from '../../auth/credentials'; -import { saveExport } from '../../output/exporter'; -import { startSpinner } from '../../output/spinner'; +import { credentialsExist, readCredentials } from "../../auth/credentials"; +import { saveExport } from "../../output/exporter"; +import { startSpinner } from "../../output/spinner"; -const BASE_URL = process.env.INSIGHTA_API_URL ?? 'http://localhost:3000'; +const BASE_URL = process.env.INSIGHTA_API_URL ?? "http://localhost:3000"; -const SUPPORTED_FORMATS = ['csv'] as const; -type ExportFormat = typeof SUPPORTED_FORMATS[number]; +const SUPPORTED_FORMATS = ["csv"] as const; +type ExportFormat = (typeof SUPPORTED_FORMATS)[number]; export interface ExportProfilesOptions { format: string; @@ -21,44 +21,47 @@ export interface ExportProfilesOptions { * The --format flag is required. Only 'csv' is supported. * Optional --gender and --country filters are passed as query params. */ -export async function exportProfiles(options: ExportProfilesOptions): Promise { +export async function exportProfiles( + options: ExportProfilesOptions, +): Promise { // Validate --format before any network I/O if (!options.format) { - console.error('Error: --format is required. Supported formats: csv'); + console.error("Error: --format is required. Supported formats: csv"); process.exit(1); } if (!SUPPORTED_FORMATS.includes(options.format as ExportFormat)) { - console.error(`Error: unsupported format "${options.format}". Supported formats: ${SUPPORTED_FORMATS.join(', ')}`); + console.error( + `Error: unsupported format "${options.format}". Supported formats: ${SUPPORTED_FORMATS.join(", ")}`, + ); process.exit(1); } if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } const creds = readCredentials()!; // Build URL with required format param and optional filter query params - const url = new URL('/api/profiles/export', BASE_URL); - url.searchParams.set('format', options.format); // backend requires this - if (options.gender) url.searchParams.set('gender', options.gender); - if (options.country) url.searchParams.set('country_id', options.country); // backend uses country_id + const url = new URL("/api/profiles/export", BASE_URL); + url.searchParams.set("format", options.format); // backend requires this + if (options.gender) url.searchParams.set("gender", options.gender); + if (options.country) url.searchParams.set("country_id", options.country); // backend uses country_id - const spinner = startSpinner('Fetching export...'); + const spinner = startSpinner("Fetching export..."); let response: Response; try { response = await fetch(url.toString(), { - headers: { + headers: { Authorization: `Bearer ${creds.accessToken}`, - 'x-client-type': 'cli', - 'x-api-version': '1' + "x-client-type": "cli", + "x-api-version": "1", }, }); console.log(response); - } catch (err) { spinner.stop(); const message = err instanceof Error ? err.message : String(err); @@ -68,7 +71,9 @@ export async function exportProfiles(options: ExportProfilesOptions): Promise { if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } @@ -20,7 +20,7 @@ export async function getProfile(id: string): Promise { let response; try { response = await apiRequest({ - method: 'GET', + method: "GET", path: `/api/profiles/${id}`, accessToken: creds.accessToken, operation: `GET /api/profiles/${id}`, diff --git a/src/commands/profiles/list.ts b/src/commands/profiles/list.ts index 6b163b2..f499181 100644 --- a/src/commands/profiles/list.ts +++ b/src/commands/profiles/list.ts @@ -1,11 +1,11 @@ -import { credentialsExist, readCredentials } from '../../auth/credentials'; -import { apiRequest } from '../../http/client'; -import { formatProfileList, type Profile } from '../../output/formatter'; -import { renderPagination } from '../../output/paginator'; -import { startSpinner } from '../../output/spinner'; +import { credentialsExist, readCredentials } from "../../auth/credentials"; +import { apiRequest } from "../../http/client"; +import { formatProfileList, type Profile } from "../../output/formatter"; +import { renderPagination } from "../../output/paginator"; +import { startSpinner } from "../../output/spinner"; interface PaginatedProfilesResponse { - status: 'success'; + status: "success"; data: Profile[]; page: number; limit: number; @@ -34,9 +34,11 @@ function parsePositiveInt(value: string, flag: string): number { return n; } -export async function listProfiles(options: ListProfilesOptions): Promise { +export async function listProfiles( + options: ListProfilesOptions, +): Promise { if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } @@ -44,34 +46,34 @@ export async function listProfiles(options: ListProfilesOptions): Promise const query: Record = {}; if (options.page) { - query.page = parsePositiveInt(options.page, 'page'); + query.page = parsePositiveInt(options.page, "page"); } if (options.limit) { - query.limit = parsePositiveInt(options.limit, 'limit'); + query.limit = parsePositiveInt(options.limit, "limit"); } // Optional filter params - if (options.gender !== undefined) query.gender = options.gender; - if (options.country !== undefined) query.country_id = options.country; + if (options.gender !== undefined) query.gender = options.gender; + if (options.country !== undefined) query.country_id = options.country; if (options.ageGroup !== undefined) query.age_group = options.ageGroup; - if (options.minAge !== undefined) query.min_age = options.minAge; - if (options.maxAge !== undefined) query.max_age = options.maxAge; - if (options.sortBy !== undefined) query.sort_by = options.sortBy; - if (options.order !== undefined) query.order = options.order; + if (options.minAge !== undefined) query.min_age = options.minAge; + if (options.maxAge !== undefined) query.max_age = options.maxAge; + if (options.sortBy !== undefined) query.sort_by = options.sortBy; + if (options.order !== undefined) query.order = options.order; const creds = readCredentials()!; - const spinner = startSpinner('Fetching profiles...'); + const spinner = startSpinner("Fetching profiles..."); let response; try { response = await apiRequest({ - method: 'GET', - path: '/api/profiles', + method: "GET", + path: "/api/profiles", query, accessToken: creds.accessToken, - operation: 'GET /api/profiles', + operation: "GET /api/profiles", }); } catch (err) { spinner.stop(); @@ -85,13 +87,18 @@ export async function listProfiles(options: ListProfilesOptions): Promise const { data, page, limit, total, total_pages } = response.data; if (data.length === 0) { - console.log('No profiles found.'); + console.log("No profiles found."); return; } console.log(formatProfileList(data)); console.log(); - console.log(renderPagination({ - limit, page, total, totalPages: total_pages - })); + console.log( + renderPagination({ + limit, + page, + total, + totalPages: total_pages, + }), + ); } diff --git a/src/commands/profiles/search.ts b/src/commands/profiles/search.ts index aa0db1f..400f699 100644 --- a/src/commands/profiles/search.ts +++ b/src/commands/profiles/search.ts @@ -1,11 +1,11 @@ -import { credentialsExist, readCredentials } from '../../auth/credentials'; -import { apiRequest } from '../../http/client'; -import { formatProfileList, type Profile } from '../../output/formatter'; -import { renderPagination } from '../../output/paginator'; -import { startSpinner } from '../../output/spinner'; +import { credentialsExist, readCredentials } from "../../auth/credentials"; +import { apiRequest } from "../../http/client"; +import { formatProfileList, type Profile } from "../../output/formatter"; +import { renderPagination } from "../../output/paginator"; +import { startSpinner } from "../../output/spinner"; interface SearchProfilesResponse { - status: 'success'; + status: "success"; data: Profile[]; page: number; limit: number; @@ -15,21 +15,21 @@ interface SearchProfilesResponse { export async function searchProfiles(query: string): Promise { if (!credentialsExist()) { - console.error('Not logged in. Run `insighta login` to authenticate.'); + console.error("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } const creds = readCredentials()!; - const spinner = startSpinner('Searching profiles...'); + const spinner = startSpinner("Searching profiles..."); let response; try { response = await apiRequest({ - method: 'GET', - path: '/api/profiles/search', + method: "GET", + path: "/api/profiles/search", query: { q: query }, accessToken: creds.accessToken, - operation: 'GET /api/profiles/search', + operation: "GET /api/profiles/search", }); } catch (err) { spinner.stop(); @@ -41,14 +41,19 @@ export async function searchProfiles(query: string): Promise { const { data, page, limit, total, total_pages } = response.data; if (data.length === 0) { - console.log('No profiles matched your query.'); + console.log("No profiles matched your query."); return; } console.log(formatProfileList(data)); console.log(); - console.log(renderPagination({ - page, limit, total, totalPages: total_pages - })); + console.log( + renderPagination({ + page, + limit, + total, + totalPages: total_pages, + }), + ); console.log(`Total results: ${total}`); } diff --git a/src/commands/whoami.test.ts b/src/commands/whoami.test.ts index b66e57b..827dad4 100644 --- a/src/commands/whoami.test.ts +++ b/src/commands/whoami.test.ts @@ -1,34 +1,36 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { test } from '@fast-check/vitest'; -import * as fc from 'fast-check'; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { test } from "@fast-check/vitest"; +import * as fc from "fast-check"; // Mocks must be declared before importing the module under test -vi.mock('../auth/credentials', () => ({ +vi.mock("../auth/credentials", () => ({ credentialsExist: vi.fn(), readCredentials: vi.fn(), })); -vi.mock('../http/client', () => ({ +vi.mock("../http/client", () => ({ apiRequest: vi.fn(), })); -import { whoami } from './whoami'; -import * as credentials from '../auth/credentials'; -import * as client from '../http/client'; +import { whoami } from "./whoami"; +import * as credentials from "../auth/credentials"; +import * as client from "../http/client"; const mockCredentialsExist = vi.mocked(credentials.credentialsExist); const mockReadCredentials = vi.mocked(credentials.readCredentials); const mockApiRequest = vi.mocked(client.apiRequest); -describe('whoami', () => { +describe("whoami", () => { let consoleLogSpy: ReturnType; let processExitSpy: ReturnType; beforeEach(() => { - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - processExitSpy = vi.spyOn(process, 'exit').mockImplementation((_code?: number) => { - throw new Error(`process.exit(${_code})`); - }); + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + processExitSpy = vi + .spyOn(process, "exit") + .mockImplementation((_code?: number) => { + throw new Error(`process.exit(${_code})`); + }); }); afterEach(() => { @@ -38,10 +40,10 @@ describe('whoami', () => { it('displays "Not logged in" and exits with code 1 when not authenticated', async () => { mockCredentialsExist.mockReturnValue(false); - await expect(whoami()).rejects.toThrow('process.exit(1)'); + await expect(whoami()).rejects.toThrow("process.exit(1)"); expect(consoleLogSpy).toHaveBeenCalledWith( - 'Not logged in. Run `insighta login` to authenticate.' + "Not logged in. Run `insighta login` to authenticate.", ); expect(processExitSpy).toHaveBeenCalledWith(1); }); @@ -52,29 +54,34 @@ describe('whoami', () => { */ test.prop([ fc.string({ minLength: 1 }), - fc.constantFrom('admin' as const, 'analyst' as const), - ])('Property 20: whoami displays both username and role from the API', async (username, role) => { - mockCredentialsExist.mockReturnValue(true); - mockReadCredentials.mockReturnValue({ - accessToken: 'test-access-token', - refreshToken: 'test-refresh-token', - expiresAt: Date.now() + 3600_000, - username, - role, - }); - mockApiRequest.mockResolvedValue({ - status: 200, - data: { data: { username, role } }, - headers: new Headers(), - }); + fc.constantFrom("admin" as const, "analyst" as const), + ])( + "Property 20: whoami displays both username and role from the API", + async (username, role) => { + mockCredentialsExist.mockReturnValue(true); + mockReadCredentials.mockReturnValue({ + accessToken: "test-access-token", + refreshToken: "test-refresh-token", + expiresAt: Date.now() + 3600_000, + username, + role, + }); + mockApiRequest.mockResolvedValue({ + status: 200, + data: { status: 'success', user: { username, role } }, + headers: new Headers(), + }); - const logs: string[] = []; - consoleLogSpy.mockImplementation((msg: string) => { logs.push(msg); }); + const logs: string[] = []; + consoleLogSpy.mockImplementation((msg: string) => { + logs.push(msg); + }); - await whoami(); + await whoami(); - const output = logs.join('\n'); - expect(output).toContain(username); - expect(output).toContain(role); - }); + const output = logs.join("\n"); + expect(output).toContain(username); + expect(output).toContain(role); + }, + ); }); diff --git a/src/commands/whoami.ts b/src/commands/whoami.ts index e8f7837..0878873 100644 --- a/src/commands/whoami.ts +++ b/src/commands/whoami.ts @@ -1,29 +1,28 @@ -import { credentialsExist, readCredentials } from '../auth/credentials'; -import { apiRequest } from '../http/client'; +import { credentialsExist, readCredentials } from "../auth/credentials"; +import { apiRequest } from "../http/client"; interface MeResponse { user: { username: string; - role: 'admin' | 'analyst'; + role: "admin" | "analyst"; }; } export async function whoami(): Promise { if (!credentialsExist()) { - console.log('Not logged in. Run `insighta login` to authenticate.'); + console.log("Not logged in. Run `insighta login` to authenticate."); process.exit(1); } const creds = readCredentials()!; const response = await apiRequest({ - method: 'GET', - path: '/auth/me', + method: "GET", + path: "/auth/me", accessToken: creds.accessToken, - operation: 'GET /auth/me', + operation: "GET /auth/me", }); - console.log(response); - const { username, role } = response.data?.user; + const { username, role } = response.data.user; console.log(`Logged in as ${username} (${role})`); } diff --git a/src/errors.ts b/src/errors.ts index 81bcd0f..4e764db 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,15 +1,19 @@ export class NotLoggedInError extends Error { - constructor(message = 'Not logged in. Run `insighta login` to authenticate.') { + constructor( + message = "Not logged in. Run `insighta login` to authenticate.", + ) { super(message); - this.name = 'NotLoggedInError'; + this.name = "NotLoggedInError"; } } export class ForbiddenError extends Error { role: string; constructor(role: string) { - super(`Permission denied: this operation requires a higher-privileged role. Your current role is \`${role}\`.`); - this.name = 'ForbiddenError'; + super( + `Permission denied: this operation requires a higher-privileged role. Your current role is \`${role}\`.`, + ); + this.name = "ForbiddenError"; this.role = role; } } @@ -18,7 +22,7 @@ export class NotFoundError extends Error { id: string; constructor(id: string) { super(`No profile found with ID \`${id}\`.`); - this.name = 'NotFoundError'; + this.name = "NotFoundError"; this.id = id; } } @@ -27,7 +31,7 @@ export class ValidationError extends Error { details: Record; constructor(message: string, details: Record = {}) { super(`Validation failed: ${message}`); - this.name = 'ValidationError'; + this.name = "ValidationError"; this.details = details; } } @@ -35,11 +39,12 @@ export class ValidationError extends Error { export class RateLimitError extends Error { retryAfter?: number; constructor(retryAfter?: number) { - const msg = retryAfter != null - ? `Rate limited. Please wait ${retryAfter} seconds before retrying.` - : 'Rate limited. Please try again later.'; + const msg = + retryAfter != null + ? `Rate limited. Please wait ${retryAfter} seconds before retrying.` + : "Rate limited. Please try again later."; super(msg); - this.name = 'RateLimitError'; + this.name = "RateLimitError"; this.retryAfter = retryAfter; } } @@ -47,20 +52,24 @@ export class RateLimitError extends Error { export class NetworkError extends Error { constructor(operation: string, details: string) { super(`Network error: ${operation} failed — ${details}`); - this.name = 'NetworkError'; + this.name = "NetworkError"; } } export class ExportError extends Error { - constructor(message = 'Export failed: the server returned an empty or malformed response.') { + constructor( + message = "Export failed: the server returned an empty or malformed response.", + ) { super(message); - this.name = 'ExportError'; + this.name = "ExportError"; } } export class TimeoutError extends Error { - constructor(message = 'Login timed out. The browser flow was not completed within 5 minutes.') { + constructor( + message = "Login timed out. The browser flow was not completed within 5 minutes.", + ) { super(message); - this.name = 'TimeoutError'; + this.name = "TimeoutError"; } } diff --git a/src/http/client.test.ts b/src/http/client.test.ts index 1be7611..cbace43 100644 --- a/src/http/client.test.ts +++ b/src/http/client.test.ts @@ -1,25 +1,45 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { ForbiddenError, NetworkError, NotFoundError, RateLimitError, ValidationError } from '../errors'; -import { apiRequest } from './client'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + ForbiddenError, + NetworkError, + NotFoundError, + RateLimitError, + ValidationError, +} from "../errors"; +import { apiRequest } from "./client"; // Mock credentials so ForbiddenError can read the role -vi.mock('../auth/credentials', () => ({ - readCredentials: vi.fn(() => ({ role: 'analyst', accessToken: 'tok', refreshToken: 'ref', expiresAt: 9999999999999, username: 'user' })), +vi.mock("../auth/credentials", () => ({ + readCredentials: vi.fn(() => ({ + role: "analyst", + accessToken: "tok", + refreshToken: "ref", + expiresAt: 9999999999999, + username: "user", + })), })); // Mock token-refresher to avoid circular dependency issues in tests -vi.mock('./token-refresher', () => ({ - refreshAndRetry: vi.fn(async () => ({ status: 200, data: { retried: true }, headers: new Headers() })), +vi.mock("./token-refresher", () => ({ + refreshAndRetry: vi.fn(async () => ({ + status: 200, + data: { retried: true }, + headers: new Headers(), + })), })); const BASE_OPTIONS = { - method: 'GET' as const, - path: '/profiles', - accessToken: 'test-token', - operation: 'test-op', + method: "GET" as const, + path: "/profiles", + accessToken: "test-token", + operation: "test-op", }; -function makeResponse(status: number, body: unknown = {}, headers: Record = {}): Response { +function makeResponse( + status: number, + body: unknown = {}, + headers: Record = {}, +): Response { const h = new Headers(headers); return { status, @@ -29,9 +49,9 @@ function makeResponse(status: number, body: unknown = {}, headers: Record { +describe("apiRequest", () => { beforeEach(() => { - vi.stubGlobal('fetch', vi.fn()); + vi.stubGlobal("fetch", vi.fn()); }); afterEach(() => { @@ -39,117 +59,155 @@ describe('apiRequest', () => { vi.clearAllMocks(); }); - it('returns data on 200', async () => { - vi.mocked(fetch).mockResolvedValue(makeResponse(200, { id: '1' })); + it("returns data on 200", async () => { + vi.mocked(fetch).mockResolvedValue(makeResponse(200, { id: "1" })); const result = await apiRequest<{ id: string }>(BASE_OPTIONS); expect(result.status).toBe(200); - expect(result.data).toEqual({ id: '1' }); + expect(result.data).toEqual({ id: "1" }); }); - it('injects Authorization header', async () => { + it("injects Authorization header", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(200, {})); await apiRequest(BASE_OPTIONS); const [, init] = vi.mocked(fetch).mock.calls[0]!; - expect((init?.headers as Record)['Authorization']).toBe('Bearer test-token'); + expect((init?.headers as Record)["Authorization"]).toBe( + "Bearer test-token", + ); }); - it('throws ForbiddenError on 403', async () => { + it("throws ForbiddenError on 403", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(403)); - await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf(ForbiddenError); + await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf( + ForbiddenError, + ); }); - it('ForbiddenError message includes role', async () => { + it("ForbiddenError message includes role", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(403)); - await expect(apiRequest(BASE_OPTIONS)).rejects.toThrow('analyst'); + await expect(apiRequest(BASE_OPTIONS)).rejects.toThrow("analyst"); }); - it('throws NotFoundError on 404', async () => { + it("throws NotFoundError on 404", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(404)); - await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf(NotFoundError); + await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf( + NotFoundError, + ); }); - it('NotFoundError id is last path segment', async () => { + it("NotFoundError id is last path segment", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(404)); - const err = await apiRequest({ ...BASE_OPTIONS, path: '/profiles/abc-123' }).catch(e => e); + const err = await apiRequest({ + ...BASE_OPTIONS, + path: "/profiles/abc-123", + }).catch((e) => e); expect(err).toBeInstanceOf(NotFoundError); - expect((err as NotFoundError).id).toBe('abc-123'); - }); - - it('throws ValidationError on 422', async () => { - vi.mocked(fetch).mockResolvedValue(makeResponse(422, { message: 'bad input', details: { name: ['too short'] } })); - await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf(ValidationError); - }); - - it('ValidationError includes message and details', async () => { - vi.mocked(fetch).mockResolvedValue(makeResponse(422, { message: 'bad input', details: { name: ['too short'] } })); - const err = await apiRequest(BASE_OPTIONS).catch(e => e) as ValidationError; - expect(err.message).toContain('bad input'); - expect(err.details).toEqual({ name: ['too short'] }); - }); - - it('throws RateLimitError on 429 without Retry-After', async () => { + expect((err as NotFoundError).id).toBe("abc-123"); + }); + + it("throws ValidationError on 422", async () => { + vi.mocked(fetch).mockResolvedValue( + makeResponse(422, { + message: "bad input", + details: { name: ["too short"] }, + }), + ); + await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf( + ValidationError, + ); + }); + + it("ValidationError includes message and details", async () => { + vi.mocked(fetch).mockResolvedValue( + makeResponse(422, { + message: "bad input", + details: { name: ["too short"] }, + }), + ); + const err = (await apiRequest(BASE_OPTIONS).catch( + (e) => e, + )) as ValidationError; + expect(err.message).toContain("bad input"); + expect(err.details).toEqual({ name: ["too short"] }); + }); + + it("throws RateLimitError on 429 without Retry-After", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(429)); - const err = await apiRequest(BASE_OPTIONS).catch(e => e) as RateLimitError; + const err = (await apiRequest(BASE_OPTIONS).catch( + (e) => e, + )) as RateLimitError; expect(err).toBeInstanceOf(RateLimitError); expect(err.retryAfter).toBeUndefined(); }); - it('RateLimitError includes retryAfter when header present', async () => { - vi.mocked(fetch).mockResolvedValue(makeResponse(429, {}, { 'Retry-After': '30' })); - const err = await apiRequest(BASE_OPTIONS).catch(e => e) as RateLimitError; + it("RateLimitError includes retryAfter when header present", async () => { + vi.mocked(fetch).mockResolvedValue( + makeResponse(429, {}, { "Retry-After": "30" }), + ); + const err = (await apiRequest(BASE_OPTIONS).catch( + (e) => e, + )) as RateLimitError; expect(err).toBeInstanceOf(RateLimitError); expect(err.retryAfter).toBe(30); - expect(err.message).toContain('30'); + expect(err.message).toContain("30"); }); - it('throws NetworkError when fetch rejects', async () => { - vi.mocked(fetch).mockRejectedValue(new Error('ECONNREFUSED')); + it("throws NetworkError when fetch rejects", async () => { + vi.mocked(fetch).mockRejectedValue(new Error("ECONNREFUSED")); await expect(apiRequest(BASE_OPTIONS)).rejects.toBeInstanceOf(NetworkError); }); - it('NetworkError message includes operation name', async () => { - vi.mocked(fetch).mockRejectedValue(new Error('ECONNREFUSED')); - const err = await apiRequest({ ...BASE_OPTIONS, operation: 'list-profiles' }).catch(e => e) as NetworkError; - expect(err.message).toContain('list-profiles'); + it("NetworkError message includes operation name", async () => { + vi.mocked(fetch).mockRejectedValue(new Error("ECONNREFUSED")); + const err = (await apiRequest({ + ...BASE_OPTIONS, + operation: "list-profiles", + }).catch((e) => e)) as NetworkError; + expect(err.message).toContain("list-profiles"); }); - it('delegates 401 to refreshAndRetry', async () => { + it("delegates 401 to refreshAndRetry", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(401)); - const { refreshAndRetry } = await import('./token-refresher'); + const { refreshAndRetry } = await import("./token-refresher"); const result = await apiRequest(BASE_OPTIONS); expect(vi.mocked(refreshAndRetry)).toHaveBeenCalledOnce(); expect(result.data).toEqual({ retried: true }); }); - it('uses path as-is for profile routes', async () => { + it("uses path as-is for profile routes", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(200, {})); - await apiRequest({ ...BASE_OPTIONS, path: '/api/profiles' }); + await apiRequest({ ...BASE_OPTIONS, path: "/api/profiles" }); const [url] = vi.mocked(fetch).mock.calls[0]!; - expect(String(url)).toContain('/api/profiles'); - expect(String(url)).not.toContain('/api/v1'); + expect(String(url)).toContain("/api/profiles"); + expect(String(url)).not.toContain("/api/v1"); }); - it('uses path as-is for auth routes', async () => { + it("uses path as-is for auth routes", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(200, {})); - await apiRequest({ ...BASE_OPTIONS, path: '/auth/me' }); + await apiRequest({ ...BASE_OPTIONS, path: "/auth/me" }); const [url] = vi.mocked(fetch).mock.calls[0]!; - expect(String(url)).toContain('/auth/me'); - expect(String(url)).not.toContain('/api/v1'); + expect(String(url)).toContain("/auth/me"); + expect(String(url)).not.toContain("/api/v1"); }); - it('appends query params to URL', async () => { + it("appends query params to URL", async () => { vi.mocked(fetch).mockResolvedValue(makeResponse(200, {})); await apiRequest({ ...BASE_OPTIONS, query: { page: 2, limit: 10 } }); const [url] = vi.mocked(fetch).mock.calls[0]!; - expect(String(url)).toContain('page=2'); - expect(String(url)).toContain('limit=10'); + expect(String(url)).toContain("page=2"); + expect(String(url)).toContain("limit=10"); }); - it('sends JSON body with Content-Type header on POST', async () => { - vi.mocked(fetch).mockResolvedValue(makeResponse(201, { id: 'new' })); - await apiRequest({ ...BASE_OPTIONS, method: 'POST', body: { name: 'Alice' } }); + it("sends JSON body with Content-Type header on POST", async () => { + vi.mocked(fetch).mockResolvedValue(makeResponse(201, { id: "new" })); + await apiRequest({ + ...BASE_OPTIONS, + method: "POST", + body: { name: "Alice" }, + }); const [, init] = vi.mocked(fetch).mock.calls[0]!; - expect((init?.headers as Record)['Content-Type']).toBe('application/json'); - expect(init?.body).toBe(JSON.stringify({ name: 'Alice' })); + expect((init?.headers as Record)["Content-Type"]).toBe( + "application/json", + ); + expect(init?.body).toBe(JSON.stringify({ name: "Alice" })); }); }); diff --git a/src/http/client.ts b/src/http/client.ts index 961e8d6..53fd59a 100644 --- a/src/http/client.ts +++ b/src/http/client.ts @@ -1,14 +1,14 @@ -import { readCredentials } from '../auth/credentials'; +import { readCredentials } from "../auth/credentials"; import { ForbiddenError, NotFoundError, ValidationError, RateLimitError, NetworkError, -} from '../errors'; +} from "../errors"; export interface ApiRequestOptions { - method: 'GET' | 'POST' | 'DELETE'; + method: "GET" | "POST" | "DELETE"; /** * Full path including prefix, e.g. '/api/profiles' or '/auth/me'. * The client does NOT add any prefix — callers are responsible for @@ -29,10 +29,13 @@ export interface ApiResponse { headers: Headers; } -const BASE_URL = process.env.INSIGHTA_API_URL ?? 'http://localhost:3000'; +const BASE_URL = process.env.INSIGHTA_API_URL ?? "http://localhost:3000"; -function buildUrl(path: string, query?: Record): string { - const normalizedPath = path.startsWith('/') ? path : `/${path}`; +function buildUrl( + path: string, + query?: Record, +): string { + const normalizedPath = path.startsWith("/") ? path : `/${path}`; const url = new URL(normalizedPath, BASE_URL); if (query) { for (const [key, value] of Object.entries(query)) { @@ -42,21 +45,23 @@ function buildUrl(path: string, query?: Record): string return url.toString(); } -export async function apiRequest(options: ApiRequestOptions): Promise> { +export async function apiRequest( + options: ApiRequestOptions, +): Promise> { const { method, path, query, body, accessToken, operation = path } = options; const url = buildUrl(path, query); const headers: Record = { Authorization: `Bearer ${accessToken}`, - 'x-client-type': 'cli', - 'x-api-version': '1' + "x-client-type": "cli", + "x-api-version": "1", }; let bodyStr: string | undefined; if (body !== undefined) { bodyStr = JSON.stringify(body); - headers['Content-Type'] = 'application/json'; + headers["Content-Type"] = "application/json"; } let response: Response; @@ -75,26 +80,29 @@ export async function apiRequest(options: ApiRequestOptions): Promise(options); } if (status === 403) { - const role = readCredentials()?.role ?? 'unknown'; + const role = readCredentials()?.role ?? "unknown"; throw new ForbiddenError(role); } if (status === 404) { - const segments = path.replace(/\/$/, '').split('/'); - const id = segments[segments.length - 1] ?? ''; + const segments = path.replace(/\/$/, "").split("/"); + const id = segments[segments.length - 1] ?? ""; throw new NotFoundError(id); } if (status === 422) { - let message = 'Unprocessable entity'; + let message = "Unprocessable entity"; let details: Record = {}; try { - const errorBody = await response.json() as { message?: string; details?: Record }; + const errorBody = (await response.json()) as { + message?: string; + details?: Record; + }; if (errorBody.message) message = errorBody.message; if (errorBody.details) details = errorBody.details; } catch { @@ -104,15 +112,18 @@ export async function apiRequest(options: ApiRequestOptions): Promise(originalOptions: ApiRequestOptions): Promise> { - const creds = readCredentials(); +export async function refreshAndRetry( + originalOptions: ApiRequestOptions, +): Promise> { + const creds = readCredentials(); - if (!creds) { - console.error('Session expired. Run `insighta login` to re-authenticate.' ); - process.exit(1); - } + if (!creds) { + console.error("Session expired. Run `insighta login` to re-authenticate."); + process.exit(1); + } - // Attempt to refresh token - let refreshResponse: ApiResponse; - - try { - refreshResponse = await apiRequest({ - method: 'POST', - path: '/auth/refresh', - body: { refresh_token: creds.refreshToken }, - accessToken: creds.accessToken, // still needed for the request wrapper - operation: 'token refresh', - }); - } catch (err) { - if (err instanceof NetworkError) { - console.error(err.message); - process.exit(1); - } - throw err; - } + // Attempt to refresh token + let refreshResponse: ApiResponse; - // 400 or 401 from the refresh endpoint -> session is dead - if (refreshResponse.status === 400 || refreshResponse.status === 401) { - deleteCredentials(); - console.error('Session expired. Run `insighta login` to re-authenticate.'); - process.exit(1); + try { + refreshResponse = await apiRequest({ + method: "POST", + path: "/auth/refresh", + body: { refresh_token: creds.refreshToken }, + accessToken: creds.accessToken, // still needed for the request wrapper + operation: "token refresh", + }); + } catch (err) { + if (err instanceof NetworkError) { + console.error(err.message); + process.exit(1); } + throw err; + } - // Update credentials store with new tokens - const { access_token, refresh_token, expires_in } = refreshResponse.data; - writeCredentials({ - ...creds, - accessToken: access_token, - refreshToken: refresh_token, - expiresAt: Date.now() + expires_in * 1000, - }); + // 400 or 401 from the refresh endpoint -> session is dead + if (refreshResponse.status === 400 || refreshResponse.status === 401) { + deleteCredentials(); + console.error("Session expired. Run `insighta login` to re-authenticate."); + process.exit(1); + } - // Retry the original request exactly once with the new token - return apiRequest({ - ...originalOptions, - accessToken: access_token - }); -} \ No newline at end of file + // Update credentials store with new tokens + const { access_token, refresh_token, expires_in } = refreshResponse.data; + writeCredentials({ + ...creds, + accessToken: access_token, + refreshToken: refresh_token, + expiresAt: Date.now() + expires_in * 1000, + }); + + // Retry the original request exactly once with the new token + return apiRequest({ + ...originalOptions, + accessToken: access_token, + }); +} diff --git a/src/output/exporter.test.ts b/src/output/exporter.test.ts index 04e9b1e..b5d2c14 100644 --- a/src/output/exporter.test.ts +++ b/src/output/exporter.test.ts @@ -1,13 +1,13 @@ -import { test } from '@fast-check/vitest'; -import * as fc from 'fast-check'; -import * as fs from 'fs'; -import * as path from 'path'; -import { saveExport } from './exporter'; -import { ExportError } from '../errors'; - -function makeCsvResponse(body: string, contentType = 'text/csv'): Response { +import { test } from "@fast-check/vitest"; +import * as fc from "fast-check"; +import * as fs from "fs"; +import * as path from "path"; +import { saveExport } from "./exporter"; +import { ExportError } from "../errors"; + +function makeCsvResponse(body: string, contentType = "text/csv"): Response { return new Response(body, { - headers: { 'Content-Type': contentType }, + headers: { "Content-Type": contentType }, }); } @@ -15,22 +15,25 @@ function makeCsvResponse(body: string, contentType = 'text/csv'): Response { * Property 18: Export filename and absolute path are correct * Validates: Requirements 14.2, 14.3, 14.4 */ -test.prop([ - fc.string({ minLength: 1 }).filter(s => s.trim().length > 0), -])('Property 18: export filename matches pattern and returned path is absolute', async (csvBody) => { - const response = makeCsvResponse(csvBody); - const result = await saveExport(response); - - try { - const filename = path.basename(result); - expect(filename).toMatch(/^profiles-export-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.csv$/); - expect(path.isAbsolute(result)).toBe(true); - } finally { - if (fs.existsSync(result)) { - fs.unlinkSync(result); +test.prop([fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0)])( + "Property 18: export filename matches pattern and returned path is absolute", + async (csvBody) => { + const response = makeCsvResponse(csvBody); + const result = await saveExport(response); + + try { + const filename = path.basename(result); + expect(filename).toMatch( + /^profiles-export-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.csv$/, + ); + expect(path.isAbsolute(result)).toBe(true); + } finally { + if (fs.existsSync(result)) { + fs.unlinkSync(result); + } } - } -}); + }, +); /** * Property 19: Non-CSV export response does not write a file @@ -38,24 +41,31 @@ test.prop([ */ test.prop([ fc.oneof( - fc.constant('application/json'), - fc.constant('text/plain'), - fc.constant('application/octet-stream'), - fc.constant(''), + fc.constant("application/json"), + fc.constant("text/plain"), + fc.constant("application/octet-stream"), + fc.constant(""), ), -])('Property 19: non-CSV content type throws ExportError and writes no file', async (contentType) => { - const body = 'some,data\n1,2'; - const response = makeCsvResponse(body, contentType); +])( + "Property 19: non-CSV content type throws ExportError and writes no file", + async (contentType) => { + const body = "some,data\n1,2"; + const response = makeCsvResponse(body, contentType); - const filesBefore = fs.readdirSync(process.cwd()).filter(f => f.startsWith('profiles-export-')); + const filesBefore = fs + .readdirSync(process.cwd()) + .filter((f) => f.startsWith("profiles-export-")); - await expect(saveExport(response)).rejects.toThrow(ExportError); + await expect(saveExport(response)).rejects.toThrow(ExportError); - const filesAfter = fs.readdirSync(process.cwd()).filter(f => f.startsWith('profiles-export-')); - expect(filesAfter.length).toBe(filesBefore.length); -}); + const filesAfter = fs + .readdirSync(process.cwd()) + .filter((f) => f.startsWith("profiles-export-")); + expect(filesAfter.length).toBe(filesBefore.length); + }, +); -test('throws ExportError for empty body with valid content type', async () => { - const response = makeCsvResponse(''); +test("throws ExportError for empty body with valid content type", async () => { + const response = makeCsvResponse(""); await expect(saveExport(response)).rejects.toThrow(ExportError); }); diff --git a/src/output/exporter.ts b/src/output/exporter.ts index d628a82..547e470 100644 --- a/src/output/exporter.ts +++ b/src/output/exporter.ts @@ -1,10 +1,10 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { ExportError } from '../errors'; +import * as fs from "fs"; +import * as path from "path"; +import { ExportError } from "../errors"; export async function saveExport(response: Response): Promise { - const contentType = response.headers.get('Content-Type') ?? ''; - if (!contentType.includes('text/csv')) { + const contentType = response.headers.get("Content-Type") ?? ""; + if (!contentType.includes("text/csv")) { throw new ExportError(); } @@ -13,7 +13,7 @@ export async function saveExport(response: Response): Promise { throw new ExportError(); } - const datePart = new Date().toISOString().replace(/:/g, '-').split('.')[0]; + const datePart = new Date().toISOString().replace(/:/g, "-").split(".")[0]; const filename = `profiles-export-${datePart}.csv`; const filePath = path.join(process.cwd(), filename); diff --git a/src/output/formatter.test.ts b/src/output/formatter.test.ts index e21e511..d6f3097 100644 --- a/src/output/formatter.test.ts +++ b/src/output/formatter.test.ts @@ -1,44 +1,49 @@ -import { test } from '@fast-check/vitest'; -import * as fc from 'fast-check'; -import { describe, expect } from 'vitest'; +import { test } from "@fast-check/vitest"; +import * as fc from "fast-check"; +import { describe, expect } from "vitest"; import { formatProfile, formatProfileList, formatCreateSuccess, formatDeleteConfirmation, type Profile, -} from './formatter'; +} from "./formatter"; const profileArb = fc.record({ id: fc.uuid(), name: fc.string({ minLength: 1, maxLength: 50 }), - gender: fc.constantFrom('male' as const, 'female' as const), + gender: fc.constantFrom("male" as const, "female" as const), gender_probability: fc.float({ min: 0, max: 1, noNaN: true }), age: fc.integer({ min: 0, max: 120 }), - age_group: fc.constantFrom('adult' as const, 'child' as const, 'teenager' as const, 'senior' as const), + age_group: fc.constantFrom( + "adult" as const, + "child" as const, + "teenager" as const, + "senior" as const, + ), country_id: fc.string({ minLength: 2, maxLength: 3 }), country_name: fc.string({ minLength: 1, maxLength: 50 }), country_probability: fc.float({ min: 0, max: 1, noNaN: true }), - created_at: fc.date().map(d => d.toISOString()), + created_at: fc.date().map((d) => d.toISOString()), }); const sampleProfile: Profile = { - id: 'abc-123', - name: 'Alice', - gender: 'female', + id: "abc-123", + name: "Alice", + gender: "female", gender_probability: 0.97, age: 30, - age_group: 'adult', - country_id: 'US', - country_name: 'United States', + age_group: "adult", + country_id: "US", + country_name: "United States", country_probability: 0.85, - created_at: '2024-01-01T00:00:00.000Z', + created_at: "2024-01-01T00:00:00.000Z", }; // ── formatProfile ──────────────────────────────────────────────────────────── -describe('formatProfile', () => { - test.prop([profileArb])('contains id, name, gender, country', (profile) => { +describe("formatProfile", () => { + test.prop([profileArb])("contains id, name, gender, country", (profile) => { const result = formatProfile(profile); expect(result).toContain(profile.id); expect(result).toContain(profile.name); @@ -46,25 +51,25 @@ describe('formatProfile', () => { expect(result).toContain(profile.country_name); }); - test('renders all fields for a known profile', () => { + test("renders all fields for a known profile", () => { const result = formatProfile(sampleProfile); - expect(result).toContain('abc-123'); - expect(result).toContain('Alice'); - expect(result).toContain('female'); - expect(result).toContain('United States'); - expect(result).toContain('2024-01-01T00:00:00.000Z'); + expect(result).toContain("abc-123"); + expect(result).toContain("Alice"); + expect(result).toContain("female"); + expect(result).toContain("United States"); + expect(result).toContain("2024-01-01T00:00:00.000Z"); }); }); // ── formatProfileList ──────────────────────────────────────────────────────── -describe('formatProfileList', () => { +describe("formatProfileList", () => { test('returns "No profiles found." for empty list', () => { - expect(formatProfileList([])).toBe('No profiles found.'); + expect(formatProfileList([])).toBe("No profiles found."); }); test.prop([fc.array(profileArb, { minLength: 1, maxLength: 10 })])( - 'contains each profile id and name', + "contains each profile id and name", (profiles) => { const result = formatProfileList(profiles); for (const p of profiles) { @@ -74,14 +79,14 @@ describe('formatProfileList', () => { }, ); - test('includes total count when provided', () => { + test("includes total count when provided", () => { const result = formatProfileList([sampleProfile], 42); - expect(result).toContain('42'); + expect(result).toContain("42"); }); - test('omits total line when total is not provided', () => { + test("omits total line when total is not provided", () => { const result = formatProfileList([sampleProfile]); - expect(result).not.toContain('Total results'); + expect(result).not.toContain("Total results"); }); }); @@ -92,7 +97,7 @@ describe('formatProfileList', () => { * Validates: Requirements 11.2 */ test.prop([profileArb])( - 'Property 14: formatCreateSuccess contains profile id and name', + "Property 14: formatCreateSuccess contains profile id and name", (profile) => { const result = formatCreateSuccess(profile); expect(result).toContain(profile.id); @@ -107,7 +112,7 @@ test.prop([profileArb])( * Validates: Requirements 12.2 */ test.prop([fc.uuid()])( - 'Property 16: formatDeleteConfirmation contains the profile id', + "Property 16: formatDeleteConfirmation contains the profile id", (id) => { const result = formatDeleteConfirmation(id); expect(result).toContain(id); diff --git a/src/output/formatter.ts b/src/output/formatter.ts index fd9804b..c1a25ae 100644 --- a/src/output/formatter.ts +++ b/src/output/formatter.ts @@ -1,10 +1,10 @@ export interface Profile { id: string; name: string; - gender: 'male' | 'female'; + gender: "male" | "female"; gender_probability: number; age: number; - age_group: 'adult' | 'child' | 'teenager' | 'senior'; + age_group: "adult" | "child" | "teenager" | "senior"; country_id: string; country_name: string; country_probability: number; @@ -12,9 +12,10 @@ export interface Profile { } export function formatProfile(profile: Profile): string { - const createdAt = profile.created_at instanceof Date - ? profile.created_at.toISOString() - : profile.created_at; + const createdAt = + profile.created_at instanceof Date + ? profile.created_at.toISOString() + : profile.created_at; const lines: string[] = [ `ID: ${profile.id}`, `Name: ${profile.name}`, @@ -23,14 +24,14 @@ export function formatProfile(profile: Profile): string { `Country: ${profile.country_name} [${profile.country_id}] (${(profile.country_probability * 100).toFixed(1)}%)`, `Created: ${createdAt}`, ]; - return lines.join('\n'); + return lines.join("\n"); } export function formatProfileList(profiles: Profile[], total?: number): string { if (profiles.length === 0) { - return 'No profiles found.'; + return "No profiles found."; } - const divider = '─'.repeat(40); + const divider = "─".repeat(40); const list = profiles.map(formatProfile).join(`\n${divider}\n`); if (total !== undefined) { return `${list}\n\nTotal results: ${total}`; diff --git a/src/output/paginator.test.ts b/src/output/paginator.test.ts index 1399eef..38d1dcf 100644 --- a/src/output/paginator.test.ts +++ b/src/output/paginator.test.ts @@ -1,6 +1,6 @@ -import { test } from '@fast-check/vitest'; -import * as fc from 'fast-check'; -import { renderPagination } from './paginator'; +import { test } from "@fast-check/vitest"; +import * as fc from "fast-check"; +import { renderPagination } from "./paginator"; /** * Property 11: Pagination display contains all four metadata values @@ -11,10 +11,13 @@ test.prop([ fc.integer({ min: 1, max: 100 }), fc.integer({ min: 0, max: 1000000 }), fc.integer({ min: 1, max: 10000 }), -])('Property 11: rendered pagination string contains all four metadata values', (page, limit, total, totalPages) => { - const result = renderPagination({ page, limit, total, totalPages }); - expect(result).toContain(String(page)); - expect(result).toContain(String(limit)); - expect(result).toContain(String(total)); - expect(result).toContain(String(totalPages)); -}); +])( + "Property 11: rendered pagination string contains all four metadata values", + (page, limit, total, totalPages) => { + const result = renderPagination({ page, limit, total, totalPages }); + expect(result).toContain(String(page)); + expect(result).toContain(String(limit)); + expect(result).toContain(String(total)); + expect(result).toContain(String(totalPages)); + }, +); diff --git a/src/output/spinner.ts b/src/output/spinner.ts index 3e184d8..a0ba19f 100644 --- a/src/output/spinner.ts +++ b/src/output/spinner.ts @@ -4,7 +4,7 @@ * (e.g. when output is piped or redirected). */ -const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; +const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; const INTERVAL_MS = 80; export interface Spinner { @@ -57,7 +57,7 @@ export function startSpinner(message: string): Spinner { stopped = true; clearInterval(timer); // Clear the spinner line - process.stdout.write('\r\x1b[K'); + process.stdout.write("\r\x1b[K"); if (finalMessage) process.stdout.write(`${finalMessage}\n`); }, }; diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..513c621 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*", "bin/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/vitest.config.ts b/vitest.config.ts index 287634a..23c8ad3 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,13 +1,13 @@ -import { defineConfig } from 'vitest/config'; +import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - environment: 'node', - include: ['src/**/*.test.ts'], + environment: "node", + include: ["src/**/*.test.ts"], coverage: { - provider: 'v8', - reporter: ['text', 'lcov'], + provider: "v8", + reporter: ["text", "lcov"], }, }, });