diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..a3446c8f1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,205 @@ +# Copilot Instructions for GitHub Copilot for Azure + +## Repository Overview + +This repository contains agent skills for GitHub Copilot's Azure integration. Skills provide specialized capabilities for deployment, validation, diagnostics, and Azure service management. + +--- + +## Directory Structure + +``` +plugin/ +└── skills/ + └── / + ├── SKILL.md # Skill definition (required) + └── reference/ # Reference guides (optional) + +tests/ +├── detection/ # App type detection tests (264 tests) +│ ├── __tests__/ +│ ├── fixtures/ +│ ├── src/ +│ └── package.json +├── nodejs-production/ # Node.js production readiness tests (83 tests) +│ ├── __tests__/ +│ ├── fixtures/ +│ ├── src/ +│ └── package.json +├── validation/ # Azure validation tests (165 tests) +│ ├── __tests__/ +│ ├── fixtures/ +│ ├── src/ +│ └── package.json +└── README.md + +.github/ +├── agents/ # Custom agent definitions +├── workflows/ # CI/CD workflows +└── ISSUE_TEMPLATE/ +``` + +--- + +## Skill Development + +### Skill File Format + +Every skill requires a `SKILL.md` file with YAML frontmatter: + +```markdown +--- +name: skill-name +description: Brief description of what the skill does. Include trigger phrases. +--- + +## Skill content and instructions... +``` + +### Key Guidelines + +- **Use `azd` for deployments** - Azure Developer CLI, not `az` CLI +- **Reference guides** - Place detailed service-specific docs in `reference/` subdirectory +- **MCP tools** - Document which Azure MCP tools the skill uses +- **Detection logic** - If the skill involves app detection, follow patterns in `azure-deploy` + +--- + +## Testing + +### Test Structure + +Tests are organized into focused suites, each with its own `package.json`: + +| Suite | Location | Tests | Purpose | +|-------|----------|-------|---------| +| **Detection** | `tests/detection/` | 264 | App type detection, service selection | +| **Node.js Production** | `tests/nodejs-production/` | 83 | Production readiness validation | +| **Validation** | `tests/validation/` | 165 | Azure resource validation, Bicep, preflight | + +### Running Tests + +```bash +# Detection tests +cd tests/detection && npm test + +# Node.js production tests +cd tests/nodejs-production && npm test + +# Validation tests +cd tests/validation && npm test +``` + +### Test Expectations + +- **Detection tests**: 264+ tests, all should pass +- **Node.js production tests**: 83+ tests +- **Validation tests**: 165+ tests + +--- + +## Adding Tests + +### ⚠️ IMPORTANT: Only Add Tests to Existing Suites + +**DO NOT create new test suites or test areas.** Add tests only to existing coverage areas: + +- `tests/__tests__/detection/` - Detection logic tests +- `tests/nodejs-production/` - Node.js production readiness +- `tests/validation/` - Azure validation (naming, Bicep, preflight) + +### When to Add Tests + +Add tests when: +- ✅ A new skill is added that affects detection logic +- ✅ A new function is added to an existing tested module +- ✅ A bug is fixed in tested code +- ✅ A new framework or service needs detection + +Do NOT add tests when: +- ❌ The change is in an untested area (e.g., AI skills, database skills) +- ❌ Creating a new test suite would be required +- ❌ The skill doesn't have detection or validation logic + +### Adding Detection Tests + +1. **Add fixture** (if testing a new project type): + ```bash + mkdir -p tests/fixtures/projects/my-new-framework + # Add minimal config files for detection + ``` + +2. **Update expectations** in `tests/fixtures/expectations.json`: + ```json + { + "my-new-framework": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "MyFramework" + } + } + ``` + +3. **Update detection logic** (if new pattern): + - `tests/src/detection/filePatterns.js` - Add file patterns + - `tests/src/detection/appTypeDetector.js` - Add detection logic + +4. **Run tests** to verify: + ```bash + cd tests && npm test + ``` + +### Adding Validation Tests + +Add to existing test files in `tests/validation/__tests__/`: +- `resourceNameValidator.test.js` - Azure resource naming +- `bicepValidator.test.js` - Bicep file validation +- `preflightValidator.test.js` - Preflight checks +- `integration.test.js` - End-to-end scenarios + +### Keeping Tests Updated + +When making changes to skills or detection logic: + +1. **Check if tests exist** for the affected area +2. **Update tests** to match new behavior +3. **Update expectations.json** if service mappings change +4. **Run all test suites** before committing: + ```bash + cd tests && npm test + cd tests/nodejs-production && npm test + cd tests/validation && npm test + ``` + +--- + +## Skill Routing + +All deployment scenarios route to `azure-deploy` skill with reference guides: + +| Detection Signal | Service | Reference Guide | +|------------------|---------|-----------------| +| `azure.yaml` | Azure Developer CLI | - | +| `Dockerfile`, `docker-compose.yml` | Container Apps | `reference/container-apps.md` | +| `host.json`, `function.json` | Azure Functions | `reference/functions.md` | +| `staticwebapp.config.json` | Static Web Apps | `reference/static-web-apps.md` | +| Express, Flask, Django, etc. | App Service | `reference/app-service.md` | + +--- + +## Code Style + +- **JavaScript/Node.js** - Use ES modules, Jest for testing +- **Markdown** - Use ATX-style headers (`#`), fenced code blocks +- **YAML** - 2-space indentation +- **Avoid over-commenting** - Code should be self-documenting + +--- + +## Pull Requests + +- Include test updates when changing detection or validation logic +- Verify all existing tests pass +- Update README files if adding new test coverage +- Reference related issues or PRs in commit messages diff --git a/.github/workflows/test-detection.yml b/.github/workflows/test-detection.yml new file mode 100644 index 000000000..4c9d26076 --- /dev/null +++ b/.github/workflows/test-detection.yml @@ -0,0 +1,90 @@ +name: Detection Tests + +on: + push: + branches: [main] + paths: + - 'plugin/skills/azure-deploy/**' + - 'tests/detection/**' + pull_request: + branches: [main] + paths: + - 'plugin/skills/azure-deploy/**' + - 'tests/detection/**' + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20, 22] + + defaults: + run: + working-directory: tests/detection + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: tests/detection/package-lock.json + + - name: Install dependencies + run: npm ci || npm install + + - name: Run tests + run: npm test + + - name: Run tests with coverage + run: npm run test:coverage + + - name: Upload coverage to Codecov + if: matrix.node-version == 20 + uses: codecov/codecov-action@v4 + with: + directory: tests/detection/coverage + flags: detection + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + lint: + name: Lint + runs-on: ubuntu-latest + + defaults: + run: + working-directory: tests/detection + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: tests/detection/package-lock.json + + - name: Install dependencies + run: npm ci || npm install + + - name: Check fixture/expectation sync + run: | + node -e " + const { validateFixturesHaveExpectations } = require('./utils/fixture-loader'); + const result = validateFixturesHaveExpectations(); + if (!result.valid) { + if (result.missing.length) console.error('Missing expectations:', result.missing); + if (result.extra.length) console.error('Extra expectations:', result.extra); + process.exit(1); + } + console.log('✓ All fixtures have expectations'); + " diff --git a/.github/workflows/test-nodejs-production.yml b/.github/workflows/test-nodejs-production.yml new file mode 100644 index 000000000..39d0afc5c --- /dev/null +++ b/.github/workflows/test-nodejs-production.yml @@ -0,0 +1,56 @@ +name: Node.js Production Tests + +on: + push: + branches: [main] + paths: + - 'plugin/skills/azure-nodejs-production/**' + - 'tests/nodejs-production/**' + pull_request: + branches: [main] + paths: + - 'plugin/skills/azure-nodejs-production/**' + - 'tests/nodejs-production/**' + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20, 22] + + defaults: + run: + working-directory: tests/nodejs-production + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: tests/nodejs-production/package-lock.json + + - name: Install dependencies + run: npm ci || npm install + + - name: Run tests + run: npm test + + - name: Run tests with coverage + run: npm run test:coverage + + - name: Upload coverage to Codecov + if: matrix.node-version == 20 + uses: codecov/codecov-action@v4 + with: + directory: tests/nodejs-production/coverage + flags: nodejs-production + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/test-validation.yml b/.github/workflows/test-validation.yml new file mode 100644 index 000000000..41f32cddf --- /dev/null +++ b/.github/workflows/test-validation.yml @@ -0,0 +1,58 @@ +name: Validation Tests + +on: + push: + branches: [main] + paths: + - 'plugin/skills/azure-validation/**' + - 'plugin/skills/azure-deployment-preflight/**' + - 'tests/validation/**' + pull_request: + branches: [main] + paths: + - 'plugin/skills/azure-validation/**' + - 'plugin/skills/azure-deployment-preflight/**' + - 'tests/validation/**' + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20, 22] + + defaults: + run: + working-directory: tests/validation + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: tests/validation/package-lock.json + + - name: Install dependencies + run: npm ci || npm install + + - name: Run tests + run: npm test + + - name: Run tests with coverage + run: npm run test:coverage + + - name: Upload coverage to Codecov + if: matrix.node-version == 20 + uses: codecov/codecov-action@v4 + with: + directory: tests/validation/coverage + flags: validation + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..99462330c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,389 @@ +# Azure Skill Tests + +Automated test suites for Azure deployment skills. These tests verify detection logic, production readiness validation, and Azure resource validation. + +--- + +## Table of Contents + +- [Setup](#setup) +- [Running Tests](#running-tests) +- [Test Structure](#test-structure) +- [Adding New Tests](#adding-new-tests) +- [Test Coverage vs Skills](#test-coverage-vs-skills) +- [Detection Logic Reference](#detection-logic-reference) + +--- + +## Setup + +### Prerequisites + +- Node.js 18+ installed +- npm or yarn + +### Installation + +Each suite is independent with its own `package.json`. Install dependencies per suite: + +```bash +# Detection tests +cd tests/detection && npm install + +# Node.js production tests +cd tests/nodejs-production && npm install + +# Validation tests +cd tests/validation && npm install +``` + +--- + +## Running Tests + +### Run Individual Suites + +```bash +# Detection tests (181 tests) +cd tests/detection && npm test + +# Node.js production tests (83 tests) +cd tests/nodejs-production && npm test + +# Validation tests (165 tests) +cd tests/validation && npm test +``` + +### Run All Suites + +```bash +# From repository root +cd tests/detection && npm install && npm test && \ +cd ../nodejs-production && npm install && npm test && \ +cd ../validation && npm install && npm test +``` + +### Additional Commands + +```bash +# Run tests in watch mode (re-runs on file changes) +npm run test:watch + +# Run with coverage report +npm run test:coverage + +# Run specific test file +npm test -- --testPathPattern="service-selection" + +# Run tests matching a pattern +npm test -- --testNamePattern="Next.js" +``` + +### Expected Output + +``` +# Detection +Test Suites: 9 passed, 9 total +Tests: 181 passed, 181 total + +# Node.js Production +Test Suites: 3 passed, 3 total +Tests: 83 passed, 83 total + +# Validation +Test Suites: 4 passed, 4 total +Tests: 165 passed, 165 total +``` + +--- + +## Test Structure + +``` +tests/ +├── detection/ # App type detection tests (181 tests) +│ ├── __tests__/ +│ │ ├── service-selection.test.js # Fixture-based tests +│ │ ├── highConfidence.test.js # Azure config file tests +│ │ ├── nodejs.test.js # Node.js framework tests +│ │ ├── python.test.js # Python framework tests +│ │ ├── dotnet.test.js # .NET framework tests +│ │ ├── java.test.js # Java framework tests +│ │ ├── static.test.js # Pure static site tests +│ │ ├── multiService.test.js # Monorepo tests +│ │ └── confidence.test.js # Confidence level tests +│ ├── fixtures/ +│ │ ├── expectations.json # Expected results for each fixture +│ │ └── projects/ # 21 real project fixtures +│ ├── src/ +│ │ ├── appTypeDetector.js # Core detection algorithm +│ │ ├── filePatterns.js # File pattern constants +│ │ └── serviceMapping.js # Azure service mappings +│ ├── utils/ +│ │ ├── fixture-loader.js # Loads fixtures and expectations +│ │ └── project-scanner.js # Scans real directories +│ ├── package.json +│ └── README.md +│ +├── nodejs-production/ # Node.js production tests (83 tests) +│ ├── __tests__/ +│ │ ├── expressValidator.test.js # Express.js validation +│ │ ├── dockerfileValidator.test.js # Dockerfile validation +│ │ └── integration.test.js # End-to-end tests +│ ├── fixtures/projects/ # Sample Express projects +│ ├── src/validators/ +│ │ ├── expressProductionValidator.js +│ │ └── dockerfileValidator.js +│ ├── package.json +│ └── README.md +│ +├── validation/ # Azure validation tests (165 tests) +│ ├── __tests__/ +│ │ ├── resourceNameValidator.test.js # Naming constraint tests +│ │ ├── bicepValidator.test.js # Bicep detection tests +│ │ ├── preflightValidator.test.js # Preflight utility tests +│ │ └── integration.test.js # End-to-end scenarios +│ ├── fixtures/ +│ │ ├── bicep/ # Sample Bicep files +│ │ └── projects/ # Sample project structures +│ ├── src/validators/ +│ │ ├── resourceNameValidator.js # Azure resource naming rules +│ │ ├── bicepValidator.js # Bicep file detection +│ │ └── preflightValidator.js # Preflight check utilities +│ ├── package.json +│ └── README.md +│ +└── README.md # This file +``` + +--- + +## Adding New Tests + +⚠️ **Only add tests to existing suites. Do not create new test areas.** + +### Adding Detection Tests + +1. **Create project directory:** + ```bash + mkdir -p tests/detection/fixtures/projects/my-new-app + ``` + +2. **Add config files** (only the files needed for detection): + ```bash + echo '{"dependencies":{"@remix-run/node":"^2.0.0"}}' > tests/detection/fixtures/projects/remix-app/package.json + ``` + +3. **Add expected result to `fixtures/expectations.json`:** + ```json + { + "remix-app": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Remix" + } + } + ``` + +4. **Run tests:** + ```bash + cd tests/detection && npm test + ``` + +### Adding Validation Tests + +Add tests to existing files in `tests/validation/__tests__/`: +- `resourceNameValidator.test.js` - Azure resource naming +- `bicepValidator.test.js` - Bicep file validation +- `preflightValidator.test.js` - Preflight checks +- `integration.test.js` - End-to-end scenarios + +### Adding Node.js Production Tests + +Add tests to existing files in `tests/nodejs-production/__tests__/`: +- `expressValidator.test.js` - Express.js checks +- `dockerfileValidator.test.js` - Dockerfile checks +- `integration.test.js` - Fixture-based tests + +--- + +## Test Coverage vs Skills + +### Test Suites Overview + +| Suite | Location | Tests | Skills Covered | +|-------|----------|-------|----------------| +| **Detection** | `tests/detection/` | 181 | `azure-deploy` | +| **Node.js Production** | `tests/nodejs-production/` | 83 | `azure-nodejs-production` | +| **Validation** | `tests/validation/` | 165 | `azure-validation`, `azure-deployment-preflight` | +| **Total** | | **429** | | + +### Deployment Skills Coverage (Detection Suite) + +All deployment scenarios route to the consolidated `azure-deploy` skill with specialized reference guides: + +| Service | Tested | Fixtures | Reference Guide | +|---------|--------|----------|-----------------| +| ✅ Azure Developer CLI (azd) | Yes | `azd-fullstack` | Main orchestrator, `azure.yaml` detection | +| ✅ Container Apps | Yes | `docker-node`, `docker-compose-multi` | `reference/container-apps.md` | +| ✅ AKS | ⚠️ Partial | - | `reference/aks.md` - Needs Kubernetes fixtures | +| ✅ App Service | Yes | `express-api`, `flask-app`, `django-app`, `fastapi-app`, `dotnet-webapp`, `spring-boot`, `nextjs-ssr` | `reference/app-service.md` | +| ✅ Azure Functions | Yes | `functions-node`, `functions-python`, `functions-dotnet` | `reference/functions.md` | +| ✅ Static Web Apps | Yes | `react-vite`, `vue-vite`, `angular`, `nextjs-static`, `gatsby`, `astro`, `plain-html` | `reference/static-web-apps.md` | + +### Framework Detection Coverage + +| Language | Framework | Tested | Fixture | +|----------|-----------|--------|---------| +| **Node.js** | | | | +| | React (Vite) | ✅ | `react-vite` | +| | Vue (Vite) | ✅ | `vue-vite` | +| | Angular | ✅ | `angular` | +| | Next.js (SSG) | ✅ | `nextjs-static` | +| | Next.js (SSR) | ✅ | `nextjs-ssr` | +| | Nuxt | ✅ | Unit tests | +| | Gatsby | ✅ | `gatsby` | +| | Astro | ✅ | `astro` | +| | Express | ✅ | `express-api` | +| | NestJS | ✅ | Unit tests | +| | Fastify | ✅ | Unit tests | +| | Koa | ✅ | Unit tests | +| | Hapi | ✅ | Unit tests | +| | Svelte | ⚠️ | Needs fixture | +| | Remix | ❌ | Not implemented | +| | SvelteKit | ❌ | Not implemented | +| **Python** | | | | +| | Flask | ✅ | `flask-app` | +| | Django | ✅ | `django-app` | +| | FastAPI | ✅ | `fastapi-app` | +| | Azure Functions | ✅ | `functions-python` | +| **.NET** | | | | +| | ASP.NET Core | ✅ | `dotnet-webapp` | +| | Blazor WebAssembly | ✅ | Unit tests | +| | Azure Functions | ✅ | `functions-dotnet` | +| **Java** | | | | +| | Spring Boot | ✅ | `spring-boot` | +| | Azure Functions | ✅ | Unit tests | +| | Quarkus | ❌ | Not implemented | +| | Micronaut | ❌ | Not implemented | +| **Static** | | | | +| | Plain HTML | ✅ | `plain-html` | + +### High-Confidence Signal Coverage + +| Signal | Tested | Service | +|--------|--------|---------| +| `azure.yaml` | ✅ | Azure Developer CLI | +| `host.json` | ✅ | Azure Functions | +| `function.json` | ✅ | Azure Functions | +| `function_app.py` | ✅ | Azure Functions | +| `local.settings.json` | ⚠️ | Azure Functions | +| `staticwebapp.config.json` | ✅ | Static Web Apps | +| `swa-cli.config.json` | ✅ | Static Web Apps | +| `Dockerfile` | ✅ | Container Apps | +| `docker-compose.yml` | ✅ | Container Apps | +| `docker-compose.yaml` | ✅ | Container Apps | + +### Validation Suite Coverage + +| Category | Tests | Coverage | +|----------|-------|----------| +| **Resource Naming** | ~60 | Storage Account, Key Vault, Container Registry, Container App, App Service, Function App, Resource Group, Cosmos DB | +| **Bicep Detection** | ~50 | Target scope, parameter files, azd projects, security checks | +| **Preflight Checks** | ~35 | Tool validation, auth parsing, report generation | +| **Integration** | ~20 | End-to-end scenarios | + +### Node.js Production Suite Coverage + +| Category | Tests | Coverage | +|----------|-------|----------| +| **Express Validation** | ~40 | Trust proxy, health endpoints, cookie security, port/host binding | +| **Dockerfile Validation** | ~25 | NODE_ENV, HEALTHCHECK, non-root user, base image | +| **Integration** | ~18 | Fixture-based end-to-end tests | + +### Skills NOT Currently Tested + +These skills don't have automated tests (they're service-specific, not detection/validation skills): + +| Skill | Reason | +|-------|--------| +| `appinsights-instrumentation` | Monitoring, not deployment | +| `azure-ai` | AI services configuration | +| `azure-cli` | CLI usage guidance | +| `azure-cosmos-db` | Database service | +| `azure-cost-optimization` | Cost analysis | +| `azure-diagnostics` | Troubleshooting | +| `azure-keyvault-expiration-audit` | Security audit | +| `azure-mcp` | MCP tool usage | +| `azure-networking` | Network configuration | +| `azure-observability` | Monitoring setup | +| `azure-postgres-entra-rbac-setup` | Database security | +| `azure-redis` | Cache service | +| `azure-resource-visualizer` | Resource visualization | +| `azure-role-selector` | IAM configuration | +| `azure-security` | Security guidance | +| `azure-security-hardening` | Security hardening | +| `azure-sql-database` | Database service | +| `azure-storage` | Storage service | +| `microsoft-foundry` | AI platform | + +--- + +## CI Workflows + +| Workflow | File | Triggers | +|----------|------|----------| +| Detection | `test-detection.yml` | `azure-deploy` skill, `tests/detection/` | +| Node.js Production | `test-nodejs-production.yml` | `azure-nodejs-production` skill, `tests/nodejs-production/` | +| Validation | `test-validation.yml` | `azure-validation`, `azure-deployment-preflight` skills, `tests/validation/` | + +All workflows: +- Run on push to `main` and PRs (with path filters) +- Test on Node.js 20 and 22 +- Upload coverage to Codecov +- Support manual trigger via `workflow_dispatch` + +--- + +## Detection Logic Reference + +### Priority Order + +1. **High Confidence** - Azure config files (`azure.yaml`, `host.json`, `Dockerfile`) +2. **Multi-Service** - Monorepo patterns (`frontend/`, `backend/`, multiple `package.json`) +3. **Framework** - Language-specific frameworks +4. **Static** - Pure HTML sites + +### Confidence Levels + +| Level | Criteria | Example | +|-------|----------|---------| +| **HIGH** | Azure config file found | `azure.yaml`, `host.json`, `Dockerfile` | +| **MEDIUM** | Framework detected | Express in package.json | +| **LOW** | Language only, no framework | Generic package.json | + +### Service Mappings + +All services route to `azure-deploy` with specialized reference guides: + +| Detection | Service | Skill Route | Reference Guide | +|-----------|---------|-------------|-----------------| +| `azure.yaml` | Azure Developer CLI | `azure-deploy` | - | +| `host.json`, `function.json` | Azure Functions | `azure-deploy` | `reference/functions.md` | +| `staticwebapp.config.json` | Static Web Apps | `azure-deploy` | `reference/static-web-apps.md` | +| `Dockerfile` | Container Apps | `azure-deploy` | `reference/container-apps.md` | +| React, Vue, Angular, Gatsby, Astro | Static Web Apps | `azure-deploy` | `reference/static-web-apps.md` | +| Next.js (SSR), Express, NestJS | App Service | `azure-deploy` | `reference/app-service.md` | +| Flask, Django, FastAPI | App Service | `azure-deploy` | `reference/app-service.md` | +| ASP.NET Core | App Service | `azure-deploy` | `reference/app-service.md` | +| Spring Boot | App Service | `azure-deploy` | `reference/app-service.md` | + +--- + +## Related Documentation + +- [`azure-deploy` SKILL.md](../plugin/skills/azure-deploy/SKILL.md) - Detection logic source +- [Detection README](detection/README.md) - Detection suite details +- [Node.js Production README](nodejs-production/README.md) - Production validation details +- [Validation README](validation/README.md) - Azure validation details diff --git a/tests/detection/README.md b/tests/detection/README.md new file mode 100644 index 000000000..3f1df260b --- /dev/null +++ b/tests/detection/README.md @@ -0,0 +1,209 @@ +# Azure Skill Detection Tests + +Automated tests for the app type detection logic used by Azure deployment skills. When a user asks "what Azure service should I use?", these tests verify the detection logic correctly identifies the appropriate Azure service based on project files and structure. + +--- + +## Setup + +### Prerequisites + +- Node.js 18+ installed +- npm or yarn + +### Installation + +```bash +cd tests/detection +npm install +``` + +--- + +## Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode (re-runs on file changes) +npm run test:watch + +# Run with coverage report +npm run test:coverage + +# Run specific test file +npm test -- --testPathPattern="service-selection" + +# Run tests matching a pattern +npm test -- --testNamePattern="Next.js" +``` + +### Expected Output + +``` +Test Suites: 12 passed, 12 total +Tests: 264 passed, 264 total +Time: 0.3 s +``` + +--- + +## Test Structure + +``` +tests/detection/ +├── fixtures/ +│ ├── expectations.json # Expected results for each fixture +│ └── projects/ # 21 real project fixtures +│ ├── react-vite/ +│ ├── angular/ +│ ├── nextjs-ssr/ +│ ├── functions-node/ +│ ├── docker-node/ +│ └── ... +│ +├── src/ # Detection logic implementation +│ ├── appTypeDetector.js # Core detection algorithm +│ ├── filePatterns.js # File pattern constants +│ └── serviceMapping.js # Azure service mappings +│ +├── utils/ +│ ├── fixture-loader.js # Loads fixtures and expectations +│ └── project-scanner.js # Scans real directories +│ +├── __tests__/ # Test suites +│ ├── service-selection.test.js # Fixture-based tests (iterates over projects/) +│ ├── highConfidence.test.js # Azure config file tests +│ ├── nodejs.test.js # Node.js framework tests +│ ├── python.test.js # Python framework tests +│ ├── dotnet.test.js # .NET framework tests +│ ├── java.test.js # Java framework tests +│ ├── static.test.js # Pure static site tests +│ ├── multiService.test.js # Monorepo tests +│ └── confidence.test.js # Confidence level tests +│ +├── package.json +├── jest.config.js +└── README.md +``` + +--- + +## Adding New Tests + +### Option 1: Add a Fixture (Recommended) + +1. **Create project directory:** + ```bash + mkdir -p fixtures/projects/my-new-app + ``` + +2. **Add config files** (only the files needed for detection): + ```bash + # Example: Remix app + echo '{"dependencies":{"@remix-run/node":"^2.0.0"}}' > fixtures/projects/remix-app/package.json + ``` + +3. **Add expected result to `fixtures/expectations.json`:** + ```json + { + "remix-app": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Remix" + } + } + ``` + +4. **Update detection logic** (if new pattern): + - Add pattern to `src/filePatterns.js` + - Add detection logic to `src/appTypeDetector.js` + +5. **Run tests:** + ```bash + npm test + ``` + +### Option 2: Add a Unit Test + +Add tests to the appropriate file in `__tests__/`: + +```javascript +// In nodejs.test.js +test('detects Remix and recommends App Service', () => { + const project = { + files: ['package.json', 'remix.config.js'], + contents: { + 'package.json': { dependencies: { '@remix-run/node': '^2.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Remix'); +}); +``` + +--- + +## Test Coverage + +### Deployment Skills Coverage + +All deployment scenarios route to the consolidated `azure-deploy` skill: + +| Service | Tested | Fixtures | +|---------|--------|----------| +| ✅ Azure Developer CLI (azd) | Yes | `azd-fullstack` | +| ✅ Container Apps | Yes | `docker-node`, `docker-compose-multi` | +| ✅ AKS | ⚠️ Partial | - | +| ✅ App Service | Yes | `express-api`, `flask-app`, `django-app`, etc. | +| ✅ Azure Functions | Yes | `functions-node`, `functions-python`, `functions-dotnet` | +| ✅ Static Web Apps | Yes | `react-vite`, `vue-vite`, `angular`, etc. | + +### Framework Detection Coverage + +| Language | Framework | Tested | +|----------|-----------|--------| +| **Node.js** | React, Vue, Angular, Next.js, Express, etc. | ✅ | +| **Python** | Flask, Django, FastAPI | ✅ | +| **.NET** | ASP.NET Core, Blazor | ✅ | +| **Java** | Spring Boot | ✅ | +| **Static** | Plain HTML | ✅ | + +### High-Confidence Signal Coverage + +| Signal | Tested | Service | +|--------|--------|---------| +| `azure.yaml` | ✅ | Azure Developer CLI | +| `host.json` | ✅ | Azure Functions | +| `staticwebapp.config.json` | ✅ | Static Web Apps | +| `Dockerfile` | ✅ | Container Apps | + +--- + +## Detection Logic Reference + +### Priority Order + +1. **High Confidence** - Azure config files (`azure.yaml`, `host.json`, `Dockerfile`) +2. **Multi-Service** - Monorepo patterns (`frontend/`, `backend/`, multiple `package.json`) +3. **Framework** - Language-specific frameworks +4. **Static** - Pure HTML sites + +### Confidence Levels + +| Level | Criteria | Example | +|-------|----------|---------| +| **HIGH** | Azure config file found | `azure.yaml`, `host.json`, `Dockerfile` | +| **MEDIUM** | Framework detected | Express in package.json | +| **LOW** | Language only, no framework | Generic package.json | + +--- + +## Related Documentation + +- [`azure-deploy` SKILL.md](../../plugin/skills/azure-deploy/SKILL.md) - Detection logic source diff --git a/tests/detection/__tests__/confidence.test.js b/tests/detection/__tests__/confidence.test.js new file mode 100644 index 000000000..4630d57a3 --- /dev/null +++ b/tests/detection/__tests__/confidence.test.js @@ -0,0 +1,267 @@ +/** + * Confidence Level Tests + * + * Tests to verify that confidence levels are correctly assigned + * based on the strength of detection signals. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Confidence Level Assignment', () => { + describe('HIGH Confidence', () => { + test('azure.yaml provides HIGH confidence', () => { + const project = { + files: ['azure.yaml'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('host.json provides HIGH confidence', () => { + const project = { + files: ['host.json'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('function.json provides HIGH confidence', () => { + const project = { + files: ['function.json'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('function_app.py provides HIGH confidence', () => { + const project = { + files: ['function_app.py', 'requirements.txt'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('staticwebapp.config.json provides HIGH confidence', () => { + const project = { + files: ['staticwebapp.config.json'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('Dockerfile provides HIGH confidence', () => { + const project = { + files: ['Dockerfile'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('.NET csproj with AzureFunctionsVersion provides HIGH confidence', () => { + const project = { + files: ['App.csproj'], + contents: { + 'App.csproj': 'v4' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('MEDIUM Confidence', () => { + test('Next.js config provides MEDIUM confidence', () => { + const project = { + files: ['package.json', 'next.config.js'], + contents: { + 'package.json': { dependencies: { next: '14.0.0' } }, + 'next.config.js': '' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Angular config provides MEDIUM confidence', () => { + const project = { + files: ['package.json', 'angular.json'], + contents: { + 'package.json': { dependencies: {} } + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Express dependency provides MEDIUM confidence', () => { + const project = { + files: ['package.json'], + contents: { + 'package.json': { dependencies: { express: '^4.0.0' } } + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Flask dependency provides MEDIUM confidence', () => { + const project = { + files: ['requirements.txt'], + contents: { + 'requirements.txt': 'flask==3.0.0' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Spring Boot provides MEDIUM confidence', () => { + const project = { + files: ['pom.xml'], + contents: { + 'pom.xml': 'spring-boot-starter-web' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Pure static HTML provides MEDIUM confidence', () => { + const project = { + files: ['index.html'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('Multi-service detection provides MEDIUM confidence', () => { + const project = { + files: ['frontend/package.json', 'backend/package.json'], + contents: {}, + directories: ['frontend', 'backend'] + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + }); + + describe('LOW Confidence', () => { + test('Empty project provides LOW confidence', () => { + const project = { + files: [], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('Generic Node.js without framework provides LOW confidence', () => { + const project = { + files: ['package.json', 'index.js'], + contents: { + 'package.json': { name: 'my-app' } + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('Generic Python without framework provides LOW confidence', () => { + const project = { + files: ['requirements.txt', 'main.py'], + contents: { + 'requirements.txt': 'requests\nnumpy' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('Generic .NET without framework provides LOW confidence', () => { + const project = { + files: ['App.csproj'], + contents: { + 'App.csproj': '' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('Generic Java without framework provides LOW confidence', () => { + const project = { + files: ['pom.xml'], + contents: { + 'pom.xml': 'my-app' + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('Non-code files only provides LOW confidence', () => { + const project = { + files: ['README.md', 'LICENSE', '.gitignore', 'docs/guide.md'], + contents: {} + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + }); + + describe('Confidence Hierarchy', () => { + test('HIGH > MEDIUM: config file overrides framework detection', () => { + // Dockerfile (HIGH) should override Express detection (MEDIUM) + const project = { + files: ['Dockerfile', 'package.json'], + contents: { + 'package.json': { dependencies: { express: '^4.0.0' } } + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('MEDIUM > LOW: framework detection overrides generic language', () => { + // Express (MEDIUM) should result in MEDIUM, not LOW + const project = { + files: ['package.json'], + contents: { + 'package.json': { dependencies: { express: '^4.0.0' } } + } + }; + + const result = detectAppType(project); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + }); +}); diff --git a/tests/detection/__tests__/dotnet.test.js b/tests/detection/__tests__/dotnet.test.js new file mode 100644 index 000000000..e4ed4e5bb --- /dev/null +++ b/tests/detection/__tests__/dotnet.test.js @@ -0,0 +1,242 @@ +/** + * .NET Framework Detection Tests + * + * Tests for detecting .NET frameworks and routing to appropriate Azure services. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('.NET Framework Detection', () => { + describe('Azure Functions', () => { + test('detects Azure Functions from csproj with AzureFunctionsVersion', () => { + const project = { + files: ['MyFunctions.csproj', 'Function1.cs'], + contents: { + 'MyFunctions.csproj': ` + + + net8.0 + v4 + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.framework).toBe('Azure Functions (.NET)'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects isolated worker model Functions', () => { + const project = { + files: ['FunctionApp.csproj', 'Program.cs'], + contents: { + 'FunctionApp.csproj': ` + + + net8.0 + v4 + Exe + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.framework).toBe('Azure Functions (.NET)'); + }); + }); + + describe('Blazor WebAssembly', () => { + test('detects Blazor WebAssembly and recommends Static Web Apps', () => { + const project = { + files: ['BlazorApp.csproj', 'Program.cs', 'wwwroot/index.html'], + contents: { + 'BlazorApp.csproj': ` + + + net8.0 + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.framework).toBe('Blazor WebAssembly'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + }); + + describe('ASP.NET Core', () => { + test('detects ASP.NET Core Web API and recommends App Service', () => { + const project = { + files: ['WebApi.csproj', 'Program.cs', 'Controllers/WeatherController.cs'], + contents: { + 'WebApi.csproj': ` + + + net8.0 + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('ASP.NET Core'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects ASP.NET Core MVC application', () => { + const project = { + files: ['WebApp.csproj', 'Program.cs', 'Views/Home/Index.cshtml'], + contents: { + 'WebApp.csproj': ` + + + net8.0 + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('ASP.NET Core'); + }); + + test('detects minimal API ASP.NET Core', () => { + const project = { + files: ['MinimalApi.csproj', 'Program.cs'], + contents: { + 'MinimalApi.csproj': ` + + + net8.0 + + +` + } + }; + + // Note: Web SDK implies ASP.NET Core + const result = detectAppType(project); + + // Without explicit Microsoft.AspNetCore references, might be lower confidence + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + }); + }); + + describe('Generic .NET', () => { + test('detects generic .NET console app with low confidence', () => { + const project = { + files: ['ConsoleApp.csproj', 'Program.cs'], + contents: { + 'ConsoleApp.csproj': ` + + + Exe + net8.0 + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + + test('detects .NET solution file', () => { + const project = { + files: ['MySolution.sln', 'src/WebApp/WebApp.csproj'], + contents: {} + }; + + const result = detectAppType(project); + + // Solution without accessible csproj content + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + }); + + describe('Priority Order', () => { + test('Functions takes priority over ASP.NET Core', () => { + const project = { + files: ['FunctionApp.csproj'], + contents: { + 'FunctionApp.csproj': ` + + + v4 + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + }); + + test('Blazor WebAssembly takes priority over ASP.NET Core', () => { + const project = { + files: ['BlazorApp.csproj'], + contents: { + 'BlazorApp.csproj': ` + + + + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Blazor WebAssembly'); + }); + }); +}); diff --git a/tests/detection/__tests__/highConfidence.test.js b/tests/detection/__tests__/highConfidence.test.js new file mode 100644 index 000000000..59b6226ee --- /dev/null +++ b/tests/detection/__tests__/highConfidence.test.js @@ -0,0 +1,174 @@ +/** + * High Confidence Detection Tests + * + * Tests for Azure configuration files that provide high-confidence + * service recommendations (azure.yaml, host.json, Dockerfile, etc.) + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('High Confidence Detection', () => { + describe('Azure Developer CLI (azure.yaml)', () => { + test('detects azure.yaml and recommends azd', () => { + const project = { + files: ['azure.yaml', 'package.json', 'src/index.js'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.skill).toBe(SKILL_ROUTES.DEPLOY); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + expect(result.reason).toContain('azure.yaml'); + }); + + test('azure.yaml takes priority over other indicators', () => { + const project = { + files: ['azure.yaml', 'Dockerfile', 'host.json', 'package.json'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('Azure Functions', () => { + test('detects host.json and routes to azure-deploy', () => { + const project = { + files: ['host.json', 'package.json'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects function.json and routes to azure-deploy', () => { + const project = { + files: ['function.json', 'index.js'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects function_app.py (Python v2 model)', () => { + const project = { + files: ['function_app.py', 'requirements.txt'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('Static Web Apps', () => { + test('detects staticwebapp.config.json', () => { + const project = { + files: ['staticwebapp.config.json', 'index.html', 'package.json'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects swa-cli.config.json', () => { + const project = { + files: ['swa-cli.config.json', 'package.json'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('Container Apps', () => { + test('detects Dockerfile and routes to azure-deploy', () => { + const project = { + files: ['Dockerfile', 'package.json', 'src/index.js'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.CONTAINER_APPS); + expect(result.skill).toBe(SKILL_ROUTES.CONTAINER_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects docker-compose.yml', () => { + const project = { + files: ['docker-compose.yml', 'backend/Dockerfile', 'frontend/Dockerfile'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.CONTAINER_APPS); + expect(result.skill).toBe(SKILL_ROUTES.CONTAINER_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects docker-compose.yaml (alternative extension)', () => { + const project = { + files: ['docker-compose.yaml', 'app/Dockerfile'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.CONTAINER_APPS); + expect(result.skill).toBe(SKILL_ROUTES.CONTAINER_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('Priority Order', () => { + test('Functions takes priority over Container Apps when both present', () => { + const project = { + files: ['host.json', 'Dockerfile'], + contents: {} + }; + + const result = detectAppType(project); + + // Functions indicators should be checked before Dockerfile + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + }); + + test('SWA config takes priority over Dockerfile', () => { + const project = { + files: ['staticwebapp.config.json', 'Dockerfile'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + }); + }); +}); diff --git a/tests/detection/__tests__/java.test.js b/tests/detection/__tests__/java.test.js new file mode 100644 index 000000000..f694c9d08 --- /dev/null +++ b/tests/detection/__tests__/java.test.js @@ -0,0 +1,212 @@ +/** + * Java Framework Detection Tests + * + * Tests for detecting Java frameworks and routing to appropriate Azure services. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Java Framework Detection', () => { + describe('Azure Functions', () => { + test('detects Azure Functions from pom.xml dependency', () => { + const project = { + files: ['pom.xml', 'src/main/java/Function.java'], + contents: { + 'pom.xml': ` + + + + com.microsoft.azure.functions + azure-functions-java-library + 3.0.0 + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.framework).toBe('Azure Functions (Java)'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects Azure Functions from build.gradle', () => { + const project = { + files: ['build.gradle', 'src/main/java/Function.java'], + contents: { + 'build.gradle': ` +plugins { + id 'java' + id 'com.microsoft.azure.azurefunctions' version '1.12.0' +} + +dependencies { + implementation 'com.microsoft.azure.functions:azure-functions-java-library:3.0.0' +} +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.framework).toBe('Azure Functions (Java)'); + }); + }); + + describe('Spring Boot', () => { + test('detects Spring Boot from pom.xml parent', () => { + const project = { + files: ['pom.xml', 'src/main/java/Application.java'], + contents: { + 'pom.xml': ` + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + org.springframework.boot + spring-boot-starter-web + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('Spring Boot'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects Spring Boot from build.gradle', () => { + const project = { + files: ['build.gradle', 'src/main/java/Application.java'], + contents: { + 'build.gradle': ` +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.0' +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +} +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Spring Boot'); + }); + + test('detects Spring Boot from build.gradle.kts (Kotlin DSL)', () => { + const project = { + files: ['build.gradle.kts', 'src/main/kotlin/Application.kt'], + contents: { + 'build.gradle.kts': ` +plugins { + kotlin("jvm") version "1.9.0" + id("org.springframework.boot") version "3.2.0" +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") +} +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Spring Boot'); + }); + }); + + describe('Generic Java', () => { + test('detects generic Java Maven project with low confidence', () => { + const project = { + files: ['pom.xml', 'src/main/java/App.java'], + contents: { + 'pom.xml': ` + + 4.0.0 + com.example + my-app + 1.0.0 + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + expect(result.framework).toBeUndefined(); + }); + + test('detects generic Java Gradle project with low confidence', () => { + const project = { + files: ['build.gradle', 'src/main/java/Main.java'], + contents: { + 'build.gradle': ` +plugins { + id 'java' + id 'application' +} + +mainClassName = 'Main' +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + }); + + describe('Priority Order', () => { + test('Functions takes priority over Spring Boot', () => { + const project = { + files: ['pom.xml'], + contents: { + 'pom.xml': ` + + + + com.microsoft.azure.functions + azure-functions-java-library + + + org.springframework.boot + spring-boot-starter-web + + + +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + }); + }); +}); diff --git a/tests/detection/__tests__/multiService.test.js b/tests/detection/__tests__/multiService.test.js new file mode 100644 index 000000000..7fbe5307e --- /dev/null +++ b/tests/detection/__tests__/multiService.test.js @@ -0,0 +1,216 @@ +/** + * Multi-Service Architecture Detection Tests + * + * Tests for detecting monorepos and multi-service applications + * that should use Azure Developer CLI with Infrastructure as Code. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Multi-Service Architecture Detection', () => { + describe('Monorepo Detection', () => { + test('detects frontend + backend directory structure', () => { + const project = { + files: [ + 'frontend/package.json', + 'backend/package.json', + 'README.md' + ], + contents: {}, + directories: ['frontend', 'backend'] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.skill).toBe(SKILL_ROUTES.DEPLOY); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + expect(result.reason).toContain('multi-service'); + }); + + test('detects web + api directory structure', () => { + const project = { + files: [ + 'web/package.json', + 'api/package.json' + ], + contents: {}, + directories: ['web', 'api'] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.reason).toContain('multi-service'); + }); + + test('detects packages directory (monorepo)', () => { + const project = { + files: [ + 'packages/ui/package.json', + 'packages/api/package.json', + 'packages/shared/package.json' + ], + contents: {}, + directories: ['packages', 'apps'] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + }); + + test('detects apps directory (Turborepo/Nx style)', () => { + const project = { + files: [ + 'apps/web/package.json', + 'apps/api/package.json' + ], + contents: {}, + directories: ['apps', 'packages'] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + }); + + test('detects services directory (microservices)', () => { + const project = { + files: [ + 'services/auth/package.json', + 'services/orders/package.json', + 'services/gateway/package.json' + ], + contents: {}, + directories: ['services', 'shared'] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + }); + }); + + describe('Multiple package.json Detection', () => { + test('detects multiple package.json in subdirectories', () => { + const project = { + files: [ + 'client/package.json', + 'server/package.json' + ], + contents: {}, + directories: [] + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.reason).toContain('multi-service'); + }); + + test('single package.json is NOT multi-service', () => { + const project = { + files: ['package.json', 'src/index.js'], + contents: { + 'package.json': { dependencies: { express: '^4.0.0' } } + }, + directories: [] + }; + + const result = detectAppType(project); + + // Should be single service, not multi-service + expect(result.service).not.toBe(AZURE_SERVICES.AZD); + expect(result.framework).toBe('Express'); + }); + + test('root package.json with subdirectory package.json is multi-service', () => { + const project = { + files: [ + 'package.json', + 'apps/web/package.json', + 'apps/api/package.json' + ], + contents: {}, + directories: ['apps'] + }; + + const result = detectAppType(project); + + // Multiple package.json files detected + expect(result.service).toBe(AZURE_SERVICES.AZD); + }); + }); + + describe('Priority Over Framework Detection', () => { + test('azure.yaml takes priority over multi-service', () => { + const project = { + files: [ + 'azure.yaml', + 'frontend/package.json', + 'backend/package.json' + ], + contents: {}, + directories: ['frontend', 'backend'] + }; + + const result = detectAppType(project); + + // azure.yaml is high confidence and checked first + expect(result.service).toBe(AZURE_SERVICES.AZD); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + expect(result.reason).toContain('azure.yaml'); + }); + + test('Dockerfile takes priority over multi-service directories', () => { + const project = { + files: [ + 'Dockerfile', + 'frontend/package.json', + 'backend/package.json' + ], + contents: {}, + directories: ['frontend', 'backend'] + }; + + const result = detectAppType(project); + + // Dockerfile is high confidence + expect(result.service).toBe(AZURE_SERVICES.CONTAINER_APPS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + }); + + describe('Edge Cases', () => { + test('single service directory is NOT multi-service', () => { + const project = { + files: ['api/package.json'], + contents: {}, + directories: ['api'] + }; + + const result = detectAppType(project); + + // Only one service directory, should not trigger multi-service + expect(result.service).not.toBe(AZURE_SERVICES.AZD); + }); + + test('nested directories without recognized names', () => { + const project = { + files: [ + 'src/package.json', + 'lib/package.json' + ], + contents: {}, + directories: ['src', 'lib'] + }; + + const result = detectAppType(project); + + // Multiple package.json should still trigger + expect(result.service).toBe(AZURE_SERVICES.AZD); + }); + }); +}); diff --git a/tests/detection/__tests__/nodejs.test.js b/tests/detection/__tests__/nodejs.test.js new file mode 100644 index 000000000..aef610a9d --- /dev/null +++ b/tests/detection/__tests__/nodejs.test.js @@ -0,0 +1,318 @@ +/** + * Node.js Framework Detection Tests + * + * Tests for detecting Node.js/JavaScript/TypeScript frameworks + * and routing to appropriate Azure services. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Node.js Framework Detection', () => { + describe('Next.js', () => { + test('detects Next.js with SSR and recommends App Service', () => { + const project = { + files: ['package.json', 'next.config.js'], + contents: { + 'package.json': { dependencies: { next: '^14.0.0', react: '^18.0.0' } }, + 'next.config.js': 'module.exports = { reactStrictMode: true };' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('Next.js (SSR)'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects Next.js with static export and recommends Static Web Apps', () => { + const project = { + files: ['package.json', 'next.config.js'], + contents: { + 'package.json': { dependencies: { next: '^14.0.0' } }, + 'next.config.js': "module.exports = { output: 'export' };" + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.framework).toBe('Next.js (SSG)'); + }); + + test('detects next.config.mjs', () => { + const project = { + files: ['package.json', 'next.config.mjs'], + contents: { + 'package.json': { dependencies: { next: '^14.0.0' } }, + 'next.config.mjs': 'export default { reactStrictMode: true };' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Next.js (SSR)'); + }); + + test('detects next.config.ts', () => { + const project = { + files: ['package.json', 'next.config.ts'], + contents: { + 'package.json': { dependencies: { next: '^14.0.0' } }, + 'next.config.ts': 'export default { reactStrictMode: true };' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + }); + }); + + describe('Nuxt', () => { + test('detects Nuxt with SSR and recommends App Service', () => { + const project = { + files: ['package.json', 'nuxt.config.ts'], + contents: { + 'package.json': { dependencies: { nuxt: '^3.0.0' } }, + 'nuxt.config.ts': 'export default defineNuxtConfig({ });' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Nuxt (SSR)'); + }); + + test('detects Nuxt with static target and recommends Static Web Apps', () => { + const project = { + files: ['package.json', 'nuxt.config.ts'], + contents: { + 'package.json': { dependencies: { nuxt: '^3.0.0' } }, + 'nuxt.config.ts': "export default defineNuxtConfig({ ssr: false });" + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Nuxt (Static)'); + }); + }); + + describe('Angular', () => { + test('detects Angular and recommends Static Web Apps', () => { + const project = { + files: ['package.json', 'angular.json'], + contents: { + 'package.json': { dependencies: { '@angular/core': '^17.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.framework).toBe('Angular'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + }); + + describe('Vite-based (React, Vue, Svelte)', () => { + test('detects Vite with React', () => { + const project = { + files: ['package.json', 'vite.config.ts'], + contents: { + 'package.json': { + dependencies: { react: '^18.0.0' }, + devDependencies: { vite: '^5.0.0' } + } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('React (Vite)'); + }); + + test('detects Vite with Vue', () => { + const project = { + files: ['package.json', 'vite.config.js'], + contents: { + 'package.json': { + dependencies: { vue: '^3.0.0' }, + devDependencies: { vite: '^5.0.0' } + } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Vue (Vite)'); + }); + + test('detects Vite with Svelte', () => { + const project = { + files: ['package.json', 'vite.config.js'], + contents: { + 'package.json': { + dependencies: { svelte: '^4.0.0' }, + devDependencies: { vite: '^5.0.0' } + } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Svelte (Vite)'); + }); + + test('detects plain Vite without specific framework', () => { + const project = { + files: ['package.json', 'vite.config.mjs'], + contents: { + 'package.json': { + devDependencies: { vite: '^5.0.0' } + } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Vite'); + }); + }); + + describe('Gatsby', () => { + test('detects Gatsby and recommends Static Web Apps', () => { + const project = { + files: ['package.json', 'gatsby-config.js'], + contents: { + 'package.json': { dependencies: { gatsby: '^5.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Gatsby'); + }); + }); + + describe('Astro', () => { + test('detects Astro and recommends Static Web Apps', () => { + const project = { + files: ['package.json', 'astro.config.mjs'], + contents: { + 'package.json': { dependencies: { astro: '^4.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Astro'); + }); + }); + + describe('NestJS', () => { + test('detects NestJS and recommends App Service', () => { + const project = { + files: ['package.json', 'nest-cli.json'], + contents: { + 'package.json': { dependencies: { '@nestjs/core': '^10.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('NestJS'); + }); + }); + + describe('Express and other server frameworks', () => { + test('detects Express and recommends App Service', () => { + const project = { + files: ['package.json', 'src/index.js'], + contents: { + 'package.json': { dependencies: { express: '^4.18.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Express'); + }); + + test('detects Fastify and recommends App Service', () => { + const project = { + files: ['package.json', 'src/server.js'], + contents: { + 'package.json': { dependencies: { fastify: '^4.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Fastify'); + }); + + test('detects Koa and recommends App Service', () => { + const project = { + files: ['package.json', 'app.js'], + contents: { + 'package.json': { dependencies: { koa: '^2.14.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Koa'); + }); + + test('detects Hapi and recommends App Service', () => { + const project = { + files: ['package.json', 'server.js'], + contents: { + 'package.json': { dependencies: { '@hapi/hapi': '^21.0.0' } } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('@hapi/hapi'); + }); + }); + + describe('Generic Node.js', () => { + test('detects generic Node.js project with low confidence', () => { + const project = { + files: ['package.json', 'index.js'], + contents: { + 'package.json': { name: 'my-app', version: '1.0.0' } + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + expect(result.framework).toBeUndefined(); + }); + }); +}); diff --git a/tests/detection/__tests__/python.test.js b/tests/detection/__tests__/python.test.js new file mode 100644 index 000000000..59268d04c --- /dev/null +++ b/tests/detection/__tests__/python.test.js @@ -0,0 +1,216 @@ +/** + * Python Framework Detection Tests + * + * Tests for detecting Python frameworks and routing to appropriate Azure services. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Python Framework Detection', () => { + describe('Azure Functions', () => { + test('detects function_app.py (v2 model) with high confidence', () => { + const project = { + files: ['function_app.py', 'requirements.txt', 'host.json'], + contents: { + 'requirements.txt': 'azure-functions\nrequests' + } + }; + + const result = detectAppType(project); + + // function_app.py should trigger high confidence detection + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.HIGH); + }); + + test('detects azure-functions dependency in requirements.txt', () => { + const project = { + files: ['requirements.txt', 'main.py'], + contents: { + 'requirements.txt': 'azure-functions==1.18.0\nrequests>=2.28.0' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + expect(result.skill).toBe(SKILL_ROUTES.FUNCTIONS); + expect(result.framework).toBe('Azure Functions (Python)'); + }); + + test('detects azure-functions in pyproject.toml', () => { + const project = { + files: ['pyproject.toml', 'function_app.py'], + contents: { + 'pyproject.toml': ` +[project] +name = "my-functions" +dependencies = [ + "azure-functions", +] +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.FUNCTIONS); + }); + }); + + describe('Flask', () => { + test('detects Flask and recommends App Service', () => { + const project = { + files: ['requirements.txt', 'app.py'], + contents: { + 'requirements.txt': 'flask==3.0.0\ngunicorn' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('Flask'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects Flask with version specifier', () => { + const project = { + files: ['requirements.txt', 'app.py'], + contents: { + 'requirements.txt': 'Flask>=2.0.0,<4.0.0\nWerkzeug' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Flask'); + }); + }); + + describe('Django', () => { + test('detects Django and recommends App Service', () => { + const project = { + files: ['requirements.txt', 'manage.py', 'mysite/settings.py'], + contents: { + 'requirements.txt': 'django==5.0.0\npsycopg2-binary' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('Django'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects Django in pyproject.toml', () => { + const project = { + files: ['pyproject.toml', 'manage.py'], + contents: { + 'pyproject.toml': ` +[project] +name = "my-django-app" +dependencies = [ + "django>=4.2", + "gunicorn", +] +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('Django'); + }); + }); + + describe('FastAPI', () => { + test('detects FastAPI and recommends App Service', () => { + const project = { + files: ['requirements.txt', 'main.py'], + contents: { + 'requirements.txt': 'fastapi==0.109.0\nuvicorn[standard]' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.skill).toBe(SKILL_ROUTES.APP_SERVICE); + expect(result.framework).toBe('FastAPI'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects FastAPI with tilde version specifier', () => { + const project = { + files: ['requirements.txt', 'app/main.py'], + contents: { + 'requirements.txt': 'fastapi~=0.100.0\npydantic' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.framework).toBe('FastAPI'); + }); + }); + + describe('Generic Python', () => { + test('detects generic Python with requirements.txt and low confidence', () => { + const project = { + files: ['requirements.txt', 'main.py'], + contents: { + 'requirements.txt': 'requests\nnumpy\npandas' + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + expect(result.framework).toBeUndefined(); + }); + + test('detects generic Python with pyproject.toml', () => { + const project = { + files: ['pyproject.toml', 'src/app.py'], + contents: { + 'pyproject.toml': ` +[project] +name = "my-app" +dependencies = ["requests", "pydantic"] +` + } + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.APP_SERVICE); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + }); + + describe('Framework Priority', () => { + test('FastAPI takes priority when multiple frameworks present', () => { + const project = { + files: ['requirements.txt', 'main.py'], + contents: { + 'requirements.txt': 'fastapi\nflask\ndjango' + } + }; + + const result = detectAppType(project); + + // FastAPI is checked first based on order in detection logic + expect(result.framework).toBe('FastAPI'); + }); + }); +}); diff --git a/tests/detection/__tests__/service-selection.test.js b/tests/detection/__tests__/service-selection.test.js new file mode 100644 index 000000000..4ad406b4b --- /dev/null +++ b/tests/detection/__tests__/service-selection.test.js @@ -0,0 +1,176 @@ +/** + * Service Selection Tests (Fixture-based) + * + * Tests that iterate over project fixtures and validate + * detection against expected results in expectations.json + */ + +const { detectProject, normalizeResult } = require('../utils/project-scanner'); +const { + listProjectFixtures, + getProjectPath, + loadExpectations, + validateFixturesHaveExpectations +} = require('../utils/fixture-loader'); + +describe('Azure Deploy - Service Selection (Fixtures)', () => { + const fixtures = listProjectFixtures(); + const expectations = loadExpectations(); + + // Validate test setup + describe('Test Setup Validation', () => { + it('should have fixtures defined', () => { + expect(fixtures.length).toBeGreaterThan(0); + }); + + it('should have expectations for all fixtures', () => { + const validation = validateFixturesHaveExpectations(); + if (!validation.valid) { + if (validation.missing.length > 0) { + console.warn('Missing expectations for fixtures:', validation.missing); + } + if (validation.extra.length > 0) { + console.warn('Extra expectations without fixtures:', validation.extra); + } + } + expect(validation.valid).toBe(true); + }); + }); + + // Test each fixture + describe('Service Detection', () => { + fixtures.forEach(fixtureName => { + const expected = expectations[fixtureName]; + + if (!expected) { + it.skip(`${fixtureName} - missing expectations`, () => {}); + return; + } + + it(`should detect ${fixtureName} as ${expected.service}`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + + expect(normalized.service).toBe(expected.service); + }); + }); + }); + + // Group tests by Azure service type + describe('Static Web Apps Detection', () => { + const swaFixtures = Object.entries(expectations) + .filter(([_, exp]) => exp.service === 'static-web-apps') + .map(([name]) => name); + + swaFixtures.forEach(fixtureName => { + if (!fixtures.includes(fixtureName)) return; + + it(`should classify ${fixtureName} as static-web-apps`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + expect(normalized.service).toBe('static-web-apps'); + }); + }); + }); + + describe('Azure Functions Detection', () => { + const functionsFixtures = Object.entries(expectations) + .filter(([_, exp]) => exp.service === 'functions') + .map(([name]) => name); + + functionsFixtures.forEach(fixtureName => { + if (!fixtures.includes(fixtureName)) return; + + it(`should classify ${fixtureName} as functions`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + expect(normalized.service).toBe('functions'); + }); + }); + }); + + describe('Container Apps Detection', () => { + const containerFixtures = Object.entries(expectations) + .filter(([_, exp]) => exp.service === 'container-apps') + .map(([name]) => name); + + containerFixtures.forEach(fixtureName => { + if (!fixtures.includes(fixtureName)) return; + + it(`should classify ${fixtureName} as container-apps`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + expect(normalized.service).toBe('container-apps'); + }); + }); + }); + + describe('App Service Detection', () => { + const appServiceFixtures = Object.entries(expectations) + .filter(([_, exp]) => exp.service === 'app-service') + .map(([name]) => name); + + appServiceFixtures.forEach(fixtureName => { + if (!fixtures.includes(fixtureName)) return; + + it(`should classify ${fixtureName} as app-service`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + expect(normalized.service).toBe('app-service'); + }); + }); + }); + + describe('Multi-Service (azd) Detection', () => { + const azdFixtures = Object.entries(expectations) + .filter(([_, exp]) => exp.service === 'azd') + .map(([name]) => name); + + azdFixtures.forEach(fixtureName => { + if (!fixtures.includes(fixtureName)) return; + + it(`should classify ${fixtureName} as azd`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + const normalized = normalizeResult(result); + expect(normalized.service).toBe('azd'); + }); + }); + }); + + // Confidence level tests + describe('Confidence Assessment', () => { + fixtures.forEach(fixtureName => { + const expected = expectations[fixtureName]; + if (!expected) return; + + it(`should assess ${fixtureName} with ${expected.confidence} confidence`, () => { + const projectPath = getProjectPath(fixtureName); + const result = detectProject(projectPath); + expect(result.confidence).toBe(expected.confidence); + }); + }); + }); + + // Framework detection tests (where applicable) + describe('Framework Detection', () => { + const fixturesWithFramework = Object.entries(expectations) + .filter(([_, exp]) => exp.framework) + .map(([name, exp]) => ({ name, expectedFramework: exp.framework })); + + fixturesWithFramework.forEach(({ name, expectedFramework }) => { + if (!fixtures.includes(name)) return; + + it(`should detect ${name} framework as ${expectedFramework}`, () => { + const projectPath = getProjectPath(name); + const result = detectProject(projectPath); + expect(result.framework).toBe(expectedFramework); + }); + }); + }); +}); diff --git a/tests/detection/__tests__/static.test.js b/tests/detection/__tests__/static.test.js new file mode 100644 index 000000000..df566c50e --- /dev/null +++ b/tests/detection/__tests__/static.test.js @@ -0,0 +1,132 @@ +/** + * Static Site Detection Tests + * + * Tests for detecting pure static HTML sites. + */ + +const { detectAppType } = require('../src/appTypeDetector'); +const { AZURE_SERVICES, SKILL_ROUTES, CONFIDENCE_LEVELS } = require('../src/serviceMapping'); + +describe('Static Site Detection', () => { + describe('Pure HTML Sites', () => { + test('detects index.html only site', () => { + const project = { + files: ['index.html'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.skill).toBe(SKILL_ROUTES.STATIC_WEB_APPS); + expect(result.framework).toBe('Static HTML'); + expect(result.confidence).toBe(CONFIDENCE_LEVELS.MEDIUM); + }); + + test('detects multi-page HTML site', () => { + const project = { + files: ['index.html', 'about.html', 'contact.html', 'styles.css', 'script.js'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + expect(result.framework).toBe('Static HTML'); + }); + + test('detects HTML site with assets', () => { + const project = { + files: ['index.html', 'css/style.css', 'js/app.js', 'images/logo.png'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.service).toBe(AZURE_SERVICES.STATIC_WEB_APPS); + }); + }); + + describe('Non-Static Sites (should not match static detection)', () => { + test('HTML with package.json is NOT pure static', () => { + const project = { + files: ['index.html', 'package.json'], + contents: { + 'package.json': { name: 'my-app' } + } + }; + + const result = detectAppType(project); + + // Should be detected as Node.js, not pure static HTML + expect(result.framework).not.toBe('Static HTML'); + }); + + test('HTML with requirements.txt is NOT pure static', () => { + const project = { + files: ['index.html', 'requirements.txt', 'app.py'], + contents: { + 'requirements.txt': 'flask' + } + }; + + const result = detectAppType(project); + + // Should be detected as Python Flask + expect(result.framework).toBe('Flask'); + }); + + test('HTML with csproj is NOT pure static', () => { + const project = { + files: ['index.html', 'WebApp.csproj'], + contents: { + 'WebApp.csproj': '' + } + }; + + const result = detectAppType(project); + + // Should be detected as .NET + expect(result.framework).not.toBe('Static HTML'); + }); + + test('HTML with pom.xml is NOT pure static', () => { + const project = { + files: ['index.html', 'pom.xml'], + contents: { + 'pom.xml': '' + } + }; + + const result = detectAppType(project); + + // Should be detected as Java + expect(result.framework).not.toBe('Static HTML'); + }); + }); + + describe('Edge Cases', () => { + test('empty project returns low confidence', () => { + const project = { + files: [], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + expect(result.service).toBeNull(); + }); + + test('project with only non-relevant files returns low confidence', () => { + const project = { + files: ['README.md', 'LICENSE', '.gitignore'], + contents: {} + }; + + const result = detectAppType(project); + + expect(result.confidence).toBe(CONFIDENCE_LEVELS.LOW); + }); + }); +}); diff --git a/tests/detection/fixtures/expectations.json b/tests/detection/fixtures/expectations.json new file mode 100644 index 000000000..94205057e --- /dev/null +++ b/tests/detection/fixtures/expectations.json @@ -0,0 +1,117 @@ +{ + "react-vite": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "React (Vite)" + }, + "vue-vite": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Vue (Vite)" + }, + "angular": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Angular" + }, + "nextjs-static": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Next.js (SSG)" + }, + "nextjs-ssr": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Next.js (SSR)" + }, + "plain-html": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Static HTML" + }, + "gatsby": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Gatsby" + }, + "astro": { + "service": "static-web-apps", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Astro" + }, + "functions-node": { + "service": "functions", + "skill": "azure-deploy", + "confidence": "HIGH" + }, + "functions-python": { + "service": "functions", + "skill": "azure-deploy", + "confidence": "HIGH" + }, + "functions-dotnet": { + "service": "functions", + "skill": "azure-deploy", + "confidence": "HIGH", + "framework": "Azure Functions (.NET)" + }, + "docker-node": { + "service": "container-apps", + "skill": "azure-deploy", + "confidence": "HIGH" + }, + "docker-compose-multi": { + "service": "container-apps", + "skill": "azure-deploy", + "confidence": "HIGH" + }, + "express-api": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Express" + }, + "flask-app": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Flask" + }, + "django-app": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Django" + }, + "fastapi-app": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "FastAPI" + }, + "dotnet-webapp": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "ASP.NET Core" + }, + "spring-boot": { + "service": "app-service", + "skill": "azure-deploy", + "confidence": "MEDIUM", + "framework": "Spring Boot" + }, + "azd-fullstack": { + "service": "azd", + "skill": "azure-deploy", + "confidence": "HIGH" + } +} diff --git a/tests/detection/fixtures/projects/angular/angular.json b/tests/detection/fixtures/projects/angular/angular.json new file mode 100644 index 000000000..9e2c1fd04 --- /dev/null +++ b/tests/detection/fixtures/projects/angular/angular.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "projects": { + "angular-app": { + "architect": { + "build": { + "options": { + "outputPath": "dist/angular-app" + } + } + } + } + } +} diff --git a/tests/detection/fixtures/projects/angular/package.json b/tests/detection/fixtures/projects/angular/package.json new file mode 100644 index 000000000..67da93d8f --- /dev/null +++ b/tests/detection/fixtures/projects/angular/package.json @@ -0,0 +1,7 @@ +{ + "name": "angular-app", + "dependencies": { + "@angular/core": "^17.0.0", + "@angular/common": "^17.0.0" + } +} diff --git a/tests/detection/fixtures/projects/astro/astro.config.mjs b/tests/detection/fixtures/projects/astro/astro.config.mjs new file mode 100644 index 000000000..d7ac4b718 --- /dev/null +++ b/tests/detection/fixtures/projects/astro/astro.config.mjs @@ -0,0 +1,2 @@ +import { defineConfig } from 'astro/config'; +export default defineConfig({}); diff --git a/tests/detection/fixtures/projects/astro/package.json b/tests/detection/fixtures/projects/astro/package.json new file mode 100644 index 000000000..0bf76f589 --- /dev/null +++ b/tests/detection/fixtures/projects/astro/package.json @@ -0,0 +1,6 @@ +{ + "name": "astro-site", + "dependencies": { + "astro": "^4.0.0" + } +} diff --git a/tests/detection/fixtures/projects/azd-fullstack/api/package.json b/tests/detection/fixtures/projects/azd-fullstack/api/package.json new file mode 100644 index 000000000..f0fc45e8f --- /dev/null +++ b/tests/detection/fixtures/projects/azd-fullstack/api/package.json @@ -0,0 +1,6 @@ +{ + "name": "api", + "dependencies": { + "express": "^4.18.0" + } +} diff --git a/tests/detection/fixtures/projects/azd-fullstack/azure.yaml b/tests/detection/fixtures/projects/azd-fullstack/azure.yaml new file mode 100644 index 000000000..e0397db31 --- /dev/null +++ b/tests/detection/fixtures/projects/azd-fullstack/azure.yaml @@ -0,0 +1,10 @@ +name: fullstack-app +services: + api: + project: ./api + language: js + host: containerapp + frontend: + project: ./frontend + language: js + host: containerapp diff --git a/tests/detection/fixtures/projects/azd-fullstack/frontend/package.json b/tests/detection/fixtures/projects/azd-fullstack/frontend/package.json new file mode 100644 index 000000000..b5637dbe7 --- /dev/null +++ b/tests/detection/fixtures/projects/azd-fullstack/frontend/package.json @@ -0,0 +1,6 @@ +{ + "name": "frontend", + "dependencies": { + "react": "^18.2.0" + } +} diff --git a/tests/detection/fixtures/projects/azd-fullstack/infra/main.bicep b/tests/detection/fixtures/projects/azd-fullstack/infra/main.bicep new file mode 100644 index 000000000..f61fff5ff --- /dev/null +++ b/tests/detection/fixtures/projects/azd-fullstack/infra/main.bicep @@ -0,0 +1,9 @@ +targetScope = 'subscription' + +param location string = 'eastus' +param environmentName string + +resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'rg-${environmentName}' + location: location +} diff --git a/tests/detection/fixtures/projects/django-app/requirements.txt b/tests/detection/fixtures/projects/django-app/requirements.txt new file mode 100644 index 000000000..a1a20ec5f --- /dev/null +++ b/tests/detection/fixtures/projects/django-app/requirements.txt @@ -0,0 +1,3 @@ +django==5.0.0 +psycopg2-binary +gunicorn diff --git a/tests/detection/fixtures/projects/docker-compose-multi/docker-compose.yml b/tests/detection/fixtures/projects/docker-compose-multi/docker-compose.yml new file mode 100644 index 000000000..67dd445a5 --- /dev/null +++ b/tests/detection/fixtures/projects/docker-compose-multi/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' +services: + web: + build: ./web + ports: + - "3000:3000" + api: + build: ./api + ports: + - "8080:8080" diff --git a/tests/detection/fixtures/projects/docker-node/Dockerfile b/tests/detection/fixtures/projects/docker-node/Dockerfile new file mode 100644 index 000000000..31b77c54b --- /dev/null +++ b/tests/detection/fixtures/projects/docker-node/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/tests/detection/fixtures/projects/docker-node/package.json b/tests/detection/fixtures/projects/docker-node/package.json new file mode 100644 index 000000000..40205fdf1 --- /dev/null +++ b/tests/detection/fixtures/projects/docker-node/package.json @@ -0,0 +1,6 @@ +{ + "name": "docker-node-app", + "dependencies": { + "express": "^4.18.0" + } +} diff --git a/tests/detection/fixtures/projects/dotnet-webapp/WebApp.csproj b/tests/detection/fixtures/projects/dotnet-webapp/WebApp.csproj new file mode 100644 index 000000000..2f5597e33 --- /dev/null +++ b/tests/detection/fixtures/projects/dotnet-webapp/WebApp.csproj @@ -0,0 +1,8 @@ + + + net8.0 + + + + + diff --git a/tests/detection/fixtures/projects/express-api/package.json b/tests/detection/fixtures/projects/express-api/package.json new file mode 100644 index 000000000..50f87864d --- /dev/null +++ b/tests/detection/fixtures/projects/express-api/package.json @@ -0,0 +1,7 @@ +{ + "name": "express-api", + "dependencies": { + "express": "^4.18.0", + "cors": "^2.8.5" + } +} diff --git a/tests/detection/fixtures/projects/fastapi-app/requirements.txt b/tests/detection/fixtures/projects/fastapi-app/requirements.txt new file mode 100644 index 000000000..eb45f0ff4 --- /dev/null +++ b/tests/detection/fixtures/projects/fastapi-app/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.109.0 +uvicorn[standard] diff --git a/tests/detection/fixtures/projects/flask-app/requirements.txt b/tests/detection/fixtures/projects/flask-app/requirements.txt new file mode 100644 index 000000000..489d4d8a4 --- /dev/null +++ b/tests/detection/fixtures/projects/flask-app/requirements.txt @@ -0,0 +1,2 @@ +flask==3.0.0 +gunicorn diff --git a/tests/detection/fixtures/projects/functions-dotnet/FunctionsApp.csproj b/tests/detection/fixtures/projects/functions-dotnet/FunctionsApp.csproj new file mode 100644 index 000000000..dbc8948f1 --- /dev/null +++ b/tests/detection/fixtures/projects/functions-dotnet/FunctionsApp.csproj @@ -0,0 +1,9 @@ + + + net8.0 + v4 + + + + + diff --git a/tests/detection/fixtures/projects/functions-node/hello/function.json b/tests/detection/fixtures/projects/functions-node/hello/function.json new file mode 100644 index 000000000..e3e13662c --- /dev/null +++ b/tests/detection/fixtures/projects/functions-node/hello/function.json @@ -0,0 +1,10 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "methods": ["get", "post"] + } + ] +} diff --git a/tests/detection/fixtures/projects/functions-node/host.json b/tests/detection/fixtures/projects/functions-node/host.json new file mode 100644 index 000000000..b7e5ad1c0 --- /dev/null +++ b/tests/detection/fixtures/projects/functions-node/host.json @@ -0,0 +1,7 @@ +{ + "version": "2.0", + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} diff --git a/tests/detection/fixtures/projects/functions-node/package.json b/tests/detection/fixtures/projects/functions-node/package.json new file mode 100644 index 000000000..3f839254b --- /dev/null +++ b/tests/detection/fixtures/projects/functions-node/package.json @@ -0,0 +1,6 @@ +{ + "name": "azure-functions-node", + "dependencies": { + "@azure/functions": "^4.0.0" + } +} diff --git a/tests/detection/fixtures/projects/functions-python/function_app.py b/tests/detection/fixtures/projects/functions-python/function_app.py new file mode 100644 index 000000000..bbd4b6770 --- /dev/null +++ b/tests/detection/fixtures/projects/functions-python/function_app.py @@ -0,0 +1,7 @@ +import azure.functions as func + +app = func.FunctionApp() + +@app.route(route="hello") +def hello(req: func.HttpRequest) -> func.HttpResponse: + return func.HttpResponse("Hello!") diff --git a/tests/detection/fixtures/projects/functions-python/requirements.txt b/tests/detection/fixtures/projects/functions-python/requirements.txt new file mode 100644 index 000000000..75db2c4f6 --- /dev/null +++ b/tests/detection/fixtures/projects/functions-python/requirements.txt @@ -0,0 +1 @@ +azure-functions diff --git a/tests/detection/fixtures/projects/gatsby/gatsby-config.js b/tests/detection/fixtures/projects/gatsby/gatsby-config.js new file mode 100644 index 000000000..dd65b98fa --- /dev/null +++ b/tests/detection/fixtures/projects/gatsby/gatsby-config.js @@ -0,0 +1,3 @@ +module.exports = { + siteMetadata: { title: 'Gatsby Site' } +} diff --git a/tests/detection/fixtures/projects/gatsby/package.json b/tests/detection/fixtures/projects/gatsby/package.json new file mode 100644 index 000000000..2e8081cee --- /dev/null +++ b/tests/detection/fixtures/projects/gatsby/package.json @@ -0,0 +1,7 @@ +{ + "name": "gatsby-site", + "dependencies": { + "gatsby": "^5.0.0", + "react": "^18.2.0" + } +} diff --git a/tests/detection/fixtures/projects/nextjs-ssr/next.config.js b/tests/detection/fixtures/projects/nextjs-ssr/next.config.js new file mode 100644 index 000000000..baa13377c --- /dev/null +++ b/tests/detection/fixtures/projects/nextjs-ssr/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +module.exports = { + reactStrictMode: true +} diff --git a/tests/detection/fixtures/projects/nextjs-ssr/package.json b/tests/detection/fixtures/projects/nextjs-ssr/package.json new file mode 100644 index 000000000..624703d5d --- /dev/null +++ b/tests/detection/fixtures/projects/nextjs-ssr/package.json @@ -0,0 +1,7 @@ +{ + "name": "nextjs-ssr-app", + "dependencies": { + "next": "^14.0.0", + "react": "^18.2.0" + } +} diff --git a/tests/detection/fixtures/projects/nextjs-static/next.config.js b/tests/detection/fixtures/projects/nextjs-static/next.config.js new file mode 100644 index 000000000..2f8f3c2f6 --- /dev/null +++ b/tests/detection/fixtures/projects/nextjs-static/next.config.js @@ -0,0 +1,5 @@ +/** @type {import('next').NextConfig} */ +module.exports = { + output: 'export', + trailingSlash: true +} diff --git a/tests/detection/fixtures/projects/nextjs-static/package.json b/tests/detection/fixtures/projects/nextjs-static/package.json new file mode 100644 index 000000000..3a3ced4c2 --- /dev/null +++ b/tests/detection/fixtures/projects/nextjs-static/package.json @@ -0,0 +1,7 @@ +{ + "name": "nextjs-static-app", + "dependencies": { + "next": "^14.0.0", + "react": "^18.2.0" + } +} diff --git a/tests/detection/fixtures/projects/plain-html/index.html b/tests/detection/fixtures/projects/plain-html/index.html new file mode 100644 index 000000000..c6b7e230b --- /dev/null +++ b/tests/detection/fixtures/projects/plain-html/index.html @@ -0,0 +1,5 @@ + + +Static Site +

