@tb.p/dd 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/controllers/getExtensionsController.js +27 -0
- package/controllers/newController.js +72 -0
- package/controllers/resumeController.js +233 -0
- package/database/README.md +262 -0
- package/database/dbConnection.js +314 -0
- package/database/dbOperations.js +332 -0
- package/database/dbUtils.js +125 -0
- package/database/dbValidator.js +325 -0
- package/database/index.js +102 -0
- package/index.js +75 -0
- package/package.json +32 -0
- package/processors/optionETL.js +82 -0
- package/utils/README.md +261 -0
- package/utils/candidateDetection.js +541 -0
- package/utils/duplicateMover.js +140 -0
- package/utils/duplicateReporter.js +91 -0
- package/utils/fileHasher.js +195 -0
- package/utils/fileMover.js +180 -0
- package/utils/fileScanner.js +128 -0
- package/utils/fileSystemUtils.js +192 -0
- package/utils/index.js +5 -0
- package/validators/optionValidator.js +103 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File System Utilities for @tb.p/dd
|
|
6
|
+
* Essential file system operations for scanning, moving, and managing files
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if a path exists (async)
|
|
11
|
+
* @param {string} filePath - Path to check
|
|
12
|
+
* @returns {Promise<boolean>} - True if path exists
|
|
13
|
+
*/
|
|
14
|
+
async function pathExists(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
await fs.access(filePath);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get file stats (async)
|
|
25
|
+
* @param {string} filePath - Path to file
|
|
26
|
+
* @returns {Promise<fs.Stats>} - File stats
|
|
27
|
+
*/
|
|
28
|
+
async function getFileStats(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
return await fs.stat(filePath);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`Failed to get stats for ${filePath}: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if path is a directory
|
|
38
|
+
* @param {string} filePath - Path to check
|
|
39
|
+
* @returns {Promise<boolean>} - True if directory
|
|
40
|
+
*/
|
|
41
|
+
async function isDirectory(filePath) {
|
|
42
|
+
const stats = await getFileStats(filePath);
|
|
43
|
+
return stats.isDirectory();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get file extension (without dot)
|
|
48
|
+
* @param {string} filePath - Path to file
|
|
49
|
+
* @returns {string} - File extension without dot
|
|
50
|
+
*/
|
|
51
|
+
function getFileExtension(filePath) {
|
|
52
|
+
return path.extname(filePath).toLowerCase().substring(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Normalize path separators for current OS
|
|
57
|
+
* @param {string} filePath - Path to normalize
|
|
58
|
+
* @returns {string} - Normalized path
|
|
59
|
+
*/
|
|
60
|
+
function normalizePath(filePath) {
|
|
61
|
+
return path.normalize(filePath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Join paths safely
|
|
66
|
+
* @param {...string} paths - Paths to join
|
|
67
|
+
* @returns {string} - Joined path
|
|
68
|
+
*/
|
|
69
|
+
function joinPaths(...paths) {
|
|
70
|
+
return path.join(...paths);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create directory recursively
|
|
75
|
+
* @param {string} dirPath - Directory path to create
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
async function createDirectory(dirPath) {
|
|
79
|
+
try {
|
|
80
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Remove file or directory
|
|
88
|
+
* @param {string} filePath - Path to remove
|
|
89
|
+
* @param {Object} options - Options
|
|
90
|
+
* @param {boolean} options.recursive - Remove recursively for directories
|
|
91
|
+
* @param {boolean} options.force - Force removal
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function removePath(filePath, options = {}) {
|
|
95
|
+
const { recursive = false, force = false } = options;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const stats = await getFileStats(filePath);
|
|
99
|
+
|
|
100
|
+
if (stats.isDirectory()) {
|
|
101
|
+
if (recursive) {
|
|
102
|
+
await fs.rmdir(filePath, { recursive: true });
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(`Directory ${filePath} cannot be removed without recursive option`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
await fs.unlink(filePath);
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (force) {
|
|
111
|
+
console.warn(`Warning: Failed to remove ${filePath}: ${error.message}`);
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(`Failed to remove ${filePath}: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Copy file
|
|
120
|
+
* @param {string} src - Source file path
|
|
121
|
+
* @param {string} dest - Destination file path
|
|
122
|
+
* @param {Object} options - Options
|
|
123
|
+
* @param {boolean} options.overwrite - Overwrite if destination exists
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async function copyFile(src, dest, options = {}) {
|
|
127
|
+
const { overwrite = false } = options;
|
|
128
|
+
|
|
129
|
+
if (!overwrite && await pathExists(dest)) {
|
|
130
|
+
throw new Error(`Destination file already exists: ${dest}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Ensure destination directory exists
|
|
134
|
+
const destDir = path.dirname(dest);
|
|
135
|
+
await createDirectory(destDir);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await fs.copyFile(src, dest);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new Error(`Failed to copy file from ${src} to ${dest}: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Move file
|
|
146
|
+
* @param {string} src - Source file path
|
|
147
|
+
* @param {string} dest - Destination file path
|
|
148
|
+
* @param {Object} options - Options
|
|
149
|
+
* @param {boolean} options.overwrite - Overwrite if destination exists
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
async function moveFile(src, dest, options = {}) {
|
|
153
|
+
const { overwrite = false } = options;
|
|
154
|
+
|
|
155
|
+
if (!overwrite && await pathExists(dest)) {
|
|
156
|
+
throw new Error(`Destination file already exists: ${dest}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Ensure destination directory exists
|
|
160
|
+
const destDir = path.dirname(dest);
|
|
161
|
+
await createDirectory(destDir);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await fs.rename(src, dest);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
throw new Error(`Failed to move file from ${src} to ${dest}: ${error.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get file size in bytes
|
|
172
|
+
* @param {string} filePath - Path to file
|
|
173
|
+
* @returns {Promise<number>} - File size in bytes
|
|
174
|
+
*/
|
|
175
|
+
async function getFileSize(filePath) {
|
|
176
|
+
const stats = await getFileStats(filePath);
|
|
177
|
+
return stats.size;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
pathExists,
|
|
182
|
+
getFileStats,
|
|
183
|
+
isDirectory,
|
|
184
|
+
getFileExtension,
|
|
185
|
+
normalizePath,
|
|
186
|
+
joinPaths,
|
|
187
|
+
createDirectory,
|
|
188
|
+
removePath,
|
|
189
|
+
copyFile,
|
|
190
|
+
moveFile,
|
|
191
|
+
getFileSize
|
|
192
|
+
};
|
package/utils/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
function validatePath(path) {
|
|
4
|
+
try {
|
|
5
|
+
const stats = fs.statSync(path);
|
|
6
|
+
return stats.isDirectory();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function validateNewFilePath(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(filePath)) {
|
|
15
|
+
return {
|
|
16
|
+
isValid: false,
|
|
17
|
+
error: `File already exists: ${filePath}`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parentDir = path.dirname(filePath);
|
|
22
|
+
if (!fs.existsSync(parentDir)) {
|
|
23
|
+
return {
|
|
24
|
+
isValid: false,
|
|
25
|
+
error: `Parent directory does not exist: ${parentDir}`
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const testFile = path.join(parentDir, '.test-write-permission');
|
|
30
|
+
fs.writeFileSync(testFile, 'test');
|
|
31
|
+
fs.unlinkSync(testFile);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
isValid: false,
|
|
35
|
+
error: `Parent directory is not writable: ${parentDir}`
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { isValid: true };
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
isValid: false,
|
|
43
|
+
error: `Invalid file path: ${filePath}`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateOptions(options) {
|
|
49
|
+
// Check if --targets was explicitly provided (not just the default)
|
|
50
|
+
// Commander.js sets the default value even when not explicitly provided
|
|
51
|
+
// We need to check if the raw targets value matches the default
|
|
52
|
+
const defaultTargets = process.cwd();
|
|
53
|
+
const targetsExplicitlyProvided = options._raw &&
|
|
54
|
+
options._raw.targets !== undefined &&
|
|
55
|
+
options._raw.targets !== defaultTargets;
|
|
56
|
+
|
|
57
|
+
// Check for get-extensions mode or save mode
|
|
58
|
+
const isGetExtensionsMode = options.mode === 'get-extensions';
|
|
59
|
+
const isSaveMode = options.mode === 'save';
|
|
60
|
+
|
|
61
|
+
if ((isGetExtensionsMode || isSaveMode) && !targetsExplicitlyProvided) {
|
|
62
|
+
console.error('Error: --targets is required when using --get-extensions or --save');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.targets) {
|
|
67
|
+
const invalidPaths = [];
|
|
68
|
+
for (const target of options.targets) {
|
|
69
|
+
if (!validatePath(target)) {
|
|
70
|
+
invalidPaths.push(target);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (invalidPaths.length > 0) {
|
|
75
|
+
console.error('Error: The following target paths do not exist or are not directories:');
|
|
76
|
+
invalidPaths.forEach(path => console.error(` - ${path}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (options.move && !options.saveDb && !options.resumeDb) {
|
|
82
|
+
console.error('Error: --move is only available with --save or --resume');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (options.extensions && !options.saveDb && !options.resumeDb) {
|
|
87
|
+
console.error('Error: --extensions is only available with --save or --resume');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
if (options.saveDb) {
|
|
91
|
+
const validation = validateNewFilePath(options.saveDb);
|
|
92
|
+
if (!validation.isValid) {
|
|
93
|
+
console.error(`Error: ${validation.error}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
validateOptions,
|
|
101
|
+
validatePath,
|
|
102
|
+
validateNewFilePath
|
|
103
|
+
};
|