@lexmanh/shed-cli 0.2.0-beta.9 → 0.3.0-beta.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/dist/cli.js +227 -172
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,50 @@ import { createRequire as createRequire2 } from "module";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/clean.ts
|
|
8
|
-
import
|
|
9
|
-
import * as p from "@clack/prompts";
|
|
8
|
+
import * as p2 from "@clack/prompts";
|
|
10
9
|
import {
|
|
11
10
|
RiskTier,
|
|
12
11
|
SafetyChecker,
|
|
13
12
|
Scanner,
|
|
14
13
|
defaultDetectors
|
|
15
14
|
} from "@lexmanh/shed-core";
|
|
15
|
+
import pc2 from "picocolors";
|
|
16
|
+
|
|
17
|
+
// src/scope-resolver.ts
|
|
18
|
+
import { resolve } from "path";
|
|
19
|
+
import * as p from "@clack/prompts";
|
|
20
|
+
import { resolveScopeRoots } from "@lexmanh/shed-core";
|
|
16
21
|
import pc from "picocolors";
|
|
22
|
+
async function resolveScanScope(opts = {}) {
|
|
23
|
+
const explicit = opts.path !== void 0 ? resolve(opts.path) : void 0;
|
|
24
|
+
const result = await resolveScopeRoots({ explicit });
|
|
25
|
+
if (result.ok) return { ok: true, roots: result.roots };
|
|
26
|
+
if (result.reason !== "no-default-roots" || opts.nonInteractive) {
|
|
27
|
+
return { ok: false, message: result.message };
|
|
28
|
+
}
|
|
29
|
+
const examples = result.suggestedExamples ?? [];
|
|
30
|
+
p.note(
|
|
31
|
+
[
|
|
32
|
+
"shed could not find a default dev directory under your home folder.",
|
|
33
|
+
"",
|
|
34
|
+
"Pass an explicit path to `shed scan` / `shed clean`, or create one of:",
|
|
35
|
+
...examples.map((e) => ` ${pc.dim(e)}`)
|
|
36
|
+
].join("\n"),
|
|
37
|
+
"No scan target"
|
|
38
|
+
);
|
|
39
|
+
const answer = await p.text({
|
|
40
|
+
message: "Path to scan (leave empty to cancel):",
|
|
41
|
+
placeholder: "~/projects"
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(answer) || typeof answer !== "string" || answer.trim() === "") {
|
|
44
|
+
return { ok: false, message: "Cancelled \u2014 no path provided." };
|
|
45
|
+
}
|
|
46
|
+
const trimmed = answer.trim();
|
|
47
|
+
const expanded = trimmed.startsWith("~") ? trimmed.replace(/^~/, process.env.HOME ?? process.env.USERPROFILE ?? "") : trimmed;
|
|
48
|
+
const second = await resolveScopeRoots({ explicit: resolve(expanded) });
|
|
49
|
+
if (second.ok) return { ok: true, roots: second.roots };
|
|
50
|
+
return { ok: false, message: second.message };
|
|
51
|
+
}
|
|
17
52
|
|
|
18
53
|
// src/verbose.ts
|
|
19
54
|
var _verbose = false;
|
|
@@ -36,37 +71,44 @@ function formatBytes(bytes) {
|
|
|
36
71
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
37
72
|
}
|
|
38
73
|
var RISK_BADGE = {
|
|
39
|
-
[RiskTier.Green]:
|
|
40
|
-
[RiskTier.Yellow]:
|
|
41
|
-
[RiskTier.Red]:
|
|
74
|
+
[RiskTier.Green]: pc2.green("Green "),
|
|
75
|
+
[RiskTier.Yellow]: pc2.yellow("Yellow"),
|
|
76
|
+
[RiskTier.Red]: pc2.red("Red ")
|
|
42
77
|
};
|
|
43
|
-
async function cleanCommand(path
|
|
44
|
-
const rootDir = resolve(path);
|
|
78
|
+
async function cleanCommand(path, options = {}) {
|
|
45
79
|
const isDryRun = !options.execute;
|
|
46
|
-
|
|
80
|
+
p2.intro(pc2.bgYellow(pc2.black(" shed clean ")));
|
|
81
|
+
const scope = await resolveScanScope({ path });
|
|
82
|
+
if (!scope.ok) {
|
|
83
|
+
p2.cancel(scope.message);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
47
86
|
if (isDryRun) {
|
|
48
|
-
|
|
87
|
+
p2.note(
|
|
49
88
|
"DRY-RUN mode \u2014 no files will be deleted.\nPass --execute to perform actual cleanup.",
|
|
50
89
|
"Safe mode"
|
|
51
90
|
);
|
|
52
91
|
}
|
|
53
|
-
const spinner4 =
|
|
54
|
-
verbose(
|
|
55
|
-
|
|
92
|
+
const spinner4 = p2.spinner();
|
|
93
|
+
verbose(
|
|
94
|
+
`clean roots: ${scope.roots.join(", ")}, dryRun=${isDryRun}, hardDelete=${options.hardDelete ?? false}`
|
|
95
|
+
);
|
|
96
|
+
spinner4.start(
|
|
97
|
+
scope.roots.length === 1 ? `Scanning ${scope.roots[0]} \u2026` : `Scanning ${scope.roots.length} roots \u2026`
|
|
98
|
+
);
|
|
56
99
|
const scanner = new Scanner(defaultDetectors());
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]);
|
|
100
|
+
const projectsByRoot = await Promise.all(scope.roots.map((root) => scanner.scan(root)));
|
|
101
|
+
const projects = projectsByRoot.flat();
|
|
102
|
+
const ctx = { scanRoot: scope.roots[0] ?? "/", maxDepth: 8 };
|
|
103
|
+
const globalItems = await scanner.scanGlobal(ctx);
|
|
62
104
|
const allItems = [
|
|
63
105
|
...projects.flatMap((proj) => proj.items),
|
|
64
106
|
...globalItems
|
|
65
107
|
].filter((i) => options.includeRed || i.risk !== RiskTier.Red);
|
|
66
108
|
verbose(`scan complete: ${allItems.length} cleanable items`);
|
|
67
|
-
spinner4.stop(`Found ${
|
|
109
|
+
spinner4.stop(`Found ${pc2.bold(String(allItems.length))} cleanable items.`);
|
|
68
110
|
if (allItems.length === 0) {
|
|
69
|
-
|
|
111
|
+
p2.outro(pc2.dim("Nothing to clean."));
|
|
70
112
|
return;
|
|
71
113
|
}
|
|
72
114
|
const checker = new SafetyChecker();
|
|
@@ -80,18 +122,18 @@ async function cleanCommand(path = ".", options = {}) {
|
|
|
80
122
|
return !(result2?.allowed ?? false);
|
|
81
123
|
});
|
|
82
124
|
if (blockedItems.length > 0) {
|
|
83
|
-
|
|
125
|
+
p2.note(
|
|
84
126
|
blockedItems.map((item) => {
|
|
85
127
|
const reasons = checkResults[allItems.indexOf(item)]?.reasons ?? [];
|
|
86
128
|
const blockReason = reasons.find((r) => r.severity === "block");
|
|
87
|
-
return `${
|
|
88
|
-
${
|
|
129
|
+
return `${pc2.dim(item.path)}
|
|
130
|
+
${pc2.red("\u2717")} ${blockReason?.message ?? "blocked"}`;
|
|
89
131
|
}).join("\n\n"),
|
|
90
132
|
`${blockedItems.length} item(s) blocked by safety checks`
|
|
91
133
|
);
|
|
92
134
|
}
|
|
93
135
|
if (eligibleItems.length === 0) {
|
|
94
|
-
|
|
136
|
+
p2.outro(pc2.yellow("All items were blocked by safety checks."));
|
|
95
137
|
return;
|
|
96
138
|
}
|
|
97
139
|
let selectedItems = eligibleItems;
|
|
@@ -102,31 +144,31 @@ async function cleanCommand(path = ".", options = {}) {
|
|
|
102
144
|
const greenBytes = greenItems.reduce((s, i) => s + i.sizeBytes, 0);
|
|
103
145
|
const yellowBytes = yellowItems.reduce((s, i) => s + i.sizeBytes, 0);
|
|
104
146
|
const allBytes = eligibleItems.reduce((s, i) => s + i.sizeBytes, 0);
|
|
105
|
-
const preset = await
|
|
147
|
+
const preset = await p2.select({
|
|
106
148
|
message: "What would you like to clean?",
|
|
107
149
|
options: [
|
|
108
150
|
{
|
|
109
151
|
value: "all",
|
|
110
|
-
label: `All ${
|
|
152
|
+
label: `All ${pc2.dim(`${eligibleItems.length} items \xB7 ${formatBytes(allBytes)}`)}`
|
|
111
153
|
},
|
|
112
154
|
...greenItems.length > 0 ? [
|
|
113
155
|
{
|
|
114
156
|
value: "green",
|
|
115
|
-
label: `${
|
|
157
|
+
label: `${pc2.green("Green only")} ${pc2.dim(`${greenItems.length} items \xB7 ${formatBytes(greenBytes)} \xB7 safest`)}`
|
|
116
158
|
}
|
|
117
159
|
] : [],
|
|
118
160
|
...yellowItems.length > 0 ? [
|
|
119
161
|
{
|
|
120
162
|
value: "yellow",
|
|
121
|
-
label: `${
|
|
163
|
+
label: `${pc2.yellow("Yellow only")} ${pc2.dim(`${yellowItems.length} items \xB7 ${formatBytes(yellowBytes)}`)}`
|
|
122
164
|
}
|
|
123
165
|
] : [],
|
|
124
166
|
{ value: "custom", label: "Custom (pick individual items)" },
|
|
125
|
-
{ value: "cancel", label:
|
|
167
|
+
{ value: "cancel", label: pc2.dim("Cancel (do nothing, exit)") }
|
|
126
168
|
]
|
|
127
169
|
});
|
|
128
|
-
if (
|
|
129
|
-
|
|
170
|
+
if (p2.isCancel(preset) || preset === "cancel") {
|
|
171
|
+
p2.cancel("Cleanup cancelled.");
|
|
130
172
|
return;
|
|
131
173
|
}
|
|
132
174
|
if (preset === "all") {
|
|
@@ -141,37 +183,37 @@ async function cleanCommand(path = ".", options = {}) {
|
|
|
141
183
|
const warnings = checkResults[allItems.indexOf(item)]?.reasons.filter(
|
|
142
184
|
(r) => r.severity === "warning"
|
|
143
185
|
) ?? [];
|
|
144
|
-
const warnStr = warnings.length > 0 ?
|
|
186
|
+
const warnStr = warnings.length > 0 ? pc2.yellow(` \u26A0 ${warnings.map((w) => w.message).join("; ")}`) : "";
|
|
145
187
|
return {
|
|
146
188
|
value: item,
|
|
147
|
-
label: `${RISK_BADGE[item.risk]} ${displayPath} ${
|
|
189
|
+
label: `${RISK_BADGE[item.risk]} ${displayPath} ${pc2.dim(formatBytes(item.sizeBytes))}${warnStr}`
|
|
148
190
|
};
|
|
149
191
|
});
|
|
150
|
-
const selection = await
|
|
192
|
+
const selection = await p2.multiselect({
|
|
151
193
|
message: "Select items to clean (space to toggle, enter to confirm):",
|
|
152
194
|
options: choices,
|
|
153
195
|
required: false
|
|
154
196
|
});
|
|
155
|
-
if (
|
|
156
|
-
|
|
197
|
+
if (p2.isCancel(selection)) {
|
|
198
|
+
p2.cancel("Cleanup cancelled.");
|
|
157
199
|
return;
|
|
158
200
|
}
|
|
159
201
|
selectedItems = selection;
|
|
160
202
|
}
|
|
161
203
|
}
|
|
162
204
|
if (selectedItems.length === 0) {
|
|
163
|
-
|
|
205
|
+
p2.outro(pc2.dim("Nothing selected."));
|
|
164
206
|
return;
|
|
165
207
|
}
|
|
166
208
|
const totalBytes = selectedItems.reduce((s, i) => s + i.sizeBytes, 0);
|
|
167
209
|
if (!isDryRun && !options.yes) {
|
|
168
|
-
const action = options.hardDelete ?
|
|
169
|
-
const confirmed = await
|
|
210
|
+
const action = options.hardDelete ? pc2.red("PERMANENTLY DELETE") : "move to Trash";
|
|
211
|
+
const confirmed = await p2.confirm({
|
|
170
212
|
message: `${action} ${selectedItems.length} item(s) (${formatBytes(totalBytes)})?`,
|
|
171
213
|
initialValue: false
|
|
172
214
|
});
|
|
173
|
-
if (
|
|
174
|
-
|
|
215
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
216
|
+
p2.cancel("Cleanup cancelled.");
|
|
175
217
|
return;
|
|
176
218
|
}
|
|
177
219
|
}
|
|
@@ -179,7 +221,7 @@ async function cleanCommand(path = ".", options = {}) {
|
|
|
179
221
|
`executing ${selectedItems.length} items, dryRun=${isDryRun}, hardDelete=${options.hardDelete ?? false}`
|
|
180
222
|
);
|
|
181
223
|
for (const item of selectedItems) verbose(` \u2192 ${item.path}`);
|
|
182
|
-
const execSpinner =
|
|
224
|
+
const execSpinner = p2.spinner();
|
|
183
225
|
execSpinner.start(isDryRun ? "Simulating cleanup \u2026" : "Cleaning up \u2026");
|
|
184
226
|
const result = await checker.execute(selectedItems, {
|
|
185
227
|
dryRun: isDryRun,
|
|
@@ -191,26 +233,26 @@ async function cleanCommand(path = ".", options = {}) {
|
|
|
191
233
|
const verb = isDryRun ? "Would free" : "Freed";
|
|
192
234
|
console.log(
|
|
193
235
|
`
|
|
194
|
-
${
|
|
236
|
+
${pc2.green("\u2713")} ${verb} ${pc2.bold(pc2.green(formatBytes(result.totalBytesFreed)))} across ${result.succeeded.length} item(s).`
|
|
195
237
|
);
|
|
196
238
|
}
|
|
197
239
|
if (result.skipped.length > 0) {
|
|
198
|
-
console.log(` ${
|
|
240
|
+
console.log(` ${pc2.yellow("\u26A0")} ${result.skipped.length} item(s) skipped.`);
|
|
199
241
|
for (const s of result.skipped) {
|
|
200
242
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
201
243
|
const displayPath = home ? s.item.path.replace(home, "~") : s.item.path;
|
|
202
|
-
console.log(` ${
|
|
244
|
+
console.log(` ${pc2.dim(displayPath)}: ${s.reason}`);
|
|
203
245
|
}
|
|
204
246
|
}
|
|
205
247
|
if (result.failed.length > 0) {
|
|
206
|
-
console.log(` ${
|
|
248
|
+
console.log(` ${pc2.red("\u2717")} ${result.failed.length} item(s) failed:`);
|
|
207
249
|
for (const f of result.failed) {
|
|
208
|
-
console.log(` ${
|
|
250
|
+
console.log(` ${pc2.dim(f.item.path)}: ${f.error}`);
|
|
209
251
|
}
|
|
210
252
|
}
|
|
211
253
|
console.log();
|
|
212
|
-
const outro7 = isDryRun ? `Dry-run complete. Run with ${
|
|
213
|
-
|
|
254
|
+
const outro7 = isDryRun ? `Dry-run complete. Run with ${pc2.cyan("--execute")} to perform actual cleanup.` : result.failed.length > 0 ? `Completed with ${result.failed.length} failure(s).` : "All done!";
|
|
255
|
+
p2.outro(outro7);
|
|
214
256
|
}
|
|
215
257
|
|
|
216
258
|
// src/commands/completions.ts
|
|
@@ -330,9 +372,9 @@ function completionsCommand(shell) {
|
|
|
330
372
|
}
|
|
331
373
|
|
|
332
374
|
// src/commands/config.ts
|
|
333
|
-
import * as
|
|
375
|
+
import * as p3 from "@clack/prompts";
|
|
334
376
|
import Conf from "conf";
|
|
335
|
-
import
|
|
377
|
+
import pc3 from "picocolors";
|
|
336
378
|
var DEFAULTS = {
|
|
337
379
|
scan: { maxDepth: 8, maxAgeDays: 30 },
|
|
338
380
|
clean: { hardDelete: false },
|
|
@@ -394,7 +436,7 @@ function flatSet(store, key, raw) {
|
|
|
394
436
|
return true;
|
|
395
437
|
}
|
|
396
438
|
async function configCommand(action, key, value) {
|
|
397
|
-
|
|
439
|
+
p3.intro(pc3.bgBlue(pc3.black(" shed config ")));
|
|
398
440
|
const store = getStore();
|
|
399
441
|
switch (action) {
|
|
400
442
|
case "list":
|
|
@@ -402,16 +444,16 @@ async function configCommand(action, key, value) {
|
|
|
402
444
|
const lines = Object.entries(KEY_DEFS).map(([k, def]) => {
|
|
403
445
|
const val = flatGet(store, k);
|
|
404
446
|
const isDefault = String(val) === String(def.get(DEFAULTS));
|
|
405
|
-
const valStr = isDefault ?
|
|
406
|
-
return ` ${k.padEnd(22)} ${valStr}${isDefault ?
|
|
447
|
+
const valStr = isDefault ? pc3.dim(String(val)) : pc3.cyan(String(val));
|
|
448
|
+
return ` ${k.padEnd(22)} ${valStr}${isDefault ? pc3.dim(" (default)") : ""}`;
|
|
407
449
|
});
|
|
408
|
-
|
|
409
|
-
|
|
450
|
+
p3.note(lines.join("\n"), "Current configuration");
|
|
451
|
+
p3.note(pc3.dim(store.path), "Config file");
|
|
410
452
|
break;
|
|
411
453
|
}
|
|
412
454
|
case "get": {
|
|
413
455
|
if (!key || !(key in KEY_DEFS)) {
|
|
414
|
-
|
|
456
|
+
p3.cancel(
|
|
415
457
|
!key ? "Usage: shed config get <key>" : `Unknown key: ${key}
|
|
416
458
|
Valid: ${Object.keys(KEY_DEFS).join(", ")}`
|
|
417
459
|
);
|
|
@@ -422,59 +464,59 @@ Valid: ${Object.keys(KEY_DEFS).join(", ")}`
|
|
|
422
464
|
}
|
|
423
465
|
case "set": {
|
|
424
466
|
if (!key || value === void 0) {
|
|
425
|
-
|
|
467
|
+
p3.cancel("Usage: shed config set <key> <value>");
|
|
426
468
|
process.exit(1);
|
|
427
469
|
}
|
|
428
470
|
if (!(key in KEY_DEFS)) {
|
|
429
|
-
|
|
471
|
+
p3.cancel(`Unknown key: ${key}
|
|
430
472
|
Valid: ${Object.keys(KEY_DEFS).join(", ")}`);
|
|
431
473
|
process.exit(1);
|
|
432
474
|
}
|
|
433
475
|
if (!flatSet(store, key, value)) {
|
|
434
|
-
|
|
476
|
+
p3.cancel(`Invalid value "${value}" for key "${key}"`);
|
|
435
477
|
process.exit(1);
|
|
436
478
|
}
|
|
437
|
-
|
|
479
|
+
p3.outro(`${pc3.cyan(key)} = ${pc3.green(value)}`);
|
|
438
480
|
return;
|
|
439
481
|
}
|
|
440
482
|
case "reset": {
|
|
441
483
|
if (key) {
|
|
442
484
|
if (!(key in KEY_DEFS)) {
|
|
443
|
-
|
|
485
|
+
p3.cancel(`Unknown key: ${key}`);
|
|
444
486
|
process.exit(1);
|
|
445
487
|
}
|
|
446
488
|
const [section] = key.split(".");
|
|
447
489
|
store.set(section, DEFAULTS[section]);
|
|
448
|
-
|
|
490
|
+
p3.outro(`${pc3.cyan(key)} reset to default.`);
|
|
449
491
|
} else {
|
|
450
|
-
const confirmed = await
|
|
492
|
+
const confirmed = await p3.confirm({
|
|
451
493
|
message: "Reset ALL settings to defaults?",
|
|
452
494
|
initialValue: false
|
|
453
495
|
});
|
|
454
|
-
if (
|
|
455
|
-
|
|
496
|
+
if (p3.isCancel(confirmed) || !confirmed) {
|
|
497
|
+
p3.cancel("Cancelled.");
|
|
456
498
|
return;
|
|
457
499
|
}
|
|
458
500
|
store.clear();
|
|
459
|
-
|
|
501
|
+
p3.outro("All settings reset to defaults.");
|
|
460
502
|
}
|
|
461
503
|
return;
|
|
462
504
|
}
|
|
463
505
|
default:
|
|
464
|
-
|
|
506
|
+
p3.cancel(`Unknown action: ${action}
|
|
465
507
|
Usage: shed config [list|get|set|reset]`);
|
|
466
508
|
process.exit(1);
|
|
467
509
|
}
|
|
468
|
-
|
|
510
|
+
p3.outro(pc3.dim(`Edit directly: ${store.path}`));
|
|
469
511
|
}
|
|
470
512
|
|
|
471
513
|
// src/commands/doctor.ts
|
|
472
514
|
import { arch, homedir, platform, release } from "os";
|
|
473
|
-
import * as
|
|
515
|
+
import * as p4 from "@clack/prompts";
|
|
474
516
|
import { execa } from "execa";
|
|
475
|
-
import
|
|
517
|
+
import pc4 from "picocolors";
|
|
476
518
|
async function doctorCommand() {
|
|
477
|
-
|
|
519
|
+
p4.intro(pc4.bgGreen(pc4.black(" shed doctor ")));
|
|
478
520
|
const checks = [];
|
|
479
521
|
checks.push({ name: "OS", value: `${platform()} ${release()} (${arch()})` });
|
|
480
522
|
checks.push({ name: "Home", value: homedir() });
|
|
@@ -485,25 +527,24 @@ async function doctorCommand() {
|
|
|
485
527
|
const { stdout } = await execa(tool, ["--version"], { reject: false });
|
|
486
528
|
checks.push({ name: tool, value: stdout.split("\n")[0] ?? "unknown" });
|
|
487
529
|
} catch {
|
|
488
|
-
checks.push({ name: tool, value:
|
|
530
|
+
checks.push({ name: tool, value: pc4.dim("not installed") });
|
|
489
531
|
}
|
|
490
532
|
}
|
|
491
|
-
const body = checks.map((c) => ` ${
|
|
492
|
-
|
|
493
|
-
|
|
533
|
+
const body = checks.map((c) => ` ${pc4.cyan(c.name.padEnd(10))} ${c.value}`).join("\n");
|
|
534
|
+
p4.note(body, "Environment");
|
|
535
|
+
p4.outro(pc4.green("Environment check complete."));
|
|
494
536
|
}
|
|
495
537
|
|
|
496
538
|
// src/commands/scan.ts
|
|
497
539
|
import { createRequire } from "module";
|
|
498
540
|
import { hostname } from "os";
|
|
499
|
-
import
|
|
500
|
-
import * as p4 from "@clack/prompts";
|
|
541
|
+
import * as p5 from "@clack/prompts";
|
|
501
542
|
import {
|
|
502
543
|
RiskTier as RiskTier2,
|
|
503
544
|
Scanner as Scanner2,
|
|
504
545
|
defaultDetectors as defaultDetectors2
|
|
505
546
|
} from "@lexmanh/shed-core";
|
|
506
|
-
import
|
|
547
|
+
import pc5 from "picocolors";
|
|
507
548
|
|
|
508
549
|
// src/commands/scan-aggregate.ts
|
|
509
550
|
import { dirname } from "path";
|
|
@@ -577,13 +618,13 @@ function selectTopGroups(groups, topN) {
|
|
|
577
618
|
// src/commands/scan.ts
|
|
578
619
|
var require2 = createRequire(import.meta.url);
|
|
579
620
|
var { version: SHED_VERSION } = require2("../package.json");
|
|
580
|
-
var JSON_SCHEMA_VERSION =
|
|
621
|
+
var JSON_SCHEMA_VERSION = 2;
|
|
581
622
|
var COMPACT_TOP_N = 15;
|
|
582
623
|
var DETECTOR_BREAKDOWN_TOP_N = 6;
|
|
583
624
|
var RISK_LABEL = {
|
|
584
|
-
[RiskTier2.Green]:
|
|
585
|
-
[RiskTier2.Yellow]:
|
|
586
|
-
[RiskTier2.Red]:
|
|
625
|
+
[RiskTier2.Green]: pc5.green("\u25CF Green"),
|
|
626
|
+
[RiskTier2.Yellow]: pc5.yellow("\u25CF Yellow"),
|
|
627
|
+
[RiskTier2.Red]: pc5.red("\u25CF Red")
|
|
587
628
|
};
|
|
588
629
|
var RISK_ORDER = {
|
|
589
630
|
[RiskTier2.Red]: 0,
|
|
@@ -597,21 +638,32 @@ function formatBytes2(bytes) {
|
|
|
597
638
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
598
639
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
599
640
|
}
|
|
600
|
-
async function scanCommand(path
|
|
601
|
-
const rootDir = resolve2(path);
|
|
641
|
+
async function scanCommand(path, options = {}) {
|
|
602
642
|
if (!options.json) {
|
|
603
|
-
|
|
643
|
+
p5.intro(pc5.bgCyan(pc5.black(" shed scan ")));
|
|
604
644
|
}
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
645
|
+
const scope = await resolveScanScope({ path, nonInteractive: options.json });
|
|
646
|
+
if (!scope.ok) {
|
|
647
|
+
if (options.json) {
|
|
648
|
+
console.log(
|
|
649
|
+
JSON.stringify({ schemaVersion: JSON_SCHEMA_VERSION, error: scope.message }, null, 2)
|
|
650
|
+
);
|
|
651
|
+
} else {
|
|
652
|
+
p5.cancel(scope.message);
|
|
653
|
+
}
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const spinner4 = options.json ? null : p5.spinner();
|
|
657
|
+
verbose(`scan roots: ${scope.roots.join(", ")}`);
|
|
658
|
+
spinner4?.start(
|
|
659
|
+
scope.roots.length === 1 ? `Scanning ${scope.roots[0]} \u2026` : `Scanning ${scope.roots.length} roots \u2026`
|
|
660
|
+
);
|
|
608
661
|
const scanStartedAt = Date.now();
|
|
609
662
|
const scanner = new Scanner2(defaultDetectors2());
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
]);
|
|
663
|
+
const projectsByRoot = await Promise.all(scope.roots.map((root) => scanner.scan(root)));
|
|
664
|
+
const projects = projectsByRoot.flat();
|
|
665
|
+
const ctx = { scanRoot: scope.roots[0] ?? "/", maxDepth: 8 };
|
|
666
|
+
const globalItems = await scanner.scanGlobal(ctx);
|
|
615
667
|
const allItems = [
|
|
616
668
|
...projects.flatMap((proj) => proj.items),
|
|
617
669
|
...globalItems
|
|
@@ -623,7 +675,7 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
623
675
|
for (const item of allItems)
|
|
624
676
|
verbose(` item: ${item.risk} ${item.path} (${item.sizeBytes} bytes)`);
|
|
625
677
|
spinner4?.stop(
|
|
626
|
-
`Found ${
|
|
678
|
+
`Found ${pc5.bold(String(allItems.length))} cleanable items across ${projects.length} project(s).`
|
|
627
679
|
);
|
|
628
680
|
if (options.json) {
|
|
629
681
|
const byRisk = { green: 0, yellow: 0, red: 0 };
|
|
@@ -650,7 +702,7 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
650
702
|
arch: process.arch
|
|
651
703
|
},
|
|
652
704
|
scan: {
|
|
653
|
-
|
|
705
|
+
roots: scope.roots,
|
|
654
706
|
durationMs: Date.now() - scanStartedAt
|
|
655
707
|
},
|
|
656
708
|
summary: {
|
|
@@ -669,8 +721,8 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
669
721
|
return;
|
|
670
722
|
}
|
|
671
723
|
if (allItems.length === 0) {
|
|
672
|
-
|
|
673
|
-
|
|
724
|
+
p5.note("Nothing found to clean in this directory.", "Result");
|
|
725
|
+
p5.outro(pc5.dim("All clear!"));
|
|
674
726
|
return;
|
|
675
727
|
}
|
|
676
728
|
if (options.all) {
|
|
@@ -679,8 +731,8 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
679
731
|
renderCompact(allItems);
|
|
680
732
|
}
|
|
681
733
|
console.log();
|
|
682
|
-
|
|
683
|
-
`Total recoverable: ${
|
|
734
|
+
p5.outro(
|
|
735
|
+
`Total recoverable: ${pc5.bold(pc5.green(formatBytes2(totalBytes)))} \u2014 run ${pc5.cyan("shed clean")} to proceed.`
|
|
684
736
|
);
|
|
685
737
|
}
|
|
686
738
|
function renderCompact(allItems) {
|
|
@@ -693,25 +745,25 @@ function renderCompact(allItems) {
|
|
|
693
745
|
if (item.metadata?.detectOnly === true) detectOnly++;
|
|
694
746
|
byDetector.set(item.detector, (byDetector.get(item.detector) ?? 0) + item.sizeBytes);
|
|
695
747
|
}
|
|
696
|
-
const riskLine = `${
|
|
748
|
+
const riskLine = `${pc5.green(`\u25CF ${byRisk.green} Green`)} ${pc5.yellow(`\u25CF ${byRisk.yellow} Yellow`)} ${pc5.red(`\u25CF ${byRisk.red} Red`)}${detectOnly > 0 ? pc5.dim(` (${detectOnly} detect-only)`) : ""}`;
|
|
697
749
|
const detectorLine = [...byDetector.entries()].sort(([, a], [, b]) => b - a).slice(0, DETECTOR_BREAKDOWN_TOP_N).map(([d, b]) => `${d} ${formatBytes2(b)}`).join(" \xB7 ");
|
|
698
750
|
console.log();
|
|
699
751
|
console.log(` By risk: ${riskLine}`);
|
|
700
|
-
console.log(` By detector: ${
|
|
752
|
+
console.log(` By detector: ${pc5.dim(detectorLine)}`);
|
|
701
753
|
const groups = aggregateForDisplay(allItems);
|
|
702
754
|
const { shown, hidden } = selectTopGroups(groups, COMPACT_TOP_N);
|
|
703
755
|
console.log();
|
|
704
|
-
console.log(` ${
|
|
756
|
+
console.log(` ${pc5.bold(`Top ${shown.length} items:`)}`);
|
|
705
757
|
for (const g of shown) {
|
|
706
758
|
const path = home ? g.displayPath.replace(home, "~") : g.displayPath;
|
|
707
|
-
const tag = g.type === "aggregate" ?
|
|
708
|
-
const size = g.totalBytes > 0 ?
|
|
759
|
+
const tag = g.type === "aggregate" ? pc5.dim(` (${g.itemCount} ${g.detector} items)`) : "";
|
|
760
|
+
const size = g.totalBytes > 0 ? pc5.dim(` ${formatBytes2(g.totalBytes)}`) : "";
|
|
709
761
|
console.log(` ${RISK_LABEL[g.risk]} ${path}${tag}${size}`);
|
|
710
762
|
}
|
|
711
763
|
if (hidden.groupCount > 0) {
|
|
712
764
|
console.log();
|
|
713
765
|
console.log(
|
|
714
|
-
|
|
766
|
+
pc5.dim(
|
|
715
767
|
` \u2026 ${hidden.groupCount} more groups (${hidden.itemCount} items, ${formatBytes2(hidden.totalBytes)}) \u2014 use --all to see everything`
|
|
716
768
|
)
|
|
717
769
|
);
|
|
@@ -727,24 +779,24 @@ function renderFull(allItems) {
|
|
|
727
779
|
byProject.set(key, group);
|
|
728
780
|
}
|
|
729
781
|
for (const [projectRoot, items] of byProject.entries()) {
|
|
730
|
-
const projectLabel = projectRoot === "(global)" ?
|
|
782
|
+
const projectLabel = projectRoot === "(global)" ? pc5.dim("global caches") : pc5.cyan(home ? projectRoot.replace(home, "~") : projectRoot);
|
|
731
783
|
const groupTotal = items.reduce((s, i) => s + i.sizeBytes, 0);
|
|
732
784
|
console.log(`
|
|
733
|
-
${projectLabel} ${
|
|
785
|
+
${projectLabel} ${pc5.dim(formatBytes2(groupTotal))}`);
|
|
734
786
|
for (const item of items) {
|
|
735
|
-
const size = item.sizeBytes > 0 ?
|
|
787
|
+
const size = item.sizeBytes > 0 ? pc5.dim(` ${formatBytes2(item.sizeBytes)}`) : "";
|
|
736
788
|
const displayPath = home ? item.path.replace(home, "~") : item.path;
|
|
737
789
|
const shortPath = projectRoot !== "(global)" ? displayPath.replace(home ? projectRoot.replace(home, "~") : projectRoot, "").replace(/^\//, "") || displayPath : displayPath;
|
|
738
790
|
console.log(` ${RISK_LABEL[item.risk]} ${shortPath}${size}`);
|
|
739
|
-
console.log(` ${
|
|
791
|
+
console.log(` ${pc5.dim(` ${item.description}`)}`);
|
|
740
792
|
}
|
|
741
793
|
}
|
|
742
794
|
}
|
|
743
795
|
|
|
744
796
|
// src/commands/undo.ts
|
|
745
797
|
import { platform as platform2 } from "os";
|
|
746
|
-
import * as
|
|
747
|
-
import
|
|
798
|
+
import * as p6 from "@clack/prompts";
|
|
799
|
+
import pc6 from "picocolors";
|
|
748
800
|
function trashPath() {
|
|
749
801
|
switch (platform2()) {
|
|
750
802
|
case "darwin":
|
|
@@ -766,27 +818,27 @@ function trashOpenHint() {
|
|
|
766
818
|
}
|
|
767
819
|
}
|
|
768
820
|
async function undoCommand() {
|
|
769
|
-
|
|
770
|
-
|
|
821
|
+
p6.intro(pc6.bgMagenta(pc6.black(" shed undo ")));
|
|
822
|
+
p6.note(
|
|
771
823
|
[
|
|
772
824
|
"shed clean moves items to your OS Trash by default.",
|
|
773
825
|
"",
|
|
774
|
-
` Trash location: ${
|
|
826
|
+
` Trash location: ${pc6.cyan(trashPath())}`,
|
|
775
827
|
"",
|
|
776
|
-
` To restore: ${
|
|
828
|
+
` To restore: ${pc6.dim(trashOpenHint())}`,
|
|
777
829
|
"",
|
|
778
|
-
|
|
779
|
-
|
|
830
|
+
pc6.dim("Tip: shed clean --hard-delete bypasses Trash (no undo possible)."),
|
|
831
|
+
pc6.dim(" shed clean --dry-run to preview before any real deletion.")
|
|
780
832
|
].join("\n"),
|
|
781
833
|
"How to undo a cleanup"
|
|
782
834
|
);
|
|
783
|
-
|
|
835
|
+
p6.outro(pc6.green("Nothing to do \u2014 restore items via your OS Trash."));
|
|
784
836
|
}
|
|
785
837
|
|
|
786
838
|
// src/commands/upgrade.ts
|
|
787
|
-
import * as
|
|
839
|
+
import * as p7 from "@clack/prompts";
|
|
788
840
|
import { execa as execa2 } from "execa";
|
|
789
|
-
import
|
|
841
|
+
import pc7 from "picocolors";
|
|
790
842
|
|
|
791
843
|
// src/update/detect-install.ts
|
|
792
844
|
import { realpathSync } from "fs";
|
|
@@ -794,32 +846,32 @@ import { constants, access } from "fs/promises";
|
|
|
794
846
|
import { dirname as dirname2 } from "path";
|
|
795
847
|
var PACKAGE_NAME = "@lexmanh/shed-cli";
|
|
796
848
|
function classifyInstall(resolvedPath) {
|
|
797
|
-
const
|
|
798
|
-
if (
|
|
849
|
+
const p8 = resolvedPath.replace(/\\/g, "/").toLowerCase();
|
|
850
|
+
if (p8.includes("/_npx/") || p8.includes("/npx-cache/")) {
|
|
799
851
|
return {
|
|
800
852
|
kind: "npx",
|
|
801
853
|
note: "Running via npx (ephemeral). Re-run with `npx @lexmanh/shed-cli@latest`."
|
|
802
854
|
};
|
|
803
855
|
}
|
|
804
|
-
if (
|
|
856
|
+
if (p8.includes("/bun/install/cache/") || p8.includes("/.bun/install/cache/")) {
|
|
805
857
|
return {
|
|
806
858
|
kind: "bunx",
|
|
807
859
|
note: "Running via bunx (ephemeral). Re-run with `bunx @lexmanh/shed-cli@latest`."
|
|
808
860
|
};
|
|
809
861
|
}
|
|
810
|
-
if (
|
|
862
|
+
if (p8.includes("/.volta/") || p8.includes("/volta/tools/")) {
|
|
811
863
|
return { kind: "volta" };
|
|
812
864
|
}
|
|
813
|
-
if (
|
|
865
|
+
if (p8.includes("/pnpm/global/") || p8.includes("/library/pnpm/") || p8.includes("/.local/share/pnpm/")) {
|
|
814
866
|
return { kind: "pnpm-global" };
|
|
815
867
|
}
|
|
816
|
-
if (
|
|
868
|
+
if (p8.includes("/yarn/global/") || p8.includes("/.config/yarn/global/")) {
|
|
817
869
|
return { kind: "yarn-global" };
|
|
818
870
|
}
|
|
819
|
-
if (
|
|
871
|
+
if (p8.includes("/.bun/install/global/")) {
|
|
820
872
|
return { kind: "bun-global" };
|
|
821
873
|
}
|
|
822
|
-
if (
|
|
874
|
+
if (p8.includes("/node_modules/")) {
|
|
823
875
|
return { kind: "npm-global" };
|
|
824
876
|
}
|
|
825
877
|
return { kind: "unknown" };
|
|
@@ -849,12 +901,12 @@ function detectInstall(binPath) {
|
|
|
849
901
|
} catch {
|
|
850
902
|
resolvedPath = binPath;
|
|
851
903
|
}
|
|
852
|
-
const { kind, note:
|
|
904
|
+
const { kind, note: note8 } = classifyInstall(resolvedPath);
|
|
853
905
|
return {
|
|
854
906
|
kind,
|
|
855
907
|
upgradeCommand: buildUpgradeCommand(kind),
|
|
856
908
|
resolvedPath,
|
|
857
|
-
note:
|
|
909
|
+
note: note8
|
|
858
910
|
};
|
|
859
911
|
}
|
|
860
912
|
async function needsElevation(resolvedPath) {
|
|
@@ -945,93 +997,93 @@ function isNewer(latest, current) {
|
|
|
945
997
|
|
|
946
998
|
// src/commands/upgrade.ts
|
|
947
999
|
async function upgradeCommand(opts, currentVersion) {
|
|
948
|
-
|
|
1000
|
+
p7.intro(pc7.bgMagenta(pc7.black(" shed upgrade ")));
|
|
949
1001
|
const install = detectInstall(process.argv[1] ?? "");
|
|
950
|
-
const spin =
|
|
1002
|
+
const spin = p7.spinner();
|
|
951
1003
|
spin.start("Checking npm registry\u2026");
|
|
952
1004
|
const latest = await fetchLatestVersion({ force: true });
|
|
953
1005
|
spin.stop(latest ? `Latest: v${latest}` : "Could not reach registry");
|
|
954
1006
|
if (!latest) {
|
|
955
|
-
|
|
1007
|
+
p7.outro(pc7.yellow("No upgrade information available. Check your network and try again."));
|
|
956
1008
|
process.exit(1);
|
|
957
1009
|
}
|
|
958
1010
|
if (!isNewer(latest, currentVersion)) {
|
|
959
|
-
|
|
960
|
-
`Installed: ${
|
|
961
|
-
Latest: ${
|
|
1011
|
+
p7.note(
|
|
1012
|
+
`Installed: ${pc7.cyan(`v${currentVersion}`)}
|
|
1013
|
+
Latest: ${pc7.cyan(`v${latest}`)}`,
|
|
962
1014
|
"Already up to date"
|
|
963
1015
|
);
|
|
964
|
-
|
|
1016
|
+
p7.outro(pc7.green("Nothing to do."));
|
|
965
1017
|
return;
|
|
966
1018
|
}
|
|
967
|
-
|
|
1019
|
+
p7.note(
|
|
968
1020
|
[
|
|
969
|
-
`Installed: ${
|
|
970
|
-
`Latest: ${
|
|
971
|
-
`Source: ${
|
|
972
|
-
`Path: ${
|
|
1021
|
+
`Installed: ${pc7.dim(`v${currentVersion}`)}`,
|
|
1022
|
+
`Latest: ${pc7.green(`v${latest}`)}`,
|
|
1023
|
+
`Source: ${pc7.cyan(install.kind)}`,
|
|
1024
|
+
`Path: ${pc7.dim(install.resolvedPath)}`
|
|
973
1025
|
].join("\n"),
|
|
974
1026
|
"Upgrade available"
|
|
975
1027
|
);
|
|
976
1028
|
if (!install.upgradeCommand) {
|
|
977
|
-
|
|
1029
|
+
p7.note(
|
|
978
1030
|
install.note ?? "Could not detect how shed was installed.",
|
|
979
|
-
|
|
1031
|
+
pc7.yellow("Cannot self-upgrade")
|
|
980
1032
|
);
|
|
981
|
-
|
|
1033
|
+
p7.outro(pc7.dim("Re-install manually using your preferred package manager."));
|
|
982
1034
|
return;
|
|
983
1035
|
}
|
|
984
1036
|
const elevate = await needsElevation(install.resolvedPath);
|
|
985
1037
|
const finalCommand = elevate ? `sudo ${install.upgradeCommand}` : install.upgradeCommand;
|
|
986
1038
|
if (opts.check) {
|
|
987
|
-
|
|
988
|
-
|
|
1039
|
+
p7.note(finalCommand, "Run this to upgrade");
|
|
1040
|
+
p7.outro(pc7.dim("(--check mode: nothing executed)"));
|
|
989
1041
|
return;
|
|
990
1042
|
}
|
|
991
1043
|
if (elevate) {
|
|
992
|
-
|
|
993
|
-
|
|
1044
|
+
p7.note(finalCommand, pc7.yellow("Install dir is not writable \u2014 run this manually"));
|
|
1045
|
+
p7.outro(pc7.dim("Re-run `shed upgrade` after the install completes to verify."));
|
|
994
1046
|
return;
|
|
995
1047
|
}
|
|
996
1048
|
if (!opts.yes) {
|
|
997
|
-
const ok = await
|
|
998
|
-
if (
|
|
999
|
-
|
|
1049
|
+
const ok = await p7.confirm({ message: `Run \`${finalCommand}\` now?`, initialValue: true });
|
|
1050
|
+
if (p7.isCancel(ok) || !ok) {
|
|
1051
|
+
p7.cancel("Upgrade cancelled.");
|
|
1000
1052
|
return;
|
|
1001
1053
|
}
|
|
1002
1054
|
}
|
|
1003
|
-
const runSpin =
|
|
1055
|
+
const runSpin = p7.spinner();
|
|
1004
1056
|
runSpin.start(`Running ${finalCommand}\u2026`);
|
|
1005
1057
|
try {
|
|
1006
1058
|
const [bin, ...args] = finalCommand.split(" ");
|
|
1007
1059
|
if (!bin) throw new Error("Empty upgrade command");
|
|
1008
1060
|
await execa2(bin, args, { stdio: "pipe" });
|
|
1009
|
-
runSpin.stop(
|
|
1010
|
-
|
|
1061
|
+
runSpin.stop(pc7.green(`Upgraded to v${latest}.`));
|
|
1062
|
+
p7.outro(pc7.green("Done. Re-run `shed --version` to confirm."));
|
|
1011
1063
|
} catch (err) {
|
|
1012
|
-
runSpin.stop(
|
|
1064
|
+
runSpin.stop(pc7.red("Upgrade failed."));
|
|
1013
1065
|
const message = err instanceof Error ? err.message : String(err);
|
|
1014
|
-
|
|
1015
|
-
|
|
1066
|
+
p7.note(message, pc7.red("Error"));
|
|
1067
|
+
p7.outro(pc7.dim(`You can retry manually: ${finalCommand}`));
|
|
1016
1068
|
process.exit(1);
|
|
1017
1069
|
}
|
|
1018
1070
|
}
|
|
1019
1071
|
|
|
1020
1072
|
// src/update/notifier.ts
|
|
1021
|
-
import
|
|
1073
|
+
import pc8 from "picocolors";
|
|
1022
1074
|
function maybeNotifyOfUpdate(currentVersion) {
|
|
1023
1075
|
const cached = readCachedLatest();
|
|
1024
1076
|
if (!cached || !isNewer(cached, currentVersion)) return;
|
|
1025
1077
|
const install = detectInstall(process.argv[1] ?? "");
|
|
1026
1078
|
const cmd = install.upgradeCommand ?? "shed upgrade";
|
|
1027
1079
|
const banner = [
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1080
|
+
pc8.yellow("\u25B2"),
|
|
1081
|
+
pc8.dim(`shed v${currentVersion} \u2192`),
|
|
1082
|
+
pc8.green(`v${cached}`),
|
|
1083
|
+
pc8.dim("available."),
|
|
1084
|
+
pc8.dim("Run"),
|
|
1085
|
+
pc8.cyan("`shed upgrade`"),
|
|
1086
|
+
pc8.dim(`(or \`${cmd}\`).`)
|
|
1035
1087
|
].join(" ");
|
|
1036
1088
|
console.log(banner);
|
|
1037
1089
|
}
|
|
@@ -1042,19 +1094,22 @@ function scheduleBackgroundRefresh() {
|
|
|
1042
1094
|
}
|
|
1043
1095
|
|
|
1044
1096
|
// src/logo.ts
|
|
1045
|
-
import
|
|
1046
|
-
var
|
|
1097
|
+
import pc9 from "picocolors";
|
|
1098
|
+
var CABIN = [" \u2571\u2572 ", " \u2571\u2500\u2500\u2572 ", " \u2571\u2500\u2500\u2500\u2500\u2572 ", " \u2502 \u2588\u2588 \u2502 ", " \u2514\u2500\u2500\u2500\u2500\u2518 "];
|
|
1099
|
+
var WORDMARK = [
|
|
1047
1100
|
" ____ _ _ ",
|
|
1048
1101
|
" / ___|| |__ ___ __| |",
|
|
1049
1102
|
" \\___ \\| '_ \\ / _ \\/ _` |",
|
|
1050
1103
|
" ___) | | | | __/ (_| |",
|
|
1051
1104
|
" |____/|_| |_|\\___|\\__,_|"
|
|
1052
|
-
]
|
|
1105
|
+
];
|
|
1053
1106
|
function printLogo(version2) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1107
|
+
for (let i = 0; i < CABIN.length; i++) {
|
|
1108
|
+
console.log(pc9.yellow(CABIN[i]) + pc9.cyan(WORDMARK[i]));
|
|
1109
|
+
}
|
|
1110
|
+
console.log(` ${pc9.dim(`v${version2} \xB7 safe disk cleanup \xB7 dev machines & servers`)}`);
|
|
1056
1111
|
console.log(
|
|
1057
|
-
` ${
|
|
1112
|
+
` ${pc9.dim("by")} ${pc9.white("L\xEA Xu\xE2n M\u1EA1nh")} ${pc9.dim("\xB7 https://github.com/lexmanh/shed")}
|
|
1058
1113
|
`
|
|
1059
1114
|
);
|
|
1060
1115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexmanh/shed-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.2",
|
|
4
4
|
"description": "Safe disk cleanup CLI for dev machines and Linux servers — git-aware, cross-stack, trash-by-default",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"conf": "^13.1.0",
|
|
24
24
|
"execa": "^9.5.0",
|
|
25
25
|
"picocolors": "^1.1.1",
|
|
26
|
-
"@lexmanh/shed-core": "0.
|
|
26
|
+
"@lexmanh/shed-core": "0.3.0-beta.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.10.0",
|