secmanifest 1.0.0
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/README.md +308 -0
- package/package.json +54 -0
- package/src/cli.ts +138 -0
- package/src/commands/audit.ts +249 -0
- package/src/commands/fix.ts +88 -0
- package/src/commands/watch.ts +40 -0
- package/src/core/fixer.ts +89 -0
- package/src/core/html-report.ts +259 -0
- package/src/core/notify.ts +153 -0
- package/src/core/package-manager.ts +85 -0
- package/src/core/project-analyzer.ts +84 -0
- package/src/core/reporter.ts +170 -0
- package/src/i18n/index.ts +256 -0
- package/src/scanners/backdoors.ts +192 -0
- package/src/scanners/binaries.ts +102 -0
- package/src/scanners/bundle-size.ts +114 -0
- package/src/scanners/duplicates.ts +116 -0
- package/src/scanners/integrity.ts +108 -0
- package/src/scanners/licenses.ts +111 -0
- package/src/scanners/lockfile-drift.ts +182 -0
- package/src/scanners/malware.ts +148 -0
- package/src/scanners/metadata.ts +148 -0
- package/src/scanners/node-version.ts +71 -0
- package/src/scanners/obfuscation.ts +151 -0
- package/src/scanners/outdated.ts +76 -0
- package/src/scanners/secrets.ts +224 -0
- package/src/scanners/socket-dev.ts +140 -0
- package/src/scanners/transitive.ts +97 -0
- package/src/scanners/vulnerabilities.ts +63 -0
- package/src/utils/cache.ts +59 -0
- package/src/utils/http.ts +134 -0
- package/src/utils/registry.ts +170 -0
- package/src/utils/types.ts +67 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { confirm } from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { t } from "../i18n/index.js";
|
|
6
|
+
import { detectPackageManager } from "../core/package-manager.js";
|
|
7
|
+
import {
|
|
8
|
+
analyzeProject,
|
|
9
|
+
getInstalledPackages,
|
|
10
|
+
} from "../core/project-analyzer.js";
|
|
11
|
+
import { calculateScore, renderReport } from "../core/reporter.js";
|
|
12
|
+
import { scanVulnerabilities } from "../scanners/vulnerabilities.js";
|
|
13
|
+
import { scanMalware } from "../scanners/malware.js";
|
|
14
|
+
import { scanBackdoors } from "../scanners/backdoors.js";
|
|
15
|
+
import { scanLockfileDrift } from "../scanners/lockfile-drift.js";
|
|
16
|
+
import { scanSecrets } from "../scanners/secrets.js";
|
|
17
|
+
import { scanLicenses } from "../scanners/licenses.js";
|
|
18
|
+
import { scanOutdated } from "../scanners/outdated.js";
|
|
19
|
+
import { scanMetadata } from "../scanners/metadata.js";
|
|
20
|
+
import { scanObfuscation } from "../scanners/obfuscation.js";
|
|
21
|
+
import { scanNodeVersion } from "../scanners/node-version.js";
|
|
22
|
+
import { scanSocketDev } from "../scanners/socket-dev.js";
|
|
23
|
+
import { scanTransitive } from "../scanners/transitive.js";
|
|
24
|
+
import { scanBinaries } from "../scanners/binaries.js";
|
|
25
|
+
import { scanDuplicates } from "../scanners/duplicates.js";
|
|
26
|
+
import { scanBundleSize } from "../scanners/bundle-size.js";
|
|
27
|
+
import { scanIntegrity } from "../scanners/integrity.js";
|
|
28
|
+
import { fixMultiplePackages, printFixResults } from "../core/fixer.js";
|
|
29
|
+
import { sendNotifications } from "../core/notify.js";
|
|
30
|
+
import { saveHtmlReport } from "../core/html-report.js";
|
|
31
|
+
import type { AuditReport, Finding } from "../utils/types.js";
|
|
32
|
+
|
|
33
|
+
interface AuditOptions {
|
|
34
|
+
json?: boolean;
|
|
35
|
+
verbose?: boolean;
|
|
36
|
+
skipMalware?: boolean;
|
|
37
|
+
skipSecrets?: boolean;
|
|
38
|
+
skipLicenses?: boolean;
|
|
39
|
+
skipOutdated?: boolean;
|
|
40
|
+
skipMetadata?: boolean;
|
|
41
|
+
skipObfuscation?: boolean;
|
|
42
|
+
skipNodeVersion?: boolean;
|
|
43
|
+
skipSocket?: boolean;
|
|
44
|
+
skipTransitive?: boolean;
|
|
45
|
+
skipBinaries?: boolean;
|
|
46
|
+
skipDuplicates?: boolean;
|
|
47
|
+
skipBundleSize?: boolean;
|
|
48
|
+
skipIntegrity?: boolean;
|
|
49
|
+
quick?: boolean;
|
|
50
|
+
autoFix?: boolean;
|
|
51
|
+
html?: string | boolean;
|
|
52
|
+
notify?: boolean;
|
|
53
|
+
lang?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logStep(current: number, total: number, message: string): void {
|
|
57
|
+
const pct = Math.round((current / total) * 100);
|
|
58
|
+
const barLen = 20;
|
|
59
|
+
const filled = Math.round((pct / 100) * barLen);
|
|
60
|
+
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
61
|
+
console.log(
|
|
62
|
+
chalk.gray(` [${bar}] ${pct}% `) + chalk.white(message),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function runAudit(
|
|
67
|
+
targetPath: string | undefined,
|
|
68
|
+
options: AuditOptions,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const projectPath = resolve(targetPath ?? ".");
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await stat(projectPath);
|
|
74
|
+
} catch {
|
|
75
|
+
console.error(
|
|
76
|
+
chalk.red(`${t("error.dir_not_found")} ${projectPath}`),
|
|
77
|
+
);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await stat(`${projectPath}/package.json`);
|
|
83
|
+
} catch {
|
|
84
|
+
console.error(
|
|
85
|
+
chalk.red(`${t("error.no_package_json")} ${projectPath}`),
|
|
86
|
+
);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(chalk.gray(`\n ${t("audit.detecting_pm")}`));
|
|
91
|
+
const packageManager = await detectPackageManager();
|
|
92
|
+
|
|
93
|
+
if (packageManager.blocked) {
|
|
94
|
+
if (packageManager.name === "npm") {
|
|
95
|
+
console.log(chalk.red.bold(`\n ${t("audit.veto_npm")}`));
|
|
96
|
+
console.log(chalk.yellow(` ${t("audit.veto_npm_reason")}`));
|
|
97
|
+
console.log(chalk.yellow(` ${t("audit.veto_npm_required")}\n`));
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.yellow.bold(`\n ${t("audit.no_pm_warning")}`));
|
|
100
|
+
console.log(chalk.yellow(` ${t("audit.no_pm_reason")}\n`));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const shouldContinue = await confirm({
|
|
104
|
+
message: t("audit.continue_anyway"),
|
|
105
|
+
initialValue: false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (shouldContinue !== true) {
|
|
109
|
+
console.log(chalk.gray(`\n ${t("audit.cancelled")}`));
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
console.log();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (packageManager.name && !packageManager.blocked) {
|
|
116
|
+
console.log(
|
|
117
|
+
chalk.green(
|
|
118
|
+
` ${t("audit.pm_detected")} ${packageManager.name} ${packageManager.version ? `v${packageManager.version}` : ""}`,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(chalk.gray(` ${t("audit.analyzing")}`));
|
|
124
|
+
|
|
125
|
+
let projectInfo;
|
|
126
|
+
try {
|
|
127
|
+
projectInfo = await analyzeProject(projectPath, packageManager);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(
|
|
130
|
+
chalk.red(`${t("error.analyze_failed")} ${error instanceof Error ? error.message : String(error)}`),
|
|
131
|
+
);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(chalk.gray(` ${t("audit.project")} ${projectInfo.name}@${projectInfo.version}`));
|
|
136
|
+
|
|
137
|
+
const installedPackages = await getInstalledPackages(projectPath, packageManager);
|
|
138
|
+
|
|
139
|
+
console.log(chalk.gray(` ${t("audit.packages_found")} ${installedPackages.length}`));
|
|
140
|
+
|
|
141
|
+
const allFindings: Finding[] = [];
|
|
142
|
+
const allErrors: Error[] = [];
|
|
143
|
+
const scannerStartTime = Date.now();
|
|
144
|
+
|
|
145
|
+
const scanners: Array<{
|
|
146
|
+
name: string;
|
|
147
|
+
fn: () => Promise<{ findings: Finding[]; errors: Error[]; duration: number }> | { findings: Finding[]; errors: Error[]; duration: number };
|
|
148
|
+
skip?: boolean;
|
|
149
|
+
}> = [
|
|
150
|
+
{ name: t("scanner.malware"), fn: () => Promise.resolve(scanMalware(installedPackages)), skip: options.skipMalware },
|
|
151
|
+
{ name: t("scanner.backdoors"), fn: () => Promise.resolve(scanBackdoors(projectInfo)) },
|
|
152
|
+
{ name: t("scanner.drift"), fn: () => scanLockfileDrift(projectInfo) },
|
|
153
|
+
{ name: t("scanner.secrets"), fn: () => scanSecrets(projectPath), skip: options.skipSecrets },
|
|
154
|
+
{ name: t("scanner.licenses"), fn: () => Promise.resolve(scanLicenses(projectInfo)), skip: options.skipLicenses },
|
|
155
|
+
{ name: t("scanner.metadata"), fn: () => scanMetadata(projectInfo, projectPath), skip: options.skipMetadata },
|
|
156
|
+
{ name: t("scanner.obfuscation"), fn: () => scanObfuscation(projectInfo, projectPath), skip: options.skipObfuscation },
|
|
157
|
+
{ name: t("scanner.node_version"), fn: () => scanNodeVersion(projectInfo, projectPath), skip: options.skipNodeVersion },
|
|
158
|
+
{ name: t("scanner.socket_dev"), fn: () => scanSocketDev(installedPackages), skip: options.skipSocket || options.quick },
|
|
159
|
+
{ name: t("scanner.transitive"), fn: () => scanTransitive(projectInfo, projectPath), skip: options.skipTransitive },
|
|
160
|
+
{ name: t("scanner.binaries"), fn: () => scanBinaries(projectInfo, projectPath), skip: options.skipBinaries },
|
|
161
|
+
{ name: t("scanner.duplicates"), fn: () => scanDuplicates(projectInfo, projectPath), skip: options.skipDuplicates },
|
|
162
|
+
{ name: t("scanner.bundle_size"), fn: () => scanBundleSize(projectInfo, projectPath), skip: options.skipBundleSize },
|
|
163
|
+
{ name: t("scanner.integrity"), fn: () => scanIntegrity(projectInfo, projectPath), skip: options.skipIntegrity },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
if (!options.quick) {
|
|
167
|
+
scanners.push({ name: t("scanner.sonatype"), fn: () => scanVulnerabilities(installedPackages) });
|
|
168
|
+
scanners.push({ name: t("scanner.outdated"), fn: () => scanOutdated(projectInfo, projectPath, packageManager.command || "bun"), skip: options.skipOutdated });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const activeScanners = scanners.filter((s) => !s.skip);
|
|
172
|
+
const totalSteps = activeScanners.length;
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < activeScanners.length; i++) {
|
|
175
|
+
const scanner = activeScanners[i]!;
|
|
176
|
+
logStep(i + 1, totalSteps, scanner.name);
|
|
177
|
+
try {
|
|
178
|
+
const result = await scanner.fn();
|
|
179
|
+
allFindings.push(...result.findings);
|
|
180
|
+
allErrors.push(...result.errors);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
allErrors.push(new Error(`Error in scanner "${scanner.name}": ${error instanceof Error ? error.message : String(error)}`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const totalDuration = Date.now() - scannerStartTime;
|
|
187
|
+
|
|
188
|
+
if (allErrors.length > 0 && options.verbose) {
|
|
189
|
+
console.log(chalk.yellow(`\n ${t("audit.errors_during_scan")}`));
|
|
190
|
+
for (const error of allErrors) {
|
|
191
|
+
console.log(chalk.yellow(` - ${error.message}`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const score = calculateScore(allFindings);
|
|
196
|
+
|
|
197
|
+
const report: AuditReport = {
|
|
198
|
+
projectPath,
|
|
199
|
+
projectName: projectInfo.name,
|
|
200
|
+
packageManager,
|
|
201
|
+
totalPackages: installedPackages.length,
|
|
202
|
+
findings: allFindings,
|
|
203
|
+
score,
|
|
204
|
+
duration: totalDuration,
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (options.json) {
|
|
209
|
+
console.log(JSON.stringify(report, null, 2));
|
|
210
|
+
} else {
|
|
211
|
+
renderReport(report);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.html) {
|
|
215
|
+
const htmlPath = typeof options.html === "string" ? options.html : undefined;
|
|
216
|
+
const savedPath = await saveHtmlReport(report, htmlPath);
|
|
217
|
+
console.log(chalk.green(`\n HTML report saved to: ${savedPath}`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (options.notify) {
|
|
221
|
+
await sendNotifications(report);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!options.json && allFindings.length > 0) {
|
|
225
|
+
const fixablePackages = allFindings
|
|
226
|
+
.filter((f) => f.package && f.version && (f.id.startsWith("backdoor-latest-version-") || f.id.startsWith("backdoor-open-version-") || f.severity === "critical"))
|
|
227
|
+
.map((f) => ({ name: f.package!, version: f.version!, reason: f.title }));
|
|
228
|
+
|
|
229
|
+
const uniqueFixable = fixablePackages.filter((pkg, index, self) => index === self.findIndex((p) => p.name === pkg.name));
|
|
230
|
+
|
|
231
|
+
if (uniqueFixable.length > 0) {
|
|
232
|
+
console.log(chalk.yellow(`\n ${uniqueFixable.length} package(s) can be auto-fixed:`));
|
|
233
|
+
for (const pkg of uniqueFixable) {
|
|
234
|
+
console.log(chalk.yellow(` - ${pkg.name}@${pkg.version}: ${pkg.reason}`));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!options.autoFix) {
|
|
238
|
+
const shouldFix = await confirm({ message: "Auto-fix these packages?", initialValue: false });
|
|
239
|
+
if (shouldFix === true) {
|
|
240
|
+
const results = await fixMultiplePackages(uniqueFixable, packageManager, projectPath);
|
|
241
|
+
printFixResults(results);
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
const results = await fixMultiplePackages(uniqueFixable, packageManager, projectPath);
|
|
245
|
+
printFixResults(results);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { t } from "../i18n/index.js";
|
|
5
|
+
import { detectPackageManager } from "../core/package-manager.js";
|
|
6
|
+
import { analyzeProject, getInstalledPackages } from "../core/project-analyzer.js";
|
|
7
|
+
import { fixMultiplePackages, printFixResults } from "../core/fixer.js";
|
|
8
|
+
import { findSafeVersion } from "../utils/registry.js";
|
|
9
|
+
|
|
10
|
+
interface FixOptions {
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
lang?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function runFix(packages: string[], options: FixOptions): Promise<void> {
|
|
16
|
+
const projectPath = resolve(".");
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await stat(`${projectPath}/package.json`);
|
|
20
|
+
} catch {
|
|
21
|
+
console.error(chalk.red(`${t("error.no_package_json")} ${projectPath}`));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.gray(`\n ${t("audit.detecting_pm")}`));
|
|
26
|
+
const packageManager = await detectPackageManager();
|
|
27
|
+
|
|
28
|
+
if (packageManager.blocked) {
|
|
29
|
+
console.error(chalk.red(`Error: A secure package manager is required (pnpm, bun, yarn).`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.green(` ${t("audit.pm_detected")} ${packageManager.name} ${packageManager.version ? `v${packageManager.version}` : ""}`));
|
|
34
|
+
|
|
35
|
+
let projectInfo;
|
|
36
|
+
try {
|
|
37
|
+
projectInfo = await analyzeProject(projectPath, packageManager);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(chalk.red(`${t("error.analyze_failed")} ${error instanceof Error ? error.message : String(error)}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const installedPackages = await getInstalledPackages(projectPath, packageManager);
|
|
44
|
+
|
|
45
|
+
let targetPackages: Array<{ name: string; version: string; reason: string }>;
|
|
46
|
+
|
|
47
|
+
if (packages.length > 0) {
|
|
48
|
+
targetPackages = [];
|
|
49
|
+
for (const pkgName of packages) {
|
|
50
|
+
const found = installedPackages.find((p) => p.name === pkgName);
|
|
51
|
+
if (found) {
|
|
52
|
+
targetPackages.push({ name: found.name, version: found.version, reason: "User specified" });
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.yellow(` Package not found: ${pkgName}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log(chalk.gray(`\n ${t("fix.analyzing_packages")}`));
|
|
59
|
+
const allDeps = { ...projectInfo.dependencies, ...projectInfo.devDependencies };
|
|
60
|
+
targetPackages = [];
|
|
61
|
+
|
|
62
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
63
|
+
if (version === "latest") { targetPackages.push({ name, version, reason: 'Uses "latest"' }); continue; }
|
|
64
|
+
if (version === "*" || version === ">=0.0.0" || version === ">=0") { targetPackages.push({ name, version, reason: "Open version range" }); continue; }
|
|
65
|
+
const safeInfo = await findSafeVersion(name, version);
|
|
66
|
+
if (safeInfo.safeVersion) targetPackages.push({ name, version, reason: safeInfo.reason });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (targetPackages.length === 0) {
|
|
71
|
+
console.log(chalk.green(`\n ${t("fix.no_packages")}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(chalk.yellow(`\n ${t("fix.packages_to_fix")} ${targetPackages.length}`));
|
|
76
|
+
for (const pkg of targetPackages) console.log(chalk.yellow(` - ${pkg.name}@${pkg.version}: ${pkg.reason}`));
|
|
77
|
+
|
|
78
|
+
if (options.dryRun) {
|
|
79
|
+
console.log(chalk.cyan(`\n ${t("fix.dry_run")}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const results = await fixMultiplePackages(targetPackages, packageManager, projectPath);
|
|
84
|
+
printFixResults(results);
|
|
85
|
+
|
|
86
|
+
const fixedCount = results.filter((r) => r.success).length;
|
|
87
|
+
if (fixedCount > 0) console.log(chalk.green(`\n ${fixedCount} ${t("fix.packages_fixed")} ${t("fix.run_audit")}`));
|
|
88
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { t } from "../i18n/index.js";
|
|
4
|
+
import { runAudit } from "./audit.js";
|
|
5
|
+
|
|
6
|
+
interface WatchOptions {
|
|
7
|
+
interval?: number;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
quick?: boolean;
|
|
10
|
+
lang?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function runWatch(targetPath: string | undefined, options: WatchOptions): Promise<void> {
|
|
14
|
+
const projectPath = resolve(targetPath ?? ".");
|
|
15
|
+
const intervalMs = (options.interval ?? 300) * 1000;
|
|
16
|
+
|
|
17
|
+
console.log(chalk.bold.cyan(`\n ${t("watch.title")}`));
|
|
18
|
+
console.log(chalk.gray(` ${t("watch.directory")} ${projectPath}`));
|
|
19
|
+
console.log(chalk.gray(` ${t("watch.interval")} ${intervalMs / 1000}s`));
|
|
20
|
+
console.log(chalk.gray(` ${t("watch.press_ctrlc")}\n`));
|
|
21
|
+
|
|
22
|
+
let scanCount = 0;
|
|
23
|
+
|
|
24
|
+
const runScan = async () => {
|
|
25
|
+
scanCount++;
|
|
26
|
+
console.log(chalk.bold(`\n [Scan #${scanCount}] ${new Date().toLocaleTimeString()}`));
|
|
27
|
+
try {
|
|
28
|
+
await runAudit(targetPath, { ...options, json: false });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(chalk.red(`Error in scan: ${error instanceof Error ? error.message : String(error)}`));
|
|
31
|
+
}
|
|
32
|
+
console.log(chalk.gray(`\n ${t("watch.next_scan")} ${intervalMs / 1000}s...`));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await runScan();
|
|
36
|
+
const interval = setInterval(async () => { await runScan(); }, intervalMs);
|
|
37
|
+
|
|
38
|
+
process.on("SIGINT", () => { clearInterval(interval); console.log(chalk.gray(`\n\n ${t("watch.stopping")}`)); process.exit(0); });
|
|
39
|
+
process.on("SIGTERM", () => { clearInterval(interval); process.exit(0); });
|
|
40
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { $ } from "bun";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { t } from "../i18n/index.js";
|
|
4
|
+
import type { PackageManager } from "../utils/types.js";
|
|
5
|
+
import { findSafeVersion, type SafeVersionResult } from "../utils/registry.js";
|
|
6
|
+
|
|
7
|
+
export interface FixResult {
|
|
8
|
+
packageName: string;
|
|
9
|
+
success: boolean;
|
|
10
|
+
previousVersion: string;
|
|
11
|
+
newVersion: string | null;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function fixPackage(
|
|
16
|
+
packageName: string,
|
|
17
|
+
currentVersion: string,
|
|
18
|
+
packageManager: PackageManager,
|
|
19
|
+
projectPath: string,
|
|
20
|
+
): Promise<FixResult> {
|
|
21
|
+
console.log(chalk.gray(` ${t("fix.searching_safe")} ${packageName}@${currentVersion}...`));
|
|
22
|
+
|
|
23
|
+
const safeInfo = await findSafeVersion(packageName, currentVersion);
|
|
24
|
+
|
|
25
|
+
if (!safeInfo.safeVersion) {
|
|
26
|
+
return { packageName, success: false, previousVersion: currentVersion, newVersion: null, error: `No safe alternative found. ${safeInfo.reason}` };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(chalk.yellow(` ${t("fix.unsafe_version")} ${currentVersion} -> ${t("fix.safe_version")} ${safeInfo.safeVersion}`));
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
console.log(chalk.gray(` ${t("fix.uninstalling")} ${packageName}@${currentVersion}...`));
|
|
33
|
+
const pm = packageManager.command || "bun";
|
|
34
|
+
await $`${pm} remove ${packageName}`.cwd(projectPath).quiet();
|
|
35
|
+
|
|
36
|
+
const versionSpec = `^${safeInfo.safeVersion}`;
|
|
37
|
+
console.log(chalk.gray(` ${t("fix.installing")} ${packageName}@${versionSpec}...`));
|
|
38
|
+
await $`${pm} add ${packageName}@${versionSpec}`.cwd(projectPath).quiet();
|
|
39
|
+
|
|
40
|
+
console.log(chalk.green(` ${packageName} ${t("fix.installed_successfully")} (${safeInfo.safeVersion})`));
|
|
41
|
+
return { packageName, success: true, previousVersion: currentVersion, newVersion: safeInfo.safeVersion };
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
44
|
+
console.log(chalk.red(` ${t("fix.install_error")} ${packageName}: ${msg}`));
|
|
45
|
+
try {
|
|
46
|
+
console.log(chalk.gray(` ${t("fix.restoring")}`));
|
|
47
|
+
const pm = packageManager.command || "bun";
|
|
48
|
+
await $`${pm} add ${packageName}@${currentVersion}`.cwd(projectPath).quiet();
|
|
49
|
+
} catch { /* Restore failed */ }
|
|
50
|
+
return { packageName, success: false, previousVersion: currentVersion, newVersion: null, error: msg };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function fixMultiplePackages(
|
|
55
|
+
packages: Array<{ name: string; version: string; reason: string }>,
|
|
56
|
+
packageManager: PackageManager,
|
|
57
|
+
projectPath: string,
|
|
58
|
+
): Promise<FixResult[]> {
|
|
59
|
+
const results: FixResult[] = [];
|
|
60
|
+
for (const pkg of packages) {
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.bold(` ${t("fix.packages_to_fix")} ${pkg.name}@${pkg.version}...`));
|
|
63
|
+
console.log(chalk.gray(` Razon: ${pkg.reason}`));
|
|
64
|
+
const result = await fixPackage(pkg.name, pkg.version, packageManager, projectPath);
|
|
65
|
+
results.push(result);
|
|
66
|
+
}
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function printFixResults(results: FixResult[]): void {
|
|
71
|
+
console.log();
|
|
72
|
+
console.log(chalk.bold(` ${t("fix.results")}`));
|
|
73
|
+
console.log(chalk.gray(" ─────────────────────────────────────"));
|
|
74
|
+
|
|
75
|
+
const fixed = results.filter((r) => r.success);
|
|
76
|
+
const failed = results.filter((r) => !r.success);
|
|
77
|
+
|
|
78
|
+
if (fixed.length > 0) {
|
|
79
|
+
console.log(chalk.green(`\n ${fixed.length} ${t("fix.packages_fixed")}`));
|
|
80
|
+
for (const r of fixed) console.log(chalk.green(` ${r.packageName}: ${r.previousVersion} -> ${r.newVersion}`));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (failed.length > 0) {
|
|
84
|
+
console.log(chalk.red(`\n ${failed.length} ${t("fix.packages_failed")}`));
|
|
85
|
+
for (const r of failed) console.log(chalk.red(` ${r.packageName}: ${r.error ?? "Unknown error"}`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log();
|
|
89
|
+
}
|