pnpm-catalog-updates 0.5.6 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pcu.js +1 -1
- package/dist/index.js +9559 -32
- package/dist/index.js.map +1 -1
- package/package.json +61 -103
- package/src/cli/commands/checkCommand.ts +227 -0
- package/src/cli/commands/initCommand.ts +394 -0
- package/src/cli/commands/securityCommand.ts +569 -0
- package/src/cli/commands/updateCommand.ts +245 -0
- package/src/cli/formatters/outputFormatter.ts +830 -0
- package/src/cli/formatters/progressBar.ts +700 -0
- package/src/cli/index.ts +565 -0
- package/src/cli/interactive/interactivePrompts.ts +517 -0
- package/src/cli/options/globalOptions.ts +380 -0
- package/src/cli/options/index.ts +5 -0
- package/src/cli/themes/colorTheme.ts +379 -0
- package/src/cli/validators/commandValidator.ts +395 -0
- package/src/cli/validators/index.ts +5 -0
- package/src/index.ts +4 -0
- package/LICENSE +0 -21
- package/README.ja.md +0 -582
- package/README.md +0 -690
- package/README.zh-CN.md +0 -630
- package/dist/application/services/CatalogUpdateService.d.ts +0 -209
- package/dist/application/services/CatalogUpdateService.d.ts.map +0 -1
- package/dist/application/services/CatalogUpdateService.js +0 -836
- package/dist/application/services/CatalogUpdateService.js.map +0 -1
- package/dist/application/services/WorkspaceService.d.ts +0 -139
- package/dist/application/services/WorkspaceService.d.ts.map +0 -1
- package/dist/application/services/WorkspaceService.js +0 -340
- package/dist/application/services/WorkspaceService.js.map +0 -1
- package/dist/cli/commands/CheckCommand.d.ts +0 -40
- package/dist/cli/commands/CheckCommand.d.ts.map +0 -1
- package/dist/cli/commands/CheckCommand.js +0 -177
- package/dist/cli/commands/CheckCommand.js.map +0 -1
- package/dist/cli/commands/InitCommand.d.ts +0 -53
- package/dist/cli/commands/InitCommand.d.ts.map +0 -1
- package/dist/cli/commands/InitCommand.js +0 -338
- package/dist/cli/commands/InitCommand.js.map +0 -1
- package/dist/cli/commands/SecurityCommand.d.ts +0 -113
- package/dist/cli/commands/SecurityCommand.d.ts.map +0 -1
- package/dist/cli/commands/SecurityCommand.js +0 -410
- package/dist/cli/commands/SecurityCommand.js.map +0 -1
- package/dist/cli/commands/UpdateCommand.d.ts +0 -44
- package/dist/cli/commands/UpdateCommand.d.ts.map +0 -1
- package/dist/cli/commands/UpdateCommand.js +0 -189
- package/dist/cli/commands/UpdateCommand.js.map +0 -1
- package/dist/cli/formatters/OutputFormatter.d.ts +0 -116
- package/dist/cli/formatters/OutputFormatter.d.ts.map +0 -1
- package/dist/cli/formatters/OutputFormatter.js +0 -664
- package/dist/cli/formatters/OutputFormatter.js.map +0 -1
- package/dist/cli/formatters/ProgressBar.d.ts +0 -195
- package/dist/cli/formatters/ProgressBar.d.ts.map +0 -1
- package/dist/cli/formatters/ProgressBar.js +0 -622
- package/dist/cli/formatters/ProgressBar.js.map +0 -1
- package/dist/cli/index.d.ts +0 -12
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -492
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/interactive/InteractivePrompts.d.ts +0 -85
- package/dist/cli/interactive/InteractivePrompts.d.ts.map +0 -1
- package/dist/cli/interactive/InteractivePrompts.js +0 -434
- package/dist/cli/interactive/InteractivePrompts.js.map +0 -1
- package/dist/cli/options/GlobalOptions.d.ts +0 -117
- package/dist/cli/options/GlobalOptions.d.ts.map +0 -1
- package/dist/cli/options/GlobalOptions.js +0 -278
- package/dist/cli/options/GlobalOptions.js.map +0 -1
- package/dist/cli/options/index.d.ts +0 -5
- package/dist/cli/options/index.d.ts.map +0 -1
- package/dist/cli/options/index.js +0 -5
- package/dist/cli/options/index.js.map +0 -1
- package/dist/cli/themes/ColorTheme.d.ts +0 -211
- package/dist/cli/themes/ColorTheme.d.ts.map +0 -1
- package/dist/cli/themes/ColorTheme.js +0 -267
- package/dist/cli/themes/ColorTheme.js.map +0 -1
- package/dist/cli/validators/CommandValidator.d.ts +0 -60
- package/dist/cli/validators/CommandValidator.d.ts.map +0 -1
- package/dist/cli/validators/CommandValidator.js +0 -319
- package/dist/cli/validators/CommandValidator.js.map +0 -1
- package/dist/cli/validators/index.d.ts +0 -5
- package/dist/cli/validators/index.d.ts.map +0 -1
- package/dist/cli/validators/index.js +0 -5
- package/dist/cli/validators/index.js.map +0 -1
- package/dist/common/config/Config.d.ts +0 -142
- package/dist/common/config/Config.d.ts.map +0 -1
- package/dist/common/config/Config.js +0 -382
- package/dist/common/config/Config.js.map +0 -1
- package/dist/common/config/ConfigLoader.d.ts +0 -49
- package/dist/common/config/ConfigLoader.d.ts.map +0 -1
- package/dist/common/config/ConfigLoader.js +0 -180
- package/dist/common/config/ConfigLoader.js.map +0 -1
- package/dist/common/config/PackageFilterConfig.d.ts +0 -56
- package/dist/common/config/PackageFilterConfig.d.ts.map +0 -1
- package/dist/common/config/PackageFilterConfig.js +0 -94
- package/dist/common/config/PackageFilterConfig.js.map +0 -1
- package/dist/common/config/index.d.ts +0 -8
- package/dist/common/config/index.d.ts.map +0 -1
- package/dist/common/config/index.js +0 -8
- package/dist/common/config/index.js.map +0 -1
- package/dist/common/error-handling/ErrorTracker.d.ts +0 -48
- package/dist/common/error-handling/ErrorTracker.d.ts.map +0 -1
- package/dist/common/error-handling/ErrorTracker.js +0 -93
- package/dist/common/error-handling/ErrorTracker.js.map +0 -1
- package/dist/common/error-handling/UserFriendlyErrorHandler.d.ts +0 -74
- package/dist/common/error-handling/UserFriendlyErrorHandler.d.ts.map +0 -1
- package/dist/common/error-handling/UserFriendlyErrorHandler.js +0 -703
- package/dist/common/error-handling/UserFriendlyErrorHandler.js.map +0 -1
- package/dist/common/error-handling/index.d.ts +0 -11
- package/dist/common/error-handling/index.d.ts.map +0 -1
- package/dist/common/error-handling/index.js +0 -9
- package/dist/common/error-handling/index.js.map +0 -1
- package/dist/common/logger/Logger.d.ts +0 -110
- package/dist/common/logger/Logger.d.ts.map +0 -1
- package/dist/common/logger/Logger.js +0 -289
- package/dist/common/logger/Logger.js.map +0 -1
- package/dist/common/logger/index.d.ts +0 -6
- package/dist/common/logger/index.d.ts.map +0 -1
- package/dist/common/logger/index.js +0 -6
- package/dist/common/logger/index.js.map +0 -1
- package/dist/common/types/cli.d.ts +0 -265
- package/dist/common/types/cli.d.ts.map +0 -1
- package/dist/common/types/cli.js +0 -5
- package/dist/common/types/cli.js.map +0 -1
- package/dist/common/types/core.d.ts +0 -270
- package/dist/common/types/core.d.ts.map +0 -1
- package/dist/common/types/core.js +0 -32
- package/dist/common/types/core.js.map +0 -1
- package/dist/common/types/index.d.ts +0 -8
- package/dist/common/types/index.d.ts.map +0 -1
- package/dist/common/types/index.js +0 -8
- package/dist/common/types/index.js.map +0 -1
- package/dist/common/utils/VersionChecker.d.ts +0 -54
- package/dist/common/utils/VersionChecker.d.ts.map +0 -1
- package/dist/common/utils/VersionChecker.js +0 -180
- package/dist/common/utils/VersionChecker.js.map +0 -1
- package/dist/common/utils/async.d.ts +0 -74
- package/dist/common/utils/async.d.ts.map +0 -1
- package/dist/common/utils/async.js +0 -228
- package/dist/common/utils/async.js.map +0 -1
- package/dist/common/utils/format.d.ts +0 -32
- package/dist/common/utils/format.d.ts.map +0 -1
- package/dist/common/utils/format.js +0 -121
- package/dist/common/utils/format.js.map +0 -1
- package/dist/common/utils/git.d.ts +0 -44
- package/dist/common/utils/git.d.ts.map +0 -1
- package/dist/common/utils/git.js +0 -147
- package/dist/common/utils/git.js.map +0 -1
- package/dist/common/utils/index.d.ts +0 -12
- package/dist/common/utils/index.d.ts.map +0 -1
- package/dist/common/utils/index.js +0 -12
- package/dist/common/utils/index.js.map +0 -1
- package/dist/common/utils/string.d.ts +0 -56
- package/dist/common/utils/string.d.ts.map +0 -1
- package/dist/common/utils/string.js +0 -134
- package/dist/common/utils/string.js.map +0 -1
- package/dist/common/utils/validation.d.ts +0 -88
- package/dist/common/utils/validation.d.ts.map +0 -1
- package/dist/common/utils/validation.js +0 -308
- package/dist/common/utils/validation.js.map +0 -1
- package/dist/domain/entities/Catalog.d.ts +0 -117
- package/dist/domain/entities/Catalog.d.ts.map +0 -1
- package/dist/domain/entities/Catalog.js +0 -240
- package/dist/domain/entities/Catalog.js.map +0 -1
- package/dist/domain/entities/Package.d.ts +0 -143
- package/dist/domain/entities/Package.d.ts.map +0 -1
- package/dist/domain/entities/Package.js +0 -272
- package/dist/domain/entities/Package.js.map +0 -1
- package/dist/domain/entities/Workspace.d.ts +0 -95
- package/dist/domain/entities/Workspace.d.ts.map +0 -1
- package/dist/domain/entities/Workspace.js +0 -173
- package/dist/domain/entities/Workspace.js.map +0 -1
- package/dist/domain/repositories/WorkspaceRepository.d.ts +0 -41
- package/dist/domain/repositories/WorkspaceRepository.d.ts.map +0 -1
- package/dist/domain/repositories/WorkspaceRepository.js +0 -8
- package/dist/domain/repositories/WorkspaceRepository.js.map +0 -1
- package/dist/domain/value-objects/CatalogCollection.d.ts +0 -106
- package/dist/domain/value-objects/CatalogCollection.d.ts.map +0 -1
- package/dist/domain/value-objects/CatalogCollection.js +0 -230
- package/dist/domain/value-objects/CatalogCollection.js.map +0 -1
- package/dist/domain/value-objects/PackageCollection.d.ts +0 -122
- package/dist/domain/value-objects/PackageCollection.d.ts.map +0 -1
- package/dist/domain/value-objects/PackageCollection.js +0 -263
- package/dist/domain/value-objects/PackageCollection.js.map +0 -1
- package/dist/domain/value-objects/Version.d.ts +0 -141
- package/dist/domain/value-objects/Version.d.ts.map +0 -1
- package/dist/domain/value-objects/Version.js +0 -268
- package/dist/domain/value-objects/Version.js.map +0 -1
- package/dist/domain/value-objects/WorkspaceConfig.d.ts +0 -144
- package/dist/domain/value-objects/WorkspaceConfig.d.ts.map +0 -1
- package/dist/domain/value-objects/WorkspaceConfig.js +0 -357
- package/dist/domain/value-objects/WorkspaceConfig.js.map +0 -1
- package/dist/domain/value-objects/WorkspaceId.d.ts +0 -51
- package/dist/domain/value-objects/WorkspaceId.d.ts.map +0 -1
- package/dist/domain/value-objects/WorkspaceId.js +0 -104
- package/dist/domain/value-objects/WorkspaceId.js.map +0 -1
- package/dist/domain/value-objects/WorkspacePath.d.ts +0 -75
- package/dist/domain/value-objects/WorkspacePath.d.ts.map +0 -1
- package/dist/domain/value-objects/WorkspacePath.js +0 -128
- package/dist/domain/value-objects/WorkspacePath.js.map +0 -1
- package/dist/index.d.ts +0 -25
- package/dist/index.d.ts.map +0 -1
- package/dist/infrastructure/cache/Cache.d.ts +0 -161
- package/dist/infrastructure/cache/Cache.d.ts.map +0 -1
- package/dist/infrastructure/cache/Cache.js +0 -398
- package/dist/infrastructure/cache/Cache.js.map +0 -1
- package/dist/infrastructure/cache/index.d.ts +0 -6
- package/dist/infrastructure/cache/index.d.ts.map +0 -1
- package/dist/infrastructure/cache/index.js +0 -6
- package/dist/infrastructure/cache/index.js.map +0 -1
- package/dist/infrastructure/external-services/NpmRegistryService.d.ts +0 -145
- package/dist/infrastructure/external-services/NpmRegistryService.d.ts.map +0 -1
- package/dist/infrastructure/external-services/NpmRegistryService.js +0 -466
- package/dist/infrastructure/external-services/NpmRegistryService.js.map +0 -1
- package/dist/infrastructure/file-system/FileSystemService.d.ts +0 -120
- package/dist/infrastructure/file-system/FileSystemService.d.ts.map +0 -1
- package/dist/infrastructure/file-system/FileSystemService.js +0 -663
- package/dist/infrastructure/file-system/FileSystemService.js.map +0 -1
- package/dist/infrastructure/repositories/FileWorkspaceRepository.d.ts +0 -57
- package/dist/infrastructure/repositories/FileWorkspaceRepository.d.ts.map +0 -1
- package/dist/infrastructure/repositories/FileWorkspaceRepository.js +0 -179
- package/dist/infrastructure/repositories/FileWorkspaceRepository.js.map +0 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command to perform security vulnerability scanning and automated fixes.
|
|
5
|
+
* Integrates with npm audit and snyk for comprehensive security analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from 'child_process';
|
|
9
|
+
import * as fs from 'fs-extra';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { OutputFormatter, OutputFormat } from '../formatters/outputFormatter.js';
|
|
12
|
+
import { ProgressBar } from '../formatters/progressBar.js';
|
|
13
|
+
import { StyledText, ThemeManager } from '../themes/colorTheme.js';
|
|
14
|
+
|
|
15
|
+
export interface SecurityCommandOptions {
|
|
16
|
+
workspace?: string;
|
|
17
|
+
format?: OutputFormat;
|
|
18
|
+
audit?: boolean;
|
|
19
|
+
fixVulns?: boolean;
|
|
20
|
+
severity?: 'low' | 'moderate' | 'high' | 'critical';
|
|
21
|
+
includeDev?: boolean;
|
|
22
|
+
snyk?: boolean;
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
color?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SecurityReport {
|
|
28
|
+
summary: {
|
|
29
|
+
totalVulnerabilities: number;
|
|
30
|
+
critical: number;
|
|
31
|
+
high: number;
|
|
32
|
+
moderate: number;
|
|
33
|
+
low: number;
|
|
34
|
+
info: number;
|
|
35
|
+
};
|
|
36
|
+
vulnerabilities: Vulnerability[];
|
|
37
|
+
recommendations: SecurityRecommendation[];
|
|
38
|
+
metadata: {
|
|
39
|
+
scanDate: string;
|
|
40
|
+
scanTools: string[];
|
|
41
|
+
workspacePath: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Vulnerability {
|
|
46
|
+
id: string;
|
|
47
|
+
package: string;
|
|
48
|
+
severity: 'low' | 'moderate' | 'high' | 'critical';
|
|
49
|
+
title: string;
|
|
50
|
+
url: string;
|
|
51
|
+
range: string;
|
|
52
|
+
fixAvailable: boolean | string;
|
|
53
|
+
fixVersion?: string;
|
|
54
|
+
paths: string[];
|
|
55
|
+
cwe?: string[];
|
|
56
|
+
cve?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SecurityRecommendation {
|
|
60
|
+
package: string;
|
|
61
|
+
currentVersion: string;
|
|
62
|
+
recommendedVersion: string;
|
|
63
|
+
type: 'update' | 'remove' | 'replace';
|
|
64
|
+
reason: string;
|
|
65
|
+
impact: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class SecurityCommand {
|
|
69
|
+
constructor(private readonly outputFormatter: OutputFormatter) {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute the security command
|
|
73
|
+
*/
|
|
74
|
+
async execute(options: SecurityCommandOptions = {}): Promise<void> {
|
|
75
|
+
let progressBar: ProgressBar | undefined;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Initialize theme
|
|
79
|
+
ThemeManager.setTheme('default');
|
|
80
|
+
|
|
81
|
+
// Show loading with progress bar
|
|
82
|
+
progressBar = new ProgressBar({
|
|
83
|
+
text: 'Performing security analysis...',
|
|
84
|
+
});
|
|
85
|
+
progressBar.start();
|
|
86
|
+
|
|
87
|
+
if (options.verbose) {
|
|
88
|
+
console.log(StyledText.iconAnalysis('Security vulnerability scanning'));
|
|
89
|
+
console.log(StyledText.muted(`Workspace: ${options.workspace || process.cwd()}`));
|
|
90
|
+
console.log(StyledText.muted(`Severity filter: ${options.severity || 'all'}`));
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Execute security scan
|
|
95
|
+
const report = await this.performSecurityScan(options);
|
|
96
|
+
|
|
97
|
+
progressBar.succeed('Security analysis completed');
|
|
98
|
+
|
|
99
|
+
// Format and display results
|
|
100
|
+
const formattedOutput = this.outputFormatter.formatSecurityReport(report);
|
|
101
|
+
console.log(formattedOutput);
|
|
102
|
+
|
|
103
|
+
// Show recommendations if available
|
|
104
|
+
if (report.recommendations.length > 0) {
|
|
105
|
+
this.showRecommendations(report);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Auto-fix vulnerabilities if requested
|
|
109
|
+
if (options.fixVulns) {
|
|
110
|
+
await this.autoFixVulnerabilities(report, options);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Exit with appropriate code based on findings
|
|
114
|
+
const exitCode = report.summary.critical > 0 ? 1 : 0;
|
|
115
|
+
process.exit(exitCode);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (progressBar) {
|
|
118
|
+
progressBar.fail('Security analysis failed');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.error(StyledText.iconError('Error performing security scan:'));
|
|
122
|
+
console.error(StyledText.error(String(error)));
|
|
123
|
+
|
|
124
|
+
if (options.verbose && error instanceof Error) {
|
|
125
|
+
console.error(StyledText.muted('Stack trace:'));
|
|
126
|
+
console.error(StyledText.muted(error.stack || 'No stack trace available'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Perform comprehensive security scan
|
|
135
|
+
*/
|
|
136
|
+
private async performSecurityScan(options: SecurityCommandOptions): Promise<SecurityReport> {
|
|
137
|
+
const workspacePath = options.workspace || process.cwd();
|
|
138
|
+
const vulnerabilities: Vulnerability[] = [];
|
|
139
|
+
const recommendations: SecurityRecommendation[] = [];
|
|
140
|
+
|
|
141
|
+
// Check if package.json exists
|
|
142
|
+
const packageJsonPath = path.join(workspacePath, 'package.json');
|
|
143
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
144
|
+
throw new Error(`No package.json found in ${workspacePath}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Run npm audit
|
|
148
|
+
if (options.audit !== false) {
|
|
149
|
+
const npmVulns = await this.runNpmAudit(workspacePath, options);
|
|
150
|
+
vulnerabilities.push(...npmVulns);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Run snyk scan if available
|
|
154
|
+
if (options.snyk) {
|
|
155
|
+
const snykVulns = await this.runSnykScan(workspacePath, options);
|
|
156
|
+
vulnerabilities.push(...snykVulns);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Generate recommendations
|
|
160
|
+
recommendations.push(...this.generateRecommendations(vulnerabilities));
|
|
161
|
+
|
|
162
|
+
// Filter by severity if specified
|
|
163
|
+
const filteredVulnerabilities = options.severity
|
|
164
|
+
? vulnerabilities.filter(
|
|
165
|
+
(v) => this.severityToNumber(v.severity) >= this.severityToNumber(options.severity!)
|
|
166
|
+
)
|
|
167
|
+
: vulnerabilities;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
summary: this.generateSummary(filteredVulnerabilities),
|
|
171
|
+
vulnerabilities: filteredVulnerabilities,
|
|
172
|
+
recommendations: recommendations,
|
|
173
|
+
metadata: {
|
|
174
|
+
scanDate: new Date().toISOString(),
|
|
175
|
+
scanTools: ['npm-audit', ...(options.snyk ? ['snyk'] : [])],
|
|
176
|
+
workspacePath: workspacePath,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Run npm audit scan
|
|
183
|
+
*/
|
|
184
|
+
private async runNpmAudit(
|
|
185
|
+
workspacePath: string,
|
|
186
|
+
options: SecurityCommandOptions
|
|
187
|
+
): Promise<Vulnerability[]> {
|
|
188
|
+
const auditArgs = ['audit', '--json'];
|
|
189
|
+
|
|
190
|
+
if (!options.includeDev) {
|
|
191
|
+
auditArgs.push('--omit=dev');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const result = spawnSync('npm', auditArgs, {
|
|
195
|
+
cwd: workspacePath,
|
|
196
|
+
encoding: 'utf8',
|
|
197
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (result.error) {
|
|
201
|
+
throw new Error(`npm audit failed: ${result.error.message}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (result.status === 1) {
|
|
205
|
+
// npm audit returns 1 when vulnerabilities are found
|
|
206
|
+
try {
|
|
207
|
+
const auditData = JSON.parse(result.stdout);
|
|
208
|
+
return this.parseNpmAuditResults(auditData);
|
|
209
|
+
} catch (parseError) {
|
|
210
|
+
throw new Error(`Failed to parse npm audit output: ${parseError}`);
|
|
211
|
+
}
|
|
212
|
+
} else if (result.status === 0) {
|
|
213
|
+
try {
|
|
214
|
+
const auditData = JSON.parse(result.stdout);
|
|
215
|
+
return this.parseNpmAuditResults(auditData);
|
|
216
|
+
} catch (parseError) {
|
|
217
|
+
throw new Error(`Failed to parse npm audit output: ${parseError}`);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
throw new Error(`npm audit failed with status ${result.status}: ${result.stderr}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Run snyk scan
|
|
226
|
+
*/
|
|
227
|
+
private async runSnykScan(
|
|
228
|
+
workspacePath: string,
|
|
229
|
+
options: SecurityCommandOptions
|
|
230
|
+
): Promise<Vulnerability[]> {
|
|
231
|
+
try {
|
|
232
|
+
// Check if snyk is installed
|
|
233
|
+
const versionResult = spawnSync('snyk', ['--version'], { stdio: 'pipe' });
|
|
234
|
+
if (versionResult.error) {
|
|
235
|
+
throw versionResult.error;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const snykArgs = ['test', '--json'];
|
|
239
|
+
|
|
240
|
+
if (!options.includeDev) {
|
|
241
|
+
snykArgs.push('--dev');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const result = spawnSync('snyk', snykArgs, {
|
|
245
|
+
cwd: workspacePath,
|
|
246
|
+
encoding: 'utf8',
|
|
247
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (result.error) {
|
|
251
|
+
throw result.error;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
255
|
+
throw new Error(`Snyk scan failed with status ${result.status}: ${result.stderr}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const snykData = JSON.parse(result.stdout);
|
|
259
|
+
return this.parseSnykResults(snykData);
|
|
260
|
+
} catch (error: any) {
|
|
261
|
+
if (error.code === 'ENOENT') {
|
|
262
|
+
console.warn(StyledText.iconWarning('Snyk not found. Install with: npm install -g snyk'));
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
throw new Error(`Snyk scan failed: ${error.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Parse npm audit results
|
|
271
|
+
*/
|
|
272
|
+
private parseNpmAuditResults(auditData: any): Vulnerability[] {
|
|
273
|
+
const vulnerabilities: Vulnerability[] = [];
|
|
274
|
+
|
|
275
|
+
if (!auditData.vulnerabilities) {
|
|
276
|
+
return vulnerabilities;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const [id, vuln] of Object.entries(auditData.vulnerabilities)) {
|
|
280
|
+
const vulnerability = vuln as any;
|
|
281
|
+
|
|
282
|
+
vulnerabilities.push({
|
|
283
|
+
id: id,
|
|
284
|
+
package: vulnerability.name,
|
|
285
|
+
severity: vulnerability.severity,
|
|
286
|
+
title: vulnerability.title || vulnerability.name,
|
|
287
|
+
url: vulnerability.url || `https://npmjs.com/advisories/${id}`,
|
|
288
|
+
range: vulnerability.range,
|
|
289
|
+
fixAvailable: vulnerability.fixAvailable,
|
|
290
|
+
fixVersion: vulnerability.fixAvailable === true ? vulnerability.fixAvailable : undefined,
|
|
291
|
+
paths: vulnerability.via?.map((v: any) => v.source || v.name) || [vulnerability.name],
|
|
292
|
+
cwe: vulnerability.cwe,
|
|
293
|
+
cve: vulnerability.cve,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return vulnerabilities;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Parse snyk results
|
|
302
|
+
*/
|
|
303
|
+
private parseSnykResults(snykData: any): Vulnerability[] {
|
|
304
|
+
const vulnerabilities: Vulnerability[] = [];
|
|
305
|
+
|
|
306
|
+
if (!snykData.vulnerabilities) {
|
|
307
|
+
return vulnerabilities;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const vuln of snykData.vulnerabilities) {
|
|
311
|
+
vulnerabilities.push({
|
|
312
|
+
id: vuln.id,
|
|
313
|
+
package: vuln.packageName,
|
|
314
|
+
severity: vuln.severity,
|
|
315
|
+
title: vuln.title,
|
|
316
|
+
url: vuln.url,
|
|
317
|
+
range: vuln.semver?.vulnerable?.join(' || ') || vuln.version,
|
|
318
|
+
fixAvailable: vuln.fixedIn?.length > 0,
|
|
319
|
+
fixVersion: vuln.fixedIn?.[0],
|
|
320
|
+
paths: vuln.from || [vuln.packageName],
|
|
321
|
+
cwe: vuln.identifiers?.CWE || [],
|
|
322
|
+
cve: vuln.identifiers?.CVE || [],
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return vulnerabilities;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Generate security recommendations
|
|
331
|
+
*/
|
|
332
|
+
private generateRecommendations(vulnerabilities: Vulnerability[]): SecurityRecommendation[] {
|
|
333
|
+
const recommendations: SecurityRecommendation[] = [];
|
|
334
|
+
const packages = new Set(vulnerabilities.map((v) => v.package));
|
|
335
|
+
|
|
336
|
+
for (const pkg of packages) {
|
|
337
|
+
const pkgVulns = vulnerabilities.filter((v) => v.package === pkg);
|
|
338
|
+
const criticalVulns = pkgVulns.filter(
|
|
339
|
+
(v) => v.severity === 'critical' || v.severity === 'high'
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (criticalVulns.length > 0) {
|
|
343
|
+
const fixVersions = [
|
|
344
|
+
...new Set(
|
|
345
|
+
criticalVulns.map((v) => v.fixVersion).filter((v) => v && typeof v === 'string')
|
|
346
|
+
),
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
if (fixVersions.length > 0) {
|
|
350
|
+
const currentVersion = pkgVulns[0]?.range?.split(' ')[0] || 'unknown';
|
|
351
|
+
const recommendedVersion = fixVersions[0] || 'unknown';
|
|
352
|
+
|
|
353
|
+
recommendations.push({
|
|
354
|
+
package: pkg,
|
|
355
|
+
currentVersion: currentVersion,
|
|
356
|
+
recommendedVersion: recommendedVersion,
|
|
357
|
+
type: 'update',
|
|
358
|
+
reason: `${criticalVulns.length} critical vulnerabilities found`,
|
|
359
|
+
impact: 'High - Security vulnerability fix',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return recommendations;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Generate summary from vulnerabilities
|
|
370
|
+
*/
|
|
371
|
+
private generateSummary(vulnerabilities: Vulnerability[]): SecurityReport['summary'] {
|
|
372
|
+
const summary = {
|
|
373
|
+
totalVulnerabilities: vulnerabilities.length,
|
|
374
|
+
critical: 0,
|
|
375
|
+
high: 0,
|
|
376
|
+
moderate: 0,
|
|
377
|
+
low: 0,
|
|
378
|
+
info: 0,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
for (const vuln of vulnerabilities) {
|
|
382
|
+
const severity = vuln.severity as string;
|
|
383
|
+
switch (severity) {
|
|
384
|
+
case 'critical':
|
|
385
|
+
summary.critical++;
|
|
386
|
+
break;
|
|
387
|
+
case 'high':
|
|
388
|
+
summary.high++;
|
|
389
|
+
break;
|
|
390
|
+
case 'moderate':
|
|
391
|
+
summary.moderate++;
|
|
392
|
+
break;
|
|
393
|
+
case 'low':
|
|
394
|
+
summary.low++;
|
|
395
|
+
break;
|
|
396
|
+
case 'info':
|
|
397
|
+
summary.info++;
|
|
398
|
+
break;
|
|
399
|
+
default:
|
|
400
|
+
summary.info++;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return summary;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Convert severity string to number for filtering
|
|
410
|
+
*/
|
|
411
|
+
private severityToNumber(severity: string): number {
|
|
412
|
+
switch (severity) {
|
|
413
|
+
case 'critical':
|
|
414
|
+
return 4;
|
|
415
|
+
case 'high':
|
|
416
|
+
return 3;
|
|
417
|
+
case 'moderate':
|
|
418
|
+
return 2;
|
|
419
|
+
case 'low':
|
|
420
|
+
return 1;
|
|
421
|
+
case 'info':
|
|
422
|
+
return 0;
|
|
423
|
+
default:
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Show security recommendations
|
|
430
|
+
*/
|
|
431
|
+
private showRecommendations(report: SecurityReport): void {
|
|
432
|
+
if (report.recommendations.length === 0) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
console.log('\n' + StyledText.iconInfo('Security Recommendations:'));
|
|
437
|
+
|
|
438
|
+
for (const rec of report.recommendations) {
|
|
439
|
+
console.log(
|
|
440
|
+
` ${StyledText.iconWarning()} ${rec.package}: ${rec.currentVersion} → ${rec.recommendedVersion}`
|
|
441
|
+
);
|
|
442
|
+
console.log(` ${StyledText.muted(rec.reason)}`);
|
|
443
|
+
console.log(` ${StyledText.muted(rec.impact)}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
console.log('');
|
|
447
|
+
console.log(StyledText.iconUpdate('Run with --fix-vulns to apply automatic fixes'));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Auto-fix vulnerabilities
|
|
452
|
+
*/
|
|
453
|
+
private async autoFixVulnerabilities(
|
|
454
|
+
report: SecurityReport,
|
|
455
|
+
options: SecurityCommandOptions
|
|
456
|
+
): Promise<void> {
|
|
457
|
+
if (report.recommendations.length === 0) {
|
|
458
|
+
console.log(StyledText.iconSuccess('No security fixes available'));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log('\n' + StyledText.iconUpdate('Applying security fixes...'));
|
|
463
|
+
|
|
464
|
+
const workspacePath = options.workspace || process.cwd();
|
|
465
|
+
const fixableVulns = report.recommendations.filter((r) => r.type === 'update');
|
|
466
|
+
|
|
467
|
+
if (fixableVulns.length === 0) {
|
|
468
|
+
console.log(StyledText.iconInfo('No automatic fixes available'));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
// Run npm audit fix
|
|
474
|
+
const fixArgs = ['audit', 'fix'];
|
|
475
|
+
if (!options.includeDev) {
|
|
476
|
+
fixArgs.push('--omit=dev');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const result = spawnSync('npm', fixArgs, {
|
|
480
|
+
cwd: workspacePath,
|
|
481
|
+
encoding: 'utf8',
|
|
482
|
+
stdio: 'inherit',
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (result.error) {
|
|
486
|
+
throw result.error;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (result.status !== 0) {
|
|
490
|
+
throw new Error(`npm audit fix failed with status ${result.status}`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(StyledText.iconSuccess('Security fixes applied successfully'));
|
|
494
|
+
|
|
495
|
+
// Re-run scan to verify fixes
|
|
496
|
+
console.log(StyledText.iconInfo('Re-running security scan to verify fixes...'));
|
|
497
|
+
const newReport = await this.performSecurityScan({ ...options, fixVulns: false });
|
|
498
|
+
|
|
499
|
+
if (newReport.summary.critical === 0 && newReport.summary.high === 0) {
|
|
500
|
+
console.log(
|
|
501
|
+
StyledText.iconSuccess('All critical and high severity vulnerabilities have been fixed!')
|
|
502
|
+
);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(
|
|
505
|
+
StyledText.iconWarning(
|
|
506
|
+
`${newReport.summary.critical} critical and ${newReport.summary.high} high severity vulnerabilities remain`
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
} catch (error: any) {
|
|
511
|
+
console.error(StyledText.iconError('Failed to apply security fixes:'));
|
|
512
|
+
console.error(StyledText.error(error.message));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Validate command options
|
|
518
|
+
*/
|
|
519
|
+
static validateOptions(options: SecurityCommandOptions): string[] {
|
|
520
|
+
const errors: string[] = [];
|
|
521
|
+
|
|
522
|
+
// Validate format
|
|
523
|
+
if (options.format && !['table', 'json', 'yaml', 'minimal'].includes(options.format)) {
|
|
524
|
+
errors.push('Invalid format. Must be one of: table, json, yaml, minimal');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Validate severity
|
|
528
|
+
if (options.severity && !['low', 'moderate', 'high', 'critical'].includes(options.severity)) {
|
|
529
|
+
errors.push('Invalid severity. Must be one of: low, moderate, high, critical');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return errors;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Get command help text
|
|
537
|
+
*/
|
|
538
|
+
static getHelpText(): string {
|
|
539
|
+
return `
|
|
540
|
+
Security vulnerability scanning and automated fixes
|
|
541
|
+
|
|
542
|
+
Usage:
|
|
543
|
+
pcu security [options]
|
|
544
|
+
|
|
545
|
+
Options:
|
|
546
|
+
--workspace <path> Workspace directory (default: current directory)
|
|
547
|
+
--format <type> Output format: table, json, yaml, minimal (default: table)
|
|
548
|
+
--audit Perform npm audit scan (default: true)
|
|
549
|
+
--fix-vulns Automatically fix vulnerabilities
|
|
550
|
+
--severity <level> Filter by severity: low, moderate, high, critical
|
|
551
|
+
--include-dev Include dev dependencies in scan
|
|
552
|
+
--snyk Include Snyk scan (requires snyk CLI)
|
|
553
|
+
--verbose Show detailed information
|
|
554
|
+
--no-color Disable colored output
|
|
555
|
+
|
|
556
|
+
Examples:
|
|
557
|
+
pcu security # Basic security scan
|
|
558
|
+
pcu security --fix-vulns # Scan and fix vulnerabilities
|
|
559
|
+
pcu security --severity high # Show only high severity issues
|
|
560
|
+
pcu security --snyk # Include Snyk scan
|
|
561
|
+
pcu security --format json # Output as JSON
|
|
562
|
+
|
|
563
|
+
Exit Codes:
|
|
564
|
+
0 No vulnerabilities found
|
|
565
|
+
1 Vulnerabilities found
|
|
566
|
+
2 Error occurred
|
|
567
|
+
`;
|
|
568
|
+
}
|
|
569
|
+
}
|