mirror of
https://github.com/claude-did-this/claude-hub.git
synced 2026-02-14 19:30:02 +01:00
feat: Implement combined test coverage for main project and CLI
- Add combined coverage script to merge lcov reports - Update GitHub workflows to generate and upload combined coverage - Install missing CLI dependencies (ora, yaml, cli-table3, mock-fs) - Add initial tests for SessionManager and IssueHandler - Exclude type-only files from coverage metrics - Update jest config to exclude type files from coverage This ensures Codecov receives coverage data from both the main project and CLI subdirectory, providing accurate overall project coverage metrics.
This commit is contained in:
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@@ -22,12 +22,18 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run lint:check
|
||||
- run: npm run test:ci
|
||||
- name: Install CLI dependencies
|
||||
working-directory: ./cli
|
||||
run: npm ci
|
||||
- name: Generate combined coverage
|
||||
run: ./scripts/combine-coverage.js
|
||||
env:
|
||||
NODE_ENV: test
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./coverage-combined
|
||||
fail_ci_if_error: true
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
8
.github/workflows/pull-request.yml
vendored
8
.github/workflows/pull-request.yml
vendored
@@ -20,12 +20,18 @@ jobs:
|
||||
- run: npm run format:check
|
||||
- run: npm run lint:check
|
||||
- run: npm run typecheck
|
||||
- run: npm run test:ci
|
||||
- name: Install CLI dependencies
|
||||
working-directory: ./cli
|
||||
run: npm ci
|
||||
- name: Generate combined coverage
|
||||
run: ./scripts/combine-coverage.js
|
||||
env:
|
||||
NODE_ENV: test
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./coverage-combined
|
||||
fail_ci_if_error: true
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
83
analyze-combined-coverage.js
Executable file
83
analyze-combined-coverage.js
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Read combined lcov.info
|
||||
const lcovPath = path.join(__dirname, 'coverage-combined', 'lcov.info');
|
||||
if (!fs.existsSync(lcovPath)) {
|
||||
console.error('No coverage-combined/lcov.info file found. Run npm run test:combined-coverage first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const lcovContent = fs.readFileSync(lcovPath, 'utf8');
|
||||
const lines = lcovContent.split('\n');
|
||||
|
||||
let currentFile = null;
|
||||
const fileStats = {};
|
||||
let totalLines = 0;
|
||||
let coveredLines = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('SF:')) {
|
||||
currentFile = line.substring(3);
|
||||
if (!fileStats[currentFile]) {
|
||||
fileStats[currentFile] = { lines: 0, covered: 0, functions: 0, functionsHit: 0 };
|
||||
}
|
||||
} else if (line.startsWith('DA:')) {
|
||||
const [lineNum, hits] = line.substring(3).split(',').map(Number);
|
||||
if (currentFile) {
|
||||
fileStats[currentFile].lines++;
|
||||
totalLines++;
|
||||
if (hits > 0) {
|
||||
fileStats[currentFile].covered++;
|
||||
coveredLines++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const overallCoverage = (coveredLines / totalLines) * 100;
|
||||
|
||||
console.log('\n=== Combined Coverage Analysis ===\n');
|
||||
console.log(`Total Lines: ${totalLines}`);
|
||||
console.log(`Covered Lines: ${coveredLines}`);
|
||||
console.log(`Overall Coverage: ${overallCoverage.toFixed(2)}%`);
|
||||
console.log(`Target: 80%`);
|
||||
console.log(`Status: ${overallCoverage >= 80 ? '✅ PASSED' : '❌ FAILED'}\n`);
|
||||
|
||||
// Break down by directory
|
||||
const srcFiles = Object.entries(fileStats).filter(([file]) => file.startsWith('src/'));
|
||||
const cliFiles = Object.entries(fileStats).filter(([file]) => file.startsWith('cli/'));
|
||||
|
||||
const srcStats = srcFiles.reduce((acc, [, stats]) => ({
|
||||
lines: acc.lines + stats.lines,
|
||||
covered: acc.covered + stats.covered
|
||||
}), { lines: 0, covered: 0 });
|
||||
|
||||
const cliStats = cliFiles.reduce((acc, [, stats]) => ({
|
||||
lines: acc.lines + stats.lines,
|
||||
covered: acc.covered + stats.covered
|
||||
}), { lines: 0, covered: 0 });
|
||||
|
||||
console.log('=== Coverage by Component ===');
|
||||
console.log(`Main src/: ${((srcStats.covered / srcStats.lines) * 100).toFixed(2)}% (${srcStats.covered}/${srcStats.lines} lines)`);
|
||||
console.log(`CLI: ${((cliStats.covered / cliStats.lines) * 100).toFixed(2)}% (${cliStats.covered}/${cliStats.lines} lines)`);
|
||||
|
||||
// Show files with lowest coverage
|
||||
console.log('\n=== Files with Lowest Coverage ===');
|
||||
const sorted = Object.entries(fileStats)
|
||||
.map(([file, stats]) => ({
|
||||
file,
|
||||
coverage: (stats.covered / stats.lines) * 100,
|
||||
lines: stats.lines,
|
||||
covered: stats.covered
|
||||
}))
|
||||
.sort((a, b) => a.coverage - b.coverage)
|
||||
.slice(0, 10);
|
||||
|
||||
sorted.forEach(({ file, coverage, covered, lines }) => {
|
||||
console.log(`${file.padEnd(60)} ${coverage.toFixed(2).padStart(6)}% (${covered}/${lines})`);
|
||||
});
|
||||
|
||||
process.exit(overallCoverage >= 80 ? 0 : 1);
|
||||
83
analyze-coverage.js
Normal file
83
analyze-coverage.js
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Read lcov.info
|
||||
const lcovPath = path.join(__dirname, 'coverage', 'lcov.info');
|
||||
if (!fs.existsSync(lcovPath)) {
|
||||
console.error('No coverage/lcov.info file found. Run npm test:coverage first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const lcovContent = fs.readFileSync(lcovPath, 'utf8');
|
||||
const lines = lcovContent.split('\n');
|
||||
|
||||
let currentFile = null;
|
||||
const fileStats = {};
|
||||
let totalLines = 0;
|
||||
let coveredLines = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('SF:')) {
|
||||
currentFile = line.substring(3);
|
||||
if (!fileStats[currentFile]) {
|
||||
fileStats[currentFile] = { lines: 0, covered: 0, functions: 0, functionsHit: 0 };
|
||||
}
|
||||
} else if (line.startsWith('DA:')) {
|
||||
const [lineNum, hits] = line.substring(3).split(',').map(Number);
|
||||
if (currentFile) {
|
||||
fileStats[currentFile].lines++;
|
||||
totalLines++;
|
||||
if (hits > 0) {
|
||||
fileStats[currentFile].covered++;
|
||||
coveredLines++;
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith('FNF:')) {
|
||||
if (currentFile) {
|
||||
fileStats[currentFile].functions = parseInt(line.substring(4));
|
||||
}
|
||||
} else if (line.startsWith('FNH:')) {
|
||||
if (currentFile) {
|
||||
fileStats[currentFile].functionsHit = parseInt(line.substring(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== Coverage Analysis ===\n');
|
||||
console.log(`Total Lines: ${totalLines}`);
|
||||
console.log(`Covered Lines: ${coveredLines}`);
|
||||
console.log(`Overall Coverage: ${((coveredLines / totalLines) * 100).toFixed(2)}%\n`);
|
||||
|
||||
console.log('=== File Breakdown ===\n');
|
||||
const sortedFiles = Object.entries(fileStats).sort((a, b) => {
|
||||
const coverageA = (a[1].covered / a[1].lines) * 100;
|
||||
const coverageB = (b[1].covered / b[1].lines) * 100;
|
||||
return coverageA - coverageB;
|
||||
});
|
||||
|
||||
for (const [file, stats] of sortedFiles) {
|
||||
const coverage = ((stats.covered / stats.lines) * 100).toFixed(2);
|
||||
console.log(`${file.padEnd(60)} ${coverage.padStart(6)}% (${stats.covered}/${stats.lines} lines)`);
|
||||
}
|
||||
|
||||
// Check if CLI coverage is included
|
||||
console.log('\n=== Coverage Scope Analysis ===\n');
|
||||
const cliFiles = sortedFiles.filter(([file]) => file.includes('cli/'));
|
||||
const srcFiles = sortedFiles.filter(([file]) => file.startsWith('src/'));
|
||||
|
||||
console.log(`Main src/ files: ${srcFiles.length}`);
|
||||
console.log(`CLI files: ${cliFiles.length}`);
|
||||
|
||||
if (cliFiles.length > 0) {
|
||||
console.log('\nCLI files found in coverage:');
|
||||
cliFiles.forEach(([file]) => console.log(` - ${file}`));
|
||||
}
|
||||
|
||||
// Check for any unexpected files
|
||||
const otherFiles = sortedFiles.filter(([file]) => !file.startsWith('src/') && !file.includes('cli/'));
|
||||
if (otherFiles.length > 0) {
|
||||
console.log('\nOther files in coverage:');
|
||||
otherFiles.forEach(([file]) => console.log(` - ${file}`));
|
||||
}
|
||||
99
calculate-codecov-match.js
Normal file
99
calculate-codecov-match.js
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Coverage data from the test output
|
||||
const coverageData = {
|
||||
'src/index.ts': { statements: 92.64, branches: 78.94, functions: 85.71, lines: 92.64 },
|
||||
'src/controllers/githubController.ts': { statements: 69.65, branches: 64.47, functions: 84.61, lines: 69.2 },
|
||||
'src/core/webhook/WebhookProcessor.ts': { statements: 100, branches: 92.3, functions: 100, lines: 100 },
|
||||
'src/core/webhook/WebhookRegistry.ts': { statements: 97.77, branches: 100, functions: 100, lines: 97.67 },
|
||||
'src/core/webhook/constants.ts': { statements: 100, branches: 100, functions: 100, lines: 100 },
|
||||
'src/core/webhook/index.ts': { statements: 0, branches: 100, functions: 0, lines: 0 },
|
||||
'src/providers/claude/ClaudeWebhookProvider.ts': { statements: 77.41, branches: 46.66, functions: 100, lines: 77.41 },
|
||||
'src/providers/claude/index.ts': { statements: 100, branches: 100, functions: 0, lines: 100 },
|
||||
'src/providers/claude/handlers/OrchestrationHandler.ts': { statements: 95.65, branches: 75, functions: 100, lines: 95.65 },
|
||||
'src/providers/claude/handlers/SessionHandler.ts': { statements: 96.66, branches: 89.28, functions: 100, lines: 96.66 },
|
||||
'src/providers/claude/services/SessionManager.ts': { statements: 6.06, branches: 0, functions: 0, lines: 6.06 },
|
||||
'src/providers/claude/services/TaskDecomposer.ts': { statements: 96.87, branches: 93.75, functions: 100, lines: 96.66 },
|
||||
'src/providers/github/GitHubWebhookProvider.ts': { statements: 95.45, branches: 90.62, functions: 100, lines: 95.45 },
|
||||
'src/providers/github/index.ts': { statements: 100, branches: 100, functions: 100, lines: 100 },
|
||||
'src/providers/github/handlers/IssueHandler.ts': { statements: 30.43, branches: 0, functions: 0, lines: 30.43 },
|
||||
'src/routes/github.ts': { statements: 100, branches: 100, functions: 100, lines: 100 },
|
||||
'src/routes/webhooks.ts': { statements: 92.1, branches: 100, functions: 57.14, lines: 91.66 },
|
||||
'src/services/claudeService.ts': { statements: 85.62, branches: 66.17, functions: 100, lines: 86.66 },
|
||||
'src/services/githubService.ts': { statements: 72.22, branches: 78.57, functions: 75, lines: 71.93 },
|
||||
'src/types/claude.ts': { statements: 0, branches: 100, functions: 100, lines: 0 },
|
||||
'src/types/environment.ts': { statements: 0, branches: 0, functions: 0, lines: 0 },
|
||||
'src/types/index.ts': { statements: 0, branches: 0, functions: 0, lines: 0 },
|
||||
'src/utils/awsCredentialProvider.ts': { statements: 65.68, branches: 59.25, functions: 54.54, lines: 65.68 },
|
||||
'src/utils/logger.ts': { statements: 51.61, branches: 47.36, functions: 100, lines: 51.72 },
|
||||
'src/utils/sanitize.ts': { statements: 100, branches: 100, functions: 100, lines: 100 },
|
||||
'src/utils/secureCredentials.ts': { statements: 54.28, branches: 70.58, functions: 33.33, lines: 54.28 },
|
||||
'src/utils/startup-metrics.ts': { statements: 100, branches: 100, functions: 100, lines: 100 }
|
||||
};
|
||||
|
||||
// Calculate different scenarios
|
||||
console.log('\n=== Coverage Analysis - Matching Codecov ===\n');
|
||||
|
||||
// Scenario 1: Exclude type definition files
|
||||
const withoutTypes = Object.entries(coverageData)
|
||||
.filter(([file]) => !file.includes('/types/'))
|
||||
.reduce((acc, [file, data]) => {
|
||||
acc[file] = data;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const avgWithoutTypes = calculateAverage(withoutTypes);
|
||||
console.log(`1. Without type files: ${avgWithoutTypes.toFixed(2)}%`);
|
||||
|
||||
// Scenario 2: Exclude files with 0% coverage
|
||||
const withoutZeroCoverage = Object.entries(coverageData)
|
||||
.filter(([file, data]) => data.lines > 0)
|
||||
.reduce((acc, [file, data]) => {
|
||||
acc[file] = data;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const avgWithoutZero = calculateAverage(withoutZeroCoverage);
|
||||
console.log(`2. Without 0% coverage files: ${avgWithoutZero.toFixed(2)}%`);
|
||||
|
||||
// Scenario 3: Exclude specific low coverage files
|
||||
const excludeLowCoverage = Object.entries(coverageData)
|
||||
.filter(([file]) => {
|
||||
return !file.includes('/types/') &&
|
||||
!file.includes('SessionManager.ts') &&
|
||||
!file.includes('IssueHandler.ts');
|
||||
})
|
||||
.reduce((acc, [file, data]) => {
|
||||
acc[file] = data;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const avgExcludeLow = calculateAverage(excludeLowCoverage);
|
||||
console.log(`3. Without types, SessionManager, IssueHandler: ${avgExcludeLow.toFixed(2)}%`);
|
||||
|
||||
// Scenario 4: Statement coverage only (what codecov might be reporting)
|
||||
const statementOnly = calculateStatementAverage(coverageData);
|
||||
console.log(`4. Statement coverage only: ${statementOnly.toFixed(2)}%`);
|
||||
|
||||
// Show which files have the biggest impact
|
||||
console.log('\n=== Files with lowest coverage ===');
|
||||
const sorted = Object.entries(coverageData)
|
||||
.sort((a, b) => a[1].lines - b[1].lines)
|
||||
.slice(0, 10);
|
||||
|
||||
sorted.forEach(([file, data]) => {
|
||||
console.log(`${file.padEnd(60)} ${data.lines.toFixed(2)}%`);
|
||||
});
|
||||
|
||||
function calculateAverage(data) {
|
||||
const values = Object.values(data).map(d => d.lines);
|
||||
return values.reduce((sum, val) => sum + val, 0) / values.length;
|
||||
}
|
||||
|
||||
function calculateStatementAverage(data) {
|
||||
const values = Object.values(data).map(d => d.statements);
|
||||
return values.reduce((sum, val) => sum + val, 0) / values.length;
|
||||
}
|
||||
439
cli/package-lock.json
generated
439
cli/package-lock.json
generated
@@ -10,12 +10,9 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-table3": "^0.6.3",
|
||||
"commander": "^14.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"ora": "^5.4.1",
|
||||
"uuid": "^9.0.0",
|
||||
"yaml": "^2.3.4"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-hub": "claude-hub"
|
||||
@@ -24,12 +21,16 @@
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/mock-fs": "^4.13.4",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/ora": "^3.1.0",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"cli-table3": "^0.6.5",
|
||||
"jest": "^29.5.0",
|
||||
"mock-fs": "^5.5.0",
|
||||
"ora": "^8.2.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.2"
|
||||
"typescript": "^5.3.2",
|
||||
"yaml": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -501,6 +502,7 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
@@ -1016,6 +1018,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
|
||||
"integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -1029,6 +1032,16 @@
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ora": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ora/-/ora-3.1.0.tgz",
|
||||
"integrity": "sha512-4e15N42qhHRlxyP5SpX9fK3q4tXvEkdmGdof2DZ0mqPu7glrNT8cs9bbI73NhwEGApq1TSXhs2aFmn19VCTwCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||
@@ -1099,6 +1112,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1282,35 +1296,6 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -1386,29 +1371,6 @@
|
||||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@@ -1511,20 +1473,26 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^3.1.0"
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-spinners": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
||||
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
@@ -1536,6 +1504,8 @@
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
|
||||
"integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0"
|
||||
},
|
||||
@@ -1560,14 +1530,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
||||
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
@@ -1712,17 +1674,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defaults": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
|
||||
"dependencies": {
|
||||
"clone": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -1818,7 +1769,8 @@
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
@@ -2098,6 +2050,19 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
|
||||
"integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -2260,25 +2225,6 @@
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||
@@ -2321,7 +2267,8 @@
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
@@ -2348,6 +2295,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2362,11 +2310,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-interactive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
|
||||
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
|
||||
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
@@ -2391,11 +2344,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
|
||||
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
|
||||
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -3162,15 +3117,43 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
|
||||
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.0",
|
||||
"is-unicode-supported": "^0.1.0"
|
||||
"chalk": "^5.3.0",
|
||||
"is-unicode-supported": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
|
||||
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -3277,10 +3260,24 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -3298,6 +3295,7 @@
|
||||
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz",
|
||||
"integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
@@ -3360,6 +3358,7 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
@@ -3371,27 +3370,96 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ora": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
||||
"integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
|
||||
"integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.1.0",
|
||||
"chalk": "^4.1.0",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"cli-spinners": "^2.5.0",
|
||||
"is-interactive": "^1.0.0",
|
||||
"is-unicode-supported": "^0.1.0",
|
||||
"log-symbols": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wcwidth": "^1.0.1"
|
||||
"chalk": "^5.3.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
"cli-spinners": "^2.9.2",
|
||||
"is-interactive": "^2.0.0",
|
||||
"is-unicode-supported": "^2.0.0",
|
||||
"log-symbols": "^6.0.0",
|
||||
"stdin-discarder": "^0.2.2",
|
||||
"string-width": "^7.2.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ora/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ora/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ora/node_modules/emoji-regex": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ora/node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ora/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@@ -3599,19 +3667,6 @@
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -3672,35 +3727,50 @@
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
||||
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^5.1.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
"node_modules/restore-cursor/node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
@@ -3735,7 +3805,8 @@
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
@@ -3789,12 +3860,17 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
"node_modules/stdin-discarder": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
|
||||
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-length": {
|
||||
@@ -3814,6 +3890,7 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@@ -3827,6 +3904,7 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@@ -4105,11 +4183,6 @@
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
@@ -4151,14 +4224,6 @@
|
||||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
|
||||
"dependencies": {
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -4229,6 +4294,8 @@
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
||||
@@ -20,22 +20,23 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-table3": "^0.6.3",
|
||||
"commander": "^14.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"ora": "^5.4.1",
|
||||
"uuid": "^9.0.0",
|
||||
"yaml": "^2.3.4"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/mock-fs": "^4.13.4",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/ora": "^3.1.0",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"cli-table3": "^0.6.5",
|
||||
"jest": "^29.5.0",
|
||||
"mock-fs": "^5.5.0",
|
||||
"ora": "^8.2.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.2"
|
||||
"typescript": "^5.3.2",
|
||||
"yaml": "^2.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
3819
coverage-combined/lcov.info
Normal file
3819
coverage-combined/lcov.info
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ module.exports = {
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{js,ts}',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/types/**/*.ts',
|
||||
'!**/node_modules/**',
|
||||
'!**/dist/**'
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:watch": "jest --watch",
|
||||
"test:ci": "jest --ci --coverage --testPathPattern='test/(unit|integration).*\\.test\\.(js|ts)$'",
|
||||
"test:combined-coverage": "./scripts/combine-coverage.js",
|
||||
"test:docker": "docker-compose -f docker-compose.test.yml run --rm test",
|
||||
"test:docker:integration": "docker-compose -f docker-compose.test.yml run --rm integration-test",
|
||||
"test:docker:e2e": "docker-compose -f docker-compose.test.yml run --rm e2e-test",
|
||||
|
||||
88
scripts/combine-coverage.js
Executable file
88
scripts/combine-coverage.js
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Combine coverage reports from main project and CLI
|
||||
*/
|
||||
|
||||
// Ensure coverage directories exist
|
||||
const mainCoverageDir = path.join(__dirname, '..', 'coverage');
|
||||
const cliCoverageDir = path.join(__dirname, '..', 'cli', 'coverage');
|
||||
const combinedCoverageDir = path.join(__dirname, '..', 'coverage-combined');
|
||||
|
||||
// Create combined coverage directory
|
||||
if (!fs.existsSync(combinedCoverageDir)) {
|
||||
fs.mkdirSync(combinedCoverageDir, { recursive: true });
|
||||
}
|
||||
|
||||
console.log('Generating main project coverage...');
|
||||
try {
|
||||
execSync('npm run test:ci', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
||||
} catch (error) {
|
||||
console.error('Failed to generate main project coverage');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\nGenerating CLI coverage...');
|
||||
try {
|
||||
execSync('npm run test:coverage', { stdio: 'inherit', cwd: path.join(__dirname, '..', 'cli') });
|
||||
} catch (error) {
|
||||
console.error('Failed to generate CLI coverage');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if both coverage files exist
|
||||
const mainLcov = path.join(mainCoverageDir, 'lcov.info');
|
||||
const cliLcov = path.join(cliCoverageDir, 'lcov.info');
|
||||
|
||||
if (!fs.existsSync(mainLcov)) {
|
||||
console.error('Main project lcov.info not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(cliLcov)) {
|
||||
console.error('CLI lcov.info not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read both lcov files
|
||||
const mainLcovContent = fs.readFileSync(mainLcov, 'utf8');
|
||||
const cliLcovContent = fs.readFileSync(cliLcov, 'utf8');
|
||||
|
||||
// Adjust CLI paths to be relative to project root
|
||||
const adjustedCliLcov = cliLcovContent.replace(/SF:src\//g, 'SF:cli/src/');
|
||||
|
||||
// Combine lcov files
|
||||
const combinedLcov = mainLcovContent + '\n' + adjustedCliLcov;
|
||||
|
||||
// Write combined lcov file
|
||||
const combinedLcovPath = path.join(combinedCoverageDir, 'lcov.info');
|
||||
fs.writeFileSync(combinedLcovPath, combinedLcov);
|
||||
|
||||
console.log('\nCombined coverage report written to:', combinedLcovPath);
|
||||
|
||||
// Copy coverage-final.json files as well for better reporting
|
||||
if (fs.existsSync(path.join(mainCoverageDir, 'coverage-final.json'))) {
|
||||
const mainJson = JSON.parse(fs.readFileSync(path.join(mainCoverageDir, 'coverage-final.json'), 'utf8'));
|
||||
const cliJson = JSON.parse(fs.readFileSync(path.join(cliCoverageDir, 'coverage-final.json'), 'utf8'));
|
||||
|
||||
// Adjust CLI paths in JSON
|
||||
const adjustedCliJson = {};
|
||||
for (const [key, value] of Object.entries(cliJson)) {
|
||||
const adjustedKey = key.replace(/^src\//, 'cli/src/');
|
||||
adjustedCliJson[adjustedKey] = value;
|
||||
}
|
||||
|
||||
// Combine JSON coverage
|
||||
const combinedJson = { ...mainJson, ...adjustedCliJson };
|
||||
fs.writeFileSync(
|
||||
path.join(combinedCoverageDir, 'coverage-final.json'),
|
||||
JSON.stringify(combinedJson, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
console.log('\nCoverage combination complete!');
|
||||
console.log('Upload coverage-combined/lcov.info to Codecov for full project coverage.');
|
||||
323
test/unit/providers/claude/services/SessionManager.test.ts
Normal file
323
test/unit/providers/claude/services/SessionManager.test.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
import { SessionManager } from '../../../../../src/providers/claude/services/SessionManager';
|
||||
import { execSync, spawn } from 'child_process';
|
||||
import type { ClaudeSession } from '../../../../../src/types/claude-orchestration';
|
||||
|
||||
// Mock child_process
|
||||
jest.mock('child_process', () => ({
|
||||
execSync: jest.fn(),
|
||||
spawn: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
jest.mock('../../../../../src/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
debug: jest.fn()
|
||||
})
|
||||
}));
|
||||
|
||||
describe('SessionManager', () => {
|
||||
let sessionManager: SessionManager;
|
||||
const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
|
||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
sessionManager = new SessionManager();
|
||||
|
||||
// Setup default mocks
|
||||
mockExecSync.mockReturnValue(Buffer.from(''));
|
||||
mockSpawn.mockReturnValue({
|
||||
stdout: { on: jest.fn() },
|
||||
stderr: { on: jest.fn() },
|
||||
on: jest.fn()
|
||||
} as any);
|
||||
});
|
||||
|
||||
describe('createContainer', () => {
|
||||
it('should create a container for a session', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session-123',
|
||||
type: 'analysis',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Test requirements',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const containerName = await sessionManager.createContainer(session);
|
||||
|
||||
expect(containerName).toBe('claude-analysis-test-ses');
|
||||
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('docker create'), {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors when creating container', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session-123',
|
||||
type: 'analysis',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Test requirements',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
mockExecSync.mockImplementation(() => {
|
||||
throw new Error('Docker error');
|
||||
});
|
||||
|
||||
await expect(sessionManager.createContainer(session)).rejects.toThrow('Docker error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('startSession', () => {
|
||||
it('should start a session with a container', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session-123',
|
||||
type: 'implementation',
|
||||
status: 'pending',
|
||||
containerId: 'container-123',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Implement feature X',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
// Mock spawn to simulate successful execution
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === 'data') cb(Buffer.from('Output line'));
|
||||
})
|
||||
},
|
||||
stderr: { on: jest.fn() },
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === 'close') cb(0);
|
||||
})
|
||||
};
|
||||
mockSpawn.mockReturnValue(mockProcess as any);
|
||||
|
||||
await sessionManager.startSession(session);
|
||||
|
||||
expect(mockExecSync).toHaveBeenCalledWith('docker start container-123', { stdio: 'pipe' });
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['exec', '-i', 'container-123', 'claude'])
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if session has no container ID', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session-123',
|
||||
type: 'testing',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Test requirements',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
await expect(sessionManager.startSession(session)).rejects.toThrow(
|
||||
'Session has no container ID'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSession', () => {
|
||||
it('should return a session by ID', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session-123',
|
||||
type: 'review',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Review code',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
await sessionManager.createContainer(session);
|
||||
const retrieved = sessionManager.getSession('test-session-123');
|
||||
|
||||
expect(retrieved).toBeDefined();
|
||||
expect(retrieved?.id).toBe('test-session-123');
|
||||
});
|
||||
|
||||
it('should return undefined for non-existent session', () => {
|
||||
const retrieved = sessionManager.getSession('non-existent');
|
||||
expect(retrieved).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllSessions', () => {
|
||||
it('should return all sessions', async () => {
|
||||
const session1: ClaudeSession = {
|
||||
id: 'session-1',
|
||||
type: 'analysis',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo1',
|
||||
requirements: 'Analyze',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const session2: ClaudeSession = {
|
||||
id: 'session-2',
|
||||
type: 'implementation',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo2',
|
||||
requirements: 'Implement',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
await sessionManager.createContainer(session1);
|
||||
await sessionManager.createContainer(session2);
|
||||
|
||||
const allSessions = sessionManager.getAllSessions();
|
||||
expect(allSessions).toHaveLength(2);
|
||||
expect(allSessions.map(s => s.id)).toEqual(['session-1', 'session-2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrchestrationSessions', () => {
|
||||
it('should return sessions for a specific orchestration', async () => {
|
||||
const session1: ClaudeSession = {
|
||||
id: 'orch-123-session-1',
|
||||
type: 'analysis',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Analyze',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const session2: ClaudeSession = {
|
||||
id: 'orch-123-session-2',
|
||||
type: 'implementation',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Implement',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const otherSession: ClaudeSession = {
|
||||
id: 'orch-456-session-1',
|
||||
type: 'testing',
|
||||
status: 'pending',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Test',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
await sessionManager.createContainer(session1);
|
||||
await sessionManager.createContainer(session2);
|
||||
await sessionManager.createContainer(otherSession);
|
||||
|
||||
const orchSessions = sessionManager.getOrchestrationSessions('orch-123');
|
||||
expect(orchSessions).toHaveLength(2);
|
||||
expect(orchSessions.map(s => s.id)).toEqual(['orch-123-session-1', 'orch-123-session-2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queueSession', () => {
|
||||
it('should start session immediately if no dependencies', async () => {
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session',
|
||||
type: 'analysis',
|
||||
status: 'pending',
|
||||
containerId: 'container-123',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Analyze',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const mockProcess = {
|
||||
stdout: { on: jest.fn() },
|
||||
stderr: { on: jest.fn() },
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === 'close') cb(0);
|
||||
})
|
||||
};
|
||||
mockSpawn.mockReturnValue(mockProcess as any);
|
||||
|
||||
await sessionManager.queueSession(session);
|
||||
|
||||
expect(mockExecSync).toHaveBeenCalledWith('docker start container-123', { stdio: 'pipe' });
|
||||
});
|
||||
|
||||
it('should queue session if dependencies not met', async () => {
|
||||
const depSession: ClaudeSession = {
|
||||
id: 'dep-session',
|
||||
type: 'analysis',
|
||||
status: 'running',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Analyze',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: [],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
const session: ClaudeSession = {
|
||||
id: 'test-session',
|
||||
type: 'implementation',
|
||||
status: 'pending',
|
||||
containerId: 'container-123',
|
||||
project: {
|
||||
repository: 'owner/repo',
|
||||
requirements: 'Implement',
|
||||
constraints: []
|
||||
},
|
||||
dependencies: ['dep-session'],
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
await sessionManager.createContainer(depSession);
|
||||
await sessionManager.queueSession(session);
|
||||
|
||||
// Should not start immediately
|
||||
expect(mockSpawn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
131
test/unit/providers/github/handlers/IssueHandler.test.ts
Normal file
131
test/unit/providers/github/handlers/IssueHandler.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { IssueHandler } from '../../../../../src/providers/github/handlers/IssueHandler';
|
||||
import { WebhookProcessor } from '../../../../../src/core/webhook/WebhookProcessor';
|
||||
import type { IssuesEvent } from '@octokit/webhooks-types';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../src/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
warn: jest.fn()
|
||||
})
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../src/utils/secureCredentials', () => ({
|
||||
SecureCredentials: jest.fn().mockImplementation(() => ({
|
||||
loadCredentials: jest.fn(),
|
||||
getCredential: jest.fn().mockReturnValue('mock-value')
|
||||
})),
|
||||
secureCredentials: {
|
||||
loadCredentials: jest.fn(),
|
||||
getCredential: jest.fn().mockReturnValue('mock-value')
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../src/services/claudeService');
|
||||
jest.mock('../../../../../src/services/githubService');
|
||||
|
||||
const claudeService = require('../../../../../src/services/claudeService');
|
||||
const githubService = require('../../../../../src/services/githubService');
|
||||
|
||||
describe('IssueHandler', () => {
|
||||
let handler: IssueHandler;
|
||||
let processor: WebhookProcessor;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
handler = new IssueHandler();
|
||||
processor = new WebhookProcessor({
|
||||
webhookPath: '/test',
|
||||
secret: 'test-secret'
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleIssue', () => {
|
||||
const mockEvent: IssuesEvent = {
|
||||
action: 'opened',
|
||||
issue: {
|
||||
id: 123,
|
||||
number: 1,
|
||||
title: 'Test Issue',
|
||||
body: 'This is a test issue about authentication and API integration',
|
||||
labels: [],
|
||||
state: 'open',
|
||||
user: {
|
||||
login: 'testuser',
|
||||
id: 1
|
||||
},
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
},
|
||||
repository: {
|
||||
id: 456,
|
||||
name: 'test-repo',
|
||||
full_name: 'owner/test-repo',
|
||||
owner: {
|
||||
login: 'owner',
|
||||
id: 2
|
||||
},
|
||||
private: false
|
||||
},
|
||||
sender: {
|
||||
login: 'testuser',
|
||||
id: 1
|
||||
}
|
||||
} as any;
|
||||
|
||||
it('should analyze and label new issues', async () => {
|
||||
githubService.addLabelsToIssue = jest.fn().mockResolvedValue(undefined);
|
||||
claudeService.analyzeIssueForLabels = jest.fn().mockResolvedValue({
|
||||
priority: 'medium',
|
||||
type: 'feature',
|
||||
complexity: 'moderate',
|
||||
component: 'api,auth'
|
||||
});
|
||||
|
||||
await handler.handleIssue(mockEvent, processor);
|
||||
|
||||
expect(claudeService.analyzeIssueForLabels).toHaveBeenCalledWith(
|
||||
'owner/test-repo',
|
||||
1,
|
||||
'Test Issue',
|
||||
'This is a test issue about authentication and API integration'
|
||||
);
|
||||
|
||||
expect(githubService.addLabelsToIssue).toHaveBeenCalledWith('owner/test-repo', 1, [
|
||||
'priority:medium',
|
||||
'type:feature',
|
||||
'complexity:moderate',
|
||||
'component:api',
|
||||
'component:auth'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
claudeService.analyzeIssueForLabels = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Analysis failed'));
|
||||
|
||||
await expect(handler.handleIssue(mockEvent, processor)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should skip non-opened events', async () => {
|
||||
const editEvent = { ...mockEvent, action: 'edited' } as IssuesEvent;
|
||||
|
||||
await handler.handleIssue(editEvent, processor);
|
||||
|
||||
expect(claudeService.analyzeIssueForLabels).not.toHaveBeenCalled();
|
||||
expect(githubService.addLabelsToIssue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty label analysis', async () => {
|
||||
claudeService.analyzeIssueForLabels = jest.fn().mockResolvedValue({});
|
||||
githubService.addLabelsToIssue = jest.fn();
|
||||
|
||||
await handler.handleIssue(mockEvent, processor);
|
||||
|
||||
expect(githubService.addLabelsToIssue).toHaveBeenCalledWith('owner/test-repo', 1, []);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user