@singhaman21/cleansweep 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 +447 -0
- package/USAGE.md +81 -0
- package/delete.sh +489 -0
- package/dist/bin/cli.d.ts +7 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +114 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/core/deletionEngine.d.ts +47 -0
- package/dist/core/deletionEngine.d.ts.map +1 -0
- package/dist/core/deletionEngine.js +184 -0
- package/dist/core/deletionEngine.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +54 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +232 -0
- package/dist/utils/fileUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +110 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompts.d.ts +17 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +86 -0
- package/dist/utils/prompts.js.map +1 -0
- package/package.json +49 -0
- package/src/bin/cli.ts +130 -0
- package/src/core/deletionEngine.ts +219 -0
- package/src/types.ts +30 -0
- package/src/utils/fileUtils.ts +218 -0
- package/src/utils/logger.ts +91 -0
- package/src/utils/prompts.ts +54 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File utility functions for cleansweep
|
|
3
|
+
* Handles file system operations and pattern matching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { glob } from 'glob';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a given path should be excluded based on exclusion patterns
|
|
12
|
+
* @param filePath - Path to check
|
|
13
|
+
* @param excludePatterns - Array of exclusion patterns
|
|
14
|
+
* @returns true if should be excluded, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
export function shouldExclude(filePath: string, excludePatterns: string[]): boolean {
|
|
17
|
+
for (const pattern of excludePatterns) {
|
|
18
|
+
// Check if path contains the pattern or basename matches
|
|
19
|
+
if (filePath.includes(pattern) || path.basename(filePath) === pattern) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Finds files matching a pattern with optional depth limit
|
|
28
|
+
* @param pattern - Glob pattern to match
|
|
29
|
+
* @param maxDepth - Maximum depth to search (optional)
|
|
30
|
+
* @param currentDir - Current directory to search in
|
|
31
|
+
* @returns Array of matching file paths
|
|
32
|
+
*/
|
|
33
|
+
export async function findFiles(
|
|
34
|
+
pattern: string,
|
|
35
|
+
maxDepth?: number,
|
|
36
|
+
currentDir: string = process.cwd()
|
|
37
|
+
): Promise<string[]> {
|
|
38
|
+
const options: any = {
|
|
39
|
+
cwd: currentDir,
|
|
40
|
+
absolute: false,
|
|
41
|
+
nodir: true
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (maxDepth !== undefined) {
|
|
45
|
+
options.maxDepth = maxDepth;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const files = await glob(pattern, options);
|
|
50
|
+
return files.map(file => path.relative(process.cwd(), path.resolve(currentDir, file)));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finds directories matching a pattern with optional depth limit
|
|
58
|
+
* @param pattern - Glob pattern to match
|
|
59
|
+
* @param maxDepth - Maximum depth to search (optional)
|
|
60
|
+
* @param currentDir - Current directory to search in
|
|
61
|
+
* @returns Array of matching directory paths
|
|
62
|
+
*/
|
|
63
|
+
export async function findDirectories(
|
|
64
|
+
pattern: string,
|
|
65
|
+
maxDepth?: number,
|
|
66
|
+
currentDir: string = process.cwd()
|
|
67
|
+
): Promise<string[]> {
|
|
68
|
+
const directories: string[] = [];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Recursively search for directories
|
|
72
|
+
*/
|
|
73
|
+
function searchDir(dir: string, currentDepth: number = 0): void {
|
|
74
|
+
if (maxDepth !== undefined && currentDepth > maxDepth) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const fullPath = path.join(dir, entry.name);
|
|
83
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
// Check if directory name matches pattern
|
|
87
|
+
if (matchesPattern(entry.name, pattern)) {
|
|
88
|
+
directories.push(relativePath);
|
|
89
|
+
}
|
|
90
|
+
// Recursively search subdirectories
|
|
91
|
+
searchDir(fullPath, currentDepth + 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Skip directories we can't read
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
searchDir(currentDir);
|
|
101
|
+
return directories;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Finds both files and directories matching a pattern
|
|
106
|
+
* @param pattern - Glob pattern to match
|
|
107
|
+
* @param maxDepth - Maximum depth to search (optional)
|
|
108
|
+
* @param currentDir - Current directory to search in
|
|
109
|
+
* @returns Array of matching paths
|
|
110
|
+
*/
|
|
111
|
+
export async function findItems(
|
|
112
|
+
pattern: string,
|
|
113
|
+
maxDepth?: number,
|
|
114
|
+
currentDir: string = process.cwd()
|
|
115
|
+
): Promise<string[]> {
|
|
116
|
+
const items: string[] = [];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Recursively search for items
|
|
120
|
+
*/
|
|
121
|
+
function searchDir(dir: string, currentDepth: number = 0): void {
|
|
122
|
+
if (maxDepth !== undefined && currentDepth > maxDepth) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
128
|
+
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const fullPath = path.join(dir, entry.name);
|
|
131
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
132
|
+
|
|
133
|
+
// Check if name matches pattern
|
|
134
|
+
if (matchesPattern(entry.name, pattern)) {
|
|
135
|
+
items.push(relativePath);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Recursively search subdirectories
|
|
139
|
+
if (entry.isDirectory()) {
|
|
140
|
+
searchDir(fullPath, currentDepth + 1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Skip directories we can't read
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
searchDir(currentDir);
|
|
150
|
+
return items;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Simple pattern matching (converts glob to regex-like matching)
|
|
155
|
+
* @param name - Name to check
|
|
156
|
+
* @param pattern - Pattern to match against
|
|
157
|
+
* @returns true if matches, false otherwise
|
|
158
|
+
*/
|
|
159
|
+
function matchesPattern(name: string, pattern: string): boolean {
|
|
160
|
+
// Convert glob pattern to regex
|
|
161
|
+
const regexPattern = pattern
|
|
162
|
+
.replace(/\*/g, '.*')
|
|
163
|
+
.replace(/\?/g, '.');
|
|
164
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
165
|
+
return regex.test(name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Deletes a file or directory
|
|
170
|
+
* @param itemPath - Path to the item to delete
|
|
171
|
+
* @returns true if successful, false otherwise
|
|
172
|
+
*/
|
|
173
|
+
export function deleteItem(itemPath: string): boolean {
|
|
174
|
+
try {
|
|
175
|
+
const fullPath = path.resolve(itemPath);
|
|
176
|
+
const stat = fs.statSync(fullPath);
|
|
177
|
+
|
|
178
|
+
if (stat.isDirectory()) {
|
|
179
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
180
|
+
return true;
|
|
181
|
+
} else if (stat.isFile()) {
|
|
182
|
+
fs.unlinkSync(fullPath);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if a path exists and is a file
|
|
193
|
+
* @param itemPath - Path to check
|
|
194
|
+
* @returns true if exists and is a file
|
|
195
|
+
*/
|
|
196
|
+
export function isFile(itemPath: string): boolean {
|
|
197
|
+
try {
|
|
198
|
+
const stat = fs.statSync(itemPath);
|
|
199
|
+
return stat.isFile();
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Checks if a path exists and is a directory
|
|
207
|
+
* @param itemPath - Path to check
|
|
208
|
+
* @returns true if exists and is a directory
|
|
209
|
+
*/
|
|
210
|
+
export function isDirectory(itemPath: string): boolean {
|
|
211
|
+
try {
|
|
212
|
+
const stat = fs.statSync(itemPath);
|
|
213
|
+
return stat.isDirectory();
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for cleansweep
|
|
3
|
+
* Handles logging to console and log files with timestamps
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { LogLevel, OutputFormat } from '../types';
|
|
9
|
+
|
|
10
|
+
export class Logger {
|
|
11
|
+
private logFile?: string;
|
|
12
|
+
private outputFormat: OutputFormat;
|
|
13
|
+
|
|
14
|
+
constructor(logFile?: string, outputFormat: OutputFormat = 'plain') {
|
|
15
|
+
this.logFile = logFile;
|
|
16
|
+
this.outputFormat = outputFormat;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Logs a message with timestamp to both console and log file (if specified)
|
|
21
|
+
* @param message - Message to log
|
|
22
|
+
* @param level - Log level (INFO, WARNING, ERROR)
|
|
23
|
+
*/
|
|
24
|
+
log(message: string, level: LogLevel = 'INFO'): void {
|
|
25
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
26
|
+
const formattedMessage = `[${timestamp}] [${level}] ${message}`;
|
|
27
|
+
|
|
28
|
+
// Print to console
|
|
29
|
+
if (this.outputFormat === 'json') {
|
|
30
|
+
const jsonMessage = JSON.stringify({
|
|
31
|
+
timestamp,
|
|
32
|
+
level,
|
|
33
|
+
message
|
|
34
|
+
});
|
|
35
|
+
console.log(jsonMessage);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(formattedMessage);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Write to log file if specified
|
|
41
|
+
if (this.logFile) {
|
|
42
|
+
try {
|
|
43
|
+
fs.appendFileSync(this.logFile, formattedMessage + '\n');
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`Failed to write to log file: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initializes the log file with header information
|
|
52
|
+
* @param config - Configuration object with all settings
|
|
53
|
+
*/
|
|
54
|
+
initializeLogFile(config: {
|
|
55
|
+
dryRun: boolean;
|
|
56
|
+
interactive: boolean;
|
|
57
|
+
force: boolean;
|
|
58
|
+
filesPattern?: string;
|
|
59
|
+
foldersPattern?: string;
|
|
60
|
+
typesPattern?: string;
|
|
61
|
+
excludePatterns: string[];
|
|
62
|
+
maxDepth?: number;
|
|
63
|
+
}): void {
|
|
64
|
+
if (!this.logFile) return;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const header = [
|
|
68
|
+
'==========================================',
|
|
69
|
+
`Deletion Log - ${new Date().toISOString().replace('T', ' ').slice(0, 19)}`,
|
|
70
|
+
'==========================================',
|
|
71
|
+
`Dry Run: ${config.dryRun}`,
|
|
72
|
+
`Interactive: ${config.interactive}`,
|
|
73
|
+
`Force: ${config.force}`,
|
|
74
|
+
...(config.filesPattern ? [`Files Pattern: ${config.filesPattern}`] : []),
|
|
75
|
+
...(config.foldersPattern ? [`Folders Pattern: ${config.foldersPattern}`] : []),
|
|
76
|
+
...(config.typesPattern ? [`Types Pattern: ${config.typesPattern}`] : []),
|
|
77
|
+
...(config.excludePatterns.length > 0
|
|
78
|
+
? [`Exclude Patterns: ${config.excludePatterns.join(', ')}`]
|
|
79
|
+
: []),
|
|
80
|
+
...(config.maxDepth ? [`Max Depth: ${config.maxDepth}`] : []),
|
|
81
|
+
'==========================================',
|
|
82
|
+
''
|
|
83
|
+
].join('\n');
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(this.logFile, header);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`Failed to initialize log file: ${error}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompt utilities for cleansweep
|
|
3
|
+
* Handles user confirmation prompts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as readline from 'readline';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a readline interface for user input
|
|
10
|
+
*/
|
|
11
|
+
function createReadlineInterface(): readline.Interface {
|
|
12
|
+
return readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prompts the user for confirmation before deleting an item
|
|
20
|
+
* @param item - Path of the item to delete
|
|
21
|
+
* @param force - If true, always returns true without prompting
|
|
22
|
+
* @returns Promise that resolves to true if confirmed, false otherwise
|
|
23
|
+
*/
|
|
24
|
+
export function confirmDeletion(item: string, force: boolean = false): Promise<boolean> {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
if (force) {
|
|
27
|
+
resolve(true);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rl = createReadlineInterface();
|
|
32
|
+
rl.question(`Delete '${item}'? [y/N]: `, (answer) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
const response = answer.trim().toLowerCase();
|
|
35
|
+
resolve(response === 'y' || response === 'yes');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prompts the user to proceed with deletion after preview
|
|
42
|
+
* @returns Promise that resolves to true if confirmed, false otherwise
|
|
43
|
+
*/
|
|
44
|
+
export function confirmProceed(): Promise<boolean> {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const rl = createReadlineInterface();
|
|
47
|
+
rl.question('Proceed with deletion? [y/N]: ', (answer) => {
|
|
48
|
+
rl.close();
|
|
49
|
+
const response = answer.trim().toLowerCase();
|
|
50
|
+
resolve(response === 'y' || response === 'yes');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"moduleResolution": "node"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|
|
21
|
+
|