@rainy-updates/cli 0.5.4 → 0.5.6
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/CHANGELOG.md +84 -0
- package/README.md +5 -0
- package/dist/bin/cli.js +37 -1
- package/dist/commands/audit/runner.js +43 -26
- package/dist/commands/dashboard/parser.d.ts +2 -0
- package/dist/commands/dashboard/parser.js +59 -0
- package/dist/commands/dashboard/runner.d.ts +2 -0
- package/dist/commands/dashboard/runner.js +47 -0
- package/dist/commands/doctor/parser.js +6 -0
- package/dist/commands/ga/parser.d.ts +2 -0
- package/dist/commands/ga/parser.js +50 -0
- package/dist/commands/ga/runner.d.ts +2 -0
- package/dist/commands/ga/runner.js +129 -0
- package/dist/commands/resolve/runner.js +7 -3
- package/dist/commands/review/parser.js +6 -0
- package/dist/commands/review/runner.js +4 -3
- package/dist/core/analysis-bundle.d.ts +4 -0
- package/dist/core/analysis-bundle.js +241 -0
- package/dist/core/artifacts.d.ts +3 -0
- package/dist/core/artifacts.js +48 -0
- package/dist/core/check.js +6 -1
- package/dist/core/options.d.ts +7 -1
- package/dist/core/options.js +14 -0
- package/dist/core/review-model.js +51 -177
- package/dist/core/summary.js +13 -0
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +8 -0
- package/dist/output/sarif.js +12 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/ui/dashboard/DashboardTUI.d.ts +6 -0
- package/dist/ui/dashboard/DashboardTUI.js +34 -0
- package/dist/ui/dashboard/components/DetailPanel.d.ts +4 -0
- package/dist/ui/dashboard/components/DetailPanel.js +30 -0
- package/dist/ui/dashboard/components/Footer.d.ts +4 -0
- package/dist/ui/dashboard/components/Footer.js +9 -0
- package/dist/ui/dashboard/components/Header.d.ts +4 -0
- package/dist/ui/dashboard/components/Header.js +12 -0
- package/dist/ui/dashboard/components/Sidebar.d.ts +4 -0
- package/dist/ui/dashboard/components/Sidebar.js +23 -0
- package/dist/ui/dashboard/store.d.ts +34 -0
- package/dist/ui/dashboard/store.js +148 -0
- package/dist/ui/tui.d.ts +2 -2
- package/dist/ui/tui.js +310 -79
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { VersionCache } from "../../cache/cache.js";
|
|
5
|
+
import { detectPackageManager } from "../../pm/detect.js";
|
|
6
|
+
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
7
|
+
import { stableStringify } from "../../utils/stable-json.js";
|
|
8
|
+
import { writeFileAtomic } from "../../utils/io.js";
|
|
9
|
+
export async function runGa(options) {
|
|
10
|
+
const packageManager = await detectPackageManager(options.cwd);
|
|
11
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
|
|
12
|
+
const cache = await VersionCache.create();
|
|
13
|
+
const checks = [];
|
|
14
|
+
checks.push({
|
|
15
|
+
name: "package-manager",
|
|
16
|
+
status: packageManager === "unknown" ? "warn" : "pass",
|
|
17
|
+
detail: packageManager === "unknown"
|
|
18
|
+
? "No supported lockfile was detected. npm-compatible execution is still possible."
|
|
19
|
+
: `Detected package manager: ${packageManager}.`,
|
|
20
|
+
});
|
|
21
|
+
checks.push({
|
|
22
|
+
name: "workspace-discovery",
|
|
23
|
+
status: packageDirs.length > 0 ? "pass" : "fail",
|
|
24
|
+
detail: `Discovered ${packageDirs.length} package manifest path(s).`,
|
|
25
|
+
});
|
|
26
|
+
const lockfileCheck = await detectLockfile(options.cwd);
|
|
27
|
+
checks.push(lockfileCheck);
|
|
28
|
+
checks.push({
|
|
29
|
+
name: "cache-backend",
|
|
30
|
+
status: cache.backend === "sqlite" ? "pass" : cache.degraded ? "warn" : "pass",
|
|
31
|
+
detail: cache.backend === "sqlite"
|
|
32
|
+
? "SQLite cache backend is available."
|
|
33
|
+
: `File cache backend active${cache.fallbackReason ? ` (${cache.fallbackReason})` : "."}`,
|
|
34
|
+
});
|
|
35
|
+
const distBuildExists = await fileExists(path.resolve(options.cwd, "dist/bin/cli.js"));
|
|
36
|
+
checks.push({
|
|
37
|
+
name: "dist-build",
|
|
38
|
+
status: distBuildExists ? "pass" : "warn",
|
|
39
|
+
detail: distBuildExists
|
|
40
|
+
? "Built CLI entrypoint exists in dist/bin/cli.js."
|
|
41
|
+
: "Built CLI entrypoint is missing; run the build before publishing a release artifact.",
|
|
42
|
+
});
|
|
43
|
+
checks.push({
|
|
44
|
+
name: "benchmark-gates",
|
|
45
|
+
status: (await fileExists(path.resolve(options.cwd, "scripts/perf-smoke.mjs"))) &&
|
|
46
|
+
(await fileExists(path.resolve(options.cwd, "scripts/benchmark.mjs")))
|
|
47
|
+
? "pass"
|
|
48
|
+
: "warn",
|
|
49
|
+
detail: "Benchmark scripts and perf smoke gates were checked for release readiness.",
|
|
50
|
+
});
|
|
51
|
+
checks.push({
|
|
52
|
+
name: "docs-contract",
|
|
53
|
+
status: (await fileExists(path.resolve(options.cwd, "README.md"))) &&
|
|
54
|
+
(await fileExists(path.resolve(options.cwd, "CHANGELOG.md")))
|
|
55
|
+
? "pass"
|
|
56
|
+
: "warn",
|
|
57
|
+
detail: "README and CHANGELOG presence verified.",
|
|
58
|
+
});
|
|
59
|
+
const errors = checks.filter((check) => check.status === "fail").map((check) => check.detail);
|
|
60
|
+
const warnings = checks.filter((check) => check.status === "warn").map((check) => check.detail);
|
|
61
|
+
const result = {
|
|
62
|
+
ready: errors.length === 0,
|
|
63
|
+
projectPath: options.cwd,
|
|
64
|
+
packageManager,
|
|
65
|
+
workspacePackages: packageDirs.length,
|
|
66
|
+
cacheBackend: cache.backend,
|
|
67
|
+
checks,
|
|
68
|
+
warnings,
|
|
69
|
+
errors,
|
|
70
|
+
};
|
|
71
|
+
process.stdout.write(renderGaResult(result) + "\n");
|
|
72
|
+
if (options.jsonFile) {
|
|
73
|
+
await writeFileAtomic(options.jsonFile, stableStringify(result, 2) + "\n");
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
function renderGaResult(result) {
|
|
78
|
+
const lines = [
|
|
79
|
+
`Project: ${result.projectPath}`,
|
|
80
|
+
`GA Ready: ${result.ready ? "yes" : "no"}`,
|
|
81
|
+
`Package Manager: ${result.packageManager}`,
|
|
82
|
+
`Workspace Packages: ${result.workspacePackages}`,
|
|
83
|
+
`Cache Backend: ${result.cacheBackend}`,
|
|
84
|
+
"",
|
|
85
|
+
"Checks:",
|
|
86
|
+
...result.checks.map((check) => `- [${check.status}] ${check.name}: ${check.detail}`),
|
|
87
|
+
];
|
|
88
|
+
if (result.warnings.length > 0) {
|
|
89
|
+
lines.push("", "Warnings:");
|
|
90
|
+
lines.push(...result.warnings.map((warning) => `- ${warning}`));
|
|
91
|
+
}
|
|
92
|
+
if (result.errors.length > 0) {
|
|
93
|
+
lines.push("", "Errors:");
|
|
94
|
+
lines.push(...result.errors.map((error) => `- ${error}`));
|
|
95
|
+
}
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
async function detectLockfile(cwd) {
|
|
99
|
+
const lockfiles = [
|
|
100
|
+
"pnpm-lock.yaml",
|
|
101
|
+
"package-lock.json",
|
|
102
|
+
"npm-shrinkwrap.json",
|
|
103
|
+
"bun.lock",
|
|
104
|
+
"yarn.lock",
|
|
105
|
+
];
|
|
106
|
+
for (const candidate of lockfiles) {
|
|
107
|
+
if (await fileExists(path.resolve(cwd, candidate))) {
|
|
108
|
+
return {
|
|
109
|
+
name: "lockfile",
|
|
110
|
+
status: "pass",
|
|
111
|
+
detail: `Detected lockfile: ${candidate}.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
name: "lockfile",
|
|
117
|
+
status: "warn",
|
|
118
|
+
detail: "No supported lockfile was detected.",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function fileExists(filePath) {
|
|
122
|
+
try {
|
|
123
|
+
await fs.access(filePath);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -25,7 +25,7 @@ export async function runResolve(options) {
|
|
|
25
25
|
let versionOverrides;
|
|
26
26
|
if (options.afterUpdate) {
|
|
27
27
|
versionOverrides = await fetchProposedVersions(options);
|
|
28
|
-
if (versionOverrides.size === 0) {
|
|
28
|
+
if (versionOverrides.size === 0 && !options.silent) {
|
|
29
29
|
process.stderr.write("[resolve] No pending updates found — checking current state.\n");
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -41,10 +41,14 @@ export async function runResolve(options) {
|
|
|
41
41
|
result.conflicts = conflicts;
|
|
42
42
|
result.errorConflicts = conflicts.filter((c) => c.severity === "error").length;
|
|
43
43
|
result.warningConflicts = conflicts.filter((c) => c.severity === "warning").length;
|
|
44
|
-
|
|
44
|
+
if (!options.silent) {
|
|
45
|
+
process.stdout.write(renderConflictsTable(result, options) + "\n");
|
|
46
|
+
}
|
|
45
47
|
if (options.jsonFile) {
|
|
46
48
|
await writeFileAtomic(options.jsonFile, stableStringify(result, 2) + "\n");
|
|
47
|
-
|
|
49
|
+
if (!options.silent) {
|
|
50
|
+
process.stderr.write(`[resolve] JSON report written to ${options.jsonFile}\n`);
|
|
51
|
+
}
|
|
48
52
|
}
|
|
49
53
|
return result;
|
|
50
54
|
}
|
|
@@ -46,6 +46,7 @@ export function parseReviewArgs(args) {
|
|
|
46
46
|
risk: undefined,
|
|
47
47
|
diff: undefined,
|
|
48
48
|
applySelected: false,
|
|
49
|
+
showChangelog: false,
|
|
49
50
|
};
|
|
50
51
|
for (let i = 0; i < args.length; i += 1) {
|
|
51
52
|
const current = args[i];
|
|
@@ -93,6 +94,10 @@ export function parseReviewArgs(args) {
|
|
|
93
94
|
options.applySelected = true;
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
97
|
+
if (current === "--show-changelog") {
|
|
98
|
+
options.showChangelog = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
96
101
|
if (current === "--json-file" && next) {
|
|
97
102
|
options.jsonFile = path.resolve(options.cwd, next);
|
|
98
103
|
i += 1;
|
|
@@ -164,6 +169,7 @@ Options:
|
|
|
164
169
|
--risk <level> Minimum risk: critical, high, medium, low
|
|
165
170
|
--diff <level> Filter by patch, minor, major, latest
|
|
166
171
|
--apply-selected Apply all filtered updates after review
|
|
172
|
+
--show-changelog Fetch release notes summaries for review output
|
|
167
173
|
--workspace Scan all workspace packages
|
|
168
174
|
--policy-file <path> Load policy overrides
|
|
169
175
|
--json-file <path> Write JSON review report to file
|
|
@@ -6,10 +6,11 @@ import { stableStringify } from "../../utils/stable-json.js";
|
|
|
6
6
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
7
7
|
export async function runReview(options) {
|
|
8
8
|
const review = await buildReviewResult(options);
|
|
9
|
-
let
|
|
9
|
+
let selectedItems = review.items;
|
|
10
10
|
if (options.interactive && review.updates.length > 0) {
|
|
11
|
-
|
|
11
|
+
selectedItems = await runTui(review.items);
|
|
12
12
|
}
|
|
13
|
+
const selectedUpdates = selectedItems.map((item) => item.update);
|
|
13
14
|
if (options.applySelected && selectedUpdates.length > 0) {
|
|
14
15
|
await applySelectedUpdates({
|
|
15
16
|
...options,
|
|
@@ -20,8 +21,8 @@ export async function runReview(options) {
|
|
|
20
21
|
}
|
|
21
22
|
process.stdout.write(renderReviewResult({
|
|
22
23
|
...review,
|
|
24
|
+
items: selectedItems,
|
|
23
25
|
updates: selectedUpdates,
|
|
24
|
-
items: review.items.filter((item) => selectedUpdates.some((selected) => selected.name === item.update.name && selected.packagePath === item.update.packagePath)),
|
|
25
26
|
}) + "\n");
|
|
26
27
|
if (options.jsonFile) {
|
|
27
28
|
await writeFileAtomic(options.jsonFile, stableStringify(review, 2) + "\n");
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { fetchChangelog } from "../commands/changelog/fetcher.js";
|
|
2
|
+
import { applyImpactScores } from "./impact.js";
|
|
3
|
+
import { applyRiskAssessments } from "../risk/index.js";
|
|
4
|
+
import { check } from "./check.js";
|
|
5
|
+
export async function buildAnalysisBundle(options, config = {}) {
|
|
6
|
+
const baseCheckOptions = {
|
|
7
|
+
...options,
|
|
8
|
+
interactive: false,
|
|
9
|
+
showImpact: true,
|
|
10
|
+
showHomepage: true,
|
|
11
|
+
};
|
|
12
|
+
const checkResult = await check(baseCheckOptions);
|
|
13
|
+
const [auditResult, resolveResult, healthResult, licenseResult, unusedResult] = await runSilenced(() => Promise.all([
|
|
14
|
+
import("../commands/audit/runner.js").then((mod) => mod.runAudit(toAuditOptions(options))),
|
|
15
|
+
import("../commands/resolve/runner.js").then((mod) => mod.runResolve(toResolveOptions(options))),
|
|
16
|
+
import("../commands/health/runner.js").then((mod) => mod.runHealth(toHealthOptions(options))),
|
|
17
|
+
import("../commands/licenses/runner.js").then((mod) => mod.runLicenses(toLicenseOptions(options))),
|
|
18
|
+
import("../commands/unused/runner.js").then((mod) => mod.runUnused(toUnusedOptions(options))),
|
|
19
|
+
]));
|
|
20
|
+
const items = await buildReviewItems(checkResult.updates, auditResult, resolveResult, healthResult, licenseResult, unusedResult, config);
|
|
21
|
+
return {
|
|
22
|
+
check: checkResult,
|
|
23
|
+
audit: auditResult,
|
|
24
|
+
resolve: resolveResult,
|
|
25
|
+
health: healthResult,
|
|
26
|
+
licenses: licenseResult,
|
|
27
|
+
unused: unusedResult,
|
|
28
|
+
items,
|
|
29
|
+
degradedSources: auditResult.sourceHealth
|
|
30
|
+
.filter((source) => source.status !== "ok")
|
|
31
|
+
.map((source) => source.source),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function buildReviewItems(updates, auditResult, resolveResult, healthResult, licenseResult, unusedResult, config) {
|
|
35
|
+
const advisoryPackages = new Set(auditResult.packages.map((pkg) => pkg.packageName));
|
|
36
|
+
const impactedUpdates = applyImpactScores(updates, {
|
|
37
|
+
advisoryPackages,
|
|
38
|
+
workspaceDependentCount: (name) => updates.filter((item) => item.name === name).length,
|
|
39
|
+
});
|
|
40
|
+
const healthByName = new Map(healthResult.metrics.map((metric) => [metric.name, metric]));
|
|
41
|
+
const advisoriesByName = new Map();
|
|
42
|
+
const conflictsByName = new Map();
|
|
43
|
+
const licenseByName = new Map(licenseResult.packages.map((pkg) => [pkg.name, pkg]));
|
|
44
|
+
const licenseViolationNames = new Set(licenseResult.violations.map((pkg) => pkg.name));
|
|
45
|
+
const unusedByName = new Map();
|
|
46
|
+
for (const advisory of auditResult.advisories) {
|
|
47
|
+
const list = advisoriesByName.get(advisory.packageName) ?? [];
|
|
48
|
+
list.push(advisory);
|
|
49
|
+
advisoriesByName.set(advisory.packageName, list);
|
|
50
|
+
}
|
|
51
|
+
for (const conflict of resolveResult.conflicts) {
|
|
52
|
+
const list = conflictsByName.get(conflict.requester) ?? [];
|
|
53
|
+
list.push(conflict);
|
|
54
|
+
conflictsByName.set(conflict.requester, list);
|
|
55
|
+
const peerList = conflictsByName.get(conflict.peer) ?? [];
|
|
56
|
+
peerList.push(conflict);
|
|
57
|
+
conflictsByName.set(conflict.peer, peerList);
|
|
58
|
+
}
|
|
59
|
+
for (const issue of [...unusedResult.unused, ...unusedResult.missing]) {
|
|
60
|
+
const list = unusedByName.get(issue.name) ?? [];
|
|
61
|
+
list.push(issue);
|
|
62
|
+
unusedByName.set(issue.name, list);
|
|
63
|
+
}
|
|
64
|
+
const enrichedUpdates = await maybeAttachReleaseNotes(impactedUpdates, Boolean(config.includeChangelog));
|
|
65
|
+
return applyRiskAssessments(enrichedUpdates.map((update) => enrichUpdate(update, advisoriesByName, conflictsByName, healthByName, licenseByName, licenseViolationNames, unusedByName)), {
|
|
66
|
+
knownPackageNames: new Set(updates.map((item) => item.name)),
|
|
67
|
+
}).map((item) => ({
|
|
68
|
+
...item,
|
|
69
|
+
update: {
|
|
70
|
+
...item.update,
|
|
71
|
+
policyAction: derivePolicyAction(item),
|
|
72
|
+
decisionState: deriveDecisionState(item),
|
|
73
|
+
selectedByDefault: deriveDecisionState(item) !== "blocked",
|
|
74
|
+
blockedReason: deriveDecisionState(item) === "blocked"
|
|
75
|
+
? item.update.recommendedAction
|
|
76
|
+
: undefined,
|
|
77
|
+
monitorReason: item.update.healthStatus === "stale" ? "Package health should be monitored." : undefined,
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
function enrichUpdate(update, advisoriesByName, conflictsByName, healthByName, licenseByName, licenseViolationNames, unusedByName) {
|
|
82
|
+
const advisories = advisoriesByName.get(update.name) ?? [];
|
|
83
|
+
const peerConflicts = conflictsByName.get(update.name) ?? [];
|
|
84
|
+
const health = healthByName.get(update.name);
|
|
85
|
+
const license = licenseByName.get(update.name);
|
|
86
|
+
const unusedIssues = unusedByName.get(update.name) ?? [];
|
|
87
|
+
return {
|
|
88
|
+
update: {
|
|
89
|
+
...update,
|
|
90
|
+
advisoryCount: advisories.length,
|
|
91
|
+
peerConflictSeverity: peerConflicts.some((item) => item.severity === "error")
|
|
92
|
+
? "error"
|
|
93
|
+
: peerConflicts.length > 0
|
|
94
|
+
? "warning"
|
|
95
|
+
: "none",
|
|
96
|
+
licenseStatus: licenseViolationNames.has(update.name)
|
|
97
|
+
? "denied"
|
|
98
|
+
: license
|
|
99
|
+
? "allowed"
|
|
100
|
+
: "review",
|
|
101
|
+
healthStatus: health?.flags[0] ?? "healthy",
|
|
102
|
+
},
|
|
103
|
+
advisories,
|
|
104
|
+
health,
|
|
105
|
+
peerConflicts,
|
|
106
|
+
license,
|
|
107
|
+
unusedIssues,
|
|
108
|
+
selected: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function derivePolicyAction(item) {
|
|
112
|
+
if (item.update.peerConflictSeverity === "error" || item.update.licenseStatus === "denied") {
|
|
113
|
+
return "block";
|
|
114
|
+
}
|
|
115
|
+
if ((item.update.advisoryCount ?? 0) > 0 || item.update.riskLevel === "critical") {
|
|
116
|
+
return "review";
|
|
117
|
+
}
|
|
118
|
+
if (item.update.healthStatus === "stale" || item.update.healthStatus === "archived") {
|
|
119
|
+
return "monitor";
|
|
120
|
+
}
|
|
121
|
+
return "allow";
|
|
122
|
+
}
|
|
123
|
+
function deriveDecisionState(item) {
|
|
124
|
+
if (item.update.peerConflictSeverity === "error" || item.update.licenseStatus === "denied") {
|
|
125
|
+
return "blocked";
|
|
126
|
+
}
|
|
127
|
+
if ((item.update.advisoryCount ?? 0) > 0 || item.update.riskLevel === "critical") {
|
|
128
|
+
return "actionable";
|
|
129
|
+
}
|
|
130
|
+
if (item.update.riskLevel === "high" || item.update.diffType === "major") {
|
|
131
|
+
return "review";
|
|
132
|
+
}
|
|
133
|
+
return "safe";
|
|
134
|
+
}
|
|
135
|
+
async function maybeAttachReleaseNotes(updates, includeChangelog) {
|
|
136
|
+
if (!includeChangelog || updates.length === 0) {
|
|
137
|
+
return updates;
|
|
138
|
+
}
|
|
139
|
+
const enriched = await Promise.all(updates.map(async (update) => ({
|
|
140
|
+
...update,
|
|
141
|
+
releaseNotesSummary: summarizeChangelog(await fetchChangelog(update.name, update.repository)),
|
|
142
|
+
})));
|
|
143
|
+
return enriched;
|
|
144
|
+
}
|
|
145
|
+
function summarizeChangelog(content) {
|
|
146
|
+
if (!content)
|
|
147
|
+
return undefined;
|
|
148
|
+
const lines = content
|
|
149
|
+
.split(/\r?\n/)
|
|
150
|
+
.map((line) => line.trim())
|
|
151
|
+
.filter(Boolean);
|
|
152
|
+
const title = lines.find((line) => line.startsWith("#"))?.replace(/^#+\s*/, "") ?? "Release notes";
|
|
153
|
+
const excerpt = lines.find((line) => !line.startsWith("#")) ?? "No summary available.";
|
|
154
|
+
return {
|
|
155
|
+
source: content.includes("# Release") ? "github-release" : "changelog-file",
|
|
156
|
+
title,
|
|
157
|
+
excerpt: excerpt.slice(0, 240),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function runSilenced(fn) {
|
|
161
|
+
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
162
|
+
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
163
|
+
process.stdout.write = (() => true);
|
|
164
|
+
process.stderr.write = (() => true);
|
|
165
|
+
try {
|
|
166
|
+
return await fn();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
process.stdout.write = stdoutWrite;
|
|
170
|
+
process.stderr.write = stderrWrite;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function toAuditOptions(options) {
|
|
174
|
+
return {
|
|
175
|
+
cwd: options.cwd,
|
|
176
|
+
workspace: options.workspace,
|
|
177
|
+
severity: undefined,
|
|
178
|
+
fix: false,
|
|
179
|
+
dryRun: true,
|
|
180
|
+
commit: false,
|
|
181
|
+
packageManager: "auto",
|
|
182
|
+
reportFormat: "json",
|
|
183
|
+
sourceMode: "auto",
|
|
184
|
+
jsonFile: undefined,
|
|
185
|
+
concurrency: options.concurrency,
|
|
186
|
+
registryTimeoutMs: options.registryTimeoutMs,
|
|
187
|
+
silent: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function toResolveOptions(options) {
|
|
191
|
+
return {
|
|
192
|
+
cwd: options.cwd,
|
|
193
|
+
workspace: options.workspace,
|
|
194
|
+
afterUpdate: true,
|
|
195
|
+
safe: false,
|
|
196
|
+
jsonFile: undefined,
|
|
197
|
+
concurrency: options.concurrency,
|
|
198
|
+
registryTimeoutMs: options.registryTimeoutMs,
|
|
199
|
+
cacheTtlSeconds: options.cacheTtlSeconds,
|
|
200
|
+
silent: true,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function toHealthOptions(options) {
|
|
204
|
+
return {
|
|
205
|
+
cwd: options.cwd,
|
|
206
|
+
workspace: options.workspace,
|
|
207
|
+
staleDays: 365,
|
|
208
|
+
includeDeprecated: true,
|
|
209
|
+
includeAlternatives: false,
|
|
210
|
+
reportFormat: "json",
|
|
211
|
+
jsonFile: undefined,
|
|
212
|
+
concurrency: options.concurrency,
|
|
213
|
+
registryTimeoutMs: options.registryTimeoutMs,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function toLicenseOptions(options) {
|
|
217
|
+
return {
|
|
218
|
+
cwd: options.cwd,
|
|
219
|
+
workspace: options.workspace,
|
|
220
|
+
allow: undefined,
|
|
221
|
+
deny: undefined,
|
|
222
|
+
sbomFile: undefined,
|
|
223
|
+
jsonFile: undefined,
|
|
224
|
+
diffMode: false,
|
|
225
|
+
concurrency: options.concurrency,
|
|
226
|
+
registryTimeoutMs: options.registryTimeoutMs,
|
|
227
|
+
cacheTtlSeconds: options.cacheTtlSeconds,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function toUnusedOptions(options) {
|
|
231
|
+
return {
|
|
232
|
+
cwd: options.cwd,
|
|
233
|
+
workspace: options.workspace,
|
|
234
|
+
srcDirs: ["src", "."],
|
|
235
|
+
includeDevDependencies: true,
|
|
236
|
+
fix: false,
|
|
237
|
+
dryRun: true,
|
|
238
|
+
jsonFile: undefined,
|
|
239
|
+
concurrency: options.concurrency,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ArtifactManifest, CheckResult, RunOptions } from "../types/index.js";
|
|
2
|
+
export declare function createRunId(command: string, options: RunOptions, result: CheckResult): string;
|
|
3
|
+
export declare function writeArtifactManifest(command: string, options: RunOptions, result: CheckResult): Promise<ArtifactManifest | null>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stableStringify } from "../utils/stable-json.js";
|
|
4
|
+
import { writeFileAtomic } from "../utils/io.js";
|
|
5
|
+
export function createRunId(command, options, result) {
|
|
6
|
+
const hash = crypto.createHash("sha256");
|
|
7
|
+
hash.update(stableStringify({
|
|
8
|
+
command,
|
|
9
|
+
cwd: path.resolve(options.cwd),
|
|
10
|
+
target: options.target,
|
|
11
|
+
workspace: options.workspace,
|
|
12
|
+
ciProfile: options.ciProfile,
|
|
13
|
+
updates: result.updates.map((update) => ({
|
|
14
|
+
packagePath: update.packagePath,
|
|
15
|
+
name: update.name,
|
|
16
|
+
fromRange: update.fromRange,
|
|
17
|
+
toRange: update.toRange,
|
|
18
|
+
})),
|
|
19
|
+
}, 0));
|
|
20
|
+
return hash.digest("hex").slice(0, 16);
|
|
21
|
+
}
|
|
22
|
+
export async function writeArtifactManifest(command, options, result) {
|
|
23
|
+
const shouldWrite = options.ci ||
|
|
24
|
+
Boolean(options.jsonFile) ||
|
|
25
|
+
Boolean(options.githubOutputFile) ||
|
|
26
|
+
Boolean(options.sarifFile) ||
|
|
27
|
+
Boolean(options.prReportFile);
|
|
28
|
+
if (!shouldWrite)
|
|
29
|
+
return null;
|
|
30
|
+
const runId = result.summary.runId ?? createRunId(command, options, result);
|
|
31
|
+
const artifactManifestPath = path.resolve(options.cwd, ".artifacts", `rainy-manifest-${runId}.json`);
|
|
32
|
+
const manifest = {
|
|
33
|
+
runId,
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
command,
|
|
36
|
+
projectPath: result.projectPath,
|
|
37
|
+
ciProfile: options.ciProfile,
|
|
38
|
+
artifactManifestPath,
|
|
39
|
+
outputs: {
|
|
40
|
+
jsonFile: options.jsonFile,
|
|
41
|
+
githubOutputFile: options.githubOutputFile,
|
|
42
|
+
sarifFile: options.sarifFile,
|
|
43
|
+
prReportFile: options.prReportFile,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
await writeFileAtomic(artifactManifestPath, stableStringify(manifest, 2) + "\n");
|
|
47
|
+
return manifest;
|
|
48
|
+
}
|
package/dist/core/check.js
CHANGED
|
@@ -208,6 +208,7 @@ export async function check(options) {
|
|
|
208
208
|
continue;
|
|
209
209
|
updates.push({
|
|
210
210
|
packagePath: path.resolve(task.packageDir),
|
|
211
|
+
workspaceGroup: path.basename(task.packageDir),
|
|
211
212
|
name: task.dependency.name,
|
|
212
213
|
kind: task.dependency.kind,
|
|
213
214
|
fromRange: task.dependency.range,
|
|
@@ -267,6 +268,7 @@ export async function check(options) {
|
|
|
267
268
|
policyOverridesApplied,
|
|
268
269
|
}));
|
|
269
270
|
summary.streamedEvents = streamedEvents;
|
|
271
|
+
summary.cacheBackend = cache.backend;
|
|
270
272
|
summary.riskPackages = limitedUpdates.filter((item) => item.impactScore?.rank === "critical" || item.impactScore?.rank === "high").length;
|
|
271
273
|
return {
|
|
272
274
|
projectPath: options.cwd,
|
|
@@ -305,7 +307,10 @@ function groupUpdates(updates, groupBy) {
|
|
|
305
307
|
byGroup.set(key, current);
|
|
306
308
|
}
|
|
307
309
|
return Array.from(byGroup.entries())
|
|
308
|
-
.map(([key, items]) => ({
|
|
310
|
+
.map(([key, items]) => ({
|
|
311
|
+
key,
|
|
312
|
+
items: sortUpdates(items).map((item) => ({ ...item, groupKey: key })),
|
|
313
|
+
}))
|
|
309
314
|
.sort((left, right) => left.key.localeCompare(right.key));
|
|
310
315
|
}
|
|
311
316
|
function groupKey(update, groupBy) {
|
package/dist/core/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel } from "../types/index.js";
|
|
1
|
+
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel, DashboardOptions, GaOptions } from "../types/index.js";
|
|
2
2
|
import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
|
|
3
3
|
export type ParsedCliArgs = {
|
|
4
4
|
command: "check";
|
|
@@ -52,6 +52,12 @@ export type ParsedCliArgs = {
|
|
|
52
52
|
} | {
|
|
53
53
|
command: "doctor";
|
|
54
54
|
options: DoctorOptions;
|
|
55
|
+
} | {
|
|
56
|
+
command: "dashboard";
|
|
57
|
+
options: DashboardOptions;
|
|
58
|
+
} | {
|
|
59
|
+
command: "ga";
|
|
60
|
+
options: GaOptions;
|
|
55
61
|
};
|
|
56
62
|
export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
|
|
57
63
|
export declare function ensureRiskLevel(value: string): RiskLevel;
|
package/dist/core/options.js
CHANGED
|
@@ -23,6 +23,8 @@ const KNOWN_COMMANDS = [
|
|
|
23
23
|
"snapshot",
|
|
24
24
|
"review",
|
|
25
25
|
"doctor",
|
|
26
|
+
"dashboard",
|
|
27
|
+
"ga",
|
|
26
28
|
];
|
|
27
29
|
export async function parseCliArgs(argv) {
|
|
28
30
|
const firstArg = argv[0];
|
|
@@ -72,6 +74,14 @@ export async function parseCliArgs(argv) {
|
|
|
72
74
|
const { parseDoctorArgs } = await import("../commands/doctor/parser.js");
|
|
73
75
|
return { command, options: parseDoctorArgs(args) };
|
|
74
76
|
}
|
|
77
|
+
if (command === "dashboard") {
|
|
78
|
+
const { parseDashboardArgs } = await import("../commands/dashboard/parser.js");
|
|
79
|
+
return { command, options: parseDashboardArgs(args) };
|
|
80
|
+
}
|
|
81
|
+
if (command === "ga") {
|
|
82
|
+
const { parseGaArgs } = await import("../commands/ga/parser.js");
|
|
83
|
+
return { command, options: parseGaArgs(args) };
|
|
84
|
+
}
|
|
75
85
|
const base = {
|
|
76
86
|
cwd: process.cwd(),
|
|
77
87
|
target: "latest",
|
|
@@ -450,6 +460,10 @@ export async function parseCliArgs(argv) {
|
|
|
450
460
|
base.showImpact = true;
|
|
451
461
|
continue;
|
|
452
462
|
}
|
|
463
|
+
if (current === "--show-links") {
|
|
464
|
+
base.showHomepage = true;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
453
467
|
if (current === "--show-homepage") {
|
|
454
468
|
base.showHomepage = true;
|
|
455
469
|
continue;
|