@fnndsc/chili 3.2.5 → 3.4.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/README.md +18 -11
- package/dist/commands/fs/download.d.ts +19 -4
- package/dist/commands/fs/download.js +71 -62
- package/dist/commands/fs/upload.d.ts +20 -4
- package/dist/commands/fs/upload.js +42 -31
- package/dist/commands/man/doc.js +1 -1
- package/dist/commands/plugin/readme.js +3 -0
- package/dist/filesystem/fileGroupHandler.d.ts +0 -5
- package/dist/filesystem/fileGroupHandler.js +2 -19
- package/dist/handlers/baseGroupHandler.d.ts +10 -8
- package/dist/handlers/baseGroupHandler.js +28 -23
- package/dist/index.d.ts +6 -1
- package/dist/index.js +13 -179
- package/dist/man/renderer.d.ts +1 -1
- package/dist/man/renderer.js +6 -8
- package/dist/path/pathCommand.js +6 -3
- package/dist/plugins/pluginGroupHandler.d.ts +18 -0
- package/dist/plugins/pluginGroupHandler.js +40 -1
- package/dist/run.d.ts +24 -0
- package/dist/run.js +192 -0
- package/dist/screen/screen.d.ts +13 -0
- package/dist/screen/screen.js +44 -0
- package/dist/utils/resourceData.d.ts +16 -0
- package/dist/utils/resourceData.js +23 -0
- package/dist/views/pluginParameters.d.ts +10 -0
- package/dist/views/pluginParameters.js +24 -17
- package/docs/pacs.adoc +3 -3
- package/package.json +6 -8
package/README.md
CHANGED
|
@@ -42,27 +42,34 @@ Designed for developers and power-users who want to script and control a ChRIS i
|
|
|
42
42
|
2. **[`salsa`](https://github.com/FNNDSC/salsa) (Logic)**: Shared Application Logic and Service Assets — high-level business intents.
|
|
43
43
|
3. **[`cumin`](https://github.com/FNNDSC/cumin) (Infrastructure)**: State and connection layer — authentication, context persistence, low-level API.
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Development
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
`chili` lives in the [`mise` monorepo](https://github.com/FNNDSC/mise) alongside
|
|
48
|
+
`cumin`, `salsa`, and `chell`. Build the whole stack from the repository root —
|
|
49
|
+
npm workspaces links the four packages together, so there is nothing to clone or
|
|
50
|
+
hand-link:
|
|
48
51
|
|
|
49
52
|
```bash
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
git clone https://github.com/FNNDSC/mise
|
|
54
|
+
cd mise
|
|
55
|
+
make taco # scrub → prep → cook → taste → serve (the full course)
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
### Individual steps
|
|
58
|
+
### Individual steps (run from the repo root)
|
|
55
59
|
|
|
56
60
|
| Target | Action |
|
|
57
61
|
|--------|--------|
|
|
58
|
-
| `make
|
|
59
|
-
| `make
|
|
60
|
-
| `make
|
|
61
|
-
| `make
|
|
62
|
-
| `make serve` | Link packages globally |
|
|
62
|
+
| `make prep` | `npm install` — install deps and link all four workspaces |
|
|
63
|
+
| `make cook` | Build (compile TypeScript) all packages in dependency order |
|
|
64
|
+
| `make taste` | Run the full test suite |
|
|
65
|
+
| `make serve` | Link `chell` globally |
|
|
63
66
|
| `make scrub` | Clean build artifacts and `node_modules` |
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
To build or test just this package: `npm run build -w @fnndsc/chili` /
|
|
69
|
+
`npm test -w @fnndsc/chili`.
|
|
70
|
+
|
|
71
|
+
> Use [NVM](https://github.com/nvm-sh/nvm) and Node 22.x to avoid needing `sudo`
|
|
72
|
+
> for the global link in `make serve`.
|
|
66
73
|
|
|
67
74
|
## Core Features
|
|
68
75
|
|
|
@@ -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,10 +6,9 @@ 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";
|
|
14
13
|
export { bytes_format };
|
|
15
14
|
/**
|
|
@@ -36,7 +35,7 @@ async function chrisPath_isDirectory(chrisPath) {
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
|
-
* Downloads files from ChRIS
|
|
38
|
+
* Downloads files from ChRIS and optionally emits structured progress events.
|
|
40
39
|
* @param chrisPath - Remote ChRIS path (file or directory).
|
|
41
40
|
* @param localPath - Local filesystem path.
|
|
42
41
|
* @param options - Download options.
|
|
@@ -93,61 +92,66 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
93
92
|
// Ensure parent directory exists
|
|
94
93
|
const parentDir = path.dirname(finalLocalPath);
|
|
95
94
|
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
95
|
let transferred = 0;
|
|
111
96
|
const writeStream = fs.createWriteStream(finalLocalPath);
|
|
112
97
|
stream.on("data", (chunk) => {
|
|
113
98
|
transferred += chunk.length;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
99
|
+
options.onProgress?.({
|
|
100
|
+
operation: "download",
|
|
101
|
+
kind: "transfer",
|
|
102
|
+
phase: "transferring",
|
|
103
|
+
label: `Downloading ${path.basename(finalLocalPath)}`,
|
|
104
|
+
current: transferred,
|
|
105
|
+
total: size,
|
|
106
|
+
percent: typeof size === "number" && size > 0 ? (Math.min(transferred, size) / size) * 100 : undefined,
|
|
107
|
+
unit: "bytes",
|
|
108
|
+
status: "running",
|
|
109
|
+
});
|
|
126
110
|
});
|
|
127
111
|
await pipeline(stream, writeStream);
|
|
128
|
-
if (progressBar) {
|
|
129
|
-
progressBar.update(size || transferred);
|
|
130
|
-
progressBar.stop();
|
|
131
|
-
}
|
|
132
112
|
summary.totalFiles = 1;
|
|
133
113
|
summary.transferredCount = 1;
|
|
134
114
|
summary.transferSize = transferred;
|
|
135
115
|
summary.endTime = Date.now();
|
|
136
116
|
summary.duration = (summary.endTime - summary.startTime) / 1000;
|
|
137
117
|
summary.speed = summary.duration > 0 ? summary.transferSize / summary.duration : 0;
|
|
118
|
+
options.onProgress?.({
|
|
119
|
+
operation: "download",
|
|
120
|
+
kind: "transfer",
|
|
121
|
+
phase: "complete",
|
|
122
|
+
label: "Download complete",
|
|
123
|
+
current: transferred,
|
|
124
|
+
total: size,
|
|
125
|
+
percent: typeof size === "number" && size > 0 ? 100 : undefined,
|
|
126
|
+
unit: "bytes",
|
|
127
|
+
status: "done",
|
|
128
|
+
});
|
|
138
129
|
return summary;
|
|
139
130
|
}
|
|
140
131
|
catch (error) {
|
|
141
132
|
summary.failedCount = 1;
|
|
133
|
+
options.onProgress?.({
|
|
134
|
+
operation: "download",
|
|
135
|
+
kind: "transfer",
|
|
136
|
+
phase: "failed",
|
|
137
|
+
label: "Download failed",
|
|
138
|
+
status: "error",
|
|
139
|
+
});
|
|
142
140
|
throw error;
|
|
143
141
|
}
|
|
144
142
|
}
|
|
145
143
|
// Download directory
|
|
146
144
|
console.log(chalk.cyan("Scanning files to download..."));
|
|
145
|
+
options.onProgress?.({
|
|
146
|
+
operation: "download",
|
|
147
|
+
kind: "transfer",
|
|
148
|
+
phase: "scanning",
|
|
149
|
+
label: "Scanning files to download",
|
|
150
|
+
status: "running",
|
|
151
|
+
});
|
|
147
152
|
const items = await files_listRecursive(resolvedChris);
|
|
148
153
|
const files = items.filter((item) => item.type === 'file');
|
|
149
154
|
summary.totalFiles = files.length;
|
|
150
|
-
const totalSize = files.reduce((sum, f) => sum + (f.size || 0), 0);
|
|
151
155
|
// Apply Unix semantics: trailing slash determines merge behavior
|
|
152
156
|
const targetDir = resolvedChris.endsWith('/')
|
|
153
157
|
? localPath // Merge contents
|
|
@@ -169,20 +173,17 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
169
173
|
}
|
|
170
174
|
// Ensure target directory exists
|
|
171
175
|
await directory_ensureExists(targetDir);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
rate: "--",
|
|
184
|
-
});
|
|
185
|
-
}
|
|
176
|
+
options.onProgress?.({
|
|
177
|
+
operation: "download",
|
|
178
|
+
kind: "transfer",
|
|
179
|
+
phase: "transferring",
|
|
180
|
+
label: "Downloading files",
|
|
181
|
+
current: 0,
|
|
182
|
+
total: files.length,
|
|
183
|
+
percent: files.length === 0 ? 100 : 0,
|
|
184
|
+
unit: "files",
|
|
185
|
+
status: "running",
|
|
186
|
+
});
|
|
186
187
|
// Download each file
|
|
187
188
|
for (const [index, file] of files.entries()) {
|
|
188
189
|
try {
|
|
@@ -213,23 +214,31 @@ export async function files_downloadWithProgress(chrisPath, localPath, options =
|
|
|
213
214
|
const msg = error instanceof Error ? error.message : String(error);
|
|
214
215
|
console.log(chalk.red(`\nError downloading ${file.path}: ${msg}`));
|
|
215
216
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (progressBar) {
|
|
229
|
-
progressBar.stop();
|
|
217
|
+
options.onProgress?.({
|
|
218
|
+
operation: "download",
|
|
219
|
+
kind: "transfer",
|
|
220
|
+
phase: "transferring",
|
|
221
|
+
label: "Downloading files",
|
|
222
|
+
current: index + 1,
|
|
223
|
+
total: files.length,
|
|
224
|
+
percent: files.length === 0 ? 100 : ((index + 1) / files.length) * 100,
|
|
225
|
+
unit: "files",
|
|
226
|
+
status: "running",
|
|
227
|
+
});
|
|
230
228
|
}
|
|
231
229
|
summary.endTime = Date.now();
|
|
232
230
|
summary.duration = (summary.endTime - summary.startTime) / 1000;
|
|
233
231
|
summary.speed = summary.duration > 0 ? summary.transferSize / summary.duration : 0;
|
|
232
|
+
options.onProgress?.({
|
|
233
|
+
operation: "download",
|
|
234
|
+
kind: "transfer",
|
|
235
|
+
phase: summary.failedCount === 0 ? "complete" : "failed",
|
|
236
|
+
label: "Download complete",
|
|
237
|
+
current: summary.transferredCount,
|
|
238
|
+
total: summary.totalFiles,
|
|
239
|
+
percent: summary.totalFiles === 0 ? 100 : (summary.transferredCount / summary.totalFiles) * 100,
|
|
240
|
+
unit: "files",
|
|
241
|
+
status: summary.failedCount === 0 ? "done" : "error",
|
|
242
|
+
});
|
|
234
243
|
return summary;
|
|
235
244
|
}
|
|
@@ -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,7 +7,6 @@ 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";
|
|
13
12
|
/**
|
|
@@ -101,17 +100,24 @@ async function localFiles_scan(localPath, remotePath) {
|
|
|
101
100
|
return files;
|
|
102
101
|
}
|
|
103
102
|
/**
|
|
104
|
-
* Uploads files
|
|
103
|
+
* Uploads files and optionally emits structured progress events.
|
|
105
104
|
* @param localPath - Local path (file or directory).
|
|
106
105
|
* @param remotePath - Remote ChRIS path.
|
|
106
|
+
* @param options - Upload options, including an optional progress callback.
|
|
107
107
|
* @returns Promise<UploadSummary> with upload statistics.
|
|
108
108
|
*/
|
|
109
109
|
export async function files_uploadWithProgress(localPath, remotePath, options = {}) {
|
|
110
110
|
const resolvedRemote = await path_resolveChrisFs(remotePath, {});
|
|
111
111
|
// Scan files
|
|
112
112
|
console.log(chalk.cyan("Scanning files to upload..."));
|
|
113
|
+
options.onProgress?.({
|
|
114
|
+
operation: "upload",
|
|
115
|
+
kind: "transfer",
|
|
116
|
+
phase: "scanning",
|
|
117
|
+
label: "Scanning files to upload",
|
|
118
|
+
status: "running",
|
|
119
|
+
});
|
|
113
120
|
const fileList = await localFiles_scan(localPath, resolvedRemote);
|
|
114
|
-
const totalSize = fileList.reduce((sum, f) => sum + f.size, 0);
|
|
115
121
|
// Determine actual target path (where files will be uploaded)
|
|
116
122
|
const stats = await fs.promises.stat(localPath);
|
|
117
123
|
let actualTarget = resolvedRemote;
|
|
@@ -140,20 +146,6 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
140
146
|
else {
|
|
141
147
|
// 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
148
|
}
|
|
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
149
|
// Upload files
|
|
158
150
|
const summary = {
|
|
159
151
|
startTime: Date.now(),
|
|
@@ -166,6 +158,17 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
166
158
|
speed: 0,
|
|
167
159
|
actualTargetPath: actualTarget,
|
|
168
160
|
};
|
|
161
|
+
options.onProgress?.({
|
|
162
|
+
operation: "upload",
|
|
163
|
+
kind: "transfer",
|
|
164
|
+
phase: "transferring",
|
|
165
|
+
label: "Uploading files",
|
|
166
|
+
current: 0,
|
|
167
|
+
total: fileList.length,
|
|
168
|
+
percent: fileList.length === 0 ? 100 : 0,
|
|
169
|
+
unit: "files",
|
|
170
|
+
status: "running",
|
|
171
|
+
});
|
|
169
172
|
for (const [index, file] of fileList.entries()) {
|
|
170
173
|
try {
|
|
171
174
|
const fileContent = await fs.promises.readFile(file.hostPath);
|
|
@@ -188,24 +191,32 @@ export async function files_uploadWithProgress(localPath, remotePath, options =
|
|
|
188
191
|
summary.failedCount++;
|
|
189
192
|
console.log(chalk.red(`Error uploading ${file.hostPath}: ${error instanceof Error ? error.message : String(error)}`));
|
|
190
193
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (progressBar) {
|
|
204
|
-
progressBar.stop();
|
|
194
|
+
options.onProgress?.({
|
|
195
|
+
operation: "upload",
|
|
196
|
+
kind: "transfer",
|
|
197
|
+
phase: "transferring",
|
|
198
|
+
label: "Uploading files",
|
|
199
|
+
current: index + 1,
|
|
200
|
+
total: fileList.length,
|
|
201
|
+
percent: fileList.length === 0 ? 100 : ((index + 1) / fileList.length) * 100,
|
|
202
|
+
unit: "files",
|
|
203
|
+
status: "running",
|
|
204
|
+
});
|
|
205
205
|
}
|
|
206
206
|
summary.endTime = Date.now();
|
|
207
207
|
summary.duration = (summary.endTime - summary.startTime) / 1000; // seconds
|
|
208
208
|
summary.speed = summary.transferSize / summary.duration; // bytes per second
|
|
209
|
+
options.onProgress?.({
|
|
210
|
+
operation: "upload",
|
|
211
|
+
kind: "transfer",
|
|
212
|
+
phase: summary.failedCount === 0 ? "complete" : "failed",
|
|
213
|
+
label: "Upload complete",
|
|
214
|
+
current: summary.transferredCount,
|
|
215
|
+
total: summary.totalFiles,
|
|
216
|
+
percent: summary.totalFiles === 0 ? 100 : (summary.transferredCount / summary.totalFiles) * 100,
|
|
217
|
+
unit: "files",
|
|
218
|
+
status: summary.failedCount === 0 ? "done" : "error",
|
|
219
|
+
});
|
|
209
220
|
return summary;
|
|
210
221
|
}
|
|
211
222
|
/**
|
package/dist/commands/man/doc.js
CHANGED
|
@@ -11,6 +11,9 @@ import { marked } from "marked";
|
|
|
11
11
|
import TerminalRenderer from "marked-terminal";
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
marked.setOptions({
|
|
14
|
+
// @ts-expect-error marked-terminal@6 ships .d.ts that drift from marked@9's
|
|
15
|
+
// Renderer type at this runtime-compatible version pair. (Tracked: align the
|
|
16
|
+
// marked / marked-terminal versions across chili & chell.)
|
|
14
17
|
renderer: new TerminalRenderer({
|
|
15
18
|
code: chalk.yellow,
|
|
16
19
|
blockquote: chalk.gray.italic,
|
|
@@ -25,11 +25,6 @@ export declare class FileGroupHandler {
|
|
|
25
25
|
* @returns A Promise resolving to a new FileGroupHandler instance.
|
|
26
26
|
*/
|
|
27
27
|
static handler_create(assetName: string, path?: string): Promise<FileGroupHandler>;
|
|
28
|
-
/**
|
|
29
|
-
* Removes duplicate column headers from FilteredResourceData results.
|
|
30
|
-
* Copied from BaseGroupHandler to support local listing logic.
|
|
31
|
-
*/
|
|
32
|
-
private columns_removeDuplicates;
|
|
33
28
|
/**
|
|
34
29
|
* Lists files, links, or directories using the new command logic.
|
|
35
30
|
*/
|
|
@@ -4,6 +4,7 @@ import { path_resolveChrisFs } from "../utils/cli.js";
|
|
|
4
4
|
import { FileController } from "../controllers/fileController.js";
|
|
5
5
|
import { files_create } from "../commands/fs/create.js";
|
|
6
6
|
import { errorStack } from "@fnndsc/cumin";
|
|
7
|
+
import { resourceColumns_removeDuplicates } from "../utils/resourceData.js";
|
|
7
8
|
import { table_display } from "../screen/screen.js";
|
|
8
9
|
import { files_fetchList } from "../commands/files/list.js";
|
|
9
10
|
import { fileFields_fetch } from "../commands/files/fields.js";
|
|
@@ -41,24 +42,6 @@ export class FileGroupHandler {
|
|
|
41
42
|
throw new Error(`Failed to initialize FileGroupHandler for ${assetName}: ${errorMessage}`);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Removes duplicate column headers from FilteredResourceData results.
|
|
46
|
-
* Copied from BaseGroupHandler to support local listing logic.
|
|
47
|
-
*/
|
|
48
|
-
columns_removeDuplicates(results) {
|
|
49
|
-
const uniqueHeaders = Array.from(new Set(results.selectedFields));
|
|
50
|
-
const uniqueTableData = results.tableData.map((row) => uniqueHeaders.reduce((acc, header) => {
|
|
51
|
-
if (typeof header === "string" && header in row) {
|
|
52
|
-
acc[header] = row[header];
|
|
53
|
-
}
|
|
54
|
-
return acc;
|
|
55
|
-
}, {}));
|
|
56
|
-
return {
|
|
57
|
-
...results,
|
|
58
|
-
selectedFields: uniqueHeaders,
|
|
59
|
-
tableData: uniqueTableData,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
45
|
/**
|
|
63
46
|
* Lists files, links, or directories using the new command logic.
|
|
64
47
|
*/
|
|
@@ -73,7 +56,7 @@ export class FileGroupHandler {
|
|
|
73
56
|
console.log(`No ${this.assetName} found matching the criteria.`);
|
|
74
57
|
}
|
|
75
58
|
else {
|
|
76
|
-
const uniqueResults =
|
|
59
|
+
const uniqueResults = resourceColumns_removeDuplicates(results);
|
|
77
60
|
// cumin returns dynamic table rows (Record<string, unknown>[]);
|
|
78
61
|
// narrow to the FileResource view model at this boundary.
|
|
79
62
|
console.log(fileList_render(uniqueResults.tableData, uniqueResults.selectedFields, { table: options.table, csv: options.csv }));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import { ChRISPluginGroup, ChRISFeedGroup, ChRISEmbeddedResourceGroup, ChRISPACSGroup } from "@fnndsc/cumin";
|
|
7
|
+
import { ChRISPluginGroup, ChRISFeedGroup, ChRISEmbeddedResourceGroup, ChRISPACSGroup, type CommandEnvelope } from "@fnndsc/cumin";
|
|
8
8
|
import { CLIoptions } from "../utils/cli.js";
|
|
9
9
|
import { TableOptions } from "../screen/screen.js";
|
|
10
10
|
/**
|
|
@@ -16,13 +16,6 @@ export declare class BaseGroupHandler {
|
|
|
16
16
|
displayOptions: TableOptions;
|
|
17
17
|
chrisObject: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup;
|
|
18
18
|
constructor(assetName: string, chrisObject: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup);
|
|
19
|
-
/**
|
|
20
|
-
* Removes duplicate column headers from FilteredResourceData results.
|
|
21
|
-
*
|
|
22
|
-
* @param results - The FilteredResourceData to process.
|
|
23
|
-
* @returns FilteredResourceData with unique headers and corresponding data.
|
|
24
|
-
*/
|
|
25
|
-
private columns_removeDuplicates;
|
|
26
19
|
/**
|
|
27
20
|
* Lists ChRIS resources based on provided CLI options.
|
|
28
21
|
*
|
|
@@ -33,6 +26,15 @@ export declare class BaseGroupHandler {
|
|
|
33
26
|
* Lists available fields for the current ChRIS resource type.
|
|
34
27
|
*/
|
|
35
28
|
resourceFields_list(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Renders available fields for the current ChRIS resource type as an envelope.
|
|
31
|
+
*
|
|
32
|
+
* Sans-I/O counterpart to {@link resourceFields_list} — returns the rendered
|
|
33
|
+
* text instead of printing it, so hosted surfaces can carry it over the wire.
|
|
34
|
+
*
|
|
35
|
+
* @returns An envelope carrying the field listing or an error.
|
|
36
|
+
*/
|
|
37
|
+
resourceFields_render(): Promise<CommandEnvelope>;
|
|
36
38
|
/**
|
|
37
39
|
* Prompts the user for confirmation before performing an operation.
|
|
38
40
|
*
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import { ChRISFeedGroup, record_extract, errorStack, } from "@fnndsc/cumin";
|
|
7
|
+
import { ChRISFeedGroup, record_extract, errorStack, envelope_ok, envelope_error, } from "@fnndsc/cumin";
|
|
8
8
|
import { options_toParams } from "../utils/cli.js";
|
|
9
|
-
import {
|
|
9
|
+
import { resourceColumns_removeDuplicates } from "../utils/resourceData.js";
|
|
10
|
+
import { table_display, table_render, border_draw } from "../screen/screen.js";
|
|
10
11
|
import * as readline from "readline";
|
|
11
12
|
/**
|
|
12
13
|
* Base handler for groups of ChRIS resources.
|
|
@@ -21,26 +22,6 @@ export class BaseGroupHandler {
|
|
|
21
22
|
title: { title: this.assetName, justification: "center" },
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Removes duplicate column headers from FilteredResourceData results.
|
|
26
|
-
*
|
|
27
|
-
* @param results - The FilteredResourceData to process.
|
|
28
|
-
* @returns FilteredResourceData with unique headers and corresponding data.
|
|
29
|
-
*/
|
|
30
|
-
columns_removeDuplicates(results) {
|
|
31
|
-
const uniqueHeaders = Array.from(new Set(results.selectedFields));
|
|
32
|
-
const uniqueTableData = results.tableData.map((row) => uniqueHeaders.reduce((acc, header) => {
|
|
33
|
-
if (typeof header === "string" && header in row) {
|
|
34
|
-
acc[header] = row[header];
|
|
35
|
-
}
|
|
36
|
-
return acc;
|
|
37
|
-
}, {}));
|
|
38
|
-
return {
|
|
39
|
-
...results,
|
|
40
|
-
selectedFields: uniqueHeaders,
|
|
41
|
-
tableData: uniqueTableData,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
25
|
/**
|
|
45
26
|
* Lists ChRIS resources based on provided CLI options.
|
|
46
27
|
*
|
|
@@ -77,7 +58,7 @@ export class BaseGroupHandler {
|
|
|
77
58
|
console.log(`No ${this.assetName} found matching the criteria.`);
|
|
78
59
|
}
|
|
79
60
|
else {
|
|
80
|
-
const uniqueResults =
|
|
61
|
+
const uniqueResults = resourceColumns_removeDuplicates(results);
|
|
81
62
|
// Construct column options based on parsed widths
|
|
82
63
|
const columns = uniqueResults.selectedFields.map(field => {
|
|
83
64
|
const width = columnWidths[field];
|
|
@@ -147,6 +128,30 @@ export class BaseGroupHandler {
|
|
|
147
128
|
console.log(errorStack.stack_search(this.assetName)[0]);
|
|
148
129
|
}
|
|
149
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Renders available fields for the current ChRIS resource type as an envelope.
|
|
133
|
+
*
|
|
134
|
+
* Sans-I/O counterpart to {@link resourceFields_list} — returns the rendered
|
|
135
|
+
* text instead of printing it, so hosted surfaces can carry it over the wire.
|
|
136
|
+
*
|
|
137
|
+
* @returns An envelope carrying the field listing or an error.
|
|
138
|
+
*/
|
|
139
|
+
async resourceFields_render() {
|
|
140
|
+
try {
|
|
141
|
+
const results = await this.chrisObject.asset.resourceFields_get();
|
|
142
|
+
if (!results) {
|
|
143
|
+
return envelope_error('', undefined, `An error occurred while fetching resource fields for ${this.assetName}.\n`);
|
|
144
|
+
}
|
|
145
|
+
if (results.fields.length === 0) {
|
|
146
|
+
return envelope_ok(`No resource fields found for ${this.assetName}.\n`);
|
|
147
|
+
}
|
|
148
|
+
return envelope_ok(table_render(results.fields, ["fields"]));
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const stacked = errorStack.stack_search(this.assetName)[0];
|
|
152
|
+
return envelope_error('', undefined, `${stacked ?? String(error)}\n`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
150
155
|
/**
|
|
151
156
|
* Prompts the user for confirmation before performing an operation.
|
|
152
157
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @file Command-line entry point for the chili (ChILI) CLI.
|
|
4
4
|
*
|
|
5
|
+
* This is a thin wrapper: all command wiring and execution live in {@link run}
|
|
6
|
+
* (./run.js) so the same logic can be driven in-process by a host such as chell
|
|
7
|
+
* without spawning a separate `node` subprocess. This file only adds the
|
|
8
|
+
* bin-only concerns — shell completion and fatal-exit handling.
|
|
9
|
+
*
|
|
5
10
|
* @module
|
|
6
11
|
*/
|
|
7
|
-
export { logical_toPhysical } from
|
|
12
|
+
export { logical_toPhysical } from "./utils/cli.js";
|