@git.zone/tsdisk 1.1.0 → 1.3.0
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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +7 -1
- package/dist_ts/index.js +6 -2
- package/dist_ts/tsdisk.cleanup.d.ts +26 -0
- package/dist_ts/tsdisk.cleanup.js +128 -0
- package/dist_ts/tsdisk.cli.d.ts +44 -8
- package/dist_ts/tsdisk.cli.js +247 -176
- package/dist_ts/tsdisk.plugins.d.ts +2 -2
- package/dist_ts/tsdisk.plugins.js +3 -3
- package/dist_ts/tsdisk.progress.d.ts +31 -0
- package/dist_ts/tsdisk.progress.js +64 -0
- package/dist_ts/tsdisk.scan.d.ts +22 -0
- package/dist_ts/tsdisk.scan.js +147 -0
- package/dist_ts/tsdisk.toolcaches.js +27 -9
- package/dist_ts/tsdisk.usage.d.ts +50 -0
- package/dist_ts/tsdisk.usage.js +194 -0
- package/package.json +4 -4
- package/readme.md +45 -20
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +9 -0
- package/ts/tsdisk.cleanup.ts +120 -0
- package/ts/tsdisk.cli.ts +216 -199
- package/ts/tsdisk.plugins.ts +2 -1
- package/ts/tsdisk.progress.ts +78 -0
- package/ts/tsdisk.scan.ts +151 -0
- package/ts/tsdisk.toolcaches.ts +28 -8
- package/ts/tsdisk.usage.ts +199 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createReadStream, type ReadStream } from 'node:fs';
|
|
2
|
+
import { constants, createReadStream, type ReadStream } from 'node:fs';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline/promises';
|
|
6
|
-
export { createInterface, createReadStream, fs, path, spawn, type ReadStream, };
|
|
6
|
+
export { constants, createInterface, createReadStream, fs, path, spawn, type ReadStream, };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// node native
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { constants, createReadStream } from 'node:fs';
|
|
4
4
|
import * as fs from 'node:fs/promises';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
7
|
-
export { createInterface, createReadStream, fs, path, spawn, };
|
|
8
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
7
|
+
export { constants, createInterface, createReadStream, fs, path, spawn, };
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHNkaXNrLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90c2Rpc2sucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBQ2QsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxTQUFTLEVBQUUsZ0JBQWdCLEVBQW1CLE1BQU0sU0FBUyxDQUFDO0FBQ3ZFLE9BQU8sS0FBSyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDdkMsT0FBTyxLQUFLLElBQUksTUFBTSxXQUFXLENBQUM7QUFDbEMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRXpELE9BQU8sRUFDTCxTQUFTLEVBQ1QsZUFBZSxFQUNmLGdCQUFnQixFQUNoQixFQUFFLEVBQ0YsSUFBSSxFQUNKLEtBQUssR0FFTixDQUFDIn0=
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type IUsageEntry } from './tsdisk.usage.js';
|
|
2
|
+
import type { IToolCacheFinding } from './tsdisk.toolcaches.js';
|
|
3
|
+
export interface IReclaimEstimate {
|
|
4
|
+
candidateBytes: number;
|
|
5
|
+
unknownSizeCount: number;
|
|
6
|
+
protectedCount: number;
|
|
7
|
+
confirmedReclaimableBytes: null;
|
|
8
|
+
status: 'owner-review-required';
|
|
9
|
+
}
|
|
10
|
+
/** Do not sum parents and children, or treat a protected descendant as removable. */
|
|
11
|
+
export declare function estimateReclaimableSpace(findings: IToolCacheFinding[]): IReclaimEstimate;
|
|
12
|
+
export interface IDiskProgress {
|
|
13
|
+
stage: string;
|
|
14
|
+
cacheCount: number;
|
|
15
|
+
measuredBytes: number;
|
|
16
|
+
reclaim: IReclaimEstimate;
|
|
17
|
+
largest: IUsageEntry[];
|
|
18
|
+
}
|
|
19
|
+
export declare function formatBytes(bytes: number): string;
|
|
20
|
+
/** Timer ownership is explicit; callers stop it in finally even on failure. */
|
|
21
|
+
export declare class DiskProgressReporter {
|
|
22
|
+
private write;
|
|
23
|
+
private timer?;
|
|
24
|
+
private startedAt;
|
|
25
|
+
private state;
|
|
26
|
+
constructor(write: (line: string) => void);
|
|
27
|
+
update(update: Partial<IDiskProgress>): void;
|
|
28
|
+
start(): void;
|
|
29
|
+
private print;
|
|
30
|
+
stop(stage?: string): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { containsPath } from './tsdisk.usage.js';
|
|
2
|
+
/** Do not sum parents and children, or treat a protected descendant as removable. */
|
|
3
|
+
export function estimateReclaimableSpace(findings) {
|
|
4
|
+
const protectedPaths = findings.filter((finding) => finding.marker?.safeToPrune === false).map((finding) => finding.path);
|
|
5
|
+
const candidates = findings.filter((finding) => !protectedPaths.some((path) => containsPath(path, finding.path) || containsPath(finding.path, path)));
|
|
6
|
+
const selected = [];
|
|
7
|
+
for (const finding of [...candidates].sort((a, b) => a.path.length - b.path.length)) {
|
|
8
|
+
if (finding.bytes === undefined || selected.some((parent) => containsPath(parent.path, finding.path)))
|
|
9
|
+
continue;
|
|
10
|
+
selected.push(finding);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
candidateBytes: selected.reduce((sum, finding) => sum + (finding.bytes ?? 0), 0),
|
|
14
|
+
unknownSizeCount: candidates.filter((finding) => finding.bytes === undefined).length,
|
|
15
|
+
protectedCount: protectedPaths.length,
|
|
16
|
+
confirmedReclaimableBytes: null,
|
|
17
|
+
status: 'owner-review-required',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function formatBytes(bytes) {
|
|
21
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
22
|
+
return 'n/a';
|
|
23
|
+
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
24
|
+
let value = bytes;
|
|
25
|
+
let index = 0;
|
|
26
|
+
while (value >= 1024 && index < units.length - 1) {
|
|
27
|
+
value /= 1024;
|
|
28
|
+
index++;
|
|
29
|
+
}
|
|
30
|
+
return `${value.toFixed(index === 0 || value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[index]}`;
|
|
31
|
+
}
|
|
32
|
+
/** Timer ownership is explicit; callers stop it in finally even on failure. */
|
|
33
|
+
export class DiskProgressReporter {
|
|
34
|
+
write;
|
|
35
|
+
timer;
|
|
36
|
+
startedAt = Date.now();
|
|
37
|
+
state = { stage: 'starting', cacheCount: 0, measuredBytes: 0, reclaim: estimateReclaimableSpace([]), largest: [] };
|
|
38
|
+
constructor(write) {
|
|
39
|
+
this.write = write;
|
|
40
|
+
}
|
|
41
|
+
update(update) { Object.assign(this.state, update); }
|
|
42
|
+
start() {
|
|
43
|
+
if (this.timer)
|
|
44
|
+
return;
|
|
45
|
+
this.startedAt = Date.now();
|
|
46
|
+
this.print();
|
|
47
|
+
this.timer = setInterval(() => this.print(), 5000);
|
|
48
|
+
this.timer.unref();
|
|
49
|
+
}
|
|
50
|
+
print() {
|
|
51
|
+
const state = this.state;
|
|
52
|
+
const top = state.largest.slice(0, 3).map((entry) => `${formatBytes(entry.bytes)}${entry.complete ? '' : '+'} ${JSON.stringify(entry.path)}`).join('; ');
|
|
53
|
+
this.write(`[tsdisk ${Math.floor((Date.now() - this.startedAt) / 1000)}s] ${state.stage} | measured ${formatBytes(state.measuredBytes)} | ${state.cacheCount} caches | potential cleanup ${formatBytes(state.reclaim.candidateBytes)} (owner review; ${state.reclaim.unknownSizeCount} unsized) | confirmed reclaimable: unknown${top ? ` | largest: ${top}` : ''}`);
|
|
54
|
+
}
|
|
55
|
+
stop(stage = 'finished') {
|
|
56
|
+
if (!this.timer)
|
|
57
|
+
return;
|
|
58
|
+
clearInterval(this.timer);
|
|
59
|
+
this.timer = undefined;
|
|
60
|
+
this.state.stage = stage;
|
|
61
|
+
this.print();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHNkaXNrLnByb2dyZXNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvdHNkaXNrLnByb2dyZXNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxZQUFZLEVBQW9CLE1BQU0sbUJBQW1CLENBQUM7QUFXbkUscUZBQXFGO0FBQ3JGLE1BQU0sVUFBVSx3QkFBd0IsQ0FBQyxRQUE2QjtJQUNwRSxNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLFdBQVcsS0FBSyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMxSCxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLFlBQVksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0SixNQUFNLFFBQVEsR0FBd0IsRUFBRSxDQUFDO0lBQ3pDLEtBQUssTUFBTSxPQUFPLElBQUksQ0FBQyxHQUFHLFVBQVUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUNwRixJQUFJLE9BQU8sQ0FBQyxLQUFLLEtBQUssU0FBUyxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMs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
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type IToolCacheFinding } from './tsdisk.toolcaches.js';
|
|
2
|
+
export interface IWorkspaceScanOptions {
|
|
3
|
+
maxDepth: number;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
maxEntries: number;
|
|
6
|
+
onProgress?: (result: IWorkspaceScanResult) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface IScanIssue {
|
|
9
|
+
path: string;
|
|
10
|
+
reason: 'read-error' | 'invalid-marker' | 'depth-limit' | 'entry-limit' | 'timeout' | 'mount-boundary' | 'symlink' | 'size-unavailable';
|
|
11
|
+
detail: string;
|
|
12
|
+
}
|
|
13
|
+
export interface IWorkspaceScanResult {
|
|
14
|
+
root: string;
|
|
15
|
+
findings: IToolCacheFinding[];
|
|
16
|
+
issues: IScanIssue[];
|
|
17
|
+
complete: boolean;
|
|
18
|
+
visitedEntries: number;
|
|
19
|
+
issueCounts: Partial<Record<IScanIssue['reason'], number>>;
|
|
20
|
+
usagePriorityPaths: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function scanWorkspace(root: string, options: IWorkspaceScanOptions): Promise<IWorkspaceScanResult>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as plugins from './tsdisk.plugins.js';
|
|
2
|
+
import { classifyToolCachePath, parseToolCacheMarker, toolCacheMarkerFile } from './tsdisk.toolcaches.js';
|
|
3
|
+
const excludedDirectories = new Set(['.git', 'node_modules', 'dist_ts', 'dist_ts_web', 'coverage', '.pnpm-store']);
|
|
4
|
+
export async function scanWorkspace(root, options) {
|
|
5
|
+
for (const name of ['maxDepth', 'timeoutMs', 'maxEntries']) {
|
|
6
|
+
const value = options[name];
|
|
7
|
+
if (!Number.isSafeInteger(value) || value < (name === 'maxDepth' ? 0 : 1)) {
|
|
8
|
+
throw new Error(`Invalid scan option ${name}: ${value}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const result = { root: plugins.path.resolve(root), findings: [], issues: [], issueCounts: {}, usagePriorityPaths: [], complete: true, visitedEntries: 0 };
|
|
12
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
13
|
+
let stopped = false;
|
|
14
|
+
const issue = (path, reason, detail) => {
|
|
15
|
+
result.complete = false;
|
|
16
|
+
result.issueCounts[reason] = (result.issueCounts[reason] ?? 0) + 1;
|
|
17
|
+
if (result.issues.length < 200)
|
|
18
|
+
result.issues.push({ path, reason, detail });
|
|
19
|
+
};
|
|
20
|
+
const limited = (path) => {
|
|
21
|
+
if (stopped)
|
|
22
|
+
return true;
|
|
23
|
+
if (Date.now() >= deadline || result.visitedEntries >= options.maxEntries) {
|
|
24
|
+
issue(path, Date.now() >= deadline ? 'timeout' : 'entry-limit', 'Scan stopped before visiting all entries.');
|
|
25
|
+
stopped = true;
|
|
26
|
+
}
|
|
27
|
+
return stopped;
|
|
28
|
+
};
|
|
29
|
+
let rootDev;
|
|
30
|
+
try {
|
|
31
|
+
const stat = await plugins.fs.lstat(result.root);
|
|
32
|
+
if (stat.isSymbolicLink() || await plugins.fs.realpath(result.root) !== result.root) {
|
|
33
|
+
issue(result.root, 'symlink', 'Scan roots must not contain symbolic links.');
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
if (!stat.isDirectory())
|
|
37
|
+
throw new Error('Scan root is not a directory.');
|
|
38
|
+
rootDev = stat.dev;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
issue(result.root, 'read-error', error instanceof Error ? error.message : String(error));
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
const queue = [{ directory: result.root, depth: 0 }];
|
|
45
|
+
let queueLimited = false;
|
|
46
|
+
const visit = async (directory, depth) => {
|
|
47
|
+
if (limited(directory))
|
|
48
|
+
return;
|
|
49
|
+
result.visitedEntries++;
|
|
50
|
+
try {
|
|
51
|
+
const stat = await plugins.fs.lstat(directory);
|
|
52
|
+
if (stat.isSymbolicLink()) {
|
|
53
|
+
issue(directory, 'symlink', 'Symbolic link skipped.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (stat.dev !== rootDev) {
|
|
57
|
+
issue(directory, 'mount-boundary', 'Different filesystem skipped.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (plugins.path.basename(directory) === '.nogit')
|
|
61
|
+
result.usagePriorityPaths.push(directory);
|
|
62
|
+
let marker;
|
|
63
|
+
const markerPath = plugins.path.join(directory, toolCacheMarkerFile);
|
|
64
|
+
const markerStat = await plugins.fs.lstat(markerPath).catch((error) => {
|
|
65
|
+
if (error.code === 'ENOENT')
|
|
66
|
+
return undefined;
|
|
67
|
+
throw error;
|
|
68
|
+
});
|
|
69
|
+
if (markerStat) {
|
|
70
|
+
if (!markerStat.isFile() || markerStat.size > 65536) {
|
|
71
|
+
issue(markerPath, 'invalid-marker', 'Marker must be a regular file of at most 64 KiB.');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
marker = parseToolCacheMarker(await plugins.fs.readFile(markerPath, 'utf8'));
|
|
75
|
+
if (!marker)
|
|
76
|
+
issue(markerPath, 'invalid-marker', 'Invalid cache ownership marker.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const heuristic = classifyToolCachePath(directory);
|
|
80
|
+
const classification = classifyToolCachePath(directory, marker);
|
|
81
|
+
if (classification)
|
|
82
|
+
result.findings.push({ path: directory, ...classification });
|
|
83
|
+
// Cache contents are not workspaces. Registry session directories can carry their own markers.
|
|
84
|
+
if (classification && heuristic?.kind !== 'docker-registry-cache')
|
|
85
|
+
return;
|
|
86
|
+
let enumerated = 0;
|
|
87
|
+
const children = await plugins.fs.opendir(directory);
|
|
88
|
+
for await (const child of children) {
|
|
89
|
+
// A huge flat directory must not consume the budget of sibling projects.
|
|
90
|
+
if (++enumerated > 4096) {
|
|
91
|
+
issue(directory, 'entry-limit', 'Directory listing stopped at 4096 entries; sibling directories are still scanned.');
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (excludedDirectories.has(child.name))
|
|
95
|
+
continue;
|
|
96
|
+
if (!child.isDirectory() && !child.isSymbolicLink())
|
|
97
|
+
continue;
|
|
98
|
+
if (depth >= options.maxDepth) {
|
|
99
|
+
issue(directory, 'depth-limit', `Maximum scan depth ${options.maxDepth} reached.`);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
const childPath = plugins.path.join(directory, child.name);
|
|
103
|
+
if (limited(childPath))
|
|
104
|
+
return;
|
|
105
|
+
if (child.isSymbolicLink()) {
|
|
106
|
+
result.visitedEntries++;
|
|
107
|
+
issue(childPath, 'symlink', 'Symbolic link skipped.');
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (heuristic?.kind === 'docker-registry-cache') {
|
|
111
|
+
// Only direct sessions, never registry blob trees.
|
|
112
|
+
const sessionMarker = await plugins.fs.lstat(plugins.path.join(childPath, toolCacheMarkerFile)).catch((error) => {
|
|
113
|
+
if (error.code === 'ENOENT')
|
|
114
|
+
return undefined;
|
|
115
|
+
throw error;
|
|
116
|
+
});
|
|
117
|
+
if (!sessionMarker) {
|
|
118
|
+
result.visitedEntries++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (queue.length >= options.maxEntries) {
|
|
123
|
+
if (!queueLimited)
|
|
124
|
+
issue(directory, 'entry-limit', 'Directory queue budget exhausted; already queued siblings are still scanned.');
|
|
125
|
+
queueLimited = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
queue.push({ directory: childPath, depth: depth + 1 });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
issue(directory, 'read-error', error instanceof Error ? error.message : String(error));
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
// Breadth first: canonical projects precede deeply nested retained worktrees.
|
|
136
|
+
let lastProgress = 0;
|
|
137
|
+
for (let index = 0; index < queue.length && !stopped; index++) {
|
|
138
|
+
await visit(queue[index].directory, queue[index].depth);
|
|
139
|
+
if (Date.now() - lastProgress >= 250) {
|
|
140
|
+
options.onProgress?.(result);
|
|
141
|
+
lastProgress = Date.now();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
options.onProgress?.(result);
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHNkaXNrLnNjYW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90c2Rpc2suc2Nhbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxvQkFBb0IsRUFBRSxtQkFBbUIsRUFBMEIsTUFBTSx3QkFBd0IsQ0FBQztBQXlCbEksTUFBTSxtQkFBbUIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLE1BQU0sRUFBRSxjQUFjLEVBQUUsU0FBUyxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsYUFBYSxDQUFDLENBQUMsQ0FBQztBQUVuSCxNQUFNLENBQUMsS0FBSyxVQUFVLGFBQWEsQ0FBQyxJQUFZLEVBQUUsT0FBOEI7SUFDOUUsS0FBSyxNQUFNLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxXQUFXLEVBQUUsWUFBWSxDQUFVLEVBQUUsQ0FBQztRQUNwRSxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQzFFLE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLElBQUksS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQzNELENBQUM7SUFDSCxDQUFDO0lBQ0QsTUFBTSxNQUFNLEdBQXlCLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxXQUFXLEVBQUUsRUFBRSxFQUFFLGtCQUFrQixFQUFFLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxF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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { path } from './tsdisk.plugins.js';
|
|
2
2
|
export const toolCacheMarkerFile = '.gitzone-tool-cache.json';
|
|
3
3
|
export function parseToolCacheMarker(rawArg) {
|
|
4
4
|
try {
|
|
5
5
|
const marker = JSON.parse(rawArg);
|
|
6
|
-
if (typeof marker.owner === 'string' &&
|
|
7
|
-
typeof marker.kind === 'string' &&
|
|
8
|
-
marker.safeToPrune ===
|
|
9
|
-
typeof marker.createdAt === 'string' &&
|
|
6
|
+
if (typeof marker.owner === 'string' && marker.owner.length > 0 &&
|
|
7
|
+
typeof marker.kind === 'string' && marker.kind.length > 0 &&
|
|
8
|
+
typeof marker.safeToPrune === 'boolean' &&
|
|
9
|
+
typeof marker.createdAt === 'string' && Number.isFinite(Date.parse(marker.createdAt)) &&
|
|
10
10
|
marker.schemaVersion === 1) {
|
|
11
11
|
return marker;
|
|
12
12
|
}
|
|
@@ -48,8 +48,10 @@ export function classifyToolCachePath(pathArg, markerArg) {
|
|
|
48
48
|
return {
|
|
49
49
|
ownerGuess: markerArg.owner,
|
|
50
50
|
kind: markerArg.kind,
|
|
51
|
-
risk:
|
|
52
|
-
suggestedCleanupCommand:
|
|
51
|
+
risk: 'review',
|
|
52
|
+
suggestedCleanupCommand: markerArg.safeToPrune
|
|
53
|
+
? `report-only: ownership marker is not proof of inactivity; use the owning tool to review ${quotePath(pathArg)}`
|
|
54
|
+
: `protected: owner explicitly disallows pruning ${quotePath(pathArg)}`,
|
|
53
55
|
marker: markerArg,
|
|
54
56
|
};
|
|
55
57
|
}
|
|
@@ -69,7 +71,7 @@ export function classifyToolCachePath(pathArg, markerArg) {
|
|
|
69
71
|
suggestedCleanupCommand: `report-only: verify ownership before removing ${quotePath(path.dirname(pathArg))}`,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
|
-
if (
|
|
74
|
+
if (/\/\.nogit\/tsbundle-temp(?:-[^/]+)?$/.test(normalized)) {
|
|
73
75
|
return {
|
|
74
76
|
ownerGuess: '@git.zone/tsbundle',
|
|
75
77
|
kind: 'bundle-temp-workspace',
|
|
@@ -77,6 +79,22 @@ export function classifyToolCachePath(pathArg, markerArg) {
|
|
|
77
79
|
suggestedCleanupCommand: `report-only: verify ownership before removing ${quotePath(pathArg)}`,
|
|
78
80
|
};
|
|
79
81
|
}
|
|
82
|
+
if (/\/\.nogit\/tsrust-matrix(?:-archive|-probes)?$/.test(normalized)) {
|
|
83
|
+
return {
|
|
84
|
+
ownerGuess: '@git.zone/tsrust',
|
|
85
|
+
kind: 'rust-matrix-artifacts',
|
|
86
|
+
risk: 'report-only',
|
|
87
|
+
suggestedCleanupCommand: `report-only: retained matrix runs may be needed for recovery; inspect ${quotePath(pathArg)}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (/\/\.nogit\/(?:docker-registry\.[^/]+|docker-registry-v1-cache|dockerimagestore)$/.test(normalized)) {
|
|
91
|
+
return {
|
|
92
|
+
ownerGuess: '@git.zone/tsdocker',
|
|
93
|
+
kind: 'legacy-docker-registry-cache',
|
|
94
|
+
risk: 'report-only',
|
|
95
|
+
suggestedCleanupCommand: `report-only: verify release recovery requirements before removing ${quotePath(pathArg)}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
80
98
|
if (normalized.endsWith('/.nogit/docker-registry') || normalized.includes('/.nogit/docker-registry/')) {
|
|
81
99
|
return {
|
|
82
100
|
ownerGuess: '@git.zone/tsdocker',
|
|
@@ -103,4 +121,4 @@ export function classifyToolCachePath(pathArg, markerArg) {
|
|
|
103
121
|
}
|
|
104
122
|
return undefined;
|
|
105
123
|
}
|
|
106
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
124
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHNkaXNrLnRvb2xjYWNoZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90c2Rpc2sudG9vbGNhY2hlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFM0MsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsMEJBQTBCLENBQUM7QUF5QjlELE1BQU0sVUFBVSxvQkFBb0IsQ0FBQyxNQUFjO0lBQ2pELElBQUksQ0FBQztRQUNILE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUE4QixDQUFDO1FBQy9ELElBQ0UsT0FBTyxNQUFNLENBQUMsS0FBSyxLQUFLLFFBQVEsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDO1lBQzNELE9BQU8sTUFBTSxDQUFDLElBQUksS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQztZQUN6RCxPQUFPLE1BQU0sQ0FBQyxXQUFXLEtBQUssU0FBUztZQUN2QyxPQUFPLE1BQU0sQ0FBQyxTQUFTLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDckYsTUFBTSxDQUFDLGFBQWEsS0FBSyxDQUFDLEVBQzFCLENBQUM7WUFDRCxPQUFPLE1BQTBCLENBQUM7UUFDcEMsQ0FBQztJQUNILENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsT0FBTyxTQUFTLENBQUM7QUFDbkIsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLE9BQWU7SUFDaEMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLE1BQU0sQ0FBQyxHQUFHLENBQUM7QUFDdEQsQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFDLE9B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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface IUsageEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
bytes: number;
|
|
4
|
+
complete: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface IUsageReport {
|
|
7
|
+
roots: string[];
|
|
8
|
+
measuredBytes: number;
|
|
9
|
+
entries: IUsageEntry[];
|
|
10
|
+
records: number;
|
|
11
|
+
complete: boolean;
|
|
12
|
+
code: number;
|
|
13
|
+
stderr: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function containsPath(parent: string, child: string): boolean;
|
|
16
|
+
/** Preserve explicitly selected nested mounts; subsume overlapping roots on the same device. */
|
|
17
|
+
export declare function normalizeScanRoots(roots: string[]): Promise<string[]>;
|
|
18
|
+
/** GNU du emits files immediately and inclusive directory totals on completion.
|
|
19
|
+
* Reconcile each completed directory with its observed children, rather than
|
|
20
|
+
* adding inclusive totals twice. The active ancestor chain survives a timeout.
|
|
21
|
+
*/
|
|
22
|
+
export declare class UsageAccumulator {
|
|
23
|
+
private roots;
|
|
24
|
+
private trackedPaths;
|
|
25
|
+
private priorityPaths;
|
|
26
|
+
private pending;
|
|
27
|
+
private active;
|
|
28
|
+
private largest;
|
|
29
|
+
private tracked;
|
|
30
|
+
private minimumLargestBytes;
|
|
31
|
+
private operandBytes;
|
|
32
|
+
private accountingRoots;
|
|
33
|
+
private currentRoot?;
|
|
34
|
+
measuredBytes: number;
|
|
35
|
+
records: number;
|
|
36
|
+
currentPath: string;
|
|
37
|
+
constructor(roots: string[], trackedPaths?: Set<string>, priorityPaths?: string[]);
|
|
38
|
+
consume(chunk: string): void;
|
|
39
|
+
private record;
|
|
40
|
+
snapshot(): IUsageEntry[];
|
|
41
|
+
get endedOnRecordBoundary(): boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface IUsageOptions {
|
|
44
|
+
timeoutSeconds: number;
|
|
45
|
+
trackedPaths?: string[];
|
|
46
|
+
priorityPaths?: string[];
|
|
47
|
+
onProgress?: (usage: UsageAccumulator) => void;
|
|
48
|
+
}
|
|
49
|
+
/** One streaming native pass; no output export, no symlink traversal, no raw file content reads. */
|
|
50
|
+
export declare function measureDiskUsage(roots: string[], options: IUsageOptions): Promise<IUsageReport>;
|