@fnndsc/chili 3.3.0 → 3.5.0
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/dist/chefs/chefs.js +12 -11
- package/dist/commands/fs/download.d.ts +19 -4
- package/dist/commands/fs/download.js +75 -65
- package/dist/commands/fs/upload.d.ts +20 -4
- package/dist/commands/fs/upload.js +46 -34
- package/dist/commands/man/doc.js +3 -2
- package/dist/commands/plugins/add.js +35 -34
- package/dist/config/colorConfig.js +2 -1
- package/dist/connect/connectHandler.js +7 -6
- package/dist/context/contextCommand.js +3 -2
- package/dist/controllers/fileController.js +4 -3
- package/dist/controllers/pluginController.js +2 -1
- package/dist/feeds/feedHandler.js +19 -18
- package/dist/filesystem/fileGroupHandler.js +28 -27
- package/dist/filesystem/filesystemHandler.js +13 -11
- package/dist/filesystem/inodeCommand.js +2 -1
- package/dist/handlers/baseGroupHandler.d.ts +10 -1
- package/dist/handlers/baseGroupHandler.js +38 -13
- package/dist/index.js +3 -2
- package/dist/lfs/lfs.js +6 -4
- package/dist/man/man.js +4 -3
- package/dist/man/renderer.js +3 -2
- package/dist/pacs/pacsQueryHandler.js +14 -13
- package/dist/pacs/pacsRetrieveHandler.js +13 -12
- package/dist/path/pathCommand.js +23 -22
- package/dist/plugins/pluginGroupHandler.d.ts +18 -0
- package/dist/plugins/pluginGroupHandler.js +44 -4
- package/dist/plugins/pluginHandler.js +23 -22
- package/dist/run.d.ts +13 -0
- package/dist/run.js +28 -13
- package/dist/screen/output.d.ts +88 -0
- package/dist/screen/output.js +95 -0
- package/dist/screen/screen.d.ts +13 -0
- package/dist/screen/screen.js +53 -8
- package/dist/utils/admin_prompt.js +10 -9
- package/dist/utils/docker.js +10 -9
- package/dist/views/pluginParameters.d.ts +10 -0
- package/dist/views/pluginParameters.js +25 -17
- package/docs/pacs.adoc +3 -3
- package/package.json +1 -1
package/dist/chefs/chefs.js
CHANGED
|
@@ -9,6 +9,7 @@ import { files_rm } from '../commands/fs/rm.js';
|
|
|
9
9
|
import { files_cp } from '../commands/fs/cp.js';
|
|
10
10
|
import { files_mv } from '../commands/fs/mv.js';
|
|
11
11
|
import { mkdir_render, touch_render, upload_render, cat_render, rm_render, cp_render, mv_render } from '../views/fs.js';
|
|
12
|
+
import { chiliLog } from "../screen/output.js";
|
|
12
13
|
/**
|
|
13
14
|
* Lists directory contents.
|
|
14
15
|
*
|
|
@@ -21,10 +22,10 @@ async function chefs_ls(options, pathStr = "") {
|
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
if (options.long) {
|
|
24
|
-
|
|
25
|
+
chiliLog(long_render(items, { human: !!options.human }));
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
|
-
|
|
28
|
+
chiliLog(grid_render(items));
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
@@ -36,7 +37,7 @@ async function chefs_ls(options, pathStr = "") {
|
|
|
36
37
|
*/
|
|
37
38
|
async function chefs_cp(src, dest, options) {
|
|
38
39
|
const success = await files_cp(src, dest, options);
|
|
39
|
-
|
|
40
|
+
chiliLog(cp_render(src, dest, success));
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Moves a file or directory.
|
|
@@ -47,7 +48,7 @@ async function chefs_cp(src, dest, options) {
|
|
|
47
48
|
*/
|
|
48
49
|
async function chefs_mv(src, dest, _options) {
|
|
49
50
|
const success = await files_mv(src, dest);
|
|
50
|
-
|
|
51
|
+
chiliLog(mv_render(src, dest, success));
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* Creates a directory.
|
|
@@ -56,7 +57,7 @@ async function chefs_mv(src, dest, _options) {
|
|
|
56
57
|
*/
|
|
57
58
|
async function chefs_mkdir(dirPath) {
|
|
58
59
|
const success = await files_mkdir(dirPath);
|
|
59
|
-
|
|
60
|
+
chiliLog(mkdir_render(dirPath, success));
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Creates an empty file.
|
|
@@ -65,7 +66,7 @@ async function chefs_mkdir(dirPath) {
|
|
|
65
66
|
*/
|
|
66
67
|
async function chefs_touch(filePath) {
|
|
67
68
|
const success = await files_touch(filePath);
|
|
68
|
-
|
|
69
|
+
chiliLog(touch_render(filePath, success));
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
72
|
* Uploads a local file or directory to ChRIS.
|
|
@@ -74,9 +75,9 @@ async function chefs_touch(filePath) {
|
|
|
74
75
|
* @param remotePath - Remote ChRIS path.
|
|
75
76
|
*/
|
|
76
77
|
async function chefs_upload(localPath, remotePath) {
|
|
77
|
-
|
|
78
|
+
chiliLog(`Uploading ${localPath} to ${remotePath}...`);
|
|
78
79
|
const success = await files_upload(localPath, remotePath);
|
|
79
|
-
|
|
80
|
+
chiliLog(upload_render(localPath, remotePath, success));
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Changes the current working directory.
|
|
@@ -96,7 +97,7 @@ async function chefs_cd(path) {
|
|
|
96
97
|
*/
|
|
97
98
|
async function chefs_pwd() {
|
|
98
99
|
const current = await chrisContext.current_get(Context.ChRISfolder);
|
|
99
|
-
|
|
100
|
+
chiliLog(current || "/");
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
103
|
* Displays file content.
|
|
@@ -106,7 +107,7 @@ async function chefs_pwd() {
|
|
|
106
107
|
async function chefs_cat(filePath) {
|
|
107
108
|
const result = await files_cat(filePath);
|
|
108
109
|
const content = result.ok ? result.value : null;
|
|
109
|
-
|
|
110
|
+
chiliLog(cat_render(content, filePath));
|
|
110
111
|
}
|
|
111
112
|
/**
|
|
112
113
|
* Removes a file or directory.
|
|
@@ -116,7 +117,7 @@ async function chefs_cat(filePath) {
|
|
|
116
117
|
*/
|
|
117
118
|
async function chefs_rm(targetPath, options) {
|
|
118
119
|
const result = await files_rm(targetPath, options);
|
|
119
|
-
|
|
120
|
+
chiliLog(rm_render(result));
|
|
120
121
|
}
|
|
121
122
|
/**
|
|
122
123
|
* Sets up the 'chefs' command group in Commander.
|
|
@@ -13,13 +13,28 @@ export interface DownloadSummary {
|
|
|
13
13
|
duration: number;
|
|
14
14
|
speed: number;
|
|
15
15
|
}
|
|
16
|
+
/** Download progress fact emitted to callers that want to render progress. */
|
|
17
|
+
export interface DownloadProgressEvent {
|
|
18
|
+
operation: "download";
|
|
19
|
+
kind: "transfer";
|
|
20
|
+
phase: "scanning" | "transferring" | "complete" | "failed";
|
|
21
|
+
label?: string;
|
|
22
|
+
current?: number;
|
|
23
|
+
total?: number;
|
|
24
|
+
percent?: number;
|
|
25
|
+
unit?: "files" | "bytes";
|
|
26
|
+
status?: "running" | "done" | "error";
|
|
27
|
+
}
|
|
28
|
+
/** Options for download execution. */
|
|
29
|
+
export interface DownloadOptions {
|
|
30
|
+
force?: boolean;
|
|
31
|
+
onProgress?: (event: DownloadProgressEvent) => void;
|
|
32
|
+
}
|
|
16
33
|
/**
|
|
17
|
-
* Downloads files from ChRIS
|
|
34
|
+
* Downloads files from ChRIS and optionally emits structured progress events.
|
|
18
35
|
* @param chrisPath - Remote ChRIS path (file or directory).
|
|
19
36
|
* @param localPath - Local filesystem path.
|
|
20
37
|
* @param options - Download options.
|
|
21
38
|
* @returns Promise<DownloadSummary> with download statistics.
|
|
22
39
|
*/
|
|
23
|
-
export declare function files_downloadWithProgress(chrisPath: string, localPath: string, options?:
|
|
24
|
-
force?: boolean;
|
|
25
|
-
}): Promise<DownloadSummary>;
|
|
40
|
+
export declare function files_downloadWithProgress(chrisPath: string, localPath: string, options?: DownloadOptions): Promise<DownloadSummary>;
|
|
@@ -6,11 +6,11 @@ import { fileContent_getBinaryStream, files_listRecursive } from "@fnndsc/salsa"
|
|
|
6
6
|
import { path_resolveChrisFs } from "../../utils/cli.js";
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import cliProgress from "cli-progress";
|
|
10
9
|
import chalk from "chalk";
|
|
11
10
|
import { pipeline } from "stream/promises";
|
|
12
|
-
import { bytes_format
|
|
11
|
+
import { bytes_format } from "./upload.js";
|
|
13
12
|
import { prompt_confirmOrThrow } from "../../utils/input_format.js";
|
|
13
|
+
import { chiliLog } from "../../screen/output.js";
|
|
14
14
|
export { bytes_format };
|
|
15
15
|
/**
|
|
16
16
|
* Recursively creates a directory and all parent directories.
|
|
@@ -36,7 +36,7 @@ async function chrisPath_isDirectory(chrisPath) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Downloads files from ChRIS
|
|
39
|
+
* Downloads files from ChRIS and optionally emits structured progress events.
|
|
40
40
|
* @param chrisPath - Remote ChRIS path (file or directory).
|
|
41
41
|
* @param localPath - Local filesystem path.
|
|
42
42
|
* @param options - Download options.
|
|
@@ -93,61 +93,66 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
93
93
|
// Ensure parent directory exists
|
|
94
94
|
const parentDir = path.dirname(finalLocalPath);
|
|
95
95
|
await directory_ensureExists(parentDir);
|
|
96
|
-
const showProgress = !!process.stdout.isTTY;
|
|
97
|
-
const progressBar = typeof size === "number" && showProgress
|
|
98
|
-
? new cliProgress.SingleBar({
|
|
99
|
-
format: "Downloading [{bar}] {percentage}% | ETA: {etaHuman} | Rate: {rate} | {bytes}/{totalBytes}",
|
|
100
|
-
}, cliProgress.Presets.shades_classic)
|
|
101
|
-
: null;
|
|
102
|
-
if (progressBar) {
|
|
103
|
-
progressBar.start(size, 0, {
|
|
104
|
-
etaHuman: "--",
|
|
105
|
-
rate: "--",
|
|
106
|
-
bytes: bytes_format(0),
|
|
107
|
-
totalBytes: bytes_format(size),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
96
|
let transferred = 0;
|
|
111
97
|
const writeStream = fs.createWriteStream(finalLocalPath);
|
|
112
98
|
stream.on("data", (chunk) => {
|
|
113
99
|
transferred += chunk.length;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
100
|
+
options.onProgress?.({
|
|
101
|
+
operation: "download",
|
|
102
|
+
kind: "transfer",
|
|
103
|
+
phase: "transferring",
|
|
104
|
+
label: `Downloading ${path.basename(finalLocalPath)}`,
|
|
105
|
+
current: transferred,
|
|
106
|
+
total: size,
|
|
107
|
+
percent: typeof size === "number" && size > 0 ? (Math.min(transferred, size) / size) * 100 : undefined,
|
|
108
|
+
unit: "bytes",
|
|
109
|
+
status: "running",
|
|
110
|
+
});
|
|
126
111
|
});
|
|
127
112
|
await pipeline(stream, writeStream);
|
|
128
|
-
if (progressBar) {
|
|
129
|
-
progressBar.update(size || transferred);
|
|
130
|
-
progressBar.stop();
|
|
131
|
-
}
|
|
132
113
|
summary.totalFiles = 1;
|
|
133
114
|
summary.transferredCount = 1;
|
|
134
115
|
summary.transferSize = transferred;
|
|
135
116
|
summary.endTime = Date.now();
|
|
136
117
|
summary.duration = (summary.endTime - summary.startTime) / 1000;
|
|
137
118
|
summary.speed = summary.duration > 0 ? summary.transferSize / summary.duration : 0;
|
|
119
|
+
options.onProgress?.({
|
|
120
|
+
operation: "download",
|
|
121
|
+
kind: "transfer",
|
|
122
|
+
phase: "complete",
|
|
123
|
+
label: "Download complete",
|
|
124
|
+
current: transferred,
|
|
125
|
+
total: size,
|
|
126
|
+
percent: typeof size === "number" && size > 0 ? 100 : undefined,
|
|
127
|
+
unit: "bytes",
|
|
128
|
+
status: "done",
|
|
129
|
+
});
|
|
138
130
|
return summary;
|
|
139
131
|
}
|
|
140
132
|
catch (error) {
|
|
141
133
|
summary.failedCount = 1;
|
|
134
|
+
options.onProgress?.({
|
|
135
|
+
operation: "download",
|
|
136
|
+
kind: "transfer",
|
|
137
|
+
phase: "failed",
|
|
138
|
+
label: "Download failed",
|
|
139
|
+
status: "error",
|
|
140
|
+
});
|
|
142
141
|
throw error;
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
// Download directory
|
|
146
|
-
|
|
145
|
+
chiliLog(chalk.cyan("Scanning files to download..."));
|
|
146
|
+
options.onProgress?.({
|
|
147
|
+
operation: "download",
|
|
148
|
+
kind: "transfer",
|
|
149
|
+
phase: "scanning",
|
|
150
|
+
label: "Scanning files to download",
|
|
151
|
+
status: "running",
|
|
152
|
+
});
|
|
147
153
|
const items = await files_listRecursive(resolvedChris);
|
|
148
154
|
const files = items.filter((item) => item.type === 'file');
|
|
149
155
|
summary.totalFiles = files.length;
|
|
150
|
-
const totalSize = files.reduce((sum, f) => sum + (f.size || 0), 0);
|
|
151
156
|
// Apply Unix semantics: trailing slash determines merge behavior
|
|
152
157
|
const targetDir = resolvedChris.endsWith('/')
|
|
153
158
|
? localPath // Merge contents
|
|
@@ -169,27 +174,24 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
169
174
|
}
|
|
170
175
|
// Ensure target directory exists
|
|
171
176
|
await directory_ensureExists(targetDir);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
rate: "--",
|
|
184
|
-
});
|
|
185
|
-
}
|
|
177
|
+
options.onProgress?.({
|
|
178
|
+
operation: "download",
|
|
179
|
+
kind: "transfer",
|
|
180
|
+
phase: "transferring",
|
|
181
|
+
label: "Downloading files",
|
|
182
|
+
current: 0,
|
|
183
|
+
total: files.length,
|
|
184
|
+
percent: files.length === 0 ? 100 : 0,
|
|
185
|
+
unit: "files",
|
|
186
|
+
status: "running",
|
|
187
|
+
});
|
|
186
188
|
// Download each file
|
|
187
189
|
for (const [index, file] of files.entries()) {
|
|
188
190
|
try {
|
|
189
191
|
const result = await fileContent_getBinaryStream(file.path);
|
|
190
192
|
if (!result.ok) {
|
|
191
193
|
summary.failedCount++;
|
|
192
|
-
|
|
194
|
+
chiliLog(chalk.yellow(`\nFailed to download: ${file.path}`));
|
|
193
195
|
continue;
|
|
194
196
|
}
|
|
195
197
|
const { stream, size } = result.value;
|
|
@@ -211,25 +213,33 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
211
213
|
catch (error) {
|
|
212
214
|
summary.failedCount++;
|
|
213
215
|
const msg = error instanceof Error ? error.message : String(error);
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
const elapsedSeconds = Math.max(1e-6, (Date.now() - summary.startTime) / 1000);
|
|
217
|
-
const rateCurrent = summary.transferSize > 0 ? summary.transferSize / elapsedSeconds : 0;
|
|
218
|
-
const remainingBytes = Math.max(0, totalSize - summary.transferSize);
|
|
219
|
-
const etaSeconds = rateCurrent > 0 ? remainingBytes / rateCurrent : null;
|
|
220
|
-
if (progressBar) {
|
|
221
|
-
progressBar.update(index + 1, {
|
|
222
|
-
bytes: bytes_format(summary.transferSize),
|
|
223
|
-
etaHuman: eta_format(etaSeconds),
|
|
224
|
-
rate: rate_format(rateCurrent),
|
|
225
|
-
});
|
|
216
|
+
chiliLog(chalk.red(`\nError downloading ${file.path}: ${msg}`));
|
|
226
217
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
options.onProgress?.({
|
|
219
|
+
operation: "download",
|
|
220
|
+
kind: "transfer",
|
|
221
|
+
phase: "transferring",
|
|
222
|
+
label: "Downloading files",
|
|
223
|
+
current: index + 1,
|
|
224
|
+
total: files.length,
|
|
225
|
+
percent: files.length === 0 ? 100 : ((index + 1) / files.length) * 100,
|
|
226
|
+
unit: "files",
|
|
227
|
+
status: "running",
|
|
228
|
+
});
|
|
230
229
|
}
|
|
231
230
|
summary.endTime = Date.now();
|
|
232
231
|
summary.duration = (summary.endTime - summary.startTime) / 1000;
|
|
233
232
|
summary.speed = summary.duration > 0 ? summary.transferSize / summary.duration : 0;
|
|
233
|
+
options.onProgress?.({
|
|
234
|
+
operation: "download",
|
|
235
|
+
kind: "transfer",
|
|
236
|
+
phase: summary.failedCount === 0 ? "complete" : "failed",
|
|
237
|
+
label: "Download complete",
|
|
238
|
+
current: summary.transferredCount,
|
|
239
|
+
total: summary.totalFiles,
|
|
240
|
+
percent: summary.totalFiles === 0 ? 100 : (summary.transferredCount / summary.totalFiles) * 100,
|
|
241
|
+
unit: "files",
|
|
242
|
+
status: summary.failedCount === 0 ? "done" : "error",
|
|
243
|
+
});
|
|
234
244
|
return summary;
|
|
235
245
|
}
|
|
@@ -20,6 +20,23 @@ export interface UploadSummary {
|
|
|
20
20
|
speed: number;
|
|
21
21
|
actualTargetPath: string;
|
|
22
22
|
}
|
|
23
|
+
/** Upload progress fact emitted to callers that want to render progress. */
|
|
24
|
+
export interface UploadProgressEvent {
|
|
25
|
+
operation: "upload";
|
|
26
|
+
kind: "transfer";
|
|
27
|
+
phase: "scanning" | "transferring" | "complete" | "failed";
|
|
28
|
+
label?: string;
|
|
29
|
+
current?: number;
|
|
30
|
+
total?: number;
|
|
31
|
+
percent?: number;
|
|
32
|
+
unit?: "files";
|
|
33
|
+
status?: "running" | "done" | "error";
|
|
34
|
+
}
|
|
35
|
+
/** Options for upload execution. */
|
|
36
|
+
export interface UploadOptions {
|
|
37
|
+
force?: boolean;
|
|
38
|
+
onProgress?: (event: UploadProgressEvent) => void;
|
|
39
|
+
}
|
|
23
40
|
/**
|
|
24
41
|
* Formats bytes into human-readable format.
|
|
25
42
|
* @param bytes - Number of bytes.
|
|
@@ -37,14 +54,13 @@ export declare function eta_format(seconds: number | null): string;
|
|
|
37
54
|
*/
|
|
38
55
|
export declare function rate_format(rateBytesPerSec: number): string;
|
|
39
56
|
/**
|
|
40
|
-
* Uploads files
|
|
57
|
+
* Uploads files and optionally emits structured progress events.
|
|
41
58
|
* @param localPath - Local path (file or directory).
|
|
42
59
|
* @param remotePath - Remote ChRIS path.
|
|
60
|
+
* @param options - Upload options, including an optional progress callback.
|
|
43
61
|
* @returns Promise<UploadSummary> with upload statistics.
|
|
44
62
|
*/
|
|
45
|
-
export declare function files_uploadWithProgress(localPath: string, remotePath: string, options?:
|
|
46
|
-
force?: boolean;
|
|
47
|
-
}): Promise<UploadSummary>;
|
|
63
|
+
export declare function files_uploadWithProgress(localPath: string, remotePath: string, options?: UploadOptions): Promise<UploadSummary>;
|
|
48
64
|
/**
|
|
49
65
|
* Uploads a local file or directory to ChRIS.
|
|
50
66
|
*
|
|
@@ -7,9 +7,9 @@ import { chrisIO } from "@fnndsc/cumin";
|
|
|
7
7
|
import { path_resolveChrisFs } from "../../utils/cli.js";
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
|
-
import cliProgress from "cli-progress";
|
|
11
10
|
import chalk from "chalk";
|
|
12
11
|
import { prompt_confirmOrThrow } from "../../utils/input_format.js";
|
|
12
|
+
import { chiliLog } from "../../screen/output.js";
|
|
13
13
|
/**
|
|
14
14
|
* Formats bytes into human-readable format.
|
|
15
15
|
* @param bytes - Number of bytes.
|
|
@@ -101,17 +101,24 @@ async function localFiles_scan(localPath, remotePath) {
|
|
|
101
101
|
return files;
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
|
-
* Uploads files
|
|
104
|
+
* Uploads files and optionally emits structured progress events.
|
|
105
105
|
* @param localPath - Local path (file or directory).
|
|
106
106
|
* @param remotePath - Remote ChRIS path.
|
|
107
|
+
* @param options - Upload options, including an optional progress callback.
|
|
107
108
|
* @returns Promise<UploadSummary> with upload statistics.
|
|
108
109
|
*/
|
|
109
110
|
export async function files_uploadWithProgress(localPath, remotePath, options = {}) {
|
|
110
111
|
const resolvedRemote = await path_resolveChrisFs(remotePath, {});
|
|
111
112
|
// Scan files
|
|
112
|
-
|
|
113
|
+
chiliLog(chalk.cyan("Scanning files to upload..."));
|
|
114
|
+
options.onProgress?.({
|
|
115
|
+
operation: "upload",
|
|
116
|
+
kind: "transfer",
|
|
117
|
+
phase: "scanning",
|
|
118
|
+
label: "Scanning files to upload",
|
|
119
|
+
status: "running",
|
|
120
|
+
});
|
|
113
121
|
const fileList = await localFiles_scan(localPath, resolvedRemote);
|
|
114
|
-
const totalSize = fileList.reduce((sum, f) => sum + f.size, 0);
|
|
115
122
|
// Determine actual target path (where files will be uploaded)
|
|
116
123
|
const stats = await fs.promises.stat(localPath);
|
|
117
124
|
let actualTarget = resolvedRemote;
|
|
@@ -140,20 +147,6 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
140
147
|
else {
|
|
141
148
|
// Force mode: if target exists and is a directory, no prompt. If it's a file and we're uploading a single file, proceed.
|
|
142
149
|
}
|
|
143
|
-
const showProgress = !!process.stdout.isTTY;
|
|
144
|
-
const progressBar = showProgress
|
|
145
|
-
? new cliProgress.SingleBar({
|
|
146
|
-
format: "Transferring [{bar}] {percentage}% | ETA: {etaHuman} | Rate: {rate} | {value}/{total} files | {bytes}/{totalBytes}",
|
|
147
|
-
}, cliProgress.Presets.shades_classic)
|
|
148
|
-
: null;
|
|
149
|
-
if (progressBar) {
|
|
150
|
-
progressBar.start(fileList.length, 0, {
|
|
151
|
-
bytes: "0 B",
|
|
152
|
-
totalBytes: bytes_format(totalSize),
|
|
153
|
-
etaHuman: "--",
|
|
154
|
-
rate: "--",
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
150
|
// Upload files
|
|
158
151
|
const summary = {
|
|
159
152
|
startTime: Date.now(),
|
|
@@ -166,6 +159,17 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
166
159
|
speed: 0,
|
|
167
160
|
actualTargetPath: actualTarget,
|
|
168
161
|
};
|
|
162
|
+
options.onProgress?.({
|
|
163
|
+
operation: "upload",
|
|
164
|
+
kind: "transfer",
|
|
165
|
+
phase: "transferring",
|
|
166
|
+
label: "Uploading files",
|
|
167
|
+
current: 0,
|
|
168
|
+
total: fileList.length,
|
|
169
|
+
percent: fileList.length === 0 ? 100 : 0,
|
|
170
|
+
unit: "files",
|
|
171
|
+
status: "running",
|
|
172
|
+
});
|
|
169
173
|
for (const [index, file] of fileList.entries()) {
|
|
170
174
|
try {
|
|
171
175
|
const fileContent = await fs.promises.readFile(file.hostPath);
|
|
@@ -181,31 +185,39 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
181
185
|
}
|
|
182
186
|
else {
|
|
183
187
|
summary.failedCount++;
|
|
184
|
-
|
|
188
|
+
chiliLog(chalk.yellow(`Failed to upload: ${file.hostPath}`));
|
|
185
189
|
}
|
|
186
190
|
}
|
|
187
191
|
catch (error) {
|
|
188
192
|
summary.failedCount++;
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
const elapsedSeconds = Math.max(1e-6, (Date.now() - summary.startTime) / 1000);
|
|
192
|
-
const rateCurrent = summary.transferSize > 0 ? summary.transferSize / elapsedSeconds : 0;
|
|
193
|
-
const remainingBytes = Math.max(0, totalSize - summary.transferSize);
|
|
194
|
-
const etaSeconds = rateCurrent > 0 ? remainingBytes / rateCurrent : null;
|
|
195
|
-
if (progressBar) {
|
|
196
|
-
progressBar.update(index + 1, {
|
|
197
|
-
bytes: bytes_format(summary.transferSize),
|
|
198
|
-
etaHuman: eta_format(etaSeconds),
|
|
199
|
-
rate: rate_format(rateCurrent),
|
|
200
|
-
});
|
|
193
|
+
chiliLog(chalk.red(`Error uploading ${file.hostPath}: ${error instanceof Error ? error.message : String(error)}`));
|
|
201
194
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
options.onProgress?.({
|
|
196
|
+
operation: "upload",
|
|
197
|
+
kind: "transfer",
|
|
198
|
+
phase: "transferring",
|
|
199
|
+
label: "Uploading files",
|
|
200
|
+
current: index + 1,
|
|
201
|
+
total: fileList.length,
|
|
202
|
+
percent: fileList.length === 0 ? 100 : ((index + 1) / fileList.length) * 100,
|
|
203
|
+
unit: "files",
|
|
204
|
+
status: "running",
|
|
205
|
+
});
|
|
205
206
|
}
|
|
206
207
|
summary.endTime = Date.now();
|
|
207
208
|
summary.duration = (summary.endTime - summary.startTime) / 1000; // seconds
|
|
208
209
|
summary.speed = summary.transferSize / summary.duration; // bytes per second
|
|
210
|
+
options.onProgress?.({
|
|
211
|
+
operation: "upload",
|
|
212
|
+
kind: "transfer",
|
|
213
|
+
phase: summary.failedCount === 0 ? "complete" : "failed",
|
|
214
|
+
label: "Upload complete",
|
|
215
|
+
current: summary.transferredCount,
|
|
216
|
+
total: summary.totalFiles,
|
|
217
|
+
percent: summary.totalFiles === 0 ? 100 : (summary.transferredCount / summary.totalFiles) * 100,
|
|
218
|
+
unit: "files",
|
|
219
|
+
status: summary.failedCount === 0 ? "done" : "error",
|
|
220
|
+
});
|
|
209
221
|
return summary;
|
|
210
222
|
}
|
|
211
223
|
/**
|
package/dist/commands/man/doc.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { projectDir_get, browser_open, asciidoc_render } from "../../man/renderer.js";
|
|
12
|
+
import { chiliErrLog, chiliLog } from "../../screen/output.js";
|
|
12
13
|
/**
|
|
13
14
|
* Displays a documentation page for a specific topic.
|
|
14
15
|
*
|
|
@@ -19,7 +20,7 @@ export async function manPage_display(options) {
|
|
|
19
20
|
const docDir = path.join(projectDir_get(), "doc");
|
|
20
21
|
const docPath = path.join(docDir, `${options.topic}.adoc`);
|
|
21
22
|
if (!fs.existsSync(docPath)) {
|
|
22
|
-
|
|
23
|
+
chiliErrLog(`Documentation for '${options.topic}' not found.`);
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
if (options.browser) {
|
|
@@ -27,6 +28,6 @@ export async function manPage_display(options) {
|
|
|
27
28
|
}
|
|
28
29
|
else {
|
|
29
30
|
const content = fs.readFileSync(docPath, "utf-8");
|
|
30
|
-
|
|
31
|
+
chiliLog(await asciidoc_render(content, options.style, options.width));
|
|
31
32
|
}
|
|
32
33
|
}
|