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