@tukuyomil032/broom 1.0.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/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/commands/analyze.js +371 -0
- package/dist/commands/backup.js +257 -0
- package/dist/commands/clean.js +255 -0
- package/dist/commands/completion.js +714 -0
- package/dist/commands/config.js +474 -0
- package/dist/commands/doctor.js +280 -0
- package/dist/commands/duplicates.js +325 -0
- package/dist/commands/help.js +34 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/installer.js +266 -0
- package/dist/commands/optimize.js +270 -0
- package/dist/commands/purge.js +271 -0
- package/dist/commands/remove.js +184 -0
- package/dist/commands/reports.js +173 -0
- package/dist/commands/schedule.js +249 -0
- package/dist/commands/status.js +468 -0
- package/dist/commands/touchid.js +230 -0
- package/dist/commands/uninstall.js +336 -0
- package/dist/commands/update.js +182 -0
- package/dist/commands/watch.js +258 -0
- package/dist/index.js +131 -0
- package/dist/scanners/base.js +21 -0
- package/dist/scanners/browser-cache.js +111 -0
- package/dist/scanners/dev-cache.js +64 -0
- package/dist/scanners/docker.js +96 -0
- package/dist/scanners/downloads.js +66 -0
- package/dist/scanners/homebrew.js +82 -0
- package/dist/scanners/index.js +126 -0
- package/dist/scanners/installer.js +87 -0
- package/dist/scanners/ios-backups.js +82 -0
- package/dist/scanners/node-modules.js +75 -0
- package/dist/scanners/temp-files.js +65 -0
- package/dist/scanners/trash.js +90 -0
- package/dist/scanners/user-cache.js +62 -0
- package/dist/scanners/user-logs.js +53 -0
- package/dist/scanners/xcode.js +124 -0
- package/dist/types/index.js +23 -0
- package/dist/ui/index.js +5 -0
- package/dist/ui/monitors.js +345 -0
- package/dist/ui/output.js +304 -0
- package/dist/ui/prompts.js +270 -0
- package/dist/utils/config.js +133 -0
- package/dist/utils/debug.js +119 -0
- package/dist/utils/fs.js +283 -0
- package/dist/utils/help.js +265 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/paths.js +142 -0
- package/dist/utils/report.js +404 -0
- package/package.json +87 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path definitions for cleanup operations
|
|
3
|
+
*/
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
const HOME = homedir();
|
|
7
|
+
export const paths = {
|
|
8
|
+
// User caches
|
|
9
|
+
userCache: join(HOME, 'Library/Caches'),
|
|
10
|
+
userLogs: join(HOME, 'Library/Logs'),
|
|
11
|
+
// System caches (requires sudo)
|
|
12
|
+
systemCache: '/Library/Caches',
|
|
13
|
+
systemLogs: '/Library/Logs',
|
|
14
|
+
// Trash
|
|
15
|
+
trash: join(HOME, '.Trash'),
|
|
16
|
+
// Downloads
|
|
17
|
+
downloads: join(HOME, 'Downloads'),
|
|
18
|
+
// Browser caches
|
|
19
|
+
browserCache: {
|
|
20
|
+
chrome: join(HOME, 'Library/Caches/Google/Chrome'),
|
|
21
|
+
chromeProfile: join(HOME, 'Library/Application Support/Google/Chrome/Default/Cache'),
|
|
22
|
+
safari: join(HOME, 'Library/Caches/com.apple.Safari'),
|
|
23
|
+
firefox: join(HOME, 'Library/Caches/Firefox'),
|
|
24
|
+
firefoxProfile: join(HOME, 'Library/Application Support/Firefox/Profiles'),
|
|
25
|
+
edge: join(HOME, 'Library/Caches/Microsoft Edge'),
|
|
26
|
+
edgeProfile: join(HOME, 'Library/Application Support/Microsoft Edge/Default/Cache'),
|
|
27
|
+
brave: join(HOME, 'Library/Caches/BraveSoftware/Brave-Browser'),
|
|
28
|
+
braveProfile: join(HOME, 'Library/Application Support/BraveSoftware/Brave-Browser/Default/Cache'),
|
|
29
|
+
arc: join(HOME, 'Library/Caches/company.thebrowser.Browser'),
|
|
30
|
+
arcProfile: join(HOME, 'Library/Application Support/Arc/User Data/Default/Cache'),
|
|
31
|
+
},
|
|
32
|
+
// Development caches
|
|
33
|
+
devCache: {
|
|
34
|
+
npm: join(HOME, '.npm'),
|
|
35
|
+
npmCache: join(HOME, '.npm/_cacache'),
|
|
36
|
+
yarn: join(HOME, '.yarn/cache'),
|
|
37
|
+
pnpm: join(HOME, '.pnpm-store'),
|
|
38
|
+
bun: join(HOME, '.bun'),
|
|
39
|
+
pip: join(HOME, 'Library/Caches/pip'),
|
|
40
|
+
pipCache: join(HOME, '.cache/pip'),
|
|
41
|
+
cargo: join(HOME, '.cargo/registry/cache'),
|
|
42
|
+
rustup: join(HOME, '.rustup/downloads'),
|
|
43
|
+
go: join(HOME, 'go/pkg/mod/cache'),
|
|
44
|
+
gradle: join(HOME, '.gradle/caches'),
|
|
45
|
+
maven: join(HOME, '.m2/repository'),
|
|
46
|
+
cocoapods: join(HOME, 'Library/Caches/CocoaPods'),
|
|
47
|
+
carthage: join(HOME, 'Library/Caches/org.carthage.CarthageKit'),
|
|
48
|
+
composer: join(HOME, '.composer/cache'),
|
|
49
|
+
},
|
|
50
|
+
// Xcode caches
|
|
51
|
+
xcode: {
|
|
52
|
+
derivedData: join(HOME, 'Library/Developer/Xcode/DerivedData'),
|
|
53
|
+
archives: join(HOME, 'Library/Developer/Xcode/Archives'),
|
|
54
|
+
deviceSupport: join(HOME, 'Library/Developer/Xcode/iOS DeviceSupport'),
|
|
55
|
+
simulatorCache: join(HOME, 'Library/Developer/CoreSimulator/Caches'),
|
|
56
|
+
simulatorDevices: join(HOME, 'Library/Developer/CoreSimulator/Devices'),
|
|
57
|
+
modulesCache: join(HOME, 'Library/Developer/Xcode/ModuleCache'),
|
|
58
|
+
previewsCache: join(HOME, 'Library/Developer/Xcode/UserData/Previews'),
|
|
59
|
+
},
|
|
60
|
+
// Homebrew
|
|
61
|
+
homebrew: {
|
|
62
|
+
cache: join(HOME, 'Library/Caches/Homebrew'),
|
|
63
|
+
logs: join(HOME, 'Library/Logs/Homebrew'),
|
|
64
|
+
downloads: '/opt/homebrew/var/homebrew/cache', // Apple Silicon
|
|
65
|
+
},
|
|
66
|
+
// Docker
|
|
67
|
+
docker: {
|
|
68
|
+
data: join(HOME, 'Library/Containers/com.docker.docker/Data'),
|
|
69
|
+
vmDisk: join(HOME, 'Library/Containers/com.docker.docker/Data/vms'),
|
|
70
|
+
},
|
|
71
|
+
// iOS backups
|
|
72
|
+
iosBackups: join(HOME, 'Library/Application Support/MobileSync/Backup'),
|
|
73
|
+
// Temp files
|
|
74
|
+
tempFiles: {
|
|
75
|
+
userTemp: join(HOME, 'Library/Caches/TemporaryItems'),
|
|
76
|
+
systemTemp: '/private/tmp',
|
|
77
|
+
varTmp: '/var/tmp',
|
|
78
|
+
},
|
|
79
|
+
// Spotlight
|
|
80
|
+
spotlight: {
|
|
81
|
+
index: '/.Spotlight-V100',
|
|
82
|
+
},
|
|
83
|
+
// Time Machine local snapshots
|
|
84
|
+
timeMachine: '/.MobileBackups',
|
|
85
|
+
// Application Support
|
|
86
|
+
applicationSupport: join(HOME, 'Library/Application Support'),
|
|
87
|
+
// Preferences
|
|
88
|
+
preferences: join(HOME, 'Library/Preferences'),
|
|
89
|
+
// Saved Application State
|
|
90
|
+
savedState: join(HOME, 'Library/Saved Application State'),
|
|
91
|
+
// Applications
|
|
92
|
+
applications: '/Applications',
|
|
93
|
+
userApplications: join(HOME, 'Applications'),
|
|
94
|
+
};
|
|
95
|
+
// Common app data patterns
|
|
96
|
+
export const appDataPatterns = [
|
|
97
|
+
// General caches
|
|
98
|
+
'Library/Caches/*',
|
|
99
|
+
'Library/Application Support/*',
|
|
100
|
+
'Library/Preferences/*',
|
|
101
|
+
'Library/Saved Application State/*',
|
|
102
|
+
'Library/Logs/*',
|
|
103
|
+
// Launch agents/daemons
|
|
104
|
+
'Library/LaunchAgents/*',
|
|
105
|
+
'Library/LaunchDaemons/*',
|
|
106
|
+
// Containers
|
|
107
|
+
'Library/Containers/*',
|
|
108
|
+
'Library/Group Containers/*',
|
|
109
|
+
];
|
|
110
|
+
// Files to exclude from cleanup
|
|
111
|
+
export const excludePatterns = [
|
|
112
|
+
// System essentials
|
|
113
|
+
'com.apple.*',
|
|
114
|
+
// Active browser profiles
|
|
115
|
+
'**/Cookies',
|
|
116
|
+
'**/Bookmarks',
|
|
117
|
+
'**/History',
|
|
118
|
+
'**/Login Data',
|
|
119
|
+
// Development essentials
|
|
120
|
+
'node_modules/.package-lock.json',
|
|
121
|
+
'.git',
|
|
122
|
+
// User data
|
|
123
|
+
'**/LocalStorage',
|
|
124
|
+
'**/IndexedDB',
|
|
125
|
+
];
|
|
126
|
+
// Category paths mapping
|
|
127
|
+
export const categoryPaths = {
|
|
128
|
+
'user-cache': [paths.userCache],
|
|
129
|
+
'system-cache': [paths.systemCache],
|
|
130
|
+
'user-logs': [paths.userLogs],
|
|
131
|
+
'system-logs': [paths.systemLogs],
|
|
132
|
+
trash: [paths.trash],
|
|
133
|
+
downloads: [paths.downloads],
|
|
134
|
+
'browser-cache': Object.values(paths.browserCache),
|
|
135
|
+
'dev-cache': Object.values(paths.devCache),
|
|
136
|
+
xcode: Object.values(paths.xcode),
|
|
137
|
+
homebrew: [paths.homebrew.cache, paths.homebrew.logs],
|
|
138
|
+
docker: [paths.docker.data],
|
|
139
|
+
'ios-backups': [paths.iosBackups],
|
|
140
|
+
'temp-files': Object.values(paths.tempFiles),
|
|
141
|
+
};
|
|
142
|
+
export default paths;
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Report Generator
|
|
3
|
+
*/
|
|
4
|
+
import Handlebars from 'handlebars';
|
|
5
|
+
import { formatSize } from '../utils/fs.js';
|
|
6
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
7
|
+
import { dirname } from 'path';
|
|
8
|
+
// Register Handlebars helpers
|
|
9
|
+
Handlebars.registerHelper('formatSize', (bytes) => {
|
|
10
|
+
return formatSize(bytes);
|
|
11
|
+
});
|
|
12
|
+
Handlebars.registerHelper('formatDate', (date) => {
|
|
13
|
+
return new Date(date).toLocaleString('ja-JP');
|
|
14
|
+
});
|
|
15
|
+
Handlebars.registerHelper('formatDuration', (ms) => {
|
|
16
|
+
const seconds = Math.floor(ms / 1000);
|
|
17
|
+
const minutes = Math.floor(seconds / 60);
|
|
18
|
+
const hours = Math.floor(minutes / 60);
|
|
19
|
+
if (hours > 0) {
|
|
20
|
+
return `${hours}時間${minutes % 60}分${seconds % 60}秒`;
|
|
21
|
+
}
|
|
22
|
+
else if (minutes > 0) {
|
|
23
|
+
return `${minutes}分${seconds % 60}秒`;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
return `${seconds}秒`;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
Handlebars.registerHelper('toFixed', (num, decimals) => {
|
|
30
|
+
return num.toFixed(decimals);
|
|
31
|
+
});
|
|
32
|
+
Handlebars.registerHelper('json', (obj) => {
|
|
33
|
+
return JSON.stringify(obj);
|
|
34
|
+
});
|
|
35
|
+
// Comparison helper
|
|
36
|
+
Handlebars.registerHelper('eq', (a, b) => {
|
|
37
|
+
return a === b;
|
|
38
|
+
});
|
|
39
|
+
// Greater than helper
|
|
40
|
+
Handlebars.registerHelper('gt', (a, b) => {
|
|
41
|
+
return a > b;
|
|
42
|
+
});
|
|
43
|
+
// Map helper - extract property from array
|
|
44
|
+
Handlebars.registerHelper('map', (array, property) => {
|
|
45
|
+
if (!Array.isArray(array))
|
|
46
|
+
return [];
|
|
47
|
+
return array.map((item) => item[property]);
|
|
48
|
+
});
|
|
49
|
+
const HTML_TEMPLATE = `<!DOCTYPE html>
|
|
50
|
+
<html lang="ja">
|
|
51
|
+
<head>
|
|
52
|
+
<meta charset="UTF-8">
|
|
53
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
54
|
+
<title>Broom Cleanup Report - {{formatDate metadata.generatedAt}}</title>
|
|
55
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
56
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
57
|
+
<style>
|
|
58
|
+
@media print {
|
|
59
|
+
.no-print { display: none; }
|
|
60
|
+
body { background: white; }
|
|
61
|
+
}
|
|
62
|
+
@page {
|
|
63
|
+
margin: 2cm;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body class="bg-gray-50">
|
|
68
|
+
<!-- Header -->
|
|
69
|
+
<header class="bg-gradient-to-r from-cyan-500 to-blue-500 text-white py-8 no-print">
|
|
70
|
+
<div class="container mx-auto px-4">
|
|
71
|
+
<h1 class="text-4xl font-bold flex items-center gap-2">
|
|
72
|
+
🧹 Broom Cleanup Report
|
|
73
|
+
</h1>
|
|
74
|
+
<p class="text-cyan-100 mt-2">{{formatDate metadata.generatedAt}}</p>
|
|
75
|
+
</div>
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<!-- Summary Cards -->
|
|
79
|
+
<section class="container mx-auto px-4 py-8">
|
|
80
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
81
|
+
<!-- Space Freed -->
|
|
82
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
83
|
+
<div class="text-gray-600 text-sm font-medium">回収した容量</div>
|
|
84
|
+
<div class="text-3xl font-bold text-green-600 mt-2">{{formatSize summary.totalSpaceFreed}}</div>
|
|
85
|
+
<div class="text-gray-500 text-xs mt-1">{{summary.totalFilesDeleted}} ファイル</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Files Deleted -->
|
|
89
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
90
|
+
<div class="text-gray-600 text-sm font-medium">削除ファイル数</div>
|
|
91
|
+
<div class="text-3xl font-bold text-blue-600 mt-2">{{summary.totalFilesDeleted}}</div>
|
|
92
|
+
<div class="text-gray-500 text-xs mt-1">合計</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Time Elapsed -->
|
|
96
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
97
|
+
<div class="text-gray-600 text-sm font-medium">処理時間</div>
|
|
98
|
+
<div class="text-3xl font-bold text-purple-600 mt-2">{{formatDuration summary.timeElapsed}}</div>
|
|
99
|
+
<div class="text-gray-500 text-xs mt-1">経過時間</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Status -->
|
|
103
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
104
|
+
<div class="text-gray-600 text-sm font-medium">ステータス</div>
|
|
105
|
+
<div class="text-3xl font-bold {{#if (eq summary.status 'success')}}text-green-600{{else}}text-yellow-600{{/if}} mt-2">
|
|
106
|
+
{{#if (eq summary.status 'success')}}✓ 成功{{else}}⚠ 部分的{{/if}}
|
|
107
|
+
</div>
|
|
108
|
+
<div class="text-gray-500 text-xs mt-1">完了</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<!-- Charts -->
|
|
114
|
+
<section class="container mx-auto px-4 py-8">
|
|
115
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
116
|
+
<!-- Category Breakdown -->
|
|
117
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
118
|
+
<h3 class="text-xl font-semibold mb-4">カテゴリ別内訳</h3>
|
|
119
|
+
<div class="h-64">
|
|
120
|
+
<canvas id="categoryChart"></canvas>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<!-- Disk Comparison -->
|
|
125
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
126
|
+
<h3 class="text-xl font-semibold mb-4">ディスク使用量の変化</h3>
|
|
127
|
+
<div class="h-64">
|
|
128
|
+
<canvas id="diskChart"></canvas>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</section>
|
|
133
|
+
|
|
134
|
+
<!-- Files Table -->
|
|
135
|
+
<section class="container mx-auto px-4 py-8">
|
|
136
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
137
|
+
<h3 class="text-xl font-semibold mb-4">削除されたファイル</h3>
|
|
138
|
+
<div class="overflow-x-auto">
|
|
139
|
+
<table class="min-w-full table-auto">
|
|
140
|
+
<thead class="bg-gray-50">
|
|
141
|
+
<tr>
|
|
142
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">パス</th>
|
|
143
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">サイズ</th>
|
|
144
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">カテゴリ</th>
|
|
145
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">削除時刻</th>
|
|
146
|
+
</tr>
|
|
147
|
+
</thead>
|
|
148
|
+
<tbody class="divide-y divide-gray-200">
|
|
149
|
+
{{#each files}}
|
|
150
|
+
<tr class="hover:bg-gray-50">
|
|
151
|
+
<td class="px-4 py-3 font-mono text-xs text-gray-800">{{this.path}}</td>
|
|
152
|
+
<td class="px-4 py-3 text-sm">{{formatSize this.size}}</td>
|
|
153
|
+
<td class="px-4 py-3">
|
|
154
|
+
<span class="px-2 py-1 rounded text-xs bg-blue-100 text-blue-800">
|
|
155
|
+
{{this.category}}
|
|
156
|
+
</span>
|
|
157
|
+
</td>
|
|
158
|
+
<td class="px-4 py-3 text-sm text-gray-600">{{formatDate this.deletedAt}}</td>
|
|
159
|
+
</tr>
|
|
160
|
+
{{/each}}
|
|
161
|
+
</tbody>
|
|
162
|
+
</table>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</section>
|
|
166
|
+
|
|
167
|
+
<!-- Metadata -->
|
|
168
|
+
<section class="container mx-auto px-4 py-8">
|
|
169
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
170
|
+
<h3 class="text-xl font-semibold mb-4">実行情報</h3>
|
|
171
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
172
|
+
<div>
|
|
173
|
+
<span class="text-gray-600">コマンド:</span>
|
|
174
|
+
<code class="ml-2 px-2 py-1 bg-gray-100 rounded">{{metadata.command}}</code>
|
|
175
|
+
</div>
|
|
176
|
+
<div>
|
|
177
|
+
<span class="text-gray-600">Broomバージョン:</span>
|
|
178
|
+
<span class="ml-2 font-mono">{{metadata.broomVersion}}</span>
|
|
179
|
+
</div>
|
|
180
|
+
<div>
|
|
181
|
+
<span class="text-gray-600">ホスト名:</span>
|
|
182
|
+
<span class="ml-2 font-mono">{{metadata.hostname}}</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div>
|
|
185
|
+
<span class="text-gray-600">ユーザー:</span>
|
|
186
|
+
<span class="ml-2 font-mono">{{metadata.username}}</span>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<!-- Export Button -->
|
|
193
|
+
<div class="container mx-auto px-4 py-8 no-print">
|
|
194
|
+
<button onclick="window.print()"
|
|
195
|
+
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg shadow transition">
|
|
196
|
+
📄 PDFとして保存
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Chart Scripts -->
|
|
201
|
+
<script>
|
|
202
|
+
// Category Pie Chart
|
|
203
|
+
const categoryCtx = document.getElementById('categoryChart').getContext('2d');
|
|
204
|
+
new Chart(categoryCtx, {
|
|
205
|
+
type: 'doughnut',
|
|
206
|
+
data: {
|
|
207
|
+
labels: {{{json (map categories 'name')}}},
|
|
208
|
+
datasets: [{
|
|
209
|
+
data: {{{json (map categories 'spaceFreed')}}},
|
|
210
|
+
backgroundColor: {{{json (map categories 'color')}}}
|
|
211
|
+
}]
|
|
212
|
+
},
|
|
213
|
+
options: {
|
|
214
|
+
responsive: true,
|
|
215
|
+
maintainAspectRatio: false,
|
|
216
|
+
plugins: {
|
|
217
|
+
legend: {
|
|
218
|
+
position: 'bottom',
|
|
219
|
+
labels: {
|
|
220
|
+
padding: 15,
|
|
221
|
+
font: { size: 12 }
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
tooltip: {
|
|
225
|
+
callbacks: {
|
|
226
|
+
label: function(context) {
|
|
227
|
+
const label = context.label || '';
|
|
228
|
+
const value = context.parsed || 0;
|
|
229
|
+
const size = (value / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
230
|
+
return label + ': ' + size;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Disk Comparison Bar Chart
|
|
239
|
+
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
|
240
|
+
new Chart(diskCtx, {
|
|
241
|
+
type: 'bar',
|
|
242
|
+
data: {
|
|
243
|
+
labels: ['削除前', '削除後'],
|
|
244
|
+
datasets: [
|
|
245
|
+
{
|
|
246
|
+
label: '使用中',
|
|
247
|
+
data: [
|
|
248
|
+
{{diskComparison.before.used}},
|
|
249
|
+
{{diskComparison.after.used}}
|
|
250
|
+
],
|
|
251
|
+
backgroundColor: 'rgba(239, 68, 68, 0.8)'
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
label: '空き容量',
|
|
255
|
+
data: [
|
|
256
|
+
{{diskComparison.before.free}},
|
|
257
|
+
{{diskComparison.after.free}}
|
|
258
|
+
],
|
|
259
|
+
backgroundColor: 'rgba(16, 185, 129, 0.8)'
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
options: {
|
|
264
|
+
responsive: true,
|
|
265
|
+
maintainAspectRatio: false,
|
|
266
|
+
scales: {
|
|
267
|
+
x: { stacked: true },
|
|
268
|
+
y: {
|
|
269
|
+
stacked: true,
|
|
270
|
+
ticks: {
|
|
271
|
+
callback: function(value) {
|
|
272
|
+
return (value / 1024 / 1024 / 1024 / 1024).toFixed(1) + ' TB';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
plugins: {
|
|
278
|
+
tooltip: {
|
|
279
|
+
callbacks: {
|
|
280
|
+
label: function(context) {
|
|
281
|
+
const label = context.dataset.label || '';
|
|
282
|
+
const value = context.parsed.y || 0;
|
|
283
|
+
const size = (value / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
284
|
+
return label + ': ' + size;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Helper for mapping arrays
|
|
293
|
+
Handlebars.registerHelper('map', function(array, property) {
|
|
294
|
+
return array.map(item => item[property]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
Handlebars.registerHelper('eq', function(a, b) {
|
|
298
|
+
return a === b;
|
|
299
|
+
});
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
<footer class="container mx-auto px-4 py-8 text-center text-gray-600 text-sm">
|
|
303
|
+
<p>Generated by Broom v{{metadata.broomVersion}} - macOS Disk Cleanup Tool</p>
|
|
304
|
+
</footer>
|
|
305
|
+
</body>
|
|
306
|
+
</html>`;
|
|
307
|
+
export class ReportGenerator {
|
|
308
|
+
constructor() {
|
|
309
|
+
this.deletedFiles = [];
|
|
310
|
+
this.categories = new Map();
|
|
311
|
+
this.beforeDisk = null;
|
|
312
|
+
this.startTime = new Date();
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Record disk state before cleanup
|
|
316
|
+
*/
|
|
317
|
+
recordDiskBefore(disk) {
|
|
318
|
+
this.beforeDisk = disk;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Record a file deletion
|
|
322
|
+
*/
|
|
323
|
+
recordDeletion(file, size, category) {
|
|
324
|
+
this.deletedFiles.push({
|
|
325
|
+
path: file,
|
|
326
|
+
size,
|
|
327
|
+
category,
|
|
328
|
+
deletedAt: new Date(),
|
|
329
|
+
});
|
|
330
|
+
// Update category stats
|
|
331
|
+
const existing = this.categories.get(category) || {
|
|
332
|
+
filesDeleted: 0,
|
|
333
|
+
spaceFreed: 0,
|
|
334
|
+
color: this.getCategoryColor(category),
|
|
335
|
+
};
|
|
336
|
+
existing.filesDeleted++;
|
|
337
|
+
existing.spaceFreed += size;
|
|
338
|
+
this.categories.set(category, existing);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Get color for category
|
|
342
|
+
*/
|
|
343
|
+
getCategoryColor(category) {
|
|
344
|
+
const colors = {
|
|
345
|
+
'User Cache': '#3B82F6',
|
|
346
|
+
'Browser Cache': '#10B981',
|
|
347
|
+
Logs: '#F59E0B',
|
|
348
|
+
Trash: '#EF4444',
|
|
349
|
+
'Dev Cache': '#8B5CF6',
|
|
350
|
+
Xcode: '#EC4899',
|
|
351
|
+
Installer: '#6366F1',
|
|
352
|
+
Downloads: '#F97316',
|
|
353
|
+
};
|
|
354
|
+
return colors[category] || '#6B7280';
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Generate HTML report
|
|
358
|
+
*/
|
|
359
|
+
async generate(outputPath, afterDisk) {
|
|
360
|
+
const { hostname } = await import('os');
|
|
361
|
+
const { execSync } = await import('child_process');
|
|
362
|
+
const username = execSync('whoami').toString().trim();
|
|
363
|
+
// Calculate summary
|
|
364
|
+
const totalSpaceFreed = this.deletedFiles.reduce((sum, f) => sum + f.size, 0);
|
|
365
|
+
const timeElapsed = Date.now() - this.startTime.getTime();
|
|
366
|
+
// Prepare category data
|
|
367
|
+
const categoryData = Array.from(this.categories.entries()).map(([name, data]) => ({
|
|
368
|
+
name,
|
|
369
|
+
filesDeleted: data.filesDeleted,
|
|
370
|
+
spaceFreed: data.spaceFreed,
|
|
371
|
+
percentage: (data.spaceFreed / totalSpaceFreed) * 100,
|
|
372
|
+
color: data.color,
|
|
373
|
+
}));
|
|
374
|
+
const report = {
|
|
375
|
+
metadata: {
|
|
376
|
+
generatedAt: new Date(),
|
|
377
|
+
broomVersion: '1.0.0',
|
|
378
|
+
command: process.argv.slice(2).join(' '),
|
|
379
|
+
hostname: hostname(),
|
|
380
|
+
username,
|
|
381
|
+
},
|
|
382
|
+
summary: {
|
|
383
|
+
totalFilesDeleted: this.deletedFiles.length,
|
|
384
|
+
totalSpaceFreed,
|
|
385
|
+
timeElapsed,
|
|
386
|
+
status: 'success',
|
|
387
|
+
errors: [],
|
|
388
|
+
},
|
|
389
|
+
categories: categoryData,
|
|
390
|
+
files: this.deletedFiles,
|
|
391
|
+
diskComparison: {
|
|
392
|
+
before: this.beforeDisk || afterDisk,
|
|
393
|
+
after: afterDisk,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
// Compile template
|
|
397
|
+
const template = Handlebars.compile(HTML_TEMPLATE);
|
|
398
|
+
const html = template(report);
|
|
399
|
+
// Ensure directory exists
|
|
400
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
401
|
+
// Write HTML file
|
|
402
|
+
await writeFile(outputPath, html, 'utf-8');
|
|
403
|
+
}
|
|
404
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tukuyomil032/broom",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "🧹 macOS Disk Cleanup CLI - Clean up caches, logs, trash, browser data, dev artifacts, and more",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"broom": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
18
|
+
"prepare": "husky",
|
|
19
|
+
"prepublishOnly": "bun run build",
|
|
20
|
+
"dev": "bun run src/index.ts",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
23
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
24
|
+
"lint-staged": "lint-staged",
|
|
25
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
26
|
+
"format:check": "prettier --check \"src/**/*.ts\""
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"cli",
|
|
30
|
+
"macos",
|
|
31
|
+
"cleanup",
|
|
32
|
+
"disk-space",
|
|
33
|
+
"cache-cleaner",
|
|
34
|
+
"system-maintenance",
|
|
35
|
+
"typescript"
|
|
36
|
+
],
|
|
37
|
+
"author": "tukuyomil032",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/tukuyomil032/broom.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/tukuyomil032/broom/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/tukuyomil032/broom#readme",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@inquirer/prompts": "^8.2.0",
|
|
49
|
+
"@types/handlebars": "^4.1.0",
|
|
50
|
+
"blessed": "^0.1.81",
|
|
51
|
+
"blessed-contrib": "^4.11.0",
|
|
52
|
+
"chalk": "^5.6.2",
|
|
53
|
+
"cli-table3": "^0.6.5",
|
|
54
|
+
"commander": "^14.0.2",
|
|
55
|
+
"fast-glob": "^3.3.3",
|
|
56
|
+
"handlebars": "^4.7.8",
|
|
57
|
+
"inquirer": "^13.2.1",
|
|
58
|
+
"ora": "^9.1.0",
|
|
59
|
+
"p-limit": "^7.2.0",
|
|
60
|
+
"systeminformation": "^5.30.6",
|
|
61
|
+
"yargs": "^18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@eslint/json": "^0.14.0",
|
|
65
|
+
"@types/blessed": "^0.1.27",
|
|
66
|
+
"@types/node": "^25.0.10",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
|
68
|
+
"@typescript-eslint/parser": "^8.53.1",
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"eslint-config-prettier": "^10.1.8",
|
|
71
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
72
|
+
"globals": "^17.1.0",
|
|
73
|
+
"husky": "^9.1.7",
|
|
74
|
+
"lint-staged": "^16.2.7",
|
|
75
|
+
"prettier": "^3.8.1",
|
|
76
|
+
"ts-node": "^10.9.2",
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"typescript-eslint": "^8.54.0"
|
|
79
|
+
},
|
|
80
|
+
"lint-staged": {
|
|
81
|
+
"*.ts": [
|
|
82
|
+
"eslint --fix",
|
|
83
|
+
"prettier --write"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"packageManager": "bun@1.3.6"
|
|
87
|
+
}
|