@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.
@@ -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,5 @@
1
+ // Re-export all individual functions from each module
2
+ export * from './fileSystemUtils.js';
3
+ export * from './fileScanner.js';
4
+ export * from './fileMover.js';
5
+ export * from './fileHasher.js';
@@ -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
+ };