canvaslms-cli 1.6.0 → 1.6.3
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 +210 -186
- package/README.md +131 -129
- package/dist/commands/announcements.d.ts +2 -2
- package/dist/commands/announcements.d.ts.map +1 -1
- package/dist/commands/announcements.js +43 -124
- package/dist/commands/announcements.js.map +1 -1
- package/dist/commands/api.d.ts +1 -1
- package/dist/commands/api.d.ts.map +1 -1
- package/dist/commands/api.js +1 -1
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/assignments.d.ts +2 -2
- package/dist/commands/assignments.d.ts.map +1 -1
- package/dist/commands/assignments.js +40 -128
- package/dist/commands/assignments.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +102 -85
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/grades.d.ts +2 -2
- package/dist/commands/grades.d.ts.map +1 -1
- package/dist/commands/grades.js +296 -178
- package/dist/commands/grades.js.map +1 -1
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +26 -46
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/profile.d.ts.map +1 -1
- package/dist/commands/profile.js +57 -32
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/submit.d.ts +2 -2
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +110 -165
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +14 -14
- package/dist/lib/api-client.d.ts +3 -0
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +31 -16
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/config-validator.d.ts.map +1 -1
- package/dist/lib/config-validator.js +12 -12
- package/dist/lib/config-validator.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +16 -14
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/display.d.ts +84 -0
- package/dist/lib/display.d.ts.map +1 -0
- package/dist/lib/display.js +603 -0
- package/dist/lib/display.js.map +1 -0
- package/dist/lib/file-upload.d.ts.map +1 -1
- package/dist/lib/file-upload.js +17 -17
- package/dist/lib/file-upload.js.map +1 -1
- package/dist/lib/interactive.d.ts +1 -1
- package/dist/lib/interactive.d.ts.map +1 -1
- package/dist/lib/interactive.js +200 -143
- package/dist/lib/interactive.js.map +1 -1
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +62 -62
- package/dist/src/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +9 -4
- package/dist/commands/coursenames.d.ts +0 -15
- package/dist/commands/coursenames.d.ts.map +0 -1
- package/dist/commands/coursenames.js +0 -150
- package/dist/commands/coursenames.js.map +0 -1
- package/dist/lib/course-utils.d.ts +0 -6
- package/dist/lib/course-utils.d.ts.map +0 -1
- package/dist/lib/course-utils.js +0 -17
- package/dist/lib/course-utils.js.map +0 -1
package/dist/lib/interactive.js
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import readline from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import AdmZip from
|
|
5
|
-
import chalk from
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import AdmZip from "adm-zip";
|
|
5
|
+
import chalk from "chalk";
|
|
6
6
|
const KEYS = {
|
|
7
|
-
UP:
|
|
8
|
-
DOWN:
|
|
9
|
-
LEFT:
|
|
10
|
-
RIGHT:
|
|
11
|
-
SPACE:
|
|
12
|
-
ENTER:
|
|
13
|
-
ESCAPE:
|
|
14
|
-
BACKSPACE:
|
|
15
|
-
TAB:
|
|
16
|
-
CTRL_C:
|
|
7
|
+
UP: "\u001b[A",
|
|
8
|
+
DOWN: "\u001b[B",
|
|
9
|
+
LEFT: "\u001b[D",
|
|
10
|
+
RIGHT: "\u001b[C",
|
|
11
|
+
SPACE: " ",
|
|
12
|
+
ENTER: "\r",
|
|
13
|
+
ESCAPE: "\u001b",
|
|
14
|
+
BACKSPACE: "\u007f",
|
|
15
|
+
TAB: "\t",
|
|
16
|
+
CTRL_C: "\u0003",
|
|
17
17
|
};
|
|
18
18
|
export function createReadlineInterface() {
|
|
19
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
20
|
+
try {
|
|
21
|
+
process.stdin.setRawMode(false);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
return readline.createInterface({
|
|
20
27
|
input: process.stdin,
|
|
21
|
-
output: process.stdout
|
|
28
|
+
output: process.stdout,
|
|
29
|
+
terminal: true,
|
|
22
30
|
});
|
|
23
31
|
}
|
|
24
32
|
export function askQuestion(rl, question) {
|
|
@@ -38,7 +46,7 @@ export function askQuestionWithValidation(rl, question, validator, errorMessage)
|
|
|
38
46
|
return;
|
|
39
47
|
}
|
|
40
48
|
else {
|
|
41
|
-
console.log(errorMessage ||
|
|
49
|
+
console.log(errorMessage || "Invalid input. Please try again.");
|
|
42
50
|
}
|
|
43
51
|
} while (true);
|
|
44
52
|
});
|
|
@@ -70,22 +78,24 @@ export async function askConfirmation(rl, question, defaultYes = true, options =
|
|
|
70
78
|
}
|
|
71
79
|
export async function selectFromList(rl, items, displayProperty = null, allowCancel = true) {
|
|
72
80
|
if (!items || items.length === 0) {
|
|
73
|
-
console.log(
|
|
81
|
+
console.log("No items to select from.");
|
|
74
82
|
return null;
|
|
75
83
|
}
|
|
76
|
-
console.log(
|
|
84
|
+
console.log("\nSelect an option:");
|
|
77
85
|
items.forEach((item, index) => {
|
|
78
|
-
const displayText = displayProperty && typeof item ===
|
|
86
|
+
const displayText = displayProperty && typeof item === "object" && item !== null
|
|
87
|
+
? item[displayProperty]
|
|
88
|
+
: item;
|
|
79
89
|
console.log(`${index + 1}. ${displayText}`);
|
|
80
90
|
});
|
|
81
91
|
if (allowCancel) {
|
|
82
|
-
console.log(
|
|
92
|
+
console.log("0. Cancel");
|
|
83
93
|
}
|
|
84
94
|
const validator = (input) => {
|
|
85
95
|
const num = parseInt(input);
|
|
86
96
|
return !isNaN(num) && num >= (allowCancel ? 0 : 1) && num <= items.length;
|
|
87
97
|
};
|
|
88
|
-
const answer = await askQuestionWithValidation(rl,
|
|
98
|
+
const answer = await askQuestionWithValidation(rl, "\nEnter your choice: ", validator, `Please enter a number between ${allowCancel ? "0" : "1"} and ${items.length}.`);
|
|
89
99
|
const choice = parseInt(answer);
|
|
90
100
|
if (choice === 0 && allowCancel) {
|
|
91
101
|
return null;
|
|
@@ -103,13 +113,14 @@ export function getSubfoldersRecursive(startDir = process.cwd()) {
|
|
|
103
113
|
const stat = fs.statSync(fullPath);
|
|
104
114
|
if (stat.isDirectory()) {
|
|
105
115
|
const baseName = path.basename(fullPath);
|
|
106
|
-
if ([
|
|
116
|
+
if (["node_modules", ".git", "dist", "build"].includes(baseName))
|
|
107
117
|
continue;
|
|
108
118
|
result.push(fullPath);
|
|
109
119
|
walk(fullPath);
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
|
-
catch
|
|
122
|
+
catch {
|
|
123
|
+
console.warn(`Skipped unreadable folder: ${fullPath}`);
|
|
113
124
|
}
|
|
114
125
|
}
|
|
115
126
|
}
|
|
@@ -121,33 +132,34 @@ export function getFilesMatchingWildcard(pattern, currentDir = process.cwd()) {
|
|
|
121
132
|
const allFolders = [currentDir, ...getSubfoldersRecursive(currentDir)];
|
|
122
133
|
let allFiles = [];
|
|
123
134
|
for (const folder of allFolders) {
|
|
124
|
-
const files = fs.readdirSync(folder).map(f => path.join(folder, f));
|
|
135
|
+
const files = fs.readdirSync(folder).map((f) => path.join(folder, f));
|
|
125
136
|
for (const filePath of files) {
|
|
126
137
|
try {
|
|
127
138
|
if (fs.statSync(filePath).isFile()) {
|
|
128
139
|
allFiles.push(filePath);
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
|
-
catch
|
|
142
|
+
catch {
|
|
143
|
+
}
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
let regexPattern;
|
|
135
147
|
let matchFullPath = false;
|
|
136
|
-
if (pattern ===
|
|
137
|
-
regexPattern = new RegExp(
|
|
148
|
+
if (pattern === "*" || (!pattern.includes(".") && !pattern.includes("/"))) {
|
|
149
|
+
regexPattern = new RegExp(".*", "i");
|
|
138
150
|
matchFullPath = true;
|
|
139
151
|
}
|
|
140
|
-
else if (pattern.startsWith(
|
|
152
|
+
else if (pattern.startsWith("*.")) {
|
|
141
153
|
const extension = pattern.slice(2);
|
|
142
|
-
regexPattern = new RegExp(`\\.${extension.replace(/[.*+?^${}()|[\]\\]/g,
|
|
154
|
+
regexPattern = new RegExp(`\\.${extension.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i");
|
|
143
155
|
}
|
|
144
|
-
else if (pattern.includes(
|
|
145
|
-
regexPattern = new RegExp(pattern.replace(/\*/g,
|
|
156
|
+
else if (pattern.includes("*")) {
|
|
157
|
+
regexPattern = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."), "i");
|
|
146
158
|
}
|
|
147
159
|
else {
|
|
148
|
-
regexPattern = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g,
|
|
160
|
+
regexPattern = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i");
|
|
149
161
|
}
|
|
150
|
-
const matchedFiles = allFiles.filter(filePath => {
|
|
162
|
+
const matchedFiles = allFiles.filter((filePath) => {
|
|
151
163
|
if (matchFullPath) {
|
|
152
164
|
const relPath = path.relative(currentDir, filePath);
|
|
153
165
|
return regexPattern.test(relPath);
|
|
@@ -165,40 +177,42 @@ export function getFilesMatchingWildcard(pattern, currentDir = process.cwd()) {
|
|
|
165
177
|
}
|
|
166
178
|
}
|
|
167
179
|
export function pad(str, len) {
|
|
168
|
-
return str +
|
|
180
|
+
return str + " ".repeat(Math.max(0, len - str.length));
|
|
169
181
|
}
|
|
170
182
|
export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
171
183
|
const selectedFiles = [];
|
|
172
|
-
console.log(chalk.cyan.bold(
|
|
173
|
-
console.log(chalk.cyan.bold(
|
|
174
|
-
console.log(chalk.cyan(
|
|
175
|
-
console.log(chalk.yellow(
|
|
176
|
-
console.log(
|
|
177
|
-
console.log(
|
|
184
|
+
console.log(chalk.cyan.bold("\n" + "-".repeat(50)));
|
|
185
|
+
console.log(chalk.cyan.bold("File Selection"));
|
|
186
|
+
console.log(chalk.cyan("-".repeat(50)));
|
|
187
|
+
console.log(chalk.yellow("Tips:"));
|
|
188
|
+
console.log(" • Type filename to add individual files");
|
|
189
|
+
console.log(" • Use wildcards: *.html, *.js, *.pdf, etc.");
|
|
178
190
|
console.log(' • Type "browse" to see available files');
|
|
179
191
|
console.log(' • Type "remove" to remove files from selection');
|
|
180
192
|
console.log(' • Type ".." or "back" to return to previous menu');
|
|
181
|
-
console.log(
|
|
193
|
+
console.log(" • Press Enter with no input to finish selection\n");
|
|
182
194
|
while (true) {
|
|
183
195
|
if (selectedFiles.length > 0) {
|
|
184
|
-
console.log(chalk.cyan(
|
|
196
|
+
console.log(chalk.cyan("\n" + "-".repeat(50)));
|
|
185
197
|
console.log(chalk.cyan.bold(`Currently selected (${selectedFiles.length} files):`));
|
|
186
198
|
selectedFiles.forEach((file, index) => {
|
|
187
199
|
const stats = fs.statSync(file);
|
|
188
|
-
const size = (stats.size / 1024).toFixed(1) +
|
|
189
|
-
console.log(pad(chalk.white(
|
|
200
|
+
const size = (stats.size / 1024).toFixed(1) + " KB";
|
|
201
|
+
console.log(pad(chalk.white(index + 1 + "."), 5) +
|
|
202
|
+
pad(path.basename(file), 35) +
|
|
203
|
+
chalk.gray(size));
|
|
190
204
|
});
|
|
191
|
-
console.log(chalk.cyan(
|
|
205
|
+
console.log(chalk.cyan("-".repeat(50)));
|
|
192
206
|
}
|
|
193
|
-
const input = await askQuestion(rl, chalk.bold.cyan(
|
|
207
|
+
const input = await askQuestion(rl, chalk.bold.cyan("\nAdd file (or press Enter to finish): "));
|
|
194
208
|
if (!input.trim())
|
|
195
209
|
break;
|
|
196
|
-
if (input ===
|
|
210
|
+
if (input === ".." || input.toLowerCase() === "back") {
|
|
197
211
|
return selectedFiles;
|
|
198
212
|
}
|
|
199
|
-
if (input.toLowerCase() ===
|
|
200
|
-
console.log(chalk.cyan(
|
|
201
|
-
console.log(chalk.cyan.bold(
|
|
213
|
+
if (input.toLowerCase() === "browse") {
|
|
214
|
+
console.log(chalk.cyan("\n" + "-".repeat(50)));
|
|
215
|
+
console.log(chalk.cyan.bold("Browsing available files:"));
|
|
202
216
|
try {
|
|
203
217
|
const listedFiles = [];
|
|
204
218
|
function walk(dir) {
|
|
@@ -206,7 +220,7 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
206
220
|
for (const entry of entries) {
|
|
207
221
|
const fullPath = path.join(dir, entry);
|
|
208
222
|
const relPath = path.relative(currentDir, fullPath);
|
|
209
|
-
if ([
|
|
223
|
+
if (["node_modules", ".git", "dist", "build"].includes(entry))
|
|
210
224
|
continue;
|
|
211
225
|
try {
|
|
212
226
|
const stat = fs.statSync(fullPath);
|
|
@@ -214,41 +228,47 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
214
228
|
walk(fullPath);
|
|
215
229
|
}
|
|
216
230
|
else if (stat.isFile()) {
|
|
217
|
-
listedFiles.push({
|
|
231
|
+
listedFiles.push({
|
|
232
|
+
path: fullPath,
|
|
233
|
+
rel: relPath,
|
|
234
|
+
size: stat.size,
|
|
235
|
+
});
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
|
-
catch
|
|
238
|
+
catch {
|
|
221
239
|
continue;
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
}
|
|
225
243
|
walk(currentDir);
|
|
226
244
|
if (listedFiles.length === 0) {
|
|
227
|
-
console.log(chalk.red(
|
|
245
|
+
console.log(chalk.red(" No suitable files found."));
|
|
228
246
|
}
|
|
229
247
|
else {
|
|
230
248
|
listedFiles.forEach((file, index) => {
|
|
231
249
|
const sizeKB = (file.size / 1024).toFixed(1);
|
|
232
|
-
console.log(pad(chalk.white(
|
|
250
|
+
console.log(pad(chalk.white(index + 1 + "."), 5) +
|
|
251
|
+
pad(file.rel, 35) +
|
|
252
|
+
chalk.gray(sizeKB + " KB"));
|
|
233
253
|
});
|
|
234
254
|
}
|
|
235
255
|
}
|
|
236
256
|
catch (error) {
|
|
237
257
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
238
|
-
console.log(chalk.red(
|
|
258
|
+
console.log(chalk.red(" Error reading directory: " + errorMessage));
|
|
239
259
|
}
|
|
240
260
|
continue;
|
|
241
261
|
}
|
|
242
|
-
if (input.toLowerCase() ===
|
|
262
|
+
if (input.toLowerCase() === "remove") {
|
|
243
263
|
if (selectedFiles.length === 0) {
|
|
244
|
-
console.log(chalk.red(
|
|
264
|
+
console.log(chalk.red("No files selected to remove."));
|
|
245
265
|
continue;
|
|
246
266
|
}
|
|
247
|
-
console.log(chalk.cyan(
|
|
267
|
+
console.log(chalk.cyan("\nSelect file to remove:"));
|
|
248
268
|
selectedFiles.forEach((file, index) => {
|
|
249
|
-
console.log(pad(chalk.white(
|
|
269
|
+
console.log(pad(chalk.white(index + 1 + "."), 5) + path.basename(file));
|
|
250
270
|
});
|
|
251
|
-
const removeChoice = await askQuestion(rl, chalk.bold.cyan(
|
|
271
|
+
const removeChoice = await askQuestion(rl, chalk.bold.cyan("\nEnter number to remove (or press Enter to cancel): "));
|
|
252
272
|
if (removeChoice.trim()) {
|
|
253
273
|
const removeIndex = parseInt(removeChoice) - 1;
|
|
254
274
|
if (removeIndex >= 0 && removeIndex < selectedFiles.length) {
|
|
@@ -258,14 +278,14 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
258
278
|
}
|
|
259
279
|
}
|
|
260
280
|
else {
|
|
261
|
-
console.log(chalk.red(
|
|
281
|
+
console.log(chalk.red("Invalid selection."));
|
|
262
282
|
}
|
|
263
283
|
}
|
|
264
284
|
continue;
|
|
265
285
|
}
|
|
266
286
|
let filePath = input;
|
|
267
287
|
let zipRequested = false;
|
|
268
|
-
if (filePath.endsWith(
|
|
288
|
+
if (filePath.endsWith(" -zip")) {
|
|
269
289
|
filePath = filePath.slice(0, -5).trim();
|
|
270
290
|
zipRequested = true;
|
|
271
291
|
}
|
|
@@ -274,16 +294,16 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
274
294
|
}
|
|
275
295
|
try {
|
|
276
296
|
if (!fs.existsSync(filePath)) {
|
|
277
|
-
console.log(chalk.red(
|
|
297
|
+
console.log(chalk.red("Error: File not found: " + input));
|
|
278
298
|
continue;
|
|
279
299
|
}
|
|
280
300
|
const stats = fs.statSync(filePath);
|
|
281
301
|
if (zipRequested) {
|
|
282
302
|
const baseName = path.basename(filePath);
|
|
283
|
-
const zipName = baseName.replace(/\.[^/.]+$/,
|
|
303
|
+
const zipName = baseName.replace(/\.[^/.]+$/, "") + ".zip";
|
|
284
304
|
const zipPath = path.join(currentDir, zipName);
|
|
285
305
|
const zip = new AdmZip();
|
|
286
|
-
process.stdout.write(chalk.yellow(
|
|
306
|
+
process.stdout.write(chalk.yellow("Zipping, please wait... "));
|
|
287
307
|
if (stats.isDirectory()) {
|
|
288
308
|
zip.addLocalFolder(filePath);
|
|
289
309
|
}
|
|
@@ -291,24 +311,24 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
291
311
|
zip.addLocalFile(filePath);
|
|
292
312
|
}
|
|
293
313
|
else {
|
|
294
|
-
console.log(chalk.red(
|
|
314
|
+
console.log(chalk.red("Not a file or folder."));
|
|
295
315
|
continue;
|
|
296
316
|
}
|
|
297
317
|
zip.writeZip(zipPath);
|
|
298
|
-
console.log(chalk.green(
|
|
318
|
+
console.log(chalk.green("Done."));
|
|
299
319
|
console.log(chalk.green(`Created ZIP: ${zipName}`));
|
|
300
320
|
if (selectedFiles.includes(zipPath)) {
|
|
301
321
|
console.log(chalk.yellow(`File already selected: ${zipName}`));
|
|
302
322
|
continue;
|
|
303
323
|
}
|
|
304
324
|
selectedFiles.push(zipPath);
|
|
305
|
-
const size = (fs.statSync(zipPath).size / 1024).toFixed(1) +
|
|
325
|
+
const size = (fs.statSync(zipPath).size / 1024).toFixed(1) + " KB";
|
|
306
326
|
console.log(chalk.green(`Added: ${zipName} (${size})`));
|
|
307
327
|
continue;
|
|
308
328
|
}
|
|
309
329
|
if (stats.isDirectory()) {
|
|
310
330
|
const baseName = path.basename(filePath);
|
|
311
|
-
if ([
|
|
331
|
+
if (["node_modules", ".git", "dist", "build"].includes(baseName))
|
|
312
332
|
continue;
|
|
313
333
|
const collectedFiles = [];
|
|
314
334
|
function walk(dir) {
|
|
@@ -318,7 +338,7 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
318
338
|
const stat = fs.statSync(fullPath);
|
|
319
339
|
if (stat.isDirectory()) {
|
|
320
340
|
const baseName = path.basename(fullPath);
|
|
321
|
-
if ([
|
|
341
|
+
if (["node_modules", ".git", "dist", "build"].includes(baseName))
|
|
322
342
|
continue;
|
|
323
343
|
walk(fullPath);
|
|
324
344
|
}
|
|
@@ -338,13 +358,15 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
338
358
|
const stat = fs.statSync(f);
|
|
339
359
|
totalSize += stat.size;
|
|
340
360
|
const relativePath = path.relative(currentDir, f);
|
|
341
|
-
console.log(pad(chalk.white(
|
|
361
|
+
console.log(pad(chalk.white(i + 1 + "."), 5) +
|
|
362
|
+
pad(relativePath, 35) +
|
|
363
|
+
chalk.gray((stat.size / 1024).toFixed(1) + " KB"));
|
|
342
364
|
});
|
|
343
|
-
console.log(chalk.cyan(
|
|
365
|
+
console.log(chalk.cyan("-".repeat(50)));
|
|
344
366
|
console.log(chalk.cyan(`Total size: ${(totalSize / 1024).toFixed(1)} KB`));
|
|
345
367
|
const confirmFolder = await askConfirmation(rl, chalk.bold.cyan(`Add all ${collectedFiles.length} files from this folder?`), true);
|
|
346
368
|
if (confirmFolder) {
|
|
347
|
-
const newFiles = collectedFiles.filter(f => !selectedFiles.includes(f));
|
|
369
|
+
const newFiles = collectedFiles.filter((f) => !selectedFiles.includes(f));
|
|
348
370
|
selectedFiles.push(...newFiles);
|
|
349
371
|
console.log(chalk.green(`Added ${newFiles.length} new files (${collectedFiles.length - newFiles.length} already selected)`));
|
|
350
372
|
}
|
|
@@ -355,12 +377,12 @@ export async function selectFilesImproved(rl, currentDir = process.cwd()) {
|
|
|
355
377
|
continue;
|
|
356
378
|
}
|
|
357
379
|
selectedFiles.push(filePath);
|
|
358
|
-
const size = (stats.size / 1024).toFixed(1) +
|
|
380
|
+
const size = (stats.size / 1024).toFixed(1) + " KB";
|
|
359
381
|
console.log(chalk.green(`Added: ${path.basename(filePath)} (${size})`));
|
|
360
382
|
}
|
|
361
383
|
catch (error) {
|
|
362
384
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
363
|
-
console.log(chalk.red(
|
|
385
|
+
console.log(chalk.red("Error accessing file: " + errorMessage));
|
|
364
386
|
}
|
|
365
387
|
}
|
|
366
388
|
return selectedFiles;
|
|
@@ -371,50 +393,78 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
371
393
|
let currentPath = currentDir;
|
|
372
394
|
let currentIndex = 0;
|
|
373
395
|
let isNavigating = true;
|
|
374
|
-
const prevDataListeners = process.stdin.listeners(
|
|
375
|
-
process.stdin.removeAllListeners(
|
|
396
|
+
const prevDataListeners = process.stdin.listeners("data").slice();
|
|
397
|
+
process.stdin.removeAllListeners("data");
|
|
376
398
|
if (process.stdin.isTTY) {
|
|
377
399
|
process.stdin.setRawMode(true);
|
|
378
400
|
}
|
|
379
401
|
process.stdin.resume();
|
|
380
|
-
process.stdin.setEncoding(
|
|
402
|
+
process.stdin.setEncoding("utf8");
|
|
381
403
|
function getFileIcon(filename) {
|
|
382
404
|
const ext = path.extname(filename).toLowerCase();
|
|
383
405
|
const icons = {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
406
|
+
".pdf": "📄",
|
|
407
|
+
".doc": "📄",
|
|
408
|
+
".docx": "📄",
|
|
409
|
+
".txt": "📄",
|
|
410
|
+
".js": "📜",
|
|
411
|
+
".ts": "📜",
|
|
412
|
+
".py": "📜",
|
|
413
|
+
".java": "📜",
|
|
414
|
+
".cpp": "📜",
|
|
415
|
+
".c": "📜",
|
|
416
|
+
".html": "🌐",
|
|
417
|
+
".css": "🎨",
|
|
418
|
+
".scss": "🎨",
|
|
419
|
+
".less": "🎨",
|
|
420
|
+
".json": "⚙️",
|
|
421
|
+
".xml": "⚙️",
|
|
422
|
+
".yml": "⚙️",
|
|
423
|
+
".yaml": "⚙️",
|
|
424
|
+
".zip": "📦",
|
|
425
|
+
".rar": "📦",
|
|
426
|
+
".7z": "📦",
|
|
427
|
+
".tar": "📦",
|
|
428
|
+
".jpg": "🖼️",
|
|
429
|
+
".jpeg": "🖼️",
|
|
430
|
+
".png": "🖼️",
|
|
431
|
+
".gif": "🖼️",
|
|
432
|
+
".svg": "🖼️",
|
|
433
|
+
".mp4": "🎬",
|
|
434
|
+
".avi": "🎬",
|
|
435
|
+
".mov": "🎬",
|
|
436
|
+
".mkv": "🎬",
|
|
437
|
+
".mp3": "🎵",
|
|
438
|
+
".wav": "🎵",
|
|
439
|
+
".flac": "🎵",
|
|
392
440
|
};
|
|
393
|
-
return icons[ext] ||
|
|
441
|
+
return icons[ext] || "📋";
|
|
394
442
|
}
|
|
395
443
|
function buildBreadcrumb() {
|
|
396
444
|
const relativePath = path.relative(currentDir, currentPath);
|
|
397
|
-
if (!relativePath || relativePath ===
|
|
398
|
-
return
|
|
445
|
+
if (!relativePath || relativePath === ".") {
|
|
446
|
+
return "";
|
|
399
447
|
}
|
|
400
448
|
const parts = relativePath.split(path.sep);
|
|
401
|
-
const breadcrumb = parts
|
|
449
|
+
const breadcrumb = parts
|
|
450
|
+
.map((part, index) => {
|
|
402
451
|
if (index === parts.length - 1) {
|
|
403
452
|
return chalk.white.bold(part);
|
|
404
453
|
}
|
|
405
454
|
return chalk.gray(part);
|
|
406
|
-
})
|
|
407
|
-
|
|
455
|
+
})
|
|
456
|
+
.join(chalk.gray(" › "));
|
|
457
|
+
return chalk.yellow("📂 ") + breadcrumb;
|
|
408
458
|
}
|
|
409
459
|
let lastDisplayLines = 0;
|
|
410
460
|
function stripAnsi(str) {
|
|
411
|
-
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
461
|
+
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
412
462
|
}
|
|
413
|
-
function printAndTrack(message =
|
|
463
|
+
function printAndTrack(message = "") {
|
|
414
464
|
console.log(message);
|
|
415
465
|
const width = process.stdout.columns || 80;
|
|
416
466
|
const clean = stripAnsi(message);
|
|
417
|
-
const lines = clean.split(
|
|
467
|
+
const lines = clean.split("\n");
|
|
418
468
|
let count = 0;
|
|
419
469
|
for (const line of lines) {
|
|
420
470
|
count += Math.max(1, Math.ceil((line.length || 0.5) / width));
|
|
@@ -437,11 +487,11 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
437
487
|
if (breadcrumb) {
|
|
438
488
|
printAndTrack(breadcrumb);
|
|
439
489
|
}
|
|
440
|
-
printAndTrack(chalk.gray(
|
|
490
|
+
printAndTrack(chalk.gray("💡 ↑↓←→:Navigate Space:Select Enter:Open/Finish Backspace:Up a:All c:Clear r:Reload Esc/Ctrl+C:Exit"));
|
|
441
491
|
if (Array.isArray(allowedExtensions) && allowedExtensions.length > 0) {
|
|
442
492
|
const exts = allowedExtensions
|
|
443
|
-
.map(e =>
|
|
444
|
-
.join(
|
|
493
|
+
.map((e) => e.startsWith(".") ? e.toLowerCase() : "." + e.toLowerCase())
|
|
494
|
+
.join(", ");
|
|
445
495
|
printAndTrack(chalk.yellow(`Allowed: ${exts}`));
|
|
446
496
|
}
|
|
447
497
|
if (selectedFiles.length > 0) {
|
|
@@ -457,7 +507,7 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
457
507
|
}
|
|
458
508
|
printAndTrack();
|
|
459
509
|
if (fileList.length === 0) {
|
|
460
|
-
printAndTrack(chalk.yellow(
|
|
510
|
+
printAndTrack(chalk.yellow("📭 No files found in this directory."));
|
|
461
511
|
return;
|
|
462
512
|
}
|
|
463
513
|
displayFileTree();
|
|
@@ -471,36 +521,38 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
471
521
|
if (startIdx > 0) {
|
|
472
522
|
printAndTrack(chalk.gray(` ⋮ (${startIdx} items above)`));
|
|
473
523
|
}
|
|
474
|
-
const maxItemWidth = Math.max(...visibleItems.map(item => {
|
|
524
|
+
const maxItemWidth = Math.max(...visibleItems.map((item) => {
|
|
475
525
|
const name = path.basename(item.path);
|
|
476
526
|
return name.length + 4;
|
|
477
527
|
}));
|
|
478
528
|
const itemWidth = Math.min(Math.max(maxItemWidth, 15), 25);
|
|
479
529
|
const columnsPerRow = Math.max(1, Math.floor((terminalWidth - 4) / itemWidth));
|
|
480
|
-
let currentRow =
|
|
530
|
+
let currentRow = "";
|
|
481
531
|
let itemsInCurrentRow = 0;
|
|
482
532
|
visibleItems.forEach((item, index) => {
|
|
483
533
|
const actualIndex = startIdx + index;
|
|
484
534
|
const isSelected = selectedFiles.includes(item.path);
|
|
485
535
|
const isCurrent = actualIndex === currentIndex;
|
|
486
|
-
let icon =
|
|
487
|
-
if (item.type ===
|
|
488
|
-
icon =
|
|
536
|
+
let icon = "";
|
|
537
|
+
if (item.type === "parent" || item.type === "directory") {
|
|
538
|
+
icon = "📁";
|
|
489
539
|
}
|
|
490
540
|
else {
|
|
491
541
|
icon = getFileIcon(path.basename(item.path));
|
|
492
542
|
}
|
|
493
543
|
const name = item.name || path.basename(item.path);
|
|
494
|
-
const truncatedName = name.length > itemWidth - 4
|
|
544
|
+
const truncatedName = name.length > itemWidth - 4
|
|
545
|
+
? name.slice(0, itemWidth - 7) + "..."
|
|
546
|
+
: name;
|
|
495
547
|
let itemDisplay = `${icon} ${truncatedName}`;
|
|
496
548
|
if (isCurrent) {
|
|
497
549
|
if (isSelected) {
|
|
498
550
|
itemDisplay = chalk.black.bgGreen(` ${itemDisplay}`.padEnd(itemWidth - 1));
|
|
499
551
|
}
|
|
500
|
-
else if (item.type ===
|
|
552
|
+
else if (item.type === "parent") {
|
|
501
553
|
itemDisplay = chalk.white.bgBlue(` ${itemDisplay}`.padEnd(itemWidth - 1));
|
|
502
554
|
}
|
|
503
|
-
else if (item.type ===
|
|
555
|
+
else if (item.type === "directory") {
|
|
504
556
|
itemDisplay = chalk.black.bgCyan(` ${itemDisplay}`.padEnd(itemWidth - 1));
|
|
505
557
|
}
|
|
506
558
|
else {
|
|
@@ -511,10 +563,10 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
511
563
|
if (isSelected) {
|
|
512
564
|
itemDisplay = chalk.green(`✓${itemDisplay}`.padEnd(itemWidth));
|
|
513
565
|
}
|
|
514
|
-
else if (item.type ===
|
|
566
|
+
else if (item.type === "parent") {
|
|
515
567
|
itemDisplay = chalk.blue(` ${itemDisplay}`.padEnd(itemWidth));
|
|
516
568
|
}
|
|
517
|
-
else if (item.type ===
|
|
569
|
+
else if (item.type === "directory") {
|
|
518
570
|
itemDisplay = chalk.cyan(` ${itemDisplay}`.padEnd(itemWidth));
|
|
519
571
|
}
|
|
520
572
|
else {
|
|
@@ -523,9 +575,10 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
523
575
|
}
|
|
524
576
|
currentRow += itemDisplay;
|
|
525
577
|
itemsInCurrentRow++;
|
|
526
|
-
if (itemsInCurrentRow >= columnsPerRow ||
|
|
578
|
+
if (itemsInCurrentRow >= columnsPerRow ||
|
|
579
|
+
index === visibleItems.length - 1) {
|
|
527
580
|
printAndTrack(currentRow);
|
|
528
|
-
currentRow =
|
|
581
|
+
currentRow = "";
|
|
529
582
|
itemsInCurrentRow = 0;
|
|
530
583
|
}
|
|
531
584
|
});
|
|
@@ -547,39 +600,40 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
547
600
|
try {
|
|
548
601
|
if (currentPath !== currentDir) {
|
|
549
602
|
fileList.push({
|
|
550
|
-
type:
|
|
603
|
+
type: "parent",
|
|
551
604
|
path: path.dirname(currentPath),
|
|
552
|
-
name:
|
|
605
|
+
name: "..",
|
|
553
606
|
});
|
|
554
607
|
}
|
|
555
608
|
const entries = fs.readdirSync(currentPath).sort();
|
|
556
|
-
entries.forEach(entry => {
|
|
609
|
+
entries.forEach((entry) => {
|
|
557
610
|
const fullPath = path.join(currentPath, entry);
|
|
558
611
|
const stat = fs.statSync(fullPath);
|
|
559
612
|
if (stat.isDirectory()) {
|
|
560
613
|
fileList.push({
|
|
561
|
-
type:
|
|
614
|
+
type: "directory",
|
|
562
615
|
path: fullPath,
|
|
563
|
-
name: entry
|
|
616
|
+
name: entry,
|
|
564
617
|
});
|
|
565
618
|
}
|
|
566
619
|
});
|
|
567
|
-
entries.forEach(entry => {
|
|
620
|
+
entries.forEach((entry) => {
|
|
568
621
|
const fullPath = path.join(currentPath, entry);
|
|
569
622
|
const stat = fs.statSync(fullPath);
|
|
570
623
|
if (stat.isFile()) {
|
|
571
|
-
if (Array.isArray(allowedExtensions) &&
|
|
572
|
-
|
|
624
|
+
if (Array.isArray(allowedExtensions) &&
|
|
625
|
+
allowedExtensions.length > 0) {
|
|
626
|
+
const lowerExts = allowedExtensions.map((e) => e.startsWith(".") ? e.toLowerCase() : "." + e.toLowerCase());
|
|
573
627
|
const ext = path.extname(entry).toLowerCase();
|
|
574
628
|
if (!lowerExts.includes(ext)) {
|
|
575
629
|
return;
|
|
576
630
|
}
|
|
577
631
|
}
|
|
578
632
|
fileList.push({
|
|
579
|
-
type:
|
|
633
|
+
type: "file",
|
|
580
634
|
path: fullPath,
|
|
581
635
|
name: entry,
|
|
582
|
-
size: stat.size
|
|
636
|
+
size: stat.size,
|
|
583
637
|
});
|
|
584
638
|
}
|
|
585
639
|
});
|
|
@@ -588,13 +642,13 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
588
642
|
}
|
|
589
643
|
}
|
|
590
644
|
catch (error) {
|
|
591
|
-
console.error(
|
|
645
|
+
console.error("Error reading directory:", error);
|
|
592
646
|
fileList = [];
|
|
593
647
|
}
|
|
594
648
|
}
|
|
595
649
|
function handleKeyInput(key) {
|
|
596
650
|
const terminalWidth = process.stdout.columns || 80;
|
|
597
|
-
const maxItemWidth = Math.max(...fileList.map(item => {
|
|
651
|
+
const maxItemWidth = Math.max(...fileList.map((item) => {
|
|
598
652
|
const name = path.basename(item.path);
|
|
599
653
|
return name.length + 4;
|
|
600
654
|
}));
|
|
@@ -630,7 +684,7 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
630
684
|
case KEYS.SPACE:
|
|
631
685
|
if (fileList.length > 0) {
|
|
632
686
|
const item = fileList[currentIndex];
|
|
633
|
-
if (item && item.type ===
|
|
687
|
+
if (item && item.type === "file") {
|
|
634
688
|
const index = selectedFiles.indexOf(item.path);
|
|
635
689
|
if (index === -1) {
|
|
636
690
|
selectedFiles.push(item.path);
|
|
@@ -645,7 +699,7 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
645
699
|
case KEYS.ENTER:
|
|
646
700
|
if (fileList.length > 0) {
|
|
647
701
|
const item = fileList[currentIndex];
|
|
648
|
-
if (item && (item.type ===
|
|
702
|
+
if (item && (item.type === "parent" || item.type === "directory")) {
|
|
649
703
|
currentPath = item.path;
|
|
650
704
|
currentIndex = 0;
|
|
651
705
|
refreshFileList();
|
|
@@ -671,10 +725,10 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
671
725
|
displayBrowser();
|
|
672
726
|
}
|
|
673
727
|
break;
|
|
674
|
-
case
|
|
728
|
+
case "a":
|
|
675
729
|
let addedCount = 0;
|
|
676
|
-
fileList.forEach(item => {
|
|
677
|
-
if (item.type ===
|
|
730
|
+
fileList.forEach((item) => {
|
|
731
|
+
if (item.type === "file" && !selectedFiles.includes(item.path)) {
|
|
678
732
|
selectedFiles.push(item.path);
|
|
679
733
|
addedCount++;
|
|
680
734
|
}
|
|
@@ -683,11 +737,11 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
683
737
|
displayBrowser();
|
|
684
738
|
}
|
|
685
739
|
break;
|
|
686
|
-
case
|
|
740
|
+
case "c":
|
|
687
741
|
selectedFiles.length = 0;
|
|
688
742
|
displayBrowser();
|
|
689
743
|
break;
|
|
690
|
-
case
|
|
744
|
+
case "r":
|
|
691
745
|
refreshFileList();
|
|
692
746
|
displayBrowser();
|
|
693
747
|
break;
|
|
@@ -696,7 +750,7 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
696
750
|
isNavigating = false;
|
|
697
751
|
break;
|
|
698
752
|
case KEYS.ESCAPE:
|
|
699
|
-
case
|
|
753
|
+
case "\u001b":
|
|
700
754
|
isNavigating = false;
|
|
701
755
|
break;
|
|
702
756
|
default:
|
|
@@ -713,10 +767,10 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
713
767
|
const keyStr = key.toString();
|
|
714
768
|
handleKeyInput(keyStr);
|
|
715
769
|
if (!isNavigating) {
|
|
716
|
-
process.stdin.removeListener(
|
|
770
|
+
process.stdin.removeListener("data", onData);
|
|
717
771
|
try {
|
|
718
772
|
for (const l of prevDataListeners) {
|
|
719
|
-
process.stdin.on(
|
|
773
|
+
process.stdin.on("data", l);
|
|
720
774
|
}
|
|
721
775
|
}
|
|
722
776
|
catch {
|
|
@@ -726,8 +780,8 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
726
780
|
}
|
|
727
781
|
process.stdin.pause();
|
|
728
782
|
if (selectedFiles.length > 0) {
|
|
729
|
-
console.log(chalk.green.bold(
|
|
730
|
-
console.log(chalk.cyan(
|
|
783
|
+
console.log(chalk.green.bold("✅ File Selection Complete!"));
|
|
784
|
+
console.log(chalk.cyan("-".repeat(50)));
|
|
731
785
|
const totalSize = selectedFiles.reduce((sum, file) => {
|
|
732
786
|
try {
|
|
733
787
|
return sum + fs.statSync(file).size;
|
|
@@ -740,19 +794,22 @@ export async function selectFilesKeyboard(_rl, currentDir = process.cwd(), allow
|
|
|
740
794
|
selectedFiles.forEach((file, index) => {
|
|
741
795
|
try {
|
|
742
796
|
const stats = fs.statSync(file);
|
|
743
|
-
const size = (stats.size / 1024).toFixed(1) +
|
|
744
|
-
console.log(pad(chalk.green(`${index + 1}.`), 5) +
|
|
797
|
+
const size = (stats.size / 1024).toFixed(1) + " KB";
|
|
798
|
+
console.log(pad(chalk.green(`${index + 1}.`), 5) +
|
|
799
|
+
pad(path.basename(file), 35) +
|
|
800
|
+
chalk.gray(size));
|
|
745
801
|
}
|
|
746
|
-
catch
|
|
747
|
-
console.log(pad(chalk.red(`${index + 1}.`), 5) +
|
|
802
|
+
catch {
|
|
803
|
+
console.log(pad(chalk.red(`${index + 1}.`), 5) +
|
|
804
|
+
chalk.red(path.basename(file) + " (Error reading file)"));
|
|
748
805
|
}
|
|
749
806
|
});
|
|
750
|
-
console.log(chalk.cyan(
|
|
807
|
+
console.log(chalk.cyan("-".repeat(50)));
|
|
751
808
|
}
|
|
752
809
|
resolve(selectedFiles);
|
|
753
810
|
}
|
|
754
811
|
};
|
|
755
|
-
process.stdin.on(
|
|
812
|
+
process.stdin.on("data", onData);
|
|
756
813
|
});
|
|
757
814
|
}
|
|
758
815
|
//# sourceMappingURL=interactive.js.map
|