repo-cloak-cli 1.2.4 → 1.3.2
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/.github/workflows/release.yml +92 -92
- package/LICENSE +21 -21
- package/README.md +138 -118
- package/bin/repo-cloak.js +9 -9
- package/package.json +50 -50
- package/src/cli.js +86 -84
- package/src/commands/pull.js +582 -352
- package/src/commands/push.js +250 -235
- package/src/core/anonymizer.js +128 -128
- package/src/core/copier.js +139 -139
- package/src/core/crypto.js +128 -128
- package/src/core/git.js +61 -0
- package/src/core/mapper.js +235 -235
- package/src/core/scanner.js +137 -137
- package/src/index.js +8 -8
- package/src/ui/banner.js +70 -70
- package/src/ui/fileSelector.js +256 -256
- package/src/ui/prompts.js +165 -165
- package/tests/anonymizer.test.js +127 -127
- package/tests/copier.test.js +94 -94
- package/tests/crypto.test.js +106 -106
- package/tests/git.test.js +103 -0
- package/tests/mapper.test.js +166 -166
- package/tests/scanner.test.js +100 -100
- package/medium.md +0 -319
package/src/core/anonymizer.js
CHANGED
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anonymizer
|
|
3
|
-
* Handles keyword replacement for anonymizing content
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Create a replacement function for a list of replacements
|
|
8
|
-
* @param {Array<{original: string, replacement: string}>} replacements
|
|
9
|
-
* @param {Object} options
|
|
10
|
-
* @returns {Function} Transform function
|
|
11
|
-
*/
|
|
12
|
-
export function createAnonymizer(replacements, options = {}) {
|
|
13
|
-
const { caseSensitive = false } = options;
|
|
14
|
-
|
|
15
|
-
if (!replacements || replacements.length === 0) {
|
|
16
|
-
return (content) => content;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return (content) => {
|
|
20
|
-
let result = content;
|
|
21
|
-
|
|
22
|
-
for (const { original, replacement } of replacements) {
|
|
23
|
-
if (caseSensitive) {
|
|
24
|
-
// Case-sensitive replacement
|
|
25
|
-
result = result.split(original).join(replacement);
|
|
26
|
-
} else {
|
|
27
|
-
// Case-insensitive replacement using regex
|
|
28
|
-
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
29
|
-
result = result.replace(regex, (match) => {
|
|
30
|
-
// Preserve case pattern
|
|
31
|
-
return matchCase(match, replacement);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create reverse anonymizer (for push operation)
|
|
42
|
-
* @param {Array<{original: string, replacement: string}>} replacements
|
|
43
|
-
* @returns {Function} Transform function
|
|
44
|
-
*/
|
|
45
|
-
export function createDeanonymizer(replacements, options = {}) {
|
|
46
|
-
const { caseSensitive = false } = options;
|
|
47
|
-
|
|
48
|
-
if (!replacements || replacements.length === 0) {
|
|
49
|
-
return (content) => content;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Reverse the replacements
|
|
53
|
-
const reversed = replacements.map(r => ({
|
|
54
|
-
original: r.replacement,
|
|
55
|
-
replacement: r.original
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
return createAnonymizer(reversed, options);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Escape special regex characters
|
|
63
|
-
*/
|
|
64
|
-
function escapeRegex(string) {
|
|
65
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Match the case pattern of the original to the replacement
|
|
70
|
-
*/
|
|
71
|
-
function matchCase(original, replacement) {
|
|
72
|
-
// If original is all uppercase, make replacement uppercase
|
|
73
|
-
if (original === original.toUpperCase()) {
|
|
74
|
-
return replacement.toUpperCase();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// If original is all lowercase, make replacement lowercase
|
|
78
|
-
if (original === original.toLowerCase()) {
|
|
79
|
-
return replacement.toLowerCase();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If original is Title Case, make replacement Title Case
|
|
83
|
-
if (original[0] === original[0].toUpperCase()) {
|
|
84
|
-
return replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Default: return replacement as-is
|
|
88
|
-
return replacement;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Apply replacements to a file path (for renaming files/folders)
|
|
93
|
-
*/
|
|
94
|
-
export function anonymizePath(filePath, replacements) {
|
|
95
|
-
if (!replacements || replacements.length === 0) {
|
|
96
|
-
return filePath;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let result = filePath;
|
|
100
|
-
|
|
101
|
-
for (const { original, replacement } of replacements) {
|
|
102
|
-
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
103
|
-
result = result.replace(regex, replacement);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Count replacements in content
|
|
111
|
-
*/
|
|
112
|
-
export function countReplacements(content, replacements) {
|
|
113
|
-
if (!replacements || replacements.length === 0) {
|
|
114
|
-
return 0;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let count = 0;
|
|
118
|
-
|
|
119
|
-
for (const { original } of replacements) {
|
|
120
|
-
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
121
|
-
const matches = content.match(regex);
|
|
122
|
-
if (matches) {
|
|
123
|
-
count += matches.length;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return count;
|
|
128
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Anonymizer
|
|
3
|
+
* Handles keyword replacement for anonymizing content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a replacement function for a list of replacements
|
|
8
|
+
* @param {Array<{original: string, replacement: string}>} replacements
|
|
9
|
+
* @param {Object} options
|
|
10
|
+
* @returns {Function} Transform function
|
|
11
|
+
*/
|
|
12
|
+
export function createAnonymizer(replacements, options = {}) {
|
|
13
|
+
const { caseSensitive = false } = options;
|
|
14
|
+
|
|
15
|
+
if (!replacements || replacements.length === 0) {
|
|
16
|
+
return (content) => content;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (content) => {
|
|
20
|
+
let result = content;
|
|
21
|
+
|
|
22
|
+
for (const { original, replacement } of replacements) {
|
|
23
|
+
if (caseSensitive) {
|
|
24
|
+
// Case-sensitive replacement
|
|
25
|
+
result = result.split(original).join(replacement);
|
|
26
|
+
} else {
|
|
27
|
+
// Case-insensitive replacement using regex
|
|
28
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
29
|
+
result = result.replace(regex, (match) => {
|
|
30
|
+
// Preserve case pattern
|
|
31
|
+
return matchCase(match, replacement);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create reverse anonymizer (for push operation)
|
|
42
|
+
* @param {Array<{original: string, replacement: string}>} replacements
|
|
43
|
+
* @returns {Function} Transform function
|
|
44
|
+
*/
|
|
45
|
+
export function createDeanonymizer(replacements, options = {}) {
|
|
46
|
+
const { caseSensitive = false } = options;
|
|
47
|
+
|
|
48
|
+
if (!replacements || replacements.length === 0) {
|
|
49
|
+
return (content) => content;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reverse the replacements
|
|
53
|
+
const reversed = replacements.map(r => ({
|
|
54
|
+
original: r.replacement,
|
|
55
|
+
replacement: r.original
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
return createAnonymizer(reversed, options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Escape special regex characters
|
|
63
|
+
*/
|
|
64
|
+
function escapeRegex(string) {
|
|
65
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Match the case pattern of the original to the replacement
|
|
70
|
+
*/
|
|
71
|
+
function matchCase(original, replacement) {
|
|
72
|
+
// If original is all uppercase, make replacement uppercase
|
|
73
|
+
if (original === original.toUpperCase()) {
|
|
74
|
+
return replacement.toUpperCase();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If original is all lowercase, make replacement lowercase
|
|
78
|
+
if (original === original.toLowerCase()) {
|
|
79
|
+
return replacement.toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If original is Title Case, make replacement Title Case
|
|
83
|
+
if (original[0] === original[0].toUpperCase()) {
|
|
84
|
+
return replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default: return replacement as-is
|
|
88
|
+
return replacement;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply replacements to a file path (for renaming files/folders)
|
|
93
|
+
*/
|
|
94
|
+
export function anonymizePath(filePath, replacements) {
|
|
95
|
+
if (!replacements || replacements.length === 0) {
|
|
96
|
+
return filePath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let result = filePath;
|
|
100
|
+
|
|
101
|
+
for (const { original, replacement } of replacements) {
|
|
102
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
103
|
+
result = result.replace(regex, replacement);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Count replacements in content
|
|
111
|
+
*/
|
|
112
|
+
export function countReplacements(content, replacements) {
|
|
113
|
+
if (!replacements || replacements.length === 0) {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let count = 0;
|
|
118
|
+
|
|
119
|
+
for (const { original } of replacements) {
|
|
120
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
121
|
+
const matches = content.match(regex);
|
|
122
|
+
if (matches) {
|
|
123
|
+
count += matches.length;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return count;
|
|
128
|
+
}
|
package/src/core/copier.js
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Copier
|
|
3
|
-
* Copies files while preserving directory structure
|
|
4
|
-
* Now also anonymizes folder/file names!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
-
import { dirname, join, relative, basename } from 'path';
|
|
9
|
-
import { isBinaryFile } from './scanner.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Apply replacements to a path (file/folder names)
|
|
13
|
-
*/
|
|
14
|
-
function anonymizePath(relativePath, replacements) {
|
|
15
|
-
if (!replacements || replacements.length === 0) {
|
|
16
|
-
return relativePath;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let result = relativePath;
|
|
20
|
-
for (const { original, replacement } of replacements) {
|
|
21
|
-
// Case-insensitive replacement
|
|
22
|
-
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
23
|
-
result = result.replace(regex, (match) => matchCase(match, replacement));
|
|
24
|
-
}
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Escape special regex characters
|
|
30
|
-
*/
|
|
31
|
-
function escapeRegex(string) {
|
|
32
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Match case pattern
|
|
37
|
-
*/
|
|
38
|
-
function matchCase(original, replacement) {
|
|
39
|
-
if (original === original.toUpperCase()) {
|
|
40
|
-
return replacement.toUpperCase();
|
|
41
|
-
}
|
|
42
|
-
if (original === original.toLowerCase()) {
|
|
43
|
-
return replacement.toLowerCase();
|
|
44
|
-
}
|
|
45
|
-
if (original[0] === original[0].toUpperCase()) {
|
|
46
|
-
return replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
47
|
-
}
|
|
48
|
-
return replacement;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Copy a single file, creating directories as needed
|
|
53
|
-
*/
|
|
54
|
-
export function copyFile(sourcePath, destPath) {
|
|
55
|
-
const destDir = dirname(destPath);
|
|
56
|
-
mkdirSync(destDir, { recursive: true });
|
|
57
|
-
copyFileSync(sourcePath, destPath);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Copy a file with content transformation
|
|
62
|
-
*/
|
|
63
|
-
export function copyFileWithTransform(sourcePath, destPath, transformFn) {
|
|
64
|
-
const destDir = dirname(destPath);
|
|
65
|
-
mkdirSync(destDir, { recursive: true });
|
|
66
|
-
|
|
67
|
-
if (isBinaryFile(sourcePath)) {
|
|
68
|
-
copyFileSync(sourcePath, destPath);
|
|
69
|
-
return { transformed: false };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
let content = readFileSync(sourcePath, 'utf-8');
|
|
74
|
-
const originalContent = content;
|
|
75
|
-
content = transformFn(content);
|
|
76
|
-
writeFileSync(destPath, content, 'utf-8');
|
|
77
|
-
return {
|
|
78
|
-
transformed: content !== originalContent,
|
|
79
|
-
originalLength: originalContent.length,
|
|
80
|
-
newLength: content.length
|
|
81
|
-
};
|
|
82
|
-
} catch (error) {
|
|
83
|
-
copyFileSync(sourcePath, destPath);
|
|
84
|
-
return { transformed: false, error: error.message };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Copy multiple files with progress callback
|
|
90
|
-
* Now also renames folders/files based on replacements!
|
|
91
|
-
*/
|
|
92
|
-
export async function copyFiles(files, sourceBase, destBase, transformFn, onProgress, replacements = []) {
|
|
93
|
-
const results = {
|
|
94
|
-
total: files.length,
|
|
95
|
-
copied: 0,
|
|
96
|
-
transformed: 0,
|
|
97
|
-
pathsRenamed: 0,
|
|
98
|
-
errors: []
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
for (let i = 0; i < files.length; i++) {
|
|
102
|
-
const file = files[i];
|
|
103
|
-
const originalRelativePath = typeof file === 'string'
|
|
104
|
-
? relative(sourceBase, file)
|
|
105
|
-
: file.relativePath;
|
|
106
|
-
const sourcePath = typeof file === 'string' ? file : file.absolutePath;
|
|
107
|
-
|
|
108
|
-
// Anonymize the path (folder and file names)
|
|
109
|
-
const anonymizedPath = anonymizePath(originalRelativePath, replacements);
|
|
110
|
-
const destPath = join(destBase, anonymizedPath);
|
|
111
|
-
|
|
112
|
-
if (anonymizedPath !== originalRelativePath) {
|
|
113
|
-
results.pathsRenamed++;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
if (transformFn) {
|
|
118
|
-
const result = copyFileWithTransform(sourcePath, destPath, transformFn);
|
|
119
|
-
if (result.transformed) {
|
|
120
|
-
results.transformed++;
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
copyFile(sourcePath, destPath);
|
|
124
|
-
}
|
|
125
|
-
results.copied++;
|
|
126
|
-
} catch (error) {
|
|
127
|
-
results.errors.push({
|
|
128
|
-
file: originalRelativePath,
|
|
129
|
-
error: error.message
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (onProgress) {
|
|
134
|
-
onProgress(i + 1, files.length, anonymizedPath);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return results;
|
|
139
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* File Copier
|
|
3
|
+
* Copies files while preserving directory structure
|
|
4
|
+
* Now also anonymizes folder/file names!
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { dirname, join, relative, basename } from 'path';
|
|
9
|
+
import { isBinaryFile } from './scanner.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Apply replacements to a path (file/folder names)
|
|
13
|
+
*/
|
|
14
|
+
function anonymizePath(relativePath, replacements) {
|
|
15
|
+
if (!replacements || replacements.length === 0) {
|
|
16
|
+
return relativePath;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let result = relativePath;
|
|
20
|
+
for (const { original, replacement } of replacements) {
|
|
21
|
+
// Case-insensitive replacement
|
|
22
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
23
|
+
result = result.replace(regex, (match) => matchCase(match, replacement));
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Escape special regex characters
|
|
30
|
+
*/
|
|
31
|
+
function escapeRegex(string) {
|
|
32
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Match case pattern
|
|
37
|
+
*/
|
|
38
|
+
function matchCase(original, replacement) {
|
|
39
|
+
if (original === original.toUpperCase()) {
|
|
40
|
+
return replacement.toUpperCase();
|
|
41
|
+
}
|
|
42
|
+
if (original === original.toLowerCase()) {
|
|
43
|
+
return replacement.toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
if (original[0] === original[0].toUpperCase()) {
|
|
46
|
+
return replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
47
|
+
}
|
|
48
|
+
return replacement;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Copy a single file, creating directories as needed
|
|
53
|
+
*/
|
|
54
|
+
export function copyFile(sourcePath, destPath) {
|
|
55
|
+
const destDir = dirname(destPath);
|
|
56
|
+
mkdirSync(destDir, { recursive: true });
|
|
57
|
+
copyFileSync(sourcePath, destPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Copy a file with content transformation
|
|
62
|
+
*/
|
|
63
|
+
export function copyFileWithTransform(sourcePath, destPath, transformFn) {
|
|
64
|
+
const destDir = dirname(destPath);
|
|
65
|
+
mkdirSync(destDir, { recursive: true });
|
|
66
|
+
|
|
67
|
+
if (isBinaryFile(sourcePath)) {
|
|
68
|
+
copyFileSync(sourcePath, destPath);
|
|
69
|
+
return { transformed: false };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
let content = readFileSync(sourcePath, 'utf-8');
|
|
74
|
+
const originalContent = content;
|
|
75
|
+
content = transformFn(content);
|
|
76
|
+
writeFileSync(destPath, content, 'utf-8');
|
|
77
|
+
return {
|
|
78
|
+
transformed: content !== originalContent,
|
|
79
|
+
originalLength: originalContent.length,
|
|
80
|
+
newLength: content.length
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
copyFileSync(sourcePath, destPath);
|
|
84
|
+
return { transformed: false, error: error.message };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Copy multiple files with progress callback
|
|
90
|
+
* Now also renames folders/files based on replacements!
|
|
91
|
+
*/
|
|
92
|
+
export async function copyFiles(files, sourceBase, destBase, transformFn, onProgress, replacements = []) {
|
|
93
|
+
const results = {
|
|
94
|
+
total: files.length,
|
|
95
|
+
copied: 0,
|
|
96
|
+
transformed: 0,
|
|
97
|
+
pathsRenamed: 0,
|
|
98
|
+
errors: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < files.length; i++) {
|
|
102
|
+
const file = files[i];
|
|
103
|
+
const originalRelativePath = typeof file === 'string'
|
|
104
|
+
? relative(sourceBase, file)
|
|
105
|
+
: file.relativePath;
|
|
106
|
+
const sourcePath = typeof file === 'string' ? file : file.absolutePath;
|
|
107
|
+
|
|
108
|
+
// Anonymize the path (folder and file names)
|
|
109
|
+
const anonymizedPath = anonymizePath(originalRelativePath, replacements);
|
|
110
|
+
const destPath = join(destBase, anonymizedPath);
|
|
111
|
+
|
|
112
|
+
if (anonymizedPath !== originalRelativePath) {
|
|
113
|
+
results.pathsRenamed++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (transformFn) {
|
|
118
|
+
const result = copyFileWithTransform(sourcePath, destPath, transformFn);
|
|
119
|
+
if (result.transformed) {
|
|
120
|
+
results.transformed++;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
copyFile(sourcePath, destPath);
|
|
124
|
+
}
|
|
125
|
+
results.copied++;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
results.errors.push({
|
|
128
|
+
file: originalRelativePath,
|
|
129
|
+
error: error.message
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (onProgress) {
|
|
134
|
+
onProgress(i + 1, files.length, anonymizedPath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return results;
|
|
139
|
+
}
|