pcu 0.5.6 → 0.6.3

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.
Files changed (219) hide show
  1. package/bin/pcu.js +1 -1
  2. package/dist/index.js +9559 -32
  3. package/dist/index.js.map +1 -1
  4. package/package.json +36 -84
  5. package/src/cli/commands/checkCommand.ts +227 -0
  6. package/src/cli/commands/initCommand.ts +394 -0
  7. package/src/cli/commands/securityCommand.ts +569 -0
  8. package/src/cli/commands/updateCommand.ts +245 -0
  9. package/src/cli/formatters/outputFormatter.ts +830 -0
  10. package/src/cli/formatters/progressBar.ts +700 -0
  11. package/src/cli/index.ts +565 -0
  12. package/src/cli/interactive/interactivePrompts.ts +517 -0
  13. package/src/cli/options/globalOptions.ts +380 -0
  14. package/src/cli/options/index.ts +5 -0
  15. package/src/cli/themes/colorTheme.ts +379 -0
  16. package/src/cli/validators/commandValidator.ts +395 -0
  17. package/src/cli/validators/index.ts +5 -0
  18. package/src/index.ts +4 -0
  19. package/README.ja.md +0 -582
  20. package/README.md +0 -690
  21. package/README.zh-CN.md +0 -630
  22. package/dist/application/services/CatalogUpdateService.d.ts +0 -209
  23. package/dist/application/services/CatalogUpdateService.d.ts.map +0 -1
  24. package/dist/application/services/CatalogUpdateService.js +0 -836
  25. package/dist/application/services/CatalogUpdateService.js.map +0 -1
  26. package/dist/application/services/WorkspaceService.d.ts +0 -139
  27. package/dist/application/services/WorkspaceService.d.ts.map +0 -1
  28. package/dist/application/services/WorkspaceService.js +0 -340
  29. package/dist/application/services/WorkspaceService.js.map +0 -1
  30. package/dist/cli/commands/CheckCommand.d.ts +0 -40
  31. package/dist/cli/commands/CheckCommand.d.ts.map +0 -1
  32. package/dist/cli/commands/CheckCommand.js +0 -177
  33. package/dist/cli/commands/CheckCommand.js.map +0 -1
  34. package/dist/cli/commands/InitCommand.d.ts +0 -53
  35. package/dist/cli/commands/InitCommand.d.ts.map +0 -1
  36. package/dist/cli/commands/InitCommand.js +0 -338
  37. package/dist/cli/commands/InitCommand.js.map +0 -1
  38. package/dist/cli/commands/SecurityCommand.d.ts +0 -113
  39. package/dist/cli/commands/SecurityCommand.d.ts.map +0 -1
  40. package/dist/cli/commands/SecurityCommand.js +0 -410
  41. package/dist/cli/commands/SecurityCommand.js.map +0 -1
  42. package/dist/cli/commands/UpdateCommand.d.ts +0 -44
  43. package/dist/cli/commands/UpdateCommand.d.ts.map +0 -1
  44. package/dist/cli/commands/UpdateCommand.js +0 -189
  45. package/dist/cli/commands/UpdateCommand.js.map +0 -1
  46. package/dist/cli/formatters/OutputFormatter.d.ts +0 -116
  47. package/dist/cli/formatters/OutputFormatter.d.ts.map +0 -1
  48. package/dist/cli/formatters/OutputFormatter.js +0 -664
  49. package/dist/cli/formatters/OutputFormatter.js.map +0 -1
  50. package/dist/cli/formatters/ProgressBar.d.ts +0 -195
  51. package/dist/cli/formatters/ProgressBar.d.ts.map +0 -1
  52. package/dist/cli/formatters/ProgressBar.js +0 -622
  53. package/dist/cli/formatters/ProgressBar.js.map +0 -1
  54. package/dist/cli/index.d.ts +0 -12
  55. package/dist/cli/index.d.ts.map +0 -1
  56. package/dist/cli/index.js +0 -492
  57. package/dist/cli/index.js.map +0 -1
  58. package/dist/cli/interactive/InteractivePrompts.d.ts +0 -85
  59. package/dist/cli/interactive/InteractivePrompts.d.ts.map +0 -1
  60. package/dist/cli/interactive/InteractivePrompts.js +0 -434
  61. package/dist/cli/interactive/InteractivePrompts.js.map +0 -1
  62. package/dist/cli/options/GlobalOptions.d.ts +0 -117
  63. package/dist/cli/options/GlobalOptions.d.ts.map +0 -1
  64. package/dist/cli/options/GlobalOptions.js +0 -278
  65. package/dist/cli/options/GlobalOptions.js.map +0 -1
  66. package/dist/cli/options/index.d.ts +0 -5
  67. package/dist/cli/options/index.d.ts.map +0 -1
  68. package/dist/cli/options/index.js +0 -5
  69. package/dist/cli/options/index.js.map +0 -1
  70. package/dist/cli/themes/ColorTheme.d.ts +0 -211
  71. package/dist/cli/themes/ColorTheme.d.ts.map +0 -1
  72. package/dist/cli/themes/ColorTheme.js +0 -267
  73. package/dist/cli/themes/ColorTheme.js.map +0 -1
  74. package/dist/cli/validators/CommandValidator.d.ts +0 -60
  75. package/dist/cli/validators/CommandValidator.d.ts.map +0 -1
  76. package/dist/cli/validators/CommandValidator.js +0 -319
  77. package/dist/cli/validators/CommandValidator.js.map +0 -1
  78. package/dist/cli/validators/index.d.ts +0 -5
  79. package/dist/cli/validators/index.d.ts.map +0 -1
  80. package/dist/cli/validators/index.js +0 -5
  81. package/dist/cli/validators/index.js.map +0 -1
  82. package/dist/common/config/Config.d.ts +0 -142
  83. package/dist/common/config/Config.d.ts.map +0 -1
  84. package/dist/common/config/Config.js +0 -382
  85. package/dist/common/config/Config.js.map +0 -1
  86. package/dist/common/config/ConfigLoader.d.ts +0 -49
  87. package/dist/common/config/ConfigLoader.d.ts.map +0 -1
  88. package/dist/common/config/ConfigLoader.js +0 -180
  89. package/dist/common/config/ConfigLoader.js.map +0 -1
  90. package/dist/common/config/PackageFilterConfig.d.ts +0 -56
  91. package/dist/common/config/PackageFilterConfig.d.ts.map +0 -1
  92. package/dist/common/config/PackageFilterConfig.js +0 -94
  93. package/dist/common/config/PackageFilterConfig.js.map +0 -1
  94. package/dist/common/config/index.d.ts +0 -8
  95. package/dist/common/config/index.d.ts.map +0 -1
  96. package/dist/common/config/index.js +0 -8
  97. package/dist/common/config/index.js.map +0 -1
  98. package/dist/common/error-handling/ErrorTracker.d.ts +0 -48
  99. package/dist/common/error-handling/ErrorTracker.d.ts.map +0 -1
  100. package/dist/common/error-handling/ErrorTracker.js +0 -93
  101. package/dist/common/error-handling/ErrorTracker.js.map +0 -1
  102. package/dist/common/error-handling/UserFriendlyErrorHandler.d.ts +0 -74
  103. package/dist/common/error-handling/UserFriendlyErrorHandler.d.ts.map +0 -1
  104. package/dist/common/error-handling/UserFriendlyErrorHandler.js +0 -703
  105. package/dist/common/error-handling/UserFriendlyErrorHandler.js.map +0 -1
  106. package/dist/common/error-handling/index.d.ts +0 -11
  107. package/dist/common/error-handling/index.d.ts.map +0 -1
  108. package/dist/common/error-handling/index.js +0 -9
  109. package/dist/common/error-handling/index.js.map +0 -1
  110. package/dist/common/logger/Logger.d.ts +0 -110
  111. package/dist/common/logger/Logger.d.ts.map +0 -1
  112. package/dist/common/logger/Logger.js +0 -289
  113. package/dist/common/logger/Logger.js.map +0 -1
  114. package/dist/common/logger/index.d.ts +0 -6
  115. package/dist/common/logger/index.d.ts.map +0 -1
  116. package/dist/common/logger/index.js +0 -6
  117. package/dist/common/logger/index.js.map +0 -1
  118. package/dist/common/types/cli.d.ts +0 -265
  119. package/dist/common/types/cli.d.ts.map +0 -1
  120. package/dist/common/types/cli.js +0 -5
  121. package/dist/common/types/cli.js.map +0 -1
  122. package/dist/common/types/core.d.ts +0 -270
  123. package/dist/common/types/core.d.ts.map +0 -1
  124. package/dist/common/types/core.js +0 -32
  125. package/dist/common/types/core.js.map +0 -1
  126. package/dist/common/types/index.d.ts +0 -8
  127. package/dist/common/types/index.d.ts.map +0 -1
  128. package/dist/common/types/index.js +0 -8
  129. package/dist/common/types/index.js.map +0 -1
  130. package/dist/common/utils/VersionChecker.d.ts +0 -54
  131. package/dist/common/utils/VersionChecker.d.ts.map +0 -1
  132. package/dist/common/utils/VersionChecker.js +0 -180
  133. package/dist/common/utils/VersionChecker.js.map +0 -1
  134. package/dist/common/utils/async.d.ts +0 -74
  135. package/dist/common/utils/async.d.ts.map +0 -1
  136. package/dist/common/utils/async.js +0 -228
  137. package/dist/common/utils/async.js.map +0 -1
  138. package/dist/common/utils/format.d.ts +0 -32
  139. package/dist/common/utils/format.d.ts.map +0 -1
  140. package/dist/common/utils/format.js +0 -121
  141. package/dist/common/utils/format.js.map +0 -1
  142. package/dist/common/utils/git.d.ts +0 -44
  143. package/dist/common/utils/git.d.ts.map +0 -1
  144. package/dist/common/utils/git.js +0 -147
  145. package/dist/common/utils/git.js.map +0 -1
  146. package/dist/common/utils/index.d.ts +0 -12
  147. package/dist/common/utils/index.d.ts.map +0 -1
  148. package/dist/common/utils/index.js +0 -12
  149. package/dist/common/utils/index.js.map +0 -1
  150. package/dist/common/utils/string.d.ts +0 -56
  151. package/dist/common/utils/string.d.ts.map +0 -1
  152. package/dist/common/utils/string.js +0 -134
  153. package/dist/common/utils/string.js.map +0 -1
  154. package/dist/common/utils/validation.d.ts +0 -88
  155. package/dist/common/utils/validation.d.ts.map +0 -1
  156. package/dist/common/utils/validation.js +0 -308
  157. package/dist/common/utils/validation.js.map +0 -1
  158. package/dist/domain/entities/Catalog.d.ts +0 -117
  159. package/dist/domain/entities/Catalog.d.ts.map +0 -1
  160. package/dist/domain/entities/Catalog.js +0 -240
  161. package/dist/domain/entities/Catalog.js.map +0 -1
  162. package/dist/domain/entities/Package.d.ts +0 -143
  163. package/dist/domain/entities/Package.d.ts.map +0 -1
  164. package/dist/domain/entities/Package.js +0 -272
  165. package/dist/domain/entities/Package.js.map +0 -1
  166. package/dist/domain/entities/Workspace.d.ts +0 -95
  167. package/dist/domain/entities/Workspace.d.ts.map +0 -1
  168. package/dist/domain/entities/Workspace.js +0 -173
  169. package/dist/domain/entities/Workspace.js.map +0 -1
  170. package/dist/domain/repositories/WorkspaceRepository.d.ts +0 -41
  171. package/dist/domain/repositories/WorkspaceRepository.d.ts.map +0 -1
  172. package/dist/domain/repositories/WorkspaceRepository.js +0 -8
  173. package/dist/domain/repositories/WorkspaceRepository.js.map +0 -1
  174. package/dist/domain/value-objects/CatalogCollection.d.ts +0 -106
  175. package/dist/domain/value-objects/CatalogCollection.d.ts.map +0 -1
  176. package/dist/domain/value-objects/CatalogCollection.js +0 -230
  177. package/dist/domain/value-objects/CatalogCollection.js.map +0 -1
  178. package/dist/domain/value-objects/PackageCollection.d.ts +0 -122
  179. package/dist/domain/value-objects/PackageCollection.d.ts.map +0 -1
  180. package/dist/domain/value-objects/PackageCollection.js +0 -263
  181. package/dist/domain/value-objects/PackageCollection.js.map +0 -1
  182. package/dist/domain/value-objects/Version.d.ts +0 -141
  183. package/dist/domain/value-objects/Version.d.ts.map +0 -1
  184. package/dist/domain/value-objects/Version.js +0 -268
  185. package/dist/domain/value-objects/Version.js.map +0 -1
  186. package/dist/domain/value-objects/WorkspaceConfig.d.ts +0 -144
  187. package/dist/domain/value-objects/WorkspaceConfig.d.ts.map +0 -1
  188. package/dist/domain/value-objects/WorkspaceConfig.js +0 -357
  189. package/dist/domain/value-objects/WorkspaceConfig.js.map +0 -1
  190. package/dist/domain/value-objects/WorkspaceId.d.ts +0 -51
  191. package/dist/domain/value-objects/WorkspaceId.d.ts.map +0 -1
  192. package/dist/domain/value-objects/WorkspaceId.js +0 -104
  193. package/dist/domain/value-objects/WorkspaceId.js.map +0 -1
  194. package/dist/domain/value-objects/WorkspacePath.d.ts +0 -75
  195. package/dist/domain/value-objects/WorkspacePath.d.ts.map +0 -1
  196. package/dist/domain/value-objects/WorkspacePath.js +0 -128
  197. package/dist/domain/value-objects/WorkspacePath.js.map +0 -1
  198. package/dist/index.d.ts +0 -25
  199. package/dist/index.d.ts.map +0 -1
  200. package/dist/infrastructure/cache/Cache.d.ts +0 -161
  201. package/dist/infrastructure/cache/Cache.d.ts.map +0 -1
  202. package/dist/infrastructure/cache/Cache.js +0 -398
  203. package/dist/infrastructure/cache/Cache.js.map +0 -1
  204. package/dist/infrastructure/cache/index.d.ts +0 -6
  205. package/dist/infrastructure/cache/index.d.ts.map +0 -1
  206. package/dist/infrastructure/cache/index.js +0 -6
  207. package/dist/infrastructure/cache/index.js.map +0 -1
  208. package/dist/infrastructure/external-services/NpmRegistryService.d.ts +0 -145
  209. package/dist/infrastructure/external-services/NpmRegistryService.d.ts.map +0 -1
  210. package/dist/infrastructure/external-services/NpmRegistryService.js +0 -466
  211. package/dist/infrastructure/external-services/NpmRegistryService.js.map +0 -1
  212. package/dist/infrastructure/file-system/FileSystemService.d.ts +0 -120
  213. package/dist/infrastructure/file-system/FileSystemService.d.ts.map +0 -1
  214. package/dist/infrastructure/file-system/FileSystemService.js +0 -663
  215. package/dist/infrastructure/file-system/FileSystemService.js.map +0 -1
  216. package/dist/infrastructure/repositories/FileWorkspaceRepository.d.ts +0 -57
  217. package/dist/infrastructure/repositories/FileWorkspaceRepository.d.ts.map +0 -1
  218. package/dist/infrastructure/repositories/FileWorkspaceRepository.js +0 -179
  219. 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
+ }