diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml new file mode 100644 index 0000000..3f60c53 --- /dev/null +++ b/.github/workflows/scan.yml @@ -0,0 +1,130 @@ +name: Security & Linting + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + security-lint: + name: Security & Code Quality Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Verify Node.js version meets requirements + run: | + echo "Node.js version:" + node --version + echo "npm version:" + npm --version + echo "Checking Node.js version compatibility..." + node -e " + const version = process.version; + const major = parseInt(version.slice(1).split('.')[0]); + const required = 16; + console.log(\`Node.js \${version} (major: \${major})\`); + if (major < required) { + console.error(\`❌ Node.js \${major} is below minimum required version \${required}\`); + process.exit(1); + } else { + console.log(\`✅ Node.js \${major} meets minimum requirement \${required}\`); + } + " + + - name: Enable Corepack for Yarn v4 + run: | + echo "Current Node.js version:" + node --version + echo "Enabling Corepack..." + corepack enable + echo "Preparing Yarn 4.9.2..." + corepack prepare yarn@4.9.2 --activate + echo "Yarn setup complete" + + - name: Verify Yarn version + run: | + echo "Yarn version:" + yarn --version + echo "Checking package manager field..." + node -e "console.log('packageManager:', require('./package.json').packageManager)" + + - name: Install dependencies + run: | + echo "Installing dependencies with Yarn v4..." + echo "Current working directory: $(pwd)" + echo "Files in directory:" + ls -la + echo "Installing..." + yarn install --immutable + echo "Dependencies installed successfully" + + - name: Verify installation + run: | + echo "Checking node_modules..." + ls -la node_modules/ | head -10 + echo "Checking TypeScript installation..." + + # Check TypeScript + if yarn tsc --version > /dev/null 2>&1; then + echo "✅ TypeScript: $(yarn tsc --version)" + elif npx tsc --version > /dev/null 2>&1; then + echo "✅ TypeScript: $(npx tsc --version)" + else + echo "⚠️ TypeScript: Not accessible via yarn/npx, checking package.json..." + TS_VERSION=$(node -e "const pkg = require('./package.json'); console.log(pkg.devDependencies?.typescript || '')") + if [ -n "$TS_VERSION" ] && [ "$TS_VERSION" != "undefined" ]; then + echo " ✅ TypeScript is listed in devDependencies: $TS_VERSION" + else + echo " ❌ TypeScript may not be installed" + fi + fi + + echo "✅ Dependency verification completed" + + - name: TypeScript compilation check + run: | + echo "Running TypeScript compilation check..." + yarn tsc --noEmit + echo "✅ TypeScript compilation successful" + + - name: Run ESLint with security rules + run: | + echo "Running standard ESLint checks..." + yarn lint + echo "✅ Standard linting passed" + + - name: Run security-focused ESLint + run: | + echo "Running security-focused ESLint..." + yarn lint:security + echo "✅ Security linting passed" + + - name: Audit dependencies for vulnerabilities + run: | + echo "Running dependency audit..." + if yarn npm audit --severity high; then + echo "✅ Dependency audit passed - no high/critical vulnerabilities found" + fi + + - name: Check for known security issues + run: | + echo "Generating detailed security audit..." + # Use || true here to ensure JSON output is captured even if vulnerabilities are found + # This step is for reporting/analysis, not for failing the build + yarn npm audit --json --severity moderate > audit.json || true + if [ -f audit.json ]; then + vulnerabilities=$(cat audit.json | jq -r '.data.vulnerabilities // {} | length // 0' 2>/dev/null || echo "0") + echo "Found $vulnerabilities vulnerabilities" + echo "✅ Security audit completed" + else + echo "No audit data available" + fi diff --git a/.github/workflows/support.yml b/.github/workflows/support.yml new file mode 100644 index 0000000..6f2e91c --- /dev/null +++ b/.github/workflows/support.yml @@ -0,0 +1,172 @@ +name: Node.js Support Matrix + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + node-support-matrix: + name: Node.js ${{ matrix.node-version }} Compatibility + runs-on: ubuntu-latest + + strategy: + matrix: + # NOTE: Node.js versions are automatically extracted by the compatibility-summary job + # The summary report will dynamically reflect any changes made to this list + node-version: ['18', '20'] + fail-fast: false + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Verify Node.js version meets requirements + run: | + echo "Node.js version:" + node --version + echo "npm version:" + npm --version + echo "Checking Node.js version compatibility..." + node -e " + const version = process.version; + const major = parseInt(version.slice(1).split('.')[0]); + const required = 16; + console.log(\`Node.js \${version} (major: \${major})\`); + if (major < required) { + console.error(\`❌ Node.js \${major} is below minimum required version \${required}\`); + process.exit(1); + } else { + console.log(\`✅ Node.js \${major} meets minimum requirement \${required}\`); + } + " + + - name: Enable Corepack for Yarn v4 + run: | + echo "Current Node.js version:" + node --version + echo "Enabling Corepack..." + corepack enable + echo "Preparing Yarn 4.9.2..." + corepack prepare yarn@4.9.2 --activate + echo "Yarn setup complete" + + - name: Verify Yarn version + run: | + echo "Yarn version:" + yarn --version + echo "Checking package manager field..." + node -e "console.log('packageManager:', require('./package.json').packageManager)" + + - name: Install dependencies + run: | + echo "Installing dependencies with Yarn v4..." + echo "Current working directory: $(pwd)" + echo "Files in directory:" + ls -la + echo "Installing..." + yarn install --immutable + echo "Dependencies installed successfully" + + - name: Verify installation + run: | + echo "Checking node_modules..." + ls -la node_modules/ | head -10 + echo "Checking TypeScript installation..." + yarn tsc --version + + - name: TypeScript compilation check + run: | + echo "Running TypeScript compilation check..." + yarn tsc --noEmit + echo "✅ TypeScript compilation successful" + + - name: Run test suite (no coverage for compatibility test) + run: | + echo "Running test suite for Node.js ${{ matrix.node-version }}..." + yarn test --watchAll=false --verbose=false --coverage=false + echo "✅ Test suite passed" + + - name: Basic linting check + run: | + echo "Running ESLint checks..." + yarn lint + echo "✅ Linting passed" + + - name: Build verification + run: | + echo "Building project..." + yarn build + echo "Checking build output..." + ls -la dist/ + echo "✅ Build successful" + + compatibility-summary: + name: Node.js Compatibility Summary + runs-on: ubuntu-latest + needs: node-support-matrix + if: always() + + steps: + - name: Checkout code (for extracting matrix versions) + uses: actions/checkout@v4 + + - name: Node.js Compatibility Report + run: | + echo "=== Node.js Compatibility Test Results ===" + echo "Matrix job result: ${{ needs.node-support-matrix.result }}" + echo "" + + # Extract Node.js versions from the workflow file dynamically + echo "Extracting tested Node.js versions from workflow..." + NODE_VERSIONS=($(grep "node-version: \[" .github/workflows/support.yml | sed "s/.*\[\(.*\)\].*/\1/" | tr "," "\n" | sed "s/[' ]//g" | sort -n)) + echo "Detected versions: ${NODE_VERSIONS[*]}" + echo "" + + if [[ "${{ needs.node-support-matrix.result }}" == "success" ]]; then + echo "🎉 SUCCESS: All Node.js versions are fully compatible!" + echo "" + + # Dynamically generate compatibility lines + for version in "${NODE_VERSIONS[@]}"; do + echo "✅ Node.js $version - Compatible" + done + + echo "" + echo "The log-engine library works correctly across all supported Node.js versions." + + # Generate minimum version requirement dynamically + min_version=${NODE_VERSIONS[0]} + echo "Users can safely install and use this package with Node.js ${min_version}+ environments." + + elif [[ "${{ needs.node-support-matrix.result }}" == "failure" ]]; then + echo "❌ FAILURE: Some Node.js versions failed compatibility tests" + echo "" + echo "Tested versions:" + for version in "${NODE_VERSIONS[@]}"; do + echo " - Node.js $version" + done + echo "" + echo "Please check the individual job logs above to see which versions failed and why." + echo "Common issues:" + echo "- Yarn/Corepack setup problems" + echo "- TypeScript compilation issues" + echo "- Test failures specific to Node.js version" + echo "- Dependency compatibility problems" + exit 1 + else + echo "⚠️ PARTIAL: Some tests were cancelled or skipped" + echo "Result status: ${{ needs.node-support-matrix.result }}" + echo "" + echo "Tested versions:" + for version in "${NODE_VERSIONS[@]}"; do + echo " - Node.js $version" + done + exit 1 + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fac077f..8a2973b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,41 +1,141 @@ -name: Test +name: Testing & Coverage on: push: - branches: [ main, dev ] + branches: [main, dev] pull_request: - branches: [ main ] + branches: [main, dev] jobs: - test: + test-coverage: + name: Test Suite & Coverage Report runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x, 18.x, 20.x] - + steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Run tests - run: yarn test:ci - - - name: Upload coverage reports to Codecov - if: matrix.node-version == '18.x' - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage/lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Verify Node.js version meets requirements + run: | + echo "Node.js version:" + node --version + echo "npm version:" + npm --version + echo "Checking Node.js version compatibility..." + node -e " + const version = process.version; + const major = parseInt(version.slice(1).split('.')[0]); + const required = 16; + console.log(\`Node.js \${version} (major: \${major})\`); + if (major < required) { + console.error(\`❌ Node.js \${major} is below minimum required version \${required}\`); + process.exit(1); + } else { + console.log(\`✅ Node.js \${major} meets minimum requirement \${required}\`); + } + " + + - name: Enable Corepack for Yarn v4 + run: | + echo "Current Node.js version:" + node --version + echo "Enabling Corepack..." + corepack enable + echo "Preparing Yarn 4.9.2..." + corepack prepare yarn@4.9.2 --activate + echo "Yarn setup complete" + + - name: Verify Yarn version + run: | + echo "Yarn version:" + yarn --version + echo "Checking package manager field..." + node -e "console.log('packageManager:', require('./package.json').packageManager)" + + - name: Install dependencies + run: | + echo "Installing dependencies with Yarn v4..." + echo "Current working directory: $(pwd)" + echo "Files in directory:" + ls -la + echo "Installing..." + yarn install --immutable + echo "Dependencies installed successfully" + + - name: Verify installation + run: | + echo "Checking node_modules..." + ls -la node_modules/ | head -10 + echo "Checking TypeScript and Jest installation..." + + # Check TypeScript + if yarn tsc --version > /dev/null 2>&1; then + echo "✅ TypeScript: $(yarn tsc --version)" + elif npx tsc --version > /dev/null 2>&1; then + echo "✅ TypeScript: $(npx tsc --version)" + else + echo "⚠️ TypeScript: Not accessible via yarn/npx, checking package.json..." + TS_VERSION=$(node -e "const pkg = require('./package.json'); console.log(pkg.devDependencies?.typescript || '')") + if [ -n "$TS_VERSION" ] && [ "$TS_VERSION" != "undefined" ]; then + echo " ✅ TypeScript is listed in devDependencies: $TS_VERSION" + else + echo " ❌ TypeScript may not be installed" + fi + fi + + # Check Jest + if yarn jest --version > /dev/null 2>&1; then + echo "✅ Jest: $(yarn jest --version)" + elif npx jest --version > /dev/null 2>&1; then + echo "✅ Jest: $(npx jest --version)" + else + echo "⚠️ Jest: Not accessible via yarn/npx, checking package.json..." + JEST_VERSION=$(node -e "const pkg = require('./package.json'); console.log(pkg.devDependencies?.jest || '')") + if [ -n "$JEST_VERSION" ] && [ "$JEST_VERSION" != "undefined" ]; then + echo " ✅ Jest is listed in devDependencies: $JEST_VERSION" + else + echo " ❌ Jest may not be installed" + fi + fi + + echo "✅ Dependency verification completed" + + - name: Run test suite with coverage and threshold check + run: | + echo "Running full test suite with coverage..." + echo "Coverage threshold: 80% (configured in jest.config.cjs)" + yarn test --coverage --watchAll=false + echo "✅ Test suite passed with coverage requirements" + + - name: Validate coverage reports exist + run: | + echo "Validating coverage report generation..." + if [ ! -f "./coverage/lcov.info" ]; then + echo "❌ Coverage report not generated" + exit 1 + fi + echo "✅ Coverage reports generated successfully" + echo "Coverage report size:" + ls -lh ./coverage/lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/lcov.info + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true + verbose: true + + - name: Upload coverage reports as artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + path: coverage/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index 5a07258..27314d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,138 +1,194 @@ -# Logs -logs -*.log +# +#----------------------------------------------------------------------------- +# Node.js dependencies and logs +# +#----------------------------------------------------------------------------- +node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# Runtime data -pids +# +#----------------------------------------------------------------------------- +# Build outputs +# +#----------------------------------------------------------------------------- +dist/ +build/ +lib/ +out/ +.next/ +.nuxt/ + +# +#----------------------------------------------------------------------------- +# TypeScript cache and compilation +# +#----------------------------------------------------------------------------- +*.tsbuildinfo +.dccache + +# +#----------------------------------------------------------------------------- +# Coverage and test outputs +# +#----------------------------------------------------------------------------- +coverage/ +*.lcov +.nyc_output/ +test-results/ +playwright-report/ +test-output/ + +# +#----------------------------------------------------------------------------- +# Runtime data and process IDs +# +#----------------------------------------------------------------------------- +pids/ *.pid *.seed *.pid.lock -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - +# +#----------------------------------------------------------------------------- # Dependency directories -node_modules/ +# +#----------------------------------------------------------------------------- jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) web_modules/ -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory +# +#----------------------------------------------------------------------------- +# Optional caches +# +#----------------------------------------------------------------------------- .npm - -# Optional eslint cache .eslintcache - -# Optional stylelint cache .stylelintcache - -# Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ +.cache/ +.parcel-cache/ -# Optional REPL history +# +#----------------------------------------------------------------------------- +# REPL and editor files +# +#----------------------------------------------------------------------------- .node_repl_history - +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# +#----------------------------------------------------------------------------- # Output of 'npm pack' +# +#----------------------------------------------------------------------------- *.tgz -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +# +#----------------------------------------------------------------------------- +# Environment variable files +# +#----------------------------------------------------------------------------- .env +.env.test +.env.local +.env.production .env.development.local .env.test.local .env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache +# +#----------------------------------------------------------------------------- +# Temporary folders and logs +# +#----------------------------------------------------------------------------- +tmp/ +temp/ +logs/ +*.log +*.log.* + +# +#----------------------------------------------------------------------------- +# OS generated files +# +#----------------------------------------------------------------------------- +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# +#----------------------------------------------------------------------------- +# Package manager files +# +#----------------------------------------------------------------------------- +.yarn-integrity +.yarn/cache/ +.yarn/unplugged/ +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* -# Docusaurus cache and generated files -.docusaurus +# +#----------------------------------------------------------------------------- +# Database files (for local testing) +# +#----------------------------------------------------------------------------- +*.db +*.sqlite +*.sqlite3 + +# +#----------------------------------------------------------------------------- +# Security and diagnostic reports +# +#----------------------------------------------------------------------------- +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# Serverless directories +# +#----------------------------------------------------------------------------- +# Serverless and deployment +# +#----------------------------------------------------------------------------- .serverless/ - -# FuseBox cache .fusebox/ - -# DynamoDB Local files .dynamodb/ - -# TernJS port file .tern-port -# Stores VSCode versions used for testing VSCode extensions +# +#----------------------------------------------------------------------------- +# Docusaurus and documentation +# +#----------------------------------------------------------------------------- +.docusaurus/ +.vitepress/dist/ +.vitepress/cache/ + +# +#----------------------------------------------------------------------------- +# VSCode testing +# +#----------------------------------------------------------------------------- .vscode-test -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -examples/ \ No newline at end of file +# +#----------------------------------------------------------------------------- +# Project-specific files +# +#----------------------------------------------------------------------------- +examples/ +context/ \ No newline at end of file diff --git a/.snyk b/.snyk new file mode 100644 index 0000000..e8f6ff3 --- /dev/null +++ b/.snyk @@ -0,0 +1,18 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.25.0 + +# Ignore hardcoded passwords/secrets in test files +# These are legitimate test data for a redaction library, not real credentials +ignore: + SNYK-JS-HARDCODEDPASSWORDS: + - '**/src/__tests__/**/*': + reason: Test data for redaction library - these are mock passwords/secrets used to test redaction functionality + expires: '2025-09-29T00:00:00.000Z' + + SNYK-JS-HARDCODEDSECRET: + - '**/src/__tests__/**/*': + reason: Test data for redaction library - these are mock secrets used to test redaction functionality + expires: '2025-09-29T00:00:00.000Z' + +# Patch any high/critical vulnerabilities if they arise +patch: {} diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1a85ed..3af3c9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,110 +12,210 @@ There are many ways to contribute to this open source project. Any contributions ### 🧬 Development -If you can write code then create a pull request to this repo and I will review your code. Please consider submitting your pull request to the `dev` branch. I will auto reject if you submit your pull request to the `main` branch. +If you can write code then create a pull request to this repo and I will review your code. + +#### Branch Strategy + +**Local Development Workflow:** + +- **Work directly on any feature branch** for development and bug fixes +- **No CI/CD pipeline** - all validation happens locally +- **Use local testing commands** for quick validation before commits + +**Production Workflow:** + +- **Create PR to `main`** for production releases +- **Local validation required** before creating PR +- **All quality checks must pass locally** before requesting review + +> **Important**: Please ensure all tests and linting pass locally before submitting pull requests. This project is configured for local-only development workflows. #### 🔧 Setup To get started with development: 1. **Fork and clone the repository** + ```bash git clone https://github.com/your-username/log-engine.git cd log-engine ``` + 2. **Install dependencies** + ```bash - npm install - # or yarn install ``` -3. **Set up environment variables** - - Copy `.env.example` to `.env` (if available) - - Fill in the required information as described in the README -4. **Start the project in development mode** + +3. **Build the project** + ```bash - npm run dev - # or - yarn dev + yarn build ``` +4. **Run tests to verify setup** + + ```bash + yarn test + ``` + +> **Note**: This project uses Yarn for dependency management. Please use `yarn` for all development tasks to ensure consistency with our package manager configuration. + Please refer to the [README](./README.md) for more detailed setup instructions. ### 🧪 Testing -This section provides comprehensive information about testing the log-engine project and how to contribute tests. +This section provides comprehensive information about testing the log-engine project and our two-tier testing strategy optimized for different development workflows. + +#### Local Development Testing Strategy + +We use a **simplified local testing** approach optimized for developer productivity: + +🚀 **Local Development Testing** + +- **Fast feedback** in ~3-5 seconds with `yarn test` +- **Quick validation** without coverage generation using `yarn test --silent` +- **Optimized for iteration** speed and developer productivity + +🛡️ **Comprehensive Local Testing** + +- **Full coverage** reporting with `yarn test:coverage` +- **Security validation** with `yarn secure` (requires Snyk account) +- **Complete validation** with `yarn validate` before creating PRs #### Quick Start ```bash # Install dependencies -npm install +yarn install + +# 🚀 Fast local testing (recommended for development) +yarn test + +# 🛡️ Full testing with coverage (before creating PRs) +yarn test:coverage -# Run all tests -npm test +# 📊 Coverage only +yarn test:coverage -# Run tests with coverage -npm run test:coverage +# 👀 Watch mode for development +yarn test:watch -# Run tests in watch mode (for development) -npm run test:watch +# 🐛 Debug test issues +yarn test:debug ``` -#### Test Architecture +#### Test Commands Reference + +| Command | Speed | Coverage | Use Case | +|---------|-------|----------|----------| +| `test` | ⚡⚡⚡⚡⚡ | ❌ | Quick validation, daily development | +| `test:coverage` | ⚡⚡⚡ | ✅ | Pre-PR testing, quality assurance | +| `test:debug` | ⚡⚡ | ❌ | Troubleshooting hanging tests | +| `test:watch` | ⚡⚡⚡⚡ | ❌ | Live development, TDD workflow | + +#### Development Workflow -- Tests are located in `src/__tests__/` and organized by component. -- Utilities for mocking and setup are in `test-utils.ts`. -- Each test file focuses on a single responsibility for maintainability and parallel execution. +**For Feature Development:** -#### Writing and Running Tests +1. Work on feature branch from `main` +2. Use `yarn test` for quick validation during development +3. Use `yarn lint` to check code quality +4. Commit changes with descriptive messages +5. Iterate quickly with fast local feedback -- Use the provided test file template and follow the Arrange-Act-Assert pattern. -- Use descriptive test names (e.g., `should log debug messages when level is DEBUG`). -- Mock console output using utilities from `test-utils.ts`. -- Ensure each test is isolated using `beforeEach`/`afterEach`. -- Check coverage with `npm run test:coverage` (targets: Statements ≥90%, Branches ≥85%, Functions ≥90%, Lines ≥90%). +**For Production Release:** -#### Common Testing Patterns +1. Run full validation: `yarn validate` (lint + test + build) +2. Ensure coverage requirements are met with `yarn test:coverage` +3. Run security checks: `yarn secure` (if Snyk is configured) +4. Create PR to `main` for review -- Test both positive and negative cases. -- Verify console method call counts and message content. -- Test configuration changes and environment variable effects. -- See the codebase and below for more examples. +#### Test Architecture + +- **Location**: Tests are in `src/__tests__/` organized by component +- **Utilities**: Async test utilities in `async-test-utils.ts` for reliable file/HTTP testing +- **Isolation**: Each test uses unique directories to prevent conflicts +- **Cleanup**: Automated cleanup with timeout protection +- **CI Optimization**: Different configs for local vs CI environments -#### Continuous Integration +#### Test Output Suppression -- Tests must pass before merging pull requests. -- Coverage is checked in CI/CD workflows. -- Use pre-commit hooks to ensure tests pass locally. +The project uses a clean testing approach that suppresses noisy console output: -#### Troubleshooting +- **Global Setup**: `jest.setup.js` automatically mocks `console.error` during tests to prevent confusing output +- **Production Code**: Error logging works normally in production - suppression only applies to test environments +- **Test Isolation**: Each test maintains proper error logging behavior while keeping console output clean +- **CI/CD**: Clean test output in continuous integration environments without affecting production logging -- If modules are not found, ensure TypeScript is compiled (`npm run build`). -- If console mocks do not work, set them up before using LogEngine in tests. -- Always restore environment variables after environment-based tests. +This approach ensures that test logs remain readable while preserving all error logging functionality in production code. #### Contributing Tests -Before submitting: -1. Run the full test suite: `npm test` -2. Check coverage: `npm run test:coverage` -3. Ensure new features have corresponding tests -4. Follow the established testing patterns -5. Update documentation if needed - -Pull Request Checklist: -- [ ] All tests pass locally -- [ ] New functionality is tested -- [ ] Edge cases are covered -- [ ] Test names are descriptive -- [ ] Coverage requirements are met -- [ ] No test pollution (tests affect each other) +**Local Development:** + +1. Write your tests alongside feature development +2. Run `yarn test` for quick validation (~3-5 seconds) +3. Ensure tests follow established patterns and are isolated +4. Use `yarn test:watch` for live development feedback + +**Pre-Pull Request:** + +1. Run full test suite: `yarn test:coverage` +2. Verify coverage requirements are met (should be 80%+) +3. Test across different scenarios and edge cases +4. Run linting: `yarn lint` +5. Run full validation: `yarn validate` + +**Pull Request Checklist:** + +- [ ] All tests pass with `yarn test` +- [ ] Coverage requirements met with `yarn test:coverage` +- [ ] New functionality has corresponding tests +- [ ] Edge cases and error scenarios are covered +- [ ] Test names are descriptive and follow conventions +- [ ] No test pollution (tests are properly isolated) +- [ ] Tests complete quickly (no arbitrary timeouts) +- [ ] Code quality checks pass with `yarn lint` + +#### Test Best Practices + +**✅ Do:** + +- Use `async/await` for asynchronous operations +- Utilize `waitForFile()`, `waitForFileContent()` from `async-test-utils.ts` +- Create unique test directories to prevent conflicts +- Write descriptive test names +- Test both success and failure scenarios + +**❌ Don't:** + +- Use arbitrary `setTimeout()` calls +- Use `done` callbacks (prefer async/await) +- Create tests that depend on other tests +- Use fixed file paths that might conflict +- Write tests that take longer than necessary + +#### Troubleshooting Tests + +**If tests are hanging:** + +```bash +yarn test:ci:debug # Shows open handles and verbose output +``` + +**If tests are flaky:** + +- Check for shared resources (files, directories) +- Ensure proper cleanup in `afterEach`/`afterAll` +- Use unique identifiers for test artifacts Thank you for contributing to the log-engine test suite! 🧪 ### 📖 Documentation Improvements to documentation are always welcome! This includes: + - README updates - Code comments - Examples and usage guides @@ -126,6 +226,29 @@ Improvements to documentation are always welcome! This includes: For any security bugs or issues, please read the [security policy](./SECURITY.md). For other bugs, please create an issue using the bug report template. +## Local Development Philosophy + +This project is intentionally configured for **local-only development workflows** to keep the setup simple and accessible: + +### 🎯 Benefits of Local-Only Development + +- **🚀 Faster iteration**: No waiting for CI/CD pipelines +- **🛠️ Simpler setup**: No complex CI configurations to maintain +- **💰 Cost-effective**: No CI/CD resource costs +- **🔧 Developer control**: Full control over testing and validation +- **📦 Lightweight**: Focus on code quality, not infrastructure + +### 🏗️ Quality Assurance + +Quality is maintained through: + +- **Comprehensive local testing** with 95%+ coverage +- **Security scanning** via Snyk integration +- **TypeScript strict mode** for type safety +- **ESLint with security rules** for code quality +- **Pre-commit validation** via `yarn validate` +- **Manual review process** for all contributions + --- 💻 with ❤️ by [Waren Gonzaga](https://warengonzaga.com), [WG Technology Labs](https://wgtechlabs.com), and [Him](https://www.youtube.com/watch?v=HHrxS4diLew&t=44s) 🙏 diff --git a/README.md b/README.md index aee2b30..ba72356 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Log Engine 📜🚂 [![made by](https://img.shields.io/badge/made%20by-WG%20Tech%20Labs-0060a0.svg?logo=github&longCache=true&labelColor=181717&style=flat-square)](https://github.com/wgtechlabs) -[![github actions workflow status](https://img.shields.io/github/actions/workflow/status/wgtechlabs/log-engine/test.yml?branch=main&style=flat-square&logo=github&labelColor=181717)](https://github.com/wgtechlabs/log-engine/actions/workflows/test.yml) [![codecov](https://img.shields.io/codecov/c/github/wgtechlabs/log-engine?token=PWRJTBVKQ9&style=flat-square&logo=codecov&labelColor=181717)](https://codecov.io/gh/wgtechlabs/log-engine) [![npm downloads](https://img.shields.io/npm/d18m/%40wgtechlabs%2Flog-engine?style=flat-square&logo=npm&label=installs&labelColor=181717&color=%23CD0000)](https://www.npmjs.com/package/@wgtechlabs/log-engine) [![sponsors](https://img.shields.io/badge/sponsor-%E2%9D%A4-%23db61a2.svg?&logo=github&logoColor=white&labelColor=181717&style=flat-square)](https://github.com/sponsors/wgtechlabs) [![release](https://img.shields.io/github/release/wgtechlabs/log-engine.svg?logo=github&labelColor=181717&color=green&style=flat-square)](https://github.com/wgtechlabs/log-engine/releases) [![star](https://img.shields.io/github/stars/wgtechlabs/log-engine.svg?&logo=github&labelColor=181717&color=yellow&style=flat-square)](https://github.com/wgtechlabs/log-engine/stargazers) [![license](https://img.shields.io/github/license/wgtechlabs/log-engine.svg?&logo=github&labelColor=181717&style=flat-square)](https://github.com/wgtechlabs/log-engine/blob/main/license) +[![codecov](https://img.shields.io/codecov/c/github/wgtechlabs/log-engine?token=PWRJTBVKQ9&style=flat-square&logo=codecov&labelColor=181717)](https://codecov.io/gh/wgtechlabs/log-engine) [![npm downloads](https://img.shields.io/npm/dm/%40wgtechlabs%2Flog-engine?style=flat-square&logo=npm&label=installs&labelColor=181717&color=%23CD0000)](https://www.npmjs.com/package/@wgtechlabs/log-engine) [![sponsors](https://img.shields.io/badge/sponsor-%E2%9D%A4-%23db61a2.svg?&logo=github&logoColor=white&labelColor=181717&style=flat-square)](https://github.com/sponsors/wgtechlabs) [![release](https://img.shields.io/github/release/wgtechlabs/log-engine.svg?logo=github&labelColor=181717&color=green&style=flat-square)](https://github.com/wgtechlabs/log-engine/releases) [![star](https://img.shields.io/github/stars/wgtechlabs/log-engine.svg?&logo=github&labelColor=181717&color=yellow&style=flat-square)](https://github.com/wgtechlabs/log-engine/stargazers) [![license](https://img.shields.io/github/license/wgtechlabs/log-engine.svg?&logo=github&labelColor=181717&style=flat-square)](https://github.com/wgtechlabs/log-engine/blob/main/license) [![banner](https://raw.githubusercontent.com/wgtechlabs/log-engine/main/.github/assets/repo_banner.jpg)](https://github.com/wgtechlabs/log-engine) -WG's Log Engine is the **ultimate logging solution for Node.js developers** - a lightweight, battle-tested utility specifically engineered for Discord bots, Telegram bots, web servers, APIs, and server-side applications. Born from real-world development challenges and proven in production environments like the [Unthread Discord Bot](https://github.com/wgtechlabs/unthread-discord-bot/), Log Engine delivers enterprise-grade logging with zero complexity, beautiful color-coded console output, and **advanced automatic data redaction with comprehensive PII protection**. +WG's Log Engine is the **ultimate logging solution for Node.js developers** - a lightweight, battle-tested utility specifically engineered for Discord bots, Telegram bots, web servers, APIs, and server-side applications. Born from real-world development challenges and proven in production environments like the [Unthread Discord Bot](https://github.com/wgtechlabs/unthread-discord-bot/), Log Engine delivers enterprise-grade logging with zero complexity, beautiful color-coded console output, **revolutionary configurable output routing**, and **advanced automatic data redaction with comprehensive PII protection**. -**The first logging library with built-in advanced PII protection and comprehensive TypeScript support.** Stop wrestling with logging configurations and start building amazing applications safely. Whether you're creating the next viral Discord community bot, building high-performance APIs, developing microservices, or deploying production servers, Log Engine provides intelligent terminal-based logging with vibrant colors, advanced customizable redaction patterns, and automatic sensitive data protection that scales with your application's growth - from your first "Hello World" to handling millions of requests across distributed systems. +**The first logging library with built-in advanced PII protection, configurable output handlers, and comprehensive TypeScript support.** Stop wrestling with logging configurations and start building amazing applications safely. Whether you're creating the next viral Discord community bot, building high-performance APIs, developing microservices, or deploying production servers, Log Engine provides intelligent logging with vibrant colors, flexible output routing to any destination, advanced customizable redaction patterns, and automatic sensitive data protection that scales with your application's growth - from your first "Hello World" to handling millions of requests across distributed systems. ## ❣️ Motivation @@ -17,10 +17,11 @@ Log Engine transforms your development experience from chaotic debugging session ## ✨ Key Features - **🔒 Advanced Data Redaction (Enhanced!)**: Built-in PII protection with **custom regex patterns**, **dynamic field management**, and **environment-based configuration** - the first logging library with comprehensive security-first logging by default. +- **🎯 Configurable Output Handlers (New!)**: Revolutionary output routing system supporting **custom destinations**, **multiple simultaneous outputs**, and **production-ready handlers** - redirect logs to files, HTTP endpoints, GUI applications, testing frameworks, or any custom destination with zero configuration complexity. - **⚡ Custom Redaction Patterns**: Add your own regex patterns for advanced field detection and enterprise-specific data protection requirements. - **🎯 Dynamic Field Management**: Runtime configuration of sensitive fields with case-insensitive matching and partial field name detection. - **🛠️ Developer-Friendly API**: Advanced redaction methods including `testFieldRedaction()`, `withoutRedaction()`, and comprehensive configuration management. -- **📊 Comprehensive TypeScript Support**: Full type definitions with 10+ interfaces covering all functionality for maximum developer experience and IDE support. +- **📊 Comprehensive TypeScript Support**: Full type definitions with 15+ interfaces covering all functionality for maximum developer experience and IDE support. - **🚀 Lightweight & Fast**: Minimal overhead with maximum performance - designed to enhance your application, not slow it down. - **📚 No Learning Curve**: Dead simple API that you can master in seconds. No extensive documentation, complex configurations, or setup required - Log Engine works instantly. - **🌈 Colorized Console Output**: Beautiful ANSI color-coded log levels with intelligent terminal formatting - instantly identify message severity at a glance with color-coded output. @@ -30,6 +31,10 @@ Log Engine transforms your development experience from chaotic debugging session - **🔗 Zero Dependencies**: No external dependencies for maximum compatibility and security - keeps your bundle clean and your project simple. - **🔌 Easy Integration**: Simple API that works seamlessly with existing Node.js applications. Just `import` and start logging - no middleware, plugins, or configuration required. +## ⚠️ Breaking Changes Notice + +**v3.0.0 Breaking Change**: The legacy `level` configuration will be removed in v3.0.0. If you're using `LogEngine.configure({ level: LogLevel.DEBUG })`, please migrate to `LogEngine.configure({ mode: LogMode.DEBUG })`. See our [Migration Guide](#migration-guide-loglevel--logmode) for details. + ## 🤔 How It Works 1. Log Engine automatically detects your environment using `NODE_ENV` and sets appropriate log levels for optimal performance @@ -57,20 +62,24 @@ Open source development is resource-intensive. These **sponsored ads help keep L ## 📦 Installation -Install the package using npm: +Install the package using yarn (recommended): ```bash -npm install @wgtechlabs/log-engine +yarn add @wgtechlabs/log-engine ``` -Or using yarn: +Or using npm: ```bash -yarn add @wgtechlabs/log-engine +npm install @wgtechlabs/log-engine ``` +> **Note**: This project uses yarn as the primary package manager for development. If you're contributing to the project, please use yarn to ensure consistency with the development environment. + ## 🕹️ Usage +> **⚠️ Important**: If you're using the legacy `level` configuration, please migrate to `mode`. The `level` configuration is deprecated and will be removed in v3.0.0. See the [Migration Guide](#migration-guide-loglevel--logmode) below. + ### Quick Start ```typescript @@ -88,7 +97,7 @@ LogEngine.info('User login', { username: 'john_doe', // ✅ Visible password: 'secret123', // ❌ [REDACTED] email: 'john@example.com', // ❌ [REDACTED] - apiKey: 'apikey123' // ❌ [REDACTED] + apiKey: 'apikey123' // ❌ [REDACTED] }); // Advanced redaction features - Add custom patterns @@ -101,6 +110,24 @@ console.log(LogEngine.testFieldRedaction('username')); // false // Raw logging for debugging (bypasses redaction) LogEngine.withoutRedaction().info('Debug data', { password: 'visible-in-debug' }); + +// NEW: Custom output destinations (v2.0+) +LogEngine.configure({ + outputHandler: (level, message, data) => { + // Send logs anywhere: GUI apps, files, APIs, databases + myCustomHandler(level, message, data); + }, + suppressConsoleOutput: true // Optional: disable console +}); + +// NEW: Multiple outputs simultaneously +LogEngine.configure({ + outputs: [ + 'console', // Built-in console + myFileHandler, // Custom file handler + myMonitoringHandler // Custom monitoring + ] +}); ``` ### Mode-Based Configuration (Recommended) @@ -137,41 +164,59 @@ LogEngine.error('Error message'); LogEngine.log('Critical message that always shows'); ``` -### Legacy Level-Based Configuration (Backwards Compatible) +### Legacy Level-Based Configuration (⚠️ Deprecated) + +**⚠️ DEPRECATION WARNING**: The `level` configuration is deprecated and will be removed in **v3.0.0**. Please use `mode` instead. -For backwards compatibility, the old `LogLevel` API is still supported: +For backwards compatibility, the old `LogLevel` API is still supported but shows deprecation warnings: ```typescript import { LogEngine, LogLevel } from '@wgtechlabs/log-engine'; -// Legacy configuration (still works but LogMode is recommended) -LogEngine.configure({ level: LogLevel.DEBUG }); -LogEngine.configure({ level: LogLevel.INFO }); -LogEngine.configure({ level: LogLevel.WARN }); -LogEngine.configure({ level: LogLevel.ERROR }); +// ⚠️ DEPRECATED: Legacy configuration (shows warning, will be removed in v3.0.0) +LogEngine.configure({ level: LogLevel.DEBUG }); // Triggers deprecation warning +LogEngine.configure({ level: LogLevel.INFO }); // Triggers deprecation warning +LogEngine.configure({ level: LogLevel.WARN }); // Triggers deprecation warning +LogEngine.configure({ level: LogLevel.ERROR }); // Triggers deprecation warning + +// ✅ RECOMMENDED: Use the modern LogMode API instead +LogEngine.configure({ mode: LogMode.DEBUG }); // No warnings, future-proof ``` +**Why migrate to LogMode?** + +- No deprecation warnings +- Future-proof API that won't be removed +- Better separation of concerns +- Access to all v2.0+ features including output handlers +- Clearer, more intuitive configuration + ### Migration Guide: LogLevel → LogMode +**⚠️ IMPORTANT DEPRECATION NOTICE**: The `level` configuration is **deprecated as of v2.0.0** and will be **removed in v3.0.0**. Please migrate to the new `mode` configuration. + **Version 1.2.0+** introduces the new LogMode system for better separation of concerns. Here's how to migrate: ```typescript -// OLD (v1.1.0 and earlier) - still works but deprecated +// ❌ OLD (v1.1.0 and earlier) - DEPRECATED, will be removed in v3.0.0 import { LogEngine, LogLevel } from '@wgtechlabs/log-engine'; -LogEngine.configure({ level: LogLevel.DEBUG }); +LogEngine.configure({ level: LogLevel.DEBUG }); // Shows deprecation warning -// NEW (v1.2.1+) - recommended approach with advanced features +// ✅ NEW (v2.0.0+) - recommended approach with advanced features import { LogEngine, LogMode } from '@wgtechlabs/log-engine'; -LogEngine.configure({ mode: LogMode.DEBUG }); +LogEngine.configure({ mode: LogMode.DEBUG }); // Modern, future-proof API ``` +**Migration Benefits:** + **Key Benefits of LogMode:** - **Clearer API**: Separates message severity (`LogLevel`) from output control (`LogMode`) - **Better Environment Defaults**: `development→DEBUG`, `staging→WARN`, `test→ERROR` - **Advanced Features**: New redaction APIs and TypeScript interfaces work with LogMode - **Future-Proof**: All new features use the LogMode system -- **100% Backwards Compatible**: Existing code continues to work unchanged +- **100% Backwards Compatible**: Existing code continues to work unchanged (with deprecation warnings) +- **No Breaking Changes in v2.0**: Legacy `level` support maintained until v3.0.0 ### Color-Coded Output 🎨 @@ -485,9 +530,179 @@ LogEngine.withoutRedaction().info('Without redaction', data); // ⚠️ Visible **Perfect for:** Enterprise applications, compliance requirements, team development environments, production systems requiring both security and debugging flexibility, and any scenario where sensitive data protection is critical. +## 🎯 Configurable Output Handlers + +**LogEngine v2.0+ introduces revolutionary output routing capabilities that transform how and where your logs are delivered.** This powerful system enables custom log destinations, multiple simultaneous outputs, and production-ready handlers while maintaining the same simple API you know and love. + +### Zero-Configuration Custom Routing + +**Redirect logs anywhere with a single configuration:** + +```typescript +import { LogEngine } from '@wgtechlabs/log-engine'; + +// Redirect to GUI application +LogEngine.configure({ + outputHandler: (level, message, data) => { + updateReactUI({ level, message, timestamp: new Date() }); + }, + suppressConsoleOutput: true // Optional: disable console output +}); + +// Testing framework integration +const capturedLogs = []; +LogEngine.configure({ + outputHandler: (level, message, data) => { + capturedLogs.push({ level, message, data }); + }, + suppressConsoleOutput: true +}); + +// Now use LogEngine normally - output goes to your custom handler +LogEngine.info('User logged in', { userId: 123 }); +``` + +### Multiple Simultaneous Outputs + +**Log to multiple destinations at once with built-in handlers:** + +```typescript +LogEngine.configure({ + outputs: [ + 'console', // Built-in console handler + 'silent', // Built-in silent handler (no output) + (level, message, data) => { + // Custom file handler + fs.appendFileSync('./app.log', `${new Date().toISOString()} [${level}] ${message}\n`); + }, + (level, message, data) => { + // Custom network handler + if (level === 'error') { + sendToMonitoringService({ level, message, data }); + } + } + ] +}); + +// Single log call outputs to all configured destinations +LogEngine.error('Database connection failed', { retries: 3 }); +``` + +### Production-Ready Advanced Handlers + +**Enterprise-grade file and HTTP outputs with advanced features:** + +```typescript +LogEngine.configure({ + enhancedOutputs: [ + 'console', + { + type: 'file', + config: { + filePath: './logs/app.log', + maxFileSize: 10485760, // 10MB rotation + maxBackupFiles: 5, // Keep 5 backup files + append: true, // Append mode + formatter: (level, message, data) => { + const timestamp = new Date().toISOString(); + const dataStr = data ? ` | ${JSON.stringify(data)}` : ''; + return `[${timestamp}] ${level.toUpperCase()}: ${message}${dataStr}\n`; + } + } + }, + { + type: 'http', + config: { + url: 'https://api.datadog.com/v1/logs', + method: 'POST', + batchSize: 10, // Batch requests for performance + timeout: 5000, // 5-second timeout + headers: { + 'Authorization': 'Bearer your-api-key', + 'Content-Type': 'application/json' + }, + formatter: (logs) => ({ + service: 'my-app', + environment: process.env.NODE_ENV, + logs: logs.map(log => ({ + timestamp: log.timestamp, + level: log.level, + message: log.message, + metadata: log.data + })) + }) + } + } + ] +}); +``` + +### Configuration Priority System + +**Flexible configuration with intelligent priority handling:** + +```typescript +// Priority: outputs > enhancedOutputs > outputHandler > default console + +// Single handler (legacy compatibility) +LogEngine.configure({ + outputHandler: myHandler, + suppressConsoleOutput: true +}); + +// Multiple basic outputs - takes priority over outputHandler +LogEngine.configure({ + outputs: ['console', myFileHandler, myNetworkHandler] +}); + +// Advanced outputs - used when outputs not specified +LogEngine.configure({ + enhancedOutputs: [ + 'console', + { type: 'file', config: { filePath: './app.log' } } + ] +}); +``` + +### Error Handling & Resilience + +**Robust error handling ensures logging never breaks your application:** + +```typescript +LogEngine.configure({ + outputs: [ + 'console', // Always works + (level, message) => { + throw new Error('Handler failed'); // This failure won't break other outputs + }, + (level, message) => { + fs.writeFileSync('./logs/app.log', message); // This still works + } + ] +}); + +// If any handler fails, others continue working + fallback to console +LogEngine.error('Critical error'); // Logs to console + working file handler +``` + +### Output Handler Benefits + +✅ **GUI Integration** - Perfect for desktop applications, web dashboards, and real-time monitoring +✅ **Testing Support** - Capture and assert on log output in automated tests +✅ **Production Monitoring** - Send logs to Datadog, New Relic, CloudWatch, or custom endpoints +✅ **File Logging** - Persistent logs with automatic rotation and backup management +✅ **Silent Operation** - Disable console output for clean CLI tools and background services +✅ **Multi-Destination** - Log to console + file + network simultaneously with zero overhead +✅ **Error Isolation** - Failed outputs don't break other outputs or your application +✅ **Backward Compatible** - Existing code works unchanged, new features are opt-in only +✅ **TypeScript Ready** - Full type safety with comprehensive interfaces and IntelliSense +✅ **Zero Dependencies** - Built-in handlers use only Node.js standard library + +**Use Cases:** Desktop applications, testing frameworks, production monitoring, CI/CD pipelines, microservices, IoT devices, and any scenario requiring flexible log routing with enterprise-grade reliability. + ## 📚 Comprehensive TypeScript Support -**LogEngine v1.2.1+ includes extensive TypeScript definitions with 10+ interfaces for maximum type safety and developer experience:** +**LogEngine v2.0+ includes extensive TypeScript definitions with 15+ interfaces for maximum type safety and developer experience:** ### Core Interfaces @@ -500,6 +715,9 @@ import { ILogEngineWithoutRedaction, RedactionConfig, LoggerConfig, + LogOutputHandler, + FileOutputConfig, + HttpOutputConfig, IDataRedactor } from '@wgtechlabs/log-engine'; @@ -512,6 +730,18 @@ const config: RedactionConfig = { // ... fully typed configuration }; +// Type-safe output handler configuration +const outputHandler: LogOutputHandler = (level, message, data) => { + console.log(`[${level}] ${message}`, data); +}; + +const fileConfig: FileOutputConfig = { + filePath: './app.log', + maxFileSize: 1048576, + maxBackupFiles: 3, + formatter: (level, message, data) => `${level}: ${message}\n` +}; + // Advanced redaction testing with return types const isRedacted: boolean = LogEngine.testFieldRedaction('password'); const currentConfig: RedactionConfig = LogEngine.getRedactionConfig(); @@ -519,6 +749,10 @@ const currentConfig: RedactionConfig = LogEngine.getRedactionConfig(); // Type-safe raw logging const rawLogger: ILogEngineWithoutRedaction = LogEngine.withoutRedaction(); rawLogger.info('Debug info', sensitiveData); // Fully typed methods + +// ⚠️ Legacy (deprecated) - will be removed in v3.0.0 +import { LogLevel } from '@wgtechlabs/log-engine'; +LogEngine.configure({ level: LogLevel.DEBUG }); // Shows deprecation warning ``` ### Available Interfaces @@ -526,11 +760,17 @@ rawLogger.info('Debug info', sensitiveData); // Fully typed methods - **`ILogEngine`** - Complete LogEngine API with all methods - **`ILogEngineWithoutRedaction`** - Raw logging methods interface - **`IDataRedactor`** - Static DataRedactor class methods +- **`LogOutputHandler`** - Custom output handler function interface +- **`FileOutputConfig`** - File output handler configuration +- **`HttpOutputConfig`** - HTTP output handler configuration - **`RedactionConfig`** - Comprehensive redaction configuration -- **`LoggerConfig`** - Logger configuration options +- **`LoggerConfig`** - Logger configuration options (including output handlers) - **`LogEntry`** - Structured log entry interface +- **`LogLevel`** - ⚠️ **Deprecated**: Legacy level enumeration (use `LogMode` instead) +- **`LogMode`** - ✅ **Recommended**: Modern mode enumeration - **`EnvironmentConfig`** - Environment variable documentation -- **Plus 3+ additional interfaces** for advanced use cases +- **`EnvironmentConfig`** - Environment variable documentation +- **Plus 5+ additional interfaces** for advanced output handling and configuration **IDE Benefits:** IntelliSense, auto-completion, parameter hints, error detection, and comprehensive documentation tooltips throughout your development workflow. @@ -570,7 +810,28 @@ Your contributions to improving this project are greatly appreciated! 🙏✨ Contributions are welcome, create a pull request to this repo and I will review your code. Please consider to submit your pull request to the `dev` branch. Thank you! -Read the project's [contributing guide](./CONTRIBUTING.md) for more info, including testing guidelines and requirements. +**Development Environment:** + +- This project is configured for **local development workflows only** - no CI/CD setup required +- Uses **yarn** as the primary package manager for consistency +- Simple, cross-platform development setup with TypeScript, Jest, and ESLint +- Clean test output maintained using `jest.setup.js` to suppress console noise during testing +- All error logging functionality remains intact in production code +- Security scanning available via Snyk for dependency vulnerability checking + +**Available Scripts:** + +- `yarn test` - Run all tests +- `yarn test:watch` - Run tests in watch mode +- `yarn test:coverage` - Run tests with coverage reporting +- `yarn lint` - Run TypeScript and code quality checks +- `yarn lint:fix` - Automatically fix linting issues +- `yarn lint:security` - Run security-focused linting +- `yarn secure` - Run comprehensive security checks +- `yarn build` - Build the TypeScript project +- `yarn validate` - Run full validation (lint + test + build) + +Read the project's [contributing guide](./CONTRIBUTING.md) for detailed development setup, testing guidelines, and contribution requirements. ## 🙏 Sponsor @@ -597,7 +858,7 @@ This project is licensed under the [MIT License](https://opensource.org/licenses This project is created by **[Waren Gonzaga](https://github.com/warengonzaga)** under [WG Technology Labs](https://github.com/wgtechlabs), with the help of awesome [contributors](https://github.com/wgtechlabs/log-engine/graphs/contributors). -**Latest Version:** v1.3.0 - Enhanced with advanced redaction features, comprehensive TypeScript support, and 95%+ test coverage. +**Latest Version:** v2.1.0 - Enhanced with advanced output handlers, comprehensive security-first logging, complete TypeScript support, and 95%+ test coverage for local development workflows. [![contributors](https://contrib.rocks/image?repo=wgtechlabs/log-engine)](https://github.com/wgtechlabs/log-engine/graphs/contributors) diff --git a/SECURITY.md b/SECURITY.md index 31de941..9608309 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,106 @@ -# 🔒 Security +# 🔒 Security Policy -If you identify any security vulnerabilities or concerns within this repository, please report them promptly by emailing me at [security@wgtechlabs.com](mailto:security@wgtechlabs.com). +## Security-First Logging -Your efforts to help me maintain the safety and integrity of my open-source projects are greatly appreciated. Thank you for contributing to a more secure community! +Log Engine is built with security as a core principle. This document outlines our security policies, practices, and how to report vulnerabilities. + +## Built-in Security Features + +### 🛡️ Automatic Data Redaction + +- **PII Protection**: Automatically redacts passwords, API keys, emails, and other sensitive data +- **Custom Patterns**: Support for custom regex patterns to match organization-specific sensitive data +- **Runtime Configuration**: Dynamic field management with environment-based configuration +- **Zero-Config Security**: Secure by default with no configuration required + +### 🔍 Security Scanning + +The project includes comprehensive security scanning tools: + +```bash +# Run security-focused linting +yarn lint:security + +# Run dependency vulnerability scanning (requires Snyk account) +yarn secure:test + +# Run code security analysis (requires Snyk account) +yarn secure:code + +# Run complete security checks +yarn secure +``` + +### 📋 Security Best Practices + +**For Developers:** + +- Use `LogEngine.testFieldRedaction('fieldName')` to verify redaction rules +- Leverage `LogEngine.withoutRedaction()` only in development environments +- Review logs regularly to ensure sensitive data isn't being exposed +- Use environment-based configuration to disable redaction only in development + +**For Production:** + +- Never disable redaction in production environments +- Regularly audit custom redaction patterns +- Monitor log outputs for potential data leaks +- Use secure transport when sending logs to external systems + +## Supported Versions + +| Version | Supported | Security Updates | +| ------- | ------------------ | ---------------- | +| 2.1.x | ✅ Yes | ✅ Active | +| 2.0.x | ✅ Yes | ✅ Active | +| < 2.0 | ❌ No | ❌ None | + +## Reporting Vulnerabilities + +If you discover a security vulnerability, please follow responsible disclosure practices: + +### 🚨 Critical/High Severity + +For critical security issues that could compromise user data or system security: + +📧 **Email**: [security@wgtechlabs.com](mailto:security@wgtechlabs.com) + +- Response within 24 hours +- Initial assessment within 48 hours +- Security patches prioritized + +### 📊 Medium/Low Severity + +For general security improvements or non-critical issues: + +- Create a private security advisory on GitHub +- Or email [security@wgtechlabs.com](mailto:security@wgtechlabs.com) +- Response within 72 hours + +### 🛡️ What to Include + +When reporting a vulnerability, please include: + +1. **Description** of the vulnerability +2. **Steps to reproduce** the issue +3. **Potential impact** and affected versions +4. **Suggested fix** (if you have one) +5. **Your contact information** for follow-up + +### ⚡ What to Expect + +- **Acknowledgment** within 24-72 hours +- **Regular updates** on investigation progress +- **Credit** in security advisories (if desired) +- **Coordinated disclosure** timeline discussion + +## Security Contact + +**Primary Security Contact**: [security@wgtechlabs.com](mailto:security@wgtechlabs.com) +**Backup Contact**: [opensource@wgtechlabs.com](mailto:opensource@wgtechlabs.com) + +Your efforts to help maintain the security and integrity of Log Engine are greatly appreciated. Thank you for contributing to a safer open-source community! --- -🔐 with ❤️ by [Waren Gonzaga](https://warengonzaga.com) under [WG Technology Labs](https://wgtechlabs.com) and [Him](https://www.youtube.com/watch?v=HHrxS4diLew&t=44s) 🙏 \ No newline at end of file +🔐 with ❤️ by [Waren Gonzaga](https://warengonzaga.com) under [WG Technology Labs](https://wgtechlabs.com) and [Him](https://www.youtube.com/watch?v=HHrxS4diLew&t=44s) 🙏 diff --git a/codecov.yml b/codecov.yml index 5ab1b97..3f01ab3 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,11 +2,11 @@ coverage: status: project: default: - target: 90% + target: 80% threshold: 2% patch: default: - target: 85% + target: 80% ignore: - "src/**/*.test.ts" diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..84a8e4c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,81 @@ +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; + +export default [ + { + files: ['**/*.ts', '**/*.tsx'], + ignores: ['**/*.test.ts', '**/__tests__/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + }, + rules: { + // TypeScript specific rules + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/no-non-null-assertion': 'warn', + + // General ESLint rules + 'no-console': 'off', // Allow console for logging library + 'no-debugger': 'error', + 'no-var': 'error', + 'prefer-const': 'error', + 'eqeqeq': ['error', 'always'], + 'curly': ['error', 'all'], + 'brace-style': ['error', '1tbs'], + 'indent': ['error', 2], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-trailing-spaces': 'error', + 'eol-last': 'error', + + // Security-conscious rules + 'no-eval': 'error', + 'no-implied-eval': 'error', + 'no-new-func': 'error', + 'no-script-url': 'error', + }, + }, + { + files: ['**/*.test.ts', '**/__tests__/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + // Don't use project for test files to avoid TypeScript config issues + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + }, + rules: { + // Relax some rules for test files + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + 'no-console': 'off', + 'no-debugger': 'error', + 'no-var': 'error', + 'prefer-const': 'error', + 'eqeqeq': ['error', 'always'], + 'curly': ['error', 'all'], + 'brace-style': ['error', '1tbs'], + 'indent': ['error', 2], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-trailing-spaces': 'error', + 'eol-last': 'error', + }, + }, + { + ignores: ['dist/', 'coverage/', 'node_modules/', '*.config.js'], + }, +]; diff --git a/eslint.security.config.js b/eslint.security.config.js new file mode 100644 index 0000000..670ce86 --- /dev/null +++ b/eslint.security.config.js @@ -0,0 +1,66 @@ +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import securityPlugin from 'eslint-plugin-security'; + +export default [ + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + 'security': securityPlugin, + }, + rules: { + // Security plugin rules - focusing on the most critical ones + 'security/detect-object-injection': 'error', + 'security/detect-non-literal-regexp': 'error', + 'security/detect-unsafe-regex': 'error', + 'security/detect-buffer-noassert': 'error', + 'security/detect-child-process': 'error', + 'security/detect-disable-mustache-escape': 'error', + 'security/detect-eval-with-expression': 'error', + 'security/detect-no-csrf-before-method-override': 'error', + 'security/detect-non-literal-fs-filename': 'error', + 'security/detect-non-literal-require': 'error', + 'security/detect-possible-timing-attacks': 'error', + 'security/detect-pseudoRandomBytes': 'error', + 'security/detect-bidi-characters': 'error', + + // General security-conscious rules + 'no-eval': 'error', + 'no-implied-eval': 'error', + 'no-new-func': 'error', + 'no-script-url': 'error', + 'no-debugger': 'error', + 'no-console': 'warn', // Allow for logging library but warn about it + + // Prevent dangerous patterns + 'no-proto': 'error', + 'no-caller': 'error', + 'no-extend-native': 'error', + 'no-with': 'error', + }, + }, + // Relaxed rules for test files + { + files: ['**/__tests__/**/*.ts', '**/*.test.ts', '**/*test-utils.ts'], + rules: { + 'security/detect-non-literal-fs-filename': 'off', // Allow dynamic file paths in tests + 'security/detect-object-injection': 'warn', // Warn instead of error in tests + 'no-console': 'off', // Allow console in test files + }, + }, + // Relaxed security rules for advanced-outputs.ts - comprehensive path validation implemented + { + files: ['**/advanced-outputs.ts'], + rules: { + 'security/detect-non-literal-fs-filename': 'off', // Disabled: comprehensive path validation and security checks implemented + }, + }, +]; diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 0000000..e3e8c97 --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,40 @@ +/** + * Jest configuration for Log Engine - Local Development + * Simple configuration for local testing and development + */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, + setupFilesAfterEnv: ['/jest.setup.js'], + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/**/*.test.ts', + '!src/**/__tests__/**', + ], + // Coverage output + coverageReporters: ['text', 'lcov', 'html'], + coverageDirectory: 'coverage', + // Coverage thresholds for CI/CD pipeline + coverageThreshold: { + global: { + statements: 80, + branches: 80, + functions: 80, + lines: 80 + } + }, + // Use available CPU cores for better local performance + maxWorkers: '50%', + // Reasonable timeout for local development + testTimeout: 15000, + // Clean up between tests + clearMocks: true, + restoreMocks: true +}; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 2625ed3..0000000 --- a/jest.config.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Jest configuration for Log Engine testing - * Provides comprehensive test coverage with TypeScript support - */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/__tests__/**/*.test.ts'], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/'], - collectCoverageFrom: [ - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/**/*.test.ts', - '!src/**/__tests__/**', - ], - // Coverage configuration for Codecov - coverageReporters: ['text', 'lcov', 'clover', 'json'], - coverageDirectory: 'coverage', - // Coverage thresholds - coverageThreshold: { - global: { - statements: 90, - branches: 85, - functions: 90, - lines: 90 - } - }, - // Enable parallel test execution for better performance - maxWorkers: '50%', - // Timeout for individual tests - testTimeout: 10000 -}; diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..abbb1a7 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,48 @@ +/** + * Global Jest setup for Log Engine testing + * Optimized for CI environments with better error handling + */ + +// Global Jest setup to suppress console output during tests +global.console = { + ...console, + // Suppress error logs during tests to avoid confusing output + error: jest.fn(), + // Keep other console methods for debugging + log: console.log, + warn: console.warn, + info: console.info +}; + +// Optimized timeout for faster feedback +jest.setTimeout(10000); + +// Handle uncaught promise rejections in tests +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + // In test environment, we want to know about these + if (process.env.NODE_ENV === 'test') { + throw reason; + } +}); + +// Global cleanup optimized for CI +afterAll(async () => { + // Clear any remaining timers immediately + jest.clearAllTimers(); + jest.clearAllMocks(); + + // Force garbage collection if available + if (global.gc) { + global.gc(); + } +}); + +afterEach(async () => { + // Fast cleanup after each test + jest.restoreAllMocks(); + jest.clearAllMocks(); + + // Clear any timers that might be hanging around + jest.clearAllTimers(); +}); diff --git a/package.json b/package.json index 9fc8a3b..79ba6ab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "@wgtechlabs/log-engine", - "version": "2.0.0", + "version": "2.1.0", "description": "A lightweight, security-first logging utility with automatic data redaction for Node.js applications - the first logging library with built-in PII protection.", + "type": "module", "keywords": [ "logging", "security", @@ -17,6 +18,7 @@ "gdpr", "compliance" ], + "packageManager": "yarn@4.9.2", "license": "MIT", "author": "WG Tech Labs (https://wgtechlabs.com)", "contributors": [ @@ -36,33 +38,43 @@ "scripts": { "build": "tsc", "start": "node dist/index.js", - "prepublishOnly": "npm run build", + "prepublishOnly": "yarn build", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "test:coverage:watch": "jest --coverage --watch", - "test:ci": "jest --ci --coverage --watchAll=false", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "test:unit": "jest --testNamePattern=\"^((?!integration).)*$\"", "test:integration": "jest integration.test.ts", "test:redaction": "jest --testPathPattern=\"redaction/\"", - "test:redaction:core": "jest --testPathPattern=\"redaction/core.test.ts\"", - "test:redaction:env": "jest --testPathPattern=\"redaction/env.test.ts\"", - "test:redaction:advanced": "jest --testPathPattern=\"redaction/advanced.test.ts\"", - "test:redaction:logengine": "jest --testPathPattern=\"redaction/logengine.test.ts\"", + "lint": "eslint src/**/*.ts", + "lint:fix": "eslint src/**/*.ts --fix", + "lint:security": "eslint src/**/*.ts --config eslint.security.config.js", + "secure:scan": "snyk code test", + "secure:test": "snyk test --org=wgtechlabs", + "secure:code": "snyk code test --org=wgtechlabs", + "secure": "run-s lint:security secure:test secure:code", + "validate": "run-s lint test build", "clean": "rm -rf dist coverage", - "coverage:open": "open coverage/lcov-report/index.html" + "coverage:open": "open coverage/lcov-report/index.html", + "coverage:upload": "curl -Os https://uploader.codecov.io/latest/windows/codecov.exe && codecov.exe" }, - "dependencies": {}, "devDependencies": { - "typescript": "^5.0.0", - "ts-node": "^10.0.0", + "@types/eslint-plugin-security": "^3", + "@types/jest": "^29.0.0", "@types/node": "^18.0.0", + "@typescript-eslint/eslint-plugin": "^8.35.0", + "@typescript-eslint/parser": "^8.35.0", + "eslint": "^8.45.0", + "eslint-plugin-security": "^2.0.0", "jest": "^29.0.0", - "@types/jest": "^29.0.0", - "ts-jest": "^29.0.0" + "npm-run-all": "^4.1.5", + "snyk": "^1.1000.0", + "ts-jest": "^29.0.0", + "ts-node": "^10.0.0", + "typescript": "^5.8.3" }, "engines": { "node": ">=16.0.0" } -} \ No newline at end of file +} diff --git a/src/__tests__/advanced-outputs.test.ts b/src/__tests__/advanced-outputs.test.ts new file mode 100644 index 0000000..e7c6ce8 --- /dev/null +++ b/src/__tests__/advanced-outputs.test.ts @@ -0,0 +1,1208 @@ +/** + * Test suite for Advanced Output Handlers + * Tests file output, HTTP output, and enhanced configuration options + * Uses async file operations for better CI environment compatibility + */ + +import { LogEngine, LogMode } from '../index'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { + waitForFile, + waitForFiles, + waitForFileContent, + safeCleanupDirectory, + MockHttpHandler, + withTimeout +} from './async-test-utils'; + +describe('Advanced Output Handlers', () => { + // Use a unique test directory for each test run to prevent conflicts + let testDir = path.join(os.tmpdir(), `log-engine-test-advanced-${Date.now()}-${Math.random().toString(36).substring(7)}`); + + beforeEach(async () => { + // Reset to default configuration + LogEngine.configure({ + mode: LogMode.DEBUG, + outputs: undefined, + enhancedOutputs: undefined, + advancedOutputConfig: undefined, + suppressConsoleOutput: false + }); + + // Create test directory + try { + if (!fs.existsSync(testDir)) { + fs.mkdirSync(testDir, { recursive: true }); + } + } catch (error) { + // If directory creation fails, use a backup directory + console.warn('Failed to create primary test directory:', error); + testDir = path.join(os.tmpdir(), 'log-engine-test-backup-' + Date.now()); + try { + fs.mkdirSync(testDir, { recursive: true }); + console.log('Using backup test directory:', testDir); + } catch (backupError) { + console.error('Failed to create backup test directory:', backupError); + throw new Error('Unable to create test directory for advanced-outputs tests'); + } + } + }); + + afterEach(async () => { + // Reset configuration first + LogEngine.configure({ mode: LogMode.INFO }); + + // Clean up test files + try { + if (fs.existsSync(testDir)) { + const files = fs.readdirSync(testDir); + for (const file of files) { + try { + fs.unlinkSync(path.join(testDir, file)); + } catch (error) { + // Ignore individual file cleanup errors + } + } + } + } catch (error) { + // Log cleanup error but don't fail the test + } + }); + + afterAll(async () => { + // Final cleanup - use sync operations + try { + if (fs.existsSync(testDir)) { + const files = fs.readdirSync(testDir); + for (const file of files) { + try { + fs.unlinkSync(path.join(testDir, file)); + } catch (error) { + // Ignore individual file cleanup errors + } + } + try { + fs.rmdirSync(testDir); + } catch (error) { + // Ignore directory removal errors + } + } + } catch (error) { + // Ignore final cleanup errors + } + }); + + describe('File Output Handler', () => { + test('should write logs to file using advancedOutputConfig', async () => { + const logFile = path.join(testDir, 'test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + append: false + } + } + }); + + LogEngine.info('Test message', { test: 'data' }); + + // Wait for file to be created and have content + await waitForFile(logFile, 1000); + await waitForFileContent(logFile, '[INFO] Test message', 1000); + + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toContain('[INFO] Test message'); + expect(content).toContain('test'); + expect(content).toContain('data'); + }); + + test('should append to existing file when append is true', async () => { + const logFile = path.join(testDir, 'append-test.log'); + + // Write initial content + await fs.promises.writeFile(logFile, 'Initial content\n'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + append: true + } + } + }); + + LogEngine.info('Appended message'); + + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toContain('Initial content'); + expect(content).toContain('[INFO] Appended message'); + }); + + test('should use custom formatter for file output', async () => { + const logFile = path.join(testDir, 'custom-format.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + formatter: (level, message, data) => { + return `CUSTOM: ${level.toUpperCase()} - ${message}\\n`; + } + } + } + }); + + LogEngine.warn('Custom formatted message'); + + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toBe('CUSTOM: WARN - Custom formatted message\\n'); + }); + + test('should handle file rotation when maxFileSize is exceeded', async () => { + const logFile = path.join(testDir, 'rotation-test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 100, // Very small size to trigger rotation + maxBackupFiles: 2 + } + } + }); + + // Write multiple messages to trigger rotation + for (let i = 0; i < 10; i++) { + LogEngine.info(`Long message ${i} with extra content to exceed size limit and trigger rotation`); + } + + // Wait for both main file and backup file to be created + const backupFile1 = `${logFile}.1`; + await waitForFiles([logFile, backupFile1]); + + expect(fs.existsSync(logFile)).toBe(true); + expect(fs.existsSync(backupFile1)).toBe(true); + }); + + test('should handle file rotation with no existing backup files', async () => { + const logFile = path.join(testDir, 'rotation-no-backup-test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 80, // Small size to trigger rotation quickly + maxBackupFiles: 3, + append: false + } + } + }); + + // Write just enough to trigger one rotation + LogEngine.info('First message with enough content to exceed the small size limit and trigger rotation'); + LogEngine.info('Second message to trigger rotation'); + + await waitForFile(logFile, 2000); + + // Should have created backup file even when none existed before + expect(fs.existsSync(logFile)).toBe(true); + expect(fs.existsSync(`${logFile}.1`)).toBe(true); + }); + + test('should handle concurrent rotation attempts', async () => { + const logFile = path.join(testDir, 'concurrent-rotation-test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 100, // Small size to trigger rotation + maxBackupFiles: 2, + append: false + } + } + }); + + // Rapidly send many messages to test rotation concurrency protection + const promises: Promise[] = []; + for (let i = 0; i < 20; i++) { + promises.push(new Promise((resolve) => { + setTimeout(() => { + LogEngine.info(`Concurrent test message ${i} with enough content to trigger rotation`); + resolve(); + }, i * 5); // Stagger messages slightly + })); + } + + await Promise.all(promises); + await waitForFile(logFile, 3000); + + // File should exist and rotations should have been handled safely + expect(fs.existsSync(logFile)).toBe(true); + }); + }); + + describe('HTTP Output Handler', () => { + // Mock HTTP requests using our better handler + let mockHttpHandler: MockHttpHandler; + let mockFetch: jest.SpyInstance; + let consoleErrorSpy: jest.SpyInstance; + let consoleLogSpy: jest.SpyInstance; + + beforeAll(() => { + // Globally suppress console output for the entire HTTP test suite + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterAll(() => { + if (consoleErrorSpy) { + consoleErrorSpy.mockRestore(); + } + if (consoleLogSpy) { + consoleLogSpy.mockRestore(); + } + }); + + beforeEach(() => { + mockHttpHandler = new MockHttpHandler(); + + // Mock fetch to use our handler + if (typeof global.fetch === 'undefined') { + global.fetch = jest.fn(); + } + + mockFetch = jest.spyOn(global, 'fetch').mockImplementation(async (url, options) => { + mockHttpHandler.addRequest(url as string, options); + // Always resolve successfully to prevent fallback to Node.js HTTP + return Promise.resolve(new Response('{"success": true}', { status: 200 })); + }); + }); + + afterEach(() => { + if (mockFetch) { + mockFetch.mockRestore(); + } + mockHttpHandler.clear(); + }); + + test('should send logs to HTTP endpoint using advancedOutputConfig', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + method: 'POST', + headers: { 'Authorization': 'Bearer test-token' } + } + } + }); + + LogEngine.error('HTTP test message', { error: 'details' }); + + // Wait for HTTP request to be sent using our proper handler + await mockHttpHandler.waitForRequests(1); + + const requests = mockHttpHandler.getRequests(); + expect(requests.length).toBeGreaterThan(0); + + const call = requests[0]; + expect(call.url).toBe('https://api.example.com/logs'); + expect(call.options.method).toBe('POST'); + expect(call.options.headers['Authorization']).toBe('Bearer test-token'); + + const body = JSON.parse(call.options.body); + expect(body.logs).toHaveLength(1); + expect(body.logs[0].level).toBe('error'); + expect(body.logs[0].message).toContain('HTTP test message'); + }); + + test('should batch multiple logs when batchSize > 1', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + batchSize: 3 + } + } + }); + + LogEngine.info('Message 1'); + LogEngine.warn('Message 2'); + LogEngine.error('Message 3'); // This should trigger batch send + + await mockHttpHandler.waitForRequests(1); + + const requests = mockHttpHandler.getRequests(); + expect(requests.length).toBeGreaterThan(0); + + if (requests.length > 0) { + const body = JSON.parse(requests[0].options.body); + // Check if it's batched or individual logs + if (body.logs && Array.isArray(body.logs)) { + expect(body.logs.length).toBeGreaterThanOrEqual(1); + expect(body.logs[0].message).toContain('Message'); + } else { + // Individual log format + expect(body.message).toContain('Message'); + } + } + }); + + test('should use custom formatter for HTTP payload', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + formatter: (logs) => ({ + custom_format: true, + event_count: logs.length, + events: logs.map(log => `${log.level}: ${log.message}`) + }) + } + } + }); + + LogEngine.debug('Custom format test'); + + await mockHttpHandler.waitForRequests(1); + + const requests = mockHttpHandler.getRequests(); + if (requests.length > 0) { + const body = JSON.parse(requests[0].options.body); + expect(body.custom_format).toBe(true); + expect(body.event_count).toBe(1); + expect(body.events[0]).toBe('debug: Custom format test'); + } + }); + + test('should handle HTTP timeout configuration', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + timeout: 1000, + batchSize: 1 + } + } + }); + + LogEngine.error('Timeout test message'); + + await mockHttpHandler.waitForRequests(1); + + const requests = mockHttpHandler.getRequests(); + expect(requests.length).toBeGreaterThan(0); + }); + + test('should handle HTTP with custom headers and method', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + method: 'PUT', + headers: { + 'Custom-Header': 'test-value', + 'Content-Type': 'application/json' + }, + batchSize: 1 + } + } + }); + + LogEngine.info('Custom headers test'); + + await mockHttpHandler.waitForRequests(1); + + const requests = mockHttpHandler.getRequests(); + expect(requests.length).toBeGreaterThan(0); + + const call = requests[0]; + expect(call.options.method).toBe('PUT'); + expect(call.options.headers['Custom-Header']).toBe('test-value'); + }); + + test('should handle HTTP batching edge case', async () => { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + batchSize: 5 // Large batch size to test single message handling + } + } + }); + + LogEngine.info('Single batch test message'); + + // Wait for the message to be sent + await mockHttpHandler.waitForRequests(1); + const requests = mockHttpHandler.getRequests(); + expect(requests.length).toBeGreaterThan(0); + }); + + test('should handle Node.js HTTP fallback when fetch is unavailable', async () => { + // Temporarily remove fetch to trigger fallback + const originalFetch = (global as any).fetch; + delete (global as any).fetch; + + try { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + batchSize: 1 + } + } + }); + + LogEngine.error('Fallback test message'); + + // Give some time for the fallback to attempt + await new Promise(resolve => setTimeout(resolve, 100)); + + // The request won't be captured by our mock in the fallback case, + // but we're testing that the code path executes without error + expect(true).toBe(true); // Test passes if no error is thrown + } finally { + // Restore fetch + (global as any).fetch = originalFetch; + } + }); + }); + + describe('Enhanced Output Targets', () => { + test('should support configured handler objects in enhancedOutputs', async () => { + const logFile = path.join(testDir, 'enhanced-test.log'); + const capturedLogs: any[] = []; + + LogEngine.configure({ + enhancedOutputs: [ + { + type: 'file', + config: { + filePath: logFile, + formatter: (level, message) => `FILE: ${level} - ${message}\\n` + } + }, + (level, message, data) => { + capturedLogs.push({ level, message, data }); + } + ], + suppressConsoleOutput: true + }); + + LogEngine.info('Enhanced output test', { enhanced: true }); + + // Check file output + expect(fs.existsSync(logFile)).toBe(true); + const fileContent = await fs.promises.readFile(logFile, 'utf8'); + expect(fileContent).toBe('FILE: info - Enhanced output test\\n'); + + // Check custom function output + expect(capturedLogs).toHaveLength(1); + expect(capturedLogs[0].level).toBe('info'); + expect(capturedLogs[0].message).toContain('Enhanced output test'); + expect(capturedLogs[0].data.enhanced).toBe(true); + }); + + test('should support mixed enhancedOutputs with built-in strings and objects', () => { + const logFile = path.join(testDir, 'mixed-enhanced.log'); + const customLogs: any[] = []; + + LogEngine.configure({ + enhancedOutputs: [ + 'silent', // Built-in string + { + type: 'file', + config: { filePath: logFile } + }, + (level, message) => customLogs.push({ level, message }) + ] + }); + + LogEngine.warn('Mixed enhanced test'); + + // Check file was created + expect(fs.existsSync(logFile)).toBe(true); + + // Check custom function was called + expect(customLogs).toHaveLength(1); + expect(customLogs[0].level).toBe('warn'); + }); + }); + + describe('Error Handling', () => { + let consoleErrorSpy: jest.SpyInstance; + let consoleLogSpy: jest.SpyInstance; + + beforeEach(() => { + // Suppress console.error and console.log during error handling tests + // to avoid confusing test output while still testing the functionality + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + consoleLogSpy.mockRestore(); + }); + + test('should handle file write errors gracefully', () => { + // Use a path that will definitely fail on both Unix and Windows + const invalidPath = process.platform === 'win32' + ? 'Z:\\definitely\\invalid\\path\\test.log' // Non-existent drive on Windows + : '/proc/1/impossible/path/test.log'; // Restricted path on Unix + + LogEngine.configure({ + outputs: ['file'], + advancedOutputConfig: { + file: { + filePath: invalidPath + } + } + }); + + LogEngine.info('This should fail to write to file'); + + // Verify that error handler was called (console.error should have been called) + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'File output handler failed:', + expect.any(Error) + ); + // Verify fallback logging was called + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('[INFO]'), + undefined + ); + }); + + test('should continue processing other outputs when one fails', () => { + const successfulLogs: any[] = []; + + // Use a path that will definitely fail on both Unix and Windows + const invalidPath = process.platform === 'win32' + ? 'Z:\\definitely\\invalid\\path\\test.log' // Non-existent drive on Windows + : '/proc/1/impossible/path/test.log'; // Restricted path on Unix + + LogEngine.configure({ + enhancedOutputs: [ + { + type: 'file', + config: { filePath: invalidPath } + }, + (level, message) => successfulLogs.push({ level, message }), + () => { + throw new Error('Handler failure'); + } + ] + }); + + LogEngine.error('Error handling test'); + + // Verify that error handlers were called (console.error should have been called twice) + expect(consoleErrorSpy).toHaveBeenCalledTimes(2); + + // Verify that the successful handler still worked + expect(successfulLogs).toHaveLength(1); + expect(successfulLogs[0].level).toBe('error'); + }); + }); + + describe('Backward Compatibility', () => { + test('should maintain compatibility with previous outputHandler', () => { + const logs: any[] = []; + + LogEngine.configure({ + outputHandler: (level, message, data) => { + logs.push({ level, message, data }); + } + // No outputs or enhancedOutputs configured, so outputHandler should be used + }); + + LogEngine.info('Backward compatibility test'); + + expect(logs).toHaveLength(1); + expect(logs[0].level).toBe('info'); + }); + + test('should maintain compatibility with previous outputs array', () => { + const logs: any[] = []; + + LogEngine.configure({ + outputs: [ + 'silent', + (level, message) => logs.push({ level, message }) + ], + // Should ignore this when outputs is present + enhancedOutputs: ['console'] + }); + + LogEngine.warn('Backward compatibility test'); + + expect(logs).toHaveLength(1); + expect(logs[0].level).toBe('warn'); + // Custom handlers receive the formatted message with colors + expect(logs[0].message).toContain('Backward compatibility test'); + expect(logs[0].message).toContain('[WARN]'); + }); + }); + + describe('Performance', () => { + test('should handle high-volume logging efficiently', async () => { + const logFile = path.join(testDir, 'performance-test.log'); + const startTime = Date.now(); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + append: true + } + } + }); + + // Log 1000 messages + for (let i = 0; i < 1000; i++) { + LogEngine.info(`Performance test message ${i}`, { iteration: i }); + } + + const endTime = Date.now(); + const duration = endTime - startTime; + + // Should complete in reasonable time (< 5 seconds) + expect(duration).toBeLessThan(5000); + + // Check file was created and has content + expect(fs.existsSync(logFile)).toBe(true); + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toContain('Performance test message 0'); + expect(content).toContain('Performance test message 999'); + }); + }); + + describe('Edge Cases and Error Handling', () => { + test('should handle file creation with non-existent directory', async () => { + const logFile = path.join(testDir, 'subdir', 'test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + append: false + } + } + }); + + LogEngine.info('Directory creation test'); + + // Wait for file to be created + await waitForFile(logFile, 2000); + + expect(fs.existsSync(logFile)).toBe(true); + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toContain('Directory creation test'); + }); + + test('should handle file rotation with maximum backup files', async () => { + const logFile = path.join(testDir, 'rotation-max-test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 100, // Very small size to trigger rotation + maxBackupFiles: 2, // Only keep 2 backup files + append: false + } + } + }); + + // Generate enough logs to cause multiple rotations + for (let i = 0; i < 25; i++) { + LogEngine.info(`Rotation test message ${i} with extra content to exceed size limit and trigger rotation`); + } + + // Wait for files to be created + await waitForFile(logFile, 3000); + + // Main file should exist + expect(fs.existsSync(logFile)).toBe(true); + + // Check that at least one backup file exists + const backup1 = `${logFile}.1`; + expect(fs.existsSync(backup1)).toBe(true); + + // The number of backup files depends on rotation timing, but should not exceed maxBackupFiles + const backup2 = `${logFile}.2`; + const backup3 = `${logFile}.3`; + + // backup3 should not exist due to maxBackupFiles limit of 2 + expect(fs.existsSync(backup3)).toBe(false); + }); + + test('should handle queue processing during rotation', async () => { + const logFile = path.join(testDir, 'queue-test.log'); + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 150, // Small size to trigger rotation + maxBackupFiles: 3, + append: false + } + } + }); + + // Rapidly log messages to test queue processing during rotation + for (let i = 0; i < 10; i++) { + LogEngine.info(`Queue test message ${i} with enough content to trigger rotation quickly`); + } + + await waitForFile(logFile, 3000); + + expect(fs.existsSync(logFile)).toBe(true); + const content = await fs.promises.readFile(logFile, 'utf8'); + expect(content).toContain('Queue test message'); + }); + + test('should handle createBuiltInHandler with invalid config', () => { + // Test error paths in createBuiltInHandler + const { createBuiltInHandler } = require('../logger/advanced-outputs'); + + // Test file handler without filePath + const fileHandler = createBuiltInHandler('file', {}); + expect(fileHandler).toBeNull(); + + // Test http handler without url + const httpHandler = createBuiltInHandler('http', {}); + expect(httpHandler).toBeNull(); + + // Test unknown handler type + const unknownHandler = createBuiltInHandler('unknown', {}); + expect(unknownHandler).toBeNull(); + }); + + test('should handle file write with non-string data in secure file functions', () => { + // Test error path in secureWriteFileSync + const { secureWriteFileSync } = require('../logger/advanced-outputs'); + const testFile = path.join(testDir, 'data-type-test.log'); + + // This should throw an error for non-string data + expect(() => { + secureWriteFileSync(testFile, 123 as any); + }).toThrow('Data must be a string for security'); + }); + + test('should handle secure file system path validation edge cases', () => { + const { secureStatSync } = require('../logger/advanced-outputs'); + + // Test empty/null path + expect(() => { + secureStatSync(''); + }).toThrow('File path must be a non-empty string'); + + expect(() => { + secureStatSync(null as any); + }).toThrow('File path must be a non-empty string'); + + // Test path traversal with different patterns + expect(() => { + secureStatSync('../../etc/passwd'); + }).toThrow('Path traversal detected'); + + // Test access to system directories + const systemPath = process.platform === 'win32' ? 'C:\\Windows\\system32\\test.log' : '/etc/passwd'; + expect(() => { + secureStatSync(systemPath); + }).toThrow('File path outside allowed directories'); + + // Test path outside safe directories + const outsidePath = process.platform === 'win32' ? 'C:\\Users\\test.log' : '/home/user/test.log'; + expect(() => { + secureStatSync(outsidePath); + }).toThrow('File path outside allowed directories'); + }); + + test('should handle secure file system unlinkSync with invalid path restrictions', () => { + const { secureUnlinkSync } = require('../logger/advanced-outputs'); + + // Try to delete a file outside log/temp directories - use a system directory + const invalidPath = process.platform === 'win32' ? 'C:\\Users\\test.log' : '/home/user/test.log'; + expect(() => { + secureUnlinkSync(invalidPath); + }).toThrow('File path outside allowed directories'); + }); + + test('should handle secure file system renameSync with invalid paths', () => { + const { secureRenameSync } = require('../logger/advanced-outputs'); + + const validLogPath = path.join(testDir, 'test.log'); + const invalidPath = process.platform === 'win32' ? 'C:\\Users\\test.log' : '/home/user/test.log'; + + // Try to rename with source outside allowed directories + expect(() => { + secureRenameSync(invalidPath, validLogPath); + }).toThrow('File path outside allowed directories'); + + // Try to rename with destination outside allowed directories + expect(() => { + secureRenameSync(validLogPath, invalidPath); + }).toThrow('File path outside allowed directories'); + }); + + test('should handle HTTP handler with non-HTTPS URL fallback', async () => { + // Mock require to simulate Node.js environment without fetch + const originalFetch = (global as any).fetch; + delete (global as any).fetch; + + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'http://insecure.example.com/logs', // HTTP instead of HTTPS + batchSize: 1 + } + } + }); + + LogEngine.error('HTTP security test'); + + // Wait a bit for the request to be processed + await new Promise(resolve => setTimeout(resolve, 100)); + + // Should have logged a security error + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'HTTP request setup failed:', + expect.objectContaining({ + message: expect.stringContaining('HTTP (cleartext) connections are not allowed') + }) + ); + } finally { + consoleErrorSpy.mockRestore(); + (global as any).fetch = originalFetch; + } + }); + + test('should handle HTTP handler timeout in Node.js fallback', async () => { + const originalFetch = (global as any).fetch; + const originalHttps = require('https'); + delete (global as any).fetch; + + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + // Mock https.request to simulate timeout + const mockReq: any = { + on: jest.fn((event: string, callback: () => void): any => { + if (event === 'timeout') { + setTimeout(callback, 10); // Trigger timeout quickly + } + return mockReq; + }), + write: jest.fn(), + end: jest.fn(), + destroy: jest.fn() + }; + + jest.spyOn(require('https'), 'request').mockImplementation(() => mockReq); + + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com/logs', + timeout: 50, + batchSize: 1 + } + } + }); + + LogEngine.error('Timeout test'); + + // Wait for timeout to trigger + await new Promise(resolve => setTimeout(resolve, 100)); + + expect(mockReq.destroy).toHaveBeenCalled(); + } finally { + consoleErrorSpy.mockRestore(); + (global as any).fetch = originalFetch; + jest.restoreAllMocks(); + } + }); + + test('should handle HTTP flush with empty buffer', () => { + const { HttpOutputHandler } = require('../logger/advanced-outputs'); + const handler = new HttpOutputHandler({ + url: 'https://api.example.com/logs' + }); + + // Calling flush with empty buffer should not cause errors + expect(() => { + handler.flush(); + }).not.toThrow(); + }); + + test('should handle file output with rotation edge cases', async () => { + const logFile = path.join(testDir, 'rotation-edge-test.log'); + + // Create handler with rotation disabled (maxFileSize = 0) + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 0, // No rotation + append: true + } + } + }); + + // Write multiple large messages - should not trigger rotation + for (let i = 0; i < 5; i++) { + LogEngine.info(`No rotation test ${i} with a lot of content that would normally trigger rotation if maxFileSize was set`); + } + + await waitForFile(logFile, 1000); + + // Should only have main file, no backups + expect(fs.existsSync(logFile)).toBe(true); + expect(fs.existsSync(`${logFile}.1`)).toBe(false); + }); + + test('should handle file rotation with existing backup files cleanup', async () => { + const logFile = path.join(testDir, 'cleanup-rotation-test.log'); + + // Pre-create backup files to test cleanup logic + await fs.promises.writeFile(`${logFile}.1`, 'old backup 1\n'); + await fs.promises.writeFile(`${logFile}.2`, 'old backup 2\n'); + await fs.promises.writeFile(`${logFile}.3`, 'old backup 3\n'); // This should be deleted + + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + maxFileSize: 80, // Small size to trigger rotation quickly + maxBackupFiles: 2, // Only keep 2 backups + append: false + } + } + }); + + // Write messages to trigger rotation + for (let i = 0; i < 10; i++) { + LogEngine.info(`Cleanup rotation test ${i} with enough content to trigger rotation`); + } + + await waitForFile(logFile, 2000); + + // The main goal is to test rotation logic, the exact number of backup files depends on timing + // So we'll check that main file exists and at least some rotation occurred + expect(fs.existsSync(logFile)).toBe(true); + + // Check that some backup rotation occurred - either .3 was deleted or rotation happened + const backup1Exists = fs.existsSync(`${logFile}.1`); + const backup2Exists = fs.existsSync(`${logFile}.2`); + const backup3Exists = fs.existsSync(`${logFile}.3`); + + // At least one backup should exist, indicating rotation occurred + expect(backup1Exists || backup2Exists).toBe(true); + }); + + test('should handle HTTP error paths in flush operation', () => { + const { HttpOutputHandler } = require('../logger/advanced-outputs'); + + // Create handler with formatter that throws + const handler = new HttpOutputHandler({ + url: 'https://api.example.com/logs', + formatter: () => { + throw new Error('Formatter error'); + } + }); + + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + // Add log entry and trigger flush + handler.write('error', 'test message'); + + // Force flush to trigger the error + handler.flush(); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'HTTP flush failed:', + expect.any(Error) + ); + } finally { + consoleErrorSpy.mockRestore(); + } + }); + + test('should handle file write error during non-append mode', async () => { + const logFile = path.join(testDir, 'write-error-test.log'); + + // Create the file as read-only to trigger write error + await fs.promises.writeFile(logFile, 'initial content'); + await fs.promises.chmod(logFile, 0o444); // Read-only + + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + try { + LogEngine.configure({ + outputs: ['file'], + suppressConsoleOutput: true, + advancedOutputConfig: { + file: { + filePath: logFile, + append: false // Write mode should fail on read-only file + } + } + }); + + LogEngine.info('This should fail to write'); + + // Should have logged the error and fallen back to console + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'File output handler failed:', + expect.any(Error) + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('[INFO]'), + undefined + ); + } finally { + consoleErrorSpy.mockRestore(); + consoleLogSpy.mockRestore(); + + // Restore write permissions for cleanup + try { + await fs.promises.chmod(logFile, 0o644); + } catch (error) { + // Ignore cleanup errors + } + } + }); + + test('should handle URL parsing edge cases in HTTP handler', async () => { + const originalFetch = (global as any).fetch; + delete (global as any).fetch; + + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + // Mock https.request to capture the parsed URL options + let capturedOptions: any = null; + const mockReq: any = { + on: jest.fn((): any => mockReq), + write: jest.fn(), + end: jest.fn() + }; + + jest.spyOn(require('https'), 'request').mockImplementation((options) => { + capturedOptions = options; + return mockReq; + }); + + LogEngine.configure({ + outputs: ['http'], + suppressConsoleOutput: true, + advancedOutputConfig: { + http: { + url: 'https://api.example.com:8443/logs?token=abc123', + batchSize: 1 + } + } + }); + + LogEngine.error('URL parsing test'); + + // Wait for the request to be processed + await new Promise(resolve => setTimeout(resolve, 100)); + + // Verify URL was parsed correctly + expect(capturedOptions).not.toBeNull(); + expect(capturedOptions.hostname).toBe('api.example.com'); + expect(capturedOptions.port).toBe(8443); + expect(capturedOptions.path).toBe('/logs?token=abc123'); + } finally { + consoleErrorSpy.mockRestore(); + (global as any).fetch = originalFetch; + jest.restoreAllMocks(); + } + }); + + test('should handle flush timeout scheduling edge cases', async () => { + const { HttpOutputHandler } = require('../logger/advanced-outputs'); + + // Mock global fetch for this test + const mockFetch = jest.fn().mockResolvedValue(new Response('{"success": true}')); + (global as any).fetch = mockFetch; + + try { + const handler = new HttpOutputHandler({ + url: 'https://api.example.com/logs', + batchSize: 5 // Large batch size + }); + + // Add one message (should schedule timeout) + handler.write('info', 'first message'); + + // Add another message (should not schedule new timeout) + handler.write('warn', 'second message'); + + // Wait for timeout to trigger flush + await new Promise(resolve => setTimeout(resolve, 1200)); + + // Verify fetch was called + expect(mockFetch).toHaveBeenCalled(); + } finally { + delete (global as any).fetch; + } + }); + }); +}); diff --git a/src/__tests__/async-test-utils.ts b/src/__tests__/async-test-utils.ts new file mode 100644 index 0000000..b57c8f0 --- /dev/null +++ b/src/__tests__/async-test-utils.ts @@ -0,0 +1,261 @@ +/** + * Test utilities for handling async file operations properly + * Replaces arbitrary timeouts with proper Promise-based waiting + * Optimized for CI environments with faster polling and shorter timeouts + */ +import * as fs from 'fs'; +import * as path from 'path'; + +// Optimized defaults for CI environments +const DEFAULT_TIMEOUT = 3000; // Reduced from 5000ms +const POLL_INTERVAL = 5; // Reduced from 10ms for faster detection + +/** + * Wait for a file to exist with optimized polling + */ +export async function waitForFile(filePath: string, timeoutMs: number = DEFAULT_TIMEOUT): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + try { + await fs.promises.access(filePath, fs.constants.F_OK); + return; // File exists + } catch (error) { + // File doesn't exist yet, wait a bit + await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); + } + } + + throw new Error(`File ${filePath} did not appear within ${timeoutMs}ms`); +} + +/** + * Wait for multiple files to exist with parallel checking + */ +export async function waitForFiles(filePaths: string[], timeoutMs: number = DEFAULT_TIMEOUT): Promise { + await Promise.all(filePaths.map(filePath => waitForFile(filePath, timeoutMs))); +} + +/** + * Wait for a file to have specific content with optimized polling + */ +export async function waitForFileContent(filePath: string, expectedContent: string | RegExp, timeoutMs: number = DEFAULT_TIMEOUT): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + try { + const content = await fs.promises.readFile(filePath, 'utf8'); + if (typeof expectedContent === 'string') { + if (content.includes(expectedContent)) { + return; + } + } else { + if (expectedContent.test(content)) { + return; + } + } + } catch (error) { + // File might not exist yet or be readable + } + + await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); + } + + throw new Error(`File ${filePath} did not contain expected content within ${timeoutMs}ms`); +} + +/** + * Wait for a directory to be empty with faster polling + */ +export async function waitForDirectoryEmpty(dirPath: string, timeoutMs: number = DEFAULT_TIMEOUT): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + try { + const files = await fs.promises.readdir(dirPath); + if (files.length === 0) { + return; + } + } catch (error) { + // Directory might not exist, which is also "empty" + return; + } + + await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); + } + + throw new Error(`Directory ${dirPath} was not empty within ${timeoutMs}ms`); +} + +/** + * Safely remove a file with optimized retry logic + */ +export async function safeRemoveFile(filePath: string, maxRetries: number = 3): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + await fs.promises.unlink(filePath); + return; + } catch (error) { + if (i === maxRetries - 1) { + // Only throw on last retry if it's not a "file not found" error + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error; + } + } else { + // Wait a bit before retrying (reduced wait time) + await new Promise(resolve => setTimeout(resolve, 10)); + } + } + } +} + +/** + * Safely clean up a directory with retry logic + */ +export async function safeCleanupDirectory(dirPath: string): Promise { + try { + const files = await fs.promises.readdir(dirPath); + + // Remove all files with retry logic + await Promise.all(files.map(file => + safeRemoveFile(path.join(dirPath, file)) + )); + + // Remove the directory itself + await fs.promises.rmdir(dirPath); + } catch (error) { + // Directory might not exist, which is fine + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + // Try force removal as fallback + try { + await fs.promises.rm(dirPath, { recursive: true, force: true }); + } catch (fallbackError) { + // Ignore cleanup errors in tests + } + } + } +} + +/** + * Enhanced mock HTTP handler with faster timeouts and better error handling + */ +export class MockHttpHandler { + private requests: Array<{ + url: string; + options: any; + resolve: () => void; + }> = []; + + private pendingPromises: Array> = []; + private timeoutIds: Set = new Set(); + + addRequest(url: string, options: any): void { + let resolveRequest: () => void; + const promise = new Promise(resolve => { + resolveRequest = resolve; + }); + + this.requests.push({ + url, + options, + resolve: resolveRequest! + }); + + this.pendingPromises.push(promise); + } + + getRequests() { + return this.requests.map(({ url, options }) => ({ url, options })); + } + + async waitForRequests(count: number = 1, timeoutMs: number = DEFAULT_TIMEOUT): Promise { + const startTime = Date.now(); + + // Set up a timeout that will be cleaned up + let timeoutId: NodeJS.Timeout | undefined; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(`Expected ${count} requests but got ${this.requests.length} within ${timeoutMs}ms`)); + }, timeoutMs); + this.timeoutIds.add(timeoutId); + }); + + try { + while (this.requests.length < count && Date.now() - startTime < timeoutMs) { + await new Promise(resolve => { + const id = setTimeout(resolve, POLL_INTERVAL); + this.timeoutIds.add(id); + }); + } + + if (this.requests.length < count) { + throw new Error(`Expected ${count} requests but got ${this.requests.length} within ${timeoutMs}ms`); + } + + // Mark all requests as processed + this.requests.forEach(req => req.resolve()); + + // Wait for all pending promises to resolve + await Promise.all(this.pendingPromises); + } finally { + // Clean up the timeout + if (timeoutId) { + clearTimeout(timeoutId); + this.timeoutIds.delete(timeoutId); + } + } + } + + clear(): void { + // Clean up any remaining timeouts + for (const timeoutId of this.timeoutIds) { + clearTimeout(timeoutId); + } + this.timeoutIds.clear(); + + this.requests = []; + this.pendingPromises = []; + } +} + +/** + * Create a test timeout that fails fast instead of hanging + */ +export function createTestTimeout(timeoutMs: number = DEFAULT_TIMEOUT): { promise: Promise, cancel: () => void } { + let timeoutId: NodeJS.Timeout; + const promise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(`Test timed out after ${timeoutMs}ms`)); + }, timeoutMs); + }); + + const cancel = () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + + return { promise, cancel }; +} + +/** + * Race a promise against a timeout for fail-fast behavior + */ +export async function withTimeout(promise: Promise, timeoutMs: number = DEFAULT_TIMEOUT): Promise { + const timeout = createTestTimeout(timeoutMs); + + try { + const result = await Promise.race([ + promise, + timeout.promise + ]); + + // Cancel the timeout since we got a result + timeout.cancel(); + return result; + } catch (error) { + // Cancel the timeout in case of error + timeout.cancel(); + throw error; + } +} diff --git a/src/__tests__/environment.test.ts b/src/__tests__/environment.test.ts index c94edb4..6d7ed74 100644 --- a/src/__tests__/environment.test.ts +++ b/src/__tests__/environment.test.ts @@ -77,15 +77,15 @@ describe('Environment-based configuration', () => { it('should set INFO mode for production environment', () => { // Test production environment auto-configuration (show important info and above) process.env.NODE_ENV = 'production'; - + // Re-import to trigger fresh environment-based configuration jest.resetModules(); const { LogEngine: ProdLogEngine } = require('../index'); - + ProdLogEngine.debug('Debug message'); ProdLogEngine.info('Info message'); ProdLogEngine.warn('Warning message'); - + // DEBUG should be filtered, INFO and above should show expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -97,12 +97,12 @@ describe('Environment-based configuration', () => { it('should set DEBUG mode for development environment', () => { // Test development environment auto-configuration (verbose logging for debugging) process.env.NODE_ENV = 'development'; - + jest.resetModules(); const { LogEngine: DevLogEngine } = require('../index'); - + DevLogEngine.debug('Debug message'); - + // DEBUG should be visible in development expect(mocks.mockConsoleLog).toHaveBeenCalledWith( expect.stringContaining('Debug message') @@ -112,14 +112,14 @@ describe('Environment-based configuration', () => { it('should set WARN mode for staging environment', () => { // Test staging environment auto-configuration (focused logging) process.env.NODE_ENV = 'staging'; - + jest.resetModules(); const { LogEngine: StagingLogEngine } = require('../index'); - + StagingLogEngine.debug('Debug message'); StagingLogEngine.info('Info message'); StagingLogEngine.warn('Warning message'); - + // Only WARN and above should show in staging expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); @@ -128,13 +128,13 @@ describe('Environment-based configuration', () => { it('should set ERROR mode for test environment', () => { // Test environment auto-configuration (minimal logging during tests) process.env.NODE_ENV = 'test'; - + jest.resetModules(); const { LogEngine: TestLogEngine } = require('../index'); - + TestLogEngine.warn('Warning message'); TestLogEngine.error('Error message'); - + // Only ERROR should show in test environment, WARN filtered out expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); @@ -143,13 +143,13 @@ describe('Environment-based configuration', () => { it('should set INFO mode for unknown environments', () => { // Test fallback behavior for unrecognized NODE_ENV values process.env.NODE_ENV = 'unknown'; - + jest.resetModules(); const { LogEngine: UnknownLogEngine } = require('../index'); - + UnknownLogEngine.debug('Debug message'); UnknownLogEngine.info('Info message'); - + // Should default to INFO mode (DEBUG filtered, INFO shown) expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -160,13 +160,13 @@ describe('Environment-based configuration', () => { it('should set INFO mode when NODE_ENV is undefined', () => { // Test fallback behavior when NODE_ENV is not set at all delete process.env.NODE_ENV; - + jest.resetModules(); const { LogEngine: DefaultLogEngine } = require('../index'); - + DefaultLogEngine.debug('Debug message'); DefaultLogEngine.info('Info message'); - + // Should default to INFO mode when NODE_ENV is undefined expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( diff --git a/src/__tests__/formatter.test.ts b/src/__tests__/formatter.test.ts index 8dfa41e..c29ebee 100644 --- a/src/__tests__/formatter.test.ts +++ b/src/__tests__/formatter.test.ts @@ -11,10 +11,10 @@ describe('LogFormatter', () => { it('should format messages with timestamp and level', () => { // Test complete message formatting with all components const formatted = LogFormatter.format(LogLevel.INFO, 'Test message'); - + // Remove ANSI color codes for pattern matching const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); - + // Verify format: [ISO_TIMESTAMP][LOCAL_TIME][LEVEL]: message expect(cleanFormatted).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]\[\d{1,2}:\d{2}[AP]M\]\[INFO\]: Test message$/); }); @@ -26,7 +26,7 @@ describe('LogFormatter', () => { const warnFormatted = LogFormatter.format(LogLevel.WARN, 'Warn test'); const errorFormatted = LogFormatter.format(LogLevel.ERROR, 'Error test'); const logFormatted = LogFormatter.format(LogLevel.LOG, 'LOG test'); - + // Remove ANSI color codes and verify each level appears correctly expect(debugFormatted.replace(/\x1b\[[0-9;]*m/g, '')).toContain('[DEBUG]'); expect(infoFormatted.replace(/\x1b\[[0-9;]*m/g, '')).toContain('[INFO]'); @@ -39,17 +39,17 @@ describe('LogFormatter', () => { // Test that custom messages are preserved in formatted output const message = 'Custom test message'; const formatted = LogFormatter.format(LogLevel.INFO, message); - + expect(formatted).toContain(message); }); it('should handle empty messages', () => { // Test edge case: empty message should still produce valid format const formatted = LogFormatter.format(LogLevel.INFO, ''); - + // Remove ANSI color codes for pattern matching const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); - + // Should have timestamps and level but empty message at end expect(cleanFormatted).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]\[\d{1,2}:\d{2}[AP]M\]\[INFO\]: $/); }); @@ -58,7 +58,7 @@ describe('LogFormatter', () => { // Test that special characters don't break formatting const message = 'Test with special chars: !@#$%^&*()'; const formatted = LogFormatter.format(LogLevel.INFO, message); - + expect(formatted).toContain(message); }); @@ -66,7 +66,7 @@ describe('LogFormatter', () => { // Test LOG level formatting with specific color const formatted = LogFormatter.format(LogLevel.LOG, 'LOG level message'); const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); - + expect(cleanFormatted).toContain('[LOG]'); expect(cleanFormatted).toContain('LOG level message'); // Verify green color code is applied (ANSI code 32) @@ -78,7 +78,7 @@ describe('LogFormatter', () => { // @ts-ignore - intentionally passing invalid enum value for testing const formatted = LogFormatter.format(999 as LogLevel, 'Unknown level message'); const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); - + expect(cleanFormatted).toContain('[UNKNOWN]'); expect(cleanFormatted).toContain('Unknown level message'); }); @@ -87,13 +87,13 @@ describe('LogFormatter', () => { it('should format system messages with [LOG ENGINE] prefix', () => { const message = 'This is a system message'; const formatted = LogFormatter.formatSystemMessage(message); - + // Should contain the LOG ENGINE prefix expect(formatted).toContain('[LOG ENGINE]'); - + // Should contain the message content expect(formatted).toContain(message); - + // Should contain timestamp components const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); expect(cleanFormatted).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/); // ISO timestamp @@ -103,11 +103,11 @@ describe('LogFormatter', () => { it('should format system messages with colors', () => { const message = 'Colored system message'; const formatted = LogFormatter.formatSystemMessage(message); - + // Should contain ANSI color codes expect(formatted).toContain('\x1b['); // ANSI escape sequence start expect(formatted).toContain('\x1b[0m'); // Reset color code - + // Should contain yellow color for LOG ENGINE prefix expect(formatted).toContain('\x1b[33m'); // Yellow color code }); @@ -115,10 +115,10 @@ describe('LogFormatter', () => { it('should maintain consistent format structure', () => { const message = 'Test message'; const formatted = LogFormatter.formatSystemMessage(message); - + // Remove ANSI color codes for pattern matching const cleanFormatted = formatted.replace(/\x1b\[[0-9;]*m/g, ''); - + // Should follow the format: [TIMESTAMP][TIME][LOG ENGINE]: message const formatPattern = /\[.*?\]\[.*?\]\[LOG ENGINE\]: Test message/; expect(cleanFormatted).toMatch(formatPattern); @@ -152,7 +152,7 @@ describe('LogFormatter', () => { // Test boolean handling (covers line 110) const formatted = LogFormatter.format(LogLevel.INFO, 'Message', true); expect(formatted).toContain('true'); - + const formatted2 = LogFormatter.format(LogLevel.INFO, 'Message', false); expect(formatted2).toContain('false'); }); @@ -161,7 +161,7 @@ describe('LogFormatter', () => { // Test JSON.stringify error handling (covers line 117) const circularObj: any = {}; circularObj.self = circularObj; // Create circular reference - + const formatted = LogFormatter.format(LogLevel.INFO, 'Message', circularObj); expect(formatted).toContain('[Object]'); }); @@ -223,7 +223,7 @@ describe('LogFormatter', () => { describe('Module exports', () => { it('should export all formatter functions and classes', () => { const formatter = require('../formatter'); - + // Test that all expected exports are available expect(formatter.MessageFormatter).toBeDefined(); expect(formatter.LogFormatter).toBeDefined(); // Backward compatibility @@ -233,10 +233,10 @@ describe('LogFormatter', () => { expect(formatter.formatTimestamp).toBeDefined(); expect(formatter.formatData).toBeDefined(); expect(formatter.styleData).toBeDefined(); - + // Test that LogFormatter is alias for MessageFormatter expect(formatter.LogFormatter).toBe(formatter.MessageFormatter); - + // Test that functions are callable expect(typeof formatter.getTimestampComponents).toBe('function'); expect(typeof formatter.formatTimestamp).toBe('function'); diff --git a/src/__tests__/integration.test.ts b/src/__tests__/integration.test.ts index f0d4496..c4fcbeb 100644 --- a/src/__tests__/integration.test.ts +++ b/src/__tests__/integration.test.ts @@ -22,12 +22,12 @@ describe('Integration tests', () => { it('should work end-to-end with multiple log levels', () => { // Test complete workflow with mixed log levels and INFO threshold LogEngine.configure({ level: LogLevel.INFO }); - + LogEngine.debug('Debug message'); // Should be filtered LogEngine.info('Info message'); // Should show LogEngine.warn('Warning message'); // Should show LogEngine.error('Error message'); // Should show - + // Verify correct number of calls to each console method expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); // info only expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); // warn only @@ -38,11 +38,11 @@ describe('Integration tests', () => { // Test that configuration changes take effect immediately LogEngine.configure({ mode: LogMode.DEBUG }); LogEngine.debug('Debug 1'); // Should show with DEBUG mode - + LogEngine.configure({ mode: LogMode.ERROR }); LogEngine.debug('Debug 2'); // Should be filtered with ERROR mode LogEngine.error('Error 1'); // Should show with ERROR mode - + // Verify only first debug and the error were logged expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); @@ -51,13 +51,13 @@ describe('Integration tests', () => { it('should maintain state across multiple method calls', () => { // Test that configuration persists across many logging calls LogEngine.configure({ level: LogLevel.WARN }); - + // Simulate burst logging with mixed levels for (let i = 0; i < 5; i++) { LogEngine.info(`Info ${i}`); // All should be filtered LogEngine.warn(`Warning ${i}`); // All should show } - + // Verify filtering consistency across multiple calls expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); // No info messages expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(5); // All warning messages diff --git a/src/__tests__/log-engine.test.ts b/src/__tests__/log-engine.test.ts index e6cffa8..bbdf494 100644 --- a/src/__tests__/log-engine.test.ts +++ b/src/__tests__/log-engine.test.ts @@ -12,9 +12,14 @@ describe('LogEngine', () => { beforeEach(() => { // Set up console mocks to capture log output mocks = setupConsoleMocks(); - + // Reset LogEngine to consistent default state for each test using new mode API - LogEngine.configure({ mode: LogMode.INFO }); + // Clear any output handler and console suppression from previous tests + LogEngine.configure({ + mode: LogMode.INFO, + outputHandler: undefined, + suppressConsoleOutput: undefined + }); }); afterEach(() => { @@ -27,7 +32,7 @@ describe('LogEngine', () => { // Test that DEBUG mode enables debug message output LogEngine.configure({ mode: LogMode.DEBUG }); LogEngine.debug('Debug message'); - + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( expect.stringContaining('Debug message') @@ -37,72 +42,72 @@ describe('LogEngine', () => { it('should log info messages when mode is INFO or lower', () => { // Test that INFO mode shows info messages LogEngine.configure({ mode: LogMode.INFO }); - LogEngine.info('Info message'); - - expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mocks.mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Info message') - ); - }); + LogEngine.info('Info message'); + + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Info message') + ); + }); it('should log warn messages when mode is WARN or lower', () => { // Test that WARN mode shows warning messages using console.warn LogEngine.configure({ mode: LogMode.WARN }); - LogEngine.warn('Warning message'); - - expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); - expect(mocks.mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('Warning message') - ); - }); + LogEngine.warn('Warning message'); + + expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('Warning message') + ); + }); it('should log error messages when mode is ERROR or lower', () => { // Test that ERROR mode shows error messages using console.error LogEngine.configure({ mode: LogMode.ERROR }); - LogEngine.error('Error message'); - - expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); - expect(mocks.mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('Error message') - ); - }); - -it('should log LOG level messages regardless of configuration', () => { - // Test that LOG level always outputs with console.log + LogEngine.error('Error message'); + + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Error message') + ); + }); + + it('should log LOG level messages regardless of configuration', () => { + // Test that LOG level always outputs with console.log LogEngine.configure({ mode: LogMode.ERROR }); - LogEngine.log('LOG level message'); - - expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mocks.mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('LOG level message') - ); - }); + LogEngine.log('LOG level message'); + + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('LOG level message') + ); + }); }); describe('Log level filtering', () => { it('should not log debug messages when mode is INFO', () => { - // Test that higher log levels filter out lower priority messages + // Test that higher log levels filter out lower priority messages LogEngine.configure({ mode: LogMode.INFO }); - LogEngine.debug('Debug message'); - - expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); - }); + LogEngine.debug('Debug message'); + + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + }); it('should not log info messages when mode is WARN', () => { // Test that WARN mode filters out INFO messages LogEngine.configure({ mode: LogMode.WARN }); - LogEngine.info('Info message'); - - expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); - }); + LogEngine.info('Info message'); + + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + }); it('should not log warn messages when mode is ERROR', () => { // Test that ERROR mode filters out WARN messages LogEngine.configure({ mode: LogMode.ERROR }); - LogEngine.warn('Warning message'); - - expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); - }); + LogEngine.warn('Warning message'); + + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); it('should not log any messages when mode is SILENT', () => { // Test that SILENT mode completely disables all logging except LOG @@ -111,7 +116,7 @@ it('should log LOG level messages regardless of configuration', () => { LogEngine.info('Info message'); LogEngine.warn('Warning message'); LogEngine.error('Error message'); - + // No console methods should be called with SILENT mode (except LOG) expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); @@ -122,11 +127,11 @@ it('should log LOG level messages regardless of configuration', () => { // Test that LOG level bypasses SILENT configuration LogEngine.configure({ mode: LogMode.SILENT }); LogEngine.debug('Debug message'); - LogEngine.info('Info message'); + LogEngine.info('Info message'); LogEngine.warn('Warning message'); LogEngine.error('Error message'); LogEngine.log('LOG level message'); - + // Only LOG should be visible expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -140,11 +145,11 @@ it('should log LOG level messages regardless of configuration', () => { // Test that OFF mode completely disables all logging including LOG LogEngine.configure({ mode: LogMode.OFF }); LogEngine.debug('Debug message'); - LogEngine.info('Info message'); + LogEngine.info('Info message'); LogEngine.warn('Warning message'); LogEngine.error('Error message'); LogEngine.log('LOG level message'); - + // No console methods should be called with OFF level (including LOG) expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); @@ -157,7 +162,7 @@ it('should log LOG level messages regardless of configuration', () => { // Test that partial config updates work (only changing mode) LogEngine.configure({ mode: LogMode.DEBUG }); LogEngine.debug('Debug message'); - + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( expect.stringContaining('Debug message') ); @@ -168,7 +173,7 @@ it('should log LOG level messages regardless of configuration', () => { LogEngine.configure({ mode: LogMode.ERROR }); LogEngine.info('Should not appear'); LogEngine.error('Should appear'); - + // Only ERROR should be logged based on configuration expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); @@ -183,7 +188,7 @@ it('should log LOG level messages regardless of configuration', () => { LogEngine.info('Info message'); LogEngine.warn('Warning message'); LogEngine.error('Error message'); - + // Only WARN and ERROR should be logged when level is WARN expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); @@ -198,7 +203,7 @@ it('should log LOG level messages regardless of configuration', () => { LogEngine.warn('Warning message'); LogEngine.error('Error message'); LogEngine.log('LOG level message'); - + // Only LOG should be visible with SILENT mode expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -216,7 +221,7 @@ it('should log LOG level messages regardless of configuration', () => { LogEngine.warn('Warning message'); LogEngine.error('Error message'); LogEngine.log('LOG level message'); - + // No messages should be visible with OFF mode expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); @@ -227,12 +232,12 @@ it('should log LOG level messages regardless of configuration', () => { describe('Main module exports', () => { it('should export LogEngine and all types', () => { const main = require('../index'); - + // Test that main exports are available expect(main.LogEngine).toBeDefined(); expect(main.LogLevel).toBeDefined(); expect(main.LogMode).toBeDefined(); - + // Test that LogEngine has expected methods expect(typeof main.LogEngine.debug).toBe('function'); expect(typeof main.LogEngine.info).toBe('function'); @@ -244,7 +249,7 @@ it('should log LOG level messages regardless of configuration', () => { it('should test withoutRedaction method', () => { const main = require('../index'); const withoutRedaction = main.LogEngine.withoutRedaction(); - + // Test that withoutRedaction returns an object with logging methods expect(withoutRedaction).toBeDefined(); expect(typeof withoutRedaction.debug).toBe('function'); @@ -254,6 +259,154 @@ it('should log LOG level messages regardless of configuration', () => { expect(typeof withoutRedaction.log).toBe('function'); }); }); + + describe('Output Handler API', () => { + it('should support custom output handler configuration', () => { + const capturedLogs: Array<{ level: string; message: string }> = []; + + LogEngine.configure({ + mode: LogMode.DEBUG, + outputHandler: (level, message) => { + capturedLogs.push({ level, message }); + } + }); + + LogEngine.info('Test message'); + LogEngine.error('Error message'); + + expect(capturedLogs).toHaveLength(2); + expect(capturedLogs[0]).toMatchObject({ + level: 'info', + message: expect.stringContaining('Test message') + }); + expect(capturedLogs[1]).toMatchObject({ + level: 'error', + message: expect.stringContaining('Error message') + }); + + // Console should not be called + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + }); + + it('should support console suppression', () => { + LogEngine.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: true + }); + + LogEngine.info('Suppressed message'); + LogEngine.error('Suppressed error'); + + // Nothing should be logged to console + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should maintain backward compatibility when no output options are used', () => { + LogEngine.configure({ mode: LogMode.DEBUG }); + + LogEngine.info('Regular message'); + + // Should work exactly as before + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Regular message') + ); + }); + }); + + describe('Multiple Outputs API', () => { + it('should support multiple output targets', () => { + const capturedLogs1: Array<{ level: string; message: string }> = []; + const capturedLogs2: Array<{ level: string; message: string }> = []; + + LogEngine.configure({ + mode: LogMode.DEBUG, + outputs: [ + 'console', + (level, message) => capturedLogs1.push({ level, message }), + (level, message) => capturedLogs2.push({ level, message }) + ] + }); + + LogEngine.info('Multi-output test'); + LogEngine.error('Multi-output error'); + + // Console should be called + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + + // Both custom handlers should capture logs + expect(capturedLogs1).toHaveLength(2); + expect(capturedLogs2).toHaveLength(2); + + expect(capturedLogs1[0]).toMatchObject({ + level: 'info', + message: expect.stringContaining('Multi-output test') + }); + expect(capturedLogs1[1]).toMatchObject({ + level: 'error', + message: expect.stringContaining('Multi-output error') + }); + }); + + it('should support built-in handlers', () => { + LogEngine.configure({ + mode: LogMode.DEBUG, + outputs: ['console', 'silent'] + }); + + LogEngine.info('Built-in test'); + + // Console should work, silent should do nothing + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Built-in test') + ); + }); + + it('should prioritize outputs over single outputHandler', () => { + const singleHandler = jest.fn(); + const multiHandler = jest.fn(); + + LogEngine.configure({ + mode: LogMode.DEBUG, + outputHandler: singleHandler, + outputs: [multiHandler] + }); + + LogEngine.info('Priority test'); + + // Only outputs array should be used + expect(multiHandler).toHaveBeenCalledTimes(1); + expect(singleHandler).not.toHaveBeenCalled(); + }); + + it('should maintain compatibility with console + file + network pattern', () => { + const fileLogs: string[] = []; + const networkCalls: number[] = []; + + LogEngine.configure({ + mode: LogMode.DEBUG, + outputs: [ + 'console', + (level, message) => fileLogs.push(`${level}: ${message}`), + (level) => networkCalls.push(Date.now()) + ] + }); + + LogEngine.info('Pattern test'); + LogEngine.warn('Pattern warning'); + + // All three outputs should work + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); + expect(fileLogs).toHaveLength(2); + expect(networkCalls).toHaveLength(2); + }); + }); }); diff --git a/src/__tests__/logengine-api.test.ts b/src/__tests__/logengine-api.test.ts index 06374f6..159dc23 100644 --- a/src/__tests__/logengine-api.test.ts +++ b/src/__tests__/logengine-api.test.ts @@ -1,5 +1,5 @@ /** - * LogEngine API tests for Phase 4 - comprehensive interface coverage + * LogEngine API tests - comprehensive interface coverage * Tests all public API methods with TypeScript type safety */ @@ -12,393 +12,393 @@ const mockConsoleInfo = jest.fn(); const mockConsoleWarn = jest.fn(); const mockConsoleError = jest.fn(); -describe('LogEngine API Interface (Phase 4)', () => { - const originalConsole = console; - const originalEnv = process.env; +describe('LogEngine API Interface', () => { + const originalConsole = console; + const originalEnv = process.env; + + beforeEach(() => { + // Mock console methods + console.log = mockConsoleLog; + console.info = mockConsoleInfo; + console.warn = mockConsoleWarn; + console.error = mockConsoleError; + + // Clear all mocks + jest.clearAllMocks(); + + // Reset environment + process.env = { ...originalEnv }; + + // Reset LogEngine to default state + LogEngine.configure({ mode: LogMode.DEBUG }); + LogEngine.resetRedactionConfig(); + }); + + afterAll(() => { + console.log = originalConsole.log; + console.info = originalConsole.info; + console.warn = originalConsole.warn; + console.error = originalConsole.error; + process.env = originalEnv; + }); + + describe('Configuration API', () => { + test('should configure logger mode', () => { + LogEngine.configure({ mode: LogMode.ERROR }); + + // Test that only error level shows + LogEngine.info('info message'); + LogEngine.warn('warn message'); + LogEngine.error('error message'); + + expect(mockConsoleInfo).not.toHaveBeenCalled(); + expect(mockConsoleWarn).not.toHaveBeenCalled(); + expect(mockConsoleError).toHaveBeenCalledTimes(1); + }); + + test('should configure environment', () => { + LogEngine.configure({ environment: 'test', mode: LogMode.DEBUG }); + + LogEngine.info('test message'); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('test message') + ); + }); + }); + + describe('Standard Logging Methods', () => { + test('should log debug messages with redaction', () => { + LogEngine.configure({ mode: LogMode.DEBUG }); + + LogEngine.debug('Debug message', { password: 'secret123', username: 'user' }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Debug message') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('user') + ); + }); + + test('should log info messages with redaction', () => { + LogEngine.info('Info message', { email: 'user@example.com', id: 123 }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Info message') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('123') + ); + }); + + test('should log warn messages with redaction', () => { + LogEngine.warn('Warning message', { apiKey: 'sk-123', status: 'failed' }); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('Warning message') + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('failed') + ); + }); + + test('should log error messages with redaction', () => { + LogEngine.error('Error message', { token: 'jwt-token', code: 500 }); + + expect(mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Error message') + ); + expect(mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + expect(mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('500') + ); + }); + + test('should log general messages with redaction', () => { + LogEngine.log('General message', { secret: 'hidden', visible: 'shown' }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('General message') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('shown') + ); + }); + }); + + describe('Raw Logging Methods', () => { + test('should log debug messages without redaction', () => { + LogEngine.debugRaw('Debug raw', { password: 'secret123', username: 'user' }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Debug raw') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('secret123') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('user') + ); + }); - beforeEach(() => { - // Mock console methods - console.log = mockConsoleLog; - console.info = mockConsoleInfo; - console.warn = mockConsoleWarn; - console.error = mockConsoleError; + test('should log info messages without redaction', () => { + LogEngine.infoRaw('Info raw', { email: 'user@example.com', id: 123 }); - // Clear all mocks - jest.clearAllMocks(); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Info raw') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('user@example.com') + ); + }); - // Reset environment - process.env = { ...originalEnv }; + test('should log warn messages without redaction', () => { + LogEngine.warnRaw('Warn raw', { apiKey: 'sk-123', status: 'failed' }); - // Reset LogEngine to default state - LogEngine.configure({ mode: LogMode.DEBUG }); - LogEngine.resetRedactionConfig(); + expect(mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('Warn raw') + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('sk-123') + ); }); - afterAll(() => { - console.log = originalConsole.log; - console.info = originalConsole.info; - console.warn = originalConsole.warn; - console.error = originalConsole.error; - process.env = originalEnv; + test('should log error messages without redaction', () => { + LogEngine.errorRaw('Error raw', { token: 'jwt-token', code: 500 }); + + expect(mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Error raw') + ); + expect(mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('jwt-token') + ); }); - describe('Configuration API', () => { - test('should configure logger mode', () => { - LogEngine.configure({ mode: LogMode.ERROR }); - - // Test that only error level shows - LogEngine.info('info message'); - LogEngine.warn('warn message'); - LogEngine.error('error message'); - - expect(mockConsoleInfo).not.toHaveBeenCalled(); - expect(mockConsoleWarn).not.toHaveBeenCalled(); - expect(mockConsoleError).toHaveBeenCalledTimes(1); - }); - - test('should configure environment', () => { - LogEngine.configure({ environment: 'test', mode: LogMode.DEBUG }); - - LogEngine.info('test message'); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('test message') - ); - }); + test('should log general messages without redaction', () => { + LogEngine.logRaw('Log raw', { secret: 'hidden', visible: 'shown' }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Log raw') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('hidden') + ); }); + }); + + describe('Redaction Configuration API', () => { + test('should configure redaction settings', () => { + const customConfig: Partial = { + redactionText: '***HIDDEN***', + sensitiveFields: ['customField'] + }; + + LogEngine.configureRedaction(customConfig); - describe('Standard Logging Methods', () => { - test('should log debug messages with redaction', () => { - LogEngine.configure({ mode: LogMode.DEBUG }); - - LogEngine.debug('Debug message', { password: 'secret123', username: 'user' }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Debug message') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('user') - ); - }); - - test('should log info messages with redaction', () => { - LogEngine.info('Info message', { email: 'user@example.com', id: 123 }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Info message') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('123') - ); - }); - - test('should log warn messages with redaction', () => { - LogEngine.warn('Warning message', { apiKey: 'sk-123', status: 'failed' }); - - expect(mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('Warning message') - ); - expect(mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - expect(mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('failed') - ); - }); - - test('should log error messages with redaction', () => { - LogEngine.error('Error message', { token: 'jwt-token', code: 500 }); - - expect(mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('Error message') - ); - expect(mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - expect(mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('500') - ); - }); - - test('should log general messages with redaction', () => { - LogEngine.log('General message', { secret: 'hidden', visible: 'shown' }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('General message') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('shown') - ); - }); + const config = LogEngine.getRedactionConfig(); + expect(config.redactionText).toBe('***HIDDEN***'); + expect(config.sensitiveFields).toContain('customField'); }); - describe('Raw Logging Methods', () => { - test('should log debug messages without redaction', () => { - LogEngine.debugRaw('Debug raw', { password: 'secret123', username: 'user' }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Debug raw') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('secret123') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('user') - ); - }); - - test('should log info messages without redaction', () => { - LogEngine.infoRaw('Info raw', { email: 'user@example.com', id: 123 }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Info raw') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('user@example.com') - ); - }); - - test('should log warn messages without redaction', () => { - LogEngine.warnRaw('Warn raw', { apiKey: 'sk-123', status: 'failed' }); - - expect(mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('Warn raw') - ); - expect(mockConsoleWarn).toHaveBeenCalledWith( - expect.stringContaining('sk-123') - ); - }); - - test('should log error messages without redaction', () => { - LogEngine.errorRaw('Error raw', { token: 'jwt-token', code: 500 }); - - expect(mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('Error raw') - ); - expect(mockConsoleError).toHaveBeenCalledWith( - expect.stringContaining('jwt-token') - ); - }); - - test('should log general messages without redaction', () => { - LogEngine.logRaw('Log raw', { secret: 'hidden', visible: 'shown' }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('Log raw') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('hidden') - ); - }); + test('should reset redaction configuration', () => { + // Modify configuration + LogEngine.configureRedaction({ redactionText: '***CUSTOM***' }); + expect(LogEngine.getRedactionConfig().redactionText).toBe('***CUSTOM***'); + + // Reset configuration + LogEngine.resetRedactionConfig(); + expect(LogEngine.getRedactionConfig().redactionText).toBe('[REDACTED]'); }); - describe('Redaction Configuration API', () => { - test('should configure redaction settings', () => { - const customConfig: Partial = { - redactionText: '***HIDDEN***', - sensitiveFields: ['customField'] - }; - - LogEngine.configureRedaction(customConfig); - - const config = LogEngine.getRedactionConfig(); - expect(config.redactionText).toBe('***HIDDEN***'); - expect(config.sensitiveFields).toContain('customField'); - }); - - test('should reset redaction configuration', () => { - // Modify configuration - LogEngine.configureRedaction({ redactionText: '***CUSTOM***' }); - expect(LogEngine.getRedactionConfig().redactionText).toBe('***CUSTOM***'); - - // Reset configuration - LogEngine.resetRedactionConfig(); - expect(LogEngine.getRedactionConfig().redactionText).toBe('[REDACTED]'); - }); - - test('should refresh redaction configuration from environment', () => { - process.env.LOG_REDACTION_TEXT = '***ENV***'; - process.env.LOG_SENSITIVE_FIELDS = 'envField1,envField2'; - - LogEngine.refreshRedactionConfig(); - - const config = LogEngine.getRedactionConfig(); - expect(config.redactionText).toBe('***ENV***'); - expect(config.sensitiveFields).toContain('envField1'); - expect(config.sensitiveFields).toContain('envField2'); - }); - - test('should get current redaction configuration', () => { - const config = LogEngine.getRedactionConfig(); - - expect(config).toHaveProperty('enabled'); - expect(config).toHaveProperty('sensitiveFields'); - expect(config).toHaveProperty('redactionText'); - expect(config).toHaveProperty('deepRedaction'); - expect(typeof config.enabled).toBe('boolean'); - expect(Array.isArray(config.sensitiveFields)).toBe(true); - }); + test('should refresh redaction configuration from environment', () => { + process.env.LOG_REDACTION_TEXT = '***ENV***'; + process.env.LOG_SENSITIVE_FIELDS = 'envField1,envField2'; + + LogEngine.refreshRedactionConfig(); + + const config = LogEngine.getRedactionConfig(); + expect(config.redactionText).toBe('***ENV***'); + expect(config.sensitiveFields).toContain('envField1'); + expect(config.sensitiveFields).toContain('envField2'); }); - describe('Advanced Redaction API', () => { - test('should add custom redaction patterns', () => { - const patterns = [/custom.*/i, /special.*/i]; - LogEngine.addCustomRedactionPatterns(patterns); - - expect(LogEngine.testFieldRedaction('customField')).toBe(true); - expect(LogEngine.testFieldRedaction('specialData')).toBe(true); - expect(LogEngine.testFieldRedaction('normalField')).toBe(false); - }); - - test('should clear custom redaction patterns', () => { - LogEngine.addCustomRedactionPatterns([/test.*/i]); - expect(LogEngine.testFieldRedaction('testField')).toBe(true); - - LogEngine.clearCustomRedactionPatterns(); - expect(LogEngine.testFieldRedaction('testField')).toBe(false); - }); - - test('should add sensitive fields', () => { - LogEngine.addSensitiveFields(['companySecret', 'internalKey']); - - expect(LogEngine.testFieldRedaction('companySecret')).toBe(true); - expect(LogEngine.testFieldRedaction('internalKey')).toBe(true); - }); - - test('should test field redaction accurately', () => { - // Test default sensitive fields - expect(LogEngine.testFieldRedaction('password')).toBe(true); - expect(LogEngine.testFieldRedaction('email')).toBe(true); - expect(LogEngine.testFieldRedaction('username')).toBe(false); - expect(LogEngine.testFieldRedaction('id')).toBe(false); - - // Test custom fields - LogEngine.addSensitiveFields(['myCustomField']); - expect(LogEngine.testFieldRedaction('myCustomField')).toBe(true); - }); + test('should get current redaction configuration', () => { + const config = LogEngine.getRedactionConfig(); + + expect(config).toHaveProperty('enabled'); + expect(config).toHaveProperty('sensitiveFields'); + expect(config).toHaveProperty('redactionText'); + expect(config).toHaveProperty('deepRedaction'); + expect(typeof config.enabled).toBe('boolean'); + expect(Array.isArray(config.sensitiveFields)).toBe(true); }); + }); + + describe('Advanced Redaction API', () => { + test('should add custom redaction patterns', () => { + const patterns = [/custom.*/i, /special.*/i]; + LogEngine.addCustomRedactionPatterns(patterns); + + expect(LogEngine.testFieldRedaction('customField')).toBe(true); + expect(LogEngine.testFieldRedaction('specialData')).toBe(true); + expect(LogEngine.testFieldRedaction('normalField')).toBe(false); + }); + + test('should clear custom redaction patterns', () => { + LogEngine.addCustomRedactionPatterns([/test.*/i]); + expect(LogEngine.testFieldRedaction('testField')).toBe(true); + + LogEngine.clearCustomRedactionPatterns(); + expect(LogEngine.testFieldRedaction('testField')).toBe(false); + }); + + test('should add sensitive fields', () => { + LogEngine.addSensitiveFields(['companySecret', 'internalKey']); + + expect(LogEngine.testFieldRedaction('companySecret')).toBe(true); + expect(LogEngine.testFieldRedaction('internalKey')).toBe(true); + }); + + test('should test field redaction accurately', () => { + // Test default sensitive fields + expect(LogEngine.testFieldRedaction('password')).toBe(true); + expect(LogEngine.testFieldRedaction('email')).toBe(true); + expect(LogEngine.testFieldRedaction('username')).toBe(false); + expect(LogEngine.testFieldRedaction('id')).toBe(false); + + // Test custom fields + LogEngine.addSensitiveFields(['myCustomField']); + expect(LogEngine.testFieldRedaction('myCustomField')).toBe(true); + }); + }); + + describe('withoutRedaction() Method', () => { + test('should return object with raw logging methods', () => { + const rawLogger = LogEngine.withoutRedaction(); + + expect(rawLogger).toHaveProperty('debug'); + expect(rawLogger).toHaveProperty('info'); + expect(rawLogger).toHaveProperty('warn'); + expect(rawLogger).toHaveProperty('error'); + expect(rawLogger).toHaveProperty('log'); + + expect(typeof rawLogger.debug).toBe('function'); + expect(typeof rawLogger.info).toBe('function'); + expect(typeof rawLogger.warn).toBe('function'); + expect(typeof rawLogger.error).toBe('function'); + expect(typeof rawLogger.log).toBe('function'); + }); + + test('should bypass redaction when using withoutRedaction', () => { + const rawLogger = LogEngine.withoutRedaction(); + + rawLogger.info('Raw info', { password: 'secret123', email: 'user@example.com' }); + + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('secret123') + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('user@example.com') + ); + expect(mockConsoleLog).not.toHaveBeenCalledWith( + expect.stringContaining('[REDACTED]') + ); + }); + + test('should support all log levels without redaction', () => { + const rawLogger = LogEngine.withoutRedaction(); + + rawLogger.debug('Debug', { secret: 'debug-secret' }); + rawLogger.info('Info', { secret: 'info-secret' }); + rawLogger.warn('Warn', { secret: 'warn-secret' }); + rawLogger.error('Error', { secret: 'error-secret' }); + rawLogger.log('Log', { secret: 'log-secret' }); + + expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('debug-secret')); + expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('info-secret')); + expect(mockConsoleWarn).toHaveBeenCalledWith(expect.stringContaining('warn-secret')); + expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('error-secret')); + expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('log-secret')); + }); + }); + + describe('Integration Testing', () => { + test('should maintain consistent behavior across configuration changes', () => { + // Initial test + LogEngine.info('Test 1', { password: 'secret1' }); + expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('[REDACTED]')); + + jest.clearAllMocks(); + + // Change redaction text + LogEngine.configureRedaction({ redactionText: '***HIDDEN***' }); + LogEngine.info('Test 2', { password: 'secret2' }); + expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('***HIDDEN***')); + + jest.clearAllMocks(); + + // Add custom sensitive field and test with multiple sensitive fields + LogEngine.addSensitiveFields(['customSensitive']); + LogEngine.info('Test 3', { customSensitive: 'value', email: 'test@example.com' }); + + // Should contain redaction text for both fields + const calls = mockConsoleLog.mock.calls.flat(); + const logOutput = calls.join(' '); - describe('withoutRedaction() Method', () => { - test('should return object with raw logging methods', () => { - const rawLogger = LogEngine.withoutRedaction(); - - expect(rawLogger).toHaveProperty('debug'); - expect(rawLogger).toHaveProperty('info'); - expect(rawLogger).toHaveProperty('warn'); - expect(rawLogger).toHaveProperty('error'); - expect(rawLogger).toHaveProperty('log'); - - expect(typeof rawLogger.debug).toBe('function'); - expect(typeof rawLogger.info).toBe('function'); - expect(typeof rawLogger.warn).toBe('function'); - expect(typeof rawLogger.error).toBe('function'); - expect(typeof rawLogger.log).toBe('function'); - }); - - test('should bypass redaction when using withoutRedaction', () => { - const rawLogger = LogEngine.withoutRedaction(); - - rawLogger.info('Raw info', { password: 'secret123', email: 'user@example.com' }); - - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('secret123') - ); - expect(mockConsoleLog).toHaveBeenCalledWith( - expect.stringContaining('user@example.com') - ); - expect(mockConsoleLog).not.toHaveBeenCalledWith( - expect.stringContaining('[REDACTED]') - ); - }); - - test('should support all log levels without redaction', () => { - const rawLogger = LogEngine.withoutRedaction(); - - rawLogger.debug('Debug', { secret: 'debug-secret' }); - rawLogger.info('Info', { secret: 'info-secret' }); - rawLogger.warn('Warn', { secret: 'warn-secret' }); - rawLogger.error('Error', { secret: 'error-secret' }); - rawLogger.log('Log', { secret: 'log-secret' }); - - expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('debug-secret')); - expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('info-secret')); - expect(mockConsoleWarn).toHaveBeenCalledWith(expect.stringContaining('warn-secret')); - expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('error-secret')); - expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('log-secret')); - }); + // Count occurrences of redaction text + const redactionMatches = logOutput.match(/\*\*\*HIDDEN\*\*\*/g); + expect(redactionMatches).toBeTruthy(); + expect(redactionMatches!.length).toBeGreaterThanOrEqual(2); }); - describe('Integration Testing', () => { - test('should maintain consistent behavior across configuration changes', () => { - // Initial test - LogEngine.info('Test 1', { password: 'secret1' }); - expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('[REDACTED]')); - - jest.clearAllMocks(); - - // Change redaction text - LogEngine.configureRedaction({ redactionText: '***HIDDEN***' }); - LogEngine.info('Test 2', { password: 'secret2' }); - expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('***HIDDEN***')); - - jest.clearAllMocks(); - - // Add custom sensitive field and test with multiple sensitive fields - LogEngine.addSensitiveFields(['customSensitive']); - LogEngine.info('Test 3', { customSensitive: 'value', email: 'test@example.com' }); - - // Should contain redaction text for both fields - const calls = mockConsoleLog.mock.calls.flat(); - const logOutput = calls.join(' '); - - // Count occurrences of redaction text - const redactionMatches = logOutput.match(/\*\*\*HIDDEN\*\*\*/g); - expect(redactionMatches).toBeTruthy(); - expect(redactionMatches!.length).toBeGreaterThanOrEqual(2); - }); - - test('should handle complex configuration scenarios', () => { - // First, reset to known state - LogEngine.resetRedactionConfig(); - LogEngine.clearCustomRedactionPatterns(); - - // Configure multiple aspects - LogEngine.configureRedaction({ - redactionText: '###SECRET###', - sensitiveFields: ['baseField'] // This will be merged with defaults - }); - LogEngine.addCustomRedactionPatterns([/^custom.*/i]); - LogEngine.addSensitiveFields(['additionalField']); - - const testData = { - normalPassword: 'default-sensitive', // This will be redacted (password is default sensitive) - baseField: 'base-sensitive', - customField: 'pattern-sensitive', - additionalField: 'added-sensitive', - publicField: 'not-sensitive' - }; - - LogEngine.info('Complex test', testData); - - const logOutput = mockConsoleLog.mock.calls.flat().join(' '); - expect(logOutput).toContain('###SECRET###'); - expect(logOutput).toContain('not-sensitive'); - - // These should NOT appear because they should be redacted - expect(logOutput).not.toContain('base-sensitive'); - expect(logOutput).not.toContain('pattern-sensitive'); - expect(logOutput).not.toContain('added-sensitive'); - - // Note: normalPassword containing 'password' will be redacted by default rules - }); + test('should handle complex configuration scenarios', () => { + // First, reset to known state + LogEngine.resetRedactionConfig(); + LogEngine.clearCustomRedactionPatterns(); + + // Configure multiple aspects + LogEngine.configureRedaction({ + redactionText: '###SECRET###', + sensitiveFields: ['baseField'] // This will be merged with defaults + }); + LogEngine.addCustomRedactionPatterns([/^custom.*/i]); + LogEngine.addSensitiveFields(['additionalField']); + + const testData = { + normalPassword: 'default-sensitive', // This will be redacted (password is default sensitive) + baseField: 'base-sensitive', + customField: 'pattern-sensitive', + additionalField: 'added-sensitive', + publicField: 'not-sensitive' + }; + + LogEngine.info('Complex test', testData); + + const logOutput = mockConsoleLog.mock.calls.flat().join(' '); + expect(logOutput).toContain('###SECRET###'); + expect(logOutput).toContain('not-sensitive'); + + // These should NOT appear because they should be redacted + expect(logOutput).not.toContain('base-sensitive'); + expect(logOutput).not.toContain('pattern-sensitive'); + expect(logOutput).not.toContain('added-sensitive'); + + // Note: normalPassword containing 'password' will be redacted by default rules }); + }); }); diff --git a/src/__tests__/logger.test.ts b/src/__tests__/logger.test.ts index 755d4b3..7ccb352 100644 --- a/src/__tests__/logger.test.ts +++ b/src/__tests__/logger.test.ts @@ -30,7 +30,7 @@ describe('Logger class', () => { logger.info('Info message'); logger.debug('Debug message'); logger.error('Error message'); - + // In test environment, only ERROR and above should be logged by default expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(0); // INFO filtered out expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); // ERROR shown @@ -43,7 +43,7 @@ describe('Logger class', () => { // Test that logger configuration updates work correctly logger.configure({ mode: LogMode.DEBUG }); logger.debug('Debug message'); - + // DEBUG should now be visible after configuration change expect(mocks.mockConsoleLog).toHaveBeenCalledWith( expect.stringContaining('Debug message') @@ -55,7 +55,7 @@ describe('Logger class', () => { const config = logger.getConfig(); expect(config).toBeDefined(); expect(config.mode).toBeDefined(); - + // Test configuration change is reflected in getConfig logger.configure({ mode: LogMode.WARN }); const updatedConfig = logger.getConfig(); @@ -65,12 +65,12 @@ describe('Logger class', () => { it('should filter messages based on configured mode', () => { // Test log mode filtering - only WARN and ERROR should show logger.configure({ mode: LogMode.WARN }); - + logger.debug('Debug message'); logger.info('Info message'); logger.warn('Warning message'); logger.error('Error message'); - + // DEBUG and INFO should be filtered out expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); // Only WARN and ERROR should be logged @@ -81,13 +81,13 @@ describe('Logger class', () => { it('should always log LOG level messages regardless of mode configuration', () => { // Test that LOG level always outputs regardless of configured mode logger.configure({ mode: LogMode.SILENT }); - + logger.debug('Debug message'); logger.info('Info message'); logger.warn('Warning message'); logger.error('Error message'); logger.log('LOG level message'); - + // Only LOG should be visible even with SILENT mode expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -110,10 +110,10 @@ describe('Logger class', () => { testCases.forEach((mode, index) => { // Reset mocks mocks.mockConsoleLog.mockClear(); - + logger.configure({ mode }); logger.log(`LOG message ${index}`); - + // LOG should always be visible regardless of configured mode (except OFF) expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -125,13 +125,13 @@ describe('Logger class', () => { it('should not log any messages when mode is OFF including LOG level', () => { // Test that OFF mode completely disables all logging including LOG logger.configure({ mode: LogMode.OFF }); - + logger.debug('Debug message'); logger.info('Info message'); logger.warn('Warning message'); logger.error('Error message'); logger.log('LOG level message'); - + // No console methods should be called with OFF mode (including LOG) expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); @@ -159,9 +159,9 @@ describe('Logger class', () => { it('should show deprecation warning for legacy level configuration in non-test environment', () => { // Test deprecation warning (covers lines 64-66) process.env.NODE_ENV = 'development'; - + logger.configure({ level: LogLevel.DEBUG } as any); - + // Should show deprecation warning expect(mockConsoleWarn).toHaveBeenCalledTimes(3); expect(mockConsoleWarn).toHaveBeenCalledWith( @@ -178,9 +178,9 @@ describe('Logger class', () => { it('should not show deprecation warning in test environment', () => { // Test that deprecation warning is suppressed in test environment process.env.NODE_ENV = 'test'; - + logger.configure({ level: LogLevel.DEBUG } as any); - + // Should not show deprecation warning in test environment expect(mockConsoleWarn).not.toHaveBeenCalled(); }); @@ -215,7 +215,7 @@ describe('Logger class', () => { logger.configure({ level: 4 } as any); // Legacy SILENT logger.info('Info message'); logger.log('LOG message'); - + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); // Only LOG should show expect(mocks.mockConsoleLog).toHaveBeenCalledWith( expect.stringContaining('LOG message') @@ -224,7 +224,7 @@ describe('Logger class', () => { mocks.mockConsoleLog.mockClear(); logger.configure({ level: 5 } as any); // Legacy OFF logger.log('LOG message'); - + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); // Nothing should show }); }); @@ -235,11 +235,11 @@ describe('Logger class', () => { const freshLogger = new Logger(); // Configure with undefined mode to test fallback to INFO freshLogger.configure({ mode: undefined as any }); - + // Should fall back to INFO mode behavior freshLogger.debug('Debug message'); freshLogger.info('Info message'); - + // With INFO mode fallback, DEBUG should be filtered, INFO should show expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); expect(mocks.mockConsoleLog).toHaveBeenCalledWith( @@ -315,17 +315,17 @@ describe('Logger class', () => { describe('Logger module exports', () => { it('should export all logger classes', () => { const logger = require('../logger'); - + // Test that all expected exports are available expect(logger.Logger).toBeDefined(); expect(logger.CoreLogger).toBeDefined(); // Backward compatibility expect(logger.LoggerConfigManager).toBeDefined(); expect(logger.LogFilter).toBeDefined(); expect(logger.EnvironmentDetector).toBeDefined(); - + // Test that CoreLogger is alias for Logger expect(logger.CoreLogger).toBe(logger.Logger); - + // Test that classes are constructible/callable expect(typeof logger.Logger).toBe('function'); expect(typeof logger.LoggerConfigManager).toBe('function'); @@ -352,16 +352,16 @@ describe('LoggerConfigManager', () => { }; configManager.updateConfig(configWithBothLevelAndMode); - + // Get the current config const currentConfig = configManager.getConfig(); - + // Verify that the legacy level property is not present expect(currentConfig).not.toHaveProperty('level'); - + // Verify that mode is correctly set expect(currentConfig.mode).toBe(LogMode.INFO); - + // Verify other properties are preserved expect(currentConfig.redaction).toEqual({ enabled: true }); }); @@ -375,16 +375,16 @@ describe('LoggerConfigManager', () => { }; configManager.updateConfig(configWithOnlyLevel); - + // Get the current config const currentConfig = configManager.getConfig(); - + // Verify that the legacy level property is not present (it gets mapped to mode) expect(currentConfig).not.toHaveProperty('level'); - + // Verify that mode is correctly mapped from level expect(currentConfig.mode).toBe(LogMode.WARN); - + // Verify other properties are preserved expect(currentConfig.redaction).toEqual({ enabled: false }); }); @@ -398,17 +398,61 @@ describe('LoggerConfigManager', () => { }; configManager.updateConfig(configWithOnlyMode); - + // Get the current config const currentConfig = configManager.getConfig(); - + // Verify that mode is correctly set expect(currentConfig.mode).toBe(LogMode.ERROR); - + // Verify other properties are preserved expect(currentConfig.redaction).toEqual({ enabled: true }); - + // Verify no level property exists expect(currentConfig).not.toHaveProperty('level'); }); + + it('should map legacy SILENT level (4) to LogMode.SILENT', () => { + // Test that legacy SILENT level (4) maps correctly to LogMode.SILENT + const configWithLegacySilent = { + level: 4, // Legacy SILENT value + redaction: { enabled: true } + }; + + configManager.updateConfig(configWithLegacySilent); + + // Get the current config + const currentConfig = configManager.getConfig(); + + // Verify that the legacy level property is not present (it gets mapped to mode) + expect(currentConfig).not.toHaveProperty('level'); + + // Verify that mode is correctly mapped from legacy SILENT (4) to LogMode.SILENT + expect(currentConfig.mode).toBe(LogMode.SILENT); + + // Verify other properties are preserved + expect(currentConfig.redaction).toEqual({ enabled: true }); + }); + + it('should map legacy OFF level (5) to LogMode.OFF', () => { + // Test that legacy OFF level (5) maps correctly to LogMode.OFF + const configWithLegacyOff = { + level: 5, // Legacy OFF value + redaction: { enabled: false } + }; + + configManager.updateConfig(configWithLegacyOff); + + // Get the current config + const currentConfig = configManager.getConfig(); + + // Verify that the legacy level property is not present (it gets mapped to mode) + expect(currentConfig).not.toHaveProperty('level'); + + // Verify that mode is correctly mapped from legacy OFF (5) to LogMode.OFF + expect(currentConfig.mode).toBe(LogMode.OFF); + + // Verify other properties are preserved + expect(currentConfig.redaction).toEqual({ enabled: false }); + }); }); diff --git a/src/__tests__/multiple-outputs.test.ts b/src/__tests__/multiple-outputs.test.ts new file mode 100644 index 0000000..0cfe266 --- /dev/null +++ b/src/__tests__/multiple-outputs.test.ts @@ -0,0 +1,396 @@ +/** + * Tests for Multiple Output functionality + * Verifies multiple output targets, built-in handlers, and mixed configurations + */ + +import { Logger } from '../logger'; +import { LogMode } from '../types'; +import type { LogOutputHandler, OutputTarget } from '../types'; +import { setupConsoleMocks, restoreConsoleMocks, ConsoleMocks } from './test-utils'; + +describe('Multiple Output functionality', () => { + let logger: Logger; + let mocks: ConsoleMocks; + let capturedLogs1: Array<{ level: string; message: string; data?: any }>; + let capturedLogs2: Array<{ level: string; message: string; data?: any }>; + let mockHandler1: jest.MockedFunction; + let mockHandler2: jest.MockedFunction; + + beforeEach(() => { + logger = new Logger(); + mocks = setupConsoleMocks(); + capturedLogs1 = []; + capturedLogs2 = []; + + mockHandler1 = jest.fn((level: string, message: string, data?: any) => { + capturedLogs1.push({ level, message, data }); + }); + + mockHandler2 = jest.fn((level: string, message: string, data?: any) => { + capturedLogs2.push({ level, message, data }); + }); + }); + + afterEach(() => { + restoreConsoleMocks(mocks); + }); + + describe('Built-in Output Handlers', () => { + it('should support console built-in handler', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['console'] + }); + + logger.info('Test message'); + logger.warn('Warning message'); + logger.error('Error message'); + + // Should use appropriate console methods + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Test message') + ); + expect(mocks.mockConsoleWarn).toHaveBeenCalledWith( + expect.stringContaining('Warning message') + ); + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Error message') + ); + }); + + it('should support silent built-in handler', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['silent'] + }); + + logger.info('Silent message'); + logger.error('Silent error'); + + // Nothing should be logged + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should handle unknown built-in handler gracefully', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['unknown' as any] + }); + + logger.info('Test message'); + + // Should log error about unknown handler + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Unknown built-in output handler:'), + '"unknown"' + ); + }); + }); + + describe('Multiple Custom Handlers', () => { + it('should send logs to multiple custom handlers', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: [mockHandler1, mockHandler2] + }); + + logger.info('Test message', { key: 'value' }); + + // Both handlers should be called + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledTimes(1); + + expect(mockHandler1).toHaveBeenCalledWith( + 'info', + expect.stringContaining('Test message'), + { key: 'value' } + ); + expect(mockHandler2).toHaveBeenCalledWith( + 'info', + expect.stringContaining('Test message'), + { key: 'value' } + ); + + expect(capturedLogs1).toHaveLength(1); + expect(capturedLogs2).toHaveLength(1); + }); + + it('should continue processing outputs even if one fails', () => { + const faultyHandler = jest.fn(() => { + throw new Error('Handler failed'); + }); + + logger.configure({ + mode: LogMode.DEBUG, + outputs: [faultyHandler, mockHandler1, mockHandler2] + }); + + logger.info('Test message'); + + // Faulty handler should be called but fail + expect(faultyHandler).toHaveBeenCalledTimes(1); + // Other handlers should still work + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledTimes(1); + // Error should be logged + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Output handler failed') + ); + }); + }); + + describe('Mixed Output Types', () => { + it('should support mixed array of built-in and custom handlers', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['console', mockHandler1, 'silent', mockHandler2] + }); + + logger.info('Mixed output test'); + + // Console should be called + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + // Custom handlers should be called + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledTimes(1); + // Silent does nothing (no additional verification needed) + }); + + it('should handle empty outputs array', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: [] + }); + + logger.info('Empty outputs test'); + + // Nothing should be logged + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should handle invalid output types gracefully', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputs: [mockHandler1, null as any, 123 as any, mockHandler2] + }); + + logger.info('Invalid outputs test'); + + // Valid handlers should work + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledTimes(1); + // Errors should be logged for invalid outputs + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Invalid output target'), + null + ); + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Invalid output target'), + 123 + ); + }); + }); + + describe('Backward Compatibility', () => { + it('should prioritize outputs over outputHandler when both are provided', () => { + const legacyHandler = jest.fn(); + + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: legacyHandler, + outputs: [mockHandler1] + }); + + logger.info('Compatibility test'); + + // Only outputs should be used, not legacy outputHandler + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(legacyHandler).not.toHaveBeenCalled(); + }); + + it('should fall back to outputHandler when outputs is not configured', () => { + const legacyHandler = jest.fn(); + + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: legacyHandler + // No outputs property at all + }); + + logger.info('Fallback test'); + + // Should use legacy outputHandler when outputs is undefined + expect(legacyHandler).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + }); + + it('should work with outputHandler when no outputs configured', () => { + const legacyHandler = jest.fn(); + + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: legacyHandler + // No outputs property + }); + + logger.info('Legacy test'); + + // Should use legacy outputHandler + expect(legacyHandler).toHaveBeenCalledTimes(1); + }); + }); + + describe('Real-world Scenarios', () => { + it('should support console + file + network logging pattern', () => { + const fileLogs: string[] = []; + const networkLogs: Array<{ level: string; message: string; timestamp: string }> = []; + + const fileHandler: LogOutputHandler = (level, message) => { + fileLogs.push(`[${level.toUpperCase()}] ${message}`); + }; + + const networkHandler: LogOutputHandler = (level, message) => { + networkLogs.push({ + level, + message: message.replace(/\u001b\[[0-9;]*m/g, ''), // Strip ANSI colors + timestamp: new Date().toISOString() + }); + }; + + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['console', fileHandler, networkHandler] + }); + + logger.info('Application started'); + logger.warn('Memory usage high'); + logger.error('Database connection failed'); + + // Console output + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + + // File output + expect(fileLogs).toHaveLength(3); + expect(fileLogs[0]).toContain('[INFO]'); + expect(fileLogs[1]).toContain('[WARN]'); + expect(fileLogs[2]).toContain('[ERROR]'); + + // Network output + expect(networkLogs).toHaveLength(3); + expect(networkLogs[0].level).toBe('info'); + expect(networkLogs[1].level).toBe('warn'); + expect(networkLogs[2].level).toBe('error'); + }); + + it('should support development vs production output configurations', () => { + // Development: Console + detailed file logging + const devLogs: Array<{ level: string; message: string; data: any; stack?: string }> = []; + const devHandler: LogOutputHandler = (level, message, data) => { + devLogs.push({ level, message, data, stack: new Error().stack }); + }; + + logger.configure({ + mode: LogMode.DEBUG, + outputs: ['console', devHandler] + }); + + logger.debug('Debug info for development'); + logger.info('App started in dev mode'); + + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(2); + expect(devLogs).toHaveLength(2); + + // Reset for production test + jest.clearAllMocks(); + capturedLogs1.length = 0; + devLogs.length = 0; + + // Production: Structured logging only (no console) + const prodLogs: Array<{ timestamp: string; level: string; message: string }> = []; + const prodHandler: LogOutputHandler = (level, message) => { + prodLogs.push({ + timestamp: new Date().toISOString(), + level, + message: message.replace(/\u001b\[[0-9;]*m/g, '') + }); + }; + + logger.configure({ + mode: LogMode.WARN, // Less verbose in production + outputs: [prodHandler] // No console output + }); + + logger.debug('Debug ignored in prod'); + logger.info('Info ignored in prod'); + logger.warn('Warning in prod'); + logger.error('Error in prod'); + + // No console output in production + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + + // Only WARN and ERROR should be logged + expect(prodLogs).toHaveLength(2); + expect(prodLogs[0].level).toBe('warn'); + expect(prodLogs[1].level).toBe('error'); + }); + }); + + describe('Performance and Filtering', () => { + it('should respect log level filtering with multiple outputs', () => { + logger.configure({ + mode: LogMode.ERROR, // Only ERROR and LOG levels + outputs: [mockHandler1, mockHandler2, 'console'] + }); + + logger.debug('Debug filtered'); + logger.info('Info filtered'); + logger.warn('Warn filtered'); + logger.error('Error shown'); + + // Only error should reach any output + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should process outputs efficiently', () => { + const start = process.hrtime.bigint(); + + logger.configure({ + mode: LogMode.DEBUG, + outputs: [mockHandler1, mockHandler2, 'console', 'silent'] + }); + + // Log multiple messages + for (let i = 0; i < 100; i++) { + logger.info(`Message ${i}`); + } + + const end = process.hrtime.bigint(); + const duration = Number(end - start) / 1000000; // Convert to milliseconds + + // Should complete reasonably fast (less than 100ms for 100 messages) + expect(duration).toBeLessThan(100); + + // All outputs should be called + expect(mockHandler1).toHaveBeenCalledTimes(100); + expect(mockHandler2).toHaveBeenCalledTimes(100); + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(100); + }); + }); +}); diff --git a/src/__tests__/output-handler.test.ts b/src/__tests__/output-handler.test.ts new file mode 100644 index 0000000..f77d059 --- /dev/null +++ b/src/__tests__/output-handler.test.ts @@ -0,0 +1,311 @@ +/** + * Tests for Output Handler functionality + * Verifies custom output handlers, console suppression, and backward compatibility + */ + +import { Logger } from '../logger'; +import { LogMode } from '../types'; +import type { LogOutputHandler } from '../types'; +import { setupConsoleMocks, restoreConsoleMocks, ConsoleMocks } from './test-utils'; + +describe('Output Handler functionality', () => { + let logger: Logger; + let mocks: ConsoleMocks; + let capturedLogs: Array<{ level: string; message: string; data?: any }>; + let mockOutputHandler: jest.MockedFunction; + + beforeEach(() => { + logger = new Logger(); + mocks = setupConsoleMocks(); + capturedLogs = []; + mockOutputHandler = jest.fn((level: string, message: string, data?: any) => { + capturedLogs.push({ level, message, data }); + }); + }); + + afterEach(() => { + restoreConsoleMocks(mocks); + }); + + describe('Custom Output Handler', () => { + it('should use custom output handler when configured', () => { + // Configure with custom output handler + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler + }); + + logger.info('Test message', { key: 'value' }); + + // Verify custom handler was called + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + expect(mockOutputHandler).toHaveBeenCalledWith( + 'info', + expect.stringContaining('Test message'), + { key: 'value' } + ); + + // Verify console was not called + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should pass correct log levels to output handler', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler + }); + + logger.debug('Debug message'); + logger.info('Info message'); + logger.warn('Warn message'); + logger.error('Error message'); + logger.log('Log message'); + + expect(mockOutputHandler).toHaveBeenCalledTimes(5); + expect(mockOutputHandler).toHaveBeenNthCalledWith(1, 'debug', expect.any(String), undefined); + expect(mockOutputHandler).toHaveBeenNthCalledWith(2, 'info', expect.any(String), undefined); + expect(mockOutputHandler).toHaveBeenNthCalledWith(3, 'warn', expect.any(String), undefined); + expect(mockOutputHandler).toHaveBeenNthCalledWith(4, 'error', expect.any(String), undefined); + expect(mockOutputHandler).toHaveBeenNthCalledWith(5, 'log', expect.any(String), undefined); + }); + + it('should work with raw logging methods', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler + }); + + const testData = { sensitive: 'data' }; + logger.infoRaw('Raw message', testData); + + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + expect(mockOutputHandler).toHaveBeenCalledWith( + 'info', + expect.stringContaining('Raw message'), + testData + ); + }); + + it('should still respect log level filtering', () => { + logger.configure({ + mode: LogMode.ERROR, + outputHandler: mockOutputHandler + }); + + logger.debug('Debug message'); + logger.info('Info message'); + logger.error('Error message'); + + // Only error should be logged due to ERROR mode + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + expect(mockOutputHandler).toHaveBeenCalledWith( + 'error', + expect.stringContaining('Error message'), + undefined + ); + }); + }); + + describe('Console Suppression', () => { + it('should suppress console output when suppressConsoleOutput is true', () => { + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: true + }); + + logger.info('Test message'); + logger.error('Error message'); + + // Verify console was not called + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + + it('should work with custom handler and console suppression', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler, + suppressConsoleOutput: true + }); + + logger.info('Test message'); + + // Custom handler should be called + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + // Console should not be called + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + }); + + it('should go completely silent with suppressConsoleOutput=true and no handler', () => { + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: true + // No outputHandler + }); + + logger.info('Test message'); + logger.error('Error message'); + + // Nothing should be logged anywhere + expect(mocks.mockConsoleLog).not.toHaveBeenCalled(); + expect(mocks.mockConsoleError).not.toHaveBeenCalled(); + expect(mocks.mockConsoleWarn).not.toHaveBeenCalled(); + }); + }); + + describe('Backward Compatibility', () => { + it('should work exactly as before when no new options are provided', () => { + logger.configure({ + mode: LogMode.DEBUG + }); + + logger.info('Test message'); + logger.error('Error message'); + + // Should use console as before + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleLog).toHaveBeenCalledWith( + expect.stringContaining('Test message') + ); + expect(mocks.mockConsoleError).toHaveBeenCalledWith( + expect.stringContaining('Error message') + ); + }); + + it('should maintain console behavior when suppressConsoleOutput is false', () => { + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: false + }); + + logger.info('Test message'); + + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + }); + + it('should maintain console behavior when suppressConsoleOutput is undefined', () => { + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: undefined + }); + + logger.warn('Warning message'); + + expect(mocks.mockConsoleWarn).toHaveBeenCalledTimes(1); + }); + }); + + describe('Data Redaction with Output Handlers', () => { + it('should pass redacted data to output handler for regular methods', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler + }); + + logger.info('User login', { username: 'john', password: 'secret123' }); + + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + const [level, message, data] = mockOutputHandler.mock.calls[0]; + + expect(level).toBe('info'); + expect(message).toContain('User login'); + expect(data).toEqual({ username: 'john', password: '[REDACTED]' }); + }); + + it('should pass unredacted data to output handler for raw methods', () => { + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: mockOutputHandler + }); + + const originalData = { username: 'john', password: 'secret123' }; + logger.infoRaw('User login', originalData); + + expect(mockOutputHandler).toHaveBeenCalledTimes(1); + const [level, message, data] = mockOutputHandler.mock.calls[0]; + + expect(level).toBe('info'); + expect(message).toContain('User login'); + expect(data).toEqual(originalData); + }); + }); + + describe('Error Handling', () => { + it('should handle output handler errors gracefully and fallback to console', () => { + const faultyHandler: LogOutputHandler = jest.fn(() => { + throw new Error('Handler error'); + }); + + logger.configure({ + mode: LogMode.DEBUG, + outputHandler: faultyHandler + }); + + // This should not throw and should fallback to console + expect(() => { + logger.info('Test message'); + }).not.toThrow(); + + // Should have called the faulty handler + expect(faultyHandler).toHaveBeenCalledTimes(1); + + // Should have fallen back to console + expect(mocks.mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mocks.mockConsoleError).toHaveBeenCalledTimes(1); // Error message about handler failure + }); + }); + + describe('Real-world Use Cases', () => { + it('should support GUI application log capture', () => { + const uiLogs: Array<{ level: string; message: string; timestamp: Date }> = []; + + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: true, + outputHandler: (level, message) => { + uiLogs.push({ level, message, timestamp: new Date() }); + } + }); + + logger.info('Application started'); + logger.warn('Configuration warning'); + logger.error('Connection failed'); + + expect(uiLogs).toHaveLength(3); + expect(uiLogs[0]).toMatchObject({ level: 'info', message: expect.stringContaining('Application started') }); + expect(uiLogs[1]).toMatchObject({ level: 'warn', message: expect.stringContaining('Configuration warning') }); + expect(uiLogs[2]).toMatchObject({ level: 'error', message: expect.stringContaining('Connection failed') }); + }); + + it('should support testing framework log capture', () => { + const testLogs: Array<{ level: string; message: string }> = []; + + logger.configure({ + mode: LogMode.DEBUG, + suppressConsoleOutput: true, + outputHandler: (level, message) => { + testLogs.push({ level, message }); + } + }); + + // Simulate application code + logger.info('User logged in'); + logger.error('Expected error for testing'); + + // Test assertions + expect(testLogs).toHaveLength(2); + expect(testLogs).toContainEqual({ + level: 'info', + message: expect.stringContaining('User logged in') + }); + expect(testLogs).toContainEqual({ + level: 'error', + message: expect.stringContaining('Expected error for testing') + }); + }); + }); +}); diff --git a/src/__tests__/redaction/advanced.test.ts b/src/__tests__/redaction/advanced.test.ts index 9a58216..243275d 100644 --- a/src/__tests__/redaction/advanced.test.ts +++ b/src/__tests__/redaction/advanced.test.ts @@ -6,451 +6,477 @@ import { DataRedactor, defaultRedactionConfig } from '../../redaction'; describe('Data Redaction - Advanced Features', () => { - beforeEach(() => { - // Reset to default configuration before each test - DataRedactor.updateConfig(defaultRedactionConfig); + beforeEach(() => { + // Reset to default configuration before each test + DataRedactor.updateConfig(defaultRedactionConfig); + }); + + describe('Custom Pattern Management', () => { + test('should add custom patterns for field detection', () => { + // Initial test - field should not be redacted (using field that doesn't contain sensitive keywords) + expect(DataRedactor.testFieldRedaction('normalField')).toBe(false); + + // Add custom pattern + DataRedactor.addCustomPatterns([/normal.*/i]); + + // Now field should be redacted + expect(DataRedactor.testFieldRedaction('normalField')).toBe(true); + expect(DataRedactor.testFieldRedaction('NormalData')).toBe(true); + expect(DataRedactor.testFieldRedaction('unrelatedField')).toBe(false); }); - describe('Custom Pattern Management', () => { - test('should add custom patterns for field detection', () => { - // Initial test - field should not be redacted (using field that doesn't contain sensitive keywords) - expect(DataRedactor.testFieldRedaction('normalField')).toBe(false); - - // Add custom pattern - DataRedactor.addCustomPatterns([/normal.*/i]); - - // Now field should be redacted - expect(DataRedactor.testFieldRedaction('normalField')).toBe(true); - expect(DataRedactor.testFieldRedaction('NormalData')).toBe(true); - expect(DataRedactor.testFieldRedaction('unrelatedField')).toBe(false); - }); - - test('should clear custom patterns', () => { - // Add custom patterns - DataRedactor.addCustomPatterns([/custom.*/i, /special.*/i]); - - expect(DataRedactor.testFieldRedaction('customField')).toBe(true); - expect(DataRedactor.testFieldRedaction('specialField')).toBe(true); - - // Clear patterns - DataRedactor.clearCustomPatterns(); - - expect(DataRedactor.testFieldRedaction('customField')).toBe(false); - expect(DataRedactor.testFieldRedaction('specialField')).toBe(false); - }); - - test('should handle multiple custom patterns', () => { - DataRedactor.addCustomPatterns([ - /^config.*/i, - /.*data$/i, - /\btesting\b/i - ]); - - const testData = { - configCode: 'abc123', - apidata: 'sk-123', - testingData: 'sensitive', - normalField: 'public', - configValue: 'hidden', - mydata: 'private' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.configCode).toBe('[REDACTED]'); - expect(result.apidata).toBe('[REDACTED]'); - expect(result.testingData).toBe('[REDACTED]'); - expect(result.normalField).toBe('public'); - expect(result.configValue).toBe('[REDACTED]'); - expect(result.mydata).toBe('[REDACTED]'); - }); - - test('should accumulate custom patterns when called multiple times', () => { - DataRedactor.addCustomPatterns([/pattern1.*/i]); - DataRedactor.addCustomPatterns([/pattern2.*/i]); - - expect(DataRedactor.testFieldRedaction('pattern1Field')).toBe(true); - expect(DataRedactor.testFieldRedaction('pattern2Field')).toBe(true); - expect(DataRedactor.testFieldRedaction('otherField')).toBe(false); - }); + test('should clear custom patterns', () => { + // Add custom patterns + DataRedactor.addCustomPatterns([/custom.*/i, /special.*/i]); + + expect(DataRedactor.testFieldRedaction('customField')).toBe(true); + expect(DataRedactor.testFieldRedaction('specialField')).toBe(true); + + // Clear patterns + DataRedactor.clearCustomPatterns(); + + expect(DataRedactor.testFieldRedaction('customField')).toBe(false); + expect(DataRedactor.testFieldRedaction('specialField')).toBe(false); }); - describe('Sensitive Fields Management', () => { - test('should add custom sensitive fields', () => { - // Test initial state (using fields that are NOT in default sensitive list) - expect(DataRedactor.testFieldRedaction('companyValue')).toBe(false); - expect(DataRedactor.testFieldRedaction('businessData')).toBe(false); - - // Add custom sensitive fields - DataRedactor.addSensitiveFields(['companyValue', 'businessData', 'proprietaryInfo']); - - // Test after adding - expect(DataRedactor.testFieldRedaction('companyValue')).toBe(true); - expect(DataRedactor.testFieldRedaction('businessData')).toBe(true); - expect(DataRedactor.testFieldRedaction('proprietaryInfo')).toBe(true); - expect(DataRedactor.testFieldRedaction('publicField')).toBe(false); - }); - - test('should integrate custom fields with redaction', () => { - DataRedactor.addSensitiveFields(['customField', 'specialData']); - - const testData = { - customField: 'should be redacted', - specialData: 'also redacted', - password: 'default sensitive', - publicInfo: 'visible' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.customField).toBe('[REDACTED]'); - expect(result.specialData).toBe('[REDACTED]'); - expect(result.password).toBe('[REDACTED]'); - expect(result.publicInfo).toBe('visible'); - }); - - test('should handle case insensitive custom fields', () => { - DataRedactor.addSensitiveFields(['customField']); - - const testData = { - customField: 'sensitive', - CUSTOMFIELD: 'also sensitive', - CustomField: 'still sensitive' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.customField).toBe('[REDACTED]'); - expect(result.CUSTOMFIELD).toBe('[REDACTED]'); - expect(result.CustomField).toBe('[REDACTED]'); - }); + test('should handle multiple custom patterns', () => { + DataRedactor.addCustomPatterns([ + /^config.*/i, + /.*data$/i, + /\btesting\b/i + ]); + + const testData = { + configCode: 'abc123', + apidata: 'sk-123', + testingData: 'sensitive', + normalField: 'public', + configValue: 'hidden', + mydata: 'private' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.configCode).toBe('[REDACTED]'); + expect(result.apidata).toBe('[REDACTED]'); + expect(result.testingData).toBe('[REDACTED]'); + expect(result.normalField).toBe('public'); + expect(result.configValue).toBe('[REDACTED]'); + expect(result.mydata).toBe('[REDACTED]'); }); - describe('Field Redaction Testing', () => { - test('should test individual fields for redaction', () => { - // Test default sensitive fields - expect(DataRedactor.testFieldRedaction('password')).toBe(true); - expect(DataRedactor.testFieldRedaction('apiKey')).toBe(true); - expect(DataRedactor.testFieldRedaction('token')).toBe(true); - - // Test non-sensitive fields - expect(DataRedactor.testFieldRedaction('username')).toBe(false); - expect(DataRedactor.testFieldRedaction('name')).toBe(false); - expect(DataRedactor.testFieldRedaction('title')).toBe(false); - }); - - test('should test fields case insensitively', () => { - expect(DataRedactor.testFieldRedaction('PASSWORD')).toBe(true); - expect(DataRedactor.testFieldRedaction('Password')).toBe(true); - expect(DataRedactor.testFieldRedaction('ApiKey')).toBe(true); - expect(DataRedactor.testFieldRedaction('API_KEY')).toBe(true); - }); - - test('should handle empty and invalid field names', () => { - expect(DataRedactor.testFieldRedaction('')).toBe(false); - expect(DataRedactor.testFieldRedaction(null as any)).toBe(false); - expect(DataRedactor.testFieldRedaction(undefined as any)).toBe(false); - }); + test('should accumulate custom patterns when called multiple times', () => { + DataRedactor.addCustomPatterns([/pattern1.*/i]); + DataRedactor.addCustomPatterns([/pattern2.*/i]); + + expect(DataRedactor.testFieldRedaction('pattern1Field')).toBe(true); + expect(DataRedactor.testFieldRedaction('pattern2Field')).toBe(true); + expect(DataRedactor.testFieldRedaction('otherField')).toBe(false); }); + }); + + describe('Sensitive Fields Management', () => { + test('should add custom sensitive fields', () => { + // Test initial state (using fields that are NOT in default sensitive list) + expect(DataRedactor.testFieldRedaction('companyValue')).toBe(false); + expect(DataRedactor.testFieldRedaction('businessData')).toBe(false); + + // Add custom sensitive fields + DataRedactor.addSensitiveFields(['companyValue', 'businessData', 'proprietaryInfo']); + + // Test after adding + expect(DataRedactor.testFieldRedaction('companyValue')).toBe(true); + expect(DataRedactor.testFieldRedaction('businessData')).toBe(true); + expect(DataRedactor.testFieldRedaction('proprietaryInfo')).toBe(true); + expect(DataRedactor.testFieldRedaction('publicField')).toBe(false); + }); + + test('should integrate custom fields with redaction', () => { + DataRedactor.addSensitiveFields(['customField', 'specialData']); + + const testData = { + customField: 'should be redacted', + specialData: 'also redacted', + password: 'default sensitive', + publicInfo: 'visible' + }; + + const result = DataRedactor.redactData(testData); - describe('Configuration Refresh', () => { - test('should refresh configuration correctly', () => { - // Set initial custom config - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - redactionText: '***CUSTOM***', - sensitiveFields: ['customField'] - }); - - const testData = { - customField: 'sensitive' - }; - - let result = DataRedactor.redactData(testData); - expect(result.customField).toBe('***CUSTOM***'); - - // Update configuration - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - redactionText: '---HIDDEN---', - sensitiveFields: ['differentField'] - }); - - result = DataRedactor.redactData(testData); - expect(result.customField).toBe('sensitive'); // No longer sensitive - }); - - test('should handle partial configuration updates', () => { - // Get current config - const currentConfig = DataRedactor.getConfig(); - - // Update only redaction text - DataRedactor.updateConfig({ - ...currentConfig, - redactionText: '***PARTIAL***' - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('***PARTIAL***'); - }); - - test('should get current configuration', () => { - const config = DataRedactor.getConfig(); - - expect(config).toHaveProperty('enabled'); - expect(config).toHaveProperty('sensitiveFields'); - expect(config).toHaveProperty('redactionText'); - expect(config.enabled).toBe(true); - expect(Array.isArray(config.sensitiveFields)).toBe(true); - }); + expect(result.customField).toBe('[REDACTED]'); + expect(result.specialData).toBe('[REDACTED]'); + expect(result.password).toBe('[REDACTED]'); + expect(result.publicInfo).toBe('visible'); }); - describe('Configuration Edge Cases', () => { - test('should handle invalid configuration values', () => { - // Test with a simpler invalid config that won't crash - const testData = { password: 'secret' }; - - // First verify normal operation - DataRedactor.updateConfig(defaultRedactionConfig); - const normalResult = DataRedactor.redactData(testData); - expect(normalResult.password).toBe('[REDACTED]'); - - // Test with valid but unusual config - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - redactionText: '', // Empty string - maxContentLength: 0 // Zero length - }); - - const result = DataRedactor.redactData(testData); - // Should still work with fallback behavior - empty redactionText should result in empty string - expect(result.password).toBe(''); - }); - - test('should handle empty sensitive fields array', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - sensitiveFields: [] - }); - - const testData = { password: 'secret', apiKey: 'key123' }; - const result = DataRedactor.redactData(testData); - - // With empty sensitive fields, should not redact based on default patterns - expect(result.password).toBe('secret'); - expect(result.apiKey).toBe('key123'); - }); - - test('should handle very long redaction text', () => { - const longRedactionText = '[REDACTED]'.repeat(100); - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - redactionText: longRedactionText - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe(longRedactionText); - }); + test('should handle case insensitive custom fields', () => { + DataRedactor.addSensitiveFields(['customField']); + + const testData = { + customField: 'sensitive', + CUSTOMFIELD: 'also sensitive', + CustomField: 'still sensitive' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.customField).toBe('[REDACTED]'); + expect(result.CUSTOMFIELD).toBe('[REDACTED]'); + expect(result.CustomField).toBe('[REDACTED]'); + }); + }); + + describe('Field Redaction Testing', () => { + test('should test individual fields for redaction', () => { + // Test default sensitive fields + expect(DataRedactor.testFieldRedaction('password')).toBe(true); + expect(DataRedactor.testFieldRedaction('apiKey')).toBe(true); + expect(DataRedactor.testFieldRedaction('token')).toBe(true); + + // Test non-sensitive fields + expect(DataRedactor.testFieldRedaction('username')).toBe(false); + expect(DataRedactor.testFieldRedaction('name')).toBe(false); + expect(DataRedactor.testFieldRedaction('title')).toBe(false); }); - describe('Advanced Integration Tests', () => { - test('should handle complex nested structures with mixed patterns', () => { - DataRedactor.addCustomPatterns([/^internal.*/i]); - DataRedactor.addSensitiveFields(['businessSecret']); - - const complexData = { - user: { - profile: { - username: 'john', - password: 'secret123', - internalId: 'internal-456', - businessSecret: 'confidential' - } - }, - config: { - settings: [ - { name: 'theme', value: 'dark' }, - { name: 'normalSetting', value: 'normal-value' } - ] - } - }; - - const result = DataRedactor.redactData(complexData); - - expect(result.user.profile.username).toBe('john'); - expect(result.user.profile.password).toBe('[REDACTED]'); - expect(result.user.profile.internalId).toBe('[REDACTED]'); - expect(result.user.profile.businessSecret).toBe('[REDACTED]'); - expect(result.config.settings[0].value).toBe('dark'); - expect(result.config.settings[1].value).toBe('normal-value'); - }); - - test('should maintain performance with large datasets', () => { - const largeData = { - users: Array.from({ length: 1000 }, (_, i) => ({ - id: i, - username: `user${i}`, - password: `secret${i}`, - email: `user${i}@example.com` - })) - }; - - // Skip performance test in CI or set a more generous threshold - const isCI = process.env.CI === 'true' || process.env.NODE_ENV === 'test'; - const performanceThreshold = isCI ? 5000 : 2000; // More generous thresholds - - // Measure baseline performance with smaller dataset - const smallData = { - users: Array.from({ length: 100 }, (_, i) => ({ - id: i, - username: `user${i}`, - password: `secret${i}`, - email: `user${i}@example.com` - })) - }; - - const baselineStart = Date.now(); - DataRedactor.redactData(smallData); - const baselineTime = Date.now() - baselineStart; - - // Test with large dataset - const startTime = Date.now(); - const result = DataRedactor.redactData(largeData); - const endTime = Date.now(); - const executionTime = endTime - startTime; - - // Performance should scale reasonably (not more than 20x the baseline for 10x data) - const expectedMaxTime = Math.max(baselineTime * 20, performanceThreshold); - - if (!isCI) { - // Only assert performance in non-CI environments - expect(executionTime).toBeLessThan(expectedMaxTime); - } - - // Always verify redaction worked correctly - expect(result.users[0].username).toBe('user0'); - expect(result.users[0].password).toBe('[REDACTED]'); - expect(result.users[0].email).toBe('[REDACTED]'); - expect(result.users[999].password).toBe('[REDACTED]'); - - // Log performance metrics for debugging - console.log(`Redaction performance: ${executionTime}ms for 1000 users (baseline: ${baselineTime}ms for 100 users)`); - }); + test('should test fields case insensitively', () => { + expect(DataRedactor.testFieldRedaction('PASSWORD')).toBe(true); + expect(DataRedactor.testFieldRedaction('Password')).toBe(true); + expect(DataRedactor.testFieldRedaction('ApiKey')).toBe(true); + expect(DataRedactor.testFieldRedaction('API_KEY')).toBe(true); + }); + + test('should handle empty and invalid field names', () => { + expect(DataRedactor.testFieldRedaction('')).toBe(false); + expect(DataRedactor.testFieldRedaction(null as any)).toBe(false); + expect(DataRedactor.testFieldRedaction(undefined as any)).toBe(false); + }); + }); + + describe('Configuration Refresh', () => { + test('should refresh configuration correctly', () => { + // Set initial custom config + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + redactionText: '***CUSTOM***', + sensitiveFields: ['customField'] + }); + + const testData = { + customField: 'sensitive' + }; + + let result = DataRedactor.redactData(testData); + expect(result.customField).toBe('***CUSTOM***'); + + // Update configuration + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + redactionText: '---HIDDEN---', + sensitiveFields: ['differentField'] + }); + + result = DataRedactor.redactData(testData); + expect(result.customField).toBe('sensitive'); // No longer sensitive }); - describe('Circular reference and edge cases', () => { - test('should handle complex circular references', () => { - const parent: any = { name: 'parent', password: 'secret' }; - const child: any = { name: 'child', parent, token: 'token123' }; - parent.child = child; - - const result = DataRedactor.redactData(parent); - - expect(result.name).toBe('parent'); - expect(result.password).toBe('[REDACTED]'); - expect(result.child.name).toBe('child'); - expect(result.child.token).toBe('[REDACTED]'); - // Circular reference should be handled - expect(typeof result.child.parent).toBe('string'); - }); - - test('should handle arrays with circular references', () => { - const obj: any = { id: 1, password: 'secret' }; - const arr = [obj, { id: 2, apiKey: 'key123' }]; - obj.references = arr; - - const result = DataRedactor.redactData(arr); - - expect(result[0].id).toBe(1); - expect(result[0].password).toBe('[REDACTED]'); - expect(result[1].id).toBe(2); - expect(result[1].apiKey).toBe('[REDACTED]'); - }); - - test('should handle maximum depth gracefully', () => { - let deepObj: any = { level: 0, password: 'secret' }; - let current = deepObj; - - // Create very deep nesting (beyond reasonable limits) - for (let i = 1; i < 100; i++) { - current.next = { level: i, password: `secret${i}` }; - current = current.next; - } - - const result = DataRedactor.redactData(deepObj); - - expect(result.password).toBe('[REDACTED]'); - // Should handle deep nesting without stack overflow - expect(result.level).toBe(0); - }); - - test('should cover null and undefined handling branches', () => { - // Test null data - const nullResult = DataRedactor.redactData(null); - expect(nullResult).toBeNull(); - - // Test undefined data - const undefinedResult = DataRedactor.redactData(undefined); - expect(undefinedResult).toBeUndefined(); - - // Test object with null and undefined values - const mixedData = { - nullValue: null, - undefinedValue: undefined, - password: 'secret' - }; - - const result = DataRedactor.redactData(mixedData); - expect(result.nullValue).toBeNull(); - expect(result.undefinedValue).toBeUndefined(); - expect(result.password).toBe('[REDACTED]'); - }); - - test('should cover content field truncation branch', () => { - // Test content field that gets truncated - const longContent = 'a'.repeat(1500); // Longer than default maxContentLength (1000) - const contentData = { - description: longContent, - content: longContent - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toContain('... [TRUNCATED]'); - expect(result.description.length).toBeLessThan(longContent.length); - expect(result.content).toContain('... [TRUNCATED]'); - expect(result.content.length).toBeLessThan(longContent.length); - }); - - test('should cover content field without truncation branch', () => { - // Test content field that does NOT get truncated - const shortContent = 'Short description'; - const contentData = { - description: shortContent, - content: shortContent - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toBe(shortContent); - expect(result.content).toBe(shortContent); - }); - - test('should cover content field with non-string value branch', () => { - // Test content field with non-string value (should not be truncated) - const contentData = { - description: 12345, // Number instead of string - content: { nested: 'value' } // Object instead of string - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toBe(12345); - expect(result.content).toEqual({ nested: 'value' }); - }); + test('should handle partial configuration updates', () => { + // Get current config + const currentConfig = DataRedactor.getConfig(); + + // Update only redaction text + DataRedactor.updateConfig({ + ...currentConfig, + redactionText: '***PARTIAL***' + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('***PARTIAL***'); + }); + + test('should get current configuration', () => { + const config = DataRedactor.getConfig(); + + expect(config).toHaveProperty('enabled'); + expect(config).toHaveProperty('sensitiveFields'); + expect(config).toHaveProperty('redactionText'); + expect(config.enabled).toBe(true); + expect(Array.isArray(config.sensitiveFields)).toBe(true); + }); + }); + + describe('Configuration Edge Cases', () => { + test('should handle invalid configuration values', () => { + // Test with a simpler invalid config that won't crash + const testData = { password: 'secret' }; + + // First verify normal operation + DataRedactor.updateConfig(defaultRedactionConfig); + const normalResult = DataRedactor.redactData(testData); + expect(normalResult.password).toBe('[REDACTED]'); + + // Test with valid but unusual config + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + redactionText: '', // Empty string + maxContentLength: 0 // Zero length + }); + + const result = DataRedactor.redactData(testData); + // Should still work with fallback behavior - empty redactionText should result in empty string + expect(result.password).toBe(''); + }); + + test('should handle empty sensitive fields array', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + sensitiveFields: [] + }); + + const testData = { password: 'secret', apiKey: 'key123' }; + const result = DataRedactor.redactData(testData); + + // With empty sensitive fields, should not redact based on default patterns + expect(result.password).toBe('secret'); + expect(result.apiKey).toBe('key123'); + }); + + test('should handle very long redaction text', () => { + const longRedactionText = '[REDACTED]'.repeat(100); + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + redactionText: longRedactionText + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe(longRedactionText); + }); + }); + + describe('Advanced Integration Tests', () => { + test('should handle complex nested structures with mixed patterns', () => { + DataRedactor.addCustomPatterns([/^internal.*/i]); + DataRedactor.addSensitiveFields(['businessSecret']); + + const complexData = { + user: { + profile: { + username: 'john', + password: 'secret123', + internalId: 'internal-456', + businessSecret: 'confidential' + } + }, + config: { + settings: [ + { name: 'theme', value: 'dark' }, + { name: 'normalSetting', value: 'normal-value' } + ] + } + }; + + const result = DataRedactor.redactData(complexData); + + expect(result.user.profile.username).toBe('john'); + expect(result.user.profile.password).toBe('[REDACTED]'); + expect(result.user.profile.internalId).toBe('[REDACTED]'); + expect(result.user.profile.businessSecret).toBe('[REDACTED]'); + expect(result.config.settings[0].value).toBe('dark'); + expect(result.config.settings[1].value).toBe('normal-value'); + }); + + test('should maintain performance with large datasets', () => { + // Comprehensive CI/constrained environment detection + const isCI = !!( + process.env.CI || + process.env.CONTINUOUS_INTEGRATION || + process.env.BUILD_NUMBER || + process.env.GITHUB_ACTIONS || + process.env.TRAVIS || + process.env.CIRCLECI || + process.env.APPVEYOR || + process.env.GITLAB_CI || + process.env.BUILDKITE || + process.env.DRONE || + process.env.TF_BUILD || // Azure DevOps + process.env.NODE_ENV === 'test' || + process.env.JEST_WORKER_ID // Jest parallel workers + ); + + const largeData = { + users: Array.from({ length: 1000 }, (_, i) => ({ + id: i, + username: `user${i}`, + password: `secret${i}`, + email: `user${i}@example.com` + })) + }; + + // Always test functional correctness first + const result = DataRedactor.redactData(largeData); + + // Verify redaction worked correctly regardless of environment + expect(result.users[0].username).toBe('user0'); + expect(result.users[0].password).toBe('[REDACTED]'); + expect(result.users[0].email).toBe('[REDACTED]'); + expect(result.users[999].password).toBe('[REDACTED]'); + + // Only run performance assertions in local development environments + if (!isCI) { + // Measure baseline performance with smaller dataset + const smallData = { + users: Array.from({ length: 100 }, (_, i) => ({ + id: i, + username: `user${i}`, + password: `secret${i}`, + email: `user${i}@example.com` + })) + }; + + const baselineStart = performance.now(); + DataRedactor.redactData(smallData); + const baselineTime = performance.now() - baselineStart; + + // Re-test large dataset for performance measurement + const startTime = performance.now(); + DataRedactor.redactData(largeData); + const endTime = performance.now(); + const executionTime = endTime - startTime; + + // Performance should scale reasonably (not more than 15x the baseline for 10x data) + // Use more conservative scaling factor and add minimum threshold + const maxScalingFactor = 15; + const minimumThreshold = 100; // 100ms minimum threshold + const expectedMaxTime = Math.max(baselineTime * maxScalingFactor, minimumThreshold); + + expect(executionTime).toBeLessThan(expectedMaxTime); + + // Log performance metrics for debugging (only in non-CI) + if (process.env.DEBUG_PERFORMANCE) { + console.log(`Baseline (100 items): ${baselineTime.toFixed(2)}ms`); + console.log(`Large dataset (1000 items): ${executionTime.toFixed(2)}ms`); + console.log(`Scaling factor: ${(executionTime / baselineTime).toFixed(2)}x`); + } + } else { + // In CI environments, performance testing is skipped + // No console output to keep test results clean + } + }); + }); + + describe('Circular reference and edge cases', () => { + test('should handle complex circular references', () => { + const parent: any = { name: 'parent', password: 'secret' }; + const child: any = { name: 'child', parent, token: 'token123' }; + parent.child = child; + + const result = DataRedactor.redactData(parent); + + expect(result.name).toBe('parent'); + expect(result.password).toBe('[REDACTED]'); + expect(result.child.name).toBe('child'); + expect(result.child.token).toBe('[REDACTED]'); + // Circular reference should be handled + expect(typeof result.child.parent).toBe('string'); + }); + + test('should handle arrays with circular references', () => { + const obj: any = { id: 1, password: 'secret' }; + const arr = [obj, { id: 2, apiKey: 'key123' }]; + obj.references = arr; + + const result = DataRedactor.redactData(arr); + + expect(result[0].id).toBe(1); + expect(result[0].password).toBe('[REDACTED]'); + expect(result[1].id).toBe(2); + expect(result[1].apiKey).toBe('[REDACTED]'); + }); + + test('should handle maximum depth gracefully', () => { + const deepObj: any = { level: 0, password: 'secret' }; + let current = deepObj; + + // Create very deep nesting (beyond reasonable limits) + for (let i = 1; i < 100; i++) { + current.next = { level: i, password: `secret${i}` }; + current = current.next; + } + + const result = DataRedactor.redactData(deepObj); + + expect(result.password).toBe('[REDACTED]'); + // Should handle deep nesting without stack overflow + expect(result.level).toBe(0); + }); + + test('should cover null and undefined handling branches', () => { + // Test null data + const nullResult = DataRedactor.redactData(null); + expect(nullResult).toBeNull(); + + // Test undefined data + const undefinedResult = DataRedactor.redactData(undefined); + expect(undefinedResult).toBeUndefined(); + + // Test object with null and undefined values + const mixedData = { + nullValue: null, + undefinedValue: undefined, + password: 'secret' + }; + + const result = DataRedactor.redactData(mixedData); + expect(result.nullValue).toBeNull(); + expect(result.undefinedValue).toBeUndefined(); + expect(result.password).toBe('[REDACTED]'); + }); + + test('should cover content field truncation branch', () => { + // Test content field that gets truncated + const longContent = 'a'.repeat(1500); // Longer than default maxContentLength (1000) + const contentData = { + description: longContent, + content: longContent + }; + + const result = DataRedactor.redactData(contentData); + + expect(result.description).toContain('... [TRUNCATED]'); + expect(result.description.length).toBeLessThan(longContent.length); + expect(result.content).toContain('... [TRUNCATED]'); + expect(result.content.length).toBeLessThan(longContent.length); + }); + + test('should cover content field without truncation branch', () => { + // Test content field that does NOT get truncated + const shortContent = 'Short description'; + const contentData = { + description: shortContent, + content: shortContent + }; + + const result = DataRedactor.redactData(contentData); + + expect(result.description).toBe(shortContent); + expect(result.content).toBe(shortContent); + }); + + test('should cover content field with non-string value branch', () => { + // Test content field with non-string value (should not be truncated) + const contentData = { + description: 12345, // Number instead of string + content: { nested: 'value' } // Object instead of string + }; + + const result = DataRedactor.redactData(contentData); + + expect(result.description).toBe(12345); + expect(result.content).toEqual({ nested: 'value' }); }); + }); }); diff --git a/src/__tests__/redaction/core.test.ts b/src/__tests__/redaction/core.test.ts index 2353ca0..97509f2 100644 --- a/src/__tests__/redaction/core.test.ts +++ b/src/__tests__/redaction/core.test.ts @@ -6,382 +6,382 @@ import { DataRedactor, defaultRedactionConfig } from '../../redaction'; describe('Data Redaction - Core Functionality', () => { - beforeEach(() => { - // Reset to default configuration before each test - DataRedactor.updateConfig(defaultRedactionConfig); + beforeEach(() => { + // Reset to default configuration before each test + DataRedactor.updateConfig(defaultRedactionConfig); + }); + + describe('Basic redaction functionality', () => { + test('should redact sensitive fields', () => { + const testData = { + username: 'john_doe', + password: 'secret123', + email: 'john@example.com', + apiKey: 'sk-1234567890', + publicInfo: 'this is public' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.username).toBe('john_doe'); // username is not considered sensitive + expect(result.password).toBe('[REDACTED]'); + expect(result.email).toBe('[REDACTED]'); + expect(result.apiKey).toBe('[REDACTED]'); + expect(result.publicInfo).toBe('this is public'); }); - describe('Basic redaction functionality', () => { - test('should redact sensitive fields', () => { - const testData = { - username: 'john_doe', - password: 'secret123', - email: 'john@example.com', - apiKey: 'sk-1234567890', - publicInfo: 'this is public' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.username).toBe('john_doe'); // username is not considered sensitive - expect(result.password).toBe('[REDACTED]'); - expect(result.email).toBe('[REDACTED]'); - expect(result.apiKey).toBe('[REDACTED]'); - expect(result.publicInfo).toBe('this is public'); - }); - - test('should handle nested objects', () => { - const testData = { - user: { - profile: { - username: 'johndoe', - email: 'john@example.com', - password: 'secret' - }, - settings: { - theme: 'dark', - apiToken: 'token123' - } - }, - metadata: { - timestamp: '2025-01-01' - } - }; - - const result = DataRedactor.redactData(testData); - - expect(result.user.profile.username).toBe('johndoe'); // username is not considered sensitive - expect(result.user.profile.email).toBe('[REDACTED]'); - expect(result.user.profile.password).toBe('[REDACTED]'); - expect(result.user.settings.theme).toBe('dark'); - expect(result.user.settings.apiToken).toBe('[REDACTED]'); - expect(result.metadata.timestamp).toBe('2025-01-01'); - }); - - test('should handle arrays', () => { - const testData = { - users: [ - { name: 'Alice', password: 'alice123' }, - { name: 'Bob', secret: 'bob456' } - ], - config: { - items: ['item1', 'item2'] - } - }; - - const result = DataRedactor.redactData(testData); - - expect(result.users[0].name).toBe('Alice'); - expect(result.users[0].password).toBe('[REDACTED]'); - expect(result.users[1].name).toBe('Bob'); - expect(result.users[1].secret).toBe('[REDACTED]'); - expect(result.config.items).toEqual(['item1', 'item2']); - }); - - test('should handle mixed data types', () => { - const testData = { - count: 42, - isActive: true, - password: 'secret', - scores: [95, 87, 92], - metadata: null - }; - - const result = DataRedactor.redactData(testData); - - expect(result.count).toBe(42); - expect(result.isActive).toBe(true); - expect(result.password).toBe('[REDACTED]'); - expect(result.scores).toEqual([95, 87, 92]); - expect(result.metadata).toBeNull(); - }); + test('should handle nested objects', () => { + const testData = { + user: { + profile: { + username: 'johndoe', + email: 'john@example.com', + password: 'secret' + }, + settings: { + theme: 'dark', + apiToken: 'token123' + } + }, + metadata: { + timestamp: '2025-01-01' + } + }; + + const result = DataRedactor.redactData(testData); + + expect(result.user.profile.username).toBe('johndoe'); // username is not considered sensitive + expect(result.user.profile.email).toBe('[REDACTED]'); + expect(result.user.profile.password).toBe('[REDACTED]'); + expect(result.user.settings.theme).toBe('dark'); + expect(result.user.settings.apiToken).toBe('[REDACTED]'); + expect(result.metadata.timestamp).toBe('2025-01-01'); }); - describe('Field detection', () => { - test('should detect various sensitive field patterns', () => { - const testData = { - password: 'secret', - userPassword: 'secret', - PASSWORD: 'secret', - api_key: 'key123', - apiKey: 'key123', - secret: 'secret', - token: 'token123', - auth: 'auth123', - credential: 'cred123', - access_token: 'access123', - refresh_token: 'refresh123', - client_secret: 'client123' - }; - - const result = DataRedactor.redactData(testData); - - // Check some key fields that should be redacted - expect(result.password).toBe('[REDACTED]'); - expect(result.apiKey).toBe('[REDACTED]'); - expect(result.token).toBe('[REDACTED]'); - expect(result.secret).toBe('[REDACTED]'); - }); - - test('should not redact non-sensitive fields', () => { - const testData = { - name: 'John Doe', - title: 'Engineer', - department: 'IT', - publicId: 'user123' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.name).toBe('John Doe'); - expect(result.title).toBe('Engineer'); - expect(result.department).toBe('IT'); - expect(result.publicId).toBe('user123'); - }); - - test('should be case insensitive', () => { - const testData = { - Password: 'secret', - EMAIL: 'john@example.com', - Api_Key: 'key123' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.Password).toBe('[REDACTED]'); - expect(result.EMAIL).toBe('[REDACTED]'); - expect(result.Api_Key).toBe('[REDACTED]'); - }); + test('should handle arrays', () => { + const testData = { + users: [ + { name: 'Alice', password: 'alice123' }, + { name: 'Bob', secret: 'bob456' } + ], + config: { + items: ['item1', 'item2'] + } + }; + + const result = DataRedactor.redactData(testData); + + expect(result.users[0].name).toBe('Alice'); + expect(result.users[0].password).toBe('[REDACTED]'); + expect(result.users[1].name).toBe('Bob'); + expect(result.users[1].secret).toBe('[REDACTED]'); + expect(result.config.items).toEqual(['item1', 'item2']); }); - describe('Configuration', () => { - test('should use custom redaction text', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - redactionText: '***HIDDEN***' - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('***HIDDEN***'); - }); - - test('should respect custom sensitive fields', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - sensitiveFields: ['customField', 'specialData'] - }); - - const testData = { - customField: 'sensitive', - specialData: 'also sensitive', - password: 'not redacted because not in custom list', - normalField: 'normal' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.customField).toBe('[REDACTED]'); - expect(result.specialData).toBe('[REDACTED]'); - expect(result.password).toBe('not redacted because not in custom list'); - expect(result.normalField).toBe('normal'); - }); - - test('should handle disabled redaction', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - enabled: false - }); - - const testData = { password: 'secret', apiKey: 'key123' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('secret'); - expect(result.apiKey).toBe('key123'); - }); - - test('should truncate content fields', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - maxContentLength: 10 - }); - - const testData = { - content: 'This is a very long content that should be truncated', - description: 'Short desc', - password: 'secret' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.content).toContain('... [TRUNCATED]'); - expect(result.content.length).toBeLessThan(30); - expect(result.description).toBe('Short desc'); - expect(result.password).toBe('[REDACTED]'); - }); - - test('should use custom truncation text', () => { - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - maxContentLength: 5, - truncationText: '... [CUT]' - }); - - const testData = { content: 'This is long content' }; - const result = DataRedactor.redactData(testData); - - expect(result.content).toContain('... [CUT]'); - }); + test('should handle mixed data types', () => { + const testData = { + count: 42, + isActive: true, + password: 'secret', + scores: [95, 87, 92], + metadata: null + }; + + const result = DataRedactor.redactData(testData); + + expect(result.count).toBe(42); + expect(result.isActive).toBe(true); + expect(result.password).toBe('[REDACTED]'); + expect(result.scores).toEqual([95, 87, 92]); + expect(result.metadata).toBeNull(); }); + }); + + describe('Field detection', () => { + test('should detect various sensitive field patterns', () => { + const testData = { + password: 'secret', + userPassword: 'secret', + PASSWORD: 'secret', + api_key: 'key123', + apiKey: 'key123', + secret: 'secret', + token: 'token123', + auth: 'auth123', + credential: 'cred123', + access_token: 'access123', + refresh_token: 'refresh123', + client_secret: 'client123' + }; + + const result = DataRedactor.redactData(testData); + + // Check some key fields that should be redacted + expect(result.password).toBe('[REDACTED]'); + expect(result.apiKey).toBe('[REDACTED]'); + expect(result.token).toBe('[REDACTED]'); + expect(result.secret).toBe('[REDACTED]'); + }); + + test('should not redact non-sensitive fields', () => { + const testData = { + name: 'John Doe', + title: 'Engineer', + department: 'IT', + publicId: 'user123' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.name).toBe('John Doe'); + expect(result.title).toBe('Engineer'); + expect(result.department).toBe('IT'); + expect(result.publicId).toBe('user123'); + }); + + test('should be case insensitive', () => { + const testData = { + Password: 'secret', + EMAIL: 'john@example.com', + Api_Key: 'key123' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.Password).toBe('[REDACTED]'); + expect(result.EMAIL).toBe('[REDACTED]'); + expect(result.Api_Key).toBe('[REDACTED]'); + }); + }); + + describe('Configuration', () => { + test('should use custom redaction text', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + redactionText: '***HIDDEN***' + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('***HIDDEN***'); + }); + + test('should respect custom sensitive fields', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + sensitiveFields: ['customField', 'specialData'] + }); + + const testData = { + customField: 'sensitive', + specialData: 'also sensitive', + password: 'not redacted because not in custom list', + normalField: 'normal' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.customField).toBe('[REDACTED]'); + expect(result.specialData).toBe('[REDACTED]'); + expect(result.password).toBe('not redacted because not in custom list'); + expect(result.normalField).toBe('normal'); + }); + + test('should handle disabled redaction', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + enabled: false + }); + + const testData = { password: 'secret', apiKey: 'key123' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('secret'); + expect(result.apiKey).toBe('key123'); + }); + + test('should truncate content fields', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + maxContentLength: 10 + }); + + const testData = { + content: 'This is a very long content that should be truncated', + description: 'Short desc', + password: 'secret' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.content).toContain('... [TRUNCATED]'); + expect(result.content.length).toBeLessThan(30); + expect(result.description).toBe('Short desc'); + expect(result.password).toBe('[REDACTED]'); + }); + + test('should use custom truncation text', () => { + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + maxContentLength: 5, + truncationText: '... [CUT]' + }); + + const testData = { content: 'This is long content' }; + const result = DataRedactor.redactData(testData); + + expect(result.content).toContain('... [CUT]'); + }); + }); + + describe('Edge cases', () => { + test('should handle null and undefined values', () => { + const testData = { + nullValue: null, + undefinedValue: undefined, + password: 'secret' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.nullValue).toBeNull(); + expect(result.undefinedValue).toBeUndefined(); + expect(result.password).toBe('[REDACTED]'); + }); + + test('should handle null and undefined input', () => { + expect(DataRedactor.redactData(null)).toBeNull(); + expect(DataRedactor.redactData(undefined)).toBeUndefined(); + }); + + test('should handle primitive values', () => { + expect(DataRedactor.redactData('string')).toBe('string'); + expect(DataRedactor.redactData(123)).toBe(123); + expect(DataRedactor.redactData(true)).toBe(true); + }); + + test('should handle empty objects and arrays', () => { + expect(DataRedactor.redactData({})).toEqual({}); + expect(DataRedactor.redactData([])).toEqual([]); + }); + + test('should handle circular references', () => { + const obj: any = { name: 'test', password: 'secret' }; + obj.self = obj; + + const result = DataRedactor.redactData(obj); + + expect(result.name).toBe('test'); + expect(result.password).toBe('[REDACTED]'); + // The circular reference should be handled (exact string may vary) + expect(typeof result.self).toBe('string'); + expect(result.self).toContain('Circular'); + }); + + test('should handle deeply nested objects', () => { + const deepObj: any = { level: 1 }; + let current = deepObj; + + // Create a deeply nested structure + for (let i = 2; i <= 20; i++) { + current.next = { level: i }; + current = current.next; + } + current.password = 'secret'; + + const result = DataRedactor.redactData(deepObj); + + // Navigate to the deepest level + let deepResult = result; + for (let i = 1; i < 20; i++) { + deepResult = deepResult.next; + } + + expect(deepResult.password).toBe('[REDACTED]'); + }); + + test('should handle Date objects', () => { + const date = new Date('2025-01-01'); + const testData = { + createdAt: date, + password: 'secret' + }; + + const result = DataRedactor.redactData(testData); + + // Date handling may vary by implementation + expect(result.createdAt).toBeDefined(); + expect(result.password).toBe('[REDACTED]'); + }); + + test('should handle functions in objects', () => { + const testData = { + fn: () => 'function', + password: 'secret', + data: 'normal' + }; + + const result = DataRedactor.redactData(testData); + + expect(typeof result.fn).toBe('function'); + expect(result.password).toBe('[REDACTED]'); + expect(result.data).toBe('normal'); + }); + + test('should handle Set and Map objects', () => { + const testData = { + mySet: new Set([1, 2, 3]), + myMap: new Map([['key', 'value']]), + password: 'secret' + }; + + const result = DataRedactor.redactData(testData); + + // Set and Map handling may vary by implementation + expect(result.mySet).toBeDefined(); + expect(result.myMap).toBeDefined(); + expect(result.password).toBe('[REDACTED]'); + }); + + test('should handle content field truncation', () => { + const longContent = 'a'.repeat(1500); + const contentData = { + description: longContent, + content: longContent + }; + + const result = DataRedactor.redactData(contentData); + + expect(result.description).toContain('... [TRUNCATED]'); + expect(result.description.length).toBeLessThan(longContent.length); + expect(result.content).toContain('... [TRUNCATED]'); + expect(result.content.length).toBeLessThan(longContent.length); + }); + + test('should handle content field without truncation', () => { + const shortContent = 'Short description'; + const contentData = { + description: shortContent, + content: shortContent + }; + + const result = DataRedactor.redactData(contentData); + + expect(result.description).toBe(shortContent); + expect(result.content).toBe(shortContent); + }); + + test('should handle content field with non-string value', () => { + const contentData = { + description: 12345, + content: { nested: 'value' } + }; + + const result = DataRedactor.redactData(contentData); - describe('Edge cases', () => { - test('should handle null and undefined values', () => { - const testData = { - nullValue: null, - undefinedValue: undefined, - password: 'secret' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.nullValue).toBeNull(); - expect(result.undefinedValue).toBeUndefined(); - expect(result.password).toBe('[REDACTED]'); - }); - - test('should handle null and undefined input', () => { - expect(DataRedactor.redactData(null)).toBeNull(); - expect(DataRedactor.redactData(undefined)).toBeUndefined(); - }); - - test('should handle primitive values', () => { - expect(DataRedactor.redactData('string')).toBe('string'); - expect(DataRedactor.redactData(123)).toBe(123); - expect(DataRedactor.redactData(true)).toBe(true); - }); - - test('should handle empty objects and arrays', () => { - expect(DataRedactor.redactData({})).toEqual({}); - expect(DataRedactor.redactData([])).toEqual([]); - }); - - test('should handle circular references', () => { - const obj: any = { name: 'test', password: 'secret' }; - obj.self = obj; - - const result = DataRedactor.redactData(obj); - - expect(result.name).toBe('test'); - expect(result.password).toBe('[REDACTED]'); - // The circular reference should be handled (exact string may vary) - expect(typeof result.self).toBe('string'); - expect(result.self).toContain('Circular'); - }); - - test('should handle deeply nested objects', () => { - const deepObj: any = { level: 1 }; - let current = deepObj; - - // Create a deeply nested structure - for (let i = 2; i <= 20; i++) { - current.next = { level: i }; - current = current.next; - } - current.password = 'secret'; - - const result = DataRedactor.redactData(deepObj); - - // Navigate to the deepest level - let deepResult = result; - for (let i = 1; i < 20; i++) { - deepResult = deepResult.next; - } - - expect(deepResult.password).toBe('[REDACTED]'); - }); - - test('should handle Date objects', () => { - const date = new Date('2025-01-01'); - const testData = { - createdAt: date, - password: 'secret' - }; - - const result = DataRedactor.redactData(testData); - - // Date handling may vary by implementation - expect(result.createdAt).toBeDefined(); - expect(result.password).toBe('[REDACTED]'); - }); - - test('should handle functions in objects', () => { - const testData = { - fn: () => 'function', - password: 'secret', - data: 'normal' - }; - - const result = DataRedactor.redactData(testData); - - expect(typeof result.fn).toBe('function'); - expect(result.password).toBe('[REDACTED]'); - expect(result.data).toBe('normal'); - }); - - test('should handle Set and Map objects', () => { - const testData = { - mySet: new Set([1, 2, 3]), - myMap: new Map([['key', 'value']]), - password: 'secret' - }; - - const result = DataRedactor.redactData(testData); - - // Set and Map handling may vary by implementation - expect(result.mySet).toBeDefined(); - expect(result.myMap).toBeDefined(); - expect(result.password).toBe('[REDACTED]'); - }); - - test('should handle content field truncation', () => { - const longContent = 'a'.repeat(1500); - const contentData = { - description: longContent, - content: longContent - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toContain('... [TRUNCATED]'); - expect(result.description.length).toBeLessThan(longContent.length); - expect(result.content).toContain('... [TRUNCATED]'); - expect(result.content.length).toBeLessThan(longContent.length); - }); - - test('should handle content field without truncation', () => { - const shortContent = 'Short description'; - const contentData = { - description: shortContent, - content: shortContent - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toBe(shortContent); - expect(result.content).toBe(shortContent); - }); - - test('should handle content field with non-string value', () => { - const contentData = { - description: 12345, - content: { nested: 'value' } - }; - - const result = DataRedactor.redactData(contentData); - - expect(result.description).toBe(12345); - expect(result.content).toEqual({ nested: 'value' }); - }); + expect(result.description).toBe(12345); + expect(result.content).toEqual({ nested: 'value' }); }); + }); }); diff --git a/src/__tests__/redaction/env.test.ts b/src/__tests__/redaction/env.test.ts index 63c1d25..858ce40 100644 --- a/src/__tests__/redaction/env.test.ts +++ b/src/__tests__/redaction/env.test.ts @@ -6,113 +6,119 @@ import { DataRedactor, defaultRedactionConfig, RedactionController } from '../../redaction'; describe('Data Redaction - Environment Control', () => { - // Store original environment to restore after tests - const originalEnv = { ...process.env }; - - beforeEach(() => { - // Reset to default configuration before each test - DataRedactor.updateConfig(defaultRedactionConfig); - // Clear current environment and restore original - for (const key in process.env) { - delete process.env[key]; - } - Object.assign(process.env, originalEnv); + // Store original environment to restore after tests + const originalEnv = { ...process.env }; + + beforeEach(() => { + // Reset to default configuration before each test + DataRedactor.updateConfig(defaultRedactionConfig); + // Clear current environment and restore original + for (const key in process.env) { + if (Object.prototype.hasOwnProperty.call(process.env, key)) { + delete process.env[key]; + } + } + Object.assign(process.env, originalEnv); + }); + + afterAll(() => { + // Restore original environment completely + const envKeys = Object.keys(process.env); + for (const key of envKeys) { + if (Object.prototype.hasOwnProperty.call(process.env, key)) { + // Safe deletion using bracket notation with known string + delete (process.env as Record)[key]; + } + } + Object.assign(process.env, originalEnv); + }); + + describe('Environment-based control', () => { + test('should disable redaction in development environment', () => { + process.env.NODE_ENV = 'development'; + + // Re-initialize with environment config + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('secret'); }); - afterAll(() => { - // Restore original environment completely - for (const key in process.env) { - delete process.env[key]; - } - Object.assign(process.env, originalEnv); + test('should disable redaction when LOG_REDACTION_DISABLED is true', () => { + process.env.LOG_REDACTION_DISABLED = 'true'; + + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('secret'); + }); + + test('should disable redaction when DEBUG_FULL_PAYLOADS is true', () => { + process.env.DEBUG_FULL_PAYLOADS = 'true'; + + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + + const testData = { password: 'secret' }; + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('secret'); + }); + + test('should apply environment variable overrides', () => { + process.env.LOG_REDACTION_TEXT = '***ENV_REDACTED***'; + process.env.LOG_MAX_CONTENT_LENGTH = '50'; + process.env.LOG_SENSITIVE_FIELDS = 'customField,anotherField'; + + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + + const testData = { + password: 'secret', + customField: 'custom', + content: 'A'.repeat(100) + }; + + const result = DataRedactor.redactData(testData); + + expect(result.password).toBe('***ENV_REDACTED***'); + expect(result.customField).toBe('***ENV_REDACTED***'); + expect(result.content).toContain('... [TRUNCATED]'); + expect(result.content.length).toBeLessThan(70); // 50 chars + truncation text }); - describe('Environment-based control', () => { - test('should disable redaction in development environment', () => { - process.env.NODE_ENV = 'development'; - - // Re-initialize with environment config - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('secret'); - }); - - test('should disable redaction when LOG_REDACTION_DISABLED is true', () => { - process.env.LOG_REDACTION_DISABLED = 'true'; - - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('secret'); - }); - - test('should disable redaction when DEBUG_FULL_PAYLOADS is true', () => { - process.env.DEBUG_FULL_PAYLOADS = 'true'; - - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - - const testData = { password: 'secret' }; - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('secret'); - }); - - test('should apply environment variable overrides', () => { - process.env.LOG_REDACTION_TEXT = '***ENV_REDACTED***'; - process.env.LOG_MAX_CONTENT_LENGTH = '50'; - process.env.LOG_SENSITIVE_FIELDS = 'customField,anotherField'; - - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - - const testData = { - password: 'secret', - customField: 'custom', - content: 'A'.repeat(100) - }; - - const result = DataRedactor.redactData(testData); - - expect(result.password).toBe('***ENV_REDACTED***'); - expect(result.customField).toBe('***ENV_REDACTED***'); - expect(result.content).toContain('... [TRUNCATED]'); - expect(result.content.length).toBeLessThan(70); // 50 chars + truncation text - }); - - test('should apply LOG_TRUNCATION_TEXT environment variable', () => { - // Test to cover line 95 in config.ts - process.env.LOG_TRUNCATION_TEXT = '... [CUSTOM_TRUNCATED]'; - process.env.LOG_MAX_CONTENT_LENGTH = '20'; - - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - - const testData = { - content: 'This is a very long content that will be truncated' - }; - - const result = DataRedactor.redactData(testData); - - expect(result.content).toContain('... [CUSTOM_TRUNCATED]'); - expect(result.content.length).toBeLessThan(50); - }); + test('should apply LOG_TRUNCATION_TEXT environment variable', () => { + // Test to cover line 95 in config.ts + process.env.LOG_TRUNCATION_TEXT = '... [CUSTOM_TRUNCATED]'; + process.env.LOG_MAX_CONTENT_LENGTH = '20'; + + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + + const testData = { + content: 'This is a very long content that will be truncated' + }; + + const result = DataRedactor.redactData(testData); + + expect(result.content).toContain('... [CUSTOM_TRUNCATED]'); + expect(result.content.length).toBeLessThan(50); }); + }); }); diff --git a/src/__tests__/redaction/logengine.test.ts b/src/__tests__/redaction/logengine.test.ts index e70e103..46e503b 100644 --- a/src/__tests__/redaction/logengine.test.ts +++ b/src/__tests__/redaction/logengine.test.ts @@ -8,372 +8,372 @@ import { LogMode } from '../../types'; // Mock console methods to capture output for integration tests const mockConsole = { - log: jest.fn(), - warn: jest.fn(), - error: jest.fn() + log: jest.fn(), + warn: jest.fn(), + error: jest.fn() }; // Store original console methods const originalConsole = { - log: console.log, - warn: console.warn, - error: console.error + log: console.log, + warn: console.warn, + error: console.error }; describe('Data Redaction - LogEngine Integration', () => { - // Store original environment to restore after tests - const originalEnv = process.env; - - beforeEach(() => { - // Reset environment - process.env = { ...originalEnv }; - - // Clear all mock calls for integration tests - jest.clearAllMocks(); - - // Replace console methods with mocks for integration tests - console.log = mockConsole.log; - console.warn = mockConsole.warn; - console.error = mockConsole.error; - - // Configure LogEngine for testing - LogEngine.configure({ mode: LogMode.DEBUG }); + // Store original environment to restore after tests + const originalEnv = process.env; + + beforeEach(() => { + // Reset environment + process.env = { ...originalEnv }; + + // Clear all mock calls for integration tests + jest.clearAllMocks(); + + // Replace console methods with mocks for integration tests + console.log = mockConsole.log; + console.warn = mockConsole.warn; + console.error = mockConsole.error; + + // Configure LogEngine for testing + LogEngine.configure({ mode: LogMode.DEBUG }); + }); + + afterAll(() => { + // Restore original environment + process.env = originalEnv; + + // Restore original console methods + console.log = originalConsole.log; + console.warn = originalConsole.warn; + console.error = originalConsole.error; + }); + + describe('Automatic redaction in log methods', () => { + test('should redact sensitive data in debug logs', () => { + const sensitiveData = { + username: 'john_doe', + password: 'secret123', + email: 'john@example.com' + }; + + LogEngine.debug('User authentication', sensitiveData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + // Should contain the message + expect(logCall).toContain('User authentication'); + // Should contain redacted password + expect(logCall).toContain('[REDACTED]'); + // Should not contain actual password + expect(logCall).not.toContain('secret123'); + // Should not contain actual email + expect(logCall).not.toContain('john@example.com'); + // Should contain non-sensitive data (username is not sensitive) + expect(logCall).toContain('john_doe'); }); - afterAll(() => { - // Restore original environment - process.env = originalEnv; - - // Restore original console methods - console.log = originalConsole.log; - console.warn = originalConsole.warn; - console.error = originalConsole.error; + test('should redact sensitive data in info logs', () => { + const apiData = { + endpoint: '/api/users', + apiKey: 'sk-1234567890', + response: 'success' + }; + + LogEngine.info('API call completed', apiData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('API call completed'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).not.toContain('sk-1234567890'); + expect(logCall).toContain('/api/users'); + expect(logCall).toContain('success'); + }); + + test('should redact sensitive data in warn logs', () => { + const warningData = { + message: 'Rate limit approaching', + token: 'bearer-token-123', + remainingCalls: 5 + }; + + LogEngine.warn('API warning', warningData); + + expect(mockConsole.warn).toHaveBeenCalledTimes(1); + const logCall = mockConsole.warn.mock.calls[0][0]; + + expect(logCall).toContain('API warning'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).not.toContain('bearer-token-123'); + expect(logCall).toContain('Rate limit approaching'); + expect(logCall).toContain('5'); + }); + + test('should redact sensitive data in error logs', () => { + const errorData = { + error: 'Authentication failed', + userPassword: 'user-secret', + attemptCount: 3 + }; + + LogEngine.error('Login error', errorData); + + expect(mockConsole.error).toHaveBeenCalledTimes(1); + const logCall = mockConsole.error.mock.calls[0][0]; + + expect(logCall).toContain('Login error'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).not.toContain('user-secret'); + expect(logCall).toContain('Authentication failed'); + expect(logCall).toContain('3'); + }); + + test('should redact sensitive data in log messages', () => { + const criticalData = { + system: 'authentication', + clientSecret: 'super-secret-key', + status: 'active' + }; + + LogEngine.log('System status', criticalData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('System status'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).not.toContain('super-secret-key'); + expect(logCall).toContain('authentication'); + expect(logCall).toContain('active'); + }); + }); + + describe('Raw methods bypass redaction', () => { + test('should not redact data in debugRaw', () => { + const sensitiveData = { + password: 'secret123', + apiKey: 'sk-1234567890' + }; + + LogEngine.debugRaw('Debug data', sensitiveData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('Debug data'); + expect(logCall).toContain('secret123'); + expect(logCall).toContain('sk-1234567890'); + expect(logCall).not.toContain('[REDACTED]'); + }); + + test('should not redact data in infoRaw', () => { + const sensitiveData = { email: 'test@example.com' }; + + LogEngine.infoRaw('Raw info', sensitiveData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('test@example.com'); + expect(logCall).not.toContain('[REDACTED]'); + }); + + test('withoutRedaction should bypass redaction', () => { + const sensitiveData = { + password: 'secret123', + token: 'jwt-token' + }; + + LogEngine.withoutRedaction().info('Bypass redaction', sensitiveData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('secret123'); + expect(logCall).toContain('jwt-token'); + expect(logCall).not.toContain('[REDACTED]'); }); + }); + + describe('Environment-based redaction control in LogEngine', () => { + test('should not redact in development environment', () => { + process.env.NODE_ENV = 'development'; + + // Reconfigure to pick up environment changes + LogEngine.configure({ mode: LogMode.DEBUG }); + + const sensitiveData = { password: 'secret123' }; + LogEngine.info('Dev environment', sensitiveData); - describe('Automatic redaction in log methods', () => { - test('should redact sensitive data in debug logs', () => { - const sensitiveData = { - username: 'john_doe', - password: 'secret123', - email: 'john@example.com' - }; - - LogEngine.debug('User authentication', sensitiveData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - // Should contain the message - expect(logCall).toContain('User authentication'); - // Should contain redacted password - expect(logCall).toContain('[REDACTED]'); - // Should not contain actual password - expect(logCall).not.toContain('secret123'); - // Should not contain actual email - expect(logCall).not.toContain('john@example.com'); - // Should contain non-sensitive data (username is not sensitive) - expect(logCall).toContain('john_doe'); - }); - - test('should redact sensitive data in info logs', () => { - const apiData = { - endpoint: '/api/users', - apiKey: 'sk-1234567890', - response: 'success' - }; - - LogEngine.info('API call completed', apiData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('API call completed'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).not.toContain('sk-1234567890'); - expect(logCall).toContain('/api/users'); - expect(logCall).toContain('success'); - }); - - test('should redact sensitive data in warn logs', () => { - const warningData = { - message: 'Rate limit approaching', - token: 'bearer-token-123', - remainingCalls: 5 - }; - - LogEngine.warn('API warning', warningData); - - expect(mockConsole.warn).toHaveBeenCalledTimes(1); - const logCall = mockConsole.warn.mock.calls[0][0]; - - expect(logCall).toContain('API warning'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).not.toContain('bearer-token-123'); - expect(logCall).toContain('Rate limit approaching'); - expect(logCall).toContain('5'); - }); - - test('should redact sensitive data in error logs', () => { - const errorData = { - error: 'Authentication failed', - userPassword: 'user-secret', - attemptCount: 3 - }; - - LogEngine.error('Login error', errorData); - - expect(mockConsole.error).toHaveBeenCalledTimes(1); - const logCall = mockConsole.error.mock.calls[0][0]; - - expect(logCall).toContain('Login error'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).not.toContain('user-secret'); - expect(logCall).toContain('Authentication failed'); - expect(logCall).toContain('3'); - }); - - test('should redact sensitive data in log messages', () => { - const criticalData = { - system: 'authentication', - clientSecret: 'super-secret-key', - status: 'active' - }; - - LogEngine.log('System status', criticalData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('System status'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).not.toContain('super-secret-key'); - expect(logCall).toContain('authentication'); - expect(logCall).toContain('active'); - }); + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('secret123'); + expect(logCall).not.toContain('[REDACTED]'); + }); + }); + + describe('Redaction configuration', () => { + test('should apply custom redaction configuration', () => { + LogEngine.configureRedaction({ + redactionText: '***CUSTOM***', + sensitiveFields: ['customField'] + }); + + const testData = { + customField: 'sensitive', + password: 'should-not-be-redacted', + normalField: 'normal' + }; + + LogEngine.info('Custom config test', testData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('***CUSTOM***'); + expect(logCall).toContain('should-not-be-redacted'); // Not in custom sensitive fields + expect(logCall).toContain('normal'); + expect(logCall).not.toContain('sensitive'); }); - describe('Raw methods bypass redaction', () => { - test('should not redact data in debugRaw', () => { - const sensitiveData = { - password: 'secret123', - apiKey: 'sk-1234567890' - }; - - LogEngine.debugRaw('Debug data', sensitiveData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('Debug data'); - expect(logCall).toContain('secret123'); - expect(logCall).toContain('sk-1234567890'); - expect(logCall).not.toContain('[REDACTED]'); - }); - - test('should not redact data in infoRaw', () => { - const sensitiveData = { email: 'test@example.com' }; - - LogEngine.infoRaw('Raw info', sensitiveData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('test@example.com'); - expect(logCall).not.toContain('[REDACTED]'); - }); - - test('withoutRedaction should bypass redaction', () => { - const sensitiveData = { - password: 'secret123', - token: 'jwt-token' - }; - - LogEngine.withoutRedaction().info('Bypass redaction', sensitiveData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('secret123'); - expect(logCall).toContain('jwt-token'); - expect(logCall).not.toContain('[REDACTED]'); - }); + test('should get current redaction config', () => { + const config = LogEngine.getRedactionConfig(); + + expect(config).toHaveProperty('enabled'); + expect(config).toHaveProperty('sensitiveFields'); + expect(config).toHaveProperty('redactionText'); + expect(Array.isArray(config.sensitiveFields)).toBe(true); }); + }); + + describe('Content truncation in LogEngine', () => { + test('should truncate long content in log messages', () => { + LogEngine.configureRedaction({ + maxContentLength: 50 + }); + + const longContent = 'A'.repeat(200); + const testData = { + content: longContent, + description: longContent + }; + + LogEngine.info('Truncation test', testData); - describe('Environment-based redaction control in LogEngine', () => { - test('should not redact in development environment', () => { - process.env.NODE_ENV = 'development'; - - // Reconfigure to pick up environment changes - LogEngine.configure({ mode: LogMode.DEBUG }); - - const sensitiveData = { password: 'secret123' }; - LogEngine.info('Dev environment', sensitiveData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('secret123'); - expect(logCall).not.toContain('[REDACTED]'); - }); + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('... [TRUNCATED]'); + expect(logCall).not.toContain(longContent); }); - describe('Redaction configuration', () => { - test('should apply custom redaction configuration', () => { - LogEngine.configureRedaction({ - redactionText: '***CUSTOM***', - sensitiveFields: ['customField'] - }); - - const testData = { - customField: 'sensitive', - password: 'should-not-be-redacted', - normalField: 'normal' - }; - - LogEngine.info('Custom config test', testData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('***CUSTOM***'); - expect(logCall).toContain('should-not-be-redacted'); // Not in custom sensitive fields - expect(logCall).toContain('normal'); - expect(logCall).not.toContain('sensitive'); - }); - - test('should get current redaction config', () => { - const config = LogEngine.getRedactionConfig(); - - expect(config).toHaveProperty('enabled'); - expect(config).toHaveProperty('sensitiveFields'); - expect(config).toHaveProperty('redactionText'); - expect(Array.isArray(config.sensitiveFields)).toBe(true); - }); + test('should not truncate short content', () => { + LogEngine.configureRedaction({ + maxContentLength: 100 + }); + + const shortContent = 'Short content'; + const testData = { + content: shortContent, + description: shortContent + }; + + LogEngine.info('No truncation test', testData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain(shortContent); + expect(logCall).not.toContain('... [TRUNCATED]'); + }); + }); + + describe('Complex integration scenarios', () => { + test('should handle mixed redaction and truncation', () => { + LogEngine.configureRedaction({ + redactionText: '***HIDDEN***', + maxContentLength: 30, + sensitiveFields: ['password', 'apiKey'], + contentFields: ['description', 'content'] + }); + + const complexData = { + user: 'john_doe', + password: 'secret123', + apiKey: 'sk-abcdef123456', + description: 'This is a very long description that should be truncated', + content: 'Short content', + publicInfo: 'visible data' + }; + + LogEngine.info('Complex scenario', complexData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('john_doe'); + expect(logCall).toContain('***HIDDEN***'); + expect(logCall).not.toContain('secret123'); + expect(logCall).not.toContain('sk-abcdef123456'); + expect(logCall).toContain('... [TRUNCATED]'); + expect(logCall).toContain('Short content'); + expect(logCall).toContain('visible data'); }); - describe('Content truncation in LogEngine', () => { - test('should truncate long content in log messages', () => { - LogEngine.configureRedaction({ - maxContentLength: 50 - }); - - const longContent = 'A'.repeat(200); - const testData = { - content: longContent, - description: longContent - }; - - LogEngine.info('Truncation test', testData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('... [TRUNCATED]'); - expect(logCall).not.toContain(longContent); - }); - - test('should not truncate short content', () => { - LogEngine.configureRedaction({ - maxContentLength: 100 - }); - - const shortContent = 'Short content'; - const testData = { - content: shortContent, - description: shortContent - }; - - LogEngine.info('No truncation test', testData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain(shortContent); - expect(logCall).not.toContain('... [TRUNCATED]'); - }); + test('should handle arrays with sensitive data', () => { + const arrayData = { + users: [ + { name: 'Alice', password: 'alice123', role: 'admin' }, + { name: 'Bob', password: 'bob456', role: 'user' } + ], + metadata: { + total: 2, + apiKey: 'sk-array-test' + } + }; + + LogEngine.info('Array test', arrayData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + expect(logCall).toContain('Alice'); + expect(logCall).toContain('Bob'); + expect(logCall).toContain('admin'); + expect(logCall).toContain('user'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).not.toContain('alice123'); + expect(logCall).not.toContain('bob456'); + expect(logCall).not.toContain('sk-array-test'); + expect(logCall).toContain('2'); }); - describe('Complex integration scenarios', () => { - test('should handle mixed redaction and truncation', () => { - LogEngine.configureRedaction({ - redactionText: '***HIDDEN***', - maxContentLength: 30, - sensitiveFields: ['password', 'apiKey'], - contentFields: ['description', 'content'] - }); - - const complexData = { - user: 'john_doe', - password: 'secret123', - apiKey: 'sk-abcdef123456', - description: 'This is a very long description that should be truncated', - content: 'Short content', - publicInfo: 'visible data' - }; - - LogEngine.info('Complex scenario', complexData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('john_doe'); - expect(logCall).toContain('***HIDDEN***'); - expect(logCall).not.toContain('secret123'); - expect(logCall).not.toContain('sk-abcdef123456'); - expect(logCall).toContain('... [TRUNCATED]'); - expect(logCall).toContain('Short content'); - expect(logCall).toContain('visible data'); - }); - - test('should handle arrays with sensitive data', () => { - const arrayData = { - users: [ - { name: 'Alice', password: 'alice123', role: 'admin' }, - { name: 'Bob', password: 'bob456', role: 'user' } - ], - metadata: { - total: 2, - apiKey: 'sk-array-test' - } - }; - - LogEngine.info('Array test', arrayData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - expect(logCall).toContain('Alice'); - expect(logCall).toContain('Bob'); - expect(logCall).toContain('admin'); - expect(logCall).toContain('user'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).not.toContain('alice123'); - expect(logCall).not.toContain('bob456'); - expect(logCall).not.toContain('sk-array-test'); - expect(logCall).toContain('2'); - }); - - test('should maintain proper log formatting with redaction', () => { - const testData = { - timestamp: '2025-01-01T00:00:00Z', - level: 'INFO', - password: 'secret123', - message: 'Test log entry' - }; - - LogEngine.info('Format test', testData); - - expect(mockConsole.log).toHaveBeenCalledTimes(1); - const logCall = mockConsole.log.mock.calls[0][0]; - - // Should maintain readable format - expect(typeof logCall).toBe('string'); - expect(logCall.length).toBeGreaterThan(0); - expect(logCall).toContain('Format test'); - expect(logCall).toContain('2025-01-01T00:00:00Z'); - expect(logCall).toContain('INFO'); - expect(logCall).toContain('[REDACTED]'); - expect(logCall).toContain('Test log entry'); - }); + test('should maintain proper log formatting with redaction', () => { + const testData = { + timestamp: '2025-01-01T00:00:00Z', + level: 'INFO', + password: 'secret123', + message: 'Test log entry' + }; + + LogEngine.info('Format test', testData); + + expect(mockConsole.log).toHaveBeenCalledTimes(1); + const logCall = mockConsole.log.mock.calls[0][0]; + + // Should maintain readable format + expect(typeof logCall).toBe('string'); + expect(logCall.length).toBeGreaterThan(0); + expect(logCall).toContain('Format test'); + expect(logCall).toContain('2025-01-01T00:00:00Z'); + expect(logCall).toContain('INFO'); + expect(logCall).toContain('[REDACTED]'); + expect(logCall).toContain('Test log entry'); }); + }); }); diff --git a/src/__tests__/redaction/smoke.test.ts b/src/__tests__/redaction/smoke.test.ts index 70de9f0..d3ddfba 100644 --- a/src/__tests__/redaction/smoke.test.ts +++ b/src/__tests__/redaction/smoke.test.ts @@ -5,24 +5,24 @@ import { DataRedactor, defaultRedactionConfig } from '../../redaction'; describe('Redaction - Basic Smoke Test', () => { - beforeEach(() => { - DataRedactor.updateConfig(defaultRedactionConfig); - }); + beforeEach(() => { + DataRedactor.updateConfig(defaultRedactionConfig); + }); - test('should redact password field', () => { - const testData = { password: 'secret123' }; - const result = DataRedactor.redactData(testData); - expect(result.password).toBe('[REDACTED]'); - }); + test('should redact password field', () => { + const testData = { password: 'secret123' }; + const result = DataRedactor.redactData(testData); + expect(result.password).toBe('[REDACTED]'); + }); - test('should not redact non-sensitive field', () => { - const testData = { username: 'john_doe' }; - const result = DataRedactor.redactData(testData); - expect(result.username).toBe('john_doe'); - }); + test('should not redact non-sensitive field', () => { + const testData = { username: 'john_doe' }; + const result = DataRedactor.redactData(testData); + expect(result.username).toBe('john_doe'); + }); - test('should handle null input', () => { - const result = DataRedactor.redactData(null); - expect(result).toBeNull(); - }); + test('should handle null input', () => { + const result = DataRedactor.redactData(null); + expect(result).toBeNull(); + }); }); diff --git a/src/__tests__/redaction/test-utils.ts b/src/__tests__/redaction/test-utils.ts index 624f240..1092098 100644 --- a/src/__tests__/redaction/test-utils.ts +++ b/src/__tests__/redaction/test-utils.ts @@ -5,59 +5,67 @@ // Mock console methods to capture output for integration tests export const mockConsole = { - log: jest.fn(), - warn: jest.fn(), - error: jest.fn() + log: jest.fn(), + warn: jest.fn(), + error: jest.fn() }; // Store original console methods export const originalConsole = { - log: console.log, - warn: console.warn, - error: console.error + log: console.log, + warn: console.warn, + error: console.error }; /** * Setup console mocks for testing */ export function setupConsoleMocks(): void { - jest.clearAllMocks(); - console.log = mockConsole.log; - console.warn = mockConsole.warn; - console.error = mockConsole.error; + jest.clearAllMocks(); + console.log = mockConsole.log; + console.warn = mockConsole.warn; + console.error = mockConsole.error; } /** * Restore original console methods */ export function restoreConsole(): void { - console.log = originalConsole.log; - console.warn = originalConsole.warn; - console.error = originalConsole.error; + console.log = originalConsole.log; + console.warn = originalConsole.warn; + console.error = originalConsole.error; } /** * Setup environment for testing */ export function setupTestEnvironment(): { originalEnv: NodeJS.ProcessEnv } { - const originalEnv = { ...process.env }; - return { originalEnv }; + const originalEnv = { ...process.env }; + return { originalEnv }; } /** * Restore environment after testing */ export function restoreEnvironment(originalEnv: NodeJS.ProcessEnv): void { - // Only remove environment variables that were added during the test - // (i.e., those not present in the originalEnv) - for (const key in process.env) { - if (!(key in originalEnv)) { - delete process.env[key]; - } + // Only remove environment variables that were added during the test + // (i.e., those not present in the originalEnv) + // Create a copy of the keys to avoid mutation during iteration + const currentEnvKeys = Object.keys(process.env); + for (const key of currentEnvKeys) { + if (!Object.prototype.hasOwnProperty.call(originalEnv, key)) { + // Safe deletion using explicit type assertion + delete (process.env as Record)[key]; } - - // Restore or update variables that existed originally - for (const key in originalEnv) { - process.env[key] = originalEnv[key]; + } + + // Restore or update variables that existed originally + // Iterate over originalEnv keys safely + const originalEnvKeys = Object.keys(originalEnv); + for (const key of originalEnvKeys) { + if (Object.prototype.hasOwnProperty.call(originalEnv, key)) { + // Safe assignment using explicit access + (process.env as Record)[key] = originalEnv[key]; } + } } diff --git a/src/__tests__/test-utils.ts b/src/__tests__/test-utils.ts index 3bb7bb4..ffede98 100644 --- a/src/__tests__/test-utils.ts +++ b/src/__tests__/test-utils.ts @@ -25,7 +25,7 @@ export const setupConsoleMocks = (): ConsoleMocks => { const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); const mockConsoleWarn = jest.spyOn(console, 'warn').mockImplementation(); const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(); - + return { mockConsoleLog, mockConsoleWarn, mockConsoleError }; }; diff --git a/src/formatter/colors.ts b/src/formatter/colors.ts index 68d08dd..dd50cc1 100644 --- a/src/formatter/colors.ts +++ b/src/formatter/colors.ts @@ -8,16 +8,16 @@ * Used to create colorized console output with consistent theming */ export const colors = { - reset: '\x1b[0m', // Reset all formatting - dim: '\x1b[2m', // Dim/faded text - red: '\x1b[31m', // Red text (errors) - yellow: '\x1b[33m', // Yellow text (warnings) - blue: '\x1b[34m', // Blue text (info) - magenta: '\x1b[35m', // Magenta text (debug) - cyan: '\x1b[36m', // Cyan text (timestamps) - white: '\x1b[37m', // White text (default) - gray: '\x1b[90m', // Gray text (timestamps) - green: '\x1b[32m' // Green text (log level) + reset: '\x1b[0m', // Reset all formatting + dim: '\x1b[2m', // Dim/faded text + red: '\x1b[31m', // Red text (errors) + yellow: '\x1b[33m', // Yellow text (warnings) + blue: '\x1b[34m', // Blue text (info) + magenta: '\x1b[35m', // Magenta text (debug) + cyan: '\x1b[36m', // Cyan text (timestamps) + white: '\x1b[37m', // White text (default) + gray: '\x1b[90m', // Gray text (timestamps) + green: '\x1b[32m' // Green text (log level) } as const; /** @@ -25,9 +25,9 @@ export const colors = { * Maps semantic meanings to specific colors for consistent theming */ export const colorScheme = { - timestamp: colors.gray, - timeString: colors.cyan, - system: colors.yellow, - data: colors.dim, - reset: colors.reset + timestamp: colors.gray, + timeString: colors.cyan, + system: colors.yellow, + data: colors.dim, + reset: colors.reset } as const; diff --git a/src/formatter/data-formatter.ts b/src/formatter/data-formatter.ts index adee0f7..53610cf 100644 --- a/src/formatter/data-formatter.ts +++ b/src/formatter/data-formatter.ts @@ -3,6 +3,8 @@ * Handles serialization and formatting of data objects in log messages */ +import { LogData } from '../types'; + /** * Converts input data to a readable string suitable for log output. * @@ -11,33 +13,33 @@ * @param data - The value to format for logging * @returns The formatted string representation of the input data */ -export function formatData(data: any): string { - if (data === null) { - return 'null'; - } - - if (data === undefined) { - return ''; - } - - if (typeof data === 'string') { - return data; - } - - if (typeof data === 'number' || typeof data === 'boolean') { - return String(data); - } - - if (typeof data === 'symbol') { - return data.toString(); - } - - try { - return JSON.stringify(data, null, 0); - } catch (error) { - // Fallback for objects that can't be stringified (e.g., circular references) - return '[Object]'; - } +export function formatData(data: LogData): string { + if (data === null) { + return 'null'; + } + + if (data === undefined) { + return ''; + } + + if (typeof data === 'string') { + return data; + } + + if (typeof data === 'number' || typeof data === 'boolean') { + return String(data); + } + + if (typeof data === 'symbol') { + return data.toString(); + } + + try { + return JSON.stringify(data, null, 0); + } catch { + // Fallback for objects that can't be stringified (e.g., circular references) + return '[Object]'; + } } /** @@ -48,9 +50,9 @@ export function formatData(data: any): string { * @returns The styled data string with color codes applied, or an empty string if `dataString` is falsy. */ export function styleData(dataString: string, colors: { data: string; reset: string }): string { - if (!dataString) { - return ''; - } - - return ` ${colors.data}${dataString}${colors.reset}`; + if (!dataString) { + return ''; + } + + return ` ${colors.data}${dataString}${colors.reset}`; } diff --git a/src/formatter/message-formatter.ts b/src/formatter/message-formatter.ts index 32c008b..020a7bd 100644 --- a/src/formatter/message-formatter.ts +++ b/src/formatter/message-formatter.ts @@ -3,7 +3,7 @@ * Handles the main log message formatting with colors, timestamps, and levels */ -import { LogLevel } from '../types'; +import { LogLevel, LogData } from '../types'; import { colors, colorScheme } from './colors'; import { getTimestampComponents, formatTimestamp } from './timestamp'; import { formatData, styleData } from './data-formatter'; @@ -13,7 +13,7 @@ import { formatData, styleData } from './data-formatter'; * Provides the main formatting functionality for log messages */ export class MessageFormatter { - /** + /** * Formats a log message with timestamp, level indicator, and appropriate coloring * Creates a structured log entry: [ISO_TIMESTAMP][LOCAL_TIME][LEVEL]: message [data] * @param level - The log level to format for @@ -21,74 +21,74 @@ export class MessageFormatter { * @param data - Optional data object to include in the log output * @returns Formatted string with ANSI colors and timestamps */ - static format(level: LogLevel, message: string, data?: any): string { - const { isoTimestamp, timeString } = getTimestampComponents(); - const timestamp = formatTimestamp(isoTimestamp, timeString, colorScheme); - - const levelName = this.getLevelName(level); - const levelColor = this.getLevelColor(level); - const coloredLevel = `${levelColor}[${levelName}]${colors.reset}`; - - // Format the base message - let formattedMessage = `${timestamp}${coloredLevel}: ${message}`; - - // Append data if provided - if (data !== undefined) { - const dataString = formatData(data); - const styledData = styleData(dataString, colorScheme); - formattedMessage += styledData; - } - - // Always reset colors at the end of the entire log line - return formattedMessage + colors.reset; + static format(level: LogLevel, message: string, data?: LogData): string { + const { isoTimestamp, timeString } = getTimestampComponents(); + const timestamp = formatTimestamp(isoTimestamp, timeString, colorScheme); + + const levelName = MessageFormatter.getLevelName(level); + const levelColor = MessageFormatter.getLevelColor(level); + const coloredLevel = `${levelColor}[${levelName}]${colors.reset}`; + + // Format the base message + let formattedMessage = `${timestamp}${coloredLevel}: ${message}`; + + // Append data if provided + if (data !== undefined) { + const dataString = formatData(data); + const styledData = styleData(dataString, colorScheme); + formattedMessage += styledData; } - /** + // Always reset colors at the end of the entire log line + return formattedMessage + colors.reset; + } + + /** * Formats a Log Engine system message with [LOG ENGINE] prefix instead of log levels * Used for internal messages like deprecation warnings that should be distinguished from user logs * @param message - The system message content to format * @returns Formatted string with ANSI colors, timestamps, and LOG ENGINE prefix */ - static formatSystemMessage(message: string): string { - const { isoTimestamp, timeString } = getTimestampComponents(); - const timestamp = formatTimestamp(isoTimestamp, timeString, colorScheme); - - const coloredLogEngine = `${colorScheme.system}[LOG ENGINE]${colors.reset}`; - const coloredMessage = `${colorScheme.system}${message}${colors.reset}`; - - return `${timestamp}${coloredLogEngine}: ${coloredMessage}`; - } + static formatSystemMessage(message: string): string { + const { isoTimestamp, timeString } = getTimestampComponents(); + const timestamp = formatTimestamp(isoTimestamp, timeString, colorScheme); + + const coloredLogEngine = `${colorScheme.system}[LOG ENGINE]${colors.reset}`; + const coloredMessage = `${colorScheme.system}${message}${colors.reset}`; + + return `${timestamp}${coloredLogEngine}: ${coloredMessage}`; + } - /** + /** * Converts LogLevel enum to human-readable string * @param level - The LogLevel to convert * @returns String representation of the log level */ - private static getLevelName(level: LogLevel): string { - switch (level) { - case LogLevel.DEBUG: return 'DEBUG'; - case LogLevel.INFO: return 'INFO'; - case LogLevel.WARN: return 'WARN'; - case LogLevel.ERROR: return 'ERROR'; - case LogLevel.LOG: return 'LOG'; - default: return 'UNKNOWN'; - } + private static getLevelName(level: LogLevel): string { + switch (level) { + case LogLevel.DEBUG: return 'DEBUG'; + case LogLevel.INFO: return 'INFO'; + case LogLevel.WARN: return 'WARN'; + case LogLevel.ERROR: return 'ERROR'; + case LogLevel.LOG: return 'LOG'; + default: return 'UNKNOWN'; } + } - /** + /** * Maps LogLevel to appropriate ANSI color code * Colors help quickly identify message severity in console output * @param level - The LogLevel to get color for * @returns ANSI color escape sequence */ - private static getLevelColor(level: LogLevel): string { - switch (level) { - case LogLevel.DEBUG: return colors.magenta; // Purple for debug info - case LogLevel.INFO: return colors.blue; // Blue for general info - case LogLevel.WARN: return colors.yellow; // Yellow for warnings - case LogLevel.ERROR: return colors.red; // Red for errors - case LogLevel.LOG: return colors.green; // Green for always-on log messages - default: return colors.white; // White for unknown levels - } + private static getLevelColor(level: LogLevel): string { + switch (level) { + case LogLevel.DEBUG: return colors.magenta; // Purple for debug info + case LogLevel.INFO: return colors.blue; // Blue for general info + case LogLevel.WARN: return colors.yellow; // Yellow for warnings + case LogLevel.ERROR: return colors.red; // Red for errors + case LogLevel.LOG: return colors.green; // Green for always-on log messages + default: return colors.white; // White for unknown levels } + } } diff --git a/src/formatter/timestamp.ts b/src/formatter/timestamp.ts index 2453b6b..b17ebe6 100644 --- a/src/formatter/timestamp.ts +++ b/src/formatter/timestamp.ts @@ -11,19 +11,19 @@ export function getTimestampComponents(): { isoTimestamp: string; timeString: string; -} { - const now = new Date(); - const isoTimestamp = now.toISOString(); - const timeString = now.toLocaleTimeString('en-US', { - hour: 'numeric', - minute: '2-digit', - hour12: true - }).replace(/\s+/g, ''); + } { + const now = new Date(); + const isoTimestamp = now.toISOString(); + const timeString = now.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + hour12: true + }).replace(/\s+/g, ''); - return { - isoTimestamp, - timeString - }; + return { + isoTimestamp, + timeString + }; } /** @@ -35,12 +35,12 @@ export function getTimestampComponents(): { * @returns The combined, colorized timestamp string suitable for log messages */ export function formatTimestamp( - isoTimestamp: string, - timeString: string, - colors: { timestamp: string; timeString: string; reset: string } + isoTimestamp: string, + timeString: string, + colors: { timestamp: string; timeString: string; reset: string } ): string { - const coloredTimestamp = `${colors.timestamp}[${isoTimestamp}]${colors.reset}`; - const coloredTimeString = `${colors.timeString}[${timeString}]${colors.reset}`; - - return `${coloredTimestamp}${coloredTimeString}`; + const coloredTimestamp = `${colors.timestamp}[${isoTimestamp}]${colors.reset}`; + const coloredTimeString = `${colors.timeString}[${timeString}]${colors.reset}`; + + return `${coloredTimestamp}${coloredTimeString}`; } diff --git a/src/index.ts b/src/index.ts index 33a189f..292cca4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,19 @@ /** * Main LogEngine module - provides a comprehensive logging solution * with mode-based filtering, colorized output, and automatic data redaction - * + * * Features a modular architecture with separate modules for: * - Logger: Core logging functionality with environment-based configuration - * - Formatter: Message formatting with ANSI colors and timestamps + * - Formatter: Message formatting with ANSI colors and timestamps * - Redaction: Automatic sensitive data protection with customizable patterns - * + * * @example * ```typescript * import { LogEngine, LogMode } from '@wgtechlabs/log-engine'; - * + * * // Configure logging mode * LogEngine.configure({ mode: LogMode.DEBUG }); - * + * * // Log with automatic redaction * LogEngine.info('User login', { username: 'john', password: 'secret123' }); * // Output: [2025-06-18T...][3:45PM][INFO]: User login { username: 'john', password: '[REDACTED]' } @@ -21,8 +21,7 @@ */ import { Logger } from './logger'; -import { LogMode } from './types'; -import type { LoggerConfig, RedactionConfig } from './types'; +import type { LoggerConfig, RedactionConfig, ILogEngineWithoutRedaction, LogData } from './types'; import { DataRedactor, defaultRedactionConfig } from './redaction'; // Create a singleton logger instance @@ -33,7 +32,7 @@ const logger = new Logger(); * Provides a simple, intuitive API for all logging needs with security-first design */ export const LogEngine = { - /** + /** * Configure the logger with new settings * @param config - Configuration object containing logger settings * @example @@ -41,10 +40,10 @@ export const LogEngine = { * LogEngine.configure({ mode: LogMode.PRODUCTION }); * ``` */ - configure: (config: Partial) => logger.configure(config), + configure: (config: Partial): void => logger.configure(config), - // Standard logging methods with automatic redaction - /** + // Standard logging methods with automatic redaction + /** * Log a debug message with automatic data redaction * Only shown in DEVELOPMENT mode * @param message - The debug message to log @@ -54,9 +53,9 @@ export const LogEngine = { * LogEngine.debug('Processing user data', { userId: 123, email: 'user@example.com' }); * ``` */ - debug: (message: string, data?: any) => logger.debug(message, data), + debug: (message: string, data?: LogData): void => logger.debug(message, data), - /** + /** * Log an info message with automatic data redaction * Shown in DEVELOPMENT and PRODUCTION modes * @param message - The info message to log @@ -66,9 +65,9 @@ export const LogEngine = { * LogEngine.info('User login successful', { username: 'john' }); * ``` */ - info: (message: string, data?: any) => logger.info(message, data), + info: (message: string, data?: LogData): void => logger.info(message, data), - /** + /** * Log a warning message with automatic data redaction * Shown in DEVELOPMENT and PRODUCTION modes * @param message - The warning message to log @@ -78,9 +77,9 @@ export const LogEngine = { * LogEngine.warn('API rate limit approaching', { requestsRemaining: 10 }); * ``` */ - warn: (message: string, data?: any) => logger.warn(message, data), + warn: (message: string, data?: LogData): void => logger.warn(message, data), - /** + /** * Log an error message with automatic data redaction * Shown in DEVELOPMENT and PRODUCTION modes * @param message - The error message to log @@ -90,111 +89,111 @@ export const LogEngine = { * LogEngine.error('Database connection failed', { host: 'localhost', port: 5432 }); * ``` */ - error: (message: string, data?: any) => logger.error(message, data), + error: (message: string, data?: LogData): void => logger.error(message, data), - /** + /** * Log a critical message with automatic data redaction * Always shown regardless of mode (except OFF) - * @param message - The critical log message to log + * @param message - The critical log message to log * @param data - Optional data object to log (sensitive data will be redacted) * @example * ```typescript * LogEngine.log('Application starting', { version: '1.0.0' }); * ``` */ - log: (message: string, data?: any) => logger.log(message, data), + log: (message: string, data?: LogData): void => logger.log(message, data), - // Raw methods that bypass redaction (use with caution) - /** + // Raw methods that bypass redaction (use with caution) + /** * Log a debug message without redaction (use with caution) * Bypasses automatic data redaction for debugging purposes * @param message - The debug message to log * @param data - Optional data object to log (no redaction applied) */ - debugRaw: (message: string, data?: any) => logger.debugRaw(message, data), + debugRaw: (message: string, data?: LogData): void => logger.debugRaw(message, data), - /** + /** * Log an info message without redaction (use with caution) * Bypasses automatic data redaction for debugging purposes * @param message - The info message to log * @param data - Optional data object to log (no redaction applied) */ - infoRaw: (message: string, data?: any) => logger.infoRaw(message, data), + infoRaw: (message: string, data?: LogData): void => logger.infoRaw(message, data), - /** + /** * Log a warning message without redaction (use with caution) * Bypasses automatic data redaction for debugging purposes * @param message - The warning message to log * @param data - Optional data object to log (no redaction applied) */ - warnRaw: (message: string, data?: any) => logger.warnRaw(message, data), + warnRaw: (message: string, data?: LogData): void => logger.warnRaw(message, data), - /** + /** * Log an error message without redaction (use with caution) * Bypasses automatic data redaction for debugging purposes * @param message - The error message to log * @param data - Optional data object to log (no redaction applied) */ - errorRaw: (message: string, data?: any) => logger.errorRaw(message, data), + errorRaw: (message: string, data?: LogData): void => logger.errorRaw(message, data), - /** + /** * Log a critical message without redaction (use with caution) * Bypasses automatic data redaction for debugging purposes * @param message - The critical log message to log * @param data - Optional data object to log (no redaction applied) */ - logRaw: (message: string, data?: any) => logger.logRaw(message, data), + logRaw: (message: string, data?: LogData): void => logger.logRaw(message, data), - // Redaction configuration methods - /** + // Redaction configuration methods + /** * Configure data redaction settings * @param config - Partial redaction configuration to apply */ - configureRedaction: (config: Partial) => DataRedactor.updateConfig(config), + configureRedaction: (config: Partial): void => DataRedactor.updateConfig(config), - /** + /** * Refresh redaction configuration from environment variables * Useful for picking up runtime environment changes */ - refreshRedactionConfig: () => DataRedactor.refreshConfig(), + refreshRedactionConfig: (): void => DataRedactor.refreshConfig(), - /** + /** * Reset redaction configuration to defaults */ - resetRedactionConfig: () => DataRedactor.updateConfig(defaultRedactionConfig), + resetRedactionConfig: (): void => DataRedactor.updateConfig(defaultRedactionConfig), - /** + /** * Get current redaction configuration * @returns Current redaction configuration */ - getRedactionConfig: () => DataRedactor.getConfig(), + getRedactionConfig: (): RedactionConfig => DataRedactor.getConfig(), - // Advanced redaction methods (Phase 3) - /** + // Advanced redaction methods + /** * Add custom regex patterns for advanced field detection * @param patterns - Array of regex patterns to add */ - addCustomRedactionPatterns: (patterns: RegExp[]) => DataRedactor.addCustomPatterns(patterns), + addCustomRedactionPatterns: (patterns: RegExp[]): void => DataRedactor.addCustomPatterns(patterns), - /** + /** * Clear all custom redaction patterns */ - clearCustomRedactionPatterns: () => DataRedactor.clearCustomPatterns(), + clearCustomRedactionPatterns: (): void => DataRedactor.clearCustomPatterns(), - /** + /** * Add custom sensitive field names to the existing list * @param fields - Array of field names to add */ - addSensitiveFields: (fields: string[]) => DataRedactor.addSensitiveFields(fields), + addSensitiveFields: (fields: string[]): void => DataRedactor.addSensitiveFields(fields), - /** + /** * Test if a field name would be redacted with current configuration * @param fieldName - Field name to test * @returns true if field would be redacted, false otherwise */ - testFieldRedaction: (fieldName: string) => DataRedactor.testFieldRedaction(fieldName), + testFieldRedaction: (fieldName: string): boolean => DataRedactor.testFieldRedaction(fieldName), - /** + /** * Temporarily disable redaction for a specific logging call * @returns LogEngine instance with redaction bypassed * @example @@ -202,19 +201,30 @@ export const LogEngine = { * LogEngine.withoutRedaction().info('Debug data', sensitiveObject); * ``` */ - withoutRedaction: () => ({ - debug: (message: string, data?: any) => logger.debugRaw(message, data), - info: (message: string, data?: any) => logger.infoRaw(message, data), - warn: (message: string, data?: any) => logger.warnRaw(message, data), - error: (message: string, data?: any) => logger.errorRaw(message, data), - log: (message: string, data?: any) => logger.logRaw(message, data) - }) + withoutRedaction: (): ILogEngineWithoutRedaction => ({ + debug: (message: string, data?: LogData): void => logger.debugRaw(message, data), + info: (message: string, data?: LogData): void => logger.infoRaw(message, data), + warn: (message: string, data?: LogData): void => logger.warnRaw(message, data), + error: (message: string, data?: LogData): void => logger.errorRaw(message, data), + log: (message: string, data?: LogData): void => logger.logRaw(message, data) + }) }; // Re-export types and utilities for external use export { LogMode, LogLevel } from './types'; -export type { LoggerConfig, RedactionConfig } from './types'; +export type { + LoggerConfig, + RedactionConfig, + LogOutputHandler, + BuiltInOutputHandler, + OutputTarget, + // Advanced types + FileOutputConfig, + HttpOutputConfig, + AdvancedOutputConfig, + EnhancedOutputTarget +} from './types'; export { DataRedactor, defaultRedactionConfig, RedactionController } from './redaction'; // Default export for convenience -export default LogEngine; \ No newline at end of file +export default LogEngine; diff --git a/src/logger/advanced-outputs.ts b/src/logger/advanced-outputs.ts new file mode 100644 index 0000000..607ee79 --- /dev/null +++ b/src/logger/advanced-outputs.ts @@ -0,0 +1,629 @@ +/** + * Advanced output handlers for log-engine + * Provides file, HTTP, and other production-ready output handlers + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import type { FileOutputConfig, HttpOutputConfig } from '../types'; + +// Type definitions for HTTP operations +interface LogEntry { + level: string; + message: string; + data?: unknown; + timestamp: string; +} + +interface HttpPayload { + logs: LogEntry[]; +} + +interface HttpRequestOptions { + hostname: string; + port: number; + path: string; + method: string; + headers: Record; + timeout: number; +} + +/** + * Secure filesystem operations for logging operations + * + * SECURITY NOTE: These functions implement comprehensive path validation and access controls + * to prevent path traversal attacks, directory injection, and unauthorized file access. + * ESLint security rules are disabled for specific fs operations because: + * + * 1. All paths are validated through validatePath() which: + * - Prevents directory traversal (../) + * - Restricts access to predefined safe directories + * - Blocks access to system directories + * - Normalizes and resolves paths securely + * + * 2. The logging library requires dynamic file paths by design (user-configurable log files) + * 3. All operations are wrapped in try-catch with comprehensive error handling + * 4. File operations are restricted to log and temp directories only + */ + +/** + * Predefined safe base directories for different operation types + * Restricted to specific subdirectories to prevent unauthorized access + */ +const SAFE_BASE_DIRS = { + LOG_FILES: [path.resolve('./logs'), path.resolve('./var/log'), path.resolve('./data/logs')], + TEMP_FILES: [path.resolve('./temp'), path.resolve('./logs'), path.resolve('./tmp'), os.tmpdir()], + CONFIG_FILES: [path.resolve('./config'), path.resolve('./etc'), path.resolve('./logs')] +} as const; + +/** + * Validates file path with comprehensive security checks + * Prevents path traversal, restricts to safe directories, blocks system paths + */ +function validatePath(filePath: string): string { + if (!filePath || typeof filePath !== 'string') { + throw new Error('File path must be a non-empty string'); + } + + // Resolve and normalize the path to handle relative paths and traversal attempts + const resolvedPath = path.resolve(filePath); + const normalizedPath = path.normalize(resolvedPath); + + // Check for path traversal attempts in original path - reject ANY use of '..' + if (filePath.includes('..')) { + throw new Error(`Path traversal detected: ${filePath}`); + } + + // Ensure path is within safe directories (logs, current directory, or temp) + const safeBaseDirs = [ + ...SAFE_BASE_DIRS.LOG_FILES, + ...SAFE_BASE_DIRS.TEMP_FILES, + ...SAFE_BASE_DIRS.CONFIG_FILES + ]; + + const isInSafeDir = safeBaseDirs.some(safeDir => normalizedPath.startsWith(safeDir)); + if (!isInSafeDir) { + throw new Error(`File path outside allowed directories: ${filePath}`); + } + + // Block access to dangerous system directories + const dangerousPaths = [ + '/etc', '/sys', '/proc', '/dev', '/root', '/bin', '/sbin', + 'C:\\Windows', 'C:\\System32', 'C:\\Program Files', 'C:\\Users\\All Users' + ]; + + if (dangerousPaths.some(dangerous => normalizedPath.startsWith(dangerous))) { + throw new Error(`Access denied to system directory: ${filePath}`); + } + + return normalizedPath; +} + +/** + * Secure file existence check + * Uses fs.accessSync instead of fs.existsSync for better security practices + */ +function secureExistsSync(filePath: string): boolean { + try { + const safePath = validatePath(filePath); + // SECURITY: Path has been validated and restricted to safe directories + fs.accessSync(safePath, fs.constants.F_OK); + return true; + } catch { + return false; + } +} + +/** + * Secure directory creation with recursive option support + * Restricted to log and temp directories only + */ +function secureMkdirSync(dirPath: string, options?: { recursive?: boolean }): void { + const safePath = validatePath(dirPath); + + try { + const mkdirOptions = { recursive: Boolean(options?.recursive) }; + // SECURITY: Path has been validated and restricted to safe directories + fs.mkdirSync(safePath, mkdirOptions); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to create directory ${dirPath}: ${errorMessage}`); + } +} + +/** + * Secure file stat operation + * Returns file system statistics for validated paths only + */ +function secureStatSync(filePath: string): fs.Stats { + const safePath = validatePath(filePath); + + try { + // SECURITY: Path has been validated and restricted to safe directories + return fs.statSync(safePath); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to stat file ${filePath}: ${errorMessage}`); + } +} + +/** + * Secure file write operation + * Validates path and data before writing to prevent injection attacks + */ +function secureWriteFileSync(filePath: string, data: string, options?: { flag?: string }): void { + const safePath = validatePath(filePath); + + // Validate data parameter + if (typeof data !== 'string') { + throw new Error('Data must be a string for security'); + } + + try { + const writeOptions = options || {}; + // SECURITY: Path has been validated and restricted to safe directories + fs.writeFileSync(safePath, data, writeOptions); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to write file ${filePath}: ${errorMessage}`); + } +} + +/** + * Secure file deletion + * Restricted to log and temp files only for safety + */ +function secureUnlinkSync(filePath: string): void { + const safePath = validatePath(filePath); + + // Additional safety check: only allow deletion of log and temp files + const logDirs = [...SAFE_BASE_DIRS.LOG_FILES, ...SAFE_BASE_DIRS.TEMP_FILES]; + const isInLogDir = logDirs.some(logDir => safePath.startsWith(logDir)); + + if (!isInLogDir) { + throw new Error(`File deletion not allowed outside log/temp directories: ${filePath}`); + } + + try { + // SECURITY: Path has been validated and restricted to log/temp directories + fs.unlinkSync(safePath); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to delete file ${filePath}: ${errorMessage}`); + } +} + +/** + * Secure file rename/move operation + * Both source and destination must be in safe directories + */ +function secureRenameSync(oldPath: string, newPath: string): void { + const safeOldPath = validatePath(oldPath); + const safeNewPath = validatePath(newPath); + + // Ensure both paths are in allowed directories (log/temp only for safety) + const allowedDirs = [...SAFE_BASE_DIRS.LOG_FILES, ...SAFE_BASE_DIRS.TEMP_FILES]; + const oldInAllowed = allowedDirs.some(dir => safeOldPath.startsWith(dir)); + const newInAllowed = allowedDirs.some(dir => safeNewPath.startsWith(dir)); + + if (!oldInAllowed || !newInAllowed) { + throw new Error(`File rename not allowed outside safe directories: ${oldPath} -> ${newPath}`); + } + + try { + // SECURITY: Both paths have been validated and restricted to safe directories + fs.renameSync(safeOldPath, safeNewPath); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to rename file ${oldPath} to ${newPath}: ${errorMessage}`); + } +} + +/** + * File output handler with rotation support and concurrency protection + * Implements atomic file operations and write queuing to prevent corruption + */ +export class FileOutputHandler { + private config: Required; + private currentFileSize: number = 0; + private rotationInProgress: boolean = false; + private writeQueue: Array<{ level: string; message: string; data?: unknown }> = []; + + constructor(config: FileOutputConfig) { + // Set defaults + this.config = { + filePath: config.filePath, + append: config.append ?? true, + maxFileSize: config.maxFileSize ?? 0, // 0 means no rotation + maxBackupFiles: config.maxBackupFiles ?? 3, + formatter: config.formatter ?? this.defaultFormatter + }; + + // Ensure directory exists and validate paths + try { + const dir = path.dirname(this.config.filePath); + + if (!secureExistsSync(dir)) { + secureMkdirSync(dir, { recursive: true }); + } + + // Get current file size if it exists + if (secureExistsSync(this.config.filePath)) { + this.currentFileSize = secureStatSync(this.config.filePath).size; + } + } catch (error) { + // Re-throw with context for better error handling + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to initialize file output handler: ${errorMessage}`); + } + } + + /** + * Default formatter for file output + */ + private defaultFormatter = (level: string, message: string, data?: unknown): string => { + const timestamp = new Date().toISOString(); + const dataStr = data ? ` ${JSON.stringify(data)}` : ''; + return `${timestamp} [${level.toUpperCase()}] ${message}${dataStr}\n`; + }; + + /** + * Write log to file with rotation support and concurrency protection + * Queues writes during rotation to prevent file corruption + */ + public write = (level: string, message: string, data?: unknown): void => { + // If rotation is in progress, queue the write + if (this.rotationInProgress) { + this.writeQueue.push({ level, message, data }); + return; + } + + try { + this.writeToFile(level, message, data); + } catch (error) { + // Fallback to console if file writing fails + console.error('File output handler failed:', error); + console.log(`[${level.toUpperCase()}] ${message}`, data); + } + }; + + /** + * Write to file with concurrency protection and rotation check + * If rotation is in progress, messages are queued to prevent corruption + */ + private writeToFile(level: string, message: string, data?: unknown): void { + const formattedMessage = this.config.formatter(level, message, data); + + // Check if rotation is needed + if (this.config.maxFileSize > 0 && + this.currentFileSize + Buffer.byteLength(formattedMessage) > this.config.maxFileSize) { + this.rotateFile(); + } + + // Write to file using secure filesystem wrapper + const writeOptions = this.config.append ? { flag: 'a' } : { flag: 'w' }; + secureWriteFileSync(this.config.filePath, formattedMessage, writeOptions); + + this.currentFileSize += Buffer.byteLength(formattedMessage); + } + + /** + * Process queued writes after rotation completes + */ + private processWriteQueue(): void { + while (this.writeQueue.length > 0) { + const queuedWrite = this.writeQueue.shift(); + if (queuedWrite) { + try { + this.writeToFile(queuedWrite.level, queuedWrite.message, queuedWrite.data); + } catch (error) { + console.error('Failed to process queued write:', error); + console.log(`[${queuedWrite.level.toUpperCase()}] ${queuedWrite.message}`, queuedWrite.data); + } + } + } + } + + /** + * Rotate log files when size limit is reached + * Implements concurrency protection to prevent corruption during rotation + */ + private rotateFile(): void { + // Prevent concurrent rotations + if (this.rotationInProgress) { + return; + } + + this.rotationInProgress = true; + + try { + // Move backup files + for (let i = this.config.maxBackupFiles - 1; i >= 1; i--) { + const oldFile = `${this.config.filePath}.${i}`; + const newFile = `${this.config.filePath}.${i + 1}`; + + if (secureExistsSync(oldFile)) { + if (i === this.config.maxBackupFiles - 1) { + // Delete the oldest file + secureUnlinkSync(oldFile); + } else { + secureRenameSync(oldFile, newFile); + } + } + } + + // Move current file to .1 + if (secureExistsSync(this.config.filePath)) { + const backupFile = `${this.config.filePath}.1`; + secureRenameSync(this.config.filePath, backupFile); + } + + this.currentFileSize = 0; + } catch (error) { + console.error('File rotation failed:', error); + } finally { + // Always reset rotation flag and process queued writes + this.rotationInProgress = false; + this.processWriteQueue(); + } + } + + /** + * Clean up resources and process any remaining queued writes + */ + public destroy(): void { + // Process any remaining queued writes + if (this.writeQueue.length > 0) { + this.processWriteQueue(); + } + + // Clear the write queue + this.writeQueue = []; + this.rotationInProgress = false; + } +} + +/** + * HTTP output handler for sending logs to remote endpoints + */ +export class HttpOutputHandler { + private config: Required; + private logBuffer: LogEntry[] = []; + private flushTimeout: NodeJS.Timeout | null = null; + + constructor(config: HttpOutputConfig) { + // Set defaults + this.config = { + url: config.url, + method: config.method ?? 'POST', + headers: config.headers ?? { 'Content-Type': 'application/json' }, + batchSize: config.batchSize ?? 1, + timeout: config.timeout ?? 5000, + formatter: config.formatter ?? this.defaultFormatter + }; + } + + /** + * Default formatter for HTTP output + */ + private defaultFormatter = (logs: LogEntry[]): HttpPayload => { + return { + logs: logs.map(log => ({ + timestamp: log.timestamp, + level: log.level, + message: log.message, + data: log.data + })) + }; + }; + + /** + * Write log to HTTP endpoint with batching support + */ + public write = (level: string, message: string, data?: unknown): void => { + try { + // Add to buffer + this.logBuffer.push({ + level, + message, + data, + timestamp: new Date().toISOString() + }); + + // Flush if batch size reached + if (this.logBuffer.length >= this.config.batchSize) { + this.flush(); + } else { + // Schedule a flush if not already scheduled + if (!this.flushTimeout) { + this.flushTimeout = setTimeout(() => { + this.flush(); + }, 1000); // Flush after 1 second if batch isn't full + } + } + } catch (error) { + // Fallback to console if HTTP fails + console.error('HTTP output handler failed:', error); + console.log(`[${level.toUpperCase()}] ${message}`, data); + } + }; + + /** + * Flush buffered logs to HTTP endpoint + */ + private flush(): void { + if (this.logBuffer.length === 0) { + return; + } + + try { + const payload = this.config.formatter([...this.logBuffer]); + this.logBuffer = []; // Clear buffer + + if (this.flushTimeout) { + clearTimeout(this.flushTimeout); + this.flushTimeout = null; + } + + // Send HTTP request (using fetch if available, otherwise fall back) + this.sendHttpRequest(payload); + } catch (error) { + console.error('HTTP flush failed:', error); + } + } + + /** + * Send HTTP request with appropriate method based on environment + */ + private sendHttpRequest(payload: HttpPayload): void { + // Try to use fetch (Node.js 18+ or browser) + if (typeof fetch !== 'undefined') { + fetch(this.config.url, { + method: this.config.method, + headers: this.config.headers, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(this.config.timeout) + }).catch(error => { + console.error('HTTP request failed:', error); + }); + } else { + // Fallback for older Node.js versions + this.sendHttpRequestNodeJS(payload); + } + } + + /** + * Fallback HTTP implementation for Node.js environments without fetch + */ + private sendHttpRequestNodeJS(payload: HttpPayload): void { + try { + const https = require('https'); + + const parsedUrl = new URL(this.config.url); + const isHttps = parsedUrl.protocol === 'https:'; + + // Security: Block HTTP (cleartext) connections by default + if (!isHttps) { + throw new Error('SECURITY ERROR: HTTP (cleartext) connections are not allowed for log transmission. Use HTTPS URLs only.'); + } + + const postData = JSON.stringify(payload); + + const options: HttpRequestOptions = { + hostname: parsedUrl.hostname, + port: parsedUrl.port ? parseInt(parsedUrl.port, 10) : 443, + path: parsedUrl.pathname + parsedUrl.search, + method: this.config.method, + headers: { + ...this.config.headers, + 'Content-Length': Buffer.byteLength(postData) + }, + timeout: this.config.timeout + }; + + const req = https.request(options, (res: NodeJS.ReadableStream) => { + // Handle response (optional: log success/failure) + res.on('data', () => {}); // Consume response + res.on('end', () => {}); + }); + + req.on('error', (error: Error) => { + console.error('HTTP request failed:', error); + }); + + req.on('timeout', () => { + req.destroy(); + console.error('HTTP request timed out'); + }); + + req.write(postData); + req.end(); + } catch (error) { + console.error('HTTP request setup failed:', error); + } + } + + /** + * Cleanup method to prevent memory leaks + */ + public destroy(): void { + if (this.flushTimeout) { + clearTimeout(this.flushTimeout); + this.flushTimeout = null; + } + // Flush any remaining logs + this.flush(); + } +} + +/** + * Returns a logging handler function based on the specified type and configuration. + * + * Supported types are: + * - `'console'`: Logs to the console using the appropriate method for the log level. + * - `'silent'`: Returns a no-op handler that discards all logs. + * - `'file'`: Writes logs to a file with optional rotation; requires `filePath` in config. + * - `'http'`: Sends logs to a remote HTTP endpoint; requires `url` in config. + * + * If required configuration is missing or initialization fails, logs an error and returns either a fallback handler or `null`. + * + * @param type - The type of output handler to create (`'console'`, `'silent'`, `'file'`, or `'http'`) + * @returns A log handler function or `null` if the handler cannot be created + */ +export function createBuiltInHandler(type: string, config?: Record): ((level: string, message: string, data?: unknown) => void) | null { + switch (type) { + case 'console': + return (level: string, message: string, data?: unknown) => { + const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'; + // Use safe method call to prevent object injection + if (Object.prototype.hasOwnProperty.call(console, method) && typeof console[method as keyof Console] === 'function') { + (console[method as keyof Console] as (...args: unknown[]) => void)(message, data); + } else { + console.log(message, data); + } + }; + + case 'silent': + return () => {}; // No-op handler + + case 'file': + if (config && typeof config.filePath === 'string') { + try { + const handler = new FileOutputHandler(config as unknown as FileOutputConfig); + return handler.write; + } catch (error) { + // Return a handler that logs the expected error message and falls back to console + return (level: string, message: string, data?: unknown) => { + console.error('File output handler failed:', error); + console.log(`[${level.toUpperCase()}] ${message}`, data); + }; + } + } + console.error('File output handler requires filePath in config'); + return null; + + case 'http': + if (config && typeof config.url === 'string') { + const handler = new HttpOutputHandler(config as unknown as HttpOutputConfig); + return handler.write; + } + console.error('HTTP output handler requires url in config'); + return null; + + default: + return null; + } +} + +// Export secure filesystem functions for testing +export { + secureExistsSync, + secureMkdirSync, + secureStatSync, + secureWriteFileSync, + secureUnlinkSync, + secureRenameSync, + validatePath, + SAFE_BASE_DIRS +}; diff --git a/src/logger/config.ts b/src/logger/config.ts index 11ce2af..eb8538c 100644 --- a/src/logger/config.ts +++ b/src/logger/config.ts @@ -11,99 +11,101 @@ import { EnvironmentDetector } from './environment'; * Handles configuration validation, updates, and backward compatibility */ export class LoggerConfigManager { - private config: LoggerConfig; + private config: LoggerConfig; - constructor() { - // Set initial configuration with environment-based auto-configuration - this.config = { - mode: EnvironmentDetector.getEnvironmentMode() - }; - } + constructor() { + // Set initial configuration with environment-based auto-configuration + this.config = { + mode: EnvironmentDetector.getEnvironmentMode() + }; + } - /** + /** * Get current configuration * @returns Current logger configuration */ - getConfig(): LoggerConfig { - return { ...this.config }; - } + getConfig(): LoggerConfig { + return { ...this.config }; + } - /** + /** * Updates logger configuration with new settings * Merges provided config with existing settings (partial update) * Supports backwards compatibility by mapping level to mode with deprecation warnings * @param config - Partial configuration object to apply */ - updateConfig(config: Partial): void { - // Handle backwards compatibility - if level is provided but mode is not - if (config.level !== undefined && config.mode === undefined) { - this.handleLegacyLevelConfig(config); - } else { - // Normal configuration update - // If mode is present, remove legacy level property to avoid conflicts - if (config.mode !== undefined && config.level !== undefined) { - const { level, ...configWithoutLevel } = config; - this.config = { ...this.config, ...configWithoutLevel }; - } else { - this.config = { ...this.config, ...config }; - } - } + updateConfig(config: Partial): void { + // Handle backwards compatibility - if level is provided but mode is not + if (config.level !== undefined && config.mode === undefined) { + this.handleLegacyLevelConfig(config); + } else { + // Normal configuration update + // If mode is present, remove legacy level property to avoid conflicts + if (config.mode !== undefined && config.level !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { level, ...configWithoutLevel } = config; + this.config = { ...this.config, ...configWithoutLevel }; + } else { + this.config = { ...this.config, ...config }; + } } + } - /** + /** * Handle legacy level-based configuration with deprecation warnings * @param config - Configuration containing legacy level property */ - private handleLegacyLevelConfig(config: Partial): void { - // Map legacy level values to new LogMode values and validate first - const levelValue = config.level as number; - const mappedMode = this.mapLevelToMode(levelValue); - - // Fail fast if the level value is invalid - if (mappedMode === undefined) { - throw new Error(`Invalid LogLevel value: ${config.level}. Valid values are: DEBUG(0), INFO(1), WARN(2), ERROR(3), LOG(99), or use LogMode instead.`); - } - - // Only show deprecation warning after confirming the level is valid and in non-test environments - if (!EnvironmentDetector.isTestEnvironment()) { - this.createDeprecationWarning(); - } - - // Merge existing config with all keys from the passed config, and override mode with mapped value - // Remove the legacy 'level' property to avoid conflicts with the new 'mode' property - const { level, ...configWithoutLevel } = config; - this.config = { ...this.config, ...configWithoutLevel, mode: mappedMode }; + private handleLegacyLevelConfig(config: Partial): void { + // Map legacy level values to new LogMode values and validate first + const levelValue = config.level as number; + const mappedMode = this.mapLevelToMode(levelValue); + + // Fail fast if the level value is invalid + if (mappedMode === undefined) { + throw new Error(`Invalid LogLevel value: ${config.level}. Valid values are: DEBUG(0), INFO(1), WARN(2), ERROR(3), LOG(99), or use LogMode instead.`); } - /** + // Only show deprecation warning after confirming the level is valid and in non-test environments + if (!EnvironmentDetector.isTestEnvironment()) { + this.createDeprecationWarning(); + } + + // Merge existing config with all keys from the passed config, and override mode with mapped value + // Remove the legacy 'level' property to avoid conflicts with the new 'mode' property + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { level, ...configWithoutLevel } = config; + this.config = { ...this.config, ...configWithoutLevel, mode: mappedMode }; + } + + /** * Map legacy LogLevel values to LogMode values * @param levelValue - Legacy level value * @returns Corresponding LogMode or undefined if invalid */ - private mapLevelToMode(levelValue: number): LogMode | undefined { - const levelToModeMap: Record = { - [LogLevel.DEBUG]: LogMode.DEBUG, // 0 -> 0 - [LogLevel.INFO]: LogMode.INFO, // 1 -> 1 - [LogLevel.WARN]: LogMode.WARN, // 2 -> 2 - [LogLevel.ERROR]: LogMode.ERROR, // 3 -> 3 - [LogLevel.LOG]: LogMode.SILENT, // 99 -> 4 (preserves critical-only behavior) - 4: LogMode.SILENT, // Legacy SILENT -> 4 - 5: LogMode.OFF // Legacy OFF -> 5 - }; - - return levelToModeMap[levelValue]; + private mapLevelToMode(levelValue: number): LogMode | undefined { + // Use switch statement to avoid object injection + switch (levelValue) { + case LogLevel.DEBUG: return LogMode.DEBUG; // 0 -> 0 + case LogLevel.INFO: return LogMode.INFO; // 1 -> 1 + case LogLevel.WARN: return LogMode.WARN; // 2 -> 2 + case LogLevel.ERROR: return LogMode.ERROR; // 3 -> 3 + case LogLevel.LOG: return LogMode.SILENT; // 99 -> 4 (preserves critical-only behavior) + case 4: return LogMode.SILENT; // Legacy SILENT -> 4 + case 5: return LogMode.OFF; // Legacy OFF -> 5 + default: return undefined; } + } - /** + /** * Create deprecation warning message using LogFormatter * Outputs formatted deprecation warning messages to console */ - private createDeprecationWarning(): void { - // Import LogFormatter to format system messages properly - const { LogFormatter } = require('../formatter'); - - console.warn(LogFormatter.formatSystemMessage('⚠️ DEPRECATION WARNING: The "level" configuration is deprecated and will be removed in v2.0.0. Please use "mode" instead.')); - console.warn(LogFormatter.formatSystemMessage(' Migration: LogEngine.configure({ level: LogLevel.DEBUG }) → LogEngine.configure({ mode: LogMode.DEBUG })')); - console.warn(LogFormatter.formatSystemMessage(' See: https://github.com/wgtechlabs/log-engine#migration-guide-loglevel--logmode')); - } + private createDeprecationWarning(): void { + // Import LogFormatter to format system messages properly + const { LogFormatter } = require('../formatter'); + + console.warn(LogFormatter.formatSystemMessage('⚠️ DEPRECATION WARNING: The "level" configuration is deprecated and will be removed in v3.0.0. Please use "mode" instead.')); + console.warn(LogFormatter.formatSystemMessage(' Migration: LogEngine.configure({ level: LogLevel.DEBUG }) → LogEngine.configure({ mode: LogMode.DEBUG })')); + console.warn(LogFormatter.formatSystemMessage(' See: https://github.com/wgtechlabs/log-engine#migration-guide-loglevel--logmode')); + } } diff --git a/src/logger/core.ts b/src/logger/core.ts index df54572..7ef0997 100644 --- a/src/logger/core.ts +++ b/src/logger/core.ts @@ -6,121 +6,307 @@ * Includes automatic data redaction for sensitive information */ -import { LogLevel, LogMode, LoggerConfig } from '../types'; +import { LogLevel, LogMode, LoggerConfig, LogOutputHandler, OutputTarget, EnhancedOutputTarget, LogData } from '../types'; import { LogFormatter } from '../formatter'; import { DataRedactor, RedactionController, defaultRedactionConfig } from '../redaction'; import { LoggerConfigManager } from './config'; import { LogFilter } from './filtering'; +import { createBuiltInHandler } from './advanced-outputs'; /** * Logger class responsible for managing log output and configuration * Provides mode-based filtering and formatted console output */ export class Logger { - private configManager: LoggerConfigManager; + private configManager: LoggerConfigManager; - /** + /** * Logger constructor - sets up environment-based auto-configuration */ - constructor() { - this.configManager = new LoggerConfigManager(); + constructor() { + this.configManager = new LoggerConfigManager(); + } + + /** + * Built-in output handlers for common use cases + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private getBuiltInHandler(type: string, config?: any): LogOutputHandler | null { + switch (type) { + case 'console': + return (level: string, message: string, data?: LogData) => { + // Use appropriate console method based on level + if (level === 'error') { + if (data !== undefined) { + console.error(message, data); + } else { + console.error(message); + } + } else if (level === 'warn') { + if (data !== undefined) { + console.warn(message, data); + } else { + console.warn(message); + } + } else { + if (data !== undefined) { + console.log(message, data); + } else { + console.log(message); + } + } + }; + case 'silent': + return () => { + // Do nothing - silent output + }; + case 'file': + case 'http': + // Use advanced output handlers for file and http + return createBuiltInHandler(type, config); + default: + return null; + } + } + + /** + * Process a single output target with error handling + * @param output - Single output target to process + * @param level - Log level + * @param rawMessage - Original unformatted message + * @param formattedMessage - Formatted message for console-based outputs + * @param data - Optional data + * @param isEnhanced - Whether this is an enhanced output (supports configured handler objects) + */ + private processSingleOutput( + output: OutputTarget | EnhancedOutputTarget, + level: string, + rawMessage: string, + formattedMessage: string, + data?: LogData, + isEnhanced = false + ): void { + const config = this.configManager.getConfig(); + + try { + if (typeof output === 'string') { + // Built-in handler - get config if available + const outputConfig = config.advancedOutputConfig?.[output as keyof typeof config.advancedOutputConfig]; + const handler = this.getBuiltInHandler(output, outputConfig); + if (handler) { + // Advanced handlers (file, http) get raw message, console gets formatted + const messageToUse = (output === 'file' || output === 'http') ? rawMessage : formattedMessage; + handler(level, messageToUse, data); + } else { + console.error('[LogEngine] Unknown built-in output handler:', JSON.stringify(output)); + } + } else if (typeof output === 'function') { + // Custom function handler gets formatted message for backward compatibility + output(level, formattedMessage, data); + } else if (isEnhanced && typeof output === 'object' && output.type && output.config) { + // Configured handler object (only available for enhanced outputs) + const handler = this.getBuiltInHandler(output.type, output.config); + if (handler) { + // Advanced configured handlers get raw message + handler(level, rawMessage, data); + } else { + console.error('[LogEngine] Unknown enhanced output handler type:', JSON.stringify(output)); + } + } else { + const outputType = isEnhanced ? 'enhanced output target' : 'output target'; + console.error(`[LogEngine] Invalid ${outputType}:`, output); + } + } catch (error) { + // Continue processing other outputs even if one fails + const handlerType = isEnhanced ? 'Enhanced output' : 'Output'; + console.error(`[LogEngine] ${handlerType} handler failed: ${error}`); } + } - /** + /** + * Process multiple output targets + * @param outputs - Array of output targets to process + * @param level - Log level + * @param rawMessage - Original unformatted message + * @param formattedMessage - Formatted message for console-based outputs + * @param data - Optional data + */ + private processOutputs(outputs: OutputTarget[], level: string, rawMessage: string, formattedMessage: string, data?: LogData): void { + for (const output of outputs) { + this.processSingleOutput(output, level, rawMessage, formattedMessage, data, false); + } + } + + /** + * Process enhanced output targets + * @param enhancedOutputs - Array of enhanced output targets to process + * @param level - Log level + * @param rawMessage - Original unformatted message + * @param formattedMessage - Formatted message for console-based outputs + * @param data - Optional data + */ + private processEnhancedOutputs(enhancedOutputs: EnhancedOutputTarget[], level: string, rawMessage: string, formattedMessage: string, data?: LogData): void { + for (const output of enhancedOutputs) { + this.processSingleOutput(output, level, rawMessage, formattedMessage, data, true); + } + } + + /** * Updates logger configuration with new settings * Also updates redaction configuration based on environment * @param config - Partial configuration object to apply */ - configure(config: Partial): void { - this.configManager.updateConfig(config); - - // Update redaction configuration based on current environment - DataRedactor.updateConfig({ - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }); - } + configure(config: Partial): void { + this.configManager.updateConfig(config); + + // Update redaction configuration based on current environment + DataRedactor.updateConfig({ + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }); + } - /** + /** * Get current logger configuration * @returns Current logger configuration */ - getConfig(): LoggerConfig { - return this.configManager.getConfig(); - } + getConfig(): LoggerConfig { + return this.configManager.getConfig(); + } - /** + /** * Determines if a message should be logged based on current log mode * @param level - The log level of the message to check * @returns true if message should be logged, false otherwise */ - private shouldLog(level: LogLevel): boolean { - const currentConfig = this.configManager.getConfig(); - const currentMode = currentConfig.mode !== undefined ? currentConfig.mode : LogMode.INFO; - return LogFilter.shouldLog(level, currentMode); + private shouldLog(level: LogLevel): boolean { + const currentConfig = this.configManager.getConfig(); + const currentMode = currentConfig.mode !== undefined ? currentConfig.mode : LogMode.INFO; + return LogFilter.shouldLog(level, currentMode); + } + + /** + * Writes log output using configured output handler or default console methods + * Supports single output handler, multiple outputs, and enhanced outputs + * Priority: outputs > enhancedOutputs > outputHandler > default console + * @param level - The log level as a string + * @param rawMessage - The original unformatted message + * @param formattedMessage - The pre-formatted message to output + * @param data - Optional data object that was logged + * @param isError - Whether this is an error level message (for console.error) + * @param isWarn - Whether this is a warning level message (for console.warn) + */ + private writeToOutput(level: string, rawMessage: string, formattedMessage: string, data?: LogData, isError = false, isWarn = false): void { + const config = this.configManager.getConfig(); + + // Multiple outputs support (highest priority - newer API) + if (config.outputs !== undefined) { + if (config.outputs.length > 0) { + // Process outputs array when it has actual outputs + this.processOutputs(config.outputs, level, rawMessage, formattedMessage, data); + } + // If outputs is explicitly set to empty array, disable all logging + return; + } + + // Enhanced outputs with advanced configuration (second priority) + if (config.enhancedOutputs !== undefined && config.enhancedOutputs.length > 0) { + this.processEnhancedOutputs(config.enhancedOutputs, level, rawMessage, formattedMessage, data); + return; + } + + // Single output handler (third priority - legacy compatibility) + if (config.outputHandler) { + try { + config.outputHandler(level, formattedMessage, data); + } catch (error) { + // Fallback to console if custom handler fails + console.error(`[LogEngine] Output handler failed: ${error}. Falling back to console.`); + if (isError) { + console.error(formattedMessage); + } else if (isWarn) { + console.warn(formattedMessage); + } else { + console.log(formattedMessage); + } + } + return; } - /** + // Default: Console output (unless suppressed) + if (!config.suppressConsoleOutput) { + if (isError) { + console.error(formattedMessage); + } else if (isWarn) { + console.warn(formattedMessage); + } else { + console.log(formattedMessage); + } + } + // If suppressConsoleOutput is true and no outputHandler/outputs, do nothing (silent) + } + + /** * Log a debug message with DEBUG level formatting * Uses console.log for output with purple/magenta coloring * Automatically redacts sensitive data when provided * @param message - The debug message to log * @param data - Optional data object to log (will be redacted) */ - debug(message: string, data?: any): void { - if (this.shouldLog(LogLevel.DEBUG)) { - const processedData = DataRedactor.redactData(data); - const formatted = LogFormatter.format(LogLevel.DEBUG, message, processedData); - console.log(formatted); - } + debug(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.DEBUG)) { + const processedData = DataRedactor.redactData(data); + const formatted = LogFormatter.format(LogLevel.DEBUG, message, processedData); + this.writeToOutput('debug', message, formatted, processedData); } + } - /** + /** * Log an informational message with INFO level formatting * Uses console.log for output with blue coloring * Automatically redacts sensitive data when provided * @param message - The info message to log * @param data - Optional data object to log (will be redacted) */ - info(message: string, data?: any): void { - if (this.shouldLog(LogLevel.INFO)) { - const processedData = DataRedactor.redactData(data); - const formatted = LogFormatter.format(LogLevel.INFO, message, processedData); - console.log(formatted); - } + info(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.INFO)) { + const processedData = DataRedactor.redactData(data); + const formatted = LogFormatter.format(LogLevel.INFO, message, processedData); + this.writeToOutput('info', message, formatted, processedData); } + } - /** + /** * Log a warning message with WARN level formatting * Uses console.warn for output with yellow coloring * Automatically redacts sensitive data when provided * @param message - The warning message to log * @param data - Optional data object to log (will be redacted) */ - warn(message: string, data?: any): void { - if (this.shouldLog(LogLevel.WARN)) { - const processedData = DataRedactor.redactData(data); - const formatted = LogFormatter.format(LogLevel.WARN, message, processedData); - console.warn(formatted); - } + warn(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.WARN)) { + const processedData = DataRedactor.redactData(data); + const formatted = LogFormatter.format(LogLevel.WARN, message, processedData); + this.writeToOutput('warn', message, formatted, processedData, false, true); } + } - /** + /** * Log an error message with ERROR level formatting * Uses console.error for output with red coloring * Automatically redacts sensitive data when provided * @param message - The error message to log * @param data - Optional data object to log (will be redacted) */ - error(message: string, data?: any): void { - if (this.shouldLog(LogLevel.ERROR)) { - const processedData = DataRedactor.redactData(data); - const formatted = LogFormatter.format(LogLevel.ERROR, message, processedData); - console.error(formatted); - } + error(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.ERROR)) { + const processedData = DataRedactor.redactData(data); + const formatted = LogFormatter.format(LogLevel.ERROR, message, processedData); + this.writeToOutput('error', message, formatted, processedData, true, false); } + } - /** + /** * Log a message with LOG level formatting (always outputs unless mode is OFF) * Uses console.log for output with green coloring * LOG level bypasses normal filtering and always outputs (except when OFF is set) @@ -128,72 +314,72 @@ export class Logger { * @param message - The log message to output * @param data - Optional data object to log (will be redacted) */ - log(message: string, data?: any): void { - if (this.shouldLog(LogLevel.LOG)) { - const processedData = DataRedactor.redactData(data); - const formatted = LogFormatter.format(LogLevel.LOG, message, processedData); - console.log(formatted); - } + log(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.LOG)) { + const processedData = DataRedactor.redactData(data); + const formatted = LogFormatter.format(LogLevel.LOG, message, processedData); + this.writeToOutput('log', message, formatted, processedData); } + } - // Raw logging methods (bypass redaction for debugging) - /** + // Raw logging methods (bypass redaction for debugging) + /** * Log a debug message without data redaction * @param message - The debug message to log * @param data - Optional data object to log (no redaction applied) */ - debugRaw(message: string, data?: any): void { - if (this.shouldLog(LogLevel.DEBUG)) { - const formatted = LogFormatter.format(LogLevel.DEBUG, message, data); - console.log(formatted); - } + debugRaw(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.DEBUG)) { + const formatted = LogFormatter.format(LogLevel.DEBUG, message, data); + this.writeToOutput('debug', message, formatted, data); } + } - /** + /** * Log an info message without data redaction * @param message - The info message to log * @param data - Optional data object to log (no redaction applied) */ - infoRaw(message: string, data?: any): void { - if (this.shouldLog(LogLevel.INFO)) { - const formatted = LogFormatter.format(LogLevel.INFO, message, data); - console.log(formatted); - } + infoRaw(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.INFO)) { + const formatted = LogFormatter.format(LogLevel.INFO, message, data); + this.writeToOutput('info', message, formatted, data); } + } - /** + /** * Log a warning message without data redaction * @param message - The warning message to log * @param data - Optional data object to log (no redaction applied) */ - warnRaw(message: string, data?: any): void { - if (this.shouldLog(LogLevel.WARN)) { - const formatted = LogFormatter.format(LogLevel.WARN, message, data); - console.warn(formatted); - } + warnRaw(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.WARN)) { + const formatted = LogFormatter.format(LogLevel.WARN, message, data); + this.writeToOutput('warn', message, formatted, data, false, true); } + } - /** + /** * Log an error message without data redaction * @param message - The error message to log * @param data - Optional data object to log (no redaction applied) */ - errorRaw(message: string, data?: any): void { - if (this.shouldLog(LogLevel.ERROR)) { - const formatted = LogFormatter.format(LogLevel.ERROR, message, data); - console.error(formatted); - } + errorRaw(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.ERROR)) { + const formatted = LogFormatter.format(LogLevel.ERROR, message, data); + this.writeToOutput('error', message, formatted, data, true, false); } + } - /** + /** * Log a message without data redaction (always outputs unless mode is OFF) * @param message - The log message to output * @param data - Optional data object to log (no redaction applied) */ - logRaw(message: string, data?: any): void { - if (this.shouldLog(LogLevel.LOG)) { - const formatted = LogFormatter.format(LogLevel.LOG, message, data); - console.log(formatted); - } + logRaw(message: string, data?: LogData): void { + if (this.shouldLog(LogLevel.LOG)) { + const formatted = LogFormatter.format(LogLevel.LOG, message, data); + this.writeToOutput('log', message, formatted, data); } + } } diff --git a/src/logger/environment.ts b/src/logger/environment.ts index 3434cbd..91b9a25 100644 --- a/src/logger/environment.ts +++ b/src/logger/environment.ts @@ -9,56 +9,56 @@ import { LogMode } from '../types'; * Environment detection and configuration utilities */ export class EnvironmentDetector { - /** + /** * Get the normalized NODE_ENV value * @returns Normalized NODE_ENV (trimmed and lowercase) */ - private static getNormalizedNodeEnv(): string { - return (process.env.NODE_ENV || '').trim().toLowerCase(); - } + private static getNormalizedNodeEnv(): string { + return (process.env.NODE_ENV || '').trim().toLowerCase(); + } - /** + /** * Determines the appropriate log mode based on NODE_ENV * @returns LogMode appropriate for current environment */ - static getEnvironmentMode(): LogMode { - const nodeEnv = EnvironmentDetector.getNormalizedNodeEnv(); - - switch (nodeEnv) { - case 'development': - return LogMode.DEBUG; // Verbose logging for development - case 'production': - return LogMode.INFO; // Important info and above for production - case 'staging': - return LogMode.WARN; // Focused logging for staging - case 'test': - return LogMode.ERROR; // Minimal logging during tests - default: - return LogMode.INFO; // Default fallback for unknown environments - } + static getEnvironmentMode(): LogMode { + const nodeEnv = EnvironmentDetector.getNormalizedNodeEnv(); + + switch (nodeEnv) { + case 'development': + return LogMode.DEBUG; // Verbose logging for development + case 'production': + return LogMode.INFO; // Important info and above for production + case 'staging': + return LogMode.WARN; // Focused logging for staging + case 'test': + return LogMode.ERROR; // Minimal logging during tests + default: + return LogMode.INFO; // Default fallback for unknown environments } + } - /** + /** * Check if we're in a test environment * @returns true if NODE_ENV is 'test' */ - static isTestEnvironment(): boolean { - return EnvironmentDetector.getNormalizedNodeEnv() === 'test'; - } + static isTestEnvironment(): boolean { + return EnvironmentDetector.getNormalizedNodeEnv() === 'test'; + } - /** + /** * Check if we're in a development environment * @returns true if NODE_ENV is 'development' */ - static isDevelopmentEnvironment(): boolean { - return EnvironmentDetector.getNormalizedNodeEnv() === 'development'; - } + static isDevelopmentEnvironment(): boolean { + return EnvironmentDetector.getNormalizedNodeEnv() === 'development'; + } - /** + /** * Check if we're in a production environment * @returns true if NODE_ENV is 'production' */ - static isProductionEnvironment(): boolean { - return EnvironmentDetector.getNormalizedNodeEnv() === 'production'; - } + static isProductionEnvironment(): boolean { + return EnvironmentDetector.getNormalizedNodeEnv() === 'production'; + } } diff --git a/src/logger/filtering.ts b/src/logger/filtering.ts index 922de12..3231f67 100644 --- a/src/logger/filtering.ts +++ b/src/logger/filtering.ts @@ -10,26 +10,26 @@ import { LogLevel, LogMode } from '../types'; * Determines whether a message should be output based on current configuration */ export class LogFilter { - // Maps LogLevel values to severity ranks for consistent comparison - private static readonly SEVERITY_RANKS: Record = { - [LogLevel.DEBUG]: 0, - [LogLevel.INFO]: 1, - [LogLevel.WARN]: 2, - [LogLevel.ERROR]: 3, - [LogLevel.LOG]: 99 // Special case - always outputs (except when OFF) - }; + // Maps LogLevel values to severity ranks for consistent comparison + private static readonly SEVERITY_RANKS: Record = { + [LogLevel.DEBUG]: 0, + [LogLevel.INFO]: 1, + [LogLevel.WARN]: 2, + [LogLevel.ERROR]: 3, + [LogLevel.LOG]: 99 // Special case - always outputs (except when OFF) + }; - // Maps LogMode values to minimum severity rank required for output - private static readonly MODE_THRESHOLDS: Record = { - [LogMode.DEBUG]: 0, // Shows DEBUG and above - [LogMode.INFO]: 1, // Shows INFO and above - [LogMode.WARN]: 2, // Shows WARN and above - [LogMode.ERROR]: 3, // Shows ERROR and above - [LogMode.SILENT]: 99, // Only shows LOG messages - [LogMode.OFF]: 100 // Shows nothing - }; + // Maps LogMode values to minimum severity rank required for output + private static readonly MODE_THRESHOLDS: Record = { + [LogMode.DEBUG]: 0, // Shows DEBUG and above + [LogMode.INFO]: 1, // Shows INFO and above + [LogMode.WARN]: 2, // Shows WARN and above + [LogMode.ERROR]: 3, // Shows ERROR and above + [LogMode.SILENT]: 99, // Only shows LOG messages + [LogMode.OFF]: 100 // Shows nothing + }; - /** + /** * Determines if a message should be logged based on current log mode * Messages are shown only if their level is appropriate for the configured mode * LOG level is special - it always outputs regardless of configured mode (except when OFF is set) @@ -38,32 +38,47 @@ export class LogFilter { * @param currentMode - The current logging mode * @returns true if message should be logged, false otherwise */ - static shouldLog(level: LogLevel, currentMode: LogMode): boolean { - // Get the severity rank for the message level - const messageSeverity = this.SEVERITY_RANKS[level]; - - // Get the minimum severity threshold for the current mode - const modeThreshold = this.MODE_THRESHOLDS[currentMode]; - - // Allow the message if its severity meets or exceeds the mode threshold - return messageSeverity >= modeThreshold; - } + static shouldLog(level: LogLevel, currentMode: LogMode): boolean { + // Get the severity rank for the message level using safe lookup + const messageSeverity = LogFilter.getSeverityRank(level); + + // Get the minimum severity threshold for the current mode using safe lookup + const modeThreshold = LogFilter.getModeThreshold(currentMode); + + // Allow the message if its severity meets or exceeds the mode threshold + return messageSeverity >= modeThreshold; + } - /** + /** * Get the severity rank for a log level * @param level - The log level to get rank for * @returns Numeric severity rank */ - static getSeverityRank(level: LogLevel): number { - return this.SEVERITY_RANKS[level]; + static getSeverityRank(level: LogLevel): number { + switch (level) { + case LogLevel.DEBUG: return 0; + case LogLevel.INFO: return 1; + case LogLevel.WARN: return 2; + case LogLevel.ERROR: return 3; + case LogLevel.LOG: return 99; + default: return 0; } + } - /** + /** * Get the threshold for a log mode * @param mode - The log mode to get threshold for * @returns Numeric threshold value */ - static getModeThreshold(mode: LogMode): number { - return this.MODE_THRESHOLDS[mode]; + static getModeThreshold(mode: LogMode): number { + switch (mode) { + case LogMode.DEBUG: return 0; + case LogMode.INFO: return 1; + case LogMode.WARN: return 2; + case LogMode.ERROR: return 3; + case LogMode.SILENT: return 99; + case LogMode.OFF: return 100; + default: return 0; } + } } diff --git a/src/redaction/config.ts b/src/redaction/config.ts index 7fd1fd5..1036926 100644 --- a/src/redaction/config.ts +++ b/src/redaction/config.ts @@ -10,46 +10,46 @@ import { RedactionConfig } from '../types'; * Covers authentication, personal information, financial data, and internal systems */ export const defaultRedactionConfig: RedactionConfig = { - enabled: true, - deepRedaction: true, - maxContentLength: 100, - redactionText: '[REDACTED]', - truncationText: '... [TRUNCATED]', - - // Comprehensive list of sensitive field patterns (case-insensitive) - sensitiveFields: [ - // Authentication & Security - 'password', 'pwd', 'pass', 'passphrase', - 'token', 'accessToken', 'refreshToken', 'bearerToken', - 'apiKey', 'api_key', 'secret', 'secretKey', 'privateKey', - 'auth', 'authorization', 'authToken', - 'jwt', 'sessionId', 'session_id', - 'cookie', 'cookies', 'csrf', 'csrfToken', - - // Personal Information (PII) - 'email', 'emailAddress', 'email_address', - 'phone', 'phoneNumber', 'phone_number', 'mobile', - 'ssn', 'socialSecurityNumber', 'social_security_number', - 'address', 'homeAddress', 'workAddress', - 'firstName', 'lastName', 'fullName', 'realName', 'displayName', - 'dateOfBirth', 'dob', 'birthDate', - - // Financial Information - 'creditCard', 'credit_card', 'cardNumber', 'card_number', - 'cvv', 'cvc', 'pin', 'expiryDate', 'expiry_date', - 'bankAccount', 'bank_account', 'routingNumber', - - // Internal/System Information - 'internalId', 'userId', 'customerId', - 'personalInfo', 'pii', 'sensitive', - 'clientSecret', 'webhookSecret' - ], - - // Fields that should be truncated rather than redacted - contentFields: [ - 'content', 'text', 'message', 'body', 'data', - 'payload', 'response', 'request', 'description' - ] + enabled: true, + deepRedaction: true, + maxContentLength: 100, + redactionText: '[REDACTED]', + truncationText: '... [TRUNCATED]', + + // Comprehensive list of sensitive field patterns (case-insensitive) + sensitiveFields: [ + // Authentication & Security + 'password', 'pwd', 'pass', 'passphrase', + 'token', 'accessToken', 'refreshToken', 'bearerToken', + 'apiKey', 'api_key', 'secret', 'secretKey', 'privateKey', + 'auth', 'authorization', 'authToken', + 'jwt', 'sessionId', 'session_id', + 'cookie', 'cookies', 'csrf', 'csrfToken', + + // Personal Information (PII) + 'email', 'emailAddress', 'email_address', + 'phone', 'phoneNumber', 'phone_number', 'mobile', + 'ssn', 'socialSecurityNumber', 'social_security_number', + 'address', 'homeAddress', 'workAddress', + 'firstName', 'lastName', 'fullName', 'realName', 'displayName', + 'dateOfBirth', 'dob', 'birthDate', + + // Financial Information + 'creditCard', 'credit_card', 'cardNumber', 'card_number', + 'cvv', 'cvc', 'pin', 'expiryDate', 'expiry_date', + 'bankAccount', 'bank_account', 'routingNumber', + + // Internal/System Information + 'internalId', 'userId', 'customerId', + 'personalInfo', 'pii', 'sensitive', + 'clientSecret', 'webhookSecret' + ], + + // Fields that should be truncated rather than redacted + contentFields: [ + 'content', 'text', 'message', 'body', 'data', + 'payload', 'response', 'request', 'description' + ] }; /** @@ -57,52 +57,52 @@ export const defaultRedactionConfig: RedactionConfig = { * Determines when redaction should be disabled based on environment variables */ export class RedactionController { - /** + /** * Check if redaction should be disabled based on environment variables * Development mode and explicit flags can disable redaction for debugging * @returns true if redaction should be disabled, false otherwise */ - static isRedactionDisabled(): boolean { - return ( - process.env.NODE_ENV === 'development' || + static isRedactionDisabled(): boolean { + return ( + process.env.NODE_ENV === 'development' || process.env.LOG_REDACTION_DISABLED === 'true' || process.env.DEBUG_FULL_PAYLOADS === 'true' || process.env.LOG_LEVEL === 'debug' || process.env.LOG_REDACTION_ENABLED === 'false' - ); - } + ); + } - /** + /** * Get environment-specific configuration overrides * Allows customization through environment variables * @returns Partial redaction config with environment-based overrides */ - static getEnvironmentConfig(): Partial { - const envConfig: Partial = { - enabled: !this.isRedactionDisabled() - }; + static getEnvironmentConfig(): Partial { + const envConfig: Partial = { + enabled: !this.isRedactionDisabled() + }; - // Apply environment variable overrides if they exist - if (process.env.LOG_MAX_CONTENT_LENGTH) { - const parsedLength = parseInt(process.env.LOG_MAX_CONTENT_LENGTH, 10); - if (!isNaN(parsedLength) && parsedLength > 0) { - envConfig.maxContentLength = parsedLength; - } - } - - if (process.env.LOG_REDACTION_TEXT) { - envConfig.redactionText = process.env.LOG_REDACTION_TEXT; - } + // Apply environment variable overrides if they exist + if (process.env.LOG_MAX_CONTENT_LENGTH) { + const parsedLength = parseInt(process.env.LOG_MAX_CONTENT_LENGTH, 10); + if (!isNaN(parsedLength) && parsedLength > 0) { + envConfig.maxContentLength = parsedLength; + } + } - if (process.env.LOG_TRUNCATION_TEXT) { - envConfig.truncationText = process.env.LOG_TRUNCATION_TEXT; - } + if (process.env.LOG_REDACTION_TEXT) { + envConfig.redactionText = process.env.LOG_REDACTION_TEXT; + } - if (process.env.LOG_SENSITIVE_FIELDS) { - const customFields = process.env.LOG_SENSITIVE_FIELDS.split(',').map(f => f.trim()); - envConfig.sensitiveFields = [...defaultRedactionConfig.sensitiveFields, ...customFields]; - } + if (process.env.LOG_TRUNCATION_TEXT) { + envConfig.truncationText = process.env.LOG_TRUNCATION_TEXT; + } - return envConfig; + if (process.env.LOG_SENSITIVE_FIELDS) { + const customFields = process.env.LOG_SENSITIVE_FIELDS.split(',').map(f => f.trim()); + envConfig.sensitiveFields = [...defaultRedactionConfig.sensitiveFields, ...customFields]; } + + return envConfig; + } } diff --git a/src/redaction/redactor.ts b/src/redaction/redactor.ts index c949fae..01aebfe 100644 --- a/src/redaction/redactor.ts +++ b/src/redaction/redactor.ts @@ -3,7 +3,7 @@ * Handles automatic detection and redaction of sensitive information in log data */ -import { RedactionConfig } from '../types'; +import { RedactionConfig, LogData } from '../types'; import { defaultRedactionConfig, RedactionController } from './config'; /** @@ -11,116 +11,121 @@ import { defaultRedactionConfig, RedactionController } from './config'; * Automatically detects and redacts sensitive information while preserving structure */ export class DataRedactor { - private static config: RedactionConfig = { - ...defaultRedactionConfig, - ...RedactionController.getEnvironmentConfig() - }; + private static config: RedactionConfig = { + ...defaultRedactionConfig, + ...RedactionController.getEnvironmentConfig() + }; - // Maximum recursion depth to prevent stack overflow attacks - private static readonly MAX_RECURSION_DEPTH = 100; - // Slightly lower limit for redactObject to ensure it can be reached - private static readonly MAX_REDACT_OBJECT_DEPTH = 99; + // Maximum recursion depth to prevent stack overflow attacks + private static readonly MAX_RECURSION_DEPTH = 100; + // Slightly lower limit for redactObject to ensure it can be reached + private static readonly MAX_REDACT_OBJECT_DEPTH = 99; - /** + /** * Update the redaction configuration with new settings * Merges provided config with existing settings and reloads environment variables * @param newConfig - Partial configuration to merge with current settings */ - static updateConfig(newConfig: Partial): void { - // Reload environment configuration to pick up any changes - const envConfig = RedactionController.getEnvironmentConfig(); - this.config = { - ...defaultRedactionConfig, - ...envConfig, - ...newConfig - }; - } + static updateConfig(newConfig: Partial): void { + // Reload environment configuration to pick up any changes + const envConfig = RedactionController.getEnvironmentConfig(); + DataRedactor.config = { + ...defaultRedactionConfig, + ...envConfig, + ...newConfig + }; + } - /** + /** * Get the current redaction configuration * @returns Deep copy of current redaction configuration */ - static getConfig(): RedactionConfig { - return { - ...this.config, - sensitiveFields: [...this.config.sensitiveFields], - contentFields: [...this.config.contentFields], - customPatterns: this.config.customPatterns ? [...this.config.customPatterns] : undefined - }; - } + static getConfig(): RedactionConfig { + return { + ...DataRedactor.config, + sensitiveFields: [...DataRedactor.config.sensitiveFields], + contentFields: [...DataRedactor.config.contentFields], + customPatterns: DataRedactor.config.customPatterns ? [...DataRedactor.config.customPatterns] : undefined + }; + } - /** + /** * Refresh configuration from environment variables * Useful for picking up runtime environment changes */ - static refreshConfig(): void { - const envConfig = RedactionController.getEnvironmentConfig(); - this.config = { - ...defaultRedactionConfig, - ...envConfig - }; - } + static refreshConfig(): void { + const envConfig = RedactionController.getEnvironmentConfig(); + DataRedactor.config = { + ...defaultRedactionConfig, + ...envConfig + }; + } - /** + /** * Add custom regex patterns for advanced field detection * @param patterns - Array of regex patterns to add */ - static addCustomPatterns(patterns: RegExp[]): void { - const currentPatterns = this.config.customPatterns || []; - this.config = { - ...this.config, - customPatterns: [...currentPatterns, ...patterns] - }; - } + static addCustomPatterns(patterns: RegExp[]): void { + const currentPatterns = DataRedactor.config.customPatterns || []; + DataRedactor.config = { + ...DataRedactor.config, + customPatterns: [...currentPatterns, ...patterns] + }; + } - /** + /** * Clear all custom regex patterns */ - static clearCustomPatterns(): void { - this.config = { - ...this.config, - customPatterns: [] - }; - } + static clearCustomPatterns(): void { + DataRedactor.config = { + ...DataRedactor.config, + customPatterns: [] + }; + } - /** + /** * Add custom sensitive field names to the existing list * @param fields - Array of field names to add */ - static addSensitiveFields(fields: string[]): void { - this.config = { - ...this.config, - sensitiveFields: [...this.config.sensitiveFields, ...fields] - }; - } + static addSensitiveFields(fields: string[]): void { + DataRedactor.config = { + ...DataRedactor.config, + sensitiveFields: [...DataRedactor.config.sensitiveFields, ...fields] + }; + } - /** + /** * Test if a field name would be redacted with current configuration * @param fieldName - Field name to test * @returns true if field would be redacted, false otherwise */ - static testFieldRedaction(fieldName: string): boolean { - const testObj = { [fieldName]: 'test-value' }; - const result = this.redactData(testObj); - return result[fieldName] !== 'test-value'; + static testFieldRedaction(fieldName: string): boolean { + const testObj = { [fieldName]: 'test-value' }; + const result = DataRedactor.redactData(testObj); // Use safe property access to prevent object injection + if (Object.prototype.hasOwnProperty.call(result, fieldName)) { + // Safe access to avoid object injection + const value = result[fieldName as keyof typeof result]; + return value !== 'test-value'; } + return false; + } - /** + /** * Main entry point for data redaction * Processes any type of data and returns a redacted version * @param data - Data to be processed for redaction * @returns Redacted version of the data */ - static redactData(data: any): any { - // Skip processing if redaction is disabled or data is null/undefined - if (!this.config.enabled || data === null || data === undefined) { - return data; - } - - return this.processValue(data, new WeakSet(), 0); + static redactData(data: LogData): LogData { + // Skip processing if redaction is disabled or data is null/undefined + if (!DataRedactor.config.enabled || data === null || data === undefined) { + return data; } - /** + return DataRedactor.processValue(data, new WeakSet(), 0); + } + + /** * Process a value of any type (primitive, object, array) * Recursively handles nested structures when deepRedaction is enabled * Includes circular reference protection and recursion depth limiting @@ -129,48 +134,48 @@ export class DataRedactor { * @param depth - Current recursion depth (prevents stack overflow) * @returns Processed value with redaction applied */ - private static processValue(value: any, visited: WeakSet = new WeakSet(), depth: number = 0): any { - // Check recursion depth limit to prevent stack overflow - if (depth >= this.MAX_RECURSION_DEPTH) { - return '[Max Depth Exceeded]'; - } + private static processValue(value: LogData, visited: WeakSet = new WeakSet(), depth: number = 0): LogData { + // Check recursion depth limit to prevent stack overflow + if (depth >= DataRedactor.MAX_RECURSION_DEPTH) { + return '[Max Depth Exceeded]'; + } - // Handle null and undefined - if (value === null || value === undefined) { - return value; - } + // Handle null and undefined + if (value === null || value === undefined) { + return value; + } - // Handle arrays - process each element - if (Array.isArray(value)) { - // Check for circular reference - if (visited.has(value)) { - return '[Circular Array]'; - } - visited.add(value); - - const result = value.map(item => this.processValue(item, visited, depth + 1)); - // Keep value in visited set to detect circular references across branches - return result; - } + // Handle arrays - process each element + if (Array.isArray(value)) { + // Check for circular reference + if (visited.has(value)) { + return '[Circular Array]'; + } + visited.add(value); - // Handle objects - process each property - if (typeof value === 'object') { - // Check for circular reference - if (visited.has(value)) { - return '[Circular Object]'; - } - visited.add(value); - - const result = this.redactObject(value, visited, depth + 1); - // Keep value in visited set to detect circular references across branches - return result; - } + const result = value.map(item => DataRedactor.processValue(item, visited, depth + 1)); + // Keep value in visited set to detect circular references across branches + return result; + } + + // Handle objects - process each property + if (typeof value === 'object') { + // Check for circular reference + if (visited.has(value)) { + return '[Circular Object]'; + } + visited.add(value); - // Handle primitives (string, number, boolean) - return as-is - return value; + const result = DataRedactor.redactObject(value, visited, depth + 1); + // Keep value in visited set to detect circular references across branches + return result; } - /** + // Handle primitives (string, number, boolean) - return as-is + return value; + } + + /** * Process an object and redact sensitive fields * Handles field-level redaction and content truncation * @param obj - Object to process @@ -178,112 +183,109 @@ export class DataRedactor { * @param depth - Current recursion depth (prevents stack overflow) * @returns Object with sensitive fields redacted */ - private static redactObject(obj: Record, visited: WeakSet = new WeakSet(), depth: number = 0): Record { - // Check recursion depth limit to prevent stack overflow - if (depth >= this.MAX_REDACT_OBJECT_DEPTH) { - return { '[Max Depth Exceeded]': '[Max Depth Exceeded]' }; - } + private static redactObject(obj: Record, visited: WeakSet = new WeakSet(), depth: number = 0): Record { + // Check recursion depth limit to prevent stack overflow + if (depth >= DataRedactor.MAX_REDACT_OBJECT_DEPTH) { + return { '[Max Depth Exceeded]': '[Max Depth Exceeded]' }; + } - const redacted: Record = {}; - - for (const [key, value] of Object.entries(obj)) { - // Check if this field should be completely redacted - if (this.isSensitiveField(key)) { - redacted[key] = this.config.redactionText; - } - // Check if this field should be truncated (for large content) - else if (this.isContentField(key) && typeof value === 'string') { - redacted[key] = this.truncateContent(value); - } - // Recursively process nested objects/arrays if deep redaction is enabled - else if (this.config.deepRedaction && (typeof value === 'object' && value !== null)) { - redacted[key] = this.processValue(value, visited, depth + 1); - } - // Keep the value unchanged - else { - redacted[key] = value; - } - } + const redacted: Record = {}; - return redacted; + for (const [key, value] of Object.entries(obj)) { + // Check if this field should be completely redacted + if (DataRedactor.isSensitiveField(key)) { + Object.defineProperty(redacted, key, { value: DataRedactor.config.redactionText, enumerable: true, writable: true, configurable: true }); + } else if (DataRedactor.isContentField(key) && typeof value === 'string') { + // Check if this field should be truncated (for large content) + Object.defineProperty(redacted, key, { value: DataRedactor.truncateContent(value), enumerable: true, writable: true, configurable: true }); + } else if (DataRedactor.config.deepRedaction && (typeof value === 'object' && value !== null)) { + // Recursively process nested objects/arrays if deep redaction is enabled + Object.defineProperty(redacted, key, { value: DataRedactor.processValue(value, visited, depth + 1), enumerable: true, writable: true, configurable: true }); + } else { + // Keep the value unchanged + Object.defineProperty(redacted, key, { value: value, enumerable: true, writable: true, configurable: true }); + } } - /** + return redacted; + } + + /** * Check if a field name indicates sensitive information * Uses case-insensitive matching with exact and partial matches * Includes smart filtering to avoid false positives and custom patterns * @param fieldName - Field name to check * @returns true if field should be redacted, false otherwise */ - private static isSensitiveField(fieldName: string): boolean { - const lowerField = fieldName.toLowerCase(); - - // Check custom regex patterns first (highest priority) - if (this.config.customPatterns && this.config.customPatterns.length > 0) { - for (const pattern of this.config.customPatterns) { - if (pattern.test(fieldName)) { - return true; - } - } + private static isSensitiveField(fieldName: string): boolean { + const lowerField = fieldName.toLowerCase(); + + // Check custom regex patterns first (highest priority) + if (DataRedactor.config.customPatterns && DataRedactor.config.customPatterns.length > 0) { + for (const pattern of DataRedactor.config.customPatterns) { + if (pattern.test(fieldName)) { + return true; } - - return this.config.sensitiveFields.some(sensitive => { - const lowerSensitive = sensitive.toLowerCase(); - - // Exact match (highest confidence) - if (lowerField === lowerSensitive) { - return true; - } - - // Field ends with sensitive term (e.g., "userPassword" ends with "password") - if (lowerField.endsWith(lowerSensitive)) { - return true; - } - - // Field starts with sensitive term (e.g., "passwordHash" starts with "password") - if (lowerField.startsWith(lowerSensitive)) { - return true; - } - - // Whitelist of short sensitive terms that should always trigger substring matching - const shortSensitiveWhitelist = ['pin', 'cvv', 'cvc', 'ssn', 'pwd', 'key', 'jwt', 'dob', 'pii', 'auth', 'csrf']; - - // Field contains sensitive term - either from whitelist or length >= 5 to avoid false positives - if ((shortSensitiveWhitelist.includes(lowerSensitive) || lowerSensitive.length >= 5) && - lowerField.includes(lowerSensitive)) { - return true; - } - - // Handle compound words with underscores or camelCase - if (lowerField.includes('_' + lowerSensitive) || lowerField.includes(lowerSensitive + '_')) { - return true; - } - - return false; - }); + } } - /** + return DataRedactor.config.sensitiveFields.some(sensitive => { + const lowerSensitive = sensitive.toLowerCase(); + + // Exact match (highest confidence) + if (lowerField === lowerSensitive) { + return true; + } + + // Field ends with sensitive term (e.g., "userPassword" ends with "password") + if (lowerField.endsWith(lowerSensitive)) { + return true; + } + + // Field starts with sensitive term (e.g., "passwordHash" starts with "password") + if (lowerField.startsWith(lowerSensitive)) { + return true; + } + + // Whitelist of short sensitive terms that should always trigger substring matching + const shortSensitiveWhitelist = ['pin', 'cvv', 'cvc', 'ssn', 'pwd', 'key', 'jwt', 'dob', 'pii', 'auth', 'csrf']; + + // Field contains sensitive term - either from whitelist or length >= 5 to avoid false positives + if ((shortSensitiveWhitelist.includes(lowerSensitive) || lowerSensitive.length >= 5) && + lowerField.includes(lowerSensitive)) { + return true; + } + + // Handle compound words with underscores or camelCase + if (lowerField.includes('_' + lowerSensitive) || lowerField.includes(lowerSensitive + '_')) { + return true; + } + + return false; + }); + } + + /** * Check if a field name indicates content that should be truncated * Uses exact case-insensitive matching for content fields * @param fieldName - Field name to check * @returns true if field is a content field, false otherwise */ - private static isContentField(fieldName: string): boolean { - const lowerField = fieldName.toLowerCase(); - return this.config.contentFields.some(content => content.toLowerCase() === lowerField); - } + private static isContentField(fieldName: string): boolean { + const lowerField = fieldName.toLowerCase(); + return DataRedactor.config.contentFields.some(content => content.toLowerCase() === lowerField); + } - /** + /** * Truncate content that exceeds the maximum length * Preserves readability while preventing log bloat * @param content - Content string to potentially truncate * @returns Original content or truncated version with indicator */ - private static truncateContent(content: string): string { - if (content.length <= this.config.maxContentLength) { - return content; - } - return content.substring(0, this.config.maxContentLength) + this.config.truncationText; + private static truncateContent(content: string): string { + if (content.length <= DataRedactor.config.maxContentLength) { + return content; } + return content.substring(0, DataRedactor.config.maxContentLength) + DataRedactor.config.truncationText; + } } diff --git a/src/types/index.ts b/src/types/index.ts index 6900f7a..ae06abb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,6 +3,13 @@ * Provides strongly-typed interfaces for configuration and log levels */ +/** + * Type for log data - accepts any value since logs can contain literally anything + * This is intentionally `any` rather than `unknown` for maximum usability in a logging context + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type LogData = any; + /** * Log levels representing message severity (lowest to highest) * Used for filtering messages based on importance @@ -52,6 +59,75 @@ export interface LogEntry { message: string; } +/** + * Output handler function type for custom log output + * Receives the log level, formatted message, and optional data + */ +export type LogOutputHandler = (level: string, message: string, data?: LogData) => void; + +/** + * Built-in output handler types + */ +export type BuiltInOutputHandler = 'console' | 'silent' | 'file' | 'http'; + +/** + * Configuration for file output handler + */ +export interface FileOutputConfig { + /** File path to write logs to */ + filePath: string; + /** Whether to append to existing file (default: true) */ + append?: boolean; + /** Maximum file size before rotation in bytes (optional) */ + maxFileSize?: number; + /** Number of backup files to keep during rotation (default: 3) */ + maxBackupFiles?: number; + /** Custom format function for file output */ + formatter?: (level: string, message: string, data?: LogData) => string; +} + +/** + * Configuration for HTTP output handler + */ +export interface HttpOutputConfig { + /** HTTP endpoint URL to send logs to */ + url: string; + /** HTTP method (default: 'POST') */ + method?: 'POST' | 'PUT' | 'PATCH'; + /** Custom headers to include with requests */ + headers?: Record; + /** Batch size for sending multiple logs (default: 1) */ + batchSize?: number; + /** Timeout for HTTP requests in ms (default: 5000) */ + timeout?: number; + /** Custom format function for HTTP payload */ + formatter?: (logs: Array<{ level: string; message: string; data?: LogData; timestamp: string }>) => LogData; +} + +/** + * Configuration object for advanced built-in handlers + */ +export interface AdvancedOutputConfig { + file?: FileOutputConfig; + http?: HttpOutputConfig; +} + +/** + * Enhanced output target - can be built-in handler, custom function, or configured handler object + */ +export type EnhancedOutputTarget = BuiltInOutputHandler | LogOutputHandler | { + type: 'file'; + config: FileOutputConfig; +} | { + type: 'http'; + config: HttpOutputConfig; +}; + +/** + * Output target - can be a built-in handler string or custom function + */ +export type OutputTarget = BuiltInOutputHandler | LogOutputHandler; + /** * Configuration options for the logger * Supports both legacy level-based and new mode-based configuration @@ -64,6 +140,16 @@ export interface LoggerConfig { level?: LogLevel; /** Optional environment identifier for context (e.g., 'production', 'staging') */ environment?: string; + /** Custom output handler function to replace console output (backward compatibility) */ + outputHandler?: LogOutputHandler; + /** Array of output targets for multiple simultaneous outputs */ + outputs?: OutputTarget[]; + /** Enhanced outputs with advanced configuration support */ + enhancedOutputs?: EnhancedOutputTarget[]; + /** Whether to suppress default console output (useful with custom outputHandler) */ + suppressConsoleOutput?: boolean; + /** Advanced configuration for built-in handlers */ + advancedOutputConfig?: AdvancedOutputConfig; } /** @@ -97,31 +183,31 @@ export interface ILogEngine { // Configuration methods /** Configure the logger with new settings */ configure(config: Partial): void; - + // Standard logging methods with automatic redaction /** Log a debug message with automatic data redaction */ - debug(message: string, data?: any): void; + debug(message: string, data?: LogData): void; /** Log an info message with automatic data redaction */ - info(message: string, data?: any): void; + info(message: string, data?: LogData): void; /** Log a warn message with automatic data redaction */ - warn(message: string, data?: any): void; + warn(message: string, data?: LogData): void; /** Log an error message with automatic data redaction */ - error(message: string, data?: any): void; + error(message: string, data?: LogData): void; /** Log a message with automatic data redaction */ - log(message: string, data?: any): void; - + log(message: string, data?: LogData): void; + // Raw logging methods (bypass redaction) /** Log a debug message without redaction */ - debugRaw(message: string, data?: any): void; + debugRaw(message: string, data?: LogData): void; /** Log an info message without redaction */ - infoRaw(message: string, data?: any): void; + infoRaw(message: string, data?: LogData): void; /** Log a warn message without redaction */ - warnRaw(message: string, data?: any): void; + warnRaw(message: string, data?: LogData): void; /** Log an error message without redaction */ - errorRaw(message: string, data?: any): void; + errorRaw(message: string, data?: LogData): void; /** Log a message without redaction */ - logRaw(message: string, data?: any): void; - + logRaw(message: string, data?: LogData): void; + // Redaction configuration methods /** Configure redaction settings */ configureRedaction(config: Partial): void; @@ -131,7 +217,7 @@ export interface ILogEngine { refreshRedactionConfig(): void; /** Get current redaction configuration */ getRedactionConfig(): RedactionConfig; - + // Advanced redaction methods /** Add custom regex patterns for advanced field detection */ addCustomRedactionPatterns(patterns: RegExp[]): void; @@ -141,7 +227,7 @@ export interface ILogEngine { addSensitiveFields(fields: string[]): void; /** Test if a field name would be redacted with current configuration */ testFieldRedaction(fieldName: string): boolean; - + // Utility methods /** Temporarily disable redaction for a specific logging call */ withoutRedaction(): ILogEngineWithoutRedaction; @@ -153,15 +239,15 @@ export interface ILogEngine { */ export interface ILogEngineWithoutRedaction { /** Log a debug message without redaction */ - debug(message: string, data?: any): void; + debug(message: string, data?: LogData): void; /** Log an info message without redaction */ - info(message: string, data?: any): void; + info(message: string, data?: LogData): void; /** Log a warn message without redaction */ - warn(message: string, data?: any): void; + warn(message: string, data?: LogData): void; /** Log an error message without redaction */ - error(message: string, data?: any): void; + error(message: string, data?: LogData): void; /** Log a message without redaction */ - log(message: string, data?: any): void; + log(message: string, data?: LogData): void; } /** @@ -184,7 +270,7 @@ export interface IDataRedactor { /** Test if a field name would be redacted */ testFieldRedaction(fieldName: string): boolean; /** Redact sensitive data from any value */ - redactData(data: any): any; + redactData(data: LogData): LogData; } /** @@ -247,4 +333,4 @@ export interface EnvironmentConfig { LOG_REDACTION_TRUNCATION_TEXT?: string; /** LOG_REDACTION_DEEP - Enable deep redaction */ LOG_REDACTION_DEEP?: string; -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index d40fecf..1719e23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,2258 +1,5995 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" - integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" - integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helpers" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" - integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== - dependencies: - "@babel/parser" "^7.27.1" - "@babel/types" "^7.27.1" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.27.1": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" - integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" - integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== - dependencies: - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" - integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/template@^7.27.1", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - 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" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - 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" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - 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" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - 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" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - 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" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - 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" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - 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" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" - integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== - dependencies: - "@babel/types" "^7.20.7" - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.0.0": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/node@*": - version "22.15.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.21.tgz#196ef14fe20d87f7caf1e7b39832767f9a995b77" - integrity sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ== - dependencies: - undici-types "~6.21.0" - -"@types/node@^18.0.0": - version "18.19.103" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.103.tgz#9bbd31a54e240fc469cca409d7507ebc77536458" - integrity sha512-hHTHp+sEz6SxFsp+SA+Tqrua3AbmlAw+Y//aEwdHrdZkYVRWdvWD3y5uPZ0flYOkgskaFWqZ/YGFm3FaFQ0pRw== - dependencies: - undici-types "~5.26.4" - -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - 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" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - 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" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - 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" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0: - version "4.24.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" - integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== - dependencies: - caniuse-lite "^1.0.30001716" - electron-to-chromium "^1.5.149" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" - -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001716: - version "1.0.30001718" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82" - integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw== - -chalk@^4.0.0, chalk@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - 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" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - -dedent@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" - integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.5.149: - version "1.5.157" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz#553b122522ac7bba6f1a0dd7d50b14f297736f75" - integrity sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - 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" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - 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" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - 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" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - 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" - -istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - 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" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - 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" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - 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" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - 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" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - 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" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - 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" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - 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" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - 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" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - 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" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - 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" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - 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" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - 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" - -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - 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" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - 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" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - 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" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.0.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@^1.1.1, make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - 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" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -react-is@^18.0.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - -resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-jest@^29.0.0: - version "29.3.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.4.tgz#9354472aceae1d3867a80e8e02014ea5901aee41" - integrity sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA== - dependencies: - bs-logger "^0.2.6" - ejs "^3.1.10" - fast-json-stable-stringify "^2.1.0" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.7.2" - type-fest "^4.41.0" - yargs-parser "^21.1.1" - -ts-node@^10.0.0: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^4.41.0: - version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" - integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== - -typescript@^5.0.0: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - 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" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.2": + version: 7.27.7 + resolution: "@babel/compat-data@npm:7.27.7" + checksum: 10c0/08f2d3bd1b38e7e8cd159c5ddeb458696338ef7cd3fe0cc4384a0af5353ef8577ee3f25f01f0a88544c0e7ada972d0d2826a06744c695b211bfb172b76c0ca38 + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": + version: 7.27.7 + resolution: "@babel/core@npm:7.27.7" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.5" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.27.3" + "@babel/helpers": "npm:^7.27.6" + "@babel/parser": "npm:^7.27.7" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.27.7" + "@babel/types": "npm:^7.27.7" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/02c0cd475821c5333d5ee5eb9a0565af1a38234b37859ae09c4c95d7171bbc11a23a6f733c31b3cb12dc523311bdc8f7f9d705136f33eeb6704b7fbd6e6468ca + languageName: node + linkType: hard + +"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.7.2": + version: 7.27.5 + resolution: "@babel/generator@npm:7.27.5" + dependencies: + "@babel/parser": "npm:^7.27.5" + "@babel/types": "npm:^7.27.3" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/8f649ef4cd81765c832bb11de4d6064b035ffebdecde668ba7abee68a7b0bce5c9feabb5dc5bb8aeba5bd9e5c2afa3899d852d2bd9ca77a711ba8c8379f416f0 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" + dependencies: + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-module-transforms@npm:7.27.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/fccb4f512a13b4c069af51e1b56b20f54024bcf1591e31e978a30f3502567f34f90a80da6a19a6148c249216292a8074a0121f9e52602510ef0f32dbce95ca01 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.27.6": + version: 7.27.6 + resolution: "@babel/helpers@npm:7.27.6" + dependencies: + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.27.6" + checksum: 10c0/448bac96ef8b0f21f2294a826df9de6bf4026fd023f8a6bb6c782fe3e61946801ca24381490b8e58d861fee75cd695a1882921afbf1f53b0275ee68c938bd6d3 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/parser@npm:7.27.7" + dependencies: + "@babel/types": "npm:^7.27.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/f6202faeb873f0b3083022e50a5046fe07266d337c0a3bd80a491f8435ba6d9e383d49725e3dcd666b3b52c0dccb4e0f1f1004915762345f7eeed5ba54ea9fd2 + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.12.13": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.12.13" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e66f7a761b8360419bbb93ab67d87c8a97465ef4637a985ff682ce7ba6918b34b29d81190204cf908d0933058ee7b42737423cd8a999546c21b3aabad4affa9a + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.10.4" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.8.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.27.1 + resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/traverse@npm:7.27.7" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.5" + "@babel/parser": "npm:^7.27.7" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.27.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/941fecd0248546f059d58230590a2765d128ef072c8521c9e0bcf6037abf28a0ea4736003d0d695513128d07fe00a7bc57acaada2ed905941d44619b9f49cf0c + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.27.7, @babel/types@npm:^7.3.3": + version: 7.27.7 + resolution: "@babel/types@npm:7.27.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/1d1dcb5fa7cfba2b4034a3ab99ba17049bfc4af9e170935575246cdb1cee68b04329a0111506d9ae83fb917c47dbd4394a6db5e32fbd041b7834ffbb17ca086b + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": + version: 4.7.0 + resolution: "@eslint-community/eslint-utils@npm:4.7.0" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.12.1 + resolution: "@eslint-community/regexpp@npm:4.12.1" + checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^2.0.3": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c + languageName: node + linkType: hard + +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@sentry-internal/tracing@npm:7.120.3": + version: 7.120.3 + resolution: "@sentry-internal/tracing@npm:7.120.3" + dependencies: + "@sentry/core": "npm:7.120.3" + "@sentry/types": "npm:7.120.3" + "@sentry/utils": "npm:7.120.3" + checksum: 10c0/97d640121bb137efba6deeecbc80a61cc34b526c89d39a43efc2b8be4be159c1536951d1121503bd5171a7c5e1ef225bc632f3ef35248d6c478865abf7330cf3 + languageName: node + linkType: hard + +"@sentry/core@npm:7.120.3": + version: 7.120.3 + resolution: "@sentry/core@npm:7.120.3" + dependencies: + "@sentry/types": "npm:7.120.3" + "@sentry/utils": "npm:7.120.3" + checksum: 10c0/97bcfd423ee9583fae8dc995cefb4839eb59c9d38334ebd1b22398626692aa4df322a5386e3b25a7a96b596155f802169c4ec04e63c1835ea9794955d27ba54a + languageName: node + linkType: hard + +"@sentry/integrations@npm:7.120.3": + version: 7.120.3 + resolution: "@sentry/integrations@npm:7.120.3" + dependencies: + "@sentry/core": "npm:7.120.3" + "@sentry/types": "npm:7.120.3" + "@sentry/utils": "npm:7.120.3" + localforage: "npm:^1.8.1" + checksum: 10c0/b539d4725ac2053db94e122a8e11aac4c36218c2e301547aa95956523d552c79a835a9a8e843617be808c565b116d822ede94ee7c806bea69dc9d9f5c1edd575 + languageName: node + linkType: hard + +"@sentry/node@npm:^7.36.0": + version: 7.120.3 + resolution: "@sentry/node@npm:7.120.3" + dependencies: + "@sentry-internal/tracing": "npm:7.120.3" + "@sentry/core": "npm:7.120.3" + "@sentry/integrations": "npm:7.120.3" + "@sentry/types": "npm:7.120.3" + "@sentry/utils": "npm:7.120.3" + checksum: 10c0/b8a6c7fa530f1bf91498db0b3898095a0b94dee322bd7facae66b9ba45ee867fd383e29ac157cdf4ac98cc040f2349c5a91f072fba9551110aec8d8cf795b78a + languageName: node + linkType: hard + +"@sentry/types@npm:7.120.3": + version: 7.120.3 + resolution: "@sentry/types@npm:7.120.3" + checksum: 10c0/f7c4a46c72dd66a88053d579ad5a0137eaf6882dfe5e942828e750ad80fd8fe98ceec1a6328dfcf31f5a56849dda8ee27f7419125f905f19751119b9662dbdcb + languageName: node + linkType: hard + +"@sentry/utils@npm:7.120.3": + version: 7.120.3 + resolution: "@sentry/utils@npm:7.120.3" + dependencies: + "@sentry/types": "npm:7.120.3" + checksum: 10c0/15a1e0a0d29aae668023f68f4b3e4b3bd5353b5f033874d2bf51264206f05036e68b8d6d88078cf807fbb051c20244b1ac73b19a875da76a97e635a38b9fd180 + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node10@npm:1.0.11" + checksum: 10c0/28a0710e5d039e0de484bdf85fee883bfd3f6a8980601f4d44066b0a6bcd821d31c4e231d1117731c4e24268bd4cf2a788a6787c12fc7f8d11014c07d582783c + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.20.7 + resolution: "@types/babel__traverse@npm:7.20.7" + dependencies: + "@babel/types": "npm:^7.20.7" + checksum: 10c0/5386f0af44f8746b063b87418f06129a814e16bb2686965a575e9d7376b360b088b89177778d8c426012abc43dd1a2d8ec3218bfc382280c898682746ce2ffbd + languageName: node + linkType: hard + +"@types/eslint-plugin-security@npm:^3": + version: 3.0.0 + resolution: "@types/eslint-plugin-security@npm:3.0.0" + dependencies: + "@types/eslint": "npm:*" + checksum: 10c0/70d9a8344d75ae447afa004847f8cea57ee78436572410b2c5f73fb6252e5024f777e4e749f17e1099428244510ae4e6c255677bb9d83612c22df28e3c2da391 + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 9.6.1 + resolution: "@types/eslint@npm:9.6.1" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e + languageName: node + linkType: hard + +"@types/estree@npm:*": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/jest@npm:^29.0.0": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed + languageName: node + linkType: hard + +"@types/json-schema@npm:*": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 24.0.7 + resolution: "@types/node@npm:24.0.7" + dependencies: + undici-types: "npm:~7.8.0" + checksum: 10c0/be3849816dafc54ec79e6be6dafcf60bdb6466beaf0081b941142d260e2b2864855210dfe5b4395c59b276468528695aefcf4f060ac95cc433b2968e80a311f9 + languageName: node + linkType: hard + +"@types/node@npm:^18.0.0": + version: 18.19.113 + resolution: "@types/node@npm:18.19.113" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/d96a1849a9b58f24db926cac55fa6ea0bcd5dbc1d0dbd3dcf22e195ed4a47260daec32ffefd0281ee5fc03d621bc36e40cf60406b7473415d74b9db77279e7a1 + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.33 + resolution: "@types/yargs@npm:17.0.33" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/d16937d7ac30dff697801c3d6f235be2166df42e4a88bf730fa6dc09201de3727c0a9500c59a672122313341de5f24e45ee0ff579c08ce91928e519090b7906b + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.35.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.10.0" + "@typescript-eslint/scope-manager": "npm:8.35.0" + "@typescript-eslint/type-utils": "npm:8.35.0" + "@typescript-eslint/utils": "npm:8.35.0" + "@typescript-eslint/visitor-keys": "npm:8.35.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^7.0.0" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + "@typescript-eslint/parser": ^8.35.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/27391f1b168a175fdc62370e5afe51317d4433115abbbff8ee0aea8ecd7bf6dd541a76f8e0cc94119750ae3146863204862640acb45394f0b92809e88d39f881 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/parser@npm:8.35.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.35.0" + "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/typescript-estree": "npm:8.35.0" + "@typescript-eslint/visitor-keys": "npm:8.35.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/8f1cda98f8bee3d79266974e5e5c831a0ca473e928fb16f1dc1c85ee24f2cb9c0fcf3c1bcbbef9d6044cf063f6e59d3198b766a27000776830fe591043e11625 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/project-service@npm:8.35.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.35.0" + "@typescript-eslint/types": "npm:^8.35.0" + debug: "npm:^4.3.4" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/c2d6d44b6b2ff3ecabec8ade824163196799060ac457661eb94049487d770ce68d128b33a2f24090adf1ebcb66ff6c9a05fc6659349b9a0784a5a080ecf8ff81 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/scope-manager@npm:8.35.0" + dependencies: + "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/visitor-keys": "npm:8.35.0" + checksum: 10c0/a27cf27a1852bb0d6ea08f475fcc79557f1977be96ef563d92127e8011e4065566441c32c40eb7a530111ffd3a8489919da7f8a2b7466a610cfc9c07670a9601 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/baa18e7137ba72f7d138f50d1168e8f334198a36499f954821e2369027e5b3d53ca93c354943e7782ba5caab604b050af10f353ccca34fbc0b23c48d6174832f + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/type-utils@npm:8.35.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:8.35.0" + "@typescript-eslint/utils": "npm:8.35.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/9e23a332484a055eb73ba8918f95a981e0cec8fa623ba9ee0b57328af052628d630a415e32e0dbe95318574e62d4066f8aecc994728b3cedd906f36c616ec362 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/types@npm:8.35.0" + checksum: 10c0/a2711a932680805e83252b5d7c55ac30437bdc4d40c444606cf6ccb6ba23a682da015ec03c64635e77bf733f84d9bb76810bf4f7177fd3a660db8a2c8a05e845 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.35.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.35.0" + "@typescript-eslint/tsconfig-utils": "npm:8.35.0" + "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/visitor-keys": "npm:8.35.0" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/7e94f6a92efc5832289e8bfd0b61209aa501224c935359253c29aeef8e0b981b370ee2a43e2909991c3c3cf709fcccb6380474e0e9a863e8f89e2fbd213aed59 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/utils@npm:8.35.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.35.0" + "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/typescript-estree": "npm:8.35.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/e3317df7875305bee16edd573e4bfdafc099f26f9c284d8adb351333683aacd5b668320870653dff7ec7e0da1982bbf89dc06197bc193a3be65362f21452dbea + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.35.0" + dependencies: + "@typescript-eslint/types": "npm:8.35.0" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/df18ca9b6931cb58f5dc404fcc94f9e0cc1c22f3053c7013ab588bb8ccccd3d58a70c577c01267845d57fa124a8cf8371260d284dad97505c56b2abcf70a3dce + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + languageName: node + linkType: hard + +"@wgtechlabs/log-engine@workspace:.": + version: 0.0.0-use.local + resolution: "@wgtechlabs/log-engine@workspace:." + dependencies: + "@types/eslint-plugin-security": "npm:^3" + "@types/jest": "npm:^29.0.0" + "@types/node": "npm:^18.0.0" + "@typescript-eslint/eslint-plugin": "npm:^8.35.0" + "@typescript-eslint/parser": "npm:^8.35.0" + eslint: "npm:^8.45.0" + eslint-plugin-security: "npm:^2.0.0" + jest: "npm:^29.0.0" + npm-run-all: "npm:^4.1.5" + snyk: "npm:^1.1000.0" + ts-jest: "npm:^29.0.0" + ts-node: "npm:^10.0.0" + typescript: "npm:^5.8.3" + languageName: unknown + linkType: soft + +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.1.1": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 + languageName: node + linkType: hard + +"acorn@npm:^8.11.0, acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 + languageName: node + linkType: hard + +"ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async@npm:^3.2.3": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.1.0 + resolution: "babel-preset-current-node-syntax@npm:1.1.0" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0b838d4412e3322cb4436f246e24e9c00bebcedfd8f00a2f51489db683bd35406bbd55a700759c28d26959c6e03f84dd6a1426f576f440267c1d7a73c5717281 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"boolean@npm:^3.0.1": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: 10c0/6a0dc9668f6f3dda42a53c181fcbdad223169c8d87b6c4011b87a8b14a21770efb2934a778f063d7ece17280f8c06d313c87f7b834bb1dd526a867ffcd00febf + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.25.1 + resolution: "browserslist@npm:4.25.1" + dependencies: + caniuse-lite: "npm:^1.0.30001726" + electron-to-chromium: "npm:^1.5.173" + node-releases: "npm:^2.0.19" + update-browserslist-db: "npm:^1.1.3" + bin: + browserslist: cli.js + checksum: 10c0/acba5f0bdbd5e72dafae1e6ec79235b7bad305ed104e082ed07c34c38c7cb8ea1bc0f6be1496958c40482e40166084458fc3aee15111f15faa79212ad9081b2a + languageName: node + linkType: hard + +"bs-logger@npm:^0.2.6": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: "npm:2.x" + checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001726": + version: 1.0.30001726 + resolution: "caniuse-lite@npm:1.0.30001726" + checksum: 10c0/2c5f91da7fd9ebf8c6b432818b1498ea28aca8de22b30dafabe2a2a6da1e014f10e67e14f8e68e872a0867b6b4cd6001558dde04e3ab9770c9252ca5c8849d0e + languageName: node + linkType: hard + +"chalk@npm:^2.4.1": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.0.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.5": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 + languageName: node + linkType: hard + +"dedent@npm:^1.0.0": + version: 1.6.0 + resolution: "dedent@npm:1.6.0" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/671b8f5e390dd2a560862c4511dd6d2638e71911486f78cb32116551f8f2aa6fcaf50579ffffb2f866d46b5b80fd72470659ca5760ede8f967619ef7df79e8a5 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10c0/f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ejs@npm:^3.1.10": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.173": + version: 1.5.177 + resolution: "electron-to-chromium@npm:1.5.177" + checksum: 10c0/cf833a5ef576690e2c04a79dfbd9d73d85ae7215b6d3bd66d858e47940ddda567091688ddee0683454c89733c9acc7a4fb4dcbb2caa8c317639d9eb35074ab06 + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" + dependencies: + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b + languageName: node + linkType: hard + +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-plugin-security@npm:^2.0.0": + version: 2.1.1 + resolution: "eslint-plugin-security@npm:2.1.1" + dependencies: + safe-regex: "npm:^2.1.1" + checksum: 10c0/85e8f86a9e870db9418e2a1511278ec3d3bc6cdfbbb8f26822bf8d16b909555eab94e26f257ef516d8d05cb56eca76230d909c357e23e21df0e5271973697820 + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 + languageName: node + linkType: hard + +"eslint@npm:^8.45.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.1" + "@humanwhocodes/config-array": "npm:^0.13.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + languageName: node + linkType: hard + +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.4.2": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.2": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.19.1 + resolution: "fastq@npm:1.19.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fdir@npm:^6.4.4": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9 + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^10.2.2": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"global-agent@npm:^3.0.0": + version: 3.0.0 + resolution: "global-agent@npm:3.0.0" + dependencies: + boolean: "npm:^3.0.1" + es6-error: "npm:^4.1.1" + matcher: "npm:^3.0.0" + roarr: "npm:^2.15.3" + semver: "npm:^7.3.2" + serialize-error: "npm:^7.0.1" + checksum: 10c0/bb8750d026b25da437072762fd739098bad92ff72f66483c3929db4579e072f5523960f7e7fd70ee0d75db48898067b5dc1c9c1d17888128cff008fcc34d1bd3 + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + +"globalthis@npm:^1.0.1, globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4": + version: 2.8.9 + resolution: "hosted-git-info@npm:2.8.9" + checksum: 10c0/317cbc6b1bbbe23c2a40ae23f3dafe9fa349ce42a89a36f930e3f9c0530c179a3882d2ef1e4141a4c3674d6faaea862138ec55b43ad6f75e387fda2483a13c70 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d + languageName: node + linkType: hard + +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.0 + resolution: "is-generator-function@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.0" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/fdfa96c8087bf36fc4cd514b474ba2ff404219a4dd4cfa6cf5426404a1eed259bdcdb98f082a71029a48d01f27733e3436ecc6690129a7ec09cb0434bee03a2a + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.9.2 + resolution: "jake@npm:10.9.2" + dependencies: + async: "npm:^3.2.3" + chalk: "npm:^4.0.2" + filelist: "npm:^1.0.4" + minimatch: "npm:^3.1.2" + bin: + jake: bin/cli.js + checksum: 10c0/c4597b5ed9b6a908252feab296485a4f87cba9e26d6c20e0ca144fb69e0c40203d34a2efddb33b3d297b8bd59605e6c1f44f6221ca1e10e69175ecbf3ff5fe31 + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 + languageName: node + linkType: hard + +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jest@npm:^29.0.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-better-errors@npm:^1.0.1": + version: 1.0.2 + resolution: "json-parse-better-errors@npm:1.0.2" + checksum: 10c0/2f1287a7c833e397c9ddd361a78638e828fc523038bb3441fd4fc144cfd2c6cd4963ffb9e207e648cf7b692600f1e1e524e965c32df5152120910e4903a47dcb + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"keyv@npm:^4.5.3": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lie@npm:3.1.1": + version: 3.1.1 + resolution: "lie@npm:3.1.1" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10c0/d62685786590351b8e407814acdd89efe1cb136f05cb9236c5a97b2efdca1f631d2997310ad2d565c753db7596799870140e4777c9c9b8c44a0f6bf42d1804a1 + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"load-json-file@npm:^4.0.0": + version: 4.0.0 + resolution: "load-json-file@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.2" + parse-json: "npm:^4.0.0" + pify: "npm:^3.0.0" + strip-bom: "npm:^3.0.0" + checksum: 10c0/6b48f6a0256bdfcc8970be2c57f68f10acb2ee7e63709b386b2febb6ad3c86198f840889cdbe71d28f741cbaa2f23a7771206b138cd1bdd159564511ca37c1d5 + languageName: node + linkType: hard + +"localforage@npm:^1.8.1": + version: 1.10.0 + resolution: "localforage@npm:1.10.0" + dependencies: + lie: "npm:3.1.1" + checksum: 10c0/00f19f1f97002e6721587ed5017f502d58faf80dae567d5065d4d1ee0caf0762f40d2e2dba7f0ef7d3f14ee6203242daae9ecad97359bfc10ecff36df11d85a3 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-error@npm:^1.1.1, make-error@npm:^1.3.6": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"matcher@npm:^3.0.0": + version: 3.0.0 + resolution: "matcher@npm:3.0.0" + dependencies: + escape-string-regexp: "npm:^4.0.0" + checksum: 10c0/2edf24194a2879690bcdb29985fc6bc0d003df44e04df21ebcac721fa6ce2f6201c579866bb92f9380bffe946f11ecd8cd31f34117fb67ebf8aca604918e127e + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"memorystream@npm:^0.3.1": + version: 0.3.1 + resolution: "memorystream@npm:0.3.1" + checksum: 10c0/4bd164657711d9747ff5edb0508b2944414da3464b7fe21ac5c67cf35bba975c4b446a0124bd0f9a8be54cfc18faf92e92bd77563a20328b1ccf2ff04e9f39b9 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1": + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10c0/95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.2.0 + resolution: "node-gyp@npm:11.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9 + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + languageName: node + linkType: hard + +"normalize-package-data@npm:^2.3.2": + version: 2.5.0 + resolution: "normalize-package-data@npm:2.5.0" + dependencies: + hosted-git-info: "npm:^2.1.4" + resolve: "npm:^1.10.0" + semver: "npm:2 || 3 || 4 || 5" + validate-npm-package-license: "npm:^3.0.1" + checksum: 10c0/357cb1646deb42f8eb4c7d42c4edf0eec312f3628c2ef98501963cc4bbe7277021b2b1d977f982b2edce78f5a1014613ce9cf38085c3df2d76730481357ca504 + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"npm-run-all@npm:^4.1.5": + version: 4.1.5 + resolution: "npm-run-all@npm:4.1.5" + dependencies: + ansi-styles: "npm:^3.2.1" + chalk: "npm:^2.4.1" + cross-spawn: "npm:^6.0.5" + memorystream: "npm:^0.3.1" + minimatch: "npm:^3.0.4" + pidtree: "npm:^0.3.0" + read-pkg: "npm:^3.0.0" + shell-quote: "npm:^1.6.1" + string.prototype.padend: "npm:^3.0.0" + bin: + npm-run-all: bin/npm-run-all/index.js + run-p: bin/run-p/index.js + run-s: bin/run-s/index.js + checksum: 10c0/736ee39bd35454d3efaa4a2e53eba6c523e2e17fba21a18edcce6b221f5cab62000bef16bb6ae8aff9e615831e6b0eb25ab51d52d60e6fa6f4ea880e4c6d31f4 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-json@npm:4.0.0" + dependencies: + error-ex: "npm:^1.3.1" + json-parse-better-errors: "npm:^1.0.1" + checksum: 10c0/8d80790b772ccb1bcea4e09e2697555e519d83d04a77c2b4237389b813f82898943a93ffff7d0d2406203bdd0c30dcf95b1661e3a53f83d0e417f053957bef32 + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10c0/dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-type@npm:^3.0.0": + version: 3.0.0 + resolution: "path-type@npm:3.0.0" + dependencies: + pify: "npm:^3.0.0" + checksum: 10c0/1332c632f1cac15790ebab8dd729b67ba04fc96f81647496feb1c2975d862d046f41e4b975dbd893048999b2cc90721f72924ad820acc58c78507ba7141a8e56 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc + languageName: node + linkType: hard + +"pidtree@npm:^0.3.0": + version: 0.3.1 + resolution: "pidtree@npm:0.3.1" + bin: + pidtree: bin/pidtree.js + checksum: 10c0/cd69b0182f749f45ab48584e3442c48c5dc4512502c18d5b0147a33b042c41a4db4269b9ce2f7c48f11833ee5e79d81f5ebc6f7bf8372d4ea55726f60dc505a1 + languageName: node + linkType: hard + +"pify@npm:^3.0.0": + version: 3.0.0 + resolution: "pify@npm:3.0.0" + checksum: 10c0/fead19ed9d801f1b1fcd0638a1ac53eabbb0945bf615f2f8806a8b646565a04a1b0e7ef115c951d225f042cca388fdc1cd3add46d10d1ed6951c20bd2998af10 + languageName: node + linkType: hard + +"pirates@npm:^4.0.4": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"read-pkg@npm:^3.0.0": + version: 3.0.0 + resolution: "read-pkg@npm:3.0.0" + dependencies: + load-json-file: "npm:^4.0.0" + normalize-package-data: "npm:^2.3.2" + path-type: "npm:^3.0.0" + checksum: 10c0/65acf2df89fbcd506b48b7ced56a255ba00adf7ecaa2db759c86cc58212f6fd80f1f0b7a85c848551a5d0685232e9b64f45c1fd5b48d85df2761a160767eeb93 + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regexp-tree@npm:~0.1.1": + version: 0.1.27 + resolution: "regexp-tree@npm:0.1.27" + bin: + regexp-tree: bin/regexp-tree + checksum: 10c0/f636f44b4a0d93d7d6926585ecd81f63e4ce2ac895bc417b2ead0874cd36b337dcc3d0fedc63f69bf5aaeaa4340f36ca7e750c9687cceaf8087374e5284e843c + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d + languageName: node + linkType: hard + +"resolve@npm:^1.10.0, resolve@npm:^1.20.0": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"roarr@npm:^2.15.3": + version: 2.15.4 + resolution: "roarr@npm:2.15.4" + dependencies: + boolean: "npm:^3.0.1" + detect-node: "npm:^2.0.4" + globalthis: "npm:^1.0.1" + json-stringify-safe: "npm:^5.0.1" + semver-compare: "npm:^1.0.0" + sprintf-js: "npm:^1.1.2" + checksum: 10c0/7d01d4c14513c461778dd673a8f9e53255221f8d04173aafeb8e11b23d8b659bb83f1c90cfe81af7f9c213b8084b404b918108fd792bda76678f555340cc64ec + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"safe-regex@npm:^2.1.1": + version: 2.1.1 + resolution: "safe-regex@npm:2.1.1" + dependencies: + regexp-tree: "npm:~0.1.1" + checksum: 10c0/53eb5d3ecf4b3c0954dff465eb179af4d2f5f77f74ba7b57489adbc4fa44454c3d391f37379cd28722d9ac6fa5b70be3f4645d4bd25df395fd99b934f6ec9265 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver-compare@npm:^1.0.0": + version: 1.0.0 + resolution: "semver-compare@npm:1.0.0" + checksum: 10c0/9ef4d8b81847556f0865f46ddc4d276bace118c7cb46811867af82e837b7fc473911981d5a0abc561fa2db487065572217e5b06e18701c4281bcdd2a1affaff1 + languageName: node + linkType: hard + +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + +"semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: "npm:^0.13.1" + checksum: 10c0/7982937d578cd901276c8ab3e2c6ed8a4c174137730f1fb0402d005af209a0e84d04acc874e317c936724c7b5b26c7a96ff7e4b8d11a469f4924a4b0ea814c05 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10c0/7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10c0/9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shell-quote@npm:^1.6.1": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"snyk@npm:^1.1000.0": + version: 1.1297.3 + resolution: "snyk@npm:1.1297.3" + dependencies: + "@sentry/node": "npm:^7.36.0" + global-agent: "npm:^3.0.0" + bin: + snyk: bin/snyk + checksum: 10c0/b9e46bec5e54d5653cf9e90cc9d8ca63c7432af28b907819952c637aff4eb3a829f4963c69a66445344d0b71c59d6c1085924eb30d7200288d69d1a1a0c23242 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.5 + resolution: "socks@npm:2.8.5" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/e427d0eb0451cfd04e20b9156ea8c0e9b5e38a8d70f21e55c30fbe4214eda37cfc25d782c63f9adc5fbdad6d062a0f127ef2cefc9a44b6fee2b9ea5d1ed10827 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.2.0 + resolution: "spdx-correct@npm:3.2.0" + dependencies: + spdx-expression-parse: "npm:^3.0.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/49208f008618b9119208b0dadc9208a3a55053f4fd6a0ae8116861bd22696fc50f4142a35ebfdb389e05ccf2de8ad142573fefc9e26f670522d899f7b2fe7386 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.5.0 + resolution: "spdx-exceptions@npm:2.5.0" + checksum: 10c0/37217b7762ee0ea0d8b7d0c29fd48b7e4dfb94096b109d6255b589c561f57da93bf4e328c0290046115961b9209a8051ad9f525e48d433082fc79f496a4ea940 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: "npm:^2.1.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.21 + resolution: "spdx-license-ids@npm:3.0.21" + checksum: 10c0/ecb24c698d8496aa9efe23e0b1f751f8a7a89faedcdfcbfabae772b546c2db46ccde8f3bc447a238eb86bbcd4f73fea88720ef3b8394f7896381bec3d7736411 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.2, sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string.prototype.padend@npm:^3.0.0": + version: 3.1.6 + resolution: "string.prototype.padend@npm:3.1.6" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/8f2c8c1f3db1efcdc210668c80c87f2cea1253d6029ff296a172b5e13edc9adebeed4942d023de8d31f9b13b69f3f5d73de7141959b1f09817fba5f527e83be1 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10c0/f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6 + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f + languageName: node + linkType: hard + +"ts-jest@npm:^29.0.0": + version: 29.4.0 + resolution: "ts-jest@npm:29.4.0" + dependencies: + bs-logger: "npm:^0.2.6" + ejs: "npm:^3.1.10" + fast-json-stable-stringify: "npm:^2.1.0" + json5: "npm:^2.2.3" + lodash.memoize: "npm:^4.1.2" + make-error: "npm:^1.3.6" + semver: "npm:^7.7.2" + type-fest: "npm:^4.41.0" + yargs-parser: "npm:^21.1.1" + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + bin: + ts-jest: cli.js + checksum: 10c0/c266431200786995b5bd32f8e61f17a564ce231278aace1d98fb0ae670f24013aeea06c90ec6019431e5a6f5e798868785131bef856085c931d193e2efbcea04 + languageName: node + linkType: hard + +"ts-node@npm:^10.0.0": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.13.1": + version: 0.13.1 + resolution: "type-fest@npm:0.13.1" + checksum: 10c0/0c0fa07ae53d4e776cf4dac30d25ad799443e9eef9226f9fddbb69242db86b08584084a99885cfa5a9dfe4c063ebdc9aa7b69da348e735baede8d43f1aeae93b + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^4.41.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 + languageName: node + linkType: hard + +"typescript@npm:^5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"undici-types@npm:~7.8.0": + version: 7.8.0 + resolution: "undici-types@npm:7.8.0" + checksum: 10c0/9d9d246d1dc32f318d46116efe3cfca5a72d4f16828febc1918d94e58f6ffcf39c158aa28bf5b4fc52f410446bc7858f35151367bd7a49f21746cab6497b709b + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.3": + version: 1.1.3 + resolution: "update-browserslist-db@npm:1.1.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.1": + version: 9.3.0 + resolution: "v8-to-istanbul@npm:9.3.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 + languageName: node + linkType: hard + +"validate-npm-package-license@npm:^3.0.1": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: "npm:^3.0.0" + spdx-expression-parse: "npm:^3.0.0" + checksum: 10c0/7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + languageName: node + linkType: hard + +"which@npm:^1.2.9": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard