@rightkit/release 0.2.158 → 0.2.160
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/disk-command.mjs +64 -15
- package/disk-policy.json +6 -1
- package/disk-policy.mjs +122 -10
- package/package.json +1 -1
- package/rightkit-versions.json +10 -5
package/disk-command.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { spawnSync } from "./background-process.mjs";
|
|
7
|
+
import { compareVersions } from "./rightkit-sync.mjs";
|
|
7
8
|
import {
|
|
8
|
-
DISK_EXIT_WARNING, applySweep, checkDisk, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, readDiskPolicy,
|
|
9
|
+
DISK_EXIT_WARNING, applySweep, checkDisk, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy,
|
|
9
10
|
} from "./disk-policy.mjs";
|
|
10
11
|
|
|
11
12
|
export const LAUNCHD_LABEL = "dev.rightkit.disk-hygiene";
|
|
@@ -54,8 +55,6 @@ function parse(rest) {
|
|
|
54
55
|
return opts;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const CHECKOUT_FILE = fileURLToPath(import.meta.url);
|
|
58
|
-
|
|
59
58
|
// A path inside any git checkout (a `.git` entry on an ancestor) is development code, never an installed package.
|
|
60
59
|
export function insideGitCheckout(file, exists = existsSync) {
|
|
61
60
|
let dir = path.dirname(file);
|
|
@@ -67,19 +66,62 @@ export function insideGitCheckout(file, exists = existsSync) {
|
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
69
|
+
const CLI_TAIL = path.join("@rightkit", "release", "cli", "right-release.mjs");
|
|
70
|
+
const SHIM_TARGET = /(%~dp0|%dp0%|\$basedir|\$PSScriptRoot)?([^\s"'%$=;~]*node_modules[\\/]@rightkit[\\/]release[\\/]cli[\\/]right-release\.mjs)/;
|
|
71
|
+
|
|
72
|
+
/** Target `cli/right-release.mjs` behind the `right-release` bin on PATH: the realpath of the bin when it is the script
|
|
73
|
+
* itself, otherwise the path a pnpm/npm shim (.cmd, .ps1 or sh script) names, resolved against the shim's directory. */
|
|
74
|
+
export function cliFromPathBin({ env = process.env, exists = existsSync, readFile = (f) => readFileSync(f, "utf8") } = {}) {
|
|
75
|
+
const found = [];
|
|
76
|
+
for (const dir of String(env.PATH ?? env.Path ?? "").split(path.delimiter).filter(Boolean)) {
|
|
77
|
+
for (const name of ["right-release", "right-release.cmd", "right-release.ps1", "right-release.mjs"]) {
|
|
78
|
+
const bin = path.join(dir, name);
|
|
79
|
+
if (!exists(bin)) continue;
|
|
80
|
+
let real = bin;
|
|
81
|
+
try { real = realpathSync(bin); } catch { /* keep lexical */ }
|
|
82
|
+
if (real.endsWith(path.join("cli", "right-release.mjs"))) { found.push(real); continue; }
|
|
83
|
+
try {
|
|
84
|
+
const m = SHIM_TARGET.exec(readFile(real));
|
|
85
|
+
if (m) found.push(path.resolve(dir, (m[1] ? m[2].replace(/^[\\/]+/, "") : m[2]).split(/[\\/]/).join(path.sep)));
|
|
86
|
+
} catch { /* unreadable shim */ }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return found;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const versionOf = (cli, readFile) => {
|
|
93
|
+
try { return JSON.parse(readFile(path.join(path.dirname(cli), "..", "package.json"))).version; } catch { return "0.0.0"; }
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Global-root candidates: `<root>/@rightkit/release/...` (npm, pnpm <= 10) and `<root>/<hash>/node_modules/@rightkit/release/...`
|
|
97
|
+
* (pnpm 11 global layout), highest installed version first. */
|
|
98
|
+
export function cliFromGlobalRoots(roots, { exists = existsSync, readdir = (d) => readdirSync(d), readFile = (f) => readFileSync(f, "utf8") } = {}) {
|
|
99
|
+
const found = [];
|
|
100
|
+
for (const root of roots) {
|
|
101
|
+
found.push(path.join(root, CLI_TAIL));
|
|
102
|
+
let names = [];
|
|
103
|
+
try { names = readdir(root); } catch { /* root absent */ }
|
|
104
|
+
for (const name of names) found.push(path.join(root, name, "node_modules", CLI_TAIL));
|
|
105
|
+
}
|
|
106
|
+
return found.filter((f) => exists(f))
|
|
107
|
+
.map((f) => ({ f, v: versionOf(f, readFile) }))
|
|
108
|
+
.sort((x, y) => { try { return compareVersions(y.v, x.v); } catch { return 0; } })
|
|
109
|
+
.map((x) => x.f);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Installed @rightkit/release CLI for the Windows task: explicit --cli / RIGHT_RELEASE_INSTALLED_CLI, else the target of the
|
|
113
|
+
* `right-release` bin on PATH, else the global package roots (`npm root -g`, `pnpm root -g`, hashed pnpm 11 layout included).
|
|
114
|
+
* Only a candidate inside a git checkout is refused; the installed package may itself run install. */
|
|
115
|
+
export function resolveInstalledCli({ explicit, env = process.env, globalRoots = defaultGlobalRoots, exists = existsSync, readFile, readdir, pathBin = cliFromPathBin } = {}) {
|
|
116
|
+
const fixed = explicit || env.RIGHT_RELEASE_INSTALLED_CLI;
|
|
117
|
+
const candidates = fixed
|
|
118
|
+
? [fixed]
|
|
119
|
+
: [...pathBin({ env, exists, ...(readFile ? { readFile } : {}) }), ...cliFromGlobalRoots(globalRoots({ env }), { exists, ...(readFile ? { readFile } : {}), ...(readdir ? { readdir } : {}) })];
|
|
78
120
|
for (const candidate of candidates) {
|
|
79
121
|
if (!exists(candidate)) continue;
|
|
80
122
|
let real = candidate;
|
|
81
123
|
try { real = realpathSync(candidate); } catch { /* keep lexical */ }
|
|
82
|
-
if (
|
|
124
|
+
if (insideGitCheckout(real, exists)) continue;
|
|
83
125
|
return candidate;
|
|
84
126
|
}
|
|
85
127
|
throw new Error("disk schedule install needs the installed @rightkit/release (not a checkout): install it globally or pass --cli <path to the installed cli/right-release.mjs>");
|
|
@@ -183,6 +225,11 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
183
225
|
out({ ...p, applied }, renderSweep(p, applied));
|
|
184
226
|
return { exitCode: applied.results.some((r) => !r.ok) ? 1 : 0, plan: p, applied };
|
|
185
227
|
}
|
|
228
|
+
if (command === "purge") {
|
|
229
|
+
const purged = purgeHoldings({ policy, platform, env, days: opts.days, dryRun: opts.dryRun, ...deps.purge });
|
|
230
|
+
out(purged, `${opts.dryRun ? "Dry run: would purge" : "Purged"} ${purged.results.length} holding dir(s)${purged.results.map((r) => `\n ${formatBytes(r.bytes).padStart(9)} ${r.path}${r.ok ? "" : ` FAILED: ${r.error}`}`).join("")}`);
|
|
231
|
+
return { exitCode: purged.results.some((r) => !r.ok) ? 1 : 0, purged };
|
|
232
|
+
}
|
|
186
233
|
if (command === "run") {
|
|
187
234
|
const before = check();
|
|
188
235
|
stdout(renderCheck(before));
|
|
@@ -190,13 +237,15 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
190
237
|
stdout(renderSweep(p));
|
|
191
238
|
const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
|
|
192
239
|
stdout(`moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length})`);
|
|
240
|
+
const purged = purgeHoldings({ policy, platform, env, ...deps.purge });
|
|
241
|
+
stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
|
|
193
242
|
const after = check();
|
|
194
243
|
const warn = after.warn;
|
|
195
244
|
logEvent(logFile, { event: warn ? "disk.floor.warn" : "disk.run.ok", warnings: after.warnings, freeBytes: after.floor.freeBytes, movedBytes: applied.movedBytes });
|
|
196
245
|
if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
|
|
197
|
-
return { exitCode: warn ? DISK_EXIT_WARNING : 0, before, after, applied };
|
|
246
|
+
return { exitCode: warn ? DISK_EXIT_WARNING : 0, before, after, applied, purged };
|
|
198
247
|
}
|
|
199
|
-
throw new Error("disk usage: check|sweep [--dry-run|--delete] [--days N]|run|schedule install|uninstall|status [--json]");
|
|
248
|
+
throw new Error("disk usage: check|sweep [--dry-run|--delete] [--days N]|purge [--dry-run] [--days N]|run|schedule install|uninstall|status [--json]");
|
|
200
249
|
}
|
|
201
250
|
|
|
202
251
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/disk-policy.json
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
"mac": {"path": "/System/Volumes/Data", "label": "Mac internal", "warnFreeBytes": 42949672960},
|
|
6
6
|
"win": {"path": "D:\\", "label": "Dell D:", "warnFreeBytes": 429496729600, "warnFreePercent": 10}
|
|
7
7
|
},
|
|
8
|
+
"holdingDays": 3,
|
|
9
|
+
"holding": {
|
|
10
|
+
"mac": [{"prefix": "/", "path": "~/.rightkit-swept"}],
|
|
11
|
+
"win": [{"prefix": "C:\\", "path": "${LOCALAPPDATA}\\Orthic Labs\\.rightkit-swept"}]
|
|
12
|
+
},
|
|
8
13
|
"heavyVolume": {
|
|
9
14
|
"mac": "/Volumes/D/claude/tmp/swept",
|
|
10
15
|
"win": "D:\\claude\\tmp\\swept"
|
|
@@ -44,5 +49,5 @@
|
|
|
44
49
|
{"id": "leftover-legion-final", "platform": "mac", "path": "/Volumes/D/claude/legion-final-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
|
|
45
50
|
{"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"}
|
|
46
51
|
],
|
|
47
|
-
"protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed"]
|
|
52
|
+
"protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed", ".rightkit-swept"]
|
|
48
53
|
}
|
package/disk-policy.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, appendFileSync } from "node:fs";
|
|
1
|
+
import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, appendFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -224,9 +224,68 @@ export function planSweep({ policy, platform = process.platform, env = process.e
|
|
|
224
224
|
return { schema: 1, platform: key, staleDays, candidates, skipped, totalBytes: candidates.reduce((s, c) => s + c.bytes, 0) };
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
export
|
|
227
|
+
export const HOLDING_DIR = ".rightkit-swept";
|
|
228
|
+
export const HOLDING_MARKER = ".rightkit-release-holding";
|
|
229
|
+
const DATE_DIR = /^\d{4}-\d{2}-\d{2}$/;
|
|
230
|
+
|
|
231
|
+
// Device id of a path, or of its nearest existing ancestor (null when none can be read).
|
|
232
|
+
export function deviceOf(target) {
|
|
233
|
+
let cur = target;
|
|
234
|
+
for (;;) {
|
|
235
|
+
try { return statSync(cur).dev; } catch {
|
|
236
|
+
const up = path.dirname(cur);
|
|
237
|
+
if (up === cur) return null;
|
|
238
|
+
cur = up;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Highest ancestor of `target` still on the same device: the mount point (or drive root) holding it.
|
|
244
|
+
export function volumeRoot(target) {
|
|
245
|
+
const dev = deviceOf(target);
|
|
246
|
+
let cur = target;
|
|
247
|
+
for (;;) {
|
|
248
|
+
const up = path.dirname(cur);
|
|
249
|
+
if (up === cur || deviceOf(up) !== dev) return cur;
|
|
250
|
+
cur = up;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const isUnder = (candidate, prefix, win) => {
|
|
255
|
+
const norm = (v) => (win ? v.toLowerCase() : v);
|
|
256
|
+
const c = norm(candidate); const p = norm(prefix);
|
|
257
|
+
const withSep = p.endsWith(path.sep) || p.endsWith("/") || p.endsWith("\\") ? p : p + path.sep;
|
|
258
|
+
return c === p || c.startsWith(withSep);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Candidate holding bases for an item, best first: policy per-volume rules (longest prefix), then the item's own volume root.
|
|
262
|
+
export function holdingBases(candidate, { policy, platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
228
263
|
const key = platformKey(platform);
|
|
229
|
-
|
|
264
|
+
const out = [];
|
|
265
|
+
const rules = (policy.holding?.[key] ?? []).flatMap((r) => {
|
|
266
|
+
try { return [{ prefix: expandPolicyPath(r.prefix, { env, home }), path: expandPolicyPath(r.path, { env, home }) }]; } catch { return []; }
|
|
267
|
+
}).filter((r) => isUnder(candidate, r.prefix, key === "win")).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
268
|
+
for (const r of rules) out.push(r.path);
|
|
269
|
+
out.push(path.join(volumeRoot(path.dirname(candidate)), HOLDING_DIR));
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Create (and mark) today's holding dir on the item's own volume; null when no holding dir on that volume is possible.
|
|
274
|
+
export function pickHolding(candidate, { policy, platform = process.platform, env = process.env, home = os.homedir(), date = new Date(), bases } = {}) {
|
|
275
|
+
const dev = deviceOf(path.dirname(candidate));
|
|
276
|
+
for (const base of bases ?? holdingBases(candidate, { policy, platform, env, home })) {
|
|
277
|
+
try {
|
|
278
|
+
if (deviceOf(base) !== dev) continue; // checked before creating anything: a base on another volume would free nothing
|
|
279
|
+
mkdirSync(base, { recursive: true });
|
|
280
|
+
if (deviceOf(base) !== dev || lstatSync(base).isSymbolicLink()) continue;
|
|
281
|
+
const marker = path.join(base, HOLDING_MARKER);
|
|
282
|
+
if (!existsSync(marker)) writeFileSync(marker, "created by right-release disk; date-named children are purged after the retention window\n");
|
|
283
|
+
const dir = path.join(base, date.toISOString().slice(0, 10));
|
|
284
|
+
mkdirSync(dir, { recursive: true });
|
|
285
|
+
return dir;
|
|
286
|
+
} catch { /* try the next base */ }
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
230
289
|
}
|
|
231
290
|
|
|
232
291
|
export function moveEntry(source, destination) {
|
|
@@ -248,19 +307,27 @@ export function logEvent(logFile, event) {
|
|
|
248
307
|
} catch { /* logging must never fail the sweep */ }
|
|
249
308
|
}
|
|
250
309
|
|
|
251
|
-
export function applySweep({ plan, policy, platform = process.platform, deleteMode = false, date = new Date(), move = moveEntry, remove = (p) => rmSync(p, { recursive: true, force: true }) }) {
|
|
252
|
-
const destRoot = sweptDestination({ policy, platform, date });
|
|
310
|
+
export function applySweep({ plan, policy, platform = process.platform, deleteMode = false, date = new Date(), env = process.env, home = os.homedir(), move = moveEntry, pick = pickHolding, remove = (p) => rmSync(p, { recursive: true, force: true }) }) {
|
|
253
311
|
const logFile = path.join(policy.heavyVolume[platformKey(platform)], "disk-hygiene.jsonl");
|
|
254
312
|
const results = [];
|
|
255
313
|
for (const c of plan.candidates) {
|
|
256
|
-
const rel = path.join(c.writer, path.basename(c.path));
|
|
257
|
-
let destination = path.join(destRoot, rel);
|
|
258
|
-
if (existsSync(destination)) destination = `${destination}.${date.getTime()}`;
|
|
259
314
|
try {
|
|
260
315
|
// Re-verify at apply time: still the planned unit, not a symlink, still inside its root.
|
|
261
316
|
if (!c.root || lstatSync(c.path).isSymbolicLink() || !isInsideRoot(c.path, c.root)) throw new Error("refused: path is not a sweepable unit inside its target root");
|
|
262
|
-
if (deleteMode) { remove(c.path); results.push({ ...c, action: "delete", ok: true }); logEvent(logFile, { event: "disk.sweep.delete", ...c }); }
|
|
263
|
-
|
|
317
|
+
if (deleteMode) { remove(c.path); results.push({ ...c, action: "delete", ok: true }); logEvent(logFile, { event: "disk.sweep.delete", ...c }); continue; }
|
|
318
|
+
// An item stays on its own volume: moving it elsewhere frees nothing on the volume that is short of space.
|
|
319
|
+
const holding = pick(c.path, { policy, platform, env, home, date });
|
|
320
|
+
if (!holding) {
|
|
321
|
+
remove(c.path);
|
|
322
|
+
results.push({ ...c, action: "delete", how: "no-holding-on-volume", ok: true });
|
|
323
|
+
logEvent(logFile, { event: "disk.sweep.delete", reason: "no holding dir possible on the item's volume", ...c });
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
let destination = path.join(holding, c.writer, path.basename(c.path));
|
|
327
|
+
if (existsSync(destination)) destination = `${destination}.${date.getTime()}`;
|
|
328
|
+
const how = move(c.path, destination);
|
|
329
|
+
results.push({ ...c, action: "move", destination, how, ok: true });
|
|
330
|
+
logEvent(logFile, { event: "disk.sweep.move", ...c, destination, how });
|
|
264
331
|
} catch (error) {
|
|
265
332
|
results.push({ ...c, action: deleteMode ? "delete" : "move", ok: false, error: error.message });
|
|
266
333
|
logEvent(logFile, { event: "disk.sweep.error", ...c, error: error.message });
|
|
@@ -268,3 +335,48 @@ export function applySweep({ plan, policy, platform = process.platform, deleteMo
|
|
|
268
335
|
}
|
|
269
336
|
return { logFile, results, movedBytes: results.filter((r) => r.ok).reduce((s, r) => s + r.bytes, 0) };
|
|
270
337
|
}
|
|
338
|
+
|
|
339
|
+
// Holding bases that may exist: policy rule paths, each drive root (Windows) or /Volumes/* and the legacy heavy dir (Mac).
|
|
340
|
+
export function knownHoldingBases({ policy, platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
341
|
+
const key = platformKey(platform);
|
|
342
|
+
const bases = new Set();
|
|
343
|
+
for (const r of policy.holding?.[key] ?? []) { try { bases.add(expandPolicyPath(r.path, { env, home })); } catch { /* unset variable */ } }
|
|
344
|
+
if (key === "win") for (const letter of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { const root = `${letter}:\\`; if (existsSync(root)) bases.add(path.join(root, HOLDING_DIR)); }
|
|
345
|
+
else { bases.add(path.join(path.sep, HOLDING_DIR)); try { for (const v of readdirSync("/Volumes")) bases.add(path.join("/Volumes", v, HOLDING_DIR)); } catch { /* no /Volumes */ } }
|
|
346
|
+
return [...bases];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Remove date-named holding dirs older than `days`. Only dirs this tool created: the base must carry the holding marker
|
|
350
|
+
* (or, for the legacy heavy dir, the tool's own log), the child must be a real directory named YYYY-MM-DD, never a symlink. */
|
|
351
|
+
export function purgeHoldings({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), days = policy.holdingDays ?? 3, dryRun = false, bases, remove = (p) => rmSync(p, { recursive: true, force: true }), size = walkSize } = {}) {
|
|
352
|
+
const key = platformKey(platform);
|
|
353
|
+
const legacy = policy.heavyVolume?.[key];
|
|
354
|
+
const list = bases ?? [...knownHoldingBases({ policy, platform, env, home }), ...(legacy ? [legacy] : [])];
|
|
355
|
+
const logFile = legacy ? path.join(legacy, "disk-hygiene.jsonl") : null;
|
|
356
|
+
const cutoff = now - days * 86400_000;
|
|
357
|
+
const results = [];
|
|
358
|
+
for (const base of new Set(list)) {
|
|
359
|
+
let st;
|
|
360
|
+
try { st = lstatSync(base); } catch { continue; }
|
|
361
|
+
if (!st.isDirectory() || st.isSymbolicLink()) continue;
|
|
362
|
+
const owned = existsSync(path.join(base, HOLDING_MARKER)) || (base === legacy && existsSync(path.join(base, "disk-hygiene.jsonl")));
|
|
363
|
+
if (!owned) continue;
|
|
364
|
+
let names = [];
|
|
365
|
+
try { names = readdirSync(base); } catch { continue; }
|
|
366
|
+
for (const name of names) {
|
|
367
|
+
if (!DATE_DIR.test(name)) continue;
|
|
368
|
+
const dir = path.join(base, name);
|
|
369
|
+
let dst;
|
|
370
|
+
try { dst = lstatSync(dir); } catch { continue; }
|
|
371
|
+
if (!dst.isDirectory() || dst.isSymbolicLink()) continue;
|
|
372
|
+
const stamp = Date.parse(`${name}T00:00:00Z`);
|
|
373
|
+
if (!Number.isFinite(stamp) || stamp + 86400_000 > cutoff) continue; // the whole day must be older than the window
|
|
374
|
+
const bytes = size(dir);
|
|
375
|
+
try {
|
|
376
|
+
if (!dryRun) { remove(dir); if (logFile) logEvent(logFile, { event: "disk.purge", path: dir, bytes }); }
|
|
377
|
+
results.push({ path: dir, bytes, ok: true, dryRun });
|
|
378
|
+
} catch (error) { results.push({ path: dir, bytes, ok: false, error: error.message }); }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { results, purgedBytes: results.filter((r) => r.ok && !r.dryRun).reduce((a, r) => a + r.bytes, 0) };
|
|
382
|
+
}
|
package/package.json
CHANGED
package/rightkit-versions.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"stagedNpm": {
|
|
16
16
|
"@rightkit/app-update": "0.2.5",
|
|
17
17
|
"@rightkit/ax": "0.3.2",
|
|
18
|
-
"@rightkit/ci": "0.2.
|
|
18
|
+
"@rightkit/ci": "0.2.44",
|
|
19
19
|
"@rightkit/doc-render": "0.1.6",
|
|
20
20
|
"@rightkit/hooks": "0.1.2",
|
|
21
21
|
"@rightkit/i18n": "0.1.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@rightkit/qa": "0.4.8",
|
|
32
32
|
"@rightkit/qa-chrome": "0.1.6",
|
|
33
33
|
"@rightkit/react-shell": "0.6.4",
|
|
34
|
-
"@rightkit/release": "0.2.
|
|
34
|
+
"@rightkit/release": "0.2.160",
|
|
35
35
|
"@rightkit/settings-ui": "0.4.4",
|
|
36
36
|
"@rightkit/tauri": "0.2.1",
|
|
37
37
|
"@rightkit/tauri-shell": "0.1.1",
|
|
@@ -199,7 +199,9 @@
|
|
|
199
199
|
"0.2.150",
|
|
200
200
|
"0.2.151",
|
|
201
201
|
"0.2.155",
|
|
202
|
-
"0.2.156"
|
|
202
|
+
"0.2.156",
|
|
203
|
+
"0.2.158",
|
|
204
|
+
"0.2.159"
|
|
203
205
|
],
|
|
204
206
|
"@rightkit/qa": [
|
|
205
207
|
"0.1.0",
|
|
@@ -326,6 +328,9 @@
|
|
|
326
328
|
],
|
|
327
329
|
"@rightkit/shell": [
|
|
328
330
|
"0.1.0"
|
|
331
|
+
],
|
|
332
|
+
"@rightkit/ci": [
|
|
333
|
+
"0.2.43"
|
|
329
334
|
]
|
|
330
335
|
},
|
|
331
336
|
"cargo": {
|
|
@@ -488,7 +493,7 @@
|
|
|
488
493
|
"@rightkit/app-shell": "0.6.2",
|
|
489
494
|
"@rightkit/app-update": "0.2.5",
|
|
490
495
|
"@rightkit/ax": "0.3.2",
|
|
491
|
-
"@rightkit/ci": "0.2.
|
|
496
|
+
"@rightkit/ci": "0.2.44",
|
|
492
497
|
"@rightkit/doc-render": "0.1.6",
|
|
493
498
|
"@rightkit/git": "0.2.42",
|
|
494
499
|
"@rightkit/hooks": "0.1.2",
|
|
@@ -505,7 +510,7 @@
|
|
|
505
510
|
"@rightkit/qa": "0.4.8",
|
|
506
511
|
"@rightkit/qa-chrome": "0.1.6",
|
|
507
512
|
"@rightkit/react-shell": "0.6.4",
|
|
508
|
-
"@rightkit/release": "0.2.
|
|
513
|
+
"@rightkit/release": "0.2.159",
|
|
509
514
|
"@rightkit/settings-ui": "0.4.4",
|
|
510
515
|
"@rightkit/shell": "0.1.1",
|
|
511
516
|
"@rightkit/tauri": "0.2.1",
|