@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { chmod, mkdir, unlink } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeStdout } from "../../utils/runtime.js";
|
|
4
|
+
const MANAGED_MARKER = "# rainy-updates managed hook";
|
|
5
|
+
const HOOKS = [
|
|
6
|
+
{
|
|
7
|
+
name: "pre-commit",
|
|
8
|
+
command: '$(resolve_rup) unused --workspace --staged && $(resolve_rup) resolve --workspace --staged',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "pre-push",
|
|
12
|
+
command: '$(resolve_rup) audit --workspace --affected --report summary',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
export async function runHook(options) {
|
|
16
|
+
const hookDir = await resolveHookDir(options.cwd);
|
|
17
|
+
const result = {
|
|
18
|
+
action: options.action,
|
|
19
|
+
hookDir,
|
|
20
|
+
installed: [],
|
|
21
|
+
removed: [],
|
|
22
|
+
checked: [],
|
|
23
|
+
errors: [],
|
|
24
|
+
warnings: [],
|
|
25
|
+
};
|
|
26
|
+
if (options.action === "install") {
|
|
27
|
+
await mkdir(hookDir, { recursive: true });
|
|
28
|
+
for (const hook of HOOKS) {
|
|
29
|
+
const hookPath = path.join(hookDir, hook.name);
|
|
30
|
+
const existing = await readHookState(hookPath);
|
|
31
|
+
if (existing.status === "foreign") {
|
|
32
|
+
result.warnings.push(`Skipped ${hook.name}: existing hook is not Rainy-managed.`);
|
|
33
|
+
result.checked.push({ name: hook.name, status: "foreign" });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
await Bun.write(hookPath, renderHookScript(hook.command));
|
|
37
|
+
await makeExecutable(hookPath);
|
|
38
|
+
result.installed.push(hook.name);
|
|
39
|
+
result.checked.push({ name: hook.name, status: "managed" });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (options.action === "uninstall") {
|
|
43
|
+
for (const hook of HOOKS) {
|
|
44
|
+
const hookPath = path.join(hookDir, hook.name);
|
|
45
|
+
const existing = await readHookState(hookPath);
|
|
46
|
+
if (existing.status === "managed") {
|
|
47
|
+
await unlink(hookPath).catch(() => undefined);
|
|
48
|
+
result.removed.push(hook.name);
|
|
49
|
+
}
|
|
50
|
+
else if (existing.status === "foreign") {
|
|
51
|
+
result.warnings.push(`Left ${hook.name} untouched: existing hook is not Rainy-managed.`);
|
|
52
|
+
}
|
|
53
|
+
result.checked.push({ name: hook.name, status: existing.status });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
for (const hook of HOOKS) {
|
|
58
|
+
const hookPath = path.join(hookDir, hook.name);
|
|
59
|
+
const existing = await readHookState(hookPath);
|
|
60
|
+
result.checked.push({ name: hook.name, status: existing.status });
|
|
61
|
+
if (existing.status === "foreign") {
|
|
62
|
+
result.warnings.push(`${hook.name} exists but is not Rainy-managed.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
writeStdout(renderHookResult(result) + "\n");
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
async function resolveHookDir(cwd) {
|
|
70
|
+
const resolved = await runGit(cwd, ["rev-parse", "--git-path", "hooks"]);
|
|
71
|
+
if (!resolved.ok) {
|
|
72
|
+
throw new Error(`Unable to resolve git hooks directory: ${resolved.error}`);
|
|
73
|
+
}
|
|
74
|
+
const hookDir = resolved.stdout.trim();
|
|
75
|
+
if (hookDir.length === 0) {
|
|
76
|
+
throw new Error("Git hooks directory could not be determined.");
|
|
77
|
+
}
|
|
78
|
+
return path.resolve(cwd, hookDir);
|
|
79
|
+
}
|
|
80
|
+
async function readHookState(hookPath) {
|
|
81
|
+
try {
|
|
82
|
+
const file = Bun.file(hookPath);
|
|
83
|
+
if (!(await file.exists())) {
|
|
84
|
+
return { status: "missing" };
|
|
85
|
+
}
|
|
86
|
+
const content = await file.text();
|
|
87
|
+
return content.includes(MANAGED_MARKER)
|
|
88
|
+
? { status: "managed", content }
|
|
89
|
+
: { status: "foreign", content };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { status: "missing" };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function renderHookScript(command) {
|
|
96
|
+
return `#!/bin/sh
|
|
97
|
+
${MANAGED_MARKER}
|
|
98
|
+
set -eu
|
|
99
|
+
|
|
100
|
+
resolve_rup() {
|
|
101
|
+
if command -v rup >/dev/null 2>&1; then
|
|
102
|
+
printf '%s' "rup"
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
if command -v rainy-updates >/dev/null 2>&1; then
|
|
106
|
+
printf '%s' "rainy-updates"
|
|
107
|
+
return
|
|
108
|
+
fi
|
|
109
|
+
if command -v rainy-up >/dev/null 2>&1; then
|
|
110
|
+
printf '%s' "rainy-up"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
printf '%s\n' "Rainy Updates CLI not found in PATH." >&2
|
|
114
|
+
exit 127
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
${command}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
async function makeExecutable(filePath) {
|
|
121
|
+
await chmod(filePath, 0o755);
|
|
122
|
+
}
|
|
123
|
+
async function runGit(cwd, args) {
|
|
124
|
+
try {
|
|
125
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
126
|
+
cwd,
|
|
127
|
+
stdout: "pipe",
|
|
128
|
+
stderr: "pipe",
|
|
129
|
+
});
|
|
130
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
131
|
+
new Response(proc.stdout).text(),
|
|
132
|
+
new Response(proc.stderr).text(),
|
|
133
|
+
proc.exited,
|
|
134
|
+
]);
|
|
135
|
+
if (code !== 0) {
|
|
136
|
+
return { ok: false, error: stderr.trim() || `git ${args.join(" ")} failed` };
|
|
137
|
+
}
|
|
138
|
+
return { ok: true, stdout };
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return { ok: false, error: String(error) };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function renderHookResult(result) {
|
|
145
|
+
const lines = [
|
|
146
|
+
`Hook action: ${result.action}`,
|
|
147
|
+
`Hook dir: ${result.hookDir ?? "unknown"}`,
|
|
148
|
+
];
|
|
149
|
+
if (result.installed.length > 0) {
|
|
150
|
+
lines.push(`Installed: ${result.installed.join(", ")}`);
|
|
151
|
+
}
|
|
152
|
+
if (result.removed.length > 0) {
|
|
153
|
+
lines.push(`Removed: ${result.removed.join(", ")}`);
|
|
154
|
+
}
|
|
155
|
+
if (result.checked.length > 0) {
|
|
156
|
+
lines.push("Checks:");
|
|
157
|
+
for (const check of result.checked) {
|
|
158
|
+
lines.push(`- ${check.name}: ${check.status}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (result.warnings.length > 0) {
|
|
162
|
+
lines.push("Warnings:");
|
|
163
|
+
for (const warning of result.warnings) {
|
|
164
|
+
lines.push(`- ${warning}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (result.errors.length > 0) {
|
|
168
|
+
lines.push("Errors:");
|
|
169
|
+
for (const error of result.errors) {
|
|
170
|
+
lines.push(`- ${error}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
@@ -2,6 +2,11 @@ export function parseLicensesArgs(args) {
|
|
|
2
2
|
const options = {
|
|
3
3
|
cwd: process.cwd(),
|
|
4
4
|
workspace: false,
|
|
5
|
+
affected: false,
|
|
6
|
+
staged: false,
|
|
7
|
+
baseRef: undefined,
|
|
8
|
+
headRef: undefined,
|
|
9
|
+
sinceRef: undefined,
|
|
5
10
|
allow: undefined,
|
|
6
11
|
deny: undefined,
|
|
7
12
|
sbomFile: undefined,
|
|
@@ -25,6 +30,35 @@ export function parseLicensesArgs(args) {
|
|
|
25
30
|
options.workspace = true;
|
|
26
31
|
continue;
|
|
27
32
|
}
|
|
33
|
+
if (current === "--affected") {
|
|
34
|
+
options.affected = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (current === "--staged") {
|
|
38
|
+
options.staged = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "--base" && next) {
|
|
42
|
+
options.baseRef = next;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (current === "--base")
|
|
47
|
+
throw new Error("Missing value for --base");
|
|
48
|
+
if (current === "--head" && next) {
|
|
49
|
+
options.headRef = next;
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (current === "--head")
|
|
54
|
+
throw new Error("Missing value for --head");
|
|
55
|
+
if (current === "--since" && next) {
|
|
56
|
+
options.sinceRef = next;
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (current === "--since")
|
|
61
|
+
throw new Error("Missing value for --since");
|
|
28
62
|
if (current === "--diff") {
|
|
29
63
|
options.diffMode = true;
|
|
30
64
|
continue;
|
|
@@ -105,6 +139,11 @@ Options:
|
|
|
105
139
|
--json-file <path> Write JSON report to file
|
|
106
140
|
--diff Show only packages with a different license than last scan
|
|
107
141
|
--workspace Scan all workspace packages
|
|
142
|
+
--affected Scan changed workspace packages and dependents
|
|
143
|
+
--staged Limit scanning to staged changes
|
|
144
|
+
--base <ref> Compare changes against a base git ref
|
|
145
|
+
--head <ref> Compare changes against a head git ref
|
|
146
|
+
--since <ref> Compare changes since a git ref
|
|
108
147
|
--timeout <ms> Registry request timeout (default: 10000)
|
|
109
148
|
--concurrency <n> Parallel registry requests (default: 12)
|
|
110
149
|
--cwd <path> Working directory (default: cwd)
|
|
@@ -20,7 +20,11 @@ export async function runLicenses(options) {
|
|
|
20
20
|
errors: [],
|
|
21
21
|
warnings: [],
|
|
22
22
|
};
|
|
23
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
23
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
24
|
+
git: options,
|
|
25
|
+
includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
|
|
26
|
+
includeDependents: options.affected === true,
|
|
27
|
+
});
|
|
24
28
|
const allDeps = new Map(); // name → resolved version
|
|
25
29
|
for (const packageDir of packageDirs) {
|
|
26
30
|
let manifest;
|
|
@@ -22,7 +22,11 @@ export async function buildPeerGraph(options,
|
|
|
22
22
|
* to inject proposed upgrade versions before writing them to disk).
|
|
23
23
|
*/
|
|
24
24
|
resolvedVersionOverrides) {
|
|
25
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
25
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
26
|
+
git: options,
|
|
27
|
+
includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
|
|
28
|
+
includeDependents: options.affected === true,
|
|
29
|
+
});
|
|
26
30
|
const cache = await VersionCache.create();
|
|
27
31
|
const registry = new NpmRegistryClient(options.cwd, {
|
|
28
32
|
timeoutMs: options.registryTimeoutMs,
|
|
@@ -2,6 +2,11 @@ export function parseResolveArgs(args) {
|
|
|
2
2
|
const options = {
|
|
3
3
|
cwd: process.cwd(),
|
|
4
4
|
workspace: false,
|
|
5
|
+
affected: false,
|
|
6
|
+
staged: false,
|
|
7
|
+
baseRef: undefined,
|
|
8
|
+
headRef: undefined,
|
|
9
|
+
sinceRef: undefined,
|
|
5
10
|
afterUpdate: false,
|
|
6
11
|
safe: false,
|
|
7
12
|
jsonFile: undefined,
|
|
@@ -23,6 +28,35 @@ export function parseResolveArgs(args) {
|
|
|
23
28
|
options.workspace = true;
|
|
24
29
|
continue;
|
|
25
30
|
}
|
|
31
|
+
if (current === "--affected") {
|
|
32
|
+
options.affected = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (current === "--staged") {
|
|
36
|
+
options.staged = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (current === "--base" && next) {
|
|
40
|
+
options.baseRef = next;
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (current === "--base")
|
|
45
|
+
throw new Error("Missing value for --base");
|
|
46
|
+
if (current === "--head" && next) {
|
|
47
|
+
options.headRef = next;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (current === "--head")
|
|
52
|
+
throw new Error("Missing value for --head");
|
|
53
|
+
if (current === "--since" && next) {
|
|
54
|
+
options.sinceRef = next;
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (current === "--since")
|
|
59
|
+
throw new Error("Missing value for --since");
|
|
26
60
|
if (current === "--after-update") {
|
|
27
61
|
options.afterUpdate = true;
|
|
28
62
|
continue;
|
|
@@ -77,6 +111,11 @@ Options:
|
|
|
77
111
|
--after-update Simulate conflicts after applying pending \`rup check\` updates
|
|
78
112
|
--safe Exit non-zero if any error-level conflicts exist
|
|
79
113
|
--workspace Scan all workspace packages
|
|
114
|
+
--affected Scan changed workspace packages and their dependents
|
|
115
|
+
--staged Limit scanning to staged changes
|
|
116
|
+
--base <ref> Compare changes against a base git ref
|
|
117
|
+
--head <ref> Compare changes against a head git ref
|
|
118
|
+
--since <ref> Compare changes since a git ref
|
|
80
119
|
--json-file <path> Write JSON conflict report to file
|
|
81
120
|
--timeout <ms> Registry request timeout in ms (default: 10000)
|
|
82
121
|
--concurrency <n> Parallel registry requests (default: 12)
|
|
@@ -95,6 +95,11 @@ async function fetchProposedVersions(options) {
|
|
|
95
95
|
cooldownDays: undefined,
|
|
96
96
|
prLimit: undefined,
|
|
97
97
|
onlyChanged: false,
|
|
98
|
+
affected: options.affected,
|
|
99
|
+
staged: options.staged,
|
|
100
|
+
baseRef: options.baseRef,
|
|
101
|
+
headRef: options.headRef,
|
|
102
|
+
sinceRef: options.sinceRef,
|
|
98
103
|
ciProfile: "minimal",
|
|
99
104
|
lockfileMode: "preserve",
|
|
100
105
|
interactive: false,
|
|
@@ -37,6 +37,11 @@ export function parseReviewArgs(args) {
|
|
|
37
37
|
cooldownDays: undefined,
|
|
38
38
|
prLimit: undefined,
|
|
39
39
|
onlyChanged: false,
|
|
40
|
+
affected: false,
|
|
41
|
+
staged: false,
|
|
42
|
+
baseRef: undefined,
|
|
43
|
+
headRef: undefined,
|
|
44
|
+
sinceRef: undefined,
|
|
40
45
|
ciProfile: "minimal",
|
|
41
46
|
lockfileMode: "preserve",
|
|
42
47
|
interactive: false,
|
|
@@ -68,6 +73,39 @@ export function parseReviewArgs(args) {
|
|
|
68
73
|
options.workspace = true;
|
|
69
74
|
continue;
|
|
70
75
|
}
|
|
76
|
+
if (current === "--only-changed") {
|
|
77
|
+
options.onlyChanged = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (current === "--affected") {
|
|
81
|
+
options.affected = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (current === "--staged") {
|
|
85
|
+
options.staged = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (current === "--base" && next) {
|
|
89
|
+
options.baseRef = next;
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (current === "--base")
|
|
94
|
+
throw new Error("Missing value for --base");
|
|
95
|
+
if (current === "--head" && next) {
|
|
96
|
+
options.headRef = next;
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (current === "--head")
|
|
101
|
+
throw new Error("Missing value for --head");
|
|
102
|
+
if (current === "--since" && next) {
|
|
103
|
+
options.sinceRef = next;
|
|
104
|
+
i += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (current === "--since")
|
|
108
|
+
throw new Error("Missing value for --since");
|
|
71
109
|
if (current === "--interactive") {
|
|
72
110
|
options.interactive = true;
|
|
73
111
|
continue;
|
|
@@ -224,6 +262,12 @@ Options:
|
|
|
224
262
|
--test-command <cmd> Override the command used for test verification
|
|
225
263
|
--show-changelog Fetch release notes summaries for review output
|
|
226
264
|
--workspace Scan all workspace packages
|
|
265
|
+
--only-changed Limit analysis to changed packages
|
|
266
|
+
--affected Include changed packages and their dependents
|
|
267
|
+
--staged Limit analysis to staged changes
|
|
268
|
+
--base <ref> Compare changes against a base git ref
|
|
269
|
+
--head <ref> Compare changes against a head git ref
|
|
270
|
+
--since <ref> Compare changes since a git ref
|
|
227
271
|
--policy-file <path> Load policy overrides
|
|
228
272
|
--json-file <path> Write JSON review report to file
|
|
229
273
|
--registry-timeout-ms <n>
|
|
@@ -3,6 +3,11 @@ export function parseSnapshotArgs(args) {
|
|
|
3
3
|
const options = {
|
|
4
4
|
cwd: process.cwd(),
|
|
5
5
|
workspace: false,
|
|
6
|
+
affected: false,
|
|
7
|
+
staged: false,
|
|
8
|
+
baseRef: undefined,
|
|
9
|
+
headRef: undefined,
|
|
10
|
+
sinceRef: undefined,
|
|
6
11
|
action: "list",
|
|
7
12
|
label: undefined,
|
|
8
13
|
snapshotId: undefined,
|
|
@@ -25,6 +30,35 @@ export function parseSnapshotArgs(args) {
|
|
|
25
30
|
options.workspace = true;
|
|
26
31
|
continue;
|
|
27
32
|
}
|
|
33
|
+
if (current === "--affected") {
|
|
34
|
+
options.affected = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (current === "--staged") {
|
|
38
|
+
options.staged = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "--base" && next) {
|
|
42
|
+
options.baseRef = next;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (current === "--base")
|
|
47
|
+
throw new Error("Missing value for --base");
|
|
48
|
+
if (current === "--head" && next) {
|
|
49
|
+
options.headRef = next;
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (current === "--head")
|
|
54
|
+
throw new Error("Missing value for --head");
|
|
55
|
+
if (current === "--since" && next) {
|
|
56
|
+
options.sinceRef = next;
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (current === "--since")
|
|
61
|
+
throw new Error("Missing value for --since");
|
|
28
62
|
if (current === "--label" && next) {
|
|
29
63
|
options.label = next;
|
|
30
64
|
i++;
|
|
@@ -75,6 +109,11 @@ Options:
|
|
|
75
109
|
--label <name> Human-readable label for the snapshot
|
|
76
110
|
--store <path> Custom snapshot store file (default: .rup-snapshots.json)
|
|
77
111
|
--workspace Include all workspace packages
|
|
112
|
+
--affected Include changed workspace packages and dependents
|
|
113
|
+
--staged Limit snapshot scope to staged changes
|
|
114
|
+
--base <ref> Compare changes against a base git ref
|
|
115
|
+
--head <ref> Compare changes against a head git ref
|
|
116
|
+
--since <ref> Compare changes since a git ref
|
|
78
117
|
--cwd <path> Working directory (default: cwd)
|
|
79
118
|
--help Show this help
|
|
80
119
|
`.trimStart();
|
|
@@ -16,7 +16,10 @@ export async function runSnapshot(options) {
|
|
|
16
16
|
errors: [],
|
|
17
17
|
warnings: [],
|
|
18
18
|
};
|
|
19
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
19
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
20
|
+
git: options,
|
|
21
|
+
includeDependents: options.affected === true,
|
|
22
|
+
});
|
|
20
23
|
const store = new SnapshotStore(options.cwd, options.storeFile);
|
|
21
24
|
switch (options.action) {
|
|
22
25
|
// ─ save ──────────────────────────────────────────────────────────────────
|
|
@@ -3,6 +3,11 @@ export function parseUnusedArgs(args) {
|
|
|
3
3
|
const options = {
|
|
4
4
|
cwd: process.cwd(),
|
|
5
5
|
workspace: false,
|
|
6
|
+
affected: false,
|
|
7
|
+
staged: false,
|
|
8
|
+
baseRef: undefined,
|
|
9
|
+
headRef: undefined,
|
|
10
|
+
sinceRef: undefined,
|
|
6
11
|
srcDirs: DEFAULT_SRC_DIRS,
|
|
7
12
|
includeDevDependencies: true,
|
|
8
13
|
fix: false,
|
|
@@ -24,6 +29,35 @@ export function parseUnusedArgs(args) {
|
|
|
24
29
|
options.workspace = true;
|
|
25
30
|
continue;
|
|
26
31
|
}
|
|
32
|
+
if (current === "--affected") {
|
|
33
|
+
options.affected = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (current === "--staged") {
|
|
37
|
+
options.staged = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (current === "--base" && next) {
|
|
41
|
+
options.baseRef = next;
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (current === "--base")
|
|
46
|
+
throw new Error("Missing value for --base");
|
|
47
|
+
if (current === "--head" && next) {
|
|
48
|
+
options.headRef = next;
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (current === "--head")
|
|
53
|
+
throw new Error("Missing value for --head");
|
|
54
|
+
if (current === "--since" && next) {
|
|
55
|
+
options.sinceRef = next;
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (current === "--since")
|
|
60
|
+
throw new Error("Missing value for --since");
|
|
27
61
|
if (current === "--src" && next) {
|
|
28
62
|
options.srcDirs = next
|
|
29
63
|
.split(",")
|
|
@@ -81,6 +115,11 @@ Usage:
|
|
|
81
115
|
Options:
|
|
82
116
|
--src <dirs> Comma-separated source directories to scan (default: src)
|
|
83
117
|
--workspace Scan all workspace packages
|
|
118
|
+
--affected Scan changed workspace packages and their dependents
|
|
119
|
+
--staged Limit scanning to staged changes
|
|
120
|
+
--base <ref> Compare changes against a base git ref
|
|
121
|
+
--head <ref> Compare changes against a head git ref
|
|
122
|
+
--since <ref> Compare changes since a git ref
|
|
84
123
|
--no-dev Exclude devDependencies from unused detection
|
|
85
124
|
--fix Remove unused dependencies from package.json
|
|
86
125
|
--dry-run Preview changes without writing
|
|
@@ -24,7 +24,10 @@ export async function runUnused(options) {
|
|
|
24
24
|
errors: [],
|
|
25
25
|
warnings: [],
|
|
26
26
|
};
|
|
27
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
27
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
28
|
+
git: options,
|
|
29
|
+
includeDependents: false,
|
|
30
|
+
});
|
|
28
31
|
for (const packageDir of packageDirs) {
|
|
29
32
|
// ─ Read manifest ─────────────────────────────────────────────────────────
|
|
30
33
|
let manifest;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extracts all imported package names from a single source file.
|
|
2
|
+
* Extracts all imported package names from a single source file using AST.
|
|
3
3
|
*
|
|
4
4
|
* Handles:
|
|
5
5
|
* - ESM static: import ... from "pkg"
|
|
6
6
|
* - ESM dynamic: import("pkg")
|
|
7
7
|
* - CJS: require("pkg")
|
|
8
|
+
* - ESM re-export: export ... from "pkg"
|
|
8
9
|
*
|
|
9
10
|
* Strips subpath imports (e.g. "lodash/merge" → "lodash"),
|
|
10
11
|
* skips relative imports and node: builtins.
|