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.
@@ -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
+ }