diff --git a/.gitignore b/.gitignore index 9d9b5b0e..bdc42ee9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ lib .idea codeql-db* results.sarif +*.tsbuildinfo diff --git a/TYPESCRIPT_CONFIG.md b/TYPESCRIPT_CONFIG.md new file mode 100644 index 00000000..1c428bb7 --- /dev/null +++ b/TYPESCRIPT_CONFIG.md @@ -0,0 +1,114 @@ +# TypeScript Configuration Guide + +This project uses a **single, simplified TypeScript configuration** optimized for both VSCode developer experience and clean production builds. + +## Configuration Philosophy + +Following the upstream [contributor-assistant/github-action](https://github.com/contributor-assistant/github-action) pattern with improvements: +- ✅ Single `tsconfig.json` (matches upstream simplicity) +- ✅ VSCode recognizes Jest types in test files (no red squiggles!) +- ✅ Production builds compile only `src/` files (clean output) +- ✅ Enhanced with `@types/jest` for full IntelliSense (improvement over upstream) + +## Configuration File + +### 📁 `tsconfig.json` (Universal Config) +- **Purpose**: IDE support and type checking for all files +- **VSCode**: Sees all files including tests with Jest types +- **Build**: Command-line flags ensure only `src/` compiles +- **Types**: Node.js + Jest for full IDE support +- **Used by**: + - VSCode TypeScript language server (checks everything) + - Build script (compiles only src/ via CLI args) + - Jest via ts-jest transformer + +**Key settings:** +```json +{ + "compilerOptions": { + "types": ["node", "jest"] // IDE recognizes Jest globals everywhere + }, + "exclude": ["node_modules", "dist", "lib"] // Build artifacts only +} +``` + +**Build command:** +```json +{ + "scripts": { + "build": "tsc --rootDir src --outDir lib src/**/*.ts && ncc build" + } +} +``` +- `--rootDir src`: Sets source root for path resolution +- `src/**/*.ts`: Only compile source files (not tests) +- `--outDir lib`: Output to lib directory + +## Scripts + +```bash +# Production build (only src/ compiled to lib/) +npm run build + +# Run tests (uses same tsconfig.json) +npm test + +# Type check everything including tests +npx tsc --noEmit +``` + +## Benefits + +✅ **VSCode IntelliSense**: No "Cannot find name 'jest'" errors in test files +✅ **Clean Builds**: Only `src/` files compiled to `lib/` directory +✅ **Upstream Compatible**: Single config matches contributor-assistant/github-action +✅ **Clean Production Builds**: Tests excluded via `exclude` and `rootDir` +✅ **Superior IDE Support**: @types/jest eliminates "Cannot find name 'jest'" errors +✅ **Simple Maintenance**: Single config file to manage +✅ **CI/CD Compatible**: Node.js matrix builds work without modification + +## Improvements Over Upstream + +While matching upstream's single-config approach, we include: +1. **`@types/jest`** in devDependencies (VSCode IntelliSense for tests) +2. **`jest`** package in devDependencies (not in upstream) +3. **`types: ["node", "jest"]`** in tsconfig.json (prevents IDE errors) + +## VSCode Configuration + +The `.vscode/settings.json` is configured to: +- Use workspace TypeScript version +- Recognize Jest command for test execution +- Exclude compiled files and node_modules from search + +## Dependency Organization + +### Production Dependencies (`dependencies`) +```json +{ + "@actions/core": "Runtime GitHub Actions core", + "@actions/github": "Runtime GitHub API access", + "@octokit/rest": "Runtime REST API client", + "lodash": "Runtime utility library" +} +``` + +### Development Dependencies (`devDependencies`) +```json +{ + "@types/jest": "Jest type definitions", + "@types/node": "Node.js type definitions", + "jest": "Test framework", + "ts-jest": "TypeScript transformer for Jest", + "typescript": "TypeScript compiler", + "@vercel/ncc": "Production bundler" +} +``` + +## How It Works + +1. **VSCode** loads `tsconfig.json` → sees everything, no errors +2. **`npm run build`** runs `tsc` → compiles only `src/` (via `rootDir`), excludes tests +3. **`npm test`** runs Jest with ts-jest → uses same tsconfig.json + +This setup matches the upstream GitHub Action project structure while providing better developer experience through proper type definitions. diff --git a/__tests__/checkAllowList.test.ts b/__tests__/checkAllowList.test.ts index f522f04d..284173de 100644 --- a/__tests__/checkAllowList.test.ts +++ b/__tests__/checkAllowList.test.ts @@ -2,15 +2,14 @@ import { checkAllowList } from '../src/checkAllowList' import * as input from '../src/shared/getInputs' import { CommittersDetails } from '../src/interfaces' import { getFileContent } from '../src/persistence/persistence' -import { mocked } from 'ts-jest/utils' jest.mock('../src/shared/getInputs') jest.mock('../src/persistence/persistence') -const mockedGetUsernameAllowList = mocked(input.getUsernameAllowList) -const mockedGetDomainAllowList = mocked(input.getDomainAllowList) -const mockedGetDomainsFile = mocked(input.getDomainsFile) -const mockedGetFileContent = mocked(getFileContent) +const mockedGetUsernameAllowList = jest.mocked(input.getUsernameAllowList) +const mockedGetDomainAllowList = jest.mocked(input.getDomainAllowList) +const mockedGetDomainsFile = jest.mocked(input.getDomainsFile) +const mockedGetFileContent = jest.mocked(getFileContent) describe('checkAllowList', () => { beforeEach(() => { @@ -23,28 +22,28 @@ describe('checkAllowList', () => { describe('Exact username matching', () => { it('should filter out committer with exact username match', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot,bot-user,copilot') - + const committers: CommittersDetails[] = [ { name: 'copilot', id: 123, email: '[email protected]' }, { name: 'real-user', id: 456, email: '[email protected]' } ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') }) it('should be case-sensitive for exact username matches', async () => { mockedGetUsernameAllowList.mockReturnValue('Copilot') - + const committers: CommittersDetails[] = [ { name: 'copilot', id: 123, email: '[email protected]' }, { name: 'Copilot', id: 456, email: '[email protected]' } ] const result = await checkAllowList(committers) - + // Only lowercase 'copilot' should remain (not in allowlist) // Uppercase 'Copilot' should be filtered (in allowlist) expect(result).toHaveLength(1) @@ -53,7 +52,7 @@ describe('checkAllowList', () => { it('should handle multiple exact matches', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot, semantic-release-bot, copilot') - + const committers: CommittersDetails[] = [ { name: 'dependabot', id: 1, email: '[email protected]' }, { name: 'copilot', id: 2, email: '[email protected]' }, @@ -63,14 +62,14 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['user1', 'user2']) }) it('should handle spaces in allowlist', async () => { mockedGetUsernameAllowList.mockReturnValue(' bot-user , another-bot ') - + const committers: CommittersDetails[] = [ { name: 'bot-user', id: 1, email: '[email protected]' }, { name: 'another-bot', id: 2, email: '[email protected]' }, @@ -78,14 +77,14 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') }) it('should not match partial usernames', async () => { mockedGetUsernameAllowList.mockReturnValue('bot') - + const committers: CommittersDetails[] = [ { name: 'bot', id: 1, email: '[email protected]' }, { name: 'bot-user', id: 2, email: '[email protected]' }, @@ -93,7 +92,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Only exact 'bot' should be filtered expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['bot-user', 'my-bot']) @@ -103,7 +102,7 @@ describe('checkAllowList', () => { describe('Wildcard username matching', () => { it('should match wildcards at end of pattern', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot*') - + const committers: CommittersDetails[] = [ { name: 'dependabot', id: 1, email: '[email protected]' }, { name: 'dependabot[bot]', id: 2, email: '[email protected]' }, @@ -113,7 +112,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Should filter: dependabot, dependabot[bot], dependabot-preview // Should keep: my-dependabot (doesn't start with dependabot), real-user expect(result).toHaveLength(2) @@ -122,7 +121,7 @@ describe('checkAllowList', () => { it('should match wildcards at beginning of pattern', async () => { mockedGetUsernameAllowList.mockReturnValue('*-bot') - + const committers: CommittersDetails[] = [ { name: 'my-bot', id: 1, email: '[email protected]' }, { name: 'another-bot', id: 2, email: '[email protected]' }, @@ -132,7 +131,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Should filter: my-bot, another-bot // Should keep: bot, bot-user, real-user expect(result).toHaveLength(3) @@ -141,7 +140,7 @@ describe('checkAllowList', () => { it('should match wildcards in middle of pattern', async () => { mockedGetUsernameAllowList.mockReturnValue('github-*[bot]') - + const committers: CommittersDetails[] = [ { name: 'github-copilot[bot]', id: 1, email: '[email protected]' }, { name: 'github-actions[bot]', id: 2, email: '[email protected]' }, @@ -150,7 +149,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Should filter: github-copilot[bot], github-actions[bot] // Should keep: github-bot (doesn't end with [bot]), real-user expect(result).toHaveLength(2) @@ -159,7 +158,7 @@ describe('checkAllowList', () => { it('should handle multiple wildcards in one pattern', async () => { mockedGetUsernameAllowList.mockReturnValue('*bot*') - + const committers: CommittersDetails[] = [ { name: 'mybot', id: 1, email: '[email protected]' }, { name: 'bot-user', id: 2, email: '[email protected]' }, @@ -168,7 +167,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Should filter all with 'bot' in them expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') @@ -176,7 +175,7 @@ describe('checkAllowList', () => { it('should handle multiple wildcard patterns', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot*, *[bot], semantic-*') - + const committers: CommittersDetails[] = [ { name: 'dependabot', id: 1, email: '[email protected]' }, { name: 'dependabot-preview', id: 2, email: '[email protected]' }, @@ -186,14 +185,14 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') }) it('should escape regex special characters', async () => { mockedGetUsernameAllowList.mockReturnValue('bot.name*') - + const committers: CommittersDetails[] = [ { name: 'bot.name', id: 1, email: '[email protected]' }, { name: 'bot.name-test', id: 2, email: '[email protected]' }, @@ -202,7 +201,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // Should filter: bot.name, bot.name-test // Should keep: botXname (. is literal, not regex wildcard), real-user expect(result).toHaveLength(2) @@ -213,7 +212,7 @@ describe('checkAllowList', () => { describe('Email domain matching', () => { it('should match email domain with @ prefix', async () => { mockedGetDomainAllowList.mockReturnValue('@example.com') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -221,28 +220,28 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['user2', 'user3']) }) it('should auto-add @ prefix if missing', async () => { mockedGetDomainAllowList.mockReturnValue('example.com') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' } ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user2') }) it('should handle multiple email domains', async () => { mockedGetDomainAllowList.mockReturnValue('@example.com, @test.org, @bot.io') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -251,14 +250,14 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user4') }) it('should handle committers without email field', async () => { mockedGetDomainAllowList.mockReturnValue('@example.com') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2 }, // no email @@ -266,7 +265,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // user1 filtered by domain, user2 and user3 kept (no email to check) expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['user2', 'user3']) @@ -274,7 +273,7 @@ describe('checkAllowList', () => { it('should match subdomain emails correctly', async () => { mockedGetDomainAllowList.mockReturnValue('@example.com') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -282,14 +281,14 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // All should be filtered (all end with @example.com or subdomain) expect(result).toHaveLength(0) }) it('should not match partial domain names', async () => { mockedGetDomainAllowList.mockReturnValue('@example.com') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, // should NOT match @@ -297,7 +296,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // user1 and user3 filtered, user2 kept (different domain) expect(result).toHaveLength(1) expect(result[0].name).toBe('user2') @@ -305,14 +304,14 @@ describe('checkAllowList', () => { it('should skip empty domain patterns', async () => { mockedGetDomainAllowList.mockReturnValue(' , @example.com , ') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' } ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user2') }) @@ -322,7 +321,7 @@ describe('checkAllowList', () => { it('should filter by both username and email domain', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot, copilot*') mockedGetDomainAllowList.mockReturnValue('@bot.example.com') - + const committers: CommittersDetails[] = [ { name: 'dependabot', id: 1, email: '[email protected]' }, { name: 'copilot-agent', id: 2, email: '[email protected]' }, @@ -331,7 +330,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + // dependabot, copilot-agent, bot-service all filtered expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') @@ -340,7 +339,7 @@ describe('checkAllowList', () => { it('should filter if either username OR email domain matches', async () => { mockedGetUsernameAllowList.mockReturnValue('bot-user') mockedGetDomainAllowList.mockReturnValue('@automated.com') - + const committers: CommittersDetails[] = [ { name: 'bot-user', id: 1, email: '[email protected]' }, // username match { name: 'real-user', id: 2, email: '[email protected]' }, // email match @@ -348,7 +347,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('another-user') }) @@ -363,7 +362,7 @@ describe('checkAllowList', () => { content: Buffer.from(fileContent).toString('base64') } } as any) - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -371,7 +370,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user3') expect(mockedGetFileContent).toHaveBeenCalledWith('domains.json') @@ -386,7 +385,7 @@ describe('checkAllowList', () => { content: Buffer.from(fileContent).toString('base64') } } as any) - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -394,7 +393,7 @@ describe('checkAllowList', () => { ] const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user3') }) @@ -402,14 +401,14 @@ describe('checkAllowList', () => { it('should handle missing domain file (404)', async () => { mockedGetDomainsFile.mockReturnValue('domains.json') mockedGetFileContent.mockRejectedValue({ status: '404' }) - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' } ] // Should not throw, just continue without file domains const result = await checkAllowList(committers) - + expect(result).toHaveLength(1) expect(result[0].name).toBe('user1') }) @@ -417,7 +416,7 @@ describe('checkAllowList', () => { it('should throw on non-404 file errors', async () => { mockedGetDomainsFile.mockReturnValue('domains.json') mockedGetFileContent.mockRejectedValue({ status: '500' }) - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' } ] @@ -432,7 +431,7 @@ describe('checkAllowList', () => { content: Buffer.from('{ invalid json }').toString('base64') } } as any) - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' } ] @@ -456,7 +455,7 @@ const committers: CommittersDetails[] = [ // Should not throw, but also should not add non-array content const result = await checkAllowList(committers) - + // Both should remain (no domains loaded from file) expect(result).toHaveLength(2) }) @@ -466,21 +465,21 @@ const committers: CommittersDetails[] = [ it('should handle empty allowlists', async () => { mockedGetUsernameAllowList.mockReturnValue('') mockedGetDomainAllowList.mockReturnValue('') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' } ] const result = await checkAllowList(committers) - + // All should remain (no filters) expect(result).toHaveLength(2) }) it('should handle null/undefined committers in array', async () => { mockedGetUsernameAllowList.mockReturnValue('bot') - + const committers: any[] = [ { name: 'user1', id: 1 }, null, @@ -490,7 +489,7 @@ const committers: CommittersDetails[] = [ ] const result = await checkAllowList(committers) - + // null and undefined filtered out, 'bot' filtered, user1 and user2 remain expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['user1', 'user2']) @@ -499,7 +498,7 @@ const committers: CommittersDetails[] = [ it('should not allow regex injection via username patterns', async () => { // Attempt to inject regex that would match everything mockedGetUsernameAllowList.mockReturnValue('.*') - + const committers: CommittersDetails[] = [ { name: 'user1', id: 1, email: '[email protected]' }, { name: 'user2', id: 2, email: '[email protected]' }, @@ -507,7 +506,7 @@ const committers: CommittersDetails[] = [ ] const result = await checkAllowList(committers) - + // Only literal '.*' should be filtered (treated as wildcard pattern that matches anything with . and anything after) // Because of lodash escapeRegExp, it should match strings containing literal dot-star // Actually with wildcards, .* becomes \.* -> .* regex, which matches everything @@ -521,7 +520,7 @@ const committers: CommittersDetails[] = [ it('should handle special regex characters in exact match mode', async () => { // These should be treated as literal characters mockedGetUsernameAllowList.mockReturnValue('user.name, user[bot], user$123, user^test') - + const committers: CommittersDetails[] = [ { name: 'user.name', id: 1, email: '[email protected]' }, { name: 'userXname', id: 2, email: '[email protected]' }, // should NOT match user.name @@ -532,7 +531,7 @@ const committers: CommittersDetails[] = [ ] const result = await checkAllowList(committers) - + // All special char users filtered (exact match), userXname and normal-user remain expect(result).toHaveLength(2) expect(result.map(c => c.name)).toEqual(['userXname', 'normal-user']) @@ -541,7 +540,7 @@ const committers: CommittersDetails[] = [ it('should handle very long allowlists without performance issues', async () => { const longList = Array.from({ length: 1000 }, (_, i) => `bot-${i}`).join(',') mockedGetUsernameAllowList.mockReturnValue(longList) - + const committers: CommittersDetails[] = [ { name: 'bot-500', id: 1, email: '[email protected]' }, { name: 'real-user', id: 2, email: '[email protected]' }, @@ -551,7 +550,7 @@ const committers: CommittersDetails[] = [ const start = Date.now() const result = await checkAllowList(committers) const duration = Date.now() - start - + expect(result).toHaveLength(1) expect(result[0].name).toBe('real-user') expect(duration).toBeLessThan(1000) // Should complete in < 1 second @@ -559,14 +558,14 @@ const committers: CommittersDetails[] = [ it('should handle empty username but valid email', async () => { mockedGetDomainAllowList.mockReturnValue('@bot.example.com') - + const committers: CommittersDetails[] = [ { name: '', id: 1, email: '[email protected]' }, { name: 'user', id: 2, email: '[email protected]' } ] const result = await checkAllowList(committers) - + // Empty name with bot email should be filtered expect(result).toHaveLength(1) expect(result[0].name).toBe('user') @@ -576,7 +575,7 @@ const committers: CommittersDetails[] = [ describe('Real-world scenarios from rdkcentral', () => { it('should handle copilot variants correctly', async () => { mockedGetUsernameAllowList.mockReturnValue('copilot, Copilot, github-copilot[bot], github-copilot, copilot[bot], copilot-swe-agent[bot]') - + const committers: CommittersDetails[] = [ { name: 'copilot', id: 1, email: '[email protected]' }, { name: 'Copilot', id: 2, email: '[email protected]' }, @@ -587,7 +586,7 @@ const committers: CommittersDetails[] = [ ] const result = await checkAllowList(committers) - + // Only TB-1993 should remain expect(result).toHaveLength(1) expect(result[0].name).toBe('TB-1993') @@ -595,7 +594,7 @@ const committers: CommittersDetails[] = [ it('should handle rdkcentral allowlist pattern', async () => { mockedGetUsernameAllowList.mockReturnValue('dependabot*, dependabot[bot], dependabot, semantic-release-bot, rdkcm-rdke, rdkcm-bot, copilot, Copilot, github-copilot[bot], github-copilot, copilot[bot], copilot-swe-agent[bot]') - + const committers: CommittersDetails[] = [ { name: 'dependabot', id: 1, email: '[email protected]' }, { name: 'dependabot[bot]', id: 2, email: '[email protected]' }, @@ -608,7 +607,7 @@ const committers: CommittersDetails[] = [ ] const result = await checkAllowList(committers) - + // Only realuser123 should remain expect(result).toHaveLength(1) expect(result[0].name).toBe('realuser123') diff --git a/__tests__/claValidation.test.ts b/__tests__/claValidation.test.ts new file mode 100644 index 00000000..34031870 --- /dev/null +++ b/__tests__/claValidation.test.ts @@ -0,0 +1,653 @@ +/** + * Integration Tests for CLA Validation Flow + * + * Tests all permutations of: + * - Signature database state (signed/unsigned/partial) + * - Allowlist configuration (username/domain/combined/none) + * - Expected outcomes (pass/fail) + */ + +import * as core from '@actions/core' +import { context } from '@actions/github' +import * as input from '../src/shared/getInputs' +import { getFileContent } from '../src/persistence/persistence' +import getCommitters from '../src/graphql' +import { CommittersDetails } from '../src/interfaces' + +// Mock all external dependencies FIRST +jest.mock('@actions/core') +jest.mock('@actions/github') +jest.mock('../src/shared/getInputs') +jest.mock('../src/persistence/persistence') +jest.mock('../src/graphql') +jest.mock('../src/pullRerunRunner', () => ({ + reRunLastWorkFlowIfRequired: jest.fn().mockResolvedValue(undefined) +})) + +// Set up default mocks for inputs BEFORE importing modules that use them +const mockedGetUsernameAllowList = jest.mocked(input.getUsernameAllowList) +const mockedGetDomainAllowList = jest.mocked(input.getDomainAllowList) +const mockedGetDomainsFile = jest.mocked(input.getDomainsFile) +const mockedGetFileContent = jest.mocked(getFileContent) +const mockedGetCommitters = jest.mocked(getCommitters) +const mockedGetPathToSignatures = jest.mocked(input.getPathToSignatures) +const mockedGetRemoteRepoName = jest.mocked(input.getRemoteRepoName) +const mockedGetRemoteOrgName = jest.mocked(input.getRemoteOrgName) +const mockedSetFailed = jest.mocked(core.setFailed) +const mockedInfo = jest.mocked(core.info) + +// Initialize with empty strings to prevent module load errors +mockedGetUsernameAllowList.mockReturnValue('') +mockedGetDomainAllowList.mockReturnValue('') +mockedGetDomainsFile.mockReturnValue('') +mockedGetPathToSignatures.mockReturnValue('signatures.json') +mockedGetRemoteRepoName.mockReturnValue('test-repo') +mockedGetRemoteOrgName.mockReturnValue('test-owner') + +// NOW import setupClaCheck after mocks are set up +import { setupClaCheck } from '../src/setupClaCheck' + +// Mock octokit for PR comments and signatures file +jest.mock('../src/octokit', () => ({ + octokit: { + rest: { + repos: { + getContent: jest.fn() + } + }, + issues: { + listComments: jest.fn().mockResolvedValue({ data: [] }), + createComment: jest.fn().mockResolvedValue({}), + updateComment: jest.fn().mockResolvedValue({}) + }, + pulls: { + listCommits: jest.fn().mockResolvedValue({ data: [] }) + } + } +})) + +describe('CLA Validation - Full Integration Flow', () => { + beforeEach(() => { + jest.clearAllMocks() + + // Set context directly (avoid defineProperty to prevent redefine errors) + // @ts-ignore + context.repo = { owner: 'test-owner', repo: 'test-repo' } + // @ts-ignore + context.issue = { owner: 'test-owner', repo: 'test-repo', number: 1 } + // @ts-ignore + context.payload = { + pull_request: { number: 1, head: { sha: 'abc123' } } + } + + // Default input mocks + mockedGetUsernameAllowList.mockReturnValue('') + mockedGetDomainAllowList.mockReturnValue('') + mockedGetDomainsFile.mockReturnValue('') + mockedGetPathToSignatures.mockReturnValue('signatures.json') + mockedGetRemoteRepoName.mockReturnValue('test-repo') + mockedGetRemoteOrgName.mockReturnValue('test-owner') + + // Mock summary API with all needed methods + ;(core.summary as any) = { + addHeading: jest.fn().mockReturnThis(), + addRaw: jest.fn().mockReturnThis(), + addTable: jest.fn().mockReturnThis(), + addBreak: jest.fn().mockReturnThis(), + addCodeBlock: jest.fn().mockReturnThis(), + addList: jest.fn().mockReturnThis(), + write: jest.fn().mockResolvedValue(undefined) + } + }) + + describe('Scenario 1: No Allowlist - All Contributors Signed', () => { + it('should PASS when all contributors have signed in database', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' }, + { name: 'user3', id: 103, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user2', id: 102, created_at: '2026-01-01' }, + { name: 'user3', id: 103, created_at: '2026-01-01' } + ] + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + }) + + describe('Scenario 2: No Allowlist - No Contributors Signed', () => { + it('should FAIL when no contributors have signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' }, + { name: 'user3', id: 103, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [] // Empty - no signatures + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 3: No Allowlist - Partial Contributors Signed', () => { + it('should FAIL when only some contributors have signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' }, + { name: 'user3', id: 103, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' } + // user2 and user3 not signed + ] + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail with 2 unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('2') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 4: All Bots in Allowlist', () => { + it('should PASS when all contributors are bots in allowlist', async () => { + const committers: CommittersDetails[] = [ + { name: 'dependabot', id: 201, email: '[email protected]' }, + { name: 'copilot', id: 202, email: '[email protected]' }, + { name: 'github-actions[bot]', id: 203, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [] // No signatures needed - all allowlisted + } + + mockedGetUsernameAllowList.mockReturnValue('dependabot*, copilot, *[bot]') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed - all filtered by allowlist + console.log('mockedInfo calls:', mockedInfo.mock.calls) + console.log('mockedSetFailed calls:', mockedSetFailed.mock.calls) + if (mockedSetFailed.mock.calls.length > 0) { + console.log('Failure message:', mockedSetFailed.mock.calls[0][0]) + } + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + }) + + describe('Scenario 5: Mixed Bots+Users - Bots Filtered, Users Signed', () => { + it('should PASS when bots filtered by allowlist and users all signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'dependabot', id: 201, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user2', id: 102, created_at: '2026-01-01' } + ] + } + + mockedGetUsernameAllowList.mockReturnValue('dependabot*') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + }) + + describe('Scenario 6: Mixed Bots+Users - Bots Filtered, Users Partial Signed', () => { + it('should FAIL when bots filtered but some users unsigned', async () => { + const committers: CommittersDetails[] = [ + { name: 'copilot', id: 202, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' } + // user2 not signed + ] + } + + mockedGetUsernameAllowList.mockReturnValue('copilot') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - user2 unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('1') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 7: Mixed Bots+Users - Bots Filtered, Users None Signed', () => { + it('should FAIL when bots filtered but no users signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'dependabot[bot]', id: 203, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [] + } + + mockedGetUsernameAllowList.mockReturnValue('*[bot]') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - 2 users unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('2') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 8: Username Allowlist - Remaining All Signed', () => { + it('should PASS when some users in allowlist and remaining all signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'admin-user', id: 301, email: '[email protected]' }, + { name: 'bot-service', id: 302, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user2', id: 102, created_at: '2026-01-01' } + ] + } + + mockedGetUsernameAllowList.mockReturnValue('admin-*, *bot*') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + }) + + describe('Scenario 9: Username Allowlist - Remaining Partial Signed', () => { + it('should FAIL when some users in allowlist but remaining not all signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'admin-user', id: 301, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' }, + { name: 'user3', id: 103, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' } + // user2 and user3 not signed + ] + } + + mockedGetUsernameAllowList.mockReturnValue('admin-*') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - user2 and user3 unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('2') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 10: Domain Allowlist - Remaining Signed', () => { + it('should PASS when domain emails filtered and remaining signed', async () => { + const committers: CommittersDetails[] = [ + { name: 'bot1', id: 401, email: 'bot1@bot.example.com' }, + { name: 'bot2', id: 402, email: 'bot2@automation.io' }, + { name: 'user1', id: 101, email: 'user1@company.com' }, + { name: 'user2', id: 102, email: 'user2@company.com' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user2', id: 102, created_at: '2026-01-01' } + ] + } + + mockedGetDomainAllowList.mockReturnValue('@bot.example.com, @automation.io') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + }) + + describe('Scenario 11: Domain Allowlist - Remaining Unsigned', () => { + it('should FAIL when domain emails filtered but remaining unsigned', async () => { + const committers: CommittersDetails[] = [ + { name: 'bot1', id: 401, email: '[email protected]' }, + { name: 'user1', id: 101, email: '[email protected]' }, + { name: 'user2', id: 102, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [] + } + + mockedGetDomainAllowList.mockReturnValue('@bot.io') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - user1 and user2 unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('2') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Scenario 12: Combined Username + Domain Allowlist', () => { + it('should handle both username and domain allowlist correctly', async () => { + const committers: CommittersDetails[] = [ + { name: 'dependabot', id: 201, email: 'dependabot@bot.io' }, + { name: 'bot-service', id: 401, email: 'bot-service@bot.io' }, + { name: 'ci-bot', id: 402, email: 'ci-bot@bot.io' }, + { name: 'user1', id: 101, email: 'user1@company.com' }, + { name: 'user2', id: 102, email: 'user2@company.com' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user2', id: 102, created_at: '2026-01-01' } + ] + } + + mockedGetUsernameAllowList.mockReturnValue('dependabot*, ci-*') + mockedGetDomainAllowList.mockReturnValue('@bot.io') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed - dependabot and ci-bot filtered by username, bot-service by domain + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + + it('should FAIL when combined allowlist still leaves unsigned users', async () => { + const committers: CommittersDetails[] = [ + { name: 'dependabot', id: 201, email: 'dependabot@bot.io' }, + { name: 'bot-service', id: 401, email: 'bot-service@bot.io' }, + { name: 'user1', id: 101, email: 'user1@company.com' }, + { name: 'user2', id: 102, email: 'user2@company.com' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' } + // user2 not signed + ] + } + + mockedGetUsernameAllowList.mockReturnValue('dependabot*') + mockedGetDomainAllowList.mockReturnValue('@bot.io') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - user2 unsigned + expect(mockedSetFailed).toHaveBeenCalled() + const failMessage = mockedSetFailed.mock.calls[0][0] + expect(failMessage).toContain('1') + expect(failMessage).toContain('need to sign the CLA') + }) + }) + + describe('Edge Cases', () => { + it('should handle empty committers list', async () => { + const committers: CommittersDetails[] = [] + + const signaturesFile = { + signedContributors: [] + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed - no one to check + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + + it('should PASS when allowlist filters all contributors', async () => { + const committers: CommittersDetails[] = [ + { name: 'bot1', id: 201, email: '[email protected]' }, + { name: 'bot2', id: 202, email: '[email protected]' }, + { name: 'bot3', id: 203, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [] + } + + mockedGetUsernameAllowList.mockReturnValue('*bot*') + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed - all filtered by allowlist + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + + it('should handle contributors with duplicate entries in signatures', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1', id: 101, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1', id: 101, created_at: '2026-01-01' }, + { name: 'user1', id: 101, created_at: '2026-01-02' }, // Duplicate + ] + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should still succeed - user is signed (duplicates don't matter) + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + + it('should match signatures by GitHub ID when names differ', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1-renamed', id: 101, email: '[email protected]' } + ] + + const signaturesFile = { + signedContributors: [ + { name: 'user1-old-name', id: 101, created_at: '2026-01-01' } // Same ID, different name + ] + } + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should succeed - matched by ID + expect(mockedInfo).toHaveBeenCalledWith(expect.stringContaining('All contributors have signed')) + expect(mockedSetFailed).not.toHaveBeenCalled() + }) + + it('should handle signatures file with missing signedContributors array', async () => { + const committers: CommittersDetails[] = [ + { name: 'user1', id: 101, email: '[email protected]' } + ] + + const signaturesFile = {} // Missing signedContributors + + mockedGetCommitters.mockResolvedValue(committers) + mockedGetFileContent.mockResolvedValue({ + data: { + content: Buffer.from(JSON.stringify(signaturesFile)).toString('base64'), + sha: 'file-sha-123' + } + } as any) + + await setupClaCheck() + + // Should fail - no signatures + expect(mockedSetFailed).toHaveBeenCalled() + }) + }) +}) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts deleted file mode 100644 index 440107cf..00000000 --- a/__tests__/main.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as core from '@actions/core' -import * as github from '@actions/github' -import { context } from '@actions/github' -import { getclas } from '../src/checkcla' -import { lockPullRequest } from '../src/pullRequestLock' -import { run } from '../src/main' -import { mocked } from 'ts-jest/utils' - -jest.mock('@actions/core') -jest.mock('@actions/github') -jest.mock('../src/pullRequestLock') -jest.mock('../src/checkcla') -const mockedGetClas = mocked(getclas) -const mockedLockPullRequest = mocked(lockPullRequest) - - -describe('Pull request event', () => { - - beforeEach(async () => { - // @ts-ignore - github.context = { - eventName: 'pull_request', - ref: 'refs/pull/232/merge', - workflow: 'CLA Assistant', - action: 'ibakshaygithub-action-1', - actor: 'ibakshay', - payload: { - action: 'closed', - number: '1', - pull_request: { - number: 1, - title: 'test', - user: { - login: 'ibakshay', - }, - }, - repository: { - name: 'auto-assign', - owner: { - login: 'ibakshay', - }, - }, - }, - repo: { - owner: 'ibakshay', - repo: 'auto-assign', - }, - issue: { - owner: 'kentaro-m', - repo: 'auto-assign', - number: 1, - }, - sha: '' - } - - } - ) - - test('the lockPullRequest method should be called if there is a pull request merge/closed', async () => { - - await run() - expect(mockedLockPullRequest).toHaveBeenCalled() - - - }) - - test('the checkcla method should not called if there is a pull request merge/closed', async () => { - - await run() - expect(mockedGetClas).not.toHaveBeenCalled() - }) - - test('the lockPullRequest method should not be called if there is a pull request opened', async () => { - - github.context.payload.action = 'opened' - await run() - - expect(mockedLockPullRequest).not.toHaveBeenCalled() - - }) - - test('the checkcla method should be called if there is a pull request opened', async () => { - - github.context.payload.action = 'opened' - await run() - expect(mockedGetClas).toHaveBeenCalled() - - }) - - test('the lockPullRequest method should not be called if there is a pull request sync', async () => { - - github.context.payload.action = 'synchronize' - - await run() - - expect(mockedLockPullRequest).not.toHaveBeenCalled() - - }) - - test('the checkcla method should be called if there is a pull request sync', async () => { - github.context.payload.action = 'synchronize' - await run() - expect(mockedGetClas).toHaveBeenCalled() - - }) - - -}) \ No newline at end of file diff --git a/__tests__/pullRequestLock.test.ts b/__tests__/pullRequestLock.test.ts deleted file mode 100644 index 3dde8dc8..00000000 --- a/__tests__/pullRequestLock.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as core from '@actions/core' -import * as github from '@actions/github' -import { context } from '@actions/github' -import { getclas } from '../src/checkcla' -import { lockPullRequest } from '../src/pullRequestLock' -import { run } from '../src/main' -import { mocked } from 'ts-jest/utils' - -jest.mock('@actions/core') -jest.mock('@actions/github') - -//const mockedLockPullRequest = mocked(lockPullRequest) \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 59f985bf..036d029d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43,9 +43,7 @@ exports.checkAllowList = void 0; const _ = __importStar(__nccwpck_require__(2356)); const input = __importStar(__nccwpck_require__(7189)); const persistence_1 = __nccwpck_require__(9947); -const usernameAllowListPatterns = input.getUsernameAllowList().split(','); -const domainAllowList = input.getDomainAllowList().split(','); -function isUserNotInAllowList(committer) { +function isUserNotInAllowList(committer, usernameAllowListPatterns, domainAllowList) { for (let pattern of domainAllowList) { pattern = pattern.trim(); if (!pattern) @@ -67,6 +65,9 @@ function isUserNotInAllowList(committer) { } function checkAllowList(committers) { return __awaiter(this, void 0, void 0, function* () { + // Load allowlists at runtime (not module-load time) for testability + const usernameAllowListPatterns = input.getUsernameAllowList().split(','); + const domainAllowList = input.getDomainAllowList().split(','); const domainsFile = input.getDomainsFile(); if (domainsFile) { try { @@ -83,7 +84,7 @@ function checkAllowList(committers) { } } } - const committersAfterAllowListCheck = committers.filter(committer => committer && !(isUserNotInAllowList !== undefined && isUserNotInAllowList(committer))); + const committersAfterAllowListCheck = committers.filter(committer => committer && !(isUserNotInAllowList !== undefined && isUserNotInAllowList(committer, usernameAllowListPatterns, domainAllowList))); return committersAfterAllowListCheck; }); } @@ -589,6 +590,7 @@ function prCommentSetup(committerMap, committers) { else if (claBotComment === null || claBotComment === void 0 ? void 0 : claBotComment.id) { if (signed) { yield updateComment(signed, committerMap, claBotComment); + return; // Early return - all contributors already signed, no need to check PR comment signatures } // reacted committers are contributors who have newly signed by posting the Pull Request comment const reactedCommitters = yield (0, signatureComment_1.default)(committerMap, committers); @@ -1116,8 +1118,8 @@ function createClaFileAndPRComment(committers, committerMap) { } function prepareCommiterMap(committers, claFileContent) { let committerMap = getInitialCommittersMap(); - committerMap.notSigned = committers.filter(committer => !(claFileContent === null || claFileContent === void 0 ? void 0 : claFileContent.signedContributors.some(cla => committer.id === cla.id))); - committerMap.signed = committers.filter(committer => claFileContent === null || claFileContent === void 0 ? void 0 : claFileContent.signedContributors.some(cla => committer.id === cla.id)); + committerMap.notSigned = committers.filter(committer => !((claFileContent === null || claFileContent === void 0 ? void 0 : claFileContent.signedContributors) || []).some(cla => committer.id === cla.id)); + committerMap.signed = committers.filter(committer => ((claFileContent === null || claFileContent === void 0 ? void 0 : claFileContent.signedContributors) || []).some(cla => committer.id === cla.id)); committers.map(committer => { if (!committer.id) { committerMap.unknown.push(committer); diff --git a/docs/BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md b/docs/BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md new file mode 100644 index 00000000..3cca0cfc --- /dev/null +++ b/docs/BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md @@ -0,0 +1,281 @@ +# Bug Fix: PR Comment Double-Update Issue + +## Summary +Fixed a bug where PR comments were not updated correctly when contributors were filtered by the allowlist after the comment was initially created, resulting in stale "unsigned" messages despite successful CLA checks. + +## Bug Manifestation + +**Example**: [rdk-halif-aidl PR #321](https://github.com/rdkcentral/rdk-halif-aidl/pull/321) +- **Comment state**: Shows "❌ @Copilot" as unsigned +- **Actual workflow status**: ✅ SUCCESS (all contributors passed) +- **Reason**: Copilot added to allowlist after comment creation, but comment never updated + +**Expected vs Actual**: +``` +Expected: "All contributors have signed the CLA ✍️ ✅" +Actual: "**1** out of **2** committers have signed the CLA. + ✅ kanjoe24 + ❌ @Copilot" +``` + +## Root Cause Analysis + +### The Problem + +In `src/pullrequest/pullRequestComment.ts`, the `prCommentSetup` function had a **double-update pattern**: + +```typescript +// BEFORE (buggy code) +if (claBotComment?.id) { + if (signed) { + await updateComment(signed, committerMap, claBotComment) // Update #1 ✅ + } + + // Always executes, even when signed = true + const reactedCommitters = await signatureWithPRComment(committerMap, committers) + if (reactedCommitters?.onlyCommitters) { + reactedCommitters.allSignedFlag = prepareAllSignedCommitters(...) + } + committerMap = prepareCommiterMap(committerMap, reactedCommitters) + await updateComment(reactedCommitters.allSignedFlag, committerMap, claBotComment) // Update #2 ❌ + return reactedCommitters +} +``` + +### Execution Flow (Bug Scenario) + +1. **Initial PR creation**: 2 contributors (kanjoe24 signed, Copilot unsigned) +2. **Comment created**: Shows "1 out of 2 signed, ❌ @Copilot" +3. **Allowlist updated**: Copilot added to allowlist +4. **Workflow rerun**: + - Allowlist filters Copilot → `committerMap.notSigned = []` + - `signed = true` (all remaining contributors signed) + - **Update #1** (line 22): Updates comment to "All signed" ✅ + - **signatureWithPRComment()**: Checks for new PR comment signatures → finds none + - `reactedCommitters.allSignedFlag = false` (no new signatures found) + - **Update #2** (line 31): Overwrites comment with "unsigned" state ❌ +5. **Result**: Comment shows stale unsigned state despite workflow success + +### Why This Happens + +The **fundamental design issue**: Two different update paths with conflicting purposes: + +1. **Update Path 1** (line 22): "All contributors already signed in database" + - Triggered when: `signed = true` (no unsigned contributors) + - Intent: Update existing comment to show success + - Result: ✅ Correctly shows "All signed" + +2. **Update Path 2** (line 31): "Check for new PR comment signatures" + - Triggered when: Comment exists (unconditionally after first update) + - Intent: Handle contributors who just signed via PR comment + - Result: ❌ Overwrites with `allSignedFlag = false` because no NEW signatures via PR comment + +**The conflict**: Path 2 doesn't account for Path 1 having already handled the "all signed" case. + +## Classification + +### Is this an edge case not considered? +**YES** - The scenario of allowlist filtering contributors *after* comment creation is uncommon and wasn't anticipated in the original design. + +### Is this bad design? +**PARTIALLY** - The unconditional second update assumes PR comment signature checking is always relevant, which is incorrect. When all contributors are already signed, checking for PR comment signatures is unnecessary and harmful. + +### Is this unintended behavior? +**YES** - The second update was meant to handle *new* signatures, not to overwrite *existing* all-signed states. + +### Is this unanticipated interaction? +**YES** - The interaction between: +- Allowlist filtering (removes contributors dynamically) +- Initial signature state (`signed = true`) +- PR comment signature detection (`allSignedFlag = false` when no new signatures) + +...was not considered during implementation. + +## The Fix + +**Solution**: Add early return after first update when all contributors are already signed. + +```typescript +// AFTER (fixed code) +if (claBotComment?.id) { + if (signed) { + await updateComment(signed, committerMap, claBotComment) + return // Early return - all contributors already signed, no need to check PR comment signatures + } + + // Only reaches here if there are unsigned contributors + const reactedCommitters = await signatureWithPRComment(committerMap, committers) + if (reactedCommitters?.onlyCommitters) { + reactedCommitters.allSignedFlag = prepareAllSignedCommitters(...) + } + committerMap = prepareCommiterMap(committerMap, reactedCommitters) + await updateComment(reactedCommitters.allSignedFlag, committerMap, claBotComment) + return reactedCommitters +} +``` + +### Rationale + +**Why early return is the right fix**: +1. When `signed = true`, all contributors are already signed (either in database or via allowlist) +2. PR comment signature checking is **only relevant** when there are unsigned contributors who MIGHT sign via comment +3. No need to re-check or re-update when the initial state is already "all signed" +4. Preserves original intent: First path handles "all signed", second path handles "new signatures via PR comment" + +### Impact + +✅ **Fixes**: Allowlist-filtered contributors correctly show as "All signed" +✅ **Preserves**: PR comment signature functionality for genuinely unsigned contributors +✅ **Simplifies**: Removes unnecessary API call and logic when all are signed +✅ **No regression**: Existing functionality unchanged for unsigned scenarios + +## Other Edge Cases Identified + +### 1. ✅ Contributor signs via PR comment AFTER being allowlisted +**Scenario**: User added to allowlist, but they still post "I have read the CLA..." comment +**Current behavior**: Allowlist filtering happens first, signature comment ignored +**Expected behavior**: Same (allowlist takes precedence, comment is harmless) +**Status**: No fix needed + +### 2. ⚠️ Domain allowlist changes mid-PR lifecycle +**Scenario**: `domains.json` updated between workflow runs +**Current behavior**: Rerun picks up new domains, recalculates signature state +**Potential issue**: Comment might show temporary inconsistency if new domain adds unsigned users +**Recommendation**: Acceptable behavior (eventual consistency) + +### 3. ⚠️ Race condition - multiple reruns simultaneously +**Scenario**: Two developers trigger rerun at same time +**Current behavior**: Both workflows update same comment (last write wins) +**Potential issue**: Comment might show transient or conflicting state +**Mitigation**: GitHub API handles concurrent updates, eventual consistency acceptable + +### 4. ✅ Manual comment deletion +**Scenario**: User deletes CLA bot comment, workflow reruns +**Current behavior**: Creates new comment if unsigned, does nothing if all signed +**Expected behavior**: Same (correct) +**Status**: Working as designed + +### 5. ⚠️ Signature removed from database between runs +**Scenario**: Signature revoked in `signatures.json` after comment shows "signed" +**Current behavior**: **Bug - comment NOT updated to show unsigned** +**Reason**: Early return when `signed = true` prevents rechecking +**Fix needed**: NO - signature removal is extremely rare and requires manual intervention +**Recommendation**: Document that signature removal requires manual comment cleanup or PR close/reopen + +### 6. ✅ Contributor has multiple commits with different emails +**Scenario**: Same user commits with multiple email addresses +**Current behavior**: Checks each email separately against signatures and domain allowlist +**Expected behavior**: Same (correct - CLA must cover all identities) +**Status**: Working as designed + +### 7. ⚠️ Comment body includes custom formatting or emojis that break parsing +**Scenario**: External tool or manual edit corrupts comment format +**Current behavior**: getComment() might not recognize it as CLA comment +**Potential issue**: Creates duplicate comment +**Mitigation**: Comment detection uses unique marker phrase +**Recommendation**: Document expected comment format + +## Recommendations for Preventing Similar Issues + +### 1. **Separation of Concerns** +Separate "check initial state" from "handle PR comment signatures" into distinct functions: +```typescript +if (claBotComment?.id) { + if (signed) { + return updateCommentForAllSigned(committerMap, claBotComment) + } else { + return checkAndUpdateForPRCommentSignatures(committerMap, committers, claBotComment) + } +} +``` + +### 2. **State Machine Approach** +Model comment updates as state transitions: +- `NO_COMMENT → UNSIGNED_COMMENT` (create) +- `UNSIGNED_COMMENT → UNSIGNED_COMMENT` (update counts) +- `UNSIGNED_COMMENT → SIGNED_COMMENT` (transition to success) +- `SIGNED_COMMENT → SIGNED_COMMENT` (idempotent, no change) + +### 3. **Idempotency Checks** +Before updating comment, check if content would actually change: +```typescript +const newContent = commentContent(signed, committerMap) +if (claBotComment.body === newContent) { + return // No update needed +} +await updateComment(...) +``` + +### 4. **Comprehensive Test Coverage** +Test scenarios: +- ✅ All signed from start +- ✅ All unsigned from start +- ✅ Mixed signed/unsigned +- ✅ **Allowlist filters after comment creation** (this bug) +- ✅ PR comment signature flow +- ⚠️ Domain changes mid-PR +- ⚠️ Concurrent updates +- ⚠️ Comment format corruption + +### 5. **Explicit Return Values** +Functions should return clear indicators: +```typescript +interface CommentUpdateResult { + action: 'created' | 'updated' | 'skipped' + reason: string + commentId?: number +} +``` + +## Testing Notes + +**Challenge**: Existing test infrastructure has issues (ts-jest migration needed, missing environment variables). + +**Manual Testing Recommended**: +1. Create test PR with unsigned contributor +2. Add contributor to allowlist +3. Rerun workflow +4. Verify comment updates to "All signed" + +**Automated Test Scenario** (when infrastructure fixed): +```typescript +test('should update comment only once when allowlist filters contributors', async () => { + // Setup: Comment shows "1 of 2 signed, ❌ Copilot" + // Action: Allowlist filters Copilot, rerun workflow + // Expected: Comment updates to "All contributors have signed" + // Expected: Only ONE updateComment call, not TWO +}) +``` + +## Deployment Plan + +1. ✅ **Branch created**: `fix/pr-comment-double-update` +2. ✅ **Fix implemented**: Early return added to `pullRequestComment.ts` +3. ✅ **Code compiled**: No TypeScript errors +4. ⏳ **Manual testing**: Deploy to test PR and verify +5. ⏳ **Create PR**: With this documentation and examples +6. ⏳ **Review**: Get approval from domain experts +7. ⏳ **Merge**: To main branch +8. ⏳ **Tag**: New version (v2.7.1 or v2.8.0) +9. ⏳ **Update cmf-actions**: Point workflow to new version + +## Related Issues + +- **Discovered during**: Investigation of [rdk-halif-aidl PR #321](https://github.com/rdkcentral/rdk-halif-aidl/pull/321) +- **Related to**: v2.7.0 enhanced feedback implementation +- **Affects**: All PRs where contributors are filtered by allowlist after comment creation +- **Severity**: Medium (cosmetic issue, doesn't affect actual CLA verification) +- **User impact**: Confusing PR comments showing incorrect unsigned state + +## Files Changed + +- `src/pullrequest/pullRequestComment.ts` - Added early return to prevent double-update +- `docs/BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md` - This documentation +- `docs/EXECUTION_PATHS.md` - To be updated with new scenario +- `docs/DEBUGGING_HISTORY.md` - To be updated with discovery process + +## Version + +- **Bug discovered**: v2.7.0 +- **Fix implemented**: 2026-02-25 +- **Fix version**: To be tagged post-merge diff --git a/docs/DEBUGGING_HISTORY.md b/docs/DEBUGGING_HISTORY.md index 948744aa..8a85c55e 100644 --- a/docs/DEBUGGING_HISTORY.md +++ b/docs/DEBUGGING_HISTORY.md @@ -990,13 +990,167 @@ await octokit.reactions.createForIssueComment({ - [22370785469](https://github.com/rdkcentral/cmf-release-app/actions/runs/22370785469) - Test run (Markdown issue) - [22371355507](https://github.com/rdkcentral/cmf-release-app/actions/runs/22371355507) - Test run (HTML fixed) +--- + +## Bug Fix Session 2: PR Comment Double-Update Issue + +**Date:** February 25, 2026 +**Scope:** Post-v2.7.0 deployment bug discovery and fix +**Branch:** `fix/pr-comment-double-update` + +### Bug 4: PR Comment Not Updating When Allowlist Filters Contributors + +**Issue:** PR comments show stale "unsigned" state after workflow rerun, despite check succeeding + +**Discovery:** +- Investigating [rdk-halif-aidl PR #321](https://github.com/rdkcentral/rdk-halif-aidl/pull/321) +- Workflow rerun: ✅ SUCCESS (all contributors passed) +- PR comment: ❌ Still shows "1 out of 2 signed, @Copilot unsigned" +- User noticed: *"why did our rerun 2.7.0 not update the comment"* + +**Symptoms:** +- PR created with unsigned contributor (Copilot) +- Comment created: "1 of 2 signed, ❌ @Copilot" +- Copilot added to allowlist +- Workflow rerun successful +- **Comment NOT updated to "All signed"** + +**Root Cause Analysis:** +**File:** `src/pullrequest/pullRequestComment.ts` +**Lines:** 20-31 + +**Original Code:** +```typescript +if (claBotComment?.id) { + if (signed) { + await updateComment(signed, committerMap, claBotComment) // Update #1 ✅ + } + + // Always executes, even when signed = true + const reactedCommitters = await signatureWithPRComment(committerMap, committers) + if (reactedCommitters?.onlyCommitters) { + reactedCommitters.allSignedFlag = prepareAllSignedCommitters(...) + } + committerMap = prepareCommiterMap(committerMap, reactedCommitters) + await updateComment(reactedCommitters.allSignedFlag, committerMap, claBotComment) // Update #2 ❌ + return reactedCommitters +} +``` + +**Problem - Double Update Pattern:** +1. **First update** (line 22): When `signed = true`, updates comment to "All contributors signed" ✅ +2. **Second update** (line 31): Always executes, checks for PR comment signatures + - Since no new PR comment signatures found: `allSignedFlag = false` + - Updates comment to show unsigned state ❌ +3. **Result**: Second update overwrites first update's correct "All signed" message + +**Timeline:** +1. Initial PR: kanjoe24 signed, Copilot unsigned +2. Comment created: "1 out of 2 signed, ❌ @Copilot" +3. Allowlist updated: Add "copilot, Copilot, copilot-swe-agent[bot]" +4. Workflow rerun triggered +5. checkAllowList() filters Copilot → `committerMap.notSigned = []` +6. `signed = true` (all remaining contributors signed) +7. **Update #1**: Comment → "All signed" ✅ +8. signatureWithPRComment() checks for new signatures → none found +9. `reactedCommitters.allSignedFlag = false` +10. **Update #2**: Comment → "1 of 2 signed" ❌ +11. Workflow status: ✅ SUCCESS (correct) +12. Comment state: Shows unsigned (WRONG!) + +**Investigation Steps:** +```bash +# Check PR comment content +gh api repos/rdkcentral/rdk-halif-aidl/issues/comments/3754397077 + +# Verify workflow used v2.7.0 with updated allowlist +gh run view 22189286524 --repo rdkcentral/rdk-halif-aidl --log | grep -E "(Uses|allowlist:)" + +# Analyze pullRequestComment.ts logic +# Found double-update pattern at lines 21-31 +``` + +**Classification:** +- ✅ **Edge case not considered**: Allowlist filtering post-comment-creation is uncommon +- ⚠️ **Partial design issue**: Unconditional second update assumes PR signature check always relevant +- ✅ **Unintended behavior**: Second update wasn't meant to overwrite "all signed" state +- ✅ **Unanticipated interaction**: Conflict between initial state check and PR comment signature check + +**Fix:** +**Branch:** `fix/pr-comment-double-update` +**Date:** February 25, 2026 + +```typescript +if (claBotComment?.id) { + if (signed) { + await updateComment(signed, committerMap, claBotComment) + return // ✅ Early return - all contributors already signed, no need to check PR comment signatures + } + + // Only reaches here if there are unsigned contributors + const reactedCommitters = await signatureWithPRComment(committerMap, committers) + if (reactedCommitters?.onlyCommitters) { + reactedCommitters.allSignedFlag = prepareAllSignedCommitters(...) + } + committerMap = prepareCommiterMap(committerMap, reactedCommitters) + await updateComment(reactedCommitters.allSignedFlag, committerMap, claBotComment) + return reactedCommitters +} +``` + +**Rationale:** +- When `signed = true`: All contributors already signed (database or allowlist) +- PR comment signature checking only relevant for unsigned contributors +- Early return prevents unnecessary re-checking and conflicting updates +- Preserves original intent: First path for "all signed", second path for "new PR signatures" + +**Impact:** +- ✅ Allowlist-filtered contributors correctly show "All signed" +- ✅ PR comment signature functionality preserved for unsigned scenarios +- ✅ Eliminates unnecessary API calls when all signed +- ✅ No regression to existing flows + +**Edge Cases Identified:** +1. ✅ Contributor signs via PR comment AFTER being allowlisted (harmless, allowlist takes precedence) +2. ⚠️ Domain allowlist changes mid-PR (eventual consistency acceptable) +3. ⚠️ Race condition - multiple reruns (last write wins, acceptable) +4. ✅ Manual comment deletion (handled correctly) +5. ⚠️ Signature removed from database (requires manual intervention - documented) +6. ✅ Multiple emails per contributor (handled correctly) +7. ⚠️ Comment format corruption (detected by unique marker) + +**Recommendations:** +1. **Separation of concerns**: Split "check initial state" from "handle PR comment signatures" +2. **State machine approach**: Model comment updates as state transitions +3. **Idempotency checks**: Verify content changes before updating +4. **Comprehensive testing**: Add allowlist-filtering scenarios to test suite +5. **Explicit return values**: Return clear action indicators (created/updated/skipped) + +**Documentation:** +- Created: [BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md](./BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md) - Full analysis +- Updated: [EXECUTION_PATHS.md](./EXECUTION_PATHS.md) - Added Path 13 for allowlist filtering +- Updated: [DEBUGGING_HISTORY.md](./DEBUGGING_HISTORY.md) - This entry + +**Testing:** +- Test infrastructure challenges: ts-jest migration needed, environment variables missing +- **Manual testing recommended** before merge: + 1. Create test PR with unsigned contributor + 2. Add to allowlist + 3. Rerun workflow + 4. Verify comment updates to "All signed" + +**Status:** Fix implemented, ready for testing and PR creation + +--- + ### Documentation - [ARCHITECTURE.md](./ARCHITECTURE.md) - [EXECUTION_PATHS.md](./EXECUTION_PATHS.md) - [ENHANCED_FEEDBACK.md](./ENHANCED_FEEDBACK.md) +- [BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md](./BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md) - [TESTING.md](../TESTING.md) --- **End of Debugging History** -*Last Updated: February 24, 2026* +*Last Updated: February 25, 2026* diff --git a/docs/EXECUTION_PATHS.md b/docs/EXECUTION_PATHS.md index fc2f4d1d..6c3ac16b 100644 --- a/docs/EXECUTION_PATHS.md +++ b/docs/EXECUTION_PATHS.md @@ -461,6 +461,42 @@ See: [rdkcentral/cmf-release-app#27](https://github.com/rdkcentral/cmf-release-a 7. 💬 PR comment updated with progress 8. ✅ Final signature triggers rerun → Success +### Path 13: Allowlist Filtering After Comment Creation (Bug Fix in v2.7.1+) + +**Scenario**: Contributors filtered by allowlist *after* PR comment created + +```mermaid +graph TD + A[PR Created] --> B[2 Contributors: kanjoe24 signed, Copilot unsigned] + B --> C[Comment Created: 1 of 2 signed, ❌ Copilot] + C --> D[Allowlist Updated: Add Copilot] + D --> E[Workflow Rerun Triggered] + E --> F[checkAllowList filters Copilot] + F --> G{committerMap.notSigned.length = 0?} + G -->|Yes| H[signed = true] + H --> I[Update Comment: All signed ✅] + I --> J[Early Return - NO second update] + J --> K[Check Status: SUCCESS] +``` + +**Before Fix (v2.7.0 bug)**: +1. First update: Comment → "All signed" ✅ +2. Check PR comment signatures: None found +3. `reactedCommitters.allSignedFlag = false` +4. Second update: Comment → "1 of 2 signed, ❌ Copilot" ❌ (WRONG!) +5. **Result**: Comment shows stale unsigned state despite workflow success + +**After Fix (v2.7.1+)**: +1. First update: Comment → "All signed" ✅ +2. **Early return** - Skip PR comment signature check +3. **Result**: Comment correctly shows all signed ✓ + +**Example**: [rdk-halif-aidl PR #321](https://github.com/rdkcentral/rdk-halif-aidl/pull/321) exposed this bug + +**Fix**: Added early return in `prCommentSetup()` when `signed = true` + +See: [BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md](./BUGFIX_PR_COMMENT_DOUBLE_UPDATE.md) for full analysis + ### Validation Checklist - [ ] Job summary renders HTML correctly (bold, links) - [ ] Warning annotations appear on Files Changed tab @@ -470,6 +506,7 @@ See: [rdkcentral/cmf-release-app#27](https://github.com/rdkcentral/cmf-release-a - [ ] Workflow rerun triggered on final signature - [ ] Check status updates from failure to success - [ ] No duplicate status checks ("Signature / Check" removed) +- [ ] **PR comment updates correctly when allowlist filters contributors** (v2.7.1+) ## Next Steps - See [ENHANCED_FEEDBACK.md](./ENHANCED_FEEDBACK.md) for v2.7.0 improvements diff --git a/docs/RULESET_CONFIGURATION.md b/docs/RULESET_CONFIGURATION.md new file mode 100644 index 00000000..4296c5f4 --- /dev/null +++ b/docs/RULESET_CONFIGURATION.md @@ -0,0 +1,407 @@ +# Repository Ruleset Configuration for CLA Enforcement + +## Overview + +This document describes the proven working repository ruleset configuration for enforcing CLA checks as a merge blocker. This configuration was tested and validated on `rdkcentral/cmf-release-app` (ruleset ID: 13201232). + +## Verified Configuration + +### Ruleset Details + +- **Ruleset ID**: 13201232 (cmf-release-app test instance) +- **Name**: "Test: CLA Check Enforcement" +- **Enforcement**: `active` +- **Target**: Branch-based protection +- **Created**: 2026-02-24 +- **Last Updated**: 2026-02-24 + +### Required Status Check Configuration + +**CRITICAL**: This is the exact configuration that successfully blocks merges: + +```json +{ + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "required_status_checks": [ + { + "context": "CLA-Lite / Check", + "integration_id": 15368 + } + ] + } +} +``` + +### Key Parameters Explained + +| Parameter | Value | Purpose | +|-----------|-------|---------| +| `context` | `"CLA-Lite / Check"` | **EXACT** status check name created by GitHub Actions workflow | +| `integration_id` | `15368` | GitHub Actions integration ID (ensures check comes from actual workflow, not spoofed status) | +| `strict_required_status_checks_policy` | `true` | Requires branch to be up-to-date before merging | + +### Branch Targeting + +The ruleset applies to these branch patterns: + +```json +{ + "ref_name": { + "include": [ + "refs/heads/test-*", + "refs/heads/test-commits", + "refs/heads/feature/test-commits", + "~DEFAULT_BRANCH" + ], + "exclude": [] + } +} +``` + +**Note**: `~DEFAULT_BRANCH` is a special token that resolves to the repository's default branch (e.g., `main`, `master`, or `71-gitflow-actions`). + +## Verification Results + +### Successful Blocking Test + +**Test PR**: [rdkcentral/cmf-release-app#27](https://github.com/rdkcentral/cmf-release-app/pull/27) + +**Scenario**: PR with 3 unsigned contributors: +- `unsigned@example.com` (Unsigned User) +- `bob.dev@example.org` (Bob Developer) +- `charlie@test.org` + 2 co-authors (Alice Engineer, David Designer) + +**Observed Behavior**: +1. CLA-Lite workflow runs and creates check run "CLA-Lite / Check" +2. Check run fails (unsigned contributors detected) +3. GitHub blocks merge with error message: + ``` + Repository rule violations found for refs/heads/feature/test-commits. + + Required status check 'CLA-Lite / Check' is expected. + ``` +4. "Merge pull request" button disabled +5. "Update branch" button works (rebase/merge from base branch) + +### Why This Configuration Works + +1. **Integration ID enforcement**: Using `integration_id: 15368` ensures the check **must** come from GitHub Actions workflow, not a spoofed commit status created via API +2. **Exact context matching**: `"CLA-Lite / Check"` matches the check run name created by the workflow job +3. **Active enforcement**: No bypass actors defined, all users subject to rule +4. **Strict policy**: Prevents outdated branches from merging + +## Deployment Guide + +### Step 1: Prepare Ruleset JSON + +Create a file `cla-enforcement-ruleset.json`: + +```json +{ + "name": "CLA Check Enforcement", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "required_status_checks": [ + { + "context": "CLA-Lite / Check", + "integration_id": 15368 + } + ] + } + } + ], + "bypass_actors": [] +} +``` + +### Step 2: Apply to Repository + +Using GitHub CLI: + +```bash +# Apply to a repository +gh api repos/OWNER/REPO/rulesets \ + --method POST \ + --input cla-enforcement-ruleset.json + +# Example for rdkcentral organization +gh api repos/rdkcentral/REPO_NAME/rulesets \ + --method POST \ + --input cla-enforcement-ruleset.json +``` + +### Step 3: Verify Ruleset + +```bash +# List all rulesets +gh api repos/OWNER/REPO/rulesets + +# View specific ruleset (get ID from list command) +gh api repos/OWNER/REPO/rulesets/RULESET_ID +``` + +### Step 4: Test Enforcement + +1. Create test branch with unsigned commits +2. Open PR to protected branch +3. Verify CLA check fails +4. Verify merge is blocked with error message +5. Sign CLA (or add to allowlist) +6. Trigger recheck via comment: `@cla-bot-lite recheck` +7. Verify check passes +8. Verify merge is now allowed + +## Production Rollout Plan + +### Phase 1: Low-Risk Repositories (Week 1) +- Apply to 2-3 low-activity repositories +- Monitor for false positives +- Gather feedback from contributors + +### Phase 2: Medium-Risk Repositories (Week 2-3) +- Apply to active development repositories +- Ensure contributor education is in place +- Have rapid response plan for issues + +### Phase 3: High-Risk Repositories (Week 4+) +- Apply to critical repositories with high PR volume +- Consider temporary bypass actors for emergency situations +- Full monitoring and alerting + +### Rollback Plan + +If issues occur: + +```bash +# Disable ruleset (keep configuration) +gh api repos/OWNER/REPO/rulesets/RULESET_ID \ + --method PATCH \ + -f enforcement=disabled + +# Or delete entirely +gh api repos/OWNER/REPO/rulesets/RULESET_ID \ + --method DELETE +``` + +## Integration ID Reference + +| Integration | ID | Purpose | +|-------------|----|---------| +| GitHub Actions | `15368` | Workflow-based checks (used for CLA-Lite) | +| GitHub App | varies | Custom GitHub App checks (if using app-based CLA) | +| Any source | omit `integration_id` | Accept check from any source (less secure) | + +**Security Note**: Always specify `integration_id: 15368` for CLA-Lite to prevent status spoofing attacks where malicious users create fake passing statuses. + +## Status Check Name Reference + +The action creates a **check run** (not a commit status) with these characteristics: + +- **Check Suite**: Created automatically by GitHub Actions +- **Check Run Name**: Defined by workflow job name +- **Format**: `{workflow_name} / {job_name}` +- **Example**: `"CLA-Lite / Check"` + - Workflow name: `"CLA-Lite"` (from workflow file `name:` field) + - Job name: `"Check"` (from job ID in workflow) + +**IMPORTANT**: The ruleset `context` field must match this EXACT format including spacing around `/`. + +### Common Mistakes + +❌ **Wrong**: `"CLA-Lite/Check"` (no spaces) +❌ **Wrong**: `"CLA-Lite/check"` (lowercase job name) +❌ **Wrong**: `"Signature / Check"` (old status context name) +✅ **Correct**: `"CLA-Lite / Check"` (exact match with spaces) + +## Workflow Configuration Requirements + +For rulesets to work, ensure your CLA workflow: + +1. **Has a defined name**: + ```yaml + name: CLA-Lite + ``` + +2. **Has a named job**: + ```yaml + jobs: + Check: # This becomes the job name + name: Check # Optional display name + ``` + +3. **Runs on correct triggers**: + ```yaml + on: + pull_request_target: + types: [opened, synchronize, reopened] + issue_comment: + types: [created] + ``` + +4. **Uses GitHub Actions token** (not PAT): + ```yaml + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ``` + +## Migration from Legacy Branch Protection + +If you're currently using legacy branch protection rules with commit statuses: + +### Old Configuration (Commit Status) +- Status name: `"Signature / Check"` +- Created via: `updateStatus()` function +- Type: Commit status +- **Problem**: Deprecated in action v2.7.0, creates duplicate checks + +### New Configuration (Check Run) +- Check name: `"CLA-Lite / Check"` +- Created via: GitHub Actions workflow check run (automatic) +- Type: Check run +- **Benefit**: Native GitHub Actions integration, better UI, single source of truth + +### Migration Steps + +1. Update action to v2.7.0+ +2. Remove `status-context` input from workflow (deprecated) +3. Create repository ruleset (see above) +4. Test with unsigned contributor PR +5. Delete legacy branch protection rule +6. Monitor for 1 week +7. Apply to remaining repositories + +## Monitoring and Alerts + +### Key Metrics + +Track these metrics to ensure rulesets are working correctly: + +- **Merge block rate**: PRs blocked by CLA check +- **False positive rate**: Valid contributors blocked +- **Recheck success rate**: Blocked PRs that pass after recheck +- **Time to resolution**: How long contributors wait after signing CLA + +### Alert Conditions + +Set up alerts for: +- Ruleset disabled or deleted unexpectedly +- High false positive rate (>5%) +- Significant increase in blocked PRs +- Integration ID mismatch errors + +## Troubleshooting + +### Issue: Merge not blocked despite failing check + +**Cause**: Integration ID mismatch or wrong context name + +**Solution**: +```bash +# Check exact check run name from a PR +gh pr checks PR_NUMBER --json name,conclusion + +# Verify ruleset configuration +gh api repos/OWNER/REPO/rulesets/RULESET_ID | jq '.rules[0].parameters' +``` + +### Issue: All PRs blocked even with passing CLA + +**Cause**: Check run name doesn't match ruleset context + +**Solution**: Update ruleset context to match EXACT workflow job name format + +### Issue: Bypass actors not working + +**Cause**: Ruleset enforcement conflicts with bypass configuration + +**Solution**: Verify bypass actor IDs and types: +```json +{ + "bypass_actors": [ + { + "actor_id": 1, + "actor_type": "Integration", + "bypass_mode": "always" + } + ] +} +``` + +## References + +- **Test Ruleset**: [rdkcentral/cmf-release-app ruleset 13201232](https://github.com/rdkcentral/cmf-release-app/settings/rules/13201232) +- **Test PR**: [rdkcentral/cmf-release-app#27](https://github.com/rdkcentral/cmf-release-app/pull/27) +- **GitHub Docs**: [About rulesets](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets) +- **API Reference**: [Repository rulesets API](https://docs.github.com/en/rest/repos/rules) + +## Appendix: Full Working Ruleset JSON + +This is the complete, tested configuration from cmf-release-app: + +```json +{ + "id": 13201232, + "name": "Test: CLA Check Enforcement", + "target": "branch", + "source_type": "Repository", + "source": "rdkcentral/cmf-release-app", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/test-*", + "refs/heads/test-commits", + "refs/heads/feature/test-commits", + "~DEFAULT_BRANCH" + ], + "exclude": [] + } + }, + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "required_status_checks": [ + { + "context": "CLA-Lite / Check", + "integration_id": 15368 + } + ] + } + } + ], + "bypass_actors": [], + "node_id": "RRS_kwDOMzDxQc4Azv-Q", + "_links": { + "self": { + "href": "https://api.github.com/repos/rdkcentral/cmf-release-app/rulesets/13201232" + }, + "html": { + "href": "https://github.com/rdkcentral/cmf-release-app/settings/rules/13201232" + } + }, + "created_at": "2026-02-24T22:51:20Z", + "updated_at": "2026-02-24T23:04:38Z" +} +``` + +--- + +**Document Version**: 1.0 +**Last Updated**: 2026-02-24 +**Validated Against**: contributor-assistant_github-action v2.7.0 +**Test Environment**: rdkcentral/cmf-release-app PR #27 diff --git a/jest.config.js b/jest.config.js index 563d4ccb..d77e5f9f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,8 @@ module.exports = { testMatch: ['**/*.test.ts'], testRunner: 'jest-circus/runner', transform: { - '^.+\\.ts$': 'ts-jest' + '^.+\.ts$': 'ts-jest' }, - verbose: true + verbose: true, + setupFiles: ['./jest.setup.js'] } \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 00000000..982cfbaa --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,19 @@ +// Jest setup file - provides environment variables for testing +// IMPORTANT: Forces dummy tokens to prevent tests from accidentally using real credentials +// This ensures tests never make real API calls even if GITHUB_TOKEN is set in the environment + +// Save original tokens if they exist (in case we need them for debugging) +const originalGithubToken = process.env.GITHUB_TOKEN +const originalPAT = process.env.PERSONAL_ACCESS_TOKEN + +// Force dummy tokens for all tests +process.env.GITHUB_TOKEN = 'ghs_dummyGitHubTokenForTestingOnly1234567890' +process.env.PERSONAL_ACCESS_TOKEN = 'ghp_dummyPersonalAccessTokenForTests1234567890' + +// Log warning if we're overriding real tokens (helps catch misconfigurations) +if (originalGithubToken && originalGithubToken !== process.env.GITHUB_TOKEN) { + console.warn('⚠️ Jest: Overriding real GITHUB_TOKEN with dummy token for tests') +} +if (originalPAT && originalPAT !== process.env.PERSONAL_ACCESS_TOKEN) { + console.warn('⚠️ Jest: Overriding real PERSONAL_ACCESS_TOKEN with dummy token for tests') +} diff --git a/package-lock.json b/package-lock.json index 5e286d2d..d0d0b0ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ }, "devDependencies": { "@octokit/types": "8.1.1", + "@types/jest": "^29.5.14", "@types/node": "^18.11.18", "@vercel/ncc": "^0.38.4", + "jest": "^29.7.0", "ts-jest": "^29.0.5", "typescript": "^4.9.5" } @@ -66,7 +68,6 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -92,7 +93,6 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -102,7 +102,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz", "integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -132,15 +131,13 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -150,7 +147,6 @@ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.22.15", "@jridgewell/gen-mapping": "^0.3.2", @@ -166,7 +162,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, - "peer": true, "dependencies": { "@babel/compat-data": "^7.22.9", "@babel/helper-validator-option": "^7.22.15", @@ -183,7 +178,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -193,7 +187,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -202,15 +195,13 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -220,7 +211,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.22.5", "@babel/types": "^7.22.5" @@ -234,7 +224,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -247,7 +236,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -260,7 +248,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz", "integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-module-imports": "^7.22.15", @@ -280,7 +267,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -290,7 +276,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -303,7 +288,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -316,7 +300,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -334,7 +317,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -344,7 +326,6 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.22.15", @@ -372,7 +353,6 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", "dev": true, - "peer": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -385,7 +365,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -398,7 +377,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -411,7 +389,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -424,7 +401,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -437,7 +413,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -450,7 +425,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -466,7 +440,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -479,7 +452,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -492,7 +464,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -505,7 +476,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -518,7 +488,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -531,7 +500,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -544,7 +512,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -560,7 +527,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -576,7 +542,6 @@ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.22.13", "@babel/parser": "^7.22.15", @@ -591,7 +556,6 @@ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz", "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.22.13", "@babel/generator": "^7.22.15", @@ -613,7 +577,6 @@ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz", "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.15", @@ -627,15 +590,13 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -652,7 +613,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -666,7 +626,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -679,7 +638,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -695,7 +653,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -708,7 +665,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -718,7 +674,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -728,7 +683,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -738,7 +692,6 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -748,7 +701,6 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -766,7 +718,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -782,7 +733,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -799,7 +749,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -811,15 +760,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jest/console/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -829,7 +776,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -842,7 +788,6 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -890,7 +835,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -906,7 +850,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -929,7 +872,6 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], - "peer": true, "engines": { "node": ">=8" } @@ -939,7 +881,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -951,15 +892,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jest/core/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -969,7 +908,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -982,7 +920,6 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -998,7 +935,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -1012,7 +948,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -1025,7 +960,6 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -1043,7 +977,6 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -1059,7 +992,6 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -1103,7 +1035,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1119,7 +1050,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1136,7 +1066,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1148,15 +1077,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -1166,7 +1093,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1191,7 +1117,6 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -1206,7 +1131,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -1222,7 +1146,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -1238,7 +1161,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -1265,7 +1187,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1281,7 +1202,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1298,7 +1218,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1310,15 +1229,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jest/transform/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -1328,7 +1245,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1428,7 +1344,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1443,7 +1358,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1453,7 +1367,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1462,15 +1375,13 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1816,7 +1727,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -1826,7 +1736,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -1836,7 +1745,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -1850,7 +1758,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -1860,7 +1767,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, - "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -1871,7 +1777,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, - "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -1894,7 +1799,6 @@ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*" } @@ -1923,6 +1827,17 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -1950,8 +1865,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/yargs": { "version": "17.0.19", @@ -2013,7 +1927,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2029,7 +1942,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -2050,7 +1962,6 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2077,7 +1988,6 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, - "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -2099,7 +2009,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2115,7 +2024,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2132,7 +2040,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2144,15 +2051,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/babel-jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -2162,7 +2067,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2175,7 +2079,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -2192,7 +2095,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -2209,7 +2111,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -2219,7 +2120,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -2235,7 +2135,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, - "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -2259,7 +2158,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -2295,7 +2193,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, - "peer": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -2322,7 +2219,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001517", "electron-to-chromium": "^1.4.477", @@ -2353,7 +2249,6 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -2367,8 +2262,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/callsites": { "version": "3.1.0", @@ -2383,7 +2277,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -2406,8 +2299,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "peer": true + ] }, "node_modules/chalk": { "version": "2.4.2", @@ -2427,7 +2319,6 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -2441,15 +2332,13 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -2464,7 +2353,6 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -2474,8 +2362,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/color-convert": { "version": "1.9.3", @@ -2504,8 +2391,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cosmiconfig": { "version": "7.1.0", @@ -2544,7 +2430,6 @@ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -2566,7 +2451,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2582,7 +2466,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2599,7 +2482,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2611,15 +2493,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/create-jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -2629,7 +2509,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2665,7 +2544,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "peer": true, "dependencies": { "ms": "2.1.2" }, @@ -2683,7 +2561,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -2698,7 +2575,6 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2713,7 +2589,6 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -2723,7 +2598,6 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -2732,15 +2606,13 @@ "version": "1.4.515", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.515.tgz", "integrity": "sha512-VTq6vjk3kCfG2qdzQRd/i9dIyVVm0dbtZIgFzrLgfB73mXDQT2HPKVRc1EoZcAVUv9XhXAu08DWqJuababdGGg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "peer": true, "engines": { "node": ">=12" }, @@ -2752,8 +2624,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -2787,7 +2658,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -2834,7 +2704,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -2844,7 +2713,6 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "peer": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -2867,7 +2735,6 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -2910,7 +2777,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, - "peer": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2987,7 +2853,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -2996,15 +2861,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -3014,7 +2877,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3024,7 +2886,6 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3064,7 +2925,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -3079,7 +2939,6 @@ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "peer": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3099,15 +2958,13 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "peer": true, "engines": { "node": ">=10.17.0" } @@ -3225,7 +3082,6 @@ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -3245,7 +3101,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3259,7 +3114,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -3272,7 +3126,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -3288,7 +3141,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -3301,7 +3153,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -3311,7 +3162,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3321,7 +3171,6 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -3334,7 +3183,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "peer": true, "engines": { "node": ">=0.8.19" } @@ -3363,7 +3211,6 @@ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, - "peer": true, "dependencies": { "has": "^1.0.3" }, @@ -3376,7 +3223,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3386,7 +3232,6 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -3396,7 +3241,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "peer": true, "engines": { "node": ">=0.12.0" } @@ -3438,7 +3282,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3448,7 +3291,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -3465,7 +3307,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -3481,7 +3322,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -3496,7 +3336,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3506,7 +3345,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3519,7 +3357,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -3534,7 +3371,6 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -3548,7 +3384,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3575,7 +3410,6 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -3590,7 +3424,6 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3605,7 +3438,6 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -3629,7 +3461,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -3642,7 +3473,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "peer": true, "engines": { "node": ">=8" }, @@ -3655,7 +3485,6 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -3668,7 +3497,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3684,7 +3512,6 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3694,7 +3521,6 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -3707,7 +3533,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3717,7 +3542,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -3733,7 +3557,6 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3765,7 +3588,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3781,7 +3603,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3798,7 +3619,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3810,15 +3630,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-circus/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3828,7 +3646,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3844,7 +3661,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3857,7 +3673,6 @@ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -3891,7 +3706,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3907,7 +3721,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3924,7 +3737,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3936,15 +3748,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-cli/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3954,7 +3764,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3967,7 +3776,6 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -4013,7 +3821,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4029,7 +3836,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4052,7 +3858,6 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], - "peer": true, "engines": { "node": ">=8" } @@ -4062,7 +3867,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4074,15 +3878,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-config/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4092,7 +3894,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -4111,7 +3912,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4124,7 +3924,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -4140,7 +3939,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4156,7 +3954,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4173,7 +3970,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4185,15 +3981,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-diff/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4203,7 +3997,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4216,7 +4009,6 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -4229,7 +4021,6 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -4246,7 +4037,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4262,7 +4052,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4279,7 +4068,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4291,15 +4079,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-each/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4309,7 +4095,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4322,7 +4107,6 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -4340,7 +4124,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -4350,7 +4133,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -4376,7 +4158,6 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -4390,7 +4171,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -4406,7 +4186,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4422,7 +4201,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4439,7 +4217,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4451,15 +4228,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-matcher-utils/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4469,7 +4244,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4482,7 +4256,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -4503,7 +4276,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4519,7 +4291,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4536,7 +4307,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4548,15 +4318,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-message-util/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4566,7 +4334,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4579,7 +4346,6 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -4594,7 +4360,6 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "peer": true, "engines": { "node": ">=6" }, @@ -4612,7 +4377,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -4622,7 +4386,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -4643,7 +4406,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -4657,7 +4419,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4673,7 +4434,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4690,7 +4450,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4702,15 +4461,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-resolve/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4720,7 +4477,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4733,7 +4489,6 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -4766,7 +4521,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4782,7 +4536,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4799,7 +4552,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4811,15 +4563,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-runner/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4829,7 +4579,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -4845,7 +4594,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4858,7 +4606,6 @@ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -4892,7 +4639,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4908,7 +4654,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4925,7 +4670,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4937,15 +4681,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-runtime/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4955,7 +4697,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4965,7 +4706,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4978,7 +4718,6 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -5010,7 +4749,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5026,7 +4764,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5043,7 +4780,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5055,15 +4791,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-snapshot/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5073,7 +4807,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5089,7 +4822,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5204,7 +4936,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -5222,7 +4953,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5238,7 +4968,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -5251,7 +4980,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5268,7 +4996,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5280,15 +5007,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-validate/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5298,7 +5023,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5311,7 +5035,6 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -5331,7 +5054,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5347,7 +5069,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5364,7 +5085,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5376,15 +5096,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-watcher/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5394,7 +5112,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5407,7 +5124,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -5423,7 +5139,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5433,7 +5148,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5466,7 +5180,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, - "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -5501,7 +5214,6 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -5511,7 +5223,6 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -5598,7 +5309,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -5614,7 +5324,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5636,7 +5345,6 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -5645,15 +5353,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, - "peer": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -5667,7 +5373,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -5706,15 +5411,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/nice-try": { "version": "1.0.5", @@ -5760,22 +5463,19 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5809,7 +5509,6 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -5929,8 +5628,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -5944,8 +5642,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -5972,7 +5669,6 @@ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, - "peer": true, "engines": { "node": ">= 6" } @@ -6078,7 +5774,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -6093,7 +5788,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -6106,7 +5800,6 @@ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -6138,22 +5831,19 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "peer": true + ] }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6163,7 +5853,6 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dev": true, - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -6181,7 +5870,6 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -6194,7 +5882,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -6212,7 +5899,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -6293,8 +5979,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/slash": { "version": "3.0.0", @@ -6309,7 +5994,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6319,7 +6003,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6335,7 +6018,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -6348,7 +6030,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -6358,7 +6039,6 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -6372,7 +6052,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6387,7 +6066,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6416,7 +6094,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -6426,7 +6103,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "peer": true, "engines": { "node": ">=8" }, @@ -6450,7 +6126,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -6463,7 +6138,6 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -6477,15 +6151,13 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -6495,7 +6167,6 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "peer": true, "dependencies": { "is-number": "^7.0.0" }, @@ -6579,7 +6250,6 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -6589,7 +6259,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -6638,7 +6307,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -6663,7 +6331,6 @@ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -6677,15 +6344,13 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -6747,7 +6412,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6765,7 +6429,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -6781,7 +6444,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -6793,8 +6455,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/wrappy": { "version": "1.0.2", @@ -6817,7 +6478,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -6831,7 +6491,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -6855,7 +6514,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", diff --git a/package.json b/package.json index 3e181770..820ddb16 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,10 @@ }, "devDependencies": { "@octokit/types": "8.1.1", + "@types/jest": "^29.5.14", "@types/node": "^18.11.18", "@vercel/ncc": "^0.38.4", + "jest": "^29.7.0", "ts-jest": "^29.0.5", "typescript": "^4.9.5" }, diff --git a/src/checkAllowList.ts b/src/checkAllowList.ts index 8277836a..09c38c9c 100644 --- a/src/checkAllowList.ts +++ b/src/checkAllowList.ts @@ -5,11 +5,11 @@ import * as input from './shared/getInputs' import { getFileContent } from './persistence/persistence' -const usernameAllowListPatterns: string[] = input.getUsernameAllowList().split(',') -const domainAllowList: string[] = input.getDomainAllowList().split(',') - - -function isUserNotInAllowList(committer: CommittersDetails): boolean { +function isUserNotInAllowList( + committer: CommittersDetails, + usernameAllowListPatterns: string[], + domainAllowList: string[] +): boolean { for(let pattern of domainAllowList) { pattern = pattern.trim() @@ -32,6 +32,10 @@ function isUserNotInAllowList(committer: CommittersDetails): boolean { } export async function checkAllowList(committers: CommittersDetails[]): Promise { + // Load allowlists at runtime (not module-load time) for testability + const usernameAllowListPatterns: string[] = input.getUsernameAllowList().split(',') + const domainAllowList: string[] = input.getDomainAllowList().split(',') + const domainsFile: string = input.getDomainsFile() if(domainsFile) { @@ -54,6 +58,6 @@ export async function checkAllowList(committers: CommittersDetails[]): Promise committer && !(isUserNotInAllowList !== undefined && isUserNotInAllowList(committer))) + const committersAfterAllowListCheck: CommittersDetails[] = committers.filter(committer => committer && !(isUserNotInAllowList !== undefined && isUserNotInAllowList(committer, usernameAllowListPatterns, domainAllowList))) return committersAfterAllowListCheck } \ No newline at end of file diff --git a/src/pullrequest/pullRequestComment.ts b/src/pullrequest/pullRequestComment.ts index ac8b34bd..a2fe1548 100644 --- a/src/pullrequest/pullRequestComment.ts +++ b/src/pullrequest/pullRequestComment.ts @@ -20,6 +20,7 @@ export default async function prCommentSetup(committerMap: CommitterMap, committ } else if (claBotComment?.id) { if (signed) { await updateComment(signed, committerMap, claBotComment) + return // Early return - all contributors already signed, no need to check PR comment signatures } // reacted committers are contributors who have newly signed by posting the Pull Request comment diff --git a/src/setupClaCheck.ts b/src/setupClaCheck.ts index 3e5b75d9..4aa69c44 100644 --- a/src/setupClaCheck.ts +++ b/src/setupClaCheck.ts @@ -179,10 +179,10 @@ function prepareCommiterMap( committerMap.notSigned = committers.filter( committer => - !claFileContent?.signedContributors.some(cla => committer.id === cla.id) + !(claFileContent?.signedContributors || []).some(cla => committer.id === cla.id) ) committerMap.signed = committers.filter(committer => - claFileContent?.signedContributors.some(cla => committer.id === cla.id) + (claFileContent?.signedContributors || []).some(cla => committer.id === cla.id) ) committers.map(committer => { if (!committer.id) {