Hello World

+ diff --git a/tests/detection/fixtures/projects/react-vite/package.json b/tests/detection/fixtures/projects/react-vite/package.json new file mode 100644 index 000000000..47fe2c66e --- /dev/null +++ b/tests/detection/fixtures/projects/react-vite/package.json @@ -0,0 +1,11 @@ +{ + "name": "react-vite-app", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "vite": "^5.0.0", + "@vitejs/plugin-react": "^4.0.0" + } +} diff --git a/tests/detection/fixtures/projects/react-vite/vite.config.js b/tests/detection/fixtures/projects/react-vite/vite.config.js new file mode 100644 index 000000000..ecc7ca4ec --- /dev/null +++ b/tests/detection/fixtures/projects/react-vite/vite.config.js @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +export default defineConfig({ plugins: [react()] }) diff --git a/tests/detection/fixtures/projects/spring-boot/pom.xml b/tests/detection/fixtures/projects/spring-boot/pom.xml new file mode 100644 index 000000000..98b0c7a9a --- /dev/null +++ b/tests/detection/fixtures/projects/spring-boot/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + spring-boot-app + + + org.springframework.boot + spring-boot-starter-web + + + diff --git a/tests/detection/fixtures/projects/vue-vite/package.json b/tests/detection/fixtures/projects/vue-vite/package.json new file mode 100644 index 000000000..48e5b66c6 --- /dev/null +++ b/tests/detection/fixtures/projects/vue-vite/package.json @@ -0,0 +1,10 @@ +{ + "name": "vue-vite-app", + "dependencies": { + "vue": "^3.3.0" + }, + "devDependencies": { + "vite": "^5.0.0", + "@vitejs/plugin-vue": "^4.0.0" + } +} diff --git a/tests/detection/fixtures/projects/vue-vite/vite.config.js b/tests/detection/fixtures/projects/vue-vite/vite.config.js new file mode 100644 index 000000000..b5b46bbc4 --- /dev/null +++ b/tests/detection/fixtures/projects/vue-vite/vite.config.js @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +export default defineConfig({ plugins: [vue()] }) diff --git a/tests/detection/jest.config.js b/tests/detection/jest.config.js new file mode 100644 index 000000000..d3e553a5c --- /dev/null +++ b/tests/detection/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.js'], + collectCoverageFrom: ['src/**/*.js'], + coverageDirectory: 'coverage', + verbose: true +}; diff --git a/tests/detection/package-lock.json b/tests/detection/package-lock.json new file mode 100644 index 000000000..89de7845c --- /dev/null +++ b/tests/detection/package-lock.json @@ -0,0 +1,3649 @@ +{ + "name": "azure-skill-detection-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-skill-detection-tests", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "jest": "^29.7.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/detection/package.json b/tests/detection/package.json new file mode 100644 index 000000000..b1a19f01d --- /dev/null +++ b/tests/detection/package.json @@ -0,0 +1,16 @@ +{ + "name": "azure-skill-detection-tests", + "version": "1.0.0", + "description": "Tests for Azure app type detection logic", + "main": "index.js", + "scripts": { + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "keywords": ["azure", "detection", "testing"], + "license": "MIT", + "devDependencies": { + "jest": "^29.7.0" + } +} diff --git a/tests/detection/src/appTypeDetector.js b/tests/detection/src/appTypeDetector.js new file mode 100644 index 000000000..d5a7182f8 --- /dev/null +++ b/tests/detection/src/appTypeDetector.js @@ -0,0 +1,544 @@ +/** + * App Type Detector + * + * Detects application type from project structure and recommends Azure services. + * Based on azure-deploy SKILL.md detection logic. + */ + +const { + HIGH_CONFIDENCE_PATTERNS, + NODEJS_PATTERNS, + PYTHON_PATTERNS, + DOTNET_PATTERNS, + JAVA_PATTERNS, + STATIC_PATTERNS, + MULTI_SERVICE_PATTERNS +} = require('./filePatterns'); + +const { + AZURE_SERVICES, + SKILL_ROUTES, + CONFIDENCE_LEVELS, + createRecommendation +} = require('./serviceMapping'); + +/** + * Detects application type and recommends Azure service + * @param {Object} projectStructure - Object representing project files + * @param {string[]} projectStructure.files - Array of file paths + * @param {Object} projectStructure.contents - Map of file path to content + * @param {string[]} [projectStructure.directories] - Array of directory names + * @returns {ServiceRecommendation} Recommended Azure service + */ +function detectAppType(projectStructure) { + const { files = [], contents = {}, directories = [] } = projectStructure; + + // Phase 1: Check high-confidence Azure configuration files + const highConfidenceResult = checkHighConfidencePatterns(files, contents); + if (highConfidenceResult) { + return highConfidenceResult; + } + + // Phase 2: Check for multi-service architecture + const multiServiceResult = checkMultiServicePatterns(files, directories); + if (multiServiceResult) { + return multiServiceResult; + } + + // Phase 3: Detect by language/framework + const frameworkResult = detectFramework(files, contents); + if (frameworkResult) { + return frameworkResult; + } + + // Phase 4: Check for pure static site + const staticResult = checkStaticSite(files); + if (staticResult) { + return staticResult; + } + + // Low confidence - unable to determine + return createRecommendation( + null, + SKILL_ROUTES.DEPLOY, + CONFIDENCE_LEVELS.LOW, + 'Unable to determine application type. Please provide more information.' + ); +} + +/** + * Check for high-confidence Azure configuration patterns + */ +function checkHighConfidencePatterns(files, contents) { + const hasFile = (pattern) => { + if (Array.isArray(pattern)) { + return pattern.some(p => files.includes(p)); + } + return files.includes(pattern); + }; + + // Azure Developer CLI project + if (hasFile(HIGH_CONFIDENCE_PATTERNS.azureYaml)) { + return createRecommendation( + AZURE_SERVICES.AZD, + SKILL_ROUTES.DEPLOY, + CONFIDENCE_LEVELS.HIGH, + 'Found azure.yaml - project is configured for Azure Developer CLI' + ); + } + + // Azure Functions + if (hasFile(HIGH_CONFIDENCE_PATTERNS.hostJson) || + hasFile(HIGH_CONFIDENCE_PATTERNS.functionJson) || + hasFile(HIGH_CONFIDENCE_PATTERNS.functionAppPy)) { + return createRecommendation( + AZURE_SERVICES.FUNCTIONS, + SKILL_ROUTES.FUNCTIONS, + CONFIDENCE_LEVELS.HIGH, + 'Found Azure Functions configuration file (host.json, function.json, or function_app.py)' + ); + } + + // Static Web Apps + if (hasFile(HIGH_CONFIDENCE_PATTERNS.staticWebAppConfig) || + hasFile(HIGH_CONFIDENCE_PATTERNS.swaCliConfig)) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.HIGH, + 'Found Static Web Apps configuration file' + ); + } + + // Container Apps (Dockerfile) + if (hasFile(HIGH_CONFIDENCE_PATTERNS.dockerfile) || + hasFile(HIGH_CONFIDENCE_PATTERNS.dockerCompose) || + hasFile(HIGH_CONFIDENCE_PATTERNS.dockerComposeYaml)) { + return createRecommendation( + AZURE_SERVICES.CONTAINER_APPS, + SKILL_ROUTES.CONTAINER_APPS, + CONFIDENCE_LEVELS.HIGH, + 'Found Dockerfile or docker-compose.yml - containerized application' + ); + } + + return null; +} + +/** + * Check for multi-service architecture patterns + */ +function checkMultiServicePatterns(files, directories) { + const serviceDirectories = MULTI_SERVICE_PATTERNS.serviceDirectories; + const foundServiceDirs = directories.filter(dir => + serviceDirectories.some(sd => dir.toLowerCase().includes(sd)) + ); + + // Check for multiple package.json in subdirectories + const packageJsonFiles = files.filter(f => + f.includes('/') && f.endsWith('package.json') + ); + + if (foundServiceDirs.length >= 2 || packageJsonFiles.length >= 2) { + return createRecommendation( + AZURE_SERVICES.AZD, + SKILL_ROUTES.DEPLOY, + CONFIDENCE_LEVELS.MEDIUM, + 'Detected multi-service architecture - recommend Azure Developer CLI with Infrastructure as Code' + ); + } + + return null; +} + +/** + * Detect framework from files and contents + */ +function detectFramework(files, contents) { + // Check Node.js + if (files.includes(NODEJS_PATTERNS.packageJson)) { + return detectNodeJsFramework(files, contents); + } + + // Check Python + if (files.includes(PYTHON_PATTERNS.requirementsTxt) || + files.includes(PYTHON_PATTERNS.pyprojectToml)) { + return detectPythonFramework(files, contents); + } + + // Check .NET + const csprojFile = files.find(f => f.endsWith('.csproj')); + if (csprojFile || files.find(f => f.endsWith('.sln'))) { + return detectDotNetFramework(files, contents, csprojFile); + } + + // Check Java + if (files.includes(JAVA_PATTERNS.pomXml) || + files.includes(JAVA_PATTERNS.buildGradle) || + files.includes(JAVA_PATTERNS.buildGradleKts)) { + return detectJavaFramework(files, contents); + } + + return null; +} + +/** + * Detect Node.js framework + */ +function detectNodeJsFramework(files, contents) { + const hasFile = (pattern) => { + if (Array.isArray(pattern)) { + return pattern.some(p => files.includes(p)); + } + return files.includes(pattern); + }; + + const packageJson = contents['package.json']; + const dependencies = packageJson ? + { ...packageJson.dependencies, ...packageJson.devDependencies } : {}; + + // Next.js + if (hasFile(NODEJS_PATTERNS.nextConfig)) { + // Check for static export + const nextConfigFile = NODEJS_PATTERNS.nextConfig.find(f => files.includes(f)); + const nextConfig = contents[nextConfigFile]; + + if (nextConfig && typeof nextConfig === 'string' && nextConfig.includes("output: 'export'")) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Next.js with static export detected', + 'Next.js (SSG)' + ); + } + + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'Next.js with server-side rendering detected', + 'Next.js (SSR)' + ); + } + + // Nuxt + if (hasFile(NODEJS_PATTERNS.nuxtConfig)) { + const nuxtConfigFile = NODEJS_PATTERNS.nuxtConfig.find(f => files.includes(f)); + const nuxtConfig = contents[nuxtConfigFile]; + + if (nuxtConfig && typeof nuxtConfig === 'string' && + (nuxtConfig.includes('ssr: false') || nuxtConfig.includes("target: 'static'"))) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Nuxt with static generation detected', + 'Nuxt (Static)' + ); + } + + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'Nuxt with server-side rendering detected', + 'Nuxt (SSR)' + ); + } + + // Angular + if (hasFile(NODEJS_PATTERNS.angularJson)) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Angular application detected', + 'Angular' + ); + } + + // Vite (React, Vue, Svelte) + if (hasFile(NODEJS_PATTERNS.viteConfig)) { + let framework = 'Vite'; + if (dependencies.react) framework = 'React (Vite)'; + else if (dependencies.vue) framework = 'Vue (Vite)'; + else if (dependencies.svelte) framework = 'Svelte (Vite)'; + + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Vite-based static application detected', + framework + ); + } + + // Gatsby + if (hasFile(NODEJS_PATTERNS.gatsbyConfig)) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Gatsby static site generator detected', + 'Gatsby' + ); + } + + // Astro + if (hasFile(NODEJS_PATTERNS.astroConfig)) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Astro framework detected', + 'Astro' + ); + } + + // NestJS + if (hasFile(NODEJS_PATTERNS.nestCliJson)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'NestJS backend framework detected', + 'NestJS' + ); + } + + // Express/Fastify/Koa/Hapi (server frameworks) + const serverFramework = NODEJS_PATTERNS.serverDependencies.find(dep => dependencies[dep]); + if (serverFramework) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + `${serverFramework} server framework detected`, + serverFramework.charAt(0).toUpperCase() + serverFramework.slice(1) + ); + } + + // Generic Node.js with no framework detected + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.LOW, + 'Node.js project detected, but no specific framework identified' + ); +} + +/** + * Detect Python framework + */ +function detectPythonFramework(files, contents) { + // Check for function_app.py (Azure Functions v2) + if (files.includes('function_app.py')) { + return createRecommendation( + AZURE_SERVICES.FUNCTIONS, + SKILL_ROUTES.FUNCTIONS, + CONFIDENCE_LEVELS.HIGH, + 'Azure Functions Python v2 programming model detected', + 'Azure Functions (Python)' + ); + } + + // Parse requirements.txt or pyproject.toml + let dependencies = []; + + if (contents['requirements.txt']) { + dependencies = contents['requirements.txt'] + .toLowerCase() + .split('\n') + .map(line => line.split('==')[0].split('>=')[0].split('~=')[0].trim()); + } + + if (contents['pyproject.toml']) { + const tomlContent = contents['pyproject.toml'].toLowerCase(); + Object.values(PYTHON_PATTERNS.frameworks).forEach(framework => { + if (tomlContent.includes(framework)) { + dependencies.push(framework); + } + }); + } + + // Azure Functions + if (dependencies.includes(PYTHON_PATTERNS.frameworks.azureFunctions)) { + return createRecommendation( + AZURE_SERVICES.FUNCTIONS, + SKILL_ROUTES.FUNCTIONS, + CONFIDENCE_LEVELS.MEDIUM, + 'azure-functions dependency detected', + 'Azure Functions (Python)' + ); + } + + // FastAPI + if (dependencies.includes(PYTHON_PATTERNS.frameworks.fastapi)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'FastAPI framework detected', + 'FastAPI' + ); + } + + // Flask + if (dependencies.includes(PYTHON_PATTERNS.frameworks.flask)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'Flask framework detected', + 'Flask' + ); + } + + // Django + if (dependencies.includes(PYTHON_PATTERNS.frameworks.django)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'Django framework detected', + 'Django' + ); + } + + // Generic Python + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.LOW, + 'Python project detected, but no specific framework identified' + ); +} + +/** + * Detect .NET framework + */ +function detectDotNetFramework(files, contents, csprojFile) { + const csprojContent = csprojFile ? contents[csprojFile] : ''; + + // Azure Functions + if (csprojContent && csprojContent.includes(DOTNET_PATTERNS.azureFunctionsVersion)) { + return createRecommendation( + AZURE_SERVICES.FUNCTIONS, + SKILL_ROUTES.FUNCTIONS, + CONFIDENCE_LEVELS.HIGH, + 'Azure Functions .NET project detected', + 'Azure Functions (.NET)' + ); + } + + // Blazor WebAssembly + if (csprojContent && csprojContent.includes(DOTNET_PATTERNS.blazorWebAssembly)) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Blazor WebAssembly project detected', + 'Blazor WebAssembly' + ); + } + + // ASP.NET Core + if (csprojContent && csprojContent.includes(DOTNET_PATTERNS.aspNetCore)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'ASP.NET Core application detected', + 'ASP.NET Core' + ); + } + + // Generic .NET + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.LOW, + '.NET project detected, but no specific framework identified' + ); +} + +/** + * Detect Java framework + */ +function detectJavaFramework(files, contents) { + let dependencyContent = ''; + + if (contents['pom.xml']) { + dependencyContent = contents['pom.xml'].toLowerCase(); + } else if (contents['build.gradle']) { + dependencyContent = contents['build.gradle'].toLowerCase(); + } else if (contents['build.gradle.kts']) { + dependencyContent = contents['build.gradle.kts'].toLowerCase(); + } + + // Azure Functions + if (dependencyContent.includes(JAVA_PATTERNS.dependencies.azureFunctions)) { + return createRecommendation( + AZURE_SERVICES.FUNCTIONS, + SKILL_ROUTES.FUNCTIONS, + CONFIDENCE_LEVELS.MEDIUM, + 'Azure Functions Java project detected', + 'Azure Functions (Java)' + ); + } + + // Spring Boot + if (dependencyContent.includes(JAVA_PATTERNS.dependencies.springBoot)) { + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.MEDIUM, + 'Spring Boot application detected', + 'Spring Boot' + ); + } + + // Generic Java + return createRecommendation( + AZURE_SERVICES.APP_SERVICE, + SKILL_ROUTES.APP_SERVICE, + CONFIDENCE_LEVELS.LOW, + 'Java project detected, but no specific framework identified' + ); +} + +/** + * Check for pure static site + */ +function checkStaticSite(files) { + // Has index.html but no package.json or other language files + if (files.includes(STATIC_PATTERNS.indexHtml) && + !files.includes('package.json') && + !files.includes('requirements.txt') && + !files.find(f => f.endsWith('.csproj')) && + !files.includes('pom.xml') && + !files.includes('build.gradle')) { + return createRecommendation( + AZURE_SERVICES.STATIC_WEB_APPS, + SKILL_ROUTES.STATIC_WEB_APPS, + CONFIDENCE_LEVELS.MEDIUM, + 'Pure static HTML site detected', + 'Static HTML' + ); + } + + return null; +} + +module.exports = { + detectAppType, + checkHighConfidencePatterns, + checkMultiServicePatterns, + detectFramework, + detectNodeJsFramework, + detectPythonFramework, + detectDotNetFramework, + detectJavaFramework, + checkStaticSite +}; diff --git a/tests/detection/src/filePatterns.js b/tests/detection/src/filePatterns.js new file mode 100644 index 000000000..fc9fd9e79 --- /dev/null +++ b/tests/detection/src/filePatterns.js @@ -0,0 +1,97 @@ +/** + * File patterns used to detect application types and Azure service recommendations. + * Based on azure-deploy SKILL.md detection logic. + */ + +const HIGH_CONFIDENCE_PATTERNS = { + // Azure configuration files - highest priority + azureYaml: 'azure.yaml', + + // Azure Functions indicators + hostJson: 'host.json', + functionJson: 'function.json', + localSettingsJson: 'local.settings.json', + functionAppPy: 'function_app.py', + + // Static Web Apps indicators + staticWebAppConfig: 'staticwebapp.config.json', + swaCliConfig: 'swa-cli.config.json', + + // Container indicators + dockerfile: 'Dockerfile', + dockerCompose: 'docker-compose.yml', + dockerComposeYaml: 'docker-compose.yaml' +}; + +const NODEJS_PATTERNS = { + packageJson: 'package.json', + + // Framework config files + nextConfig: ['next.config.js', 'next.config.mjs', 'next.config.ts'], + nuxtConfig: ['nuxt.config.ts', 'nuxt.config.js'], + angularJson: 'angular.json', + viteConfig: ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'], + gatsbyConfig: 'gatsby-config.js', + astroConfig: 'astro.config.mjs', + nestCliJson: 'nest-cli.json', + + // Server framework dependencies (in package.json) + serverDependencies: ['express', 'fastify', 'koa', 'hapi', '@hapi/hapi'] +}; + +const PYTHON_PATTERNS = { + requirementsTxt: 'requirements.txt', + pyprojectToml: 'pyproject.toml', + + // Framework dependencies (in requirements.txt or pyproject.toml) + frameworks: { + flask: 'flask', + django: 'django', + fastapi: 'fastapi', + azureFunctions: 'azure-functions' + } +}; + +const DOTNET_PATTERNS = { + csproj: '*.csproj', + sln: '*.sln', + + // Project type indicators (in .csproj content) + azureFunctionsVersion: '', + blazorWebAssembly: 'Microsoft.AspNetCore.Components.WebAssembly', + aspNetCore: 'Microsoft.AspNetCore' +}; + +const JAVA_PATTERNS = { + pomXml: 'pom.xml', + buildGradle: 'build.gradle', + buildGradleKts: 'build.gradle.kts', + + // Dependencies (in pom.xml or build.gradle) + dependencies: { + azureFunctions: 'azure-functions', + springBoot: 'spring-boot' + } +}; + +const STATIC_PATTERNS = { + indexHtml: 'index.html' +}; + +const MULTI_SERVICE_PATTERNS = { + // Directory names indicating multi-service + serviceDirectories: ['frontend', 'backend', 'api', 'web', 'packages', 'apps', 'services'], + + // Environment files that may contain service references + envFiles: ['.env', '.env.local', '.env.production'] +}; + +module.exports = { + HIGH_CONFIDENCE_PATTERNS, + NODEJS_PATTERNS, + PYTHON_PATTERNS, + DOTNET_PATTERNS, + JAVA_PATTERNS, + STATIC_PATTERNS, + MULTI_SERVICE_PATTERNS +}; diff --git a/tests/detection/src/serviceMapping.js b/tests/detection/src/serviceMapping.js new file mode 100644 index 000000000..cac63a0af --- /dev/null +++ b/tests/detection/src/serviceMapping.js @@ -0,0 +1,58 @@ +/** + * Azure service mappings based on detection results. + * Maps detected patterns to recommended Azure services and skills. + */ + +const AZURE_SERVICES = { + FUNCTIONS: 'Azure Functions', + STATIC_WEB_APPS: 'Static Web Apps', + CONTAINER_APPS: 'Container Apps', + APP_SERVICE: 'App Service', + AZD: 'Azure Developer CLI (azd)' +}; + +// All deployment skills consolidated into azure-deploy +// Reference guides: ./reference/functions.md, ./reference/static-web-apps.md, +// ./reference/container-apps.md, ./reference/app-service.md, ./reference/aks.md +const SKILL_ROUTES = { + FUNCTIONS: 'azure-deploy', + STATIC_WEB_APPS: 'azure-deploy', + CONTAINER_APPS: 'azure-deploy', + APP_SERVICE: 'azure-deploy', + AKS: 'azure-deploy', + DEPLOY: 'azure-deploy' +}; + +const CONFIDENCE_LEVELS = { + HIGH: 'HIGH', + MEDIUM: 'MEDIUM', + LOW: 'LOW' +}; + +/** + * Service recommendation result + * @typedef {Object} ServiceRecommendation + * @property {string} service - The recommended Azure service + * @property {string} skill - The skill to route to + * @property {string} confidence - Confidence level (HIGH, MEDIUM, LOW) + * @property {string} reason - Explanation for the recommendation + * @property {string} [framework] - Detected framework (if applicable) + */ + +/** + * Creates a service recommendation object + */ +function createRecommendation(service, skill, confidence, reason, framework = null) { + const result = { service, skill, confidence, reason }; + if (framework) { + result.framework = framework; + } + return result; +} + +module.exports = { + AZURE_SERVICES, + SKILL_ROUTES, + CONFIDENCE_LEVELS, + createRecommendation +}; diff --git a/tests/detection/utils/fixture-loader.js b/tests/detection/utils/fixture-loader.js new file mode 100644 index 000000000..b8564e916 --- /dev/null +++ b/tests/detection/utils/fixture-loader.js @@ -0,0 +1,74 @@ +/** + * Fixture Loader Utilities + * + * Loads project fixtures and expectations for testing. + */ + +const fs = require('fs'); +const path = require('path'); + +const FIXTURES_DIR = path.join(__dirname, '..', 'fixtures', 'projects'); +const EXPECTATIONS_FILE = path.join(__dirname, '..', 'fixtures', 'expectations.json'); + +/** + * List all project fixture directories + * @returns {string[]} Array of fixture names + */ +function listProjectFixtures() { + if (!fs.existsSync(FIXTURES_DIR)) { + return []; + } + return fs.readdirSync(FIXTURES_DIR).filter(name => { + const fullPath = path.join(FIXTURES_DIR, name); + return fs.statSync(fullPath).isDirectory(); + }); +} + +/** + * Get the full path to a project fixture + * @param {string} fixtureName - Name of the fixture + * @returns {string} Full path to the fixture directory + */ +function getProjectPath(fixtureName) { + return path.join(FIXTURES_DIR, fixtureName); +} + +/** + * Load expectations from JSON file + * @returns {Object} Expectations object keyed by fixture name + */ +function loadExpectations() { + if (!fs.existsSync(EXPECTATIONS_FILE)) { + return {}; + } + const content = fs.readFileSync(EXPECTATIONS_FILE, 'utf-8'); + return JSON.parse(content); +} + +/** + * Validate that all fixtures have corresponding expectations + * @returns {{valid: boolean, missing: string[], extra: string[]}} + */ +function validateFixturesHaveExpectations() { + const fixtures = listProjectFixtures(); + const expectations = loadExpectations(); + const expectationKeys = Object.keys(expectations); + + const missing = fixtures.filter(f => !expectationKeys.includes(f)); + const extra = expectationKeys.filter(e => !fixtures.includes(e)); + + return { + valid: missing.length === 0 && extra.length === 0, + missing, + extra + }; +} + +module.exports = { + listProjectFixtures, + getProjectPath, + loadExpectations, + validateFixturesHaveExpectations, + FIXTURES_DIR, + EXPECTATIONS_FILE +}; diff --git a/tests/detection/utils/project-scanner.js b/tests/detection/utils/project-scanner.js new file mode 100644 index 000000000..da26cab6a --- /dev/null +++ b/tests/detection/utils/project-scanner.js @@ -0,0 +1,150 @@ +/** + * Project Scanner + * + * Scans a project directory and detects the application type, + * recommended Azure service, and confidence level. + */ + +const fs = require('fs'); +const path = require('path'); + +const { + AZURE_SERVICES, + SKILL_ROUTES, + CONFIDENCE_LEVELS, + createRecommendation +} = require('../src/serviceMapping'); + +/** + * Recursively get all files in a directory + * @param {string} dir - Directory path + * @param {string} baseDir - Base directory for relative paths + * @returns {string[]} Array of relative file paths + */ +function getAllFiles(dir, baseDir = dir) { + const files = []; + + if (!fs.existsSync(dir)) { + return files; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + const relativePath = path.relative(baseDir, fullPath); + + if (entry.isDirectory()) { + // Skip node_modules and hidden directories + if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) { + files.push(...getAllFiles(fullPath, baseDir)); + } + } else { + files.push(relativePath); + } + } + + return files; +} + +/** + * Read file content safely + * @param {string} filePath - Path to file + * @returns {string|null} File content or null if error + */ +function readFileSafe(filePath) { + try { + return fs.readFileSync(filePath, 'utf-8'); + } catch { + return null; + } +} + +/** + * Detect project type from a directory + * @param {string} projectPath - Path to project directory + * @returns {Object} Detection result + */ +function detectProject(projectPath) { + const files = getAllFiles(projectPath); + const contents = {}; + + // Read relevant config files + const configFiles = [ + 'package.json', 'requirements.txt', 'pyproject.toml', + 'azure.yaml', 'host.json', 'function.json', 'function_app.py', + 'staticwebapp.config.json', 'swa-cli.config.json', + 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', + 'next.config.js', 'next.config.mjs', 'next.config.ts', + 'nuxt.config.js', 'nuxt.config.ts', + 'angular.json', 'vite.config.js', 'vite.config.ts', 'vite.config.mjs', + 'gatsby-config.js', 'astro.config.mjs', 'nest-cli.json', + 'pom.xml', 'build.gradle', 'build.gradle.kts' + ]; + + // Also read .csproj files + const csprojFiles = files.filter(f => f.endsWith('.csproj')); + + for (const file of [...configFiles, ...csprojFiles]) { + const filePath = path.join(projectPath, file); + if (fs.existsSync(filePath)) { + const content = readFileSafe(filePath); + if (content) { + // Parse JSON files + if (file.endsWith('.json')) { + try { + contents[file] = JSON.parse(content); + } catch { + contents[file] = content; + } + } else { + contents[file] = content; + } + } + } + } + + // Get directory names + const directories = fs.readdirSync(projectPath, { withFileTypes: true }) + .filter(d => d.isDirectory() && !d.name.startsWith('.') && d.name !== 'node_modules') + .map(d => d.name); + + // Use the existing detection logic + const { detectAppType } = require('../src/appTypeDetector'); + + return detectAppType({ + files, + contents, + directories + }); +} + +/** + * Map internal service names to expected format + * @param {Object} result - Detection result + * @returns {Object} Normalized result + */ +function normalizeResult(result) { + const serviceMap = { + 'Azure Functions': 'functions', + 'Static Web Apps': 'static-web-apps', + 'Container Apps': 'container-apps', + 'App Service': 'app-service', + 'Azure Developer CLI (azd)': 'azd' + }; + + return { + service: serviceMap[result.service] || result.service, + skill: result.skill, + confidence: result.confidence, + framework: result.framework, + reason: result.reason + }; +} + +module.exports = { + detectProject, + normalizeResult, + getAllFiles, + readFileSafe +}; diff --git a/tests/nodejs-production/README.md b/tests/nodejs-production/README.md new file mode 100644 index 000000000..f68f6ad6b --- /dev/null +++ b/tests/nodejs-production/README.md @@ -0,0 +1,137 @@ +# Node.js Production Tests + +Tests for the `azure-nodejs-production` skill, which validates Express.js applications for Azure production best practices. + +## Overview + +This test suite validates that Node.js/Express apps have proper configuration for running in production on Azure. The skill covers: + +- **Trust Proxy** - Required behind Azure load balancers/App Gateway +- **Health Endpoints** - For Azure App Service health checks +- **Cookie Security** - Proper sameSite, secure, httpOnly settings +- **Port Binding** - Using `process.env.PORT` for Azure compatibility +- **Host Binding** - Using `0.0.0.0` instead of `localhost` for containers +- **Dockerfile Best Practices** - NODE_ENV, HEALTHCHECK, non-root user + +## Setup + +```bash +cd tests/nodejs-production +npm install +``` + +## Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Run tests with coverage +npm run test:coverage +``` + +## Test Structure + +``` +tests/nodejs-production/ +├── __tests__/ +│ ├── expressValidator.test.js # Unit tests for Express validation +│ ├── dockerfileValidator.test.js # Unit tests for Dockerfile validation +│ └── integration.test.js # Fixture-based integration tests +├── src/validators/ +│ ├── expressProductionValidator.js # Express.js production checks +│ └── dockerfileValidator.js # Dockerfile production checks +├── fixtures/ +│ ├── expectations.json # Expected results for fixtures +│ └── projects/ # Sample Express projects +│ ├── express-prod-correct/ # All best practices ✅ +│ ├── express-missing-trust-proxy/ +│ ├── express-missing-health/ +│ ├── express-localhost-bind/ +│ └── dockerfile-missing-healthcheck/ +├── package.json +├── jest.config.js +└── README.md +``` + +## Adding New Tests + +### Adding a Unit Test + +Add test cases to the appropriate test file in `__tests__/`: + +```javascript +// __tests__/expressValidator.test.js +test('detects new pattern', () => { + const content = `app.someNewPattern()`; + const result = checkNewPattern(content); + expect(result.passed).toBe(false); +}); +``` + +### Adding a Fixture Test + +1. Create a new project directory in `fixtures/projects/`: + ``` + fixtures/projects/my-new-fixture/ + ├── server.js + ├── package.json + └── Dockerfile (optional) + ``` + +2. Add expected results to `fixtures/expectations.json`: + ```json + { + "my-new-fixture": { + "description": "Description of what this tests", + "express": { + "valid": false, + "errors": ["expected error substring"], + "checks": { + "trustProxy": false, + "healthEndpoint": true + } + } + } + } + ``` + +3. The integration tests will automatically pick up the new fixture. + +## Validation Checks + +### Express Validation + +| Check | What it validates | +|-------|-------------------| +| `trustProxy` | `app.set('trust proxy', 1)` for Azure load balancers | +| `healthEndpoint` | `/health`, `/healthz`, `/ready`, or `/liveness` endpoint | +| `cookieConfig` | `sameSite: 'lax'` and `secure` settings for sessions/cookies | +| `portBinding` | `process.env.PORT` usage instead of hardcoded port | +| `hostBinding` | Binding to `0.0.0.0` instead of `localhost` | + +### Dockerfile Validation + +| Check | What it validates | +|-------|-------------------| +| `nodeEnvProduction` | `ENV NODE_ENV=production` instruction | +| `healthcheck` | `HEALTHCHECK CMD ...` instruction | +| `nonRootUser` | `USER node` or other non-root user | +| `properBaseImage` | Alpine/slim image with explicit version | + +## CI/CD + +Tests run automatically via GitHub Actions on: +- Push to `main` affecting `plugin/skills/azure-nodejs-production/` or `tests/nodejs-production/` +- Pull requests to `main` affecting the same paths +- Manual trigger via `workflow_dispatch` + +See `.github/workflows/test-nodejs-production.yml` + +## Related Skills + +- `azure-nodejs-production` - The skill this test suite validates +- `azure-deploy` - App type detection (separate test suite in `tests/`) diff --git a/tests/nodejs-production/__tests__/dockerfileValidator.test.js b/tests/nodejs-production/__tests__/dockerfileValidator.test.js new file mode 100644 index 000000000..49065e6c1 --- /dev/null +++ b/tests/nodejs-production/__tests__/dockerfileValidator.test.js @@ -0,0 +1,215 @@ +/** + * Tests for Dockerfile Validator + */ + +const { + checkNodeEnvProduction, + checkHealthcheck, + checkNonRootUser, + checkBaseImage +} = require('../src/validators/dockerfileValidator'); + +describe('Dockerfile Validator', () => { + + describe('checkNodeEnvProduction', () => { + test('passes with ENV NODE_ENV=production', () => { + const content = ` +FROM node:20-alpine +WORKDIR /app +ENV NODE_ENV=production +COPY . . +RUN npm ci --only=production +CMD ["node", "server.js"] + `; + const result = checkNodeEnvProduction(content); + expect(result.passed).toBe(true); + }); + + test('passes with ENV NODE_ENV production (space separator)', () => { + const content = ` +FROM node:20 +ENV NODE_ENV production +CMD ["npm", "start"] + `; + const result = checkNodeEnvProduction(content); + expect(result.passed).toBe(true); + }); + + test('passes with ARG NODE_ENV=production', () => { + const content = ` +FROM node:20-alpine +ARG NODE_ENV=production +RUN npm ci + `; + const result = checkNodeEnvProduction(content); + expect(result.passed).toBe(true); + }); + + test('fails when NODE_ENV is missing', () => { + const content = ` +FROM node:20-alpine +WORKDIR /app +COPY . . +RUN npm ci +CMD ["node", "server.js"] + `; + const result = checkNodeEnvProduction(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('NODE_ENV=production'); + }); + + test('fails when NODE_ENV is set to development', () => { + const content = ` +FROM node:20 +ENV NODE_ENV=development +CMD ["npm", "start"] + `; + const result = checkNodeEnvProduction(content); + expect(result.passed).toBe(false); + }); + }); + + describe('checkHealthcheck', () => { + test('passes with HEALTHCHECK CMD', () => { + const content = ` +FROM node:20-alpine +HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1 +CMD ["node", "server.js"] + `; + const result = checkHealthcheck(content); + expect(result.passed).toBe(true); + }); + + test('passes with HEALTHCHECK with options', () => { + const content = ` +FROM node:20-alpine +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl -f http://localhost:3000/health || exit 1 +CMD ["node", "server.js"] + `; + const result = checkHealthcheck(content); + expect(result.passed).toBe(true); + }); + + test('passes with HEALTHCHECK NONE (explicitly disabled)', () => { + const content = ` +FROM node:20-alpine +HEALTHCHECK NONE +CMD ["node", "server.js"] + `; + const result = checkHealthcheck(content); + expect(result.passed).toBe(true); + }); + + test('fails when HEALTHCHECK is missing', () => { + const content = ` +FROM node:20-alpine +WORKDIR /app +COPY . . +CMD ["node", "server.js"] + `; + const result = checkHealthcheck(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('HEALTHCHECK'); + }); + }); + + describe('checkNonRootUser', () => { + test('passes with USER node', () => { + const content = ` +FROM node:20-alpine +WORKDIR /app +COPY --chown=node:node . . +USER node +CMD ["node", "server.js"] + `; + const result = checkNonRootUser(content); + expect(result.passed).toBe(true); + }); + + test('passes with USER appuser', () => { + const content = ` +FROM node:20-alpine +RUN adduser -D appuser +USER appuser +CMD ["node", "server.js"] + `; + const result = checkNonRootUser(content); + expect(result.passed).toBe(true); + }); + + test('passes with numeric USER', () => { + const content = ` +FROM node:20-alpine +USER 1000 +CMD ["node", "server.js"] + `; + const result = checkNonRootUser(content); + expect(result.passed).toBe(true); + }); + + test('fails when running as root (node image)', () => { + const content = ` +FROM node:20-alpine +WORKDIR /app +COPY . . +CMD ["node", "server.js"] + `; + const result = checkNonRootUser(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('node'); + }); + + test('fails when USER root is explicit', () => { + const content = ` +FROM node:20 +USER root +CMD ["node", "server.js"] + `; + const result = checkNonRootUser(content); + expect(result.passed).toBe(false); + }); + }); + + describe('checkBaseImage', () => { + test('passes with alpine image', () => { + const content = `FROM node:20-alpine`; + const result = checkBaseImage(content); + expect(result.passed).toBe(true); + expect(result.message).toContain('optimized'); + }); + + test('passes with slim image', () => { + const content = `FROM node:20-slim`; + const result = checkBaseImage(content); + expect(result.passed).toBe(true); + }); + + test('passes with explicit version', () => { + const content = `FROM node:20.10.0`; + const result = checkBaseImage(content); + expect(result.passed).toBe(true); + }); + + test('warns with :latest tag', () => { + const content = `FROM node:latest`; + const result = checkBaseImage(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('specific version'); + }); + + test('warns with no tag', () => { + const content = `FROM node`; + const result = checkBaseImage(content); + expect(result.passed).toBe(false); + }); + + test('fails with no FROM instruction', () => { + const content = ` +WORKDIR /app +COPY . . + `; + const result = checkBaseImage(content); + expect(result.passed).toBe(false); + }); + }); +}); diff --git a/tests/nodejs-production/__tests__/expressValidator.test.js b/tests/nodejs-production/__tests__/expressValidator.test.js new file mode 100644 index 000000000..c8030f5b4 --- /dev/null +++ b/tests/nodejs-production/__tests__/expressValidator.test.js @@ -0,0 +1,236 @@ +/** + * Tests for Express.js Production Validator + */ + +const path = require('path'); +const { + checkTrustProxy, + checkHealthEndpoint, + checkCookieConfig, + checkPortBinding, + checkHostBinding +} = require('../src/validators/expressProductionValidator'); + +describe('Express Production Validator', () => { + + describe('checkTrustProxy', () => { + test('passes when trust proxy is set with number', () => { + const content = ` + const app = express(); + app.set('trust proxy', 1); + `; + const result = checkTrustProxy(content); + expect(result.passed).toBe(true); + }); + + test('passes when trust proxy is set with true', () => { + const content = `app.set("trust proxy", true);`; + const result = checkTrustProxy(content); + expect(result.passed).toBe(true); + }); + + test('passes when trust proxy is set to loopback', () => { + const content = `app.set('trust proxy', 'loopback');`; + const result = checkTrustProxy(content); + expect(result.passed).toBe(true); + }); + + test('fails when trust proxy is missing', () => { + const content = ` + const app = express(); + app.use(express.json()); + app.listen(3000); + `; + const result = checkTrustProxy(content); + expect(result.passed).toBe(false); + expect(result.message).toContain("trust proxy"); + }); + + test('fails when only other settings are present', () => { + const content = ` + app.set('view engine', 'ejs'); + app.set('port', 3000); + `; + const result = checkTrustProxy(content); + expect(result.passed).toBe(false); + }); + }); + + describe('checkHealthEndpoint', () => { + test('passes when /health route exists', () => { + const content = ` + app.get('/health', (req, res) => { + res.status(200).send('OK'); + }); + `; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(true); + }); + + test('passes when /healthz route exists (k8s convention)', () => { + const content = `app.get('/healthz', (req, res) => res.sendStatus(200));`; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(true); + }); + + test('passes when /ready route exists', () => { + const content = `router.get('/ready', healthController.check);`; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(true); + }); + + test('passes when /liveness route exists', () => { + const content = `app.get('/liveness', (req, res) => res.json({ status: 'ok' }));`; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(true); + }); + + test('fails when no health endpoint exists', () => { + const content = ` + app.get('/', (req, res) => res.send('Hello')); + app.get('/api/users', userController.list); + `; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('/health'); + }); + + test('fails with only similar but incorrect routes', () => { + const content = ` + app.get('/healthy-recipes', recipeController.list); + app.get('/api/health-data', dataController.get); + `; + const result = checkHealthEndpoint(content); + expect(result.passed).toBe(false); + }); + }); + + describe('checkCookieConfig', () => { + test('passes when no cookies are used', () => { + const content = ` + const app = express(); + app.get('/', (req, res) => res.send('Hello')); + `; + const result = checkCookieConfig(content); + expect(result.passed).toBe(true); + }); + + test('passes when cookie config is secure', () => { + const content = ` + app.use(session({ + cookie: { + sameSite: 'lax', + secure: process.env.NODE_ENV === 'production', + httpOnly: true + } + })); + `; + const result = checkCookieConfig(content); + expect(result.passed).toBe(true); + }); + + test('passes with strict sameSite', () => { + const content = ` + res.cookie('token', value, { + sameSite: 'strict', + secure: true + }); + `; + const result = checkCookieConfig(content); + expect(result.passed).toBe(true); + }); + + test('warns when sameSite is missing', () => { + const content = ` + app.use(session({ + cookie: { + secure: true, + httpOnly: true + } + })); + `; + const result = checkCookieConfig(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('sameSite'); + }); + + test('warns when secure is missing', () => { + const content = ` + res.cookie('session', data, { + sameSite: 'lax', + httpOnly: true + }); + `; + const result = checkCookieConfig(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('secure'); + }); + }); + + describe('checkPortBinding', () => { + test('passes with process.env.PORT fallback', () => { + const content = ` + const port = process.env.PORT || 3000; + app.listen(port); + `; + const result = checkPortBinding(content); + expect(result.passed).toBe(true); + }); + + test('passes with PORT in listen call', () => { + const content = `app.listen(process.env.PORT || 8080);`; + const result = checkPortBinding(content); + expect(result.passed).toBe(true); + }); + + test('passes with direct PORT assignment', () => { + const content = ` + const port = PORT || 3000; + server.listen(port); + `; + const result = checkPortBinding(content); + expect(result.passed).toBe(true); + }); + + test('fails with hardcoded port', () => { + const content = `app.listen(3000);`; + const result = checkPortBinding(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('Hardcoded'); + }); + + test('fails with hardcoded port 8080', () => { + const content = `server.listen(8080, () => console.log('Running'));`; + const result = checkPortBinding(content); + expect(result.passed).toBe(false); + }); + }); + + describe('checkHostBinding', () => { + test('passes with 0.0.0.0 binding', () => { + const content = `app.listen(port, '0.0.0.0', callback);`; + const result = checkHostBinding(content); + expect(result.passed).toBe(true); + }); + + test('passes with no explicit host (default)', () => { + const content = `app.listen(port, callback);`; + const result = checkHostBinding(content); + expect(result.passed).toBe(true); + }); + + test('warns when binding to localhost', () => { + const content = `app.listen(3000, 'localhost', callback);`; + const result = checkHostBinding(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('localhost'); + }); + + test('warns when binding to 127.0.0.1', () => { + const content = `server.listen(port, '127.0.0.1');`; + const result = checkHostBinding(content); + expect(result.passed).toBe(false); + expect(result.message).toContain('0.0.0.0'); + }); + }); +}); diff --git a/tests/nodejs-production/__tests__/integration.test.js b/tests/nodejs-production/__tests__/integration.test.js new file mode 100644 index 000000000..e0e20c7f9 --- /dev/null +++ b/tests/nodejs-production/__tests__/integration.test.js @@ -0,0 +1,116 @@ +/** + * Integration tests using fixtures + * + * Tests the validators against real project fixtures with expected results + */ + +const path = require('path'); +const fs = require('fs'); +const { validateExpressApp } = require('../src/validators/expressProductionValidator'); +const { validateDockerfile } = require('../src/validators/dockerfileValidator'); + +const fixturesDir = path.join(__dirname, '..', 'fixtures', 'projects'); +const expectationsPath = path.join(__dirname, '..', 'fixtures', 'expectations.json'); + +// Load expectations +const expectations = JSON.parse(fs.readFileSync(expectationsPath, 'utf8')); + +describe('Fixture-based Integration Tests', () => { + + // Get all fixture directories + const fixtures = fs.readdirSync(fixturesDir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name); + + describe('Express Validation', () => { + fixtures.forEach(fixtureName => { + const expected = expectations[fixtureName]; + if (!expected || !expected.express) return; + + describe(fixtureName, () => { + const projectDir = path.join(fixturesDir, fixtureName); + let result; + + beforeAll(() => { + result = validateExpressApp(projectDir); + }); + + test(`should ${expected.express.valid ? 'pass' : 'fail'} validation`, () => { + expect(result.valid).toBe(expected.express.valid); + }); + + if (expected.express.errors && expected.express.errors.length > 0) { + test('should have expected errors', () => { + expected.express.errors.forEach(errorSubstring => { + const hasError = result.errors.some(e => e.includes(errorSubstring)); + expect(hasError).toBe(true); + }); + }); + } + + if (expected.express.warnings && expected.express.warnings.length > 0) { + test('should have expected warnings', () => { + expected.express.warnings.forEach(warningSubstring => { + const hasWarning = result.warnings.some(w => w.includes(warningSubstring)); + expect(hasWarning).toBe(true); + }); + }); + } + + if (expected.express.checks) { + Object.entries(expected.express.checks).forEach(([checkName, shouldPass]) => { + test(`check: ${checkName} should ${shouldPass ? 'pass' : 'fail'}`, () => { + expect(result.checks[checkName].passed).toBe(shouldPass); + }); + }); + } + }); + }); + }); + + describe('Dockerfile Validation', () => { + fixtures.forEach(fixtureName => { + const expected = expectations[fixtureName]; + if (!expected || !expected.dockerfile) return; + + describe(fixtureName, () => { + const projectDir = path.join(fixturesDir, fixtureName); + let result; + + beforeAll(() => { + result = validateDockerfile(projectDir); + }); + + test(`should ${expected.dockerfile.valid ? 'pass' : 'fail'} validation`, () => { + expect(result.valid).toBe(expected.dockerfile.valid); + }); + + if (expected.dockerfile.errors && expected.dockerfile.errors.length > 0) { + test('should have expected errors', () => { + expected.dockerfile.errors.forEach(errorSubstring => { + const hasError = result.errors.some(e => e.includes(errorSubstring)); + expect(hasError).toBe(true); + }); + }); + } + + if (expected.dockerfile.warnings && expected.dockerfile.warnings.length > 0) { + test('should have expected warnings', () => { + expected.dockerfile.warnings.forEach(warningSubstring => { + const hasWarning = result.warnings.some(w => w.includes(warningSubstring)); + expect(hasWarning).toBe(true); + }); + }); + } + + if (expected.dockerfile.checks) { + Object.entries(expected.dockerfile.checks).forEach(([checkName, shouldPass]) => { + test(`check: ${checkName} should ${shouldPass ? 'pass' : 'fail'}`, () => { + expect(result.checks[checkName].passed).toBe(shouldPass); + }); + }); + } + }); + }); + }); +}); diff --git a/tests/nodejs-production/fixtures/expectations.json b/tests/nodejs-production/fixtures/expectations.json new file mode 100644 index 000000000..644734998 --- /dev/null +++ b/tests/nodejs-production/fixtures/expectations.json @@ -0,0 +1,85 @@ +{ + "express-prod-correct": { + "description": "Express app with all production best practices", + "express": { + "valid": true, + "errors": [], + "checks": { + "trustProxy": true, + "healthEndpoint": true, + "portBinding": true, + "hostBinding": true + } + }, + "dockerfile": { + "valid": true, + "errors": [], + "checks": { + "nodeEnvProduction": true, + "healthcheck": true, + "nonRootUser": true, + "properBaseImage": true + } + } + }, + "express-missing-trust-proxy": { + "description": "Express app missing trust proxy setting", + "express": { + "valid": false, + "errors": ["trust proxy"], + "checks": { + "trustProxy": false, + "healthEndpoint": true, + "portBinding": true + } + } + }, + "express-missing-health": { + "description": "Express app missing health endpoint", + "express": { + "valid": false, + "errors": ["/health"], + "checks": { + "trustProxy": true, + "healthEndpoint": false, + "portBinding": true + } + } + }, + "express-localhost-bind": { + "description": "Express app binding to localhost instead of 0.0.0.0", + "express": { + "valid": false, + "errors": ["Hardcoded"], + "warnings": ["localhost"], + "checks": { + "trustProxy": true, + "healthEndpoint": true, + "portBinding": false, + "hostBinding": false + } + } + }, + "dockerfile-missing-healthcheck": { + "description": "Dockerfile missing HEALTHCHECK, NODE_ENV, and USER", + "express": { + "valid": true, + "checks": { + "trustProxy": true, + "healthEndpoint": true, + "portBinding": true + } + }, + "dockerfile": { + "valid": false, + "errors": ["NODE_ENV"], + "warnings": ["HEALTHCHECK", "node"], + "checks": { + "nodeEnvProduction": false, + "healthcheck": false, + "nonRootUser": false, + "properBaseImage": true + } + } + } +} diff --git a/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/Dockerfile b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/Dockerfile new file mode 100644 index 000000000..280f0ef59 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . + +# This Dockerfile is missing production best practices: +# - No NODE_ENV environment variable +# - No USER instruction (running as root) +# - No HEALTHCHECK instruction + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/package.json b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/package.json new file mode 100644 index 000000000..c75614589 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/package.json @@ -0,0 +1,8 @@ +{ + "name": "dockerfile-missing-healthcheck", + "version": "1.0.0", + "main": "server.js", + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/server.js b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/server.js new file mode 100644 index 000000000..6d9182884 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/dockerfile-missing-healthcheck/server.js @@ -0,0 +1,16 @@ +const express = require('express'); + +const app = express(); +const port = process.env.PORT || 3000; + +app.set('trust proxy', 1); + +app.get('/', (req, res) => { + res.json({ message: 'Hello World' }); +}); + +app.get('/health', (req, res) => { + res.status(200).send('OK'); +}); + +app.listen(port); diff --git a/tests/nodejs-production/fixtures/projects/express-localhost-bind/package.json b/tests/nodejs-production/fixtures/projects/express-localhost-bind/package.json new file mode 100644 index 000000000..9f4b04562 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-localhost-bind/package.json @@ -0,0 +1,8 @@ +{ + "name": "express-localhost-bind", + "version": "1.0.0", + "main": "server.js", + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/tests/nodejs-production/fixtures/projects/express-localhost-bind/server.js b/tests/nodejs-production/fixtures/projects/express-localhost-bind/server.js new file mode 100644 index 000000000..3d7f8a8ef --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-localhost-bind/server.js @@ -0,0 +1,20 @@ +const express = require('express'); + +const app = express(); + +app.set('trust proxy', 1); + +app.use(express.json()); + +app.get('/', (req, res) => { + res.json({ message: 'Hello World' }); +}); + +app.get('/health', (req, res) => { + res.status(200).send('OK'); +}); + +// BAD: Binding to localhost instead of 0.0.0.0 +app.listen(3000, 'localhost', () => { + console.log('Server running on localhost:3000'); +}); diff --git a/tests/nodejs-production/fixtures/projects/express-missing-health/package.json b/tests/nodejs-production/fixtures/projects/express-missing-health/package.json new file mode 100644 index 000000000..01fb6068c --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-missing-health/package.json @@ -0,0 +1,8 @@ +{ + "name": "express-missing-health", + "version": "1.0.0", + "main": "server.js", + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/tests/nodejs-production/fixtures/projects/express-missing-health/server.js b/tests/nodejs-production/fixtures/projects/express-missing-health/server.js new file mode 100644 index 000000000..5fec1a052 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-missing-health/server.js @@ -0,0 +1,15 @@ +const express = require('express'); + +const app = express(); + +app.set('trust proxy', 1); + +app.use(express.json()); + +app.get('/', (req, res) => { + res.json({ message: 'Hello World' }); +}); + +// MISSING: Health check endpoint + +app.listen(process.env.PORT || 3000); diff --git a/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/package.json b/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/package.json new file mode 100644 index 000000000..dae08c288 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/package.json @@ -0,0 +1,8 @@ +{ + "name": "express-missing-trust-proxy", + "version": "1.0.0", + "main": "server.js", + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/server.js b/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/server.js new file mode 100644 index 000000000..f250a007e --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-missing-trust-proxy/server.js @@ -0,0 +1,18 @@ +const express = require('express'); + +const app = express(); + +// This app is missing trust proxy configuration! +// Behind Azure load balancers, you need: app.enable('trust proxy') + +app.use(express.json()); + +app.get('/', (req, res) => { + res.json({ message: 'Hello World' }); +}); + +app.get('/health', (req, res) => { + res.status(200).send('OK'); +}); + +app.listen(process.env.PORT || 3000); diff --git a/tests/nodejs-production/fixtures/projects/express-prod-correct/Dockerfile b/tests/nodejs-production/fixtures/projects/express-prod-correct/Dockerfile new file mode 100644 index 000000000..fe6dc9b81 --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-prod-correct/Dockerfile @@ -0,0 +1,24 @@ +FROM node:20-alpine + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci --only=production + +# Copy application code +COPY --chown=node:node . . + +# Production environment +ENV NODE_ENV=production + +# Run as non-root user +USER node + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/tests/nodejs-production/fixtures/projects/express-prod-correct/package.json b/tests/nodejs-production/fixtures/projects/express-prod-correct/package.json new file mode 100644 index 000000000..f0f07fcae --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-prod-correct/package.json @@ -0,0 +1,12 @@ +{ + "name": "express-prod-correct", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "express-session": "^1.17.3" + } +} diff --git a/tests/nodejs-production/fixtures/projects/express-prod-correct/server.js b/tests/nodejs-production/fixtures/projects/express-prod-correct/server.js new file mode 100644 index 000000000..cacc0c19b --- /dev/null +++ b/tests/nodejs-production/fixtures/projects/express-prod-correct/server.js @@ -0,0 +1,38 @@ +const express = require('express'); +const session = require('express-session'); + +const app = express(); +const port = process.env.PORT || 3000; + +// Production best practice: Trust proxy for Azure load balancers +app.set('trust proxy', 1); + +app.use(express.json()); + +// Production best practice: Secure cookie configuration +app.use(session({ + secret: process.env.SESSION_SECRET || 'dev-secret', + resave: false, + saveUninitialized: false, + cookie: { + sameSite: 'lax', + secure: process.env.NODE_ENV === 'production', + httpOnly: true, + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } +})); + +// Routes +app.get('/', (req, res) => { + res.json({ message: 'Hello World' }); +}); + +// Production best practice: Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); +}); + +// Production best practice: Bind to 0.0.0.0 for container compatibility +app.listen(port, '0.0.0.0', () => { + console.log(`Server running on port ${port}`); +}); diff --git a/tests/nodejs-production/jest.config.js b/tests/nodejs-production/jest.config.js new file mode 100644 index 000000000..6ebf99110 --- /dev/null +++ b/tests/nodejs-production/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.js'], + collectCoverageFrom: [ + 'src/**/*.js', + '!src/**/*.test.js' + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + verbose: true +}; diff --git a/tests/nodejs-production/package-lock.json b/tests/nodejs-production/package-lock.json new file mode 100644 index 000000000..cffdf14d9 --- /dev/null +++ b/tests/nodejs-production/package-lock.json @@ -0,0 +1,3651 @@ +{ + "name": "nodejs-production-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodejs-production-tests", + "version": "1.0.0", + "devDependencies": { + "jest": "^29.7.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/nodejs-production/package.json b/tests/nodejs-production/package.json new file mode 100644 index 000000000..65d92d7be --- /dev/null +++ b/tests/nodejs-production/package.json @@ -0,0 +1,16 @@ +{ + "name": "nodejs-production-tests", + "version": "1.0.0", + "description": "Tests for azure-nodejs-production skill - validates Express.js production best practices", + "scripts": { + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "devDependencies": { + "jest": "^29.7.0" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/tests/nodejs-production/src/validators/dockerfileValidator.js b/tests/nodejs-production/src/validators/dockerfileValidator.js new file mode 100644 index 000000000..70270ae39 --- /dev/null +++ b/tests/nodejs-production/src/validators/dockerfileValidator.js @@ -0,0 +1,221 @@ +/** + * Dockerfile Validator for Node.js Production + * + * Validates Dockerfiles for Node.js application production best practices + * based on the azure-nodejs-production skill requirements. + * + * Checks for: + * - NODE_ENV=production environment variable + * - HEALTHCHECK instruction + * - Non-root user + * - Proper base image + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Validation result structure + * @typedef {Object} DockerValidationResult + * @property {boolean} valid - Whether all checks passed + * @property {string[]} errors - List of validation errors + * @property {string[]} warnings - List of validation warnings + * @property {Object} checks - Individual check results + */ + +/** + * Validates a Dockerfile for Node.js production best practices + * @param {string} projectDir - Path to the project directory + * @returns {DockerValidationResult} + */ +function validateDockerfile(projectDir) { + const result = { + valid: true, + errors: [], + warnings: [], + checks: { + hasDockerfile: { passed: false, message: '' }, + nodeEnvProduction: { passed: false, message: '' }, + healthcheck: { passed: false, message: '' }, + nonRootUser: { passed: false, message: '' }, + properBaseImage: { passed: false, message: '' } + } + }; + + // Find Dockerfile + const dockerfilePath = findDockerfile(projectDir); + + if (!dockerfilePath) { + result.checks.hasDockerfile = { passed: false, message: 'No Dockerfile found' }; + result.warnings.push('No Dockerfile found in project'); + // Not having a Dockerfile isn't necessarily an error + return result; + } + + result.checks.hasDockerfile = { passed: true, message: `Found: ${path.basename(dockerfilePath)}` }; + + let content; + try { + content = fs.readFileSync(dockerfilePath, 'utf8'); + } catch (err) { + result.valid = false; + result.errors.push(`Cannot read Dockerfile: ${err.message}`); + return result; + } + + // Check NODE_ENV=production + result.checks.nodeEnvProduction = checkNodeEnvProduction(content); + if (!result.checks.nodeEnvProduction.passed) { + result.errors.push(result.checks.nodeEnvProduction.message); + } + + // Check HEALTHCHECK + result.checks.healthcheck = checkHealthcheck(content); + if (!result.checks.healthcheck.passed) { + result.warnings.push(result.checks.healthcheck.message); + } + + // Check non-root user + result.checks.nonRootUser = checkNonRootUser(content); + if (!result.checks.nonRootUser.passed) { + result.warnings.push(result.checks.nonRootUser.message); + } + + // Check base image + result.checks.properBaseImage = checkBaseImage(content); + if (!result.checks.properBaseImage.passed) { + result.warnings.push(result.checks.properBaseImage.message); + } + + // Set overall validity + result.valid = result.errors.length === 0; + + return result; +} + +/** + * Find Dockerfile in project directory + */ +function findDockerfile(projectDir) { + const candidates = ['Dockerfile', 'dockerfile', 'Dockerfile.prod', 'Dockerfile.production']; + + for (const name of candidates) { + const fullPath = path.join(projectDir, name); + if (fs.existsSync(fullPath)) { + return fullPath; + } + } + + return null; +} + +/** + * Check for NODE_ENV=production + */ +function checkNodeEnvProduction(content) { + // Match: ENV NODE_ENV=production or ENV NODE_ENV production + const nodeEnvRegex = /ENV\s+NODE_ENV[=\s]+production/i; + + // Also check ARG with default + const argRegex = /ARG\s+NODE_ENV=production/i; + + if (nodeEnvRegex.test(content) || argRegex.test(content)) { + return { passed: true, message: 'NODE_ENV=production is set' }; + } + + return { + passed: false, + message: 'Missing NODE_ENV=production. Add: ENV NODE_ENV=production' + }; +} + +/** + * Check for HEALTHCHECK instruction + */ +function checkHealthcheck(content) { + // Normalize line continuations for multi-line HEALTHCHECK instructions + const normalizedContent = content.replace(/\\\n\s*/g, ' '); + + // Match: HEALTHCHECK CMD or HEALTHCHECK --interval=30s ... CMD + // Handles both --option=value and --option value formats + const healthcheckRegex = /^HEALTHCHECK\s+(?:--[\w-]+=?\S*\s+)*(?:CMD|NONE)/im; + + if (healthcheckRegex.test(normalizedContent)) { + return { passed: true, message: 'HEALTHCHECK instruction present' }; + } + + return { + passed: false, + message: 'Missing HEALTHCHECK instruction. Add: HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1' + }; +} + +/** + * Check for non-root user + */ +function checkNonRootUser(content) { + // Match: USER node, USER appuser, USER 1000, etc (not USER root) + const userRegex = /^USER\s+(?!root)(\w+|\d+)/im; + + if (userRegex.test(content)) { + return { passed: true, message: 'Running as non-root user' }; + } + + // Check if using official node image (has node user) + const nodeImageRegex = /FROM\s+node:/i; + if (nodeImageRegex.test(content)) { + return { + passed: false, + message: 'Running as root. Add: USER node (node image includes this user)' + }; + } + + return { + passed: false, + message: 'No USER instruction found. Consider running as non-root user' + }; +} + +/** + * Check for proper base image (Alpine preferred for smaller size) + */ +function checkBaseImage(content) { + // Check FROM line + const fromRegex = /^FROM\s+(\S+)/im; + const match = content.match(fromRegex); + + if (!match) { + return { passed: false, message: 'No FROM instruction found' }; + } + + const image = match[1].toLowerCase(); + + // Check for Alpine or slim variants (recommended) + if (image.includes('alpine') || image.includes('slim')) { + return { passed: true, message: 'Using optimized base image' }; + } + + // Check for explicit version (good practice) + if (image.includes(':') && !image.endsWith(':latest')) { + return { passed: true, message: 'Base image has explicit version' }; + } + + // Using :latest is not ideal + if (image.endsWith(':latest') || !image.includes(':')) { + return { + passed: false, + message: 'Consider using specific version and slim/alpine variant (e.g., node:20-alpine)' + }; + } + + return { passed: true, message: 'Base image is specified' }; +} + +module.exports = { + validateDockerfile, + findDockerfile, + checkNodeEnvProduction, + checkHealthcheck, + checkNonRootUser, + checkBaseImage +}; diff --git a/tests/nodejs-production/src/validators/expressProductionValidator.js b/tests/nodejs-production/src/validators/expressProductionValidator.js new file mode 100644 index 000000000..956cfab7d --- /dev/null +++ b/tests/nodejs-production/src/validators/expressProductionValidator.js @@ -0,0 +1,270 @@ +/** + * Express.js Production Validator + * + * Validates Express.js applications for Azure production best practices based on + * the azure-nodejs-production skill requirements. + * + * Checks for: + * - Trust proxy configuration (required behind Azure load balancers) + * - Health check endpoint (/health) + * - Cookie security settings (sameSite, secure, httpOnly) + * - Port binding (process.env.PORT, 0.0.0.0) + * - Environment detection (NODE_ENV) + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Validation result structure + * @typedef {Object} ValidationResult + * @property {boolean} valid - Whether all checks passed + * @property {string[]} errors - List of validation errors + * @property {string[]} warnings - List of validation warnings + * @property {Object} checks - Individual check results + */ + +/** + * Validates an Express.js application for production readiness + * @param {string} projectDir - Path to the project directory + * @returns {ValidationResult} + */ +function validateExpressApp(projectDir) { + const result = { + valid: true, + errors: [], + warnings: [], + checks: { + trustProxy: { passed: false, message: '' }, + healthEndpoint: { passed: false, message: '' }, + cookieConfig: { passed: false, message: '' }, + portBinding: { passed: false, message: '' }, + hostBinding: { passed: false, message: '' } + } + }; + + // Find all JS/TS files that might contain Express app code + const appFiles = findAppFiles(projectDir); + + if (appFiles.length === 0) { + result.valid = false; + result.errors.push('No JavaScript/TypeScript files found'); + return result; + } + + // Read and combine all app file contents for analysis + const combinedContent = appFiles.map(f => { + try { + return fs.readFileSync(f, 'utf8'); + } catch { + return ''; + } + }).join('\n'); + + // Check trust proxy + result.checks.trustProxy = checkTrustProxy(combinedContent); + if (!result.checks.trustProxy.passed) { + result.errors.push(result.checks.trustProxy.message); + } + + // Check health endpoint + result.checks.healthEndpoint = checkHealthEndpoint(combinedContent); + if (!result.checks.healthEndpoint.passed) { + result.errors.push(result.checks.healthEndpoint.message); + } + + // Check cookie configuration + result.checks.cookieConfig = checkCookieConfig(combinedContent); + if (!result.checks.cookieConfig.passed && result.checks.cookieConfig.message) { + result.warnings.push(result.checks.cookieConfig.message); + } + + // Check port binding + result.checks.portBinding = checkPortBinding(combinedContent); + if (!result.checks.portBinding.passed) { + result.errors.push(result.checks.portBinding.message); + } + + // Check host binding + result.checks.hostBinding = checkHostBinding(combinedContent); + if (!result.checks.hostBinding.passed) { + result.warnings.push(result.checks.hostBinding.message); + } + + // Set overall validity (only errors affect validity, not warnings) + result.valid = result.errors.length === 0; + + return result; +} + +/** + * Find all JavaScript/TypeScript files that might contain Express app code + */ +function findAppFiles(projectDir) { + const files = []; + const extensions = ['.js', '.ts', '.mjs', '.cjs']; + + function scanDir(dir) { + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') { + scanDir(fullPath); + } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) { + files.push(fullPath); + } + } + } catch { + // Ignore permission errors + } + } + + scanDir(projectDir); + return files; +} + +/** + * Check for trust proxy configuration + * Required for Express apps behind Azure load balancers/App Gateway + */ +function checkTrustProxy(content) { + // Match: app.set('trust proxy', 1) or app.set("trust proxy", true) etc + const trustProxyRegex = /app\.set\s*\(\s*['"]trust\s*proxy['"]\s*,\s*(?:1|true|'loopback'|"loopback")/i; + + // Also check for express.json with trust proxy in options + const enableTrustProxy = /enable\s*['"]?trust\s*proxy['"]?\s*:\s*true/i; + + if (trustProxyRegex.test(content) || enableTrustProxy.test(content)) { + return { passed: true, message: 'Trust proxy is configured' }; + } + + return { + passed: false, + message: "Missing 'trust proxy' configuration. Add: app.set('trust proxy', 1)" + }; +} + +/** + * Check for health endpoint + * Required for Azure App Service health checks + */ +function checkHealthEndpoint(content) { + // Match: app.get('/health', ...) or router.get('/health', ...) + const healthRouteRegex = /(?:app|router)\.get\s*\(\s*['"]\/health['"]/i; + + // Also check for /healthz (Kubernetes convention) + const healthzRouteRegex = /(?:app|router)\.get\s*\(\s*['"]\/healthz?['"]/i; + + // Check for readiness/liveness probes + const probeRegex = /(?:app|router)\.get\s*\(\s*['"]\/(?:ready|readiness|liveness|alive)['"]/i; + + if (healthRouteRegex.test(content) || healthzRouteRegex.test(content) || probeRegex.test(content)) { + return { passed: true, message: 'Health endpoint is configured' }; + } + + return { + passed: false, + message: "Missing health check endpoint. Add: app.get('/health', (req, res) => res.status(200).send('OK'))" + }; +} + +/** + * Check cookie configuration for security + * sameSite should be 'lax' or 'strict', secure should be conditional on production + */ +function checkCookieConfig(content) { + // Check if cookies or sessions are used + const usesCookies = /cookie|session/i.test(content); + + if (!usesCookies) { + return { passed: true, message: 'No cookie/session usage detected' }; + } + + // Check for sameSite configuration + const sameSiteRegex = /sameSite\s*:\s*['"](?:lax|strict|none)['"]/i; + const secureCookieRegex = /secure\s*:\s*(?:true|process\.env\.NODE_ENV\s*===?\s*['"]production['"])/i; + + const hasSameSite = sameSiteRegex.test(content); + const hasSecure = secureCookieRegex.test(content); + + if (hasSameSite && hasSecure) { + return { passed: true, message: 'Cookie configuration looks secure' }; + } + + const issues = []; + if (!hasSameSite) issues.push("sameSite: 'lax'"); + if (!hasSecure) issues.push("secure: process.env.NODE_ENV === 'production'"); + + return { + passed: false, + message: `Cookie security settings missing: ${issues.join(', ')}` + }; +} + +/** + * Check for proper port binding using environment variable + */ +function checkPortBinding(content) { + // Match: process.env.PORT || 3000, PORT || 3000, etc + const portEnvRegex = /(?:process\.env\.)?PORT\s*\|\|?\s*\d+/; + + // Match: const port = process.env.PORT + const portAssignRegex = /(?:const|let|var)\s+port\s*=\s*(?:process\.env\.)?PORT/i; + + // Match: listen(process.env.PORT + const listenPortRegex = /\.listen\s*\(\s*(?:process\.env\.)?PORT/; + + if (portEnvRegex.test(content) || portAssignRegex.test(content) || listenPortRegex.test(content)) { + return { passed: true, message: 'Port binding uses environment variable' }; + } + + // Check if there's a hardcoded port + const hardcodedPortRegex = /\.listen\s*\(\s*\d{4}/; + if (hardcodedPortRegex.test(content)) { + return { + passed: false, + message: 'Hardcoded port detected. Use: process.env.PORT || 3000' + }; + } + + return { + passed: false, + message: 'Port binding not found. Use: app.listen(process.env.PORT || 3000)' + }; +} + +/** + * Check for proper host binding (0.0.0.0 not localhost) + */ +function checkHostBinding(content) { + // Check for localhost binding (problematic in containers) + const localhostRegex = /\.listen\s*\([^)]*['"](?:localhost|127\.0\.0\.1)['"]/; + + if (localhostRegex.test(content)) { + return { + passed: false, + message: "Binding to localhost detected. Use '0.0.0.0' for container compatibility" + }; + } + + // Check for proper 0.0.0.0 binding + const properHostRegex = /\.listen\s*\([^)]*['"]0\.0\.0\.0['"]/; + + if (properHostRegex.test(content)) { + return { passed: true, message: 'Host binding is container-compatible' }; + } + + // No explicit host is usually OK (defaults vary) + return { passed: true, message: 'No explicit host binding (may be OK)' }; +} + +module.exports = { + validateExpressApp, + findAppFiles, + checkTrustProxy, + checkHealthEndpoint, + checkCookieConfig, + checkPortBinding, + checkHostBinding +}; diff --git a/tests/validation/README.md b/tests/validation/README.md new file mode 100644 index 000000000..9e92a63df --- /dev/null +++ b/tests/validation/README.md @@ -0,0 +1,196 @@ +# Azure Validation Tests + +Automated tests for the `azure-validation` and `azure-deployment-preflight` skills. These tests verify validation logic for Azure resource naming, Bicep file detection, and preflight checks. + +--- + +## Table of Contents + +- [Setup](#setup) +- [Running Tests](#running-tests) +- [Test Structure](#test-structure) +- [Test Coverage](#test-coverage) +- [Adding New Tests](#adding-new-tests) + +--- + +## Setup + +### Prerequisites + +- Node.js 18+ installed +- npm or yarn + +### Installation + +```bash +cd tests/validation +npm install +``` + +--- + +## Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Run with coverage report +npm run test:coverage + +# Run specific test file +npm test -- --testPathPattern="resourceName" +``` + +### Expected Output + +``` +Test Suites: 4 passed, 4 total +Tests: 110+ passed, 110+ total +Time: ~0.5s +``` + +--- + +## Test Structure + +``` +tests/validation/ +├── README.md +├── package.json +├── jest.config.js +├── src/ +│ └── validators/ +│ ├── resourceNameValidator.js # Azure resource naming rules +│ ├── bicepValidator.js # Bicep file detection & validation +│ └── preflightValidator.js # Preflight check utilities +├── fixtures/ +│ ├── bicep/ +│ │ ├── valid-resourcegroup.bicep # Resource group scoped +│ │ ├── valid-subscription.bicep # Subscription scoped +│ │ ├── invalid-syntax.bicep # Intentional errors +│ │ └── with-params/ +│ │ ├── main.bicep +│ │ └── main.bicepparam +│ └── projects/ +│ ├── azd-project/ +│ │ └── azure.yaml +│ └── standalone-bicep/ +│ └── main.bicep +└── __tests__/ + ├── resourceNameValidator.test.js # Naming constraint tests + ├── bicepValidator.test.js # Bicep detection tests + ├── preflightValidator.test.js # Preflight utility tests + └── integration.test.js # End-to-end scenarios +``` + +--- + +## Test Coverage + +### Resource Naming Validation + +| Resource | Tests | Key Constraints | +|----------|-------|-----------------| +| Storage Account | 12 | 3-24 chars, lowercase+numbers only | +| Key Vault | 8 | 3-24 chars, alphanumerics+hyphens, must start with letter | +| Container Registry | 6 | 5-50 chars, alphanumerics only (no hyphens!) | +| Container App | 6 | 2-32 chars, lowercase+numbers+hyphens | +| App Service | 4 | 2-60 chars, alphanumerics+hyphens | +| Function App | 2 | Same as App Service | +| Resource Group | 4 | 1-90 chars, alphanumerics+hyphens+underscores+periods | +| Cosmos DB | 4 | 3-44 chars, lowercase+numbers+hyphens | + +### Bicep Validation + +| Feature | Tests | +|---------|-------| +| Target scope detection | 7 | +| CLI command mapping | 5 | +| Parameter file discovery | 5 | +| azd project detection | 4 | +| Bicep error parsing | 4 | +| Content validation (security) | 5 | + +### Preflight Validation + +| Feature | Tests | +|---------|-------| +| Azure CLI output parsing | 3 | +| Version parsing/comparison | 8 | +| Tool validation | 3 | +| Required tools detection | 5 | +| Validation fallback logic | 5 | +| Report generation | 6 | + +--- + +## Adding New Tests + +### Adding a New Resource Type + +1. Add constraints to `src/validators/resourceNameValidator.js`: + +```javascript +const RESOURCE_CONSTRAINTS = { + // ... existing + newResource: { + name: 'New Resource', + minLength: 3, + maxLength: 50, + pattern: /^[a-z][a-z0-9-]*$/, + patternDescription: 'lowercase letters, numbers, and hyphens', + globallyUnique: true + } +}; +``` + +2. Add tests to `__tests__/resourceNameValidator.test.js`: + +```javascript +describe('New Resource', () => { + const resourceType = 'newResource'; + + test('accepts valid name', () => { + const result = validateResourceName('my-new-resource', resourceType); + expect(result.valid).toBe(true); + }); + + test('rejects invalid characters', () => { + const result = validateResourceName('UPPERCASE', resourceType); + expect(result.valid).toBe(false); + }); +}); +``` + +### Adding a New Bicep Validation Rule + +1. Add validation logic to `src/validators/bicepValidator.js`: + +```javascript +function validateBicepContent(content) { + // ... existing checks + + // New check + if (/* some condition */) { + warnings.push({ + type: 'new-category', + message: 'Description of the issue' + }); + } +} +``` + +2. Add tests to `__tests__/bicepValidator.test.js`. + +--- + +## Related Documentation + +- [azure-validation SKILL.md](../../plugin/skills/azure-validation/SKILL.md) +- [azure-deployment-preflight SKILL.md](../../plugin/skills/azure-deployment-preflight/SKILL.md) +- [Azure naming rules](https://learn.microsoft.com/azure/azure-resource-manager/management/resource-name-rules) diff --git a/tests/validation/__tests__/bicepValidator.test.js b/tests/validation/__tests__/bicepValidator.test.js new file mode 100644 index 000000000..234353f86 --- /dev/null +++ b/tests/validation/__tests__/bicepValidator.test.js @@ -0,0 +1,356 @@ +/** + * Bicep Validator Tests + * + * Tests for Bicep file detection, scope detection, and validation. + * Based on azure-deployment-preflight/SKILL.md. + */ + +const { + detectTargetScope, + getCommandForScope, + getRequiredParamsForScope, + findParameterFiles, + isAzdProject, + findBicepFiles, + parseBicepErrors, + validateBicepContent, + analyzeBicepFile, + SCOPE_COMMANDS +} = require('../src/validators/bicepValidator'); + +describe('Bicep Validator', () => { + describe('detectTargetScope', () => { + test('detects resourceGroup scope', () => { + const content = `targetScope = 'resourceGroup'\n\nparam location string`; + expect(detectTargetScope(content)).toBe('resourceGroup'); + }); + + test('detects subscription scope', () => { + const content = `targetScope = 'subscription'\n\nparam location string`; + expect(detectTargetScope(content)).toBe('subscription'); + }); + + test('detects managementGroup scope', () => { + const content = `targetScope = 'managementGroup'\n\nparam mgId string`; + expect(detectTargetScope(content)).toBe('managementGroup'); + }); + + test('detects tenant scope', () => { + const content = `targetScope = 'tenant'\n\nparam location string`; + expect(detectTargetScope(content)).toBe('tenant'); + }); + + test('returns resourceGroup as default when no scope specified', () => { + const content = `param location string\nparam name string`; + expect(detectTargetScope(content)).toBe('resourceGroup'); + }); + + test('handles double quotes', () => { + const content = `targetScope = "subscription"\n\nparam location string`; + expect(detectTargetScope(content)).toBe('subscription'); + }); + + test('handles whitespace variations', () => { + const content = `targetScope = 'subscription'`; + expect(detectTargetScope(content)).toBe('subscription'); + }); + }); + + describe('getCommandForScope', () => { + test('returns correct command for resourceGroup', () => { + expect(getCommandForScope('resourceGroup')).toBe('az deployment group what-if'); + }); + + test('returns correct command for subscription', () => { + expect(getCommandForScope('subscription')).toBe('az deployment sub what-if'); + }); + + test('returns correct command for managementGroup', () => { + expect(getCommandForScope('managementGroup')).toBe('az deployment mg what-if'); + }); + + test('returns correct command for tenant', () => { + expect(getCommandForScope('tenant')).toBe('az deployment tenant what-if'); + }); + + test('returns null for unknown scope', () => { + expect(getCommandForScope('unknown')).toBeNull(); + }); + }); + + describe('getRequiredParamsForScope', () => { + test('resourceGroup requires --resource-group', () => { + const params = getRequiredParamsForScope('resourceGroup'); + expect(params).toContain('--resource-group'); + }); + + test('subscription requires --location', () => { + const params = getRequiredParamsForScope('subscription'); + expect(params).toContain('--location'); + }); + + test('managementGroup requires --location and --management-group-id', () => { + const params = getRequiredParamsForScope('managementGroup'); + expect(params).toContain('--location'); + expect(params).toContain('--management-group-id'); + }); + + test('tenant requires --location', () => { + const params = getRequiredParamsForScope('tenant'); + expect(params).toContain('--location'); + }); + + test('returns empty array for unknown scope', () => { + expect(getRequiredParamsForScope('unknown')).toEqual([]); + }); + }); + + describe('findParameterFiles', () => { + test('finds .bicepparam file', () => { + const files = ['infra/main.bicep', 'infra/main.bicepparam']; + const result = findParameterFiles('infra/main.bicep', files); + + expect(result.found).toBe(true); + expect(result.bicepparam).toBe('infra/main.bicepparam'); + }); + + test('finds .parameters.json file', () => { + const files = ['main.bicep', 'main.parameters.json']; + const result = findParameterFiles('main.bicep', files); + + expect(result.found).toBe(true); + expect(result.json).toContain('main.parameters.json'); + }); + + test('finds generic parameters.json', () => { + const files = ['main.bicep', 'parameters.json']; + const result = findParameterFiles('main.bicep', files); + + expect(result.found).toBe(true); + expect(result.json).toContain('parameters.json'); + }); + + test('prefers .bicepparam over .parameters.json', () => { + const files = ['main.bicep', 'main.bicepparam', 'main.parameters.json']; + const result = findParameterFiles('main.bicep', files); + + expect(result.found).toBe(true); + expect(result.bicepparam).toContain('main.bicepparam'); + expect(result.json).toContain('main.parameters.json'); + }); + + test('returns found: false when no parameter files', () => { + const files = ['main.bicep', 'other.txt']; + const result = findParameterFiles('main.bicep', files); + + expect(result.found).toBe(false); + expect(result.bicepparam).toBeNull(); + expect(result.json).toBeNull(); + }); + }); + + describe('isAzdProject', () => { + test('detects azure.yaml in root', () => { + const files = ['azure.yaml', 'package.json', 'infra/main.bicep']; + expect(isAzdProject(files)).toBe(true); + }); + + test('detects azure.yaml in subdirectory', () => { + const files = ['src/app.js', 'project/azure.yaml']; + expect(isAzdProject(files)).toBe(true); + }); + + test('returns false when no azure.yaml', () => { + const files = ['main.bicep', 'parameters.json', 'package.json']; + expect(isAzdProject(files)).toBe(false); + }); + + test('returns false for empty file list', () => { + expect(isAzdProject([])).toBe(false); + }); + }); + + describe('findBicepFiles', () => { + test('finds all .bicep files', () => { + const files = [ + 'main.bicep', + 'modules/storage.bicep', + 'modules/network.bicep', + 'parameters.json', + 'README.md' + ]; + + const bicepFiles = findBicepFiles(files); + + expect(bicepFiles).toHaveLength(3); + expect(bicepFiles).toContain('main.bicep'); + expect(bicepFiles).toContain('modules/storage.bicep'); + expect(bicepFiles).toContain('modules/network.bicep'); + }); + + test('returns empty array when no bicep files', () => { + const files = ['package.json', 'README.md']; + expect(findBicepFiles(files)).toEqual([]); + }); + + test('does not match .bicepparam files', () => { + const files = ['main.bicep', 'main.bicepparam']; + const bicepFiles = findBicepFiles(files); + + expect(bicepFiles).toHaveLength(1); + expect(bicepFiles).not.toContain('main.bicepparam'); + }); + }); + + describe('parseBicepErrors', () => { + test('parses single error', () => { + const output = `/path/to/file.bicep(22,51) : Error BCP064: Found unexpected tokens in interpolated expression.`; + const errors = parseBicepErrors(output); + + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual({ + file: '/path/to/file.bicep', + line: 22, + column: 51, + severity: 'error', + code: 'BCP064', + message: 'Found unexpected tokens in interpolated expression.' + }); + }); + + test('parses warning', () => { + const output = `main.bicep(10,5) : Warning BCP081: Resource type is deprecated.`; + const errors = parseBicepErrors(output); + + expect(errors).toHaveLength(1); + expect(errors[0].severity).toBe('warning'); + expect(errors[0].code).toBe('BCP081'); + }); + + test('parses multiple errors', () => { + const output = ` +file.bicep(1,1) : Error BCP001: First error. +file.bicep(5,10) : Error BCP002: Second error. +file.bicep(10,1) : Warning BCP003: A warning. +`; + const errors = parseBicepErrors(output); + + expect(errors).toHaveLength(3); + }); + + test('returns empty array for no errors', () => { + const output = 'Build succeeded.'; + expect(parseBicepErrors(output)).toEqual([]); + }); + }); + + describe('validateBicepContent', () => { + test('warns on hardcoded password', () => { + const content = ` +param name string +var config = { + password = 'mysecretpassword123' +} +`; + const result = validateBicepContent(content); + + expect(result.warnings.length).toBeGreaterThan(0); + expect(result.warnings[0].type).toBe('security'); + expect(result.warnings[0].message).toContain('hardcoded password'); + }); + + test('warns on sensitive param without @secure()', () => { + const content = `param dbPassword string +param location string +`; + const result = validateBicepContent(content); + + // Should warn about sensitive parameter names + expect(result.warnings.length).toBeGreaterThan(0); + expect(result.warnings.some(w => w.type === 'security')).toBe(true); + }); + + test('no warning when @secure() is present', () => { + const content = ` +@secure() +param adminPassword string +param location string +`; + const result = validateBicepContent(content); + + expect(result.warnings.some(w => + w.message.includes('adminPassword') + )).toBe(false); + }); + + test('warns on old API versions', () => { + const content = ` +resource storage 'Microsoft.Storage/storageAccounts@2019-01-01' = { + name: 'mystorage' +} +`; + const result = validateBicepContent(content); + + expect(result.warnings.some(w => + w.type === 'deprecation' + )).toBe(true); + }); + + test('valid content passes', () => { + const content = ` +param location string +param name string + +resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: name + location: location +} +`; + const result = validateBicepContent(content); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + }); + + describe('analyzeBicepFile', () => { + test('returns comprehensive analysis', () => { + const content = ` +targetScope = 'subscription' + +param location string +param rgName string +`; + const projectFiles = ['main.bicep', 'main.bicepparam', 'azure.yaml']; + + const result = analyzeBicepFile('main.bicep', content, projectFiles); + + expect(result.path).toBe('main.bicep'); + expect(result.targetScope).toBe('subscription'); + expect(result.command).toBe('az deployment sub what-if'); + expect(result.requiredParams).toContain('--location'); + expect(result.parameterFiles.found).toBe(true); + expect(result.isAzd).toBe(true); + expect(result.validation.valid).toBe(true); + }); + + test('detects non-azd project', () => { + const content = `param location string`; + const projectFiles = ['main.bicep']; + + const result = analyzeBicepFile('main.bicep', content, projectFiles); + + expect(result.isAzd).toBe(false); + }); + }); + + describe('SCOPE_COMMANDS constant', () => { + test('has all four scopes', () => { + expect(Object.keys(SCOPE_COMMANDS)).toHaveLength(4); + expect(SCOPE_COMMANDS).toHaveProperty('resourceGroup'); + expect(SCOPE_COMMANDS).toHaveProperty('subscription'); + expect(SCOPE_COMMANDS).toHaveProperty('managementGroup'); + expect(SCOPE_COMMANDS).toHaveProperty('tenant'); + }); + }); +}); diff --git a/tests/validation/__tests__/integration.test.js b/tests/validation/__tests__/integration.test.js new file mode 100644 index 000000000..737227a07 --- /dev/null +++ b/tests/validation/__tests__/integration.test.js @@ -0,0 +1,226 @@ +/** + * Integration Tests + * + * End-to-end scenarios combining multiple validators. + */ + +const path = require('path'); +const fs = require('fs'); + +const { validateResourceName, validateMultipleResources } = require('../src/validators/resourceNameValidator'); +const { analyzeBicepFile, findBicepFiles, isAzdProject, findParameterFiles } = require('../src/validators/bicepValidator'); +const { getRequiredTools, generatePreflightReport } = require('../src/validators/preflightValidator'); + +// Helper to get fixture path +const fixturesPath = path.join(__dirname, '..', 'fixtures'); + +describe('Integration Tests', () => { + describe('Complete Validation Workflow', () => { + test('validates azd project with all checks passing', () => { + // Simulate azd project structure + const projectFiles = [ + 'azure.yaml', + 'infra/main.bicep', + 'infra/main.bicepparam', + 'src/api/app.py', + 'src/web/package.json' + ]; + + const resourceNames = { + 'mystorageacct01': 'storageAccount', + 'my-keyvault-dev': 'keyVault', + 'mycontainerapp': 'containerApp' + }; + + // Check project type + expect(isAzdProject(projectFiles)).toBe(true); + + // Validate resource names + const nameResults = validateMultipleResources(resourceNames); + expect(nameResults['mystorageacct01'].valid).toBe(true); + expect(nameResults['my-keyvault-dev'].valid).toBe(true); + expect(nameResults['mycontainerapp'].valid).toBe(true); + + // Get required tools + const tools = getRequiredTools({ isAzd: true, hasBicep: true }); + expect(tools).toContain('az'); + expect(tools).toContain('azd'); + expect(tools).toContain('bicep'); + }); + + test('catches naming violations in multi-resource deployment', () => { + const resourceNames = { + 'mycompanyproductionstores': 'storageAccount', // Too long (25 chars) + 'my-container-registry': 'containerRegistry', // Has hyphens + 'UPPERCASE-APP': 'containerApp' // Uppercase + }; + + const results = validateMultipleResources(resourceNames); + + expect(results['mycompanyproductionstores'].valid).toBe(false); + expect(results['mycompanyproductionstores'].errors[0]).toContain('24 characters'); + + expect(results['my-container-registry'].valid).toBe(false); + expect(results['my-container-registry'].errors[0]).toContain('alphanumerics only'); + + expect(results['UPPERCASE-APP'].valid).toBe(false); + }); + + test('generates complete preflight report', () => { + // Simulate a deployment with issues + const preflightResults = { + tools: [ + { tool: 'az', installed: true, version: '2.76.0', meetsMinVersion: true, warning: null }, + { tool: 'azd', installed: true, version: '1.5.0', meetsMinVersion: true, warning: null }, + { tool: 'bicep', installed: false, version: null } + ], + auth: { + az: { loggedIn: true }, + azd: { loggedIn: false } + }, + resourceGroup: { name: 'my-rg', exists: false } + }; + + const report = generatePreflightReport(preflightResults); + + expect(report.status).toBe('failed'); + expect(report.summary.totalIssues).toBe(3); // bicep missing, azd not logged in, rg missing + expect(report.issues.some(i => i.category === 'tools')).toBe(true); + expect(report.issues.some(i => i.category === 'auth')).toBe(true); + expect(report.issues.some(i => i.category === 'resources')).toBe(true); + }); + }); + + describe('Bicep File Analysis', () => { + test('analyzes subscription-scoped Bicep file', () => { + const content = fs.readFileSync( + path.join(fixturesPath, 'bicep', 'valid-subscription.bicep'), + 'utf-8' + ); + + const projectFiles = ['valid-subscription.bicep']; + const analysis = analyzeBicepFile('valid-subscription.bicep', content, projectFiles); + + expect(analysis.targetScope).toBe('subscription'); + expect(analysis.command).toBe('az deployment sub what-if'); + expect(analysis.requiredParams).toContain('--location'); + expect(analysis.isAzd).toBe(false); + }); + + test('analyzes resourceGroup-scoped Bicep file', () => { + const content = fs.readFileSync( + path.join(fixturesPath, 'bicep', 'valid-resourcegroup.bicep'), + 'utf-8' + ); + + const projectFiles = ['valid-resourcegroup.bicep']; + const analysis = analyzeBicepFile('valid-resourcegroup.bicep', content, projectFiles); + + expect(analysis.targetScope).toBe('resourceGroup'); + expect(analysis.command).toBe('az deployment group what-if'); + expect(analysis.requiredParams).toContain('--resource-group'); + }); + + test('detects parameter files in with-params fixture', () => { + const files = [ + 'with-params/main.bicep', + 'with-params/main.bicepparam' + ]; + + const paramFiles = findParameterFiles('with-params/main.bicep', files); + + expect(paramFiles.found).toBe(true); + expect(paramFiles.bicepparam).toContain('main.bicepparam'); + }); + }); + + describe('Project Type Detection', () => { + test('detects azd project from fixture', () => { + const azdProjectPath = path.join(fixturesPath, 'projects', 'azd-project'); + const files = fs.readdirSync(azdProjectPath); + + expect(isAzdProject(files)).toBe(true); + }); + + test('detects standalone Bicep project', () => { + const standalonePath = path.join(fixturesPath, 'projects', 'standalone-bicep'); + const files = fs.readdirSync(standalonePath); + + expect(isAzdProject(files)).toBe(false); + + const bicepFiles = findBicepFiles(files); + expect(bicepFiles.length).toBeGreaterThan(0); + }); + }); + + describe('Real-World Scenarios', () => { + test('scenario: storage account name too long', () => { + // User tries to create a storage account with name based on project + const projectName = 'my-awesome-production-app'; + const proposedStorageName = projectName.replace(/-/g, '') + 'storage'; + + const result = validateResourceName(proposedStorageName, 'storageAccount'); + + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('24 characters'); + + // The name is 29 characters: myawesomeproductionappstorage + expect(proposedStorageName.length).toBe(29); + }); + + test('scenario: common ACR naming mistake with hyphens', () => { + // Users often try to use hyphens in ACR names + const acrName = 'my-company-acr'; + + const result = validateResourceName(acrName, 'containerRegistry'); + + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('alphanumerics only'); + }); + + test('scenario: Key Vault name starting with number', () => { + const kvName = '123-keyvault'; + + const result = validateResourceName(kvName, 'keyVault'); + + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('must start with letter'); + }); + + test('scenario: valid multi-region deployment names', () => { + const resources = { + 'prodstoreus': 'storageAccount', + 'prodstoreu2': 'storageAccount', + 'prod-kv-eastus': 'keyVault', + 'prod-kv-westus': 'keyVault', + 'prodacreastus': 'containerRegistry' + }; + + const results = validateMultipleResources(resources); + + // All should be valid + const allValid = Object.values(results).every(r => r.valid); + expect(allValid).toBe(true); + }); + }); + + describe('Edge Cases', () => { + test('handles empty project file list', () => { + expect(isAzdProject([])).toBe(false); + expect(findBicepFiles([])).toEqual([]); + }); + + test('handles Bicep file with no explicit scope', () => { + const content = ` +param name string +resource app 'Microsoft.Web/sites@2023-01-01' = { + name: name +} +`; + const analysis = analyzeBicepFile('main.bicep', content, ['main.bicep']); + + // Should default to resourceGroup + expect(analysis.targetScope).toBe('resourceGroup'); + }); + }); +}); diff --git a/tests/validation/__tests__/preflightValidator.test.js b/tests/validation/__tests__/preflightValidator.test.js new file mode 100644 index 000000000..f97a6e867 --- /dev/null +++ b/tests/validation/__tests__/preflightValidator.test.js @@ -0,0 +1,391 @@ +/** + * Preflight Validator Tests + * + * Tests for preflight validation before Azure deployments. + * Based on azure-deployment-preflight/SKILL.md. + */ + +const { + REQUIRED_TOOLS, + VERSION_COMMANDS, + MIN_VERSIONS, + parseAzAccountOutput, + parseAzdAuthStatus, + parseVersion, + compareVersions, + meetsMinVersion, + validateTool, + getRequiredTools, + determineValidationFallback, + parseResourceGroupExists, + generatePreflightReport +} = require('../src/validators/preflightValidator'); + +describe('Preflight Validator', () => { + describe('parseAzAccountOutput', () => { + test('parses valid az account show output', () => { + const output = JSON.stringify({ + id: 'sub-123', + name: 'My Subscription', + tenantId: 'tenant-456', + user: { name: 'user@example.com' }, + isDefault: true + }); + + const result = parseAzAccountOutput(output); + + expect(result.loggedIn).toBe(true); + expect(result.subscriptionId).toBe('sub-123'); + expect(result.subscriptionName).toBe('My Subscription'); + expect(result.tenantId).toBe('tenant-456'); + expect(result.user).toBe('user@example.com'); + expect(result.isDefault).toBe(true); + }); + + test('returns null for invalid JSON', () => { + const result = parseAzAccountOutput('not json'); + expect(result).toBeNull(); + }); + + test('handles missing user field', () => { + const output = JSON.stringify({ + id: 'sub-123', + name: 'My Subscription', + tenantId: 'tenant-456' + }); + + const result = parseAzAccountOutput(output); + expect(result.user).toBeNull(); + }); + }); + + describe('parseAzdAuthStatus', () => { + test('detects logged in status', () => { + const result = parseAzdAuthStatus('Logged in to Azure'); + expect(result.loggedIn).toBe(true); + }); + + test('detects authenticated status', () => { + const result = parseAzdAuthStatus('User is authenticated'); + expect(result.loggedIn).toBe(true); + }); + + test('detects not logged in status', () => { + const result = parseAzdAuthStatus('Error: not logged in'); + expect(result.loggedIn).toBe(false); + }); + + test('includes original message', () => { + const result = parseAzdAuthStatus(' Logged in as user@example.com '); + expect(result.message).toBe('Logged in as user@example.com'); + }); + }); + + describe('parseVersion', () => { + test('extracts version from az --version output', () => { + const output = 'azure-cli 2.76.0\ncore 2.76.0'; + expect(parseVersion(output)).toBe('2.76.0'); + }); + + test('extracts version from azd version output', () => { + const output = 'azd version 1.5.0 (commit abc123)'; + expect(parseVersion(output)).toBe('1.5.0'); + }); + + test('extracts version from bicep --version output', () => { + const output = 'Bicep CLI version 0.24.24 (linux-x64)'; + expect(parseVersion(output)).toBe('0.24.24'); + }); + + test('extracts version from docker --version output', () => { + const output = 'Docker version 24.0.7, build afdd53b'; + expect(parseVersion(output)).toBe('24.0.7'); + }); + + test('returns null for invalid output', () => { + expect(parseVersion('no version here')).toBeNull(); + }); + }); + + describe('compareVersions', () => { + test('returns -1 when v1 < v2', () => { + expect(compareVersions('1.0.0', '2.0.0')).toBe(-1); + expect(compareVersions('1.0.0', '1.1.0')).toBe(-1); + expect(compareVersions('1.0.0', '1.0.1')).toBe(-1); + }); + + test('returns 0 when versions are equal', () => { + expect(compareVersions('1.0.0', '1.0.0')).toBe(0); + expect(compareVersions('2.76.0', '2.76.0')).toBe(0); + }); + + test('returns 1 when v1 > v2', () => { + expect(compareVersions('2.0.0', '1.0.0')).toBe(1); + expect(compareVersions('1.1.0', '1.0.0')).toBe(1); + expect(compareVersions('1.0.1', '1.0.0')).toBe(1); + }); + + test('handles missing minor/patch versions', () => { + expect(compareVersions('1.0.0', '1.0.0')).toBe(0); + }); + }); + + describe('meetsMinVersion', () => { + test('returns true when version meets minimum', () => { + expect(meetsMinVersion('2.76.0', '2.76.0')).toBe(true); + expect(meetsMinVersion('2.77.0', '2.76.0')).toBe(true); + expect(meetsMinVersion('3.0.0', '2.76.0')).toBe(true); + }); + + test('returns false when version is below minimum', () => { + expect(meetsMinVersion('2.75.0', '2.76.0')).toBe(false); + expect(meetsMinVersion('2.14.0', '2.76.0')).toBe(false); + expect(meetsMinVersion('1.0.0', '2.76.0')).toBe(false); + }); + }); + + describe('validateTool', () => { + test('validates installed tool meeting minimum version', () => { + const result = validateTool('az', 'azure-cli 2.76.0'); + + expect(result.installed).toBe(true); + expect(result.version).toBe('2.76.0'); + expect(result.meetsMinVersion).toBe(true); + expect(result.warning).toBeNull(); + }); + + test('warns when below minimum version', () => { + const result = validateTool('az', 'azure-cli 2.14.0'); + + expect(result.installed).toBe(true); + expect(result.version).toBe('2.14.0'); + expect(result.meetsMinVersion).toBe(false); + expect(result.warning).toContain('below recommended'); + }); + + test('handles unparseable version', () => { + const result = validateTool('az', 'command not found'); + + expect(result.installed).toBe(false); + expect(result.version).toBeNull(); + expect(result.error).toBe('Could not parse version'); + }); + }); + + describe('getRequiredTools', () => { + test('always includes az', () => { + const tools = getRequiredTools({}); + expect(tools).toContain('az'); + }); + + test('includes azd for azd projects', () => { + const tools = getRequiredTools({ isAzd: true }); + expect(tools).toContain('az'); + expect(tools).toContain('azd'); + }); + + test('includes bicep for Bicep files', () => { + const tools = getRequiredTools({ hasBicep: true }); + expect(tools).toContain('az'); + expect(tools).toContain('bicep'); + }); + + test('includes docker for Dockerfiles', () => { + const tools = getRequiredTools({ hasDocker: true }); + expect(tools).toContain('az'); + expect(tools).toContain('docker'); + }); + + test('includes all tools when all options set', () => { + const tools = getRequiredTools({ isAzd: true, hasBicep: true, hasDocker: true }); + expect(tools).toContain('az'); + expect(tools).toContain('azd'); + expect(tools).toContain('bicep'); + expect(tools).toContain('docker'); + }); + }); + + describe('determineValidationFallback', () => { + test('recommends ProviderNoRbac for authorization errors', () => { + const result = determineValidationFallback('Authorization failed: insufficient permissions'); + + expect(result.shouldFallback).toBe(true); + expect(result.fallbackLevel).toBe('ProviderNoRbac'); + }); + + test('recommends ProviderNoRbac for RBAC errors', () => { + const result = determineValidationFallback('RBAC check failed'); + expect(result.shouldFallback).toBe(true); + }); + + test('recommends ProviderNoRbac for forbidden errors', () => { + const result = determineValidationFallback('Error: Forbidden (403)'); + expect(result.shouldFallback).toBe(true); + }); + + test('recommends register for provider not registered', () => { + const result = determineValidationFallback('Resource provider not registered'); + + expect(result.shouldFallback).toBe(false); + expect(result.action).toBe('register'); + }); + + test('recommends fix for template errors', () => { + const result = determineValidationFallback('Template validation failed: invalid syntax'); + + expect(result.shouldFallback).toBe(false); + expect(result.action).toBe('fix'); + }); + + test('returns no fallback for unknown errors', () => { + const result = determineValidationFallback('Some random error'); + expect(result.shouldFallback).toBe(false); + }); + }); + + describe('parseResourceGroupExists', () => { + test('returns true for "true" output', () => { + expect(parseResourceGroupExists('true')).toBe(true); + expect(parseResourceGroupExists(' true ')).toBe(true); + expect(parseResourceGroupExists('True')).toBe(true); + expect(parseResourceGroupExists('TRUE')).toBe(true); + }); + + test('returns false for "false" output', () => { + expect(parseResourceGroupExists('false')).toBe(false); + expect(parseResourceGroupExists(' false ')).toBe(false); + }); + + test('returns false for unexpected output', () => { + expect(parseResourceGroupExists('error')).toBe(false); + expect(parseResourceGroupExists('')).toBe(false); + }); + }); + + describe('generatePreflightReport', () => { + test('generates passing report when all checks pass', () => { + const results = { + tools: [ + { tool: 'az', installed: true, version: '2.76.0', meetsMinVersion: true, warning: null } + ], + auth: { + az: { loggedIn: true } + } + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('passed'); + expect(report.summary.totalIssues).toBe(0); + }); + + test('generates failing report for missing tool', () => { + const results = { + tools: [ + { tool: 'az', installed: false, version: null } + ], + auth: {} + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('failed'); + expect(report.issues).toHaveLength(1); + expect(report.issues[0].category).toBe('tools'); + expect(report.issues[0].remediation).toContain('Install'); + }); + + test('generates failing report for auth issues', () => { + const results = { + tools: [], + auth: { + az: { loggedIn: false } + } + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('failed'); + expect(report.issues).toHaveLength(1); + expect(report.issues[0].category).toBe('auth'); + expect(report.issues[0].remediation).toContain('az login'); + }); + + test('generates failing report for missing resource group', () => { + const results = { + tools: [], + auth: {}, + resourceGroup: { name: 'my-rg', exists: false } + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('failed'); + expect(report.issues).toHaveLength(1); + expect(report.issues[0].category).toBe('resources'); + expect(report.issues[0].remediation).toContain('az group create'); + }); + + test('includes warnings for outdated tools', () => { + const results = { + tools: [ + { tool: 'az', installed: true, version: '2.14.0', meetsMinVersion: false, minVersion: '2.76.0', warning: 'Version 2.14.0 is below recommended 2.76.0' } + ], + auth: {} + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('passed'); // Warnings don't fail + expect(report.warnings).toHaveLength(1); + expect(report.warnings[0].category).toBe('tools'); + }); + + test('includes timestamp', () => { + const report = generatePreflightReport({}); + expect(report.timestamp).toBeDefined(); + expect(new Date(report.timestamp)).toBeInstanceOf(Date); + }); + + test('includes bicep errors in report', () => { + const results = { + tools: [], + auth: {}, + bicep: { + errors: [ + { file: 'main.bicep', line: 10, column: 5, severity: 'error', message: 'Syntax error' } + ] + } + }; + + const report = generatePreflightReport(results); + + expect(report.status).toBe('failed'); + expect(report.issues).toHaveLength(1); + expect(report.issues[0].category).toBe('bicep'); + }); + }); + + describe('Constants', () => { + test('REQUIRED_TOOLS has expected tool sets', () => { + expect(REQUIRED_TOOLS.base).toEqual(['az']); + expect(REQUIRED_TOOLS.azd).toContain('azd'); + expect(REQUIRED_TOOLS.bicep).toContain('bicep'); + expect(REQUIRED_TOOLS.container).toContain('docker'); + }); + + test('VERSION_COMMANDS has commands for all tools', () => { + expect(VERSION_COMMANDS).toHaveProperty('az'); + expect(VERSION_COMMANDS).toHaveProperty('azd'); + expect(VERSION_COMMANDS).toHaveProperty('bicep'); + expect(VERSION_COMMANDS).toHaveProperty('docker'); + }); + + test('MIN_VERSIONS has versions for all tools', () => { + expect(MIN_VERSIONS.az).toBe('2.76.0'); + expect(MIN_VERSIONS.azd).toBe('1.0.0'); + expect(MIN_VERSIONS.bicep).toBe('0.4.0'); + expect(MIN_VERSIONS.docker).toBe('20.0.0'); + }); + }); +}); diff --git a/tests/validation/__tests__/resourceNameValidator.test.js b/tests/validation/__tests__/resourceNameValidator.test.js new file mode 100644 index 000000000..1d14dc1b3 --- /dev/null +++ b/tests/validation/__tests__/resourceNameValidator.test.js @@ -0,0 +1,452 @@ +/** + * Azure Resource Name Validator Tests + * + * Tests for validating Azure resource names against naming constraints. + * Based on azure-validation/SKILL.md naming rules. + */ + +const { + validateResourceName, + validateMultipleResources, + suggestShortenedName, + getResourceConstraints, + listResourceTypes, + RESOURCE_CONSTRAINTS +} = require('../src/validators/resourceNameValidator'); + +describe('Resource Name Validator', () => { + describe('Storage Account', () => { + const resourceType = 'storageAccount'; + + describe('valid names', () => { + test('accepts lowercase letters only', () => { + const result = validateResourceName('mystorageaccount', resourceType); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + test('accepts numbers only', () => { + const result = validateResourceName('123456789', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts mix of lowercase and numbers', () => { + const result = validateResourceName('stor123account', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts minimum length (3 chars)', () => { + const result = validateResourceName('abc', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts maximum length (24 chars)', () => { + const result = validateResourceName('a'.repeat(24), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names - length', () => { + test('rejects too short (2 chars)', () => { + const result = validateResourceName('ab', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('at least 3 characters'); + }); + + test('rejects too long (25 chars)', () => { + const result = validateResourceName('a'.repeat(25), resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('at most 24 characters'); + }); + + test('rejects common mistake: mycompanyproductionstores (25 chars)', () => { + const result = validateResourceName('mycompanyproductionstores', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('at most 24 characters'); + }); + }); + + describe('invalid names - characters', () => { + test('rejects uppercase letters', () => { + const result = validateResourceName('MyStorageAccount', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('lowercase letters and numbers only'); + }); + + test('rejects hyphens', () => { + const result = validateResourceName('my-storage-account', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('lowercase letters and numbers only'); + }); + + test('rejects underscores', () => { + const result = validateResourceName('my_storage_account', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects special characters', () => { + const result = validateResourceName('storage@account!', resourceType); + expect(result.valid).toBe(false); + }); + }); + + describe('warnings', () => { + test('warns about global uniqueness for valid names', () => { + const result = validateResourceName('mystorageaccount', resourceType); + expect(result.warnings).toHaveLength(1); + expect(result.warnings[0]).toContain('globally unique'); + }); + }); + }); + + describe('Key Vault', () => { + const resourceType = 'keyVault'; + + describe('valid names', () => { + test('accepts alphanumerics with hyphens', () => { + const result = validateResourceName('my-key-vault-01', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts uppercase letters', () => { + const result = validateResourceName('MyKeyVault', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts minimum length (3 chars)', () => { + const result = validateResourceName('kvt', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (24 chars)', () => { + const result = validateResourceName('a'.repeat(24), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects too long (25 chars)', () => { + const result = validateResourceName('a'.repeat(25), resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('at most 24 characters'); + }); + + test('rejects starting with number', () => { + const result = validateResourceName('1keyvault', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects starting with hyphen', () => { + const result = validateResourceName('-keyvault', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects ending with hyphen', () => { + const result = validateResourceName('keyvault-', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects underscores', () => { + const result = validateResourceName('my_key_vault', resourceType); + expect(result.valid).toBe(false); + }); + }); + }); + + describe('Container Registry', () => { + const resourceType = 'containerRegistry'; + + describe('valid names', () => { + test('accepts alphanumerics', () => { + const result = validateResourceName('mycontainerregistry01', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts uppercase', () => { + const result = validateResourceName('MyContainerRegistry', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts min length (5 chars)', () => { + const result = validateResourceName('abcde', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (50 chars)', () => { + const result = validateResourceName('a'.repeat(50), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects hyphens (common mistake)', () => { + const result = validateResourceName('my-container-registry', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('alphanumerics only'); + }); + + test('rejects underscores', () => { + const result = validateResourceName('my_container_registry', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects too short (4 chars)', () => { + const result = validateResourceName('abcd', resourceType); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('at least 5 characters'); + }); + }); + }); + + describe('Container App', () => { + const resourceType = 'containerApp'; + + describe('valid names', () => { + test('accepts lowercase with hyphens', () => { + const result = validateResourceName('my-container-app', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts numbers', () => { + const result = validateResourceName('app123', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (32 chars)', () => { + const result = validateResourceName('a'.repeat(32), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects uppercase', () => { + const result = validateResourceName('MyContainerApp', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects starting with number', () => { + const result = validateResourceName('1containerapp', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects too long (33 chars)', () => { + const result = validateResourceName('a'.repeat(33), resourceType); + expect(result.valid).toBe(false); + }); + }); + + describe('no global uniqueness warning', () => { + test('does not warn about global uniqueness', () => { + const result = validateResourceName('myapp', resourceType); + expect(result.valid).toBe(true); + // Container Apps are not globally unique + expect(result.warnings.some(w => w.includes('globally unique'))).toBe(false); + }); + }); + }); + + describe('App Service', () => { + const resourceType = 'appService'; + + describe('valid names', () => { + test('accepts alphanumerics with hyphens', () => { + const result = validateResourceName('my-web-app-01', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (60 chars)', () => { + const result = validateResourceName('a'.repeat(60), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects starting with hyphen', () => { + const result = validateResourceName('-webapp', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects ending with hyphen', () => { + const result = validateResourceName('webapp-', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects too long (61 chars)', () => { + const result = validateResourceName('a'.repeat(61), resourceType); + expect(result.valid).toBe(false); + }); + }); + }); + + describe('Function App', () => { + const resourceType = 'functionApp'; + + test('accepts valid name', () => { + const result = validateResourceName('my-function-app', resourceType); + expect(result.valid).toBe(true); + }); + + test('has same rules as App Service', () => { + const appServiceConstraints = getResourceConstraints('appService'); + const functionAppConstraints = getResourceConstraints('functionApp'); + + expect(functionAppConstraints.minLength).toBe(appServiceConstraints.minLength); + expect(functionAppConstraints.maxLength).toBe(appServiceConstraints.maxLength); + }); + }); + + describe('Resource Group', () => { + const resourceType = 'resourceGroup'; + + describe('valid names', () => { + test('accepts alphanumerics', () => { + const result = validateResourceName('myresourcegroup01', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts hyphens', () => { + const result = validateResourceName('my-resource-group', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts underscores', () => { + const result = validateResourceName('my_resource_group', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts periods', () => { + const result = validateResourceName('my.resource.group', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (90 chars)', () => { + const result = validateResourceName('a'.repeat(90), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects special characters', () => { + const result = validateResourceName('my@resource#group', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects too long (91 chars)', () => { + const result = validateResourceName('a'.repeat(91), resourceType); + expect(result.valid).toBe(false); + }); + }); + }); + + describe('Cosmos DB', () => { + const resourceType = 'cosmosDb'; + + describe('valid names', () => { + test('accepts lowercase with hyphens', () => { + const result = validateResourceName('my-cosmos-db', resourceType); + expect(result.valid).toBe(true); + }); + + test('accepts max length (44 chars)', () => { + const result = validateResourceName('a'.repeat(44), resourceType); + expect(result.valid).toBe(true); + }); + }); + + describe('invalid names', () => { + test('rejects uppercase', () => { + const result = validateResourceName('MyCosmosDB', resourceType); + expect(result.valid).toBe(false); + }); + + test('rejects too long (45 chars)', () => { + const result = validateResourceName('a'.repeat(45), resourceType); + expect(result.valid).toBe(false); + }); + }); + }); + + describe('Edge Cases', () => { + test('returns error for null name', () => { + const result = validateResourceName(null, 'storageAccount'); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('required'); + }); + + test('returns error for undefined name', () => { + const result = validateResourceName(undefined, 'storageAccount'); + expect(result.valid).toBe(false); + }); + + test('returns error for empty string', () => { + const result = validateResourceName('', 'storageAccount'); + expect(result.valid).toBe(false); + }); + + test('returns error for unknown resource type', () => { + const result = validateResourceName('validname', 'unknownType'); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('Unknown resource type'); + }); + }); + + describe('validateMultipleResources', () => { + test('validates multiple resources at once', () => { + const results = validateMultipleResources({ + 'mystorageaccount': 'storageAccount', + 'my-key-vault': 'keyVault', + 'INVALID_STORAGE': 'storageAccount' + }); + + expect(results['mystorageaccount'].valid).toBe(true); + expect(results['my-key-vault'].valid).toBe(true); + expect(results['INVALID_STORAGE'].valid).toBe(false); + }); + }); + + describe('suggestShortenedName', () => { + test('shortens production to prod', () => { + const result = suggestShortenedName('myproductionstorage', 'storageAccount'); + expect(result).toContain('prod'); + expect(result).not.toContain('production'); + }); + + test('removes hyphens for storage accounts', () => { + const result = suggestShortenedName('my-storage-account', 'storageAccount'); + expect(result).not.toContain('-'); + }); + + test('truncates to max length', () => { + const result = suggestShortenedName('verylongstorageaccountnamethatiswaytolong', 'storageAccount'); + expect(result.length).toBeLessThanOrEqual(24); + }); + }); + + describe('getResourceConstraints', () => { + test('returns constraints for valid type', () => { + const constraints = getResourceConstraints('storageAccount'); + expect(constraints).not.toBeNull(); + expect(constraints.maxLength).toBe(24); + }); + + test('returns null for invalid type', () => { + const constraints = getResourceConstraints('invalidType'); + expect(constraints).toBeNull(); + }); + }); + + describe('listResourceTypes', () => { + test('returns all supported resource types', () => { + const types = listResourceTypes(); + expect(types).toContain('storageAccount'); + expect(types).toContain('keyVault'); + expect(types).toContain('containerRegistry'); + expect(types).toContain('containerApp'); + expect(types).toContain('appService'); + expect(types).toContain('functionApp'); + expect(types).toContain('resourceGroup'); + expect(types).toContain('cosmosDb'); + expect(types.length).toBe(8); + }); + }); +}); diff --git a/tests/validation/fixtures/bicep/invalid-syntax.bicep b/tests/validation/fixtures/bicep/invalid-syntax.bicep new file mode 100644 index 000000000..54298249a --- /dev/null +++ b/tests/validation/fixtures/bicep/invalid-syntax.bicep @@ -0,0 +1,13 @@ +// This file has intentional syntax errors for testing + +param location string = 'eastus' +param name string + +// Missing closing brace +resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: name + location: location + // Unclosed interpolation + tags: { + env: '${location' + } diff --git a/tests/validation/fixtures/bicep/valid-resourcegroup.bicep b/tests/validation/fixtures/bicep/valid-resourcegroup.bicep new file mode 100644 index 000000000..edc17f005 --- /dev/null +++ b/tests/validation/fixtures/bicep/valid-resourcegroup.bicep @@ -0,0 +1,15 @@ +targetScope = 'resourceGroup' + +param location string = 'eastus' +param storageAccountName string + +resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' +} + +output storageId string = storage.id diff --git a/tests/validation/fixtures/bicep/valid-subscription.bicep b/tests/validation/fixtures/bicep/valid-subscription.bicep new file mode 100644 index 000000000..108cf9bf4 --- /dev/null +++ b/tests/validation/fixtures/bicep/valid-subscription.bicep @@ -0,0 +1,11 @@ +targetScope = 'subscription' + +param location string = 'eastus' +param rgName string + +resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: rgName + location: location +} + +output resourceGroupId string = rg.id diff --git a/tests/validation/fixtures/bicep/with-params/main.bicep b/tests/validation/fixtures/bicep/with-params/main.bicep new file mode 100644 index 000000000..14e44849c --- /dev/null +++ b/tests/validation/fixtures/bicep/with-params/main.bicep @@ -0,0 +1,14 @@ +param location string = 'eastus' +param storageAccountName string + +@secure() +param adminPassword string + +resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' +} diff --git a/tests/validation/fixtures/bicep/with-params/main.bicepparam b/tests/validation/fixtures/bicep/with-params/main.bicepparam new file mode 100644 index 000000000..b30e12901 --- /dev/null +++ b/tests/validation/fixtures/bicep/with-params/main.bicepparam @@ -0,0 +1,5 @@ +using './main.bicep' + +param location = 'eastus' +param storageAccountName = 'mystorageaccount01' +param adminPassword = 'placeholder-replaced-at-deploy' diff --git a/tests/validation/fixtures/projects/azd-project/azure.yaml b/tests/validation/fixtures/projects/azd-project/azure.yaml new file mode 100644 index 000000000..0b111d61b --- /dev/null +++ b/tests/validation/fixtures/projects/azd-project/azure.yaml @@ -0,0 +1,13 @@ +name: my-azd-app +metadata: + template: azd-init + +services: + api: + project: ./src/api + language: python + host: containerapp + web: + project: ./src/web + language: js + host: staticwebapp diff --git a/tests/validation/fixtures/projects/standalone-bicep/main.bicep b/tests/validation/fixtures/projects/standalone-bicep/main.bicep new file mode 100644 index 000000000..da1f1e966 --- /dev/null +++ b/tests/validation/fixtures/projects/standalone-bicep/main.bicep @@ -0,0 +1,10 @@ +param location string = 'eastus' +param appName string + +resource app 'Microsoft.Web/sites@2023-01-01' = { + name: appName + location: location + properties: { + serverFarmId: 'placeholder' + } +} diff --git a/tests/validation/jest.config.js b/tests/validation/jest.config.js new file mode 100644 index 000000000..9262be3fa --- /dev/null +++ b/tests/validation/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.js'], + collectCoverageFrom: [ + 'src/**/*.js', + '!src/**/*.test.js' + ], + coverageDirectory: 'coverage', + verbose: true +}; diff --git a/tests/validation/package-lock.json b/tests/validation/package-lock.json new file mode 100644 index 000000000..4b8b7c18d --- /dev/null +++ b/tests/validation/package-lock.json @@ -0,0 +1,3649 @@ +{ + "name": "azure-validation-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-validation-tests", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "jest": "^29.7.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/validation/package.json b/tests/validation/package.json new file mode 100644 index 000000000..173b54867 --- /dev/null +++ b/tests/validation/package.json @@ -0,0 +1,16 @@ +{ + "name": "azure-validation-tests", + "version": "1.0.0", + "description": "Tests for Azure resource naming validation, Bicep detection, and preflight checks", + "main": "index.js", + "scripts": { + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "keywords": ["azure", "validation", "bicep", "testing"], + "license": "MIT", + "devDependencies": { + "jest": "^29.7.0" + } +} diff --git a/tests/validation/src/validators/bicepValidator.js b/tests/validation/src/validators/bicepValidator.js new file mode 100644 index 000000000..e9c3e7563 --- /dev/null +++ b/tests/validation/src/validators/bicepValidator.js @@ -0,0 +1,235 @@ +/** + * Bicep File Validator + * + * Detects and validates Bicep files, target scopes, and parameter files. + * Based on azure-deployment-preflight/SKILL.md detection logic. + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Target scope to CLI command mapping + */ +const SCOPE_COMMANDS = { + resourceGroup: 'az deployment group what-if', + subscription: 'az deployment sub what-if', + managementGroup: 'az deployment mg what-if', + tenant: 'az deployment tenant what-if' +}; + +/** + * Required parameters for each scope + */ +const SCOPE_REQUIRED_PARAMS = { + resourceGroup: ['--resource-group'], + subscription: ['--location'], + managementGroup: ['--location', '--management-group-id'], + tenant: ['--location'] +}; + +/** + * Detects the target scope from Bicep file content + * @param {string} content - The Bicep file content + * @returns {string} The target scope (default: 'resourceGroup') + */ +function detectTargetScope(content) { + const match = content.match(/targetScope\s*=\s*['"](\w+)['"]/); + if (match) { + return match[1]; + } + return 'resourceGroup'; // Default scope +} + +/** + * Gets the CLI command for a given scope + * @param {string} scope - The target scope + * @returns {string|null} The CLI command or null if unknown scope + */ +function getCommandForScope(scope) { + return SCOPE_COMMANDS[scope] || null; +} + +/** + * Gets required parameters for a given scope + * @param {string} scope - The target scope + * @returns {string[]} Array of required parameter flags + */ +function getRequiredParamsForScope(scope) { + return SCOPE_REQUIRED_PARAMS[scope] || []; +} + +/** + * Finds parameter files for a given Bicep file + * @param {string} bicepFilePath - Path to the Bicep file + * @param {string[]} existingFiles - Array of existing file paths to search + * @returns {Object} Object with found parameter files + */ +function findParameterFiles(bicepFilePath, existingFiles) { + const baseName = path.basename(bicepFilePath, '.bicep'); + const dirPath = path.dirname(bicepFilePath); + + const result = { + bicepparam: null, + json: null, + found: false + }; + + // Look for .bicepparam file (preferred) + const bicepparamFile = `${baseName}.bicepparam`; + const bicepparamPath = path.join(dirPath, bicepparamFile); + if (existingFiles.includes(bicepparamPath) || existingFiles.includes(bicepparamFile)) { + result.bicepparam = bicepparamPath; + result.found = true; + } + + // Look for .parameters.json file + const jsonParamFile = `${baseName}.parameters.json`; + const jsonParamPath = path.join(dirPath, jsonParamFile); + if (existingFiles.includes(jsonParamPath) || existingFiles.includes(jsonParamFile)) { + result.json = jsonParamPath; + result.found = true; + } + + // Look for generic parameters.json + const genericParamPath = path.join(dirPath, 'parameters.json'); + if (!result.json && (existingFiles.includes(genericParamPath) || existingFiles.includes('parameters.json'))) { + result.json = genericParamPath; + result.found = true; + } + + return result; +} + +/** + * Detects if a project is an azd project + * @param {string[]} files - Array of file paths in the project + * @returns {boolean} True if azure.yaml is found + */ +function isAzdProject(files) { + return files.some(f => + f === 'azure.yaml' || + f.endsWith('/azure.yaml') || + f.endsWith('\\azure.yaml') + ); +} + +/** + * Finds all Bicep files in a list of files + * @param {string[]} files - Array of file paths + * @returns {string[]} Array of Bicep file paths + */ +function findBicepFiles(files) { + return files.filter(f => f.endsWith('.bicep')); +} + +/** + * Parses Bicep build error output + * @param {string} output - The error output from bicep build + * @returns {Object[]} Array of parsed errors + */ +function parseBicepErrors(output) { + const errors = []; + const errorPattern = /([^(]+)\((\d+),(\d+)\)\s*:\s*(Error|Warning)\s+(\w+):\s*(.+)/g; + + let match; + while ((match = errorPattern.exec(output)) !== null) { + errors.push({ + file: match[1].trim(), + line: parseInt(match[2], 10), + column: parseInt(match[3], 10), + severity: match[4].toLowerCase(), + code: match[5], + message: match[6].trim() + }); + } + + return errors; +} + +/** + * Validates Bicep file content for common issues + * @param {string} content - The Bicep file content + * @returns {Object} Validation result with warnings + */ +function validateBicepContent(content) { + const warnings = []; + const errors = []; + + // Check for hardcoded secrets (simple heuristic) + if (/password\s*=\s*['"][^'"]+['"]/i.test(content)) { + warnings.push({ + type: 'security', + message: 'Possible hardcoded password detected. Use Key Vault references or secure parameters.' + }); + } + + // Check for missing secure decorator on sensitive parameters + const paramMatches = content.matchAll(/param\s+(\w*(?:password|secret|key|token)\w*)\s+string/gi); + for (const match of paramMatches) { + const paramName = match[1]; + // Check if @secure() decorator is present before this param + const beforeParam = content.substring(0, match.index); + const lastDecorator = beforeParam.lastIndexOf('@secure()'); + const lastParam = beforeParam.lastIndexOf('param '); + + // Warn if no @secure() before this param, or if there's another param between @secure and this one + if (lastDecorator === -1 || lastDecorator < lastParam) { + warnings.push({ + type: 'security', + message: `Parameter '${paramName}' may contain sensitive data. Consider adding @secure() decorator.` + }); + } + } + + // Check for deprecated API versions (example) + if (/2019-01-01|2018-\d{2}-\d{2}/g.test(content)) { + warnings.push({ + type: 'deprecation', + message: 'Older API versions detected. Consider updating to newer API versions.' + }); + } + + return { + valid: errors.length === 0, + errors, + warnings + }; +} + +/** + * Analyzes a Bicep file and returns comprehensive information + * @param {string} filePath - Path to the Bicep file + * @param {string} content - The Bicep file content + * @param {string[]} projectFiles - All files in the project + * @returns {Object} Analysis result + */ +function analyzeBicepFile(filePath, content, projectFiles) { + const scope = detectTargetScope(content); + const paramFiles = findParameterFiles(filePath, projectFiles); + const validation = validateBicepContent(content); + + return { + path: filePath, + targetScope: scope, + command: getCommandForScope(scope), + requiredParams: getRequiredParamsForScope(scope), + parameterFiles: paramFiles, + validation, + isAzd: isAzdProject(projectFiles) + }; +} + +module.exports = { + SCOPE_COMMANDS, + SCOPE_REQUIRED_PARAMS, + detectTargetScope, + getCommandForScope, + getRequiredParamsForScope, + findParameterFiles, + isAzdProject, + findBicepFiles, + parseBicepErrors, + validateBicepContent, + analyzeBicepFile +}; diff --git a/tests/validation/src/validators/preflightValidator.js b/tests/validation/src/validators/preflightValidator.js new file mode 100644 index 000000000..6aec9a6a5 --- /dev/null +++ b/tests/validation/src/validators/preflightValidator.js @@ -0,0 +1,331 @@ +/** + * Preflight Validator + * + * Validates preflight requirements before Azure deployments. + * Based on azure-deployment-preflight/SKILL.md. + */ + +/** + * Required tools for different deployment scenarios + */ +const REQUIRED_TOOLS = { + base: ['az'], + azd: ['az', 'azd'], + bicep: ['az', 'bicep'], + container: ['az', 'docker'], + full: ['az', 'azd', 'bicep', 'docker'] +}; + +/** + * Tool version commands + */ +const VERSION_COMMANDS = { + az: 'az --version', + azd: 'azd version', + bicep: 'bicep --version', + docker: 'docker --version' +}; + +/** + * Minimum recommended versions + */ +const MIN_VERSIONS = { + az: '2.76.0', // For --validation-level support + azd: '1.0.0', + bicep: '0.4.0', + docker: '20.0.0' +}; + +/** + * Parses az account show output + * @param {string} output - JSON output from az account show + * @returns {Object|null} Parsed account info or null if invalid + */ +function parseAzAccountOutput(output) { + try { + const account = JSON.parse(output); + return { + loggedIn: true, + subscriptionId: account.id, + subscriptionName: account.name, + tenantId: account.tenantId, + user: account.user?.name || null, + isDefault: account.isDefault + }; + } catch { + return null; + } +} + +/** + * Parses azd auth login --check-status output + * @param {string} output - Output from azd auth login --check-status + * @returns {Object} Auth status + */ +function parseAzdAuthStatus(output) { + const loggedIn = output.includes('Logged in') || + output.includes('authenticated') || + !output.includes('not logged in'); + + return { + loggedIn, + message: output.trim() + }; +} + +/** + * Parses version string to comparable format + * @param {string} versionOutput - Raw version output + * @returns {string|null} Normalized version string or null + */ +function parseVersion(versionOutput) { + const match = versionOutput.match(/(\d+\.\d+\.\d+)/); + return match ? match[1] : null; +} + +/** + * Compares two semantic versions + * @param {string} v1 - First version + * @param {string} v2 - Second version + * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2 + */ +function compareVersions(v1, v2) { + const parts1 = v1.split('.').map(Number); + const parts2 = v2.split('.').map(Number); + + for (let i = 0; i < 3; i++) { + const p1 = parts1[i] || 0; + const p2 = parts2[i] || 0; + + if (p1 < p2) return -1; + if (p1 > p2) return 1; + } + + return 0; +} + +/** + * Checks if a version meets minimum requirements + * @param {string} version - The version to check + * @param {string} minVersion - The minimum required version + * @returns {boolean} True if version meets requirements + */ +function meetsMinVersion(version, minVersion) { + return compareVersions(version, minVersion) >= 0; +} + +/** + * Validates tool availability + * @param {string} tool - Tool name + * @param {string} versionOutput - Output from version command + * @returns {Object} Validation result + */ +function validateTool(tool, versionOutput) { + const version = parseVersion(versionOutput); + const minVersion = MIN_VERSIONS[tool]; + + if (!version) { + return { + tool, + installed: false, + version: null, + meetsMinVersion: false, + error: 'Could not parse version' + }; + } + + return { + tool, + installed: true, + version, + meetsMinVersion: meetsMinVersion(version, minVersion), + minVersion, + warning: !meetsMinVersion(version, minVersion) + ? `Version ${version} is below recommended ${minVersion}` + : null + }; +} + +/** + * Gets required tools for a deployment scenario + * @param {Object} options - Deployment options + * @param {boolean} options.isAzd - Is this an azd project + * @param {boolean} options.hasBicep - Does the project have Bicep files + * @param {boolean} options.hasDocker - Does the project have Dockerfile + * @returns {string[]} Array of required tool names + */ +function getRequiredTools(options = {}) { + const tools = new Set(['az']); // Always need Azure CLI + + if (options.isAzd) { + tools.add('azd'); + } + + if (options.hasBicep) { + tools.add('bicep'); + } + + if (options.hasDocker) { + tools.add('docker'); + } + + return Array.from(tools); +} + +/** + * Determines validation level fallback strategy + * @param {string} error - Error message from validation + * @returns {Object} Fallback recommendation + */ +function determineValidationFallback(error) { + const lowerError = error.toLowerCase(); + + // Permission/RBAC errors + if (lowerError.includes('authorization') || + lowerError.includes('permission') || + lowerError.includes('forbidden') || + lowerError.includes('rbac')) { + return { + shouldFallback: true, + fallbackLevel: 'ProviderNoRbac', + reason: 'Insufficient permissions for full RBAC validation' + }; + } + + // Resource provider not registered + if (lowerError.includes('not registered') || + lowerError.includes('register the subscription')) { + return { + shouldFallback: false, + action: 'register', + reason: 'Resource provider needs to be registered' + }; + } + + // Template errors - no fallback, fix the template + if (lowerError.includes('template') || + lowerError.includes('syntax') || + lowerError.includes('invalid')) { + return { + shouldFallback: false, + action: 'fix', + reason: 'Template has errors that need to be fixed' + }; + } + + return { + shouldFallback: false, + reason: 'Unknown error type' + }; +} + +/** + * Parses resource group existence check + * @param {string} output - Output from az group exists + * @returns {boolean} True if resource group exists + */ +function parseResourceGroupExists(output) { + return output.trim().toLowerCase() === 'true'; +} + +/** + * Generates preflight report structure + * @param {Object} results - All preflight check results + * @returns {Object} Structured report + */ +function generatePreflightReport(results) { + const { tools = [], auth = {}, bicep = null, resourceGroup = null } = results; + + const issues = []; + const warnings = []; + + // Check tools + for (const tool of tools) { + if (!tool.installed) { + issues.push({ + severity: 'error', + category: 'tools', + message: `${tool.tool} is not installed`, + remediation: `Install ${tool.tool} using the appropriate package manager` + }); + } else if (tool.warning) { + warnings.push({ + severity: 'warning', + category: 'tools', + message: tool.warning, + remediation: `Update ${tool.tool} to version ${tool.minVersion} or later` + }); + } + } + + // Check auth + if (auth.az && !auth.az.loggedIn) { + issues.push({ + severity: 'error', + category: 'auth', + message: 'Not logged in to Azure CLI', + remediation: 'Run: az login' + }); + } + + if (auth.azd && !auth.azd.loggedIn) { + issues.push({ + severity: 'error', + category: 'auth', + message: 'Not logged in to Azure Developer CLI', + remediation: 'Run: azd auth login' + }); + } + + // Check resource group + if (resourceGroup && !resourceGroup.exists) { + issues.push({ + severity: 'error', + category: 'resources', + message: `Resource group '${resourceGroup.name}' does not exist`, + remediation: `Run: az group create --name ${resourceGroup.name} --location ` + }); + } + + // Check bicep validation + if (bicep && bicep.errors && bicep.errors.length > 0) { + for (const error of bicep.errors) { + issues.push({ + severity: error.severity, + category: 'bicep', + message: `${error.file}(${error.line},${error.column}): ${error.message}`, + remediation: 'Fix the Bicep template error' + }); + } + } + + return { + status: issues.length === 0 ? 'passed' : 'failed', + timestamp: new Date().toISOString(), + summary: { + totalIssues: issues.length, + totalWarnings: warnings.length, + toolsChecked: tools.length, + authChecked: Object.keys(auth).length + }, + issues, + warnings, + details: results + }; +} + +module.exports = { + REQUIRED_TOOLS, + VERSION_COMMANDS, + MIN_VERSIONS, + parseAzAccountOutput, + parseAzdAuthStatus, + parseVersion, + compareVersions, + meetsMinVersion, + validateTool, + getRequiredTools, + determineValidationFallback, + parseResourceGroupExists, + generatePreflightReport +}; diff --git a/tests/validation/src/validators/resourceNameValidator.js b/tests/validation/src/validators/resourceNameValidator.js new file mode 100644 index 000000000..803ca0e57 --- /dev/null +++ b/tests/validation/src/validators/resourceNameValidator.js @@ -0,0 +1,236 @@ +/** + * Azure Resource Name Validator + * + * Validates Azure resource names against naming constraints. + * Based on azure-validation/SKILL.md naming rules. + */ + +/** + * Resource naming constraints from Azure + * @see https://learn.microsoft.com/azure/azure-resource-manager/management/resource-name-rules + */ +const RESOURCE_CONSTRAINTS = { + storageAccount: { + name: 'Storage Account', + minLength: 3, + maxLength: 24, + pattern: /^[a-z0-9]+$/, + patternDescription: 'lowercase letters and numbers only', + globallyUnique: true + }, + keyVault: { + name: 'Key Vault', + minLength: 3, + maxLength: 24, + pattern: /^[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z]$/, + patternDescription: 'alphanumerics and hyphens, must start with letter', + globallyUnique: true + }, + containerRegistry: { + name: 'Container Registry', + minLength: 5, + maxLength: 50, + pattern: /^[a-zA-Z0-9]+$/, + patternDescription: 'alphanumerics only (no hyphens)', + globallyUnique: true + }, + containerApp: { + name: 'Container App', + minLength: 2, + maxLength: 32, + pattern: /^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/, + patternDescription: 'lowercase letters, numbers, and hyphens', + globallyUnique: false + }, + appService: { + name: 'App Service', + minLength: 2, + maxLength: 60, + pattern: /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/, + patternDescription: 'alphanumerics and hyphens', + globallyUnique: true + }, + functionApp: { + name: 'Function App', + minLength: 2, + maxLength: 60, + pattern: /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/, + patternDescription: 'alphanumerics and hyphens', + globallyUnique: true + }, + resourceGroup: { + name: 'Resource Group', + minLength: 1, + maxLength: 90, + pattern: /^[a-zA-Z0-9._-]+$/, + patternDescription: 'alphanumerics, hyphens, underscores, and periods', + globallyUnique: false + }, + cosmosDb: { + name: 'Cosmos DB Account', + minLength: 3, + maxLength: 44, + pattern: /^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/, + patternDescription: 'lowercase letters, numbers, and hyphens', + globallyUnique: true + } +}; + +/** + * Validation result + * @typedef {Object} ValidationResult + * @property {boolean} valid - Whether the name is valid + * @property {string[]} errors - List of validation errors + * @property {string[]} warnings - List of warnings + */ + +/** + * Validates a resource name against its constraints + * @param {string} name - The resource name to validate + * @param {string} resourceType - The type of resource (e.g., 'storageAccount') + * @returns {ValidationResult} + */ +function validateResourceName(name, resourceType) { + const errors = []; + const warnings = []; + + const constraints = RESOURCE_CONSTRAINTS[resourceType]; + if (!constraints) { + return { + valid: false, + errors: [`Unknown resource type: ${resourceType}`], + warnings: [] + }; + } + + // Check if name is provided + if (!name || typeof name !== 'string') { + return { + valid: false, + errors: ['Resource name is required'], + warnings: [] + }; + } + + // Check minimum length + if (name.length < constraints.minLength) { + errors.push( + `${constraints.name} name must be at least ${constraints.minLength} characters (got ${name.length})` + ); + } + + // Check maximum length + if (name.length > constraints.maxLength) { + errors.push( + `${constraints.name} name must be at most ${constraints.maxLength} characters (got ${name.length})` + ); + } + + // Check pattern + if (!constraints.pattern.test(name)) { + errors.push( + `${constraints.name} name must contain only ${constraints.patternDescription}` + ); + } + + // Add warning for globally unique resources + if (constraints.globallyUnique && errors.length === 0) { + warnings.push( + `${constraints.name} names must be globally unique across Azure` + ); + } + + return { + valid: errors.length === 0, + errors, + warnings + }; +} + +/** + * Validates multiple resource names at once + * @param {Object} resources - Object mapping resource names to types + * @returns {Object} Object mapping resource names to ValidationResults + */ +function validateMultipleResources(resources) { + const results = {}; + + for (const [name, resourceType] of Object.entries(resources)) { + results[name] = validateResourceName(name, resourceType); + } + + return results; +} + +/** + * Suggests a shortened name for resources with strict length limits + * @param {string} baseName - The base name to shorten + * @param {string} resourceType - The type of resource + * @returns {string} A shortened name that fits the constraints + */ +function suggestShortenedName(baseName, resourceType) { + const constraints = RESOURCE_CONSTRAINTS[resourceType]; + if (!constraints) { + return baseName; + } + + // Common abbreviations + const abbreviations = { + 'production': 'prod', + 'development': 'dev', + 'staging': 'stg', + 'storage': 'stor', + 'container': 'ctr', + 'registry': 'reg', + 'keyvault': 'kv', + 'application': 'app', + 'service': 'svc', + 'database': 'db', + 'resource': 'rsc' + }; + + let shortened = baseName.toLowerCase(); + + // Apply abbreviations + for (const [full, abbr] of Object.entries(abbreviations)) { + shortened = shortened.replace(new RegExp(full, 'gi'), abbr); + } + + // Remove characters not allowed for storage accounts + if (resourceType === 'storageAccount') { + shortened = shortened.replace(/[^a-z0-9]/g, ''); + } + + // Truncate to max length + if (shortened.length > constraints.maxLength) { + shortened = shortened.substring(0, constraints.maxLength); + } + + return shortened; +} + +/** + * Gets the constraints for a resource type + * @param {string} resourceType - The type of resource + * @returns {Object|null} The constraints object or null if not found + */ +function getResourceConstraints(resourceType) { + return RESOURCE_CONSTRAINTS[resourceType] || null; +} + +/** + * Lists all supported resource types + * @returns {string[]} Array of supported resource type keys + */ +function listResourceTypes() { + return Object.keys(RESOURCE_CONSTRAINTS); +} + +module.exports = { + RESOURCE_CONSTRAINTS, + validateResourceName, + validateMultipleResources, + suggestShortenedName, + getResourceConstraints, + listResourceTypes +};