canvaslms-cli 1.2.0 ā 1.3.1
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/CHANGELOG.md +8 -0
- package/commands/submit.js +18 -137
- package/lib/interactive.js +219 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
- Remove files from selection
|
|
16
16
|
- Show currently selected files
|
|
17
17
|
- Smart file filtering (excludes hidden files, package files)
|
|
18
|
+
- **Wildcard support**: Use patterns like *.html, *.js, *.pdf to select multiple files
|
|
19
|
+
- File type icons for better visual identification
|
|
18
20
|
|
|
19
21
|
- **Improved Grade Viewing**:
|
|
20
22
|
- Interactive course selection for grade viewing
|
|
@@ -41,6 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
43
|
- **Announcements Command**: Show course names instead of IDs
|
|
42
44
|
- **User Experience**: More consistent and intuitive interfaces across all commands
|
|
43
45
|
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **Assignment Name Display**: Fixed "Unknown Assignment" issue in submission summary
|
|
49
|
+
- **File Selection Flow**: Better error handling and user guidance during file selection
|
|
50
|
+
- **Variable Scope**: Proper assignment variable handling throughout submission process
|
|
51
|
+
|
|
44
52
|
### Technical
|
|
45
53
|
|
|
46
54
|
- Enhanced interactive utilities in `lib/interactive.js`
|
package/commands/submit.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { makeCanvasRequest } = require('../lib/api-client');
|
|
8
|
-
const { createReadlineInterface, askQuestion } = require('../lib/interactive');
|
|
8
|
+
const { createReadlineInterface, askQuestion, askConfirmation, selectFilesImproved } = require('../lib/interactive');
|
|
9
9
|
const { uploadSingleFileToCanvas, submitAssignmentWithFiles } = require('../lib/file-upload');
|
|
10
10
|
|
|
11
11
|
async function submitAssignment(options) {
|
|
@@ -130,9 +130,7 @@ async function submitAssignment(options) {
|
|
|
130
130
|
console.log(` š Note: This assignment doesn't accept file uploads`);
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
-
|
|
134
|
-
const assignmentChoice = await askQuestion(rl, '\nEnter assignment number: ');
|
|
135
|
-
const assignmentIndex = parseInt(assignmentChoice) - 1;
|
|
133
|
+
const assignmentIndex = parseInt(assignmentChoice) - 1;
|
|
136
134
|
|
|
137
135
|
if (assignmentIndex < 0 || assignmentIndex >= assignments.length) {
|
|
138
136
|
console.log('Invalid assignment selection.');
|
|
@@ -140,7 +138,7 @@ async function submitAssignment(options) {
|
|
|
140
138
|
return;
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
|
|
141
|
+
selectedAssignment = assignments[assignmentIndex];
|
|
144
142
|
|
|
145
143
|
// Check if assignment accepts file uploads
|
|
146
144
|
if (!selectedAssignment.submission_types ||
|
|
@@ -156,8 +154,8 @@ async function submitAssignment(options) {
|
|
|
156
154
|
|
|
157
155
|
// Check if already submitted
|
|
158
156
|
if (selectedAssignment.submission && selectedAssignment.submission.submitted_at) {
|
|
159
|
-
const resubmit = await
|
|
160
|
-
if (resubmit
|
|
157
|
+
const resubmit = await askConfirmation(rl, 'This assignment has already been submitted. Do you want to resubmit?', false);
|
|
158
|
+
if (!resubmit) {
|
|
161
159
|
console.log('Submission cancelled.');
|
|
162
160
|
rl.close();
|
|
163
161
|
return;
|
|
@@ -167,16 +165,20 @@ async function submitAssignment(options) {
|
|
|
167
165
|
// Fetch assignment details if ID was provided
|
|
168
166
|
try {
|
|
169
167
|
selectedAssignment = await makeCanvasRequest('get', `courses/${courseId}/assignments/${assignmentId}`);
|
|
168
|
+
console.log(`ā
Using assignment: ${selectedAssignment.name}\n`);
|
|
170
169
|
} catch (error) {
|
|
171
170
|
console.log(`ā ļø Could not fetch assignment details for ID ${assignmentId}`);
|
|
172
171
|
selectedAssignment = { id: assignmentId, name: `Assignment ${assignmentId}` };
|
|
173
172
|
}
|
|
174
|
-
}
|
|
175
|
-
// Step 3: Select Files (if not provided)
|
|
173
|
+
} // Step 3: Select Files (if not provided)
|
|
176
174
|
let filePaths = [];
|
|
177
175
|
if (filePath) {
|
|
178
176
|
filePaths = [filePath]; // Single file provided via option
|
|
179
177
|
} else {
|
|
178
|
+
console.log('\nš File Selection');
|
|
179
|
+
console.log(`š Course: ${selectedCourse.name}`);
|
|
180
|
+
console.log(`š Assignment: ${selectedAssignment.name}\n`);
|
|
181
|
+
|
|
180
182
|
filePaths = await selectFilesImproved(rl);
|
|
181
183
|
}
|
|
182
184
|
|
|
@@ -197,7 +199,8 @@ async function submitAssignment(options) {
|
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
filePaths = validFiles;
|
|
200
|
-
|
|
202
|
+
|
|
203
|
+
// Step 4: Confirm and Submit
|
|
201
204
|
console.log('\nš Submission Summary:');
|
|
202
205
|
console.log(`š Course: ${selectedCourse?.name || 'Unknown Course'}`);
|
|
203
206
|
console.log(`š Assignment: ${selectedAssignment?.name || 'Unknown Assignment'}`);
|
|
@@ -208,8 +211,8 @@ async function submitAssignment(options) {
|
|
|
208
211
|
console.log(` ${index + 1}. ${path.basename(file)} (${size})`);
|
|
209
212
|
});
|
|
210
213
|
|
|
211
|
-
const confirm = await
|
|
212
|
-
if (confirm
|
|
214
|
+
const confirm = await askConfirmation(rl, '\nProceed with submission?', true);
|
|
215
|
+
if (!confirm) {
|
|
213
216
|
console.log('Submission cancelled.');
|
|
214
217
|
rl.close();
|
|
215
218
|
return;
|
|
@@ -226,11 +229,10 @@ async function submitAssignment(options) {
|
|
|
226
229
|
try {
|
|
227
230
|
const fileId = await uploadSingleFileToCanvas(courseId, assignmentId, currentFile);
|
|
228
231
|
uploadedFileIds.push(fileId);
|
|
229
|
-
console.log(`ā
${path.basename(currentFile)} uploaded successfully`);
|
|
230
|
-
} catch (error) {
|
|
232
|
+
console.log(`ā
${path.basename(currentFile)} uploaded successfully`); } catch (error) {
|
|
231
233
|
console.error(`ā Failed to upload ${currentFile}: ${error.message}`);
|
|
232
|
-
const continueUpload = await
|
|
233
|
-
if (continueUpload
|
|
234
|
+
const continueUpload = await askConfirmation(rl, 'Continue with remaining files?', true);
|
|
235
|
+
if (!continueUpload) {
|
|
234
236
|
break;
|
|
235
237
|
}
|
|
236
238
|
}
|
|
@@ -378,127 +380,6 @@ async function selectSingleFileFromList(rl, files) {
|
|
|
378
380
|
}
|
|
379
381
|
}
|
|
380
382
|
|
|
381
|
-
async function selectFilesImproved(rl) {
|
|
382
|
-
console.log('š Enhanced File Selection');
|
|
383
|
-
console.log('Choose files by entering their paths or browse current directory');
|
|
384
|
-
console.log('Press Enter with no input when done selecting files.\n');
|
|
385
|
-
|
|
386
|
-
const allFiles = [];
|
|
387
|
-
let fileIndex = 1;
|
|
388
|
-
|
|
389
|
-
while (true) {
|
|
390
|
-
console.log(`\nš File ${fileIndex} selection:`);
|
|
391
|
-
console.log('1. Enter file path directly');
|
|
392
|
-
console.log('2. Browse current directory');
|
|
393
|
-
console.log('3. Show currently selected files');
|
|
394
|
-
console.log('(Press Enter with no input to finish selection)');
|
|
395
|
-
|
|
396
|
-
const choice = await askQuestion(rl, '\nChoose option (1-3 or Enter to finish): ');
|
|
397
|
-
|
|
398
|
-
// Empty input means we're done selecting files
|
|
399
|
-
if (choice.trim() === '') {
|
|
400
|
-
if (allFiles.length === 0) {
|
|
401
|
-
console.log('ā ļø No files selected. Please select at least one file.');
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (choice === '1') {
|
|
408
|
-
// Direct file path entry
|
|
409
|
-
const filePath = await askQuestion(rl, 'Enter file path: ');
|
|
410
|
-
if (filePath.trim() !== '') {
|
|
411
|
-
if (fs.existsSync(filePath.trim())) {
|
|
412
|
-
if (!allFiles.includes(filePath.trim())) {
|
|
413
|
-
allFiles.push(filePath.trim());
|
|
414
|
-
const stats = fs.statSync(filePath.trim());
|
|
415
|
-
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
416
|
-
console.log(`ā
Added: ${path.basename(filePath.trim())} (${size})`);
|
|
417
|
-
fileIndex++;
|
|
418
|
-
} else {
|
|
419
|
-
console.log('ā ļø File already selected.');
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
console.log('ā File not found. Please check the path.');
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
} else if (choice === '2') {
|
|
426
|
-
// Browse current directory
|
|
427
|
-
try {
|
|
428
|
-
const files = fs.readdirSync('.').filter(file => {
|
|
429
|
-
const stats = fs.statSync(file);
|
|
430
|
-
return stats.isFile() &&
|
|
431
|
-
!file.startsWith('.') &&
|
|
432
|
-
!['package.json', 'package-lock.json', 'node_modules'].includes(file);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
if (files.length === 0) {
|
|
436
|
-
console.log('No suitable files found in current directory.');
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
console.log('\nš Files in current directory:');
|
|
441
|
-
files.forEach((file, index) => {
|
|
442
|
-
const stats = fs.statSync(file);
|
|
443
|
-
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
444
|
-
const alreadySelected = allFiles.includes(file) ? ' ā
' : '';
|
|
445
|
-
console.log(`${index + 1}. ${file} (${size})${alreadySelected}`);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
const fileChoice = await askQuestion(rl, '\nEnter file number (or Enter to go back): ');
|
|
449
|
-
if (fileChoice.trim() !== '') {
|
|
450
|
-
const fileIdx = parseInt(fileChoice) - 1;
|
|
451
|
-
if (fileIdx >= 0 && fileIdx < files.length) {
|
|
452
|
-
const selectedFile = files[fileIdx];
|
|
453
|
-
if (!allFiles.includes(selectedFile)) {
|
|
454
|
-
allFiles.push(selectedFile);
|
|
455
|
-
const stats = fs.statSync(selectedFile);
|
|
456
|
-
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
457
|
-
console.log(`ā
Added: ${selectedFile} (${size})`);
|
|
458
|
-
fileIndex++;
|
|
459
|
-
} else {
|
|
460
|
-
console.log('ā ļø File already selected.');
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
console.log('ā Invalid file number.');
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
} catch (error) {
|
|
467
|
-
console.log('ā Error reading directory:', error.message);
|
|
468
|
-
}
|
|
469
|
-
} else if (choice === '3') {
|
|
470
|
-
// Show currently selected files
|
|
471
|
-
if (allFiles.length === 0) {
|
|
472
|
-
console.log('š No files selected yet.');
|
|
473
|
-
} else {
|
|
474
|
-
console.log(`\nš Currently selected files (${allFiles.length}):`);
|
|
475
|
-
allFiles.forEach((file, index) => {
|
|
476
|
-
const stats = fs.existsSync(file) ? fs.statSync(file) : null;
|
|
477
|
-
const size = stats ? (stats.size / 1024).toFixed(1) + ' KB' : 'File not found';
|
|
478
|
-
console.log(` ${index + 1}. ${path.basename(file)} (${size})`);
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
const removeFile = await askQuestion(rl, '\nRemove a file? Enter number or press Enter to continue: ');
|
|
482
|
-
if (removeFile.trim() !== '') {
|
|
483
|
-
const removeIdx = parseInt(removeFile) - 1;
|
|
484
|
-
if (removeIdx >= 0 && removeIdx < allFiles.length) {
|
|
485
|
-
const removedFile = allFiles.splice(removeIdx, 1)[0];
|
|
486
|
-
console.log(`šļø Removed: ${path.basename(removedFile)}`);
|
|
487
|
-
fileIndex--;
|
|
488
|
-
} else {
|
|
489
|
-
console.log('ā Invalid file number.');
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
} else {
|
|
494
|
-
console.log('ā Invalid option. Please choose 1, 2, 3, or press Enter to finish.');
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
console.log(`\nā
File selection complete! Selected ${allFiles.length} file(s).`);
|
|
499
|
-
return allFiles;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
383
|
module.exports = {
|
|
503
384
|
submitAssignment
|
|
504
385
|
};
|
package/lib/interactive.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Create readline interface for user input
|
|
@@ -98,10 +100,226 @@ async function selectFromList(rl, items, displayProperty = null, allowCancel = t
|
|
|
98
100
|
return items[choice - 1];
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Get files matching a wildcard pattern
|
|
105
|
+
*/
|
|
106
|
+
function getFilesMatchingWildcard(pattern, currentDir = process.cwd()) {
|
|
107
|
+
try {
|
|
108
|
+
const files = fs.readdirSync(currentDir);
|
|
109
|
+
const matchedFiles = [];
|
|
110
|
+
|
|
111
|
+
// Convert wildcard pattern to regex
|
|
112
|
+
let regexPattern;
|
|
113
|
+
if (pattern.startsWith('*.')) {
|
|
114
|
+
// Handle *.extension patterns
|
|
115
|
+
const extension = pattern.slice(2);
|
|
116
|
+
regexPattern = new RegExp(`\\.${extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
|
|
117
|
+
} else if (pattern.includes('*')) {
|
|
118
|
+
// Handle other wildcard patterns
|
|
119
|
+
regexPattern = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'), 'i');
|
|
120
|
+
} else {
|
|
121
|
+
// Exact match
|
|
122
|
+
regexPattern = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
files.forEach(file => {
|
|
126
|
+
const filePath = path.join(currentDir, file);
|
|
127
|
+
const stats = fs.statSync(filePath);
|
|
128
|
+
|
|
129
|
+
if (stats.isFile() && regexPattern.test(file)) {
|
|
130
|
+
matchedFiles.push(filePath);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return matchedFiles;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`Error reading directory: ${error.message}`);
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Enhanced file selection with wildcard support
|
|
143
|
+
*/
|
|
144
|
+
async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
145
|
+
const selectedFiles = [];
|
|
146
|
+
|
|
147
|
+
console.log('\nš Enhanced File Selection');
|
|
148
|
+
console.log('š” Tips:');
|
|
149
|
+
console.log(' ⢠Type filename to add individual files');
|
|
150
|
+
console.log(' ⢠Use wildcards: *.html, *.js, *.pdf, etc.');
|
|
151
|
+
console.log(' ⢠Type "browse" to see available files');
|
|
152
|
+
console.log(' ⢠Type "remove" to remove files from selection');
|
|
153
|
+
console.log(' ⢠Press Enter with no input to finish selection\n');
|
|
154
|
+
|
|
155
|
+
while (true) {
|
|
156
|
+
// Show current selection
|
|
157
|
+
if (selectedFiles.length > 0) {
|
|
158
|
+
console.log(`\nš Currently selected (${selectedFiles.length} files):`);
|
|
159
|
+
selectedFiles.forEach((file, index) => {
|
|
160
|
+
const stats = fs.statSync(file);
|
|
161
|
+
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
162
|
+
console.log(` ${index + 1}. ${path.basename(file)} (${size})`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const input = await askQuestion(rl, '\nš Add file (or press Enter to finish): ');
|
|
167
|
+
|
|
168
|
+
if (!input.trim()) {
|
|
169
|
+
// Empty input - finish selection
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (input.toLowerCase() === 'browse') {
|
|
174
|
+
// Show available files
|
|
175
|
+
console.log('\nš Available files in current directory:');
|
|
176
|
+
try {
|
|
177
|
+
const files = fs.readdirSync(currentDir);
|
|
178
|
+
const filteredFiles = files.filter(file => {
|
|
179
|
+
const filePath = path.join(currentDir, file);
|
|
180
|
+
const stats = fs.statSync(filePath);
|
|
181
|
+
return stats.isFile() &&
|
|
182
|
+
!file.startsWith('.') &&
|
|
183
|
+
!['package.json', 'package-lock.json', 'node_modules'].includes(file);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (filteredFiles.length === 0) {
|
|
187
|
+
console.log(' No suitable files found.');
|
|
188
|
+
} else {
|
|
189
|
+
filteredFiles.forEach((file, index) => {
|
|
190
|
+
const filePath = path.join(currentDir, file);
|
|
191
|
+
const stats = fs.statSync(filePath);
|
|
192
|
+
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
193
|
+
const ext = path.extname(file);
|
|
194
|
+
const icon = getFileIcon(ext);
|
|
195
|
+
console.log(` ${index + 1}. ${icon} ${file} (${size})`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.log(` Error reading directory: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (input.toLowerCase() === 'remove') {
|
|
205
|
+
// Remove files from selection
|
|
206
|
+
if (selectedFiles.length === 0) {
|
|
207
|
+
console.log('ā No files selected to remove.');
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log('\nSelect file to remove:');
|
|
212
|
+
selectedFiles.forEach((file, index) => {
|
|
213
|
+
console.log(`${index + 1}. ${path.basename(file)}`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const removeChoice = await askQuestion(rl, '\nEnter number to remove (or press Enter to cancel): ');
|
|
217
|
+
if (removeChoice.trim()) {
|
|
218
|
+
const removeIndex = parseInt(removeChoice) - 1;
|
|
219
|
+
if (removeIndex >= 0 && removeIndex < selectedFiles.length) {
|
|
220
|
+
const removedFile = selectedFiles.splice(removeIndex, 1)[0];
|
|
221
|
+
console.log(`ā
Removed: ${path.basename(removedFile)}`);
|
|
222
|
+
} else {
|
|
223
|
+
console.log('ā Invalid selection.');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if input contains wildcards
|
|
230
|
+
if (input.includes('*') || input.includes('?')) {
|
|
231
|
+
const matchedFiles = getFilesMatchingWildcard(input, currentDir);
|
|
232
|
+
|
|
233
|
+
if (matchedFiles.length === 0) {
|
|
234
|
+
console.log(`ā No files found matching pattern: ${input}`);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log(`\nšÆ Found ${matchedFiles.length} files matching "${input}":`);
|
|
239
|
+
matchedFiles.forEach((file, index) => {
|
|
240
|
+
const stats = fs.statSync(file);
|
|
241
|
+
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
242
|
+
console.log(` ${index + 1}. ${path.basename(file)} (${size})`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const confirmAll = await askConfirmation(rl, `Add all ${matchedFiles.length} files?`, true);
|
|
246
|
+
|
|
247
|
+
if (confirmAll) {
|
|
248
|
+
const newFiles = matchedFiles.filter(file => !selectedFiles.includes(file));
|
|
249
|
+
selectedFiles.push(...newFiles);
|
|
250
|
+
console.log(`ā
Added ${newFiles.length} new files (${matchedFiles.length - newFiles.length} were already selected)`);
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Handle individual file selection
|
|
256
|
+
let filePath = input;
|
|
257
|
+
|
|
258
|
+
// If not absolute path, make it relative to current directory
|
|
259
|
+
if (!path.isAbsolute(filePath)) {
|
|
260
|
+
filePath = path.join(currentDir, filePath);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
if (!fs.existsSync(filePath)) {
|
|
265
|
+
console.log(`ā File not found: ${input}`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const stats = fs.statSync(filePath);
|
|
270
|
+
if (!stats.isFile()) {
|
|
271
|
+
console.log(`ā Not a file: ${input}`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (selectedFiles.includes(filePath)) {
|
|
276
|
+
console.log(`ā ļø File already selected: ${path.basename(filePath)}`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
selectedFiles.push(filePath);
|
|
281
|
+
const size = (stats.size / 1024).toFixed(1) + ' KB';
|
|
282
|
+
console.log(`ā
Added: ${path.basename(filePath)} (${size})`);
|
|
283
|
+
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.log(`ā Error accessing file: ${error.message}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return selectedFiles;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get file icon based on extension
|
|
294
|
+
*/
|
|
295
|
+
function getFileIcon(extension) {
|
|
296
|
+
const iconMap = {
|
|
297
|
+
'.js': 'š',
|
|
298
|
+
'.html': 'š',
|
|
299
|
+
'.css': 'šØ',
|
|
300
|
+
'.pdf': 'š',
|
|
301
|
+
'.doc': 'š',
|
|
302
|
+
'.docx': 'š',
|
|
303
|
+
'.txt': 'š',
|
|
304
|
+
'.md': 'š',
|
|
305
|
+
'.json': 'āļø',
|
|
306
|
+
'.xml': 'š',
|
|
307
|
+
'.zip': 'š¦',
|
|
308
|
+
'.png': 'š¼ļø',
|
|
309
|
+
'.jpg': 'š¼ļø',
|
|
310
|
+
'.jpeg': 'š¼ļø',
|
|
311
|
+
'.gif': 'š¼ļø'
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return iconMap[extension.toLowerCase()] || 'š';
|
|
315
|
+
}
|
|
316
|
+
|
|
101
317
|
module.exports = {
|
|
102
318
|
createReadlineInterface,
|
|
103
319
|
askQuestion,
|
|
104
320
|
askQuestionWithValidation,
|
|
105
321
|
askConfirmation,
|
|
106
|
-
selectFromList
|
|
322
|
+
selectFromList,
|
|
323
|
+
selectFilesImproved,
|
|
324
|
+
getFilesMatchingWildcard
|
|
107
325
|
};
|