@lowdep/dot-clean 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rushabh Shah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # dot-clean
2
+
3
+ ![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen) ![Node](https://img.shields.io/badge/node-%3E%3D14-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
4
+
5
+
6
+ Remove `.DS_Store`, `Thumbs.db`, `desktop.ini`, and other OS-generated junk files recursively. Optionally update `.gitignore` to prevent them coming back. Zero dependencies.
7
+
8
+ ---
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g dot-clean
14
+ ```
15
+
16
+ Or without installing:
17
+
18
+ ```bash
19
+ npx dot-clean
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ dot-clean # Remove macOS/Windows/Linux junk in current dir
28
+ dot-clean ~/Documents # Clean a specific directory
29
+ dot-clean --dry-run # Preview without deleting
30
+ dot-clean --all # Remove all categories (incl. IDE/editor files)
31
+ dot-clean --type macos # Only macOS junk
32
+ dot-clean --gitignore # Also update .gitignore
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Example Output
38
+
39
+ ```
40
+ dot-clean /my-project
41
+ Scanning for: macos, windows, linux...
42
+
43
+ ✗ screenshots/.DS_Store 4.1 kB
44
+ ✗ screenshots/.DS_Store 4.1 kB
45
+ ✗ backup/Thumbs.db 8.0 kB
46
+ ✗ old-files/desktop.ini 402 B
47
+
48
+ 4 items removed (16.6 kB freed)
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Categories
54
+
55
+ | Category | Files removed |
56
+ |---|---|
57
+ | `macos` | `.DS_Store`, `__MACOSX/`, `.AppleDouble`, `.Spotlight-V100`, `.Trashes`, etc. |
58
+ | `windows` | `Thumbs.db`, `desktop.ini`, `ehthumbs.db`, `$RECYCLE.BIN`, etc. |
59
+ | `linux` | `.fuse_hidden*`, `.directory`, `.Trash-*`, `.nfs*` |
60
+ | `ide` | `.idea/`, `.vscode/`, `.classpath`, `.settings/`, etc. |
61
+ | `editor` | `*~`, `*.bak`, `*.orig`, `*.swp`, `*.tmp` |
62
+
63
+ Default: `macos`, `windows`, `linux`
64
+
65
+ ---
66
+
67
+ ## Options
68
+
69
+ | Flag | Description |
70
+ |---|---|
71
+ | `--type <list>` | Comma-separated categories: `macos,windows,linux,ide,editor` |
72
+ | `--all` | Remove all categories |
73
+ | `--dry-run` | Show what would be removed, don't delete |
74
+ | `--gitignore` | Update `.gitignore` to prevent future junk |
75
+ | `--stats` | Show breakdown by category |
76
+
77
+ ---
78
+
79
+ ## License
80
+
81
+ MIT
82
+
83
+ ---
84
+
85
+ ## Keywords
86
+
87
+ `.DS_Store` · `Thumbs.db` · `desktop.ini` · `remove junk files` · `clean os files` · `delete ds_store` · `mac junk` · `cleanup` · `cross-platform` · `zero dependencies`
88
+
89
+ ---
90
+
91
+ <div align="center">
92
+
93
+ **Built to solve, shared to help — Rushabh Shah 🛠️✨**
94
+
95
+ <sub>One of 40+ zero-dependency developer CLI tools — no <code>node_modules</code>, ever.</sub>
96
+
97
+ </div>
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const VERSION = '1.0.0';
8
+
9
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
10
+ const isTTY = process.stdout.isTTY;
11
+ const c = (code, t) => isTTY ? `\x1b[${code}m${t}\x1b[0m` : t;
12
+ const bold = t => c('1', t);
13
+ const dim = t => c('2', t);
14
+ const red = t => c('31', t);
15
+ const green = t => c('32', t);
16
+ const yellow = t => c('33', t);
17
+ const cyan = t => c('36', t);
18
+
19
+ // ─── Junk file definitions ────────────────────────────────────────────────────
20
+ const JUNK_FILES = {
21
+ macos: [
22
+ '.DS_Store', '.DS_Store?', '._*', '.AppleDouble', '.AppleDB',
23
+ '.AppleDesktop', '.com.apple.timemachineexclude',
24
+ '.DocumentRevisions-V100', '.fseventsd', '.Spotlight-V100',
25
+ '.TemporaryItems', '.Trashes', '.VolumeIcon.icns', '.apdisk',
26
+ '__MACOSX',
27
+ ],
28
+ windows: [
29
+ 'Thumbs.db', 'thumbs.db', 'ehthumbs.db', 'ehthumbs_vista.db',
30
+ 'desktop.ini', 'Desktop.ini',
31
+ '$RECYCLE.BIN', 'RECYCLER', 'RECYCLED',
32
+ '*.lnk',
33
+ ],
34
+ linux: [
35
+ '.fuse_hidden*', '.directory', '.Trash-*', '.nfs*',
36
+ ],
37
+ ide: [
38
+ '.idea', '.vscode', '*.suo', '*.ntvs*', '*.njsproj', '*.sln',
39
+ '.project', '.classpath', '.settings', '*.swp', '*~',
40
+ ],
41
+ editor: [
42
+ '*~', '.#*', '.*.swp', '.*.swo', '*.bak', '*.tmp', '*.orig',
43
+ ],
44
+ };
45
+
46
+ const ALL_CATEGORIES = Object.keys(JUNK_FILES);
47
+
48
+ function matchesPattern(name, pattern) {
49
+ if (pattern.startsWith('*.')) {
50
+ return name.endsWith(pattern.slice(1));
51
+ }
52
+ if (pattern.endsWith('*')) {
53
+ return name.startsWith(pattern.slice(0, -1));
54
+ }
55
+ if (pattern.startsWith('.*')) {
56
+ // .something* style
57
+ const prefix = pattern.slice(0, -1);
58
+ return name.startsWith(prefix);
59
+ }
60
+ return name === pattern;
61
+ }
62
+
63
+ function shouldRemove(name, categories) {
64
+ for (const cat of categories) {
65
+ const patterns = JUNK_FILES[cat] || [];
66
+ for (const p of patterns) {
67
+ if (matchesPattern(name, p)) return { match: true, category: cat };
68
+ }
69
+ }
70
+ return { match: false };
71
+ }
72
+
73
+ // ─── Recursive scanner ────────────────────────────────────────────────────────
74
+ function scan(dir, categories, results = []) {
75
+ let entries;
76
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
77
+ catch { return results; }
78
+
79
+ for (const e of entries) {
80
+ const { match, category } = shouldRemove(e.name, categories);
81
+ const full = path.join(dir, e.name);
82
+
83
+ if (match) {
84
+ let size = 0;
85
+ try {
86
+ if (e.isDirectory()) {
87
+ size = getDirSize(full);
88
+ results.push({ path: full, name: e.name, category, isDir: true, size });
89
+ } else {
90
+ size = fs.statSync(full).size;
91
+ results.push({ path: full, name: e.name, category, isDir: false, size });
92
+ }
93
+ } catch { /* skip inaccessible */ }
94
+ } else if (e.isDirectory() && !e.name.startsWith('.')) {
95
+ scan(full, categories, results);
96
+ }
97
+ }
98
+
99
+ return results;
100
+ }
101
+
102
+ function getDirSize(dir) {
103
+ let total = 0;
104
+ try {
105
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
106
+ const full = path.join(dir, e.name);
107
+ if (e.isDirectory()) total += getDirSize(full);
108
+ else { try { total += fs.statSync(full).size; } catch { /* skip */ } }
109
+ }
110
+ } catch { /* skip */ }
111
+ return total;
112
+ }
113
+
114
+ function formatSize(b) {
115
+ if (b < 1024) return `${b} B`;
116
+ if (b < 1024 ** 2) return `${(b / 1024).toFixed(1)} kB`;
117
+ return `${(b / 1024 ** 2).toFixed(1)} MB`;
118
+ }
119
+
120
+ function removeRecursive(p) {
121
+ try {
122
+ if (fs.statSync(p).isDirectory()) {
123
+ for (const e of fs.readdirSync(p)) removeRecursive(path.join(p, e));
124
+ fs.rmdirSync(p);
125
+ } else {
126
+ fs.unlinkSync(p);
127
+ }
128
+ return true;
129
+ } catch { return false; }
130
+ }
131
+
132
+ // ─── .gitignore updater ───────────────────────────────────────────────────────
133
+ const GITIGNORE_ENTRIES = {
134
+ macos: ['# macOS', '.DS_Store', '.AppleDouble', '.AppleDB', '__MACOSX', '.Trashes', '.Spotlight-V100'],
135
+ windows: ['# Windows', 'Thumbs.db', 'ehthumbs.db', 'desktop.ini', '$RECYCLE.BIN'],
136
+ linux: ['# Linux', '.fuse_hidden*', '.directory', '.Trash-*'],
137
+ editor: ['# Editor backups', '*~', '*.bak', '*.orig', '*.swp'],
138
+ };
139
+
140
+ function updateGitignore(dir, categories) {
141
+ const giPath = path.join(dir, '.gitignore');
142
+ let existing = '';
143
+ try { existing = fs.readFileSync(giPath, 'utf8'); } catch { /* new file */ }
144
+
145
+ const toAdd = [];
146
+ for (const cat of categories) {
147
+ const entries = GITIGNORE_ENTRIES[cat] || [];
148
+ for (const entry of entries) {
149
+ if (!existing.includes(entry)) toAdd.push(entry);
150
+ }
151
+ }
152
+
153
+ if (!toAdd.length) {
154
+ console.log(dim(' .gitignore already up to date'));
155
+ return;
156
+ }
157
+
158
+ const addition = '\n' + toAdd.join('\n') + '\n';
159
+ fs.appendFileSync(giPath, addition);
160
+ console.log(green(` Updated .gitignore with ${toAdd.filter(e => !e.startsWith('#')).length} entries`));
161
+ }
162
+
163
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
164
+ const args = process.argv.slice(2);
165
+ const VALUE_FLAGS = new Set(['--type']);
166
+ const positional = [];
167
+ for (let i = 0; i < args.length; i++) {
168
+ if (args[i].startsWith('-')) { if (VALUE_FLAGS.has(args[i])) i++; }
169
+ else positional.push(args[i]);
170
+ }
171
+
172
+ function getFlag(f) {
173
+ const i = args.indexOf(f);
174
+ return i !== -1 ? args[i + 1] : null;
175
+ }
176
+ function hasFlag(f) { return args.includes(f); }
177
+
178
+ if (hasFlag('--version') || hasFlag('-v')) {
179
+ console.log(`dot-clean v${VERSION}`); process.exit(0);
180
+ }
181
+
182
+ if (hasFlag('--help') || hasFlag('-h')) {
183
+ console.log(`
184
+ ${bold('dot-clean')} — Remove OS-generated junk files (.DS_Store, Thumbs.db, etc.)
185
+
186
+ ${bold('USAGE')}
187
+ dot-clean [dir] [options]
188
+
189
+ ${bold('OPTIONS')}
190
+ --type <list> Categories: macos,windows,linux,ide,editor (default: macos,windows,linux)
191
+ --all Remove all categories (macos,windows,linux,ide,editor)
192
+ --dry-run Show what would be removed without removing
193
+ --gitignore Update .gitignore to prevent future junk
194
+ --stats Show category breakdown
195
+ --version Show version
196
+
197
+ ${bold('EXAMPLES')}
198
+ dot-clean # Remove macOS/Windows/Linux junk in .
199
+ dot-clean ~/Desktop # Clean a specific directory
200
+ dot-clean --all # Remove all junk including IDE/editor files
201
+ dot-clean --dry-run # Preview without deleting
202
+ dot-clean --gitignore # Also update .gitignore
203
+ dot-clean --type macos # Only remove macOS junk
204
+ `);
205
+ process.exit(0);
206
+ }
207
+
208
+ const targetDir = path.resolve(positional[0] || '.');
209
+ const dryRun = hasFlag('--dry-run');
210
+ const doGitignore = hasFlag('--gitignore');
211
+ const showStats = hasFlag('--stats');
212
+
213
+ let categories;
214
+ if (hasFlag('--all')) {
215
+ categories = ALL_CATEGORIES;
216
+ } else if (getFlag('--type')) {
217
+ categories = getFlag('--type').split(',').map(s => s.trim());
218
+ for (const c of categories) {
219
+ if (!JUNK_FILES[c]) { console.error(red(`\nUnknown type: ${c}. Valid: ${ALL_CATEGORIES.join(', ')}\n`)); process.exit(1); }
220
+ }
221
+ } else {
222
+ categories = ['macos', 'windows', 'linux'];
223
+ }
224
+
225
+ if (!fs.existsSync(targetDir)) {
226
+ console.error(red(`\nDirectory not found: ${targetDir}\n`)); process.exit(1);
227
+ }
228
+
229
+ console.log(`\n${bold('dot-clean')} ${cyan(targetDir)}`);
230
+ console.log(dim(` Scanning for: ${categories.join(', ')}...\n`));
231
+
232
+ const found = scan(targetDir, categories);
233
+
234
+ if (!found.length) {
235
+ console.log(green(' No junk files found.\n'));
236
+ if (doGitignore) updateGitignore(targetDir, categories);
237
+ process.exit(0);
238
+ }
239
+
240
+ // Group by category for stats
241
+ const byCat = {};
242
+ let totalSize = 0;
243
+ for (const f of found) {
244
+ if (!byCat[f.category]) byCat[f.category] = 0;
245
+ byCat[f.category]++;
246
+ totalSize += f.size;
247
+ }
248
+
249
+ // Display files
250
+ for (const f of found) {
251
+ const rel = path.relative(targetDir, f.path);
252
+ const sizeFmt = dim(formatSize(f.size));
253
+ const type = f.isDir ? dim('/') : '';
254
+ if (dryRun) {
255
+ console.log(` ${yellow('~')} ${rel}${type} ${sizeFmt}`);
256
+ } else {
257
+ const ok = removeRecursive(f.path);
258
+ if (ok) {
259
+ console.log(` ${red('✗')} ${rel}${type} ${sizeFmt}`);
260
+ } else {
261
+ console.log(` ${yellow('!')} ${rel}${type} ${dim('(failed to remove)')}`);
262
+ }
263
+ }
264
+ }
265
+
266
+ console.log();
267
+
268
+ if (showStats) {
269
+ for (const [cat, count] of Object.entries(byCat)) {
270
+ console.log(` ${cat.padEnd(10)} ${count} file(s)`);
271
+ }
272
+ console.log();
273
+ }
274
+
275
+ const action = dryRun ? 'would remove' : 'removed';
276
+ console.log(dryRun
277
+ ? yellow(` Dry run: ${found.length} item(s) ${action} (${formatSize(totalSize)} total)`)
278
+ : red(` ${found.length} item(s) ${action} (${formatSize(totalSize)} freed)`)
279
+ );
280
+
281
+ if (doGitignore && !dryRun) {
282
+ console.log();
283
+ updateGitignore(targetDir, categories);
284
+ }
285
+
286
+ console.log();
287
+ process.exit(0);
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@lowdep/dot-clean",
3
+ "version": "1.0.0",
4
+ "description": "Remove .DS_Store, Thumbs.db, desktop.ini, and other OS-generated junk files recursively — zero dependencies",
5
+ "bin": {
6
+ "dot-clean": "bin/dot-clean.js"
7
+ },
8
+ "keywords": [
9
+ "ds-store",
10
+ "thumbs-db",
11
+ "junk-files",
12
+ "cleanup",
13
+ "cli",
14
+ "macos",
15
+ "windows",
16
+ "zero-dependencies",
17
+ ".DS_Store",
18
+ "Thumbs.db",
19
+ "desktop.ini",
20
+ "remove junk files",
21
+ "clean os files",
22
+ "delete ds_store",
23
+ "mac junk",
24
+ "cross-platform",
25
+ "zero dependencies"
26
+ ],
27
+ "author": "Rushabh Shah",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=14"
31
+ },
32
+ "files": [
33
+ "bin/"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Rushabh5000/dot-clean.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/Rushabh5000/dot-clean/issues"
41
+ },
42
+ "homepage": "https://github.com/Rushabh5000/dot-clean#readme",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }