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/mapper.js
CHANGED
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mapping File Manager
|
|
3
|
-
* Tracks replacements and file mappings for push/pull operations
|
|
4
|
-
* Now with encrypted sensitive data!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { getOrCreateSecret, encryptReplacements, decryptReplacements, encrypt, decrypt } from './crypto.js';
|
|
10
|
-
|
|
11
|
-
const MAP_FILENAME = '.repo-cloak-map.json';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Create a mapping object with encrypted sensitive data
|
|
15
|
-
*/
|
|
16
|
-
export function createMapping(options) {
|
|
17
|
-
const {
|
|
18
|
-
sourceDir,
|
|
19
|
-
destDir,
|
|
20
|
-
replacements,
|
|
21
|
-
files,
|
|
22
|
-
timestamp = new Date().toISOString()
|
|
23
|
-
} = options;
|
|
24
|
-
|
|
25
|
-
// Get user's secret for encryption
|
|
26
|
-
const secret = getOrCreateSecret();
|
|
27
|
-
|
|
28
|
-
// Encrypt sensitive paths
|
|
29
|
-
const encryptedSource = encrypt(sourceDir, secret);
|
|
30
|
-
const encryptedDest = encrypt(destDir, secret);
|
|
31
|
-
|
|
32
|
-
// Encrypt replacements (only the "original" field)
|
|
33
|
-
const encryptedReplacements = encryptReplacements(replacements, secret);
|
|
34
|
-
|
|
35
|
-
// Encrypt original file paths
|
|
36
|
-
const encryptedFiles = files.map(f => ({
|
|
37
|
-
original: encrypt(f.original, secret),
|
|
38
|
-
cloaked: f.cloaked // Keep cloaked version visible (it's anonymized)
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
version: '1.1.0', // Updated version for encryption
|
|
43
|
-
tool: 'repo-cloak',
|
|
44
|
-
timestamp,
|
|
45
|
-
encrypted: true, // Flag indicating encryption is used
|
|
46
|
-
source: {
|
|
47
|
-
path: encryptedSource,
|
|
48
|
-
platform: process.platform
|
|
49
|
-
},
|
|
50
|
-
destination: {
|
|
51
|
-
path: encryptedDest
|
|
52
|
-
},
|
|
53
|
-
replacements: encryptedReplacements,
|
|
54
|
-
files: encryptedFiles,
|
|
55
|
-
stats: {
|
|
56
|
-
totalFiles: files.length,
|
|
57
|
-
replacementsCount: replacements.length
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Save mapping to destination directory
|
|
64
|
-
*/
|
|
65
|
-
export function saveMapping(destDir, mapping) {
|
|
66
|
-
const mapPath = join(destDir, MAP_FILENAME);
|
|
67
|
-
writeFileSync(mapPath, JSON.stringify(mapping, null, 2), 'utf-8');
|
|
68
|
-
return mapPath;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Load and decrypt mapping from a cloaked directory
|
|
73
|
-
*/
|
|
74
|
-
export function loadMapping(cloakedDir, secret = null) {
|
|
75
|
-
const mapPath = join(cloakedDir, MAP_FILENAME);
|
|
76
|
-
|
|
77
|
-
if (!existsSync(mapPath)) {
|
|
78
|
-
throw new Error(`No mapping file found in ${cloakedDir}. Is this a repo-cloak backup?`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const content = readFileSync(mapPath, 'utf-8');
|
|
83
|
-
const mapping = JSON.parse(content);
|
|
84
|
-
|
|
85
|
-
// If encrypted, attempt decryption
|
|
86
|
-
if (mapping.encrypted && secret) {
|
|
87
|
-
return decryptMapping(mapping, secret);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return mapping;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
throw new Error(`Failed to read mapping file: ${error.message}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Decrypt a mapping object
|
|
98
|
-
*/
|
|
99
|
-
export function decryptMapping(mapping, secret) {
|
|
100
|
-
if (!mapping.encrypted) {
|
|
101
|
-
return mapping;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const decryptedSource = decrypt(mapping.source?.path, secret);
|
|
106
|
-
const decryptedDest = decrypt(mapping.destination?.path, secret);
|
|
107
|
-
const decryptedReplacements = decryptReplacements(mapping.replacements || [], secret);
|
|
108
|
-
|
|
109
|
-
const decryptedFiles = (mapping.files || []).map(f => ({
|
|
110
|
-
original: decrypt(f.original, secret),
|
|
111
|
-
cloaked: f.cloaked
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
...mapping,
|
|
116
|
-
source: {
|
|
117
|
-
...mapping.source,
|
|
118
|
-
path: decryptedSource,
|
|
119
|
-
decrypted: true
|
|
120
|
-
},
|
|
121
|
-
destination: {
|
|
122
|
-
...mapping.destination,
|
|
123
|
-
path: decryptedDest,
|
|
124
|
-
decrypted: true
|
|
125
|
-
},
|
|
126
|
-
replacements: decryptedReplacements,
|
|
127
|
-
files: decryptedFiles
|
|
128
|
-
};
|
|
129
|
-
} catch (error) {
|
|
130
|
-
throw new Error(`Failed to decrypt mapping: ${error.message}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Check if a directory has a mapping file
|
|
136
|
-
*/
|
|
137
|
-
export function hasMapping(dir) {
|
|
138
|
-
const mapPath = join(dir, MAP_FILENAME);
|
|
139
|
-
return existsSync(mapPath);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get the original source path from mapping (requires secret)
|
|
144
|
-
*/
|
|
145
|
-
export function getOriginalSource(mapping) {
|
|
146
|
-
return mapping.source?.path || null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Get replacements from mapping
|
|
151
|
-
*/
|
|
152
|
-
export function getReplacements(mapping) {
|
|
153
|
-
return mapping.replacements || [];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get file list from mapping
|
|
158
|
-
*/
|
|
159
|
-
export function getFiles(mapping) {
|
|
160
|
-
return mapping.files || [];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Update mapping with additional info
|
|
165
|
-
*/
|
|
166
|
-
export function updateMapping(destDir, updates) {
|
|
167
|
-
const mapPath = join(destDir, MAP_FILENAME);
|
|
168
|
-
|
|
169
|
-
if (!existsSync(mapPath)) {
|
|
170
|
-
throw new Error('No mapping file found');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const content = readFileSync(mapPath, 'utf-8');
|
|
174
|
-
const mapping = JSON.parse(content);
|
|
175
|
-
const updated = { ...mapping, ...updates, updatedAt: new Date().toISOString() };
|
|
176
|
-
|
|
177
|
-
writeFileSync(mapPath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
178
|
-
return updated;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Load existing raw mapping (without decryption) for merging
|
|
183
|
-
*/
|
|
184
|
-
export function loadRawMapping(cloakedDir) {
|
|
185
|
-
const mapPath = join(cloakedDir, MAP_FILENAME);
|
|
186
|
-
|
|
187
|
-
if (!existsSync(mapPath)) {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const content = readFileSync(mapPath, 'utf-8');
|
|
193
|
-
return JSON.parse(content);
|
|
194
|
-
} catch (error) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Merge new files into existing mapping (for incremental pulls)
|
|
201
|
-
*/
|
|
202
|
-
export function mergeMapping(existingMapping, newFiles) {
|
|
203
|
-
// Get existing file paths for deduplication
|
|
204
|
-
const existingPaths = new Set(
|
|
205
|
-
(existingMapping.files || []).map(f => f.cloaked)
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
// Filter out files that already exist (avoid duplicates)
|
|
209
|
-
const uniqueNewFiles = newFiles.filter(f => !existingPaths.has(f.cloaked));
|
|
210
|
-
|
|
211
|
-
// Merge files
|
|
212
|
-
const mergedFiles = [
|
|
213
|
-
...(existingMapping.files || []),
|
|
214
|
-
...uniqueNewFiles
|
|
215
|
-
];
|
|
216
|
-
|
|
217
|
-
// Track pull history
|
|
218
|
-
const pullHistory = existingMapping.pullHistory || [];
|
|
219
|
-
pullHistory.push({
|
|
220
|
-
timestamp: new Date().toISOString(),
|
|
221
|
-
filesAdded: uniqueNewFiles.length,
|
|
222
|
-
totalFiles: mergedFiles.length
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
...existingMapping,
|
|
227
|
-
files: mergedFiles,
|
|
228
|
-
pullHistory,
|
|
229
|
-
stats: {
|
|
230
|
-
...existingMapping.stats,
|
|
231
|
-
totalFiles: mergedFiles.length
|
|
232
|
-
},
|
|
233
|
-
updatedAt: new Date().toISOString()
|
|
234
|
-
};
|
|
235
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mapping File Manager
|
|
3
|
+
* Tracks replacements and file mappings for push/pull operations
|
|
4
|
+
* Now with encrypted sensitive data!
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { getOrCreateSecret, encryptReplacements, decryptReplacements, encrypt, decrypt } from './crypto.js';
|
|
10
|
+
|
|
11
|
+
const MAP_FILENAME = '.repo-cloak-map.json';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a mapping object with encrypted sensitive data
|
|
15
|
+
*/
|
|
16
|
+
export function createMapping(options) {
|
|
17
|
+
const {
|
|
18
|
+
sourceDir,
|
|
19
|
+
destDir,
|
|
20
|
+
replacements,
|
|
21
|
+
files,
|
|
22
|
+
timestamp = new Date().toISOString()
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
// Get user's secret for encryption
|
|
26
|
+
const secret = getOrCreateSecret();
|
|
27
|
+
|
|
28
|
+
// Encrypt sensitive paths
|
|
29
|
+
const encryptedSource = encrypt(sourceDir, secret);
|
|
30
|
+
const encryptedDest = encrypt(destDir, secret);
|
|
31
|
+
|
|
32
|
+
// Encrypt replacements (only the "original" field)
|
|
33
|
+
const encryptedReplacements = encryptReplacements(replacements, secret);
|
|
34
|
+
|
|
35
|
+
// Encrypt original file paths
|
|
36
|
+
const encryptedFiles = files.map(f => ({
|
|
37
|
+
original: encrypt(f.original, secret),
|
|
38
|
+
cloaked: f.cloaked // Keep cloaked version visible (it's anonymized)
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
version: '1.1.0', // Updated version for encryption
|
|
43
|
+
tool: 'repo-cloak',
|
|
44
|
+
timestamp,
|
|
45
|
+
encrypted: true, // Flag indicating encryption is used
|
|
46
|
+
source: {
|
|
47
|
+
path: encryptedSource,
|
|
48
|
+
platform: process.platform
|
|
49
|
+
},
|
|
50
|
+
destination: {
|
|
51
|
+
path: encryptedDest
|
|
52
|
+
},
|
|
53
|
+
replacements: encryptedReplacements,
|
|
54
|
+
files: encryptedFiles,
|
|
55
|
+
stats: {
|
|
56
|
+
totalFiles: files.length,
|
|
57
|
+
replacementsCount: replacements.length
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Save mapping to destination directory
|
|
64
|
+
*/
|
|
65
|
+
export function saveMapping(destDir, mapping) {
|
|
66
|
+
const mapPath = join(destDir, MAP_FILENAME);
|
|
67
|
+
writeFileSync(mapPath, JSON.stringify(mapping, null, 2), 'utf-8');
|
|
68
|
+
return mapPath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Load and decrypt mapping from a cloaked directory
|
|
73
|
+
*/
|
|
74
|
+
export function loadMapping(cloakedDir, secret = null) {
|
|
75
|
+
const mapPath = join(cloakedDir, MAP_FILENAME);
|
|
76
|
+
|
|
77
|
+
if (!existsSync(mapPath)) {
|
|
78
|
+
throw new Error(`No mapping file found in ${cloakedDir}. Is this a repo-cloak backup?`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(mapPath, 'utf-8');
|
|
83
|
+
const mapping = JSON.parse(content);
|
|
84
|
+
|
|
85
|
+
// If encrypted, attempt decryption
|
|
86
|
+
if (mapping.encrypted && secret) {
|
|
87
|
+
return decryptMapping(mapping, secret);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return mapping;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`Failed to read mapping file: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Decrypt a mapping object
|
|
98
|
+
*/
|
|
99
|
+
export function decryptMapping(mapping, secret) {
|
|
100
|
+
if (!mapping.encrypted) {
|
|
101
|
+
return mapping;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const decryptedSource = decrypt(mapping.source?.path, secret);
|
|
106
|
+
const decryptedDest = decrypt(mapping.destination?.path, secret);
|
|
107
|
+
const decryptedReplacements = decryptReplacements(mapping.replacements || [], secret);
|
|
108
|
+
|
|
109
|
+
const decryptedFiles = (mapping.files || []).map(f => ({
|
|
110
|
+
original: decrypt(f.original, secret),
|
|
111
|
+
cloaked: f.cloaked
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...mapping,
|
|
116
|
+
source: {
|
|
117
|
+
...mapping.source,
|
|
118
|
+
path: decryptedSource,
|
|
119
|
+
decrypted: true
|
|
120
|
+
},
|
|
121
|
+
destination: {
|
|
122
|
+
...mapping.destination,
|
|
123
|
+
path: decryptedDest,
|
|
124
|
+
decrypted: true
|
|
125
|
+
},
|
|
126
|
+
replacements: decryptedReplacements,
|
|
127
|
+
files: decryptedFiles
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(`Failed to decrypt mapping: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a directory has a mapping file
|
|
136
|
+
*/
|
|
137
|
+
export function hasMapping(dir) {
|
|
138
|
+
const mapPath = join(dir, MAP_FILENAME);
|
|
139
|
+
return existsSync(mapPath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the original source path from mapping (requires secret)
|
|
144
|
+
*/
|
|
145
|
+
export function getOriginalSource(mapping) {
|
|
146
|
+
return mapping.source?.path || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get replacements from mapping
|
|
151
|
+
*/
|
|
152
|
+
export function getReplacements(mapping) {
|
|
153
|
+
return mapping.replacements || [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get file list from mapping
|
|
158
|
+
*/
|
|
159
|
+
export function getFiles(mapping) {
|
|
160
|
+
return mapping.files || [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update mapping with additional info
|
|
165
|
+
*/
|
|
166
|
+
export function updateMapping(destDir, updates) {
|
|
167
|
+
const mapPath = join(destDir, MAP_FILENAME);
|
|
168
|
+
|
|
169
|
+
if (!existsSync(mapPath)) {
|
|
170
|
+
throw new Error('No mapping file found');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const content = readFileSync(mapPath, 'utf-8');
|
|
174
|
+
const mapping = JSON.parse(content);
|
|
175
|
+
const updated = { ...mapping, ...updates, updatedAt: new Date().toISOString() };
|
|
176
|
+
|
|
177
|
+
writeFileSync(mapPath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
178
|
+
return updated;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Load existing raw mapping (without decryption) for merging
|
|
183
|
+
*/
|
|
184
|
+
export function loadRawMapping(cloakedDir) {
|
|
185
|
+
const mapPath = join(cloakedDir, MAP_FILENAME);
|
|
186
|
+
|
|
187
|
+
if (!existsSync(mapPath)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const content = readFileSync(mapPath, 'utf-8');
|
|
193
|
+
return JSON.parse(content);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Merge new files into existing mapping (for incremental pulls)
|
|
201
|
+
*/
|
|
202
|
+
export function mergeMapping(existingMapping, newFiles) {
|
|
203
|
+
// Get existing file paths for deduplication
|
|
204
|
+
const existingPaths = new Set(
|
|
205
|
+
(existingMapping.files || []).map(f => f.cloaked)
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Filter out files that already exist (avoid duplicates)
|
|
209
|
+
const uniqueNewFiles = newFiles.filter(f => !existingPaths.has(f.cloaked));
|
|
210
|
+
|
|
211
|
+
// Merge files
|
|
212
|
+
const mergedFiles = [
|
|
213
|
+
...(existingMapping.files || []),
|
|
214
|
+
...uniqueNewFiles
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
// Track pull history
|
|
218
|
+
const pullHistory = existingMapping.pullHistory || [];
|
|
219
|
+
pullHistory.push({
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
filesAdded: uniqueNewFiles.length,
|
|
222
|
+
totalFiles: mergedFiles.length
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...existingMapping,
|
|
227
|
+
files: mergedFiles,
|
|
228
|
+
pullHistory,
|
|
229
|
+
stats: {
|
|
230
|
+
...existingMapping.stats,
|
|
231
|
+
totalFiles: mergedFiles.length
|
|
232
|
+
},
|
|
233
|
+
updatedAt: new Date().toISOString()
|
|
234
|
+
};
|
|
235
|
+
}
|