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/ui/fileSelector.js
CHANGED
|
@@ -1,256 +1,256 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive File Selector
|
|
3
|
-
* Hierarchical tree view with pagination
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { readdirSync } from 'fs';
|
|
9
|
-
import { join, relative, sep } from 'path';
|
|
10
|
-
|
|
11
|
-
// Directories to always ignore
|
|
12
|
-
const IGNORE_DIRS = new Set([
|
|
13
|
-
'node_modules', '.git', '.svn', '.hg', '.DS_Store', 'Thumbs.db',
|
|
14
|
-
'.idea', '.vscode', '__pycache__', '.pytest_cache', 'dist', 'build',
|
|
15
|
-
'.next', '.nuxt', 'coverage', '.nyc_output', '.repo-cloak-map.json',
|
|
16
|
-
'obj', 'bin', 'packages', '.vs', 'TestResults'
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
const PAGE_SIZE = 50;
|
|
20
|
-
|
|
21
|
-
function shouldIgnore(name) {
|
|
22
|
-
return IGNORE_DIRS.has(name) || name.startsWith('.');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function buildFileIndex(baseDir, maxDepth = 8) {
|
|
26
|
-
const files = [];
|
|
27
|
-
|
|
28
|
-
function scan(dir, depth = 0) {
|
|
29
|
-
if (depth > maxDepth) return;
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
33
|
-
|
|
34
|
-
entries.sort((a, b) => {
|
|
35
|
-
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
36
|
-
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
37
|
-
return a.name.localeCompare(b.name);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
for (const entry of entries) {
|
|
41
|
-
if (shouldIgnore(entry.name)) continue;
|
|
42
|
-
|
|
43
|
-
const fullPath = join(dir, entry.name);
|
|
44
|
-
const relativePath = relative(baseDir, fullPath);
|
|
45
|
-
|
|
46
|
-
files.push({
|
|
47
|
-
name: entry.name,
|
|
48
|
-
path: fullPath,
|
|
49
|
-
relativePath,
|
|
50
|
-
isDirectory: entry.isDirectory(),
|
|
51
|
-
depth
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (entry.isDirectory()) {
|
|
55
|
-
scan(fullPath, depth + 1);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch (error) { }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
scan(baseDir);
|
|
62
|
-
return files;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getFilesInDirectory(dir) {
|
|
66
|
-
const files = [];
|
|
67
|
-
|
|
68
|
-
function collect(currentDir) {
|
|
69
|
-
try {
|
|
70
|
-
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
71
|
-
for (const entry of entries) {
|
|
72
|
-
if (shouldIgnore(entry.name)) continue;
|
|
73
|
-
const fullPath = join(currentDir, entry.name);
|
|
74
|
-
if (entry.isDirectory()) {
|
|
75
|
-
collect(fullPath);
|
|
76
|
-
} else {
|
|
77
|
-
files.push(fullPath);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
} catch (error) { }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
collect(dir);
|
|
84
|
-
return files;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function formatTreeItem(item) {
|
|
88
|
-
const indent = ' '.repeat(item.depth);
|
|
89
|
-
const icon = item.isDirectory ? 'š' : 'š';
|
|
90
|
-
const name = item.isDirectory
|
|
91
|
-
? chalk.blue.bold(item.name)
|
|
92
|
-
: chalk.white(item.name);
|
|
93
|
-
return `${indent}${icon} ${name}`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Show paginated results with Load More option
|
|
98
|
-
*/
|
|
99
|
-
async function showPaginatedResults(filtered, selectedPaths, page = 0) {
|
|
100
|
-
const start = page * PAGE_SIZE;
|
|
101
|
-
const end = Math.min(start + PAGE_SIZE, filtered.length);
|
|
102
|
-
const pageItems = filtered.slice(start, end);
|
|
103
|
-
const hasMore = end < filtered.length;
|
|
104
|
-
const remaining = filtered.length - end;
|
|
105
|
-
|
|
106
|
-
// Build choices
|
|
107
|
-
const choices = pageItems.map(f => {
|
|
108
|
-
let isChecked = false;
|
|
109
|
-
if (f.isDirectory) {
|
|
110
|
-
const childFiles = getFilesInDirectory(f.path);
|
|
111
|
-
isChecked = childFiles.length > 0 && childFiles.every(fp => selectedPaths.has(fp));
|
|
112
|
-
} else {
|
|
113
|
-
isChecked = selectedPaths.has(f.path);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
name: formatTreeItem(f),
|
|
118
|
-
value: f,
|
|
119
|
-
checked: isChecked,
|
|
120
|
-
short: f.relativePath
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Add separator and load more option if there are more results
|
|
125
|
-
if (hasMore) {
|
|
126
|
-
choices.push(new inquirer.Separator(chalk.dim('āāāāāāāāāāāāāāāāāāāāāāāāāāāāā')));
|
|
127
|
-
choices.push({
|
|
128
|
-
name: chalk.cyan(`⬠Load next ${Math.min(PAGE_SIZE, remaining)} items (${remaining} remaining)`),
|
|
129
|
-
value: '__LOAD_MORE__',
|
|
130
|
-
short: 'Load more'
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const pageInfo = `Page ${page + 1} (${start + 1}-${end} of ${filtered.length})`;
|
|
135
|
-
|
|
136
|
-
const { picked } = await inquirer.prompt([
|
|
137
|
-
{
|
|
138
|
-
type: 'checkbox',
|
|
139
|
-
name: 'picked',
|
|
140
|
-
message: `${pageInfo}:`,
|
|
141
|
-
choices,
|
|
142
|
-
pageSize: 25,
|
|
143
|
-
loop: false
|
|
144
|
-
}
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
return { picked, pageItems, hasMore };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function selectFiles(sourceDir) {
|
|
151
|
-
console.log(chalk.cyan('\nš Scanning directory...'));
|
|
152
|
-
|
|
153
|
-
const fileIndex = buildFileIndex(sourceDir);
|
|
154
|
-
console.log(chalk.dim(` Found ${fileIndex.length} items\n`));
|
|
155
|
-
|
|
156
|
-
if (fileIndex.length === 0) {
|
|
157
|
-
console.log(chalk.yellow(' No files found.'));
|
|
158
|
-
return [];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const selectedPaths = new Set();
|
|
162
|
-
let continueLoop = true;
|
|
163
|
-
|
|
164
|
-
console.log(chalk.cyan('š File Selection'));
|
|
165
|
-
console.log(chalk.dim(' ⢠Type to filter ā Space to tick ā Enter to confirm'));
|
|
166
|
-
console.log(chalk.dim(' ⢠Select "Load more" to see next batch'));
|
|
167
|
-
console.log(chalk.dim(' ⢠Empty search = done\n'));
|
|
168
|
-
|
|
169
|
-
while (continueLoop) {
|
|
170
|
-
if (selectedPaths.size > 0) {
|
|
171
|
-
console.log(chalk.green(`\n š¦ ${selectedPaths.size} file(s) selected`));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const { searchTerm } = await inquirer.prompt([
|
|
175
|
-
{
|
|
176
|
-
type: 'input',
|
|
177
|
-
name: 'searchTerm',
|
|
178
|
-
message: 'Filter (empty = done):',
|
|
179
|
-
prefix: 'š'
|
|
180
|
-
}
|
|
181
|
-
]);
|
|
182
|
-
|
|
183
|
-
if (!searchTerm.trim()) {
|
|
184
|
-
if (selectedPaths.size === 0) {
|
|
185
|
-
const { confirmExit } = await inquirer.prompt([
|
|
186
|
-
{ type: 'confirm', name: 'confirmExit', message: 'No files selected. Exit?', default: false }
|
|
187
|
-
]);
|
|
188
|
-
if (confirmExit) continueLoop = false;
|
|
189
|
-
} else {
|
|
190
|
-
continueLoop = false;
|
|
191
|
-
}
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Filter by search term
|
|
196
|
-
const query = searchTerm.toLowerCase();
|
|
197
|
-
const filtered = fileIndex.filter(f =>
|
|
198
|
-
f.relativePath.toLowerCase().includes(query) ||
|
|
199
|
-
f.name.toLowerCase().includes(query)
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
if (filtered.length === 0) {
|
|
203
|
-
console.log(chalk.yellow(' No matches found.'));
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
console.log(chalk.dim(` Found ${filtered.length} matches`));
|
|
208
|
-
|
|
209
|
-
// Pagination loop
|
|
210
|
-
let page = 0;
|
|
211
|
-
let continuePaging = true;
|
|
212
|
-
|
|
213
|
-
while (continuePaging) {
|
|
214
|
-
const { picked, pageItems, hasMore } = await showPaginatedResults(filtered, selectedPaths, page);
|
|
215
|
-
|
|
216
|
-
// Check if user selected "Load More"
|
|
217
|
-
const loadMore = picked.find(p => p === '__LOAD_MORE__');
|
|
218
|
-
const actualPicks = picked.filter(p => p !== '__LOAD_MORE__');
|
|
219
|
-
|
|
220
|
-
// Process deselections for this page
|
|
221
|
-
const pickedPaths = new Set(actualPicks.map(p => p.path));
|
|
222
|
-
for (const f of pageItems) {
|
|
223
|
-
if (!pickedPaths.has(f.path)) {
|
|
224
|
-
if (f.isDirectory) {
|
|
225
|
-
const childFiles = getFilesInDirectory(f.path);
|
|
226
|
-
childFiles.forEach(fp => selectedPaths.delete(fp));
|
|
227
|
-
} else {
|
|
228
|
-
selectedPaths.delete(f.path);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Process selections
|
|
234
|
-
for (const f of actualPicks) {
|
|
235
|
-
if (f.isDirectory) {
|
|
236
|
-
const childFiles = getFilesInDirectory(f.path);
|
|
237
|
-
childFiles.forEach(fp => selectedPaths.add(fp));
|
|
238
|
-
console.log(chalk.green(` + š ${f.relativePath}/ (${childFiles.length} files)`));
|
|
239
|
-
} else {
|
|
240
|
-
selectedPaths.add(f.path);
|
|
241
|
-
console.log(chalk.green(` + š ${f.relativePath}`));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Handle load more or exit pagination
|
|
246
|
-
if (loadMore && hasMore) {
|
|
247
|
-
page++;
|
|
248
|
-
} else {
|
|
249
|
-
continuePaging = false;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
console.log(chalk.green(`\nā Total: ${selectedPaths.size} files selected\n`));
|
|
255
|
-
return Array.from(selectedPaths);
|
|
256
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Interactive File Selector
|
|
3
|
+
* Hierarchical tree view with pagination
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { readdirSync } from 'fs';
|
|
9
|
+
import { join, relative, sep } from 'path';
|
|
10
|
+
|
|
11
|
+
// Directories to always ignore
|
|
12
|
+
const IGNORE_DIRS = new Set([
|
|
13
|
+
'node_modules', '.git', '.svn', '.hg', '.DS_Store', 'Thumbs.db',
|
|
14
|
+
'.idea', '.vscode', '__pycache__', '.pytest_cache', 'dist', 'build',
|
|
15
|
+
'.next', '.nuxt', 'coverage', '.nyc_output', '.repo-cloak-map.json',
|
|
16
|
+
'obj', 'bin', 'packages', '.vs', 'TestResults'
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const PAGE_SIZE = 50;
|
|
20
|
+
|
|
21
|
+
function shouldIgnore(name) {
|
|
22
|
+
return IGNORE_DIRS.has(name) || name.startsWith('.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildFileIndex(baseDir, maxDepth = 8) {
|
|
26
|
+
const files = [];
|
|
27
|
+
|
|
28
|
+
function scan(dir, depth = 0) {
|
|
29
|
+
if (depth > maxDepth) return;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
33
|
+
|
|
34
|
+
entries.sort((a, b) => {
|
|
35
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
36
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
37
|
+
return a.name.localeCompare(b.name);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (shouldIgnore(entry.name)) continue;
|
|
42
|
+
|
|
43
|
+
const fullPath = join(dir, entry.name);
|
|
44
|
+
const relativePath = relative(baseDir, fullPath);
|
|
45
|
+
|
|
46
|
+
files.push({
|
|
47
|
+
name: entry.name,
|
|
48
|
+
path: fullPath,
|
|
49
|
+
relativePath,
|
|
50
|
+
isDirectory: entry.isDirectory(),
|
|
51
|
+
depth
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
scan(fullPath, depth + 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (error) { }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
scan(baseDir);
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getFilesInDirectory(dir) {
|
|
66
|
+
const files = [];
|
|
67
|
+
|
|
68
|
+
function collect(currentDir) {
|
|
69
|
+
try {
|
|
70
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (shouldIgnore(entry.name)) continue;
|
|
73
|
+
const fullPath = join(currentDir, entry.name);
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
collect(fullPath);
|
|
76
|
+
} else {
|
|
77
|
+
files.push(fullPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) { }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
collect(dir);
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatTreeItem(item) {
|
|
88
|
+
const indent = ' '.repeat(item.depth);
|
|
89
|
+
const icon = item.isDirectory ? 'š' : 'š';
|
|
90
|
+
const name = item.isDirectory
|
|
91
|
+
? chalk.blue.bold(item.name)
|
|
92
|
+
: chalk.white(item.name);
|
|
93
|
+
return `${indent}${icon} ${name}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Show paginated results with Load More option
|
|
98
|
+
*/
|
|
99
|
+
async function showPaginatedResults(filtered, selectedPaths, page = 0) {
|
|
100
|
+
const start = page * PAGE_SIZE;
|
|
101
|
+
const end = Math.min(start + PAGE_SIZE, filtered.length);
|
|
102
|
+
const pageItems = filtered.slice(start, end);
|
|
103
|
+
const hasMore = end < filtered.length;
|
|
104
|
+
const remaining = filtered.length - end;
|
|
105
|
+
|
|
106
|
+
// Build choices
|
|
107
|
+
const choices = pageItems.map(f => {
|
|
108
|
+
let isChecked = false;
|
|
109
|
+
if (f.isDirectory) {
|
|
110
|
+
const childFiles = getFilesInDirectory(f.path);
|
|
111
|
+
isChecked = childFiles.length > 0 && childFiles.every(fp => selectedPaths.has(fp));
|
|
112
|
+
} else {
|
|
113
|
+
isChecked = selectedPaths.has(f.path);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name: formatTreeItem(f),
|
|
118
|
+
value: f,
|
|
119
|
+
checked: isChecked,
|
|
120
|
+
short: f.relativePath
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Add separator and load more option if there are more results
|
|
125
|
+
if (hasMore) {
|
|
126
|
+
choices.push(new inquirer.Separator(chalk.dim('āāāāāāāāāāāāāāāāāāāāāāāāāāāāā')));
|
|
127
|
+
choices.push({
|
|
128
|
+
name: chalk.cyan(`⬠Load next ${Math.min(PAGE_SIZE, remaining)} items (${remaining} remaining)`),
|
|
129
|
+
value: '__LOAD_MORE__',
|
|
130
|
+
short: 'Load more'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const pageInfo = `Page ${page + 1} (${start + 1}-${end} of ${filtered.length})`;
|
|
135
|
+
|
|
136
|
+
const { picked } = await inquirer.prompt([
|
|
137
|
+
{
|
|
138
|
+
type: 'checkbox',
|
|
139
|
+
name: 'picked',
|
|
140
|
+
message: `${pageInfo}:`,
|
|
141
|
+
choices,
|
|
142
|
+
pageSize: 25,
|
|
143
|
+
loop: false
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
return { picked, pageItems, hasMore };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function selectFiles(sourceDir) {
|
|
151
|
+
console.log(chalk.cyan('\nš Scanning directory...'));
|
|
152
|
+
|
|
153
|
+
const fileIndex = buildFileIndex(sourceDir);
|
|
154
|
+
console.log(chalk.dim(` Found ${fileIndex.length} items\n`));
|
|
155
|
+
|
|
156
|
+
if (fileIndex.length === 0) {
|
|
157
|
+
console.log(chalk.yellow(' No files found.'));
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const selectedPaths = new Set();
|
|
162
|
+
let continueLoop = true;
|
|
163
|
+
|
|
164
|
+
console.log(chalk.cyan('š File Selection'));
|
|
165
|
+
console.log(chalk.dim(' ⢠Type to filter ā Space to tick ā Enter to confirm'));
|
|
166
|
+
console.log(chalk.dim(' ⢠Select "Load more" to see next batch'));
|
|
167
|
+
console.log(chalk.dim(' ⢠Empty search = done\n'));
|
|
168
|
+
|
|
169
|
+
while (continueLoop) {
|
|
170
|
+
if (selectedPaths.size > 0) {
|
|
171
|
+
console.log(chalk.green(`\n š¦ ${selectedPaths.size} file(s) selected`));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { searchTerm } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'searchTerm',
|
|
178
|
+
message: 'Filter (empty = done):',
|
|
179
|
+
prefix: 'š'
|
|
180
|
+
}
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
if (!searchTerm.trim()) {
|
|
184
|
+
if (selectedPaths.size === 0) {
|
|
185
|
+
const { confirmExit } = await inquirer.prompt([
|
|
186
|
+
{ type: 'confirm', name: 'confirmExit', message: 'No files selected. Exit?', default: false }
|
|
187
|
+
]);
|
|
188
|
+
if (confirmExit) continueLoop = false;
|
|
189
|
+
} else {
|
|
190
|
+
continueLoop = false;
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Filter by search term
|
|
196
|
+
const query = searchTerm.toLowerCase();
|
|
197
|
+
const filtered = fileIndex.filter(f =>
|
|
198
|
+
f.relativePath.toLowerCase().includes(query) ||
|
|
199
|
+
f.name.toLowerCase().includes(query)
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (filtered.length === 0) {
|
|
203
|
+
console.log(chalk.yellow(' No matches found.'));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(chalk.dim(` Found ${filtered.length} matches`));
|
|
208
|
+
|
|
209
|
+
// Pagination loop
|
|
210
|
+
let page = 0;
|
|
211
|
+
let continuePaging = true;
|
|
212
|
+
|
|
213
|
+
while (continuePaging) {
|
|
214
|
+
const { picked, pageItems, hasMore } = await showPaginatedResults(filtered, selectedPaths, page);
|
|
215
|
+
|
|
216
|
+
// Check if user selected "Load More"
|
|
217
|
+
const loadMore = picked.find(p => p === '__LOAD_MORE__');
|
|
218
|
+
const actualPicks = picked.filter(p => p !== '__LOAD_MORE__');
|
|
219
|
+
|
|
220
|
+
// Process deselections for this page
|
|
221
|
+
const pickedPaths = new Set(actualPicks.map(p => p.path));
|
|
222
|
+
for (const f of pageItems) {
|
|
223
|
+
if (!pickedPaths.has(f.path)) {
|
|
224
|
+
if (f.isDirectory) {
|
|
225
|
+
const childFiles = getFilesInDirectory(f.path);
|
|
226
|
+
childFiles.forEach(fp => selectedPaths.delete(fp));
|
|
227
|
+
} else {
|
|
228
|
+
selectedPaths.delete(f.path);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Process selections
|
|
234
|
+
for (const f of actualPicks) {
|
|
235
|
+
if (f.isDirectory) {
|
|
236
|
+
const childFiles = getFilesInDirectory(f.path);
|
|
237
|
+
childFiles.forEach(fp => selectedPaths.add(fp));
|
|
238
|
+
console.log(chalk.green(` + š ${f.relativePath}/ (${childFiles.length} files)`));
|
|
239
|
+
} else {
|
|
240
|
+
selectedPaths.add(f.path);
|
|
241
|
+
console.log(chalk.green(` + š ${f.relativePath}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle load more or exit pagination
|
|
246
|
+
if (loadMore && hasMore) {
|
|
247
|
+
page++;
|
|
248
|
+
} else {
|
|
249
|
+
continuePaging = false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(chalk.green(`\nā Total: ${selectedPaths.size} files selected\n`));
|
|
255
|
+
return Array.from(selectedPaths);
|
|
256
|
+
}
|