@rainy-updates/cli 0.6.0 → 0.6.2
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 +111 -0
- package/README.md +12 -0
- package/dist/bin/cli.js +12 -127
- package/dist/bin/dispatch.js +6 -0
- package/dist/bin/help.js +48 -0
- package/dist/bin/main.d.ts +1 -0
- package/dist/bin/main.js +126 -0
- package/dist/commands/audit/parser.js +36 -0
- package/dist/commands/audit/runner.js +17 -18
- package/dist/commands/bisect/oracle.js +21 -17
- package/dist/commands/bisect/runner.js +4 -3
- package/dist/commands/dashboard/parser.js +41 -0
- package/dist/commands/dashboard/runner.js +3 -0
- package/dist/commands/doctor/parser.js +44 -0
- package/dist/commands/ga/parser.js +39 -0
- package/dist/commands/ga/runner.js +73 -9
- package/dist/commands/health/parser.js +36 -0
- package/dist/commands/health/runner.js +5 -1
- package/dist/commands/hook/parser.d.ts +2 -0
- package/dist/commands/hook/parser.js +40 -0
- package/dist/commands/hook/runner.d.ts +2 -0
- package/dist/commands/hook/runner.js +174 -0
- package/dist/commands/licenses/parser.js +39 -0
- package/dist/commands/licenses/runner.js +5 -1
- package/dist/commands/resolve/graph/builder.js +5 -1
- package/dist/commands/resolve/parser.js +39 -0
- package/dist/commands/resolve/runner.js +5 -0
- package/dist/commands/review/parser.js +44 -0
- package/dist/commands/snapshot/parser.js +39 -0
- package/dist/commands/snapshot/runner.js +4 -1
- package/dist/commands/unused/parser.js +39 -0
- package/dist/commands/unused/runner.js +4 -1
- package/dist/commands/unused/scanner.d.ts +2 -1
- package/dist/commands/unused/scanner.js +60 -44
- package/dist/core/check.js +5 -1
- package/dist/core/doctor/findings.js +4 -4
- package/dist/core/init-ci.js +28 -26
- package/dist/core/options.d.ts +4 -1
- package/dist/core/options.js +57 -0
- package/dist/core/verification.js +11 -9
- package/dist/core/warm-cache.js +5 -1
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/git/scope.d.ts +19 -0
- package/dist/git/scope.js +167 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/sarif.js +2 -8
- package/dist/pm/detect.d.ts +37 -0
- package/dist/pm/detect.js +133 -2
- package/dist/pm/install.d.ts +2 -1
- package/dist/pm/install.js +7 -5
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +59 -1
- package/dist/ui/dashboard-state.d.ts +7 -0
- package/dist/ui/dashboard-state.js +44 -0
- package/dist/ui/tui.d.ts +3 -0
- package/dist/ui/tui.js +311 -111
- package/dist/utils/shell.d.ts +6 -0
- package/dist/utils/shell.js +18 -0
- package/dist/workspace/discover.d.ts +7 -1
- package/dist/workspace/discover.js +12 -3
- package/package.json +16 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
|
|
3
|
+
import { buildShellInvocation } from "../../utils/shell.js";
|
|
3
4
|
/**
|
|
4
5
|
* The "oracle" for bisect: installs a specific version of a package
|
|
5
6
|
* into the project's node_modules (via the shell), then runs --cmd.
|
|
@@ -11,9 +12,12 @@ export async function bisectOracle(packageName, version, options) {
|
|
|
11
12
|
process.stderr.write(`[bisect:dry-run] Would test ${packageName}@${version}\n`);
|
|
12
13
|
return "skip";
|
|
13
14
|
}
|
|
14
|
-
const detected = await
|
|
15
|
-
const
|
|
16
|
-
const installResult = await
|
|
15
|
+
const detected = await detectPackageManagerDetails(options.cwd);
|
|
16
|
+
const profile = createPackageManagerProfile("auto", detected, "bun");
|
|
17
|
+
const installResult = await runCommand(buildAddInvocation(profile, [`${packageName}@${version}`], {
|
|
18
|
+
exact: true,
|
|
19
|
+
noSave: true,
|
|
20
|
+
}), options.cwd);
|
|
17
21
|
if (installResult !== 0) {
|
|
18
22
|
process.stderr.write(`[bisect] Failed to install ${packageName}@${version}, skipping.\n`);
|
|
19
23
|
return "skip";
|
|
@@ -26,8 +30,8 @@ export async function bisectOracle(packageName, version, options) {
|
|
|
26
30
|
}
|
|
27
31
|
async function runShell(command, cwd) {
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
30
|
-
const proc = Bun.spawn([
|
|
33
|
+
const invocation = buildShellInvocation(command);
|
|
34
|
+
const proc = Bun.spawn([invocation.shell, ...invocation.args], {
|
|
31
35
|
cwd: path.resolve(cwd),
|
|
32
36
|
stdout: "pipe",
|
|
33
37
|
stderr: "pipe",
|
|
@@ -38,16 +42,16 @@ async function runShell(command, cwd) {
|
|
|
38
42
|
return 1;
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
async function runCommand(command, cwd) {
|
|
46
|
+
try {
|
|
47
|
+
const proc = Bun.spawn([command.command, ...command.args], {
|
|
48
|
+
cwd: path.resolve(cwd),
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
});
|
|
52
|
+
return await proc.exited;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return 1;
|
|
52
56
|
}
|
|
53
57
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
|
|
2
2
|
import { fetchBisectVersions, bisectVersions } from "./engine.js";
|
|
3
3
|
/**
|
|
4
4
|
* Entry point for the `bisect` command. Lazy-loaded by cli.ts.
|
|
5
5
|
* Fully isolated: does NOT import anything from core/options.ts.
|
|
6
6
|
*/
|
|
7
7
|
export async function runBisect(options) {
|
|
8
|
-
const detected = await
|
|
8
|
+
const detected = await detectPackageManagerDetails(options.cwd);
|
|
9
|
+
const profile = createPackageManagerProfile("auto", detected, "bun");
|
|
9
10
|
const runtimeOptions = {
|
|
10
11
|
...options,
|
|
11
12
|
testCommand: options.testCommand ||
|
|
12
|
-
|
|
13
|
+
buildTestCommand(profile),
|
|
13
14
|
};
|
|
14
15
|
process.stderr.write(`\n[bisect] Fetching available versions for ${runtimeOptions.packageName}...\n`);
|
|
15
16
|
const versions = await fetchBisectVersions(runtimeOptions);
|
|
@@ -40,6 +40,11 @@ export function parseDashboardArgs(args) {
|
|
|
40
40
|
cooldownDays: undefined,
|
|
41
41
|
prLimit: undefined,
|
|
42
42
|
onlyChanged: false,
|
|
43
|
+
affected: false,
|
|
44
|
+
staged: false,
|
|
45
|
+
baseRef: undefined,
|
|
46
|
+
headRef: undefined,
|
|
47
|
+
sinceRef: undefined,
|
|
43
48
|
ciProfile: "minimal",
|
|
44
49
|
lockfileMode: "preserve",
|
|
45
50
|
interactive: true,
|
|
@@ -148,6 +153,42 @@ export function parseDashboardArgs(args) {
|
|
|
148
153
|
options.workspace = true;
|
|
149
154
|
continue;
|
|
150
155
|
}
|
|
156
|
+
if (arg === "--only-changed") {
|
|
157
|
+
options.onlyChanged = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (arg === "--affected") {
|
|
161
|
+
options.affected = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (arg === "--staged") {
|
|
165
|
+
options.staged = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (arg === "--base" && nextArg) {
|
|
169
|
+
options.baseRef = nextArg;
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (arg === "--base") {
|
|
174
|
+
throw new Error("Missing value for --base");
|
|
175
|
+
}
|
|
176
|
+
if (arg === "--head" && nextArg) {
|
|
177
|
+
options.headRef = nextArg;
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === "--head") {
|
|
182
|
+
throw new Error("Missing value for --head");
|
|
183
|
+
}
|
|
184
|
+
if (arg === "--since" && nextArg) {
|
|
185
|
+
options.sinceRef = nextArg;
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === "--since") {
|
|
190
|
+
throw new Error("Missing value for --since");
|
|
191
|
+
}
|
|
151
192
|
if (arg === "--cwd" && nextArg) {
|
|
152
193
|
options.cwd = path.resolve(nextArg);
|
|
153
194
|
i++;
|
|
@@ -3,6 +3,7 @@ import { buildReviewResult, renderReviewResult } from "../../core/review-model.j
|
|
|
3
3
|
import { applySelectedUpdates } from "../../core/upgrade.js";
|
|
4
4
|
import { runVerification } from "../../core/verification.js";
|
|
5
5
|
import { runTui } from "../../ui/tui.js";
|
|
6
|
+
import { deriveDashboardInitialFilter, deriveDashboardInitialTab, } from "../../ui/dashboard-state.js";
|
|
6
7
|
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
7
8
|
export async function runDashboard(options, prebuiltReview) {
|
|
8
9
|
const review = prebuiltReview ?? (await buildReviewResult({
|
|
@@ -73,5 +74,7 @@ async function selectDashboardItems(options, visibleItems) {
|
|
|
73
74
|
? "Rainy Dashboard: Upgrade Queue"
|
|
74
75
|
: "Rainy Dashboard: Review Queue",
|
|
75
76
|
subtitle: `focus=${options.focus} mode=${options.mode} Enter confirms the selected decision set`,
|
|
77
|
+
initialFilter: deriveDashboardInitialFilter(options),
|
|
78
|
+
initialTab: deriveDashboardInitialTab(options),
|
|
76
79
|
});
|
|
77
80
|
}
|
|
@@ -36,6 +36,11 @@ export function parseDoctorArgs(args) {
|
|
|
36
36
|
cooldownDays: undefined,
|
|
37
37
|
prLimit: undefined,
|
|
38
38
|
onlyChanged: false,
|
|
39
|
+
affected: false,
|
|
40
|
+
staged: false,
|
|
41
|
+
baseRef: undefined,
|
|
42
|
+
headRef: undefined,
|
|
43
|
+
sinceRef: undefined,
|
|
39
44
|
ciProfile: "minimal",
|
|
40
45
|
lockfileMode: "preserve",
|
|
41
46
|
interactive: false,
|
|
@@ -64,6 +69,39 @@ export function parseDoctorArgs(args) {
|
|
|
64
69
|
options.workspace = true;
|
|
65
70
|
continue;
|
|
66
71
|
}
|
|
72
|
+
if (current === "--only-changed") {
|
|
73
|
+
options.onlyChanged = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (current === "--affected") {
|
|
77
|
+
options.affected = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (current === "--staged") {
|
|
81
|
+
options.staged = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (current === "--base" && next) {
|
|
85
|
+
options.baseRef = next;
|
|
86
|
+
i += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (current === "--base")
|
|
90
|
+
throw new Error("Missing value for --base");
|
|
91
|
+
if (current === "--head" && next) {
|
|
92
|
+
options.headRef = next;
|
|
93
|
+
i += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (current === "--head")
|
|
97
|
+
throw new Error("Missing value for --head");
|
|
98
|
+
if (current === "--since" && next) {
|
|
99
|
+
options.sinceRef = next;
|
|
100
|
+
i += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (current === "--since")
|
|
104
|
+
throw new Error("Missing value for --since");
|
|
67
105
|
if (current === "--verdict-only") {
|
|
68
106
|
options.verdictOnly = true;
|
|
69
107
|
continue;
|
|
@@ -104,6 +142,12 @@ Options:
|
|
|
104
142
|
--include-changelog Include release note summaries in the aggregated review data
|
|
105
143
|
--agent-report Print a prompt-ready remediation report for coding agents
|
|
106
144
|
--workspace Scan all workspace packages
|
|
145
|
+
--only-changed Limit analysis to changed packages
|
|
146
|
+
--affected Include changed packages and their dependents
|
|
147
|
+
--staged Limit analysis to staged changes
|
|
148
|
+
--base <ref> Compare changes against a base git ref
|
|
149
|
+
--head <ref> Compare changes against a head git ref
|
|
150
|
+
--since <ref> Compare changes since a git ref
|
|
107
151
|
--json-file <path> Write JSON doctor report to file
|
|
108
152
|
--cwd <path>
|
|
109
153
|
`.trimStart();
|
|
@@ -4,6 +4,11 @@ export function parseGaArgs(args) {
|
|
|
4
4
|
const options = {
|
|
5
5
|
cwd: getRuntimeCwd(),
|
|
6
6
|
workspace: false,
|
|
7
|
+
affected: false,
|
|
8
|
+
staged: false,
|
|
9
|
+
baseRef: undefined,
|
|
10
|
+
headRef: undefined,
|
|
11
|
+
sinceRef: undefined,
|
|
7
12
|
jsonFile: undefined,
|
|
8
13
|
};
|
|
9
14
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -20,6 +25,35 @@ export function parseGaArgs(args) {
|
|
|
20
25
|
options.workspace = true;
|
|
21
26
|
continue;
|
|
22
27
|
}
|
|
28
|
+
if (current === "--affected") {
|
|
29
|
+
options.affected = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (current === "--staged") {
|
|
33
|
+
options.staged = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (current === "--base" && next) {
|
|
37
|
+
options.baseRef = next;
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "--base")
|
|
42
|
+
throw new Error("Missing value for --base");
|
|
43
|
+
if (current === "--head" && next) {
|
|
44
|
+
options.headRef = next;
|
|
45
|
+
i += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (current === "--head")
|
|
49
|
+
throw new Error("Missing value for --head");
|
|
50
|
+
if (current === "--since" && next) {
|
|
51
|
+
options.sinceRef = next;
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (current === "--since")
|
|
56
|
+
throw new Error("Missing value for --since");
|
|
23
57
|
if (current === "--json-file" && next) {
|
|
24
58
|
options.jsonFile = path.resolve(options.cwd, next);
|
|
25
59
|
i += 1;
|
|
@@ -45,6 +79,11 @@ Usage:
|
|
|
45
79
|
|
|
46
80
|
Options:
|
|
47
81
|
--workspace Evaluate workspace package coverage
|
|
82
|
+
--affected Evaluate changed workspace packages and dependents
|
|
83
|
+
--staged Limit GA discovery to staged changes
|
|
84
|
+
--base <ref> Compare changes against a base git ref
|
|
85
|
+
--head <ref> Compare changes against a head git ref
|
|
86
|
+
--since <ref> Compare changes since a git ref
|
|
48
87
|
--json-file <path> Write JSON GA report to file
|
|
49
88
|
--cwd <path>
|
|
50
89
|
`.trimStart();
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { VersionCache } from "../../cache/cache.js";
|
|
3
|
-
import {
|
|
3
|
+
import { detectPackageManagerDetails } from "../../pm/detect.js";
|
|
4
4
|
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
5
5
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
6
6
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
7
7
|
import { writeStdout } from "../../utils/runtime.js";
|
|
8
8
|
export async function runGa(options) {
|
|
9
|
-
const packageManager = await
|
|
10
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
9
|
+
const packageManager = await detectPackageManagerDetails(options.cwd);
|
|
10
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
11
|
+
git: options,
|
|
12
|
+
includeDependents: options.affected === true,
|
|
13
|
+
});
|
|
11
14
|
const cache = await VersionCache.create();
|
|
12
15
|
const checks = [];
|
|
13
16
|
checks.push({
|
|
14
17
|
name: "package-manager",
|
|
15
|
-
status: packageManager === "unknown" ? "warn" : "pass",
|
|
16
|
-
detail: packageManager === "unknown"
|
|
18
|
+
status: packageManager.manager === "unknown" ? "warn" : "pass",
|
|
19
|
+
detail: packageManager.manager === "unknown"
|
|
17
20
|
? "No supported lockfile was detected. Rainy can still run, but runtime/package verification will fall back to generic defaults."
|
|
18
|
-
: `Detected package manager: ${packageManager}. Runtime and verification can align with this package ecosystem, including Bun.`,
|
|
21
|
+
: `Detected package manager: ${packageManager.manager} via ${packageManager.source}${packageManager.lockfile ? ` (${packageManager.lockfile})` : ""}${packageManager.packageManagerField ? ` [${packageManager.packageManagerField}]` : ""}. Runtime and verification can align with this package ecosystem, including Bun.`,
|
|
19
22
|
});
|
|
20
23
|
checks.push({
|
|
21
24
|
name: "workspace-discovery",
|
|
@@ -39,14 +42,16 @@ export async function runGa(options) {
|
|
|
39
42
|
? "Built CLI entrypoint exists in dist/bin/cli.js."
|
|
40
43
|
: "Built CLI entrypoint is missing; run the build before publishing a release artifact.",
|
|
41
44
|
});
|
|
42
|
-
const compiledBinaryExists = await
|
|
45
|
+
const compiledBinaryExists = await detectCompiledBinary(options.cwd);
|
|
43
46
|
checks.push({
|
|
44
47
|
name: "runtime-artifacts",
|
|
45
48
|
status: compiledBinaryExists ? "pass" : "warn",
|
|
46
49
|
detail: compiledBinaryExists
|
|
47
|
-
? "Compiled Bun runtime artifact exists in dist
|
|
50
|
+
? "Compiled Bun runtime artifact exists in dist/."
|
|
48
51
|
: "Compiled Bun runtime artifact is missing; run bun run build:exe before publishing Bun-first release artifacts.",
|
|
49
52
|
});
|
|
53
|
+
checks.push(await detectAutomationEntryPoints(options.cwd));
|
|
54
|
+
checks.push(await detectPlatformSupport(options.cwd));
|
|
50
55
|
checks.push({
|
|
51
56
|
name: "benchmark-gates",
|
|
52
57
|
status: (await fileExists(path.resolve(options.cwd, "scripts/perf-smoke.mjs"))) &&
|
|
@@ -68,7 +73,7 @@ export async function runGa(options) {
|
|
|
68
73
|
const result = {
|
|
69
74
|
ready: errors.length === 0,
|
|
70
75
|
projectPath: options.cwd,
|
|
71
|
-
packageManager,
|
|
76
|
+
packageManager: packageManager.manager,
|
|
72
77
|
workspacePackages: packageDirs.length,
|
|
73
78
|
cacheBackend: cache.backend,
|
|
74
79
|
checks,
|
|
@@ -125,6 +130,65 @@ async function detectLockfile(cwd) {
|
|
|
125
130
|
detail: "No supported lockfile was detected.",
|
|
126
131
|
};
|
|
127
132
|
}
|
|
133
|
+
async function detectCompiledBinary(cwd) {
|
|
134
|
+
return ((await fileExists(path.resolve(cwd, "dist/rup"))) ||
|
|
135
|
+
(await fileExists(path.resolve(cwd, "dist/rup.exe"))));
|
|
136
|
+
}
|
|
137
|
+
async function detectAutomationEntryPoints(cwd) {
|
|
138
|
+
const packageJsonPath = path.resolve(cwd, "package.json");
|
|
139
|
+
let scripts = {};
|
|
140
|
+
try {
|
|
141
|
+
const manifest = (await Bun.file(packageJsonPath).json());
|
|
142
|
+
scripts = manifest.scripts ?? {};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
scripts = {};
|
|
146
|
+
}
|
|
147
|
+
const hasMakefile = (await fileExists(path.resolve(cwd, "Makefile"))) ||
|
|
148
|
+
(await fileExists(path.resolve(cwd, "makefile")));
|
|
149
|
+
const requiredScripts = ["build", "check", "test:prod"];
|
|
150
|
+
const missingScripts = requiredScripts.filter((script) => !scripts[script]);
|
|
151
|
+
if (hasMakefile && missingScripts.length === 0) {
|
|
152
|
+
return {
|
|
153
|
+
name: "automation-entrypoints",
|
|
154
|
+
status: "pass",
|
|
155
|
+
detail: "Portable automation entrypoints are available via package scripts and Makefile targets.",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (missingScripts.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
name: "automation-entrypoints",
|
|
161
|
+
status: "pass",
|
|
162
|
+
detail: "Portable package scripts are available for build, check, and test:prod.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
name: "automation-entrypoints",
|
|
167
|
+
status: "warn",
|
|
168
|
+
detail: `Missing automation entrypoints: ${missingScripts.join(", ")}.`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async function detectPlatformSupport(cwd) {
|
|
172
|
+
const packageJsonPath = path.resolve(cwd, "package.json");
|
|
173
|
+
let scripts = {};
|
|
174
|
+
try {
|
|
175
|
+
const manifest = (await Bun.file(packageJsonPath).json());
|
|
176
|
+
scripts = manifest.scripts ?? {};
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
scripts = {};
|
|
180
|
+
}
|
|
181
|
+
const suspectScripts = Object.entries(scripts)
|
|
182
|
+
.filter(([, command]) => /\brm\s+-rf\b|\btest\s+-x\b|\bchmod\b|\bcp\s+-R\b/.test(command))
|
|
183
|
+
.map(([name]) => name);
|
|
184
|
+
return {
|
|
185
|
+
name: "platform-support",
|
|
186
|
+
status: suspectScripts.length === 0 ? "pass" : "warn",
|
|
187
|
+
detail: suspectScripts.length === 0
|
|
188
|
+
? "No obvious POSIX-only package scripts were detected for release-critical automation."
|
|
189
|
+
: `These scripts still look POSIX-specific and may need extra Windows work: ${suspectScripts.join(", ")}.`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
128
192
|
async function fileExists(filePath) {
|
|
129
193
|
try {
|
|
130
194
|
return await Bun.file(filePath).exists();
|
|
@@ -4,6 +4,11 @@ export function parseHealthArgs(args) {
|
|
|
4
4
|
const options = {
|
|
5
5
|
cwd: getRuntimeCwd(),
|
|
6
6
|
workspace: false,
|
|
7
|
+
affected: false,
|
|
8
|
+
staged: false,
|
|
9
|
+
baseRef: undefined,
|
|
10
|
+
headRef: undefined,
|
|
11
|
+
sinceRef: undefined,
|
|
7
12
|
staleDays: 365,
|
|
8
13
|
includeDeprecated: true,
|
|
9
14
|
includeAlternatives: false,
|
|
@@ -28,6 +33,37 @@ export function parseHealthArgs(args) {
|
|
|
28
33
|
index += 1;
|
|
29
34
|
continue;
|
|
30
35
|
}
|
|
36
|
+
if (current === "--affected") {
|
|
37
|
+
options.affected = true;
|
|
38
|
+
index += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "--staged") {
|
|
42
|
+
options.staged = true;
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (current === "--base" && next) {
|
|
47
|
+
options.baseRef = next;
|
|
48
|
+
index += 2;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (current === "--base")
|
|
52
|
+
throw new Error("Missing value for --base");
|
|
53
|
+
if (current === "--head" && next) {
|
|
54
|
+
options.headRef = next;
|
|
55
|
+
index += 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (current === "--head")
|
|
59
|
+
throw new Error("Missing value for --head");
|
|
60
|
+
if (current === "--since" && next) {
|
|
61
|
+
options.sinceRef = next;
|
|
62
|
+
index += 2;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (current === "--since")
|
|
66
|
+
throw new Error("Missing value for --since");
|
|
31
67
|
if (current === "--stale" && next) {
|
|
32
68
|
// Accept "12m" → 365, "6m" → 180, "365d" → 365, or plain number
|
|
33
69
|
const match = next.match(/^(\d+)(m|d)?$/);
|
|
@@ -89,7 +89,11 @@ export async function runHealth(options) {
|
|
|
89
89
|
errors: [],
|
|
90
90
|
warnings: [],
|
|
91
91
|
};
|
|
92
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
92
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
93
|
+
git: options,
|
|
94
|
+
includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
|
|
95
|
+
includeDependents: options.affected === true,
|
|
96
|
+
});
|
|
93
97
|
const versionMap = new Map();
|
|
94
98
|
for (const dir of packageDirs) {
|
|
95
99
|
let manifest;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function parseHookArgs(args) {
|
|
3
|
+
let cwd = process.cwd();
|
|
4
|
+
let action = "doctor";
|
|
5
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6
|
+
const current = args[index];
|
|
7
|
+
const next = args[index + 1];
|
|
8
|
+
if ((current === "install" || current === "uninstall" || current === "doctor") && index === 0) {
|
|
9
|
+
action = current;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (current === "--cwd" && next) {
|
|
13
|
+
cwd = path.resolve(next);
|
|
14
|
+
index += 1;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (current === "--cwd") {
|
|
18
|
+
throw new Error("Missing value for --cwd");
|
|
19
|
+
}
|
|
20
|
+
if (current === "--help" || current === "-h") {
|
|
21
|
+
process.stdout.write(HOOK_HELP);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
if (current.startsWith("-")) {
|
|
25
|
+
throw new Error(`Unknown hook option: ${current}`);
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Unexpected hook argument: ${current}`);
|
|
28
|
+
}
|
|
29
|
+
return { cwd, action };
|
|
30
|
+
}
|
|
31
|
+
const HOOK_HELP = `
|
|
32
|
+
rup hook — Install or inspect Rainy-managed git hooks
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
rup hook <install|uninstall|doctor> [options]
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--cwd <path> Working directory (default: cwd)
|
|
39
|
+
--help Show this help
|
|
40
|
+
`.trimStart();
|