@fnndsc/chili 3.4.0 → 3.6.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.js +4 -3
- package/dist/commands/fs/upload.js +4 -3
- 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.d.ts +18 -5
- package/dist/filesystem/fileGroupHandler.js +68 -46
- package/dist/filesystem/filesystemHandler.js +13 -11
- package/dist/filesystem/inodeCommand.js +3 -2
- package/dist/handlers/baseGroupHandler.d.ts +8 -2
- package/dist/handlers/baseGroupHandler.js +30 -18
- 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 +17 -6
- package/dist/plugins/pluginGroupHandler.js +58 -37
- package/dist/plugins/pluginHandler.js +23 -22
- package/dist/run.d.ts +22 -0
- package/dist/run.js +42 -46
- package/dist/screen/output.d.ts +88 -0
- package/dist/screen/output.js +95 -0
- package/dist/screen/screen.js +9 -8
- package/dist/utils/admin_prompt.js +10 -9
- package/dist/utils/docker.js +10 -9
- package/dist/views/pluginParameters.js +2 -1
- package/package.json +1 -1
|
@@ -9,12 +9,13 @@ import { options_toParams } from "../utils/cli.js";
|
|
|
9
9
|
import { resourceColumns_removeDuplicates } from "../utils/resourceData.js";
|
|
10
10
|
import { table_display, table_render, border_draw } from "../screen/screen.js";
|
|
11
11
|
import * as readline from "readline";
|
|
12
|
+
import { chiliErrLog, chiliLog } from "../screen/output.js";
|
|
12
13
|
/**
|
|
13
14
|
* Base handler for groups of ChRIS resources.
|
|
14
15
|
* Provides common functionality for listing and deleting resources.
|
|
15
16
|
*/
|
|
16
17
|
export class BaseGroupHandler {
|
|
17
|
-
constructor(assetName, chrisObject) {
|
|
18
|
+
constructor(assetName, chrisObject = null) {
|
|
18
19
|
this.assetName = "";
|
|
19
20
|
this.assetName = assetName;
|
|
20
21
|
this.chrisObject = chrisObject;
|
|
@@ -22,6 +23,17 @@ export class BaseGroupHandler {
|
|
|
22
23
|
title: { title: this.assetName, justification: "center" },
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The resolved ChRIS context, for use inside actions. Throws if read before
|
|
28
|
+
* the context has been set — a lazy handler must resolve its context before
|
|
29
|
+
* an action dereferences it.
|
|
30
|
+
*/
|
|
31
|
+
get chrisObjectResolved() {
|
|
32
|
+
if (!this.chrisObject) {
|
|
33
|
+
throw new Error(`ChRIS context for '${this.assetName}' is not available in the current context.`);
|
|
34
|
+
}
|
|
35
|
+
return this.chrisObject;
|
|
36
|
+
}
|
|
25
37
|
/**
|
|
26
38
|
* Lists ChRIS resources based on provided CLI options.
|
|
27
39
|
*
|
|
@@ -49,13 +61,13 @@ export class BaseGroupHandler {
|
|
|
49
61
|
options.fields = cleanFields.join(',');
|
|
50
62
|
}
|
|
51
63
|
const params = options_toParams(options);
|
|
52
|
-
const results = await this.
|
|
64
|
+
const results = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions(params);
|
|
53
65
|
if (!results) {
|
|
54
|
-
|
|
66
|
+
chiliErrLog(`No ${this.assetName} resources found. Perhaps check your current context?`);
|
|
55
67
|
return;
|
|
56
68
|
}
|
|
57
69
|
if (results.tableData.length === 0) {
|
|
58
|
-
|
|
70
|
+
chiliLog(`No ${this.assetName} found matching the criteria.`);
|
|
59
71
|
}
|
|
60
72
|
else {
|
|
61
73
|
const uniqueResults = resourceColumns_removeDuplicates(results);
|
|
@@ -73,7 +85,7 @@ export class BaseGroupHandler {
|
|
|
73
85
|
return `"${String(val !== undefined ? val : '').split('"').join('""')}"`;
|
|
74
86
|
}).join(',');
|
|
75
87
|
}).join('\n');
|
|
76
|
-
|
|
88
|
+
chiliLog(header + '\n' + rows);
|
|
77
89
|
}
|
|
78
90
|
else if (options.table) {
|
|
79
91
|
// Explicit Table format (with borders and title)
|
|
@@ -104,7 +116,7 @@ export class BaseGroupHandler {
|
|
|
104
116
|
}
|
|
105
117
|
}
|
|
106
118
|
catch (error) {
|
|
107
|
-
|
|
119
|
+
chiliLog(errorStack.stack_search(this.assetName)[0]);
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
/**
|
|
@@ -112,20 +124,20 @@ export class BaseGroupHandler {
|
|
|
112
124
|
*/
|
|
113
125
|
async resourceFields_list() {
|
|
114
126
|
try {
|
|
115
|
-
const results = await this.
|
|
127
|
+
const results = await this.chrisObjectResolved.asset.resourceFields_get();
|
|
116
128
|
if (!results) {
|
|
117
|
-
|
|
129
|
+
chiliErrLog(`An error occurred while fetching resource fields for ${this.assetName}.`);
|
|
118
130
|
return;
|
|
119
131
|
}
|
|
120
132
|
if (results.fields.length === 0) {
|
|
121
|
-
|
|
133
|
+
chiliLog(`No resource fields found for ${this.assetName}.`);
|
|
122
134
|
}
|
|
123
135
|
else {
|
|
124
136
|
table_display(results.fields, ["fields"]);
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
139
|
catch (error) {
|
|
128
|
-
|
|
140
|
+
chiliLog(errorStack.stack_search(this.assetName)[0]);
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
/**
|
|
@@ -138,7 +150,7 @@ export class BaseGroupHandler {
|
|
|
138
150
|
*/
|
|
139
151
|
async resourceFields_render() {
|
|
140
152
|
try {
|
|
141
|
-
const results = await this.
|
|
153
|
+
const results = await this.chrisObjectResolved.asset.resourceFields_get();
|
|
142
154
|
if (!results) {
|
|
143
155
|
return envelope_error('', undefined, `An error occurred while fetching resource fields for ${this.assetName}.\n`);
|
|
144
156
|
}
|
|
@@ -240,21 +252,21 @@ export class BaseGroupHandler {
|
|
|
240
252
|
let OKorNot = "";
|
|
241
253
|
for (const id of IDs) {
|
|
242
254
|
try {
|
|
243
|
-
const searchResults = await this.
|
|
255
|
+
const searchResults = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions({
|
|
244
256
|
id: id,
|
|
245
257
|
});
|
|
246
|
-
|
|
258
|
+
chiliLog(border_draw(`checking ${this.assetName} id ${id} ... ${this.msg_OKorNot(searchResults)}`));
|
|
247
259
|
if (!force) {
|
|
248
260
|
confirm = await this.user_confirmContinuation(id, "delete");
|
|
249
261
|
if (!confirm) {
|
|
250
262
|
continue;
|
|
251
263
|
}
|
|
252
264
|
}
|
|
253
|
-
delop = await this.
|
|
265
|
+
delop = await this.chrisObjectResolved.asset.resourceItem_delete(id);
|
|
254
266
|
border_draw(`deleting ${this.assetName} id ${id} ... ${this.msg_OKorNot(true)}`);
|
|
255
267
|
}
|
|
256
268
|
catch (error) {
|
|
257
|
-
|
|
269
|
+
chiliErrLog(`${error}`);
|
|
258
270
|
return false;
|
|
259
271
|
}
|
|
260
272
|
}
|
|
@@ -268,7 +280,7 @@ export class BaseGroupHandler {
|
|
|
268
280
|
*/
|
|
269
281
|
async IDs_getFromSearch(options) {
|
|
270
282
|
const params = options_toParams(options);
|
|
271
|
-
const searchResults = await this.
|
|
283
|
+
const searchResults = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions(params);
|
|
272
284
|
if (!searchResults) {
|
|
273
285
|
return null;
|
|
274
286
|
}
|
|
@@ -284,7 +296,7 @@ export class BaseGroupHandler {
|
|
|
284
296
|
let nIDs;
|
|
285
297
|
nIDs = await this.IDs_getFromSearch(options);
|
|
286
298
|
if (!nIDs) {
|
|
287
|
-
|
|
299
|
+
chiliErrLog(`No ${this.assetName} matched the search criteria.`);
|
|
288
300
|
return;
|
|
289
301
|
}
|
|
290
302
|
if (nIDs) {
|
|
@@ -334,7 +346,7 @@ export class BaseGroupHandler {
|
|
|
334
346
|
.option("-f, --force", `force the deletion without prompting for user confirmation`)
|
|
335
347
|
.action(async (searchable, options) => {
|
|
336
348
|
const searchParts = searchable.split("++").map((part) => part.trim());
|
|
337
|
-
//
|
|
349
|
+
// chiliLog(`searchParts = ${searchParts}`);
|
|
338
350
|
for (const searchPart of searchParts) {
|
|
339
351
|
const currentOptions = {
|
|
340
352
|
...options,
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import omelette from "omelette";
|
|
13
13
|
import { run } from "./run.js";
|
|
14
|
+
import { chiliErrLog, chiliLog } from "./screen/output.js";
|
|
14
15
|
/**
|
|
15
16
|
* Sets up shell command completion for the standalone chili CLI using omelette.
|
|
16
17
|
* This is a bin-only concern; in-process hosts provide their own completion.
|
|
@@ -50,7 +51,7 @@ function commandCompletion_setup() {
|
|
|
50
51
|
},
|
|
51
52
|
});
|
|
52
53
|
completion.on("man.doc", ({ reply }) => {
|
|
53
|
-
|
|
54
|
+
chiliLog("Autocomplete triggered for man doc command");
|
|
54
55
|
reply([]);
|
|
55
56
|
});
|
|
56
57
|
completion.init();
|
|
@@ -66,7 +67,7 @@ async function main() {
|
|
|
66
67
|
await run(process.argv.slice(2));
|
|
67
68
|
}
|
|
68
69
|
main().catch((error) => {
|
|
69
|
-
|
|
70
|
+
chiliErrLog("An error occurred:", error);
|
|
70
71
|
process.exit(1);
|
|
71
72
|
});
|
|
72
73
|
// Re-export library utilities for consumers that import the package root.
|
package/dist/lfs/lfs.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
+
import { table_render } from "../screen/screen.js";
|
|
12
|
+
import { chiliErrLog, chiliLog } from "../screen/output.js";
|
|
11
13
|
/**
|
|
12
14
|
* Lists the contents of a directory with details.
|
|
13
15
|
*
|
|
@@ -22,10 +24,10 @@ export async function dirContents_list(filepath) {
|
|
|
22
24
|
return { filename: file, "size(KB)": size, created_at: birthtime };
|
|
23
25
|
});
|
|
24
26
|
const detailedFiles = await Promise.all(detailedFilesPromises);
|
|
25
|
-
|
|
27
|
+
chiliLog(table_render(detailedFiles, ["filename", "size(KB)", "created_at"]));
|
|
26
28
|
}
|
|
27
29
|
catch (error) {
|
|
28
|
-
|
|
30
|
+
chiliErrLog("Error occurred while reading the directory!", error);
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
@@ -36,7 +38,7 @@ export async function dirContents_list(filepath) {
|
|
|
36
38
|
export function dir_create(filepath) {
|
|
37
39
|
if (!fs.existsSync(filepath)) {
|
|
38
40
|
fs.mkdirSync(filepath);
|
|
39
|
-
|
|
41
|
+
chiliLog("The directory has been created successfully");
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
@@ -46,7 +48,7 @@ export function dir_create(filepath) {
|
|
|
46
48
|
*/
|
|
47
49
|
export function file_create(filepath) {
|
|
48
50
|
fs.openSync(filepath, "w");
|
|
49
|
-
|
|
51
|
+
chiliLog("An empty file has been created");
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Sets up the 'lfs' command group in Commander.
|
package/dist/man/man.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { topics_list } from "../commands/man/topics.js";
|
|
2
2
|
import { manPage_display } from "../commands/man/doc.js";
|
|
3
|
+
import { chiliLog } from "../screen/output.js";
|
|
3
4
|
/**
|
|
4
5
|
* Sets up the 'man' command for displaying ChILI manual and help pages.
|
|
5
6
|
*
|
|
@@ -24,8 +25,8 @@ export function manCommand_setup(program) {
|
|
|
24
25
|
.description("List the available manual page topics")
|
|
25
26
|
.action(async () => {
|
|
26
27
|
const files = await topics_list();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
chiliLog("\n\nThe following topics are available:");
|
|
29
|
+
chiliLog("(read more with 'chili man doc <topic>')\n");
|
|
30
|
+
chiliLog(files.join("\n"));
|
|
30
31
|
});
|
|
31
32
|
}
|
package/dist/man/renderer.js
CHANGED
|
@@ -9,6 +9,7 @@ import chalk from "chalk";
|
|
|
9
9
|
import { convert as adoc_convert } from "asciidoctor";
|
|
10
10
|
import { exec } from "child_process";
|
|
11
11
|
import os from "os";
|
|
12
|
+
import { chiliErrLog } from "../screen/output.js";
|
|
12
13
|
const headingStyles = [
|
|
13
14
|
{ regex: /<h1.*?>(.*?)<\/h1>/g, font: "Standard", color: chalk.magenta },
|
|
14
15
|
{ regex: /<h2.*?>(.*?)<\/h2>/g, font: "Slant", color: chalk.yellow },
|
|
@@ -175,11 +176,11 @@ export async function browser_open(filePath) {
|
|
|
175
176
|
: "xdg-open";
|
|
176
177
|
exec(`${command} ${tempHtmlPath}`, (error) => {
|
|
177
178
|
if (error) {
|
|
178
|
-
|
|
179
|
+
chiliErrLog("Error opening browser:", error);
|
|
179
180
|
}
|
|
180
181
|
});
|
|
181
182
|
}
|
|
182
183
|
catch (error) {
|
|
183
|
-
|
|
184
|
+
chiliErrLog("Error opening documentation in browser:", error instanceof Error ? error.message : String(error));
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -9,6 +9,7 @@ import { BaseGroupHandler } from "../handlers/baseGroupHandler.js";
|
|
|
9
9
|
import { border_draw } from "../screen/screen.js";
|
|
10
10
|
import { pacsQueryResult_renderPretty } from "./pacsResultRender.js";
|
|
11
11
|
import { pacsQueryPayload_build } from "./pacsQueryPayload.js";
|
|
12
|
+
import { chiliLog } from "../screen/output.js";
|
|
12
13
|
/**
|
|
13
14
|
* Handler for PACS queries commands.
|
|
14
15
|
*/
|
|
@@ -69,7 +70,7 @@ export class PACSQueryGroupHandler {
|
|
|
69
70
|
const listCommand = this.baseGroupHandler.baseListCommand_create(async (options) => {
|
|
70
71
|
const pacsserver = await this.pacsserverContext_resolve(options.pacsserver);
|
|
71
72
|
if (!pacsserver) {
|
|
72
|
-
|
|
73
|
+
chiliLog(border_draw("No PACS server in context. Use --pacsserver or set via context."));
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
const mergedOptions = this.options_withPACSFilter(options, pacsserver);
|
|
@@ -111,17 +112,17 @@ export class PACSQueryGroupHandler {
|
|
|
111
112
|
}
|
|
112
113
|
const payload = pacsQueryPayload_build(queryInput, options.title, options.description);
|
|
113
114
|
if (!payload) {
|
|
114
|
-
|
|
115
|
+
chiliLog(border_draw("Invalid query format. Provide JSON or comma-separated key:value pairs."));
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
const result = await pacsQueries_create(pacsserver, payload);
|
|
118
119
|
if (!result.ok) {
|
|
119
120
|
const errors = errorStack_getAllOfType("error");
|
|
120
121
|
if (errors.length) {
|
|
121
|
-
errors.forEach((msg) =>
|
|
122
|
+
errors.forEach((msg) => chiliLog(border_draw(msg)));
|
|
122
123
|
}
|
|
123
124
|
else {
|
|
124
|
-
|
|
125
|
+
chiliLog(border_draw("Failed to create PACS query."));
|
|
125
126
|
}
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
@@ -133,7 +134,7 @@ export class PACSQueryGroupHandler {
|
|
|
133
134
|
`pacs=${pacsserver}`,
|
|
134
135
|
`title="${created.title ?? options.title ?? ""}"`,
|
|
135
136
|
].join(" ");
|
|
136
|
-
|
|
137
|
+
chiliLog(border_draw(msg.trim()));
|
|
137
138
|
});
|
|
138
139
|
}
|
|
139
140
|
/**
|
|
@@ -149,17 +150,17 @@ export class PACSQueryGroupHandler {
|
|
|
149
150
|
.action(async (queryId, options) => {
|
|
150
151
|
const idNum = Number(queryId);
|
|
151
152
|
if (Number.isNaN(idNum)) {
|
|
152
|
-
|
|
153
|
+
chiliLog(border_draw("queryId must be a number."));
|
|
153
154
|
return;
|
|
154
155
|
}
|
|
155
156
|
const result = await pacsQuery_resultDecode(idNum);
|
|
156
157
|
if (!result.ok) {
|
|
157
158
|
const errors = errorStack_getAllOfType("error");
|
|
158
159
|
if (errors.length) {
|
|
159
|
-
errors.forEach((msg) =>
|
|
160
|
+
errors.forEach((msg) => chiliLog(border_draw(msg)));
|
|
160
161
|
}
|
|
161
162
|
else {
|
|
162
|
-
|
|
163
|
+
chiliLog(border_draw(`Failed to decode PACS query result for ${idNum}.`));
|
|
163
164
|
}
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
@@ -167,23 +168,23 @@ export class PACSQueryGroupHandler {
|
|
|
167
168
|
// Prefer JSON, then text, else indicate base64 length
|
|
168
169
|
if (decoded.json !== undefined) {
|
|
169
170
|
if (options.raw) {
|
|
170
|
-
|
|
171
|
+
chiliLog(border_draw(JSON.stringify(decoded.json, null, 2)));
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
173
174
|
const pretty = this.pacsResult_renderPretty(decoded.json);
|
|
174
175
|
if (pretty) {
|
|
175
|
-
|
|
176
|
+
chiliLog(border_draw(pretty));
|
|
176
177
|
}
|
|
177
178
|
else {
|
|
178
|
-
|
|
179
|
+
chiliLog(border_draw(JSON.stringify(decoded.json, null, 2)));
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
else if (decoded.text) {
|
|
182
|
-
|
|
183
|
+
chiliLog(border_draw(decoded.text));
|
|
183
184
|
}
|
|
184
185
|
else {
|
|
185
186
|
const len = decoded.raw.length;
|
|
186
|
-
|
|
187
|
+
chiliLog(border_draw(`Decoded payload available (base64 length ${len}), but not printable.`));
|
|
187
188
|
}
|
|
188
189
|
});
|
|
189
190
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { errorStack_getAllOfType, } from "@fnndsc/cumin";
|
|
7
7
|
import { pacsRetrieve_create, pacsRetrieve_delete, pacsRetrieve_statusForQuery, } from "@fnndsc/salsa";
|
|
8
8
|
import { border_draw } from "../screen/screen.js";
|
|
9
|
+
import { chiliLog } from "../screen/output.js";
|
|
9
10
|
/**
|
|
10
11
|
* Handler for PACS retrieve commands.
|
|
11
12
|
*/
|
|
@@ -52,17 +53,17 @@ export class PACSRetrieveGroupHandler {
|
|
|
52
53
|
async retrieve_pull(queryIdStr) {
|
|
53
54
|
const queryId = Number(queryIdStr);
|
|
54
55
|
if (Number.isNaN(queryId)) {
|
|
55
|
-
|
|
56
|
+
chiliLog(border_draw("Query ID must be a number."));
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
const result = await pacsRetrieve_create(queryId);
|
|
59
60
|
if (!result.ok) {
|
|
60
61
|
const errors = errorStack_getAllOfType("error");
|
|
61
62
|
if (errors.length) {
|
|
62
|
-
errors.forEach((msg) =>
|
|
63
|
+
errors.forEach((msg) => chiliLog(border_draw(msg)));
|
|
63
64
|
}
|
|
64
65
|
else {
|
|
65
|
-
|
|
66
|
+
chiliLog(border_draw(`Failed to create PACS retrieve for query ${queryId}.`));
|
|
66
67
|
}
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
@@ -73,7 +74,7 @@ export class PACSRetrieveGroupHandler {
|
|
|
73
74
|
`query=${queryId}`,
|
|
74
75
|
`status=${record.status || "created"}`,
|
|
75
76
|
].join(" ");
|
|
76
|
-
|
|
77
|
+
chiliLog(border_draw(msg));
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
80
|
* Handle the 'report' command - show detailed status.
|
|
@@ -83,23 +84,23 @@ export class PACSRetrieveGroupHandler {
|
|
|
83
84
|
async retrieve_report(queryIdStr) {
|
|
84
85
|
const queryId = Number(queryIdStr);
|
|
85
86
|
if (Number.isNaN(queryId)) {
|
|
86
|
-
|
|
87
|
+
chiliLog(border_draw("Query ID must be a number."));
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
const result = await pacsRetrieve_statusForQuery(queryId);
|
|
90
91
|
if (!result.ok) {
|
|
91
92
|
const errors = errorStack_getAllOfType("error");
|
|
92
93
|
if (errors.length) {
|
|
93
|
-
errors.forEach((msg) =>
|
|
94
|
+
errors.forEach((msg) => chiliLog(border_draw(msg)));
|
|
94
95
|
}
|
|
95
96
|
else {
|
|
96
|
-
|
|
97
|
+
chiliLog(border_draw(`Failed to generate status report for query ${queryId}.`));
|
|
97
98
|
}
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
100
101
|
const report = result.value;
|
|
101
102
|
const rendered = this.report_render(report);
|
|
102
|
-
|
|
103
|
+
chiliLog(border_draw(rendered));
|
|
103
104
|
}
|
|
104
105
|
/**
|
|
105
106
|
* Handle the 'cancel' command - delete a retrieve.
|
|
@@ -109,21 +110,21 @@ export class PACSRetrieveGroupHandler {
|
|
|
109
110
|
async retrieve_cancel(retrieveIdStr) {
|
|
110
111
|
const retrieveId = Number(retrieveIdStr);
|
|
111
112
|
if (Number.isNaN(retrieveId)) {
|
|
112
|
-
|
|
113
|
+
chiliLog(border_draw("Retrieve ID must be a number."));
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
116
|
const result = await pacsRetrieve_delete(retrieveId);
|
|
116
117
|
if (!result.ok) {
|
|
117
118
|
const errors = errorStack_getAllOfType("error");
|
|
118
119
|
if (errors.length) {
|
|
119
|
-
errors.forEach((msg) =>
|
|
120
|
+
errors.forEach((msg) => chiliLog(border_draw(msg)));
|
|
120
121
|
}
|
|
121
122
|
else {
|
|
122
|
-
|
|
123
|
+
chiliLog(border_draw(`Failed to cancel PACS retrieve ${retrieveId}.`));
|
|
123
124
|
}
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
+
chiliLog(border_draw(`PACS retrieve ${retrieveId} cancelled.`));
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Determine overall completion status from series statuses.
|
package/dist/path/pathCommand.js
CHANGED
|
@@ -16,6 +16,7 @@ import open from "open";
|
|
|
16
16
|
import { execFile } from "child_process";
|
|
17
17
|
import os from "os";
|
|
18
18
|
import { files_downloadWithProgress } from "../commands/fs/download.js";
|
|
19
|
+
import { chiliErrLog, chiliLog } from "../screen/output.js";
|
|
19
20
|
/**
|
|
20
21
|
* Creates a summary table for file transfer details.
|
|
21
22
|
*
|
|
@@ -166,7 +167,7 @@ async function mermaid_renderServerSide(mermaidDefinition, outputFile) {
|
|
|
166
167
|
return new Promise((resolve, reject) => {
|
|
167
168
|
execFile("npx", ["mmdc", "-i", inputFile, "-o", outputFile], (error) => {
|
|
168
169
|
if (error) {
|
|
169
|
-
|
|
170
|
+
chiliErrLog(`Mermaid render failed. Ensure @mermaid-js/mermaid-cli is available (npx mmdc): ${error.message}`);
|
|
170
171
|
reject(error);
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
@@ -412,10 +413,10 @@ async function scanResult_render(scanResult, options) {
|
|
|
412
413
|
try {
|
|
413
414
|
const outputFile = path.resolve(options.save);
|
|
414
415
|
const savedFilePath = await mermaid_renderServerSide(mermaidDefinition, outputFile);
|
|
415
|
-
|
|
416
|
+
chiliLog(`Mermaid diagram saved to: ${savedFilePath}`);
|
|
416
417
|
}
|
|
417
418
|
catch (error) {
|
|
418
|
-
|
|
419
|
+
chiliErrLog(`Failed to save Mermaid diagram: ${error}`);
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
422
|
else {
|
|
@@ -424,16 +425,16 @@ async function scanResult_render(scanResult, options) {
|
|
|
424
425
|
return;
|
|
425
426
|
}
|
|
426
427
|
if (options.tree) {
|
|
427
|
-
|
|
428
|
+
chiliLog(archyTree_create(scanResult.fileInfo));
|
|
428
429
|
return;
|
|
429
430
|
}
|
|
430
431
|
if (!options.silent) {
|
|
431
432
|
for (const file of scanResult.fileInfo) {
|
|
432
433
|
if (file.isLink && !options.follow) {
|
|
433
|
-
|
|
434
|
+
chiliLog(`${file.chrisPath} -> ${file.linkTarget}`);
|
|
434
435
|
}
|
|
435
436
|
else {
|
|
436
|
-
|
|
437
|
+
chiliLog(`${file.chrisPath}`);
|
|
437
438
|
}
|
|
438
439
|
}
|
|
439
440
|
}
|
|
@@ -447,24 +448,24 @@ async function scanResult_render(scanResult, options) {
|
|
|
447
448
|
export async function scan_do(options) {
|
|
448
449
|
const chrisFolder = await chrisContext.current_get(Context.ChRISfolder);
|
|
449
450
|
if (!chrisFolder) {
|
|
450
|
-
|
|
451
|
+
chiliErrLog(chalk.red("No ChRIS folder context set. Use 'folder=' to set a context."));
|
|
451
452
|
return null;
|
|
452
453
|
}
|
|
453
454
|
if (!options.silent) {
|
|
454
|
-
|
|
455
|
+
chiliLog(chalk.cyan(`Scanning for ${options.dirsOnly ? "directories" : "all files"} recursively from ${chrisFolder}`));
|
|
455
456
|
}
|
|
456
457
|
const hostBasePath = options.hostpath || process.cwd();
|
|
457
458
|
const scanResult = await chrisFS_scan(chrisFolder, hostBasePath, options.follow, options.dirsOnly);
|
|
458
459
|
if (!scanResult) {
|
|
459
|
-
|
|
460
|
+
chiliErrLog(chalk.red("Failed to scan ChRIS filesystem."));
|
|
460
461
|
return null;
|
|
461
462
|
}
|
|
462
463
|
const filtered = scanResult_filter(scanResult, options);
|
|
463
464
|
await scanResult_render(filtered, options);
|
|
464
465
|
if (!options.silent) {
|
|
465
|
-
|
|
466
|
+
chiliLog(chalk.green(`Total size: ${bytes_format(filtered.totalSize)}`));
|
|
466
467
|
if (options.filter || options.endsWith) {
|
|
467
|
-
|
|
468
|
+
chiliLog(chalk.cyan(`Filtered results: ${filtered.fileInfo.length} items`));
|
|
468
469
|
}
|
|
469
470
|
}
|
|
470
471
|
return filtered;
|
|
@@ -517,25 +518,25 @@ async function localFS_scan(options) {
|
|
|
517
518
|
await chrisContext.currentContext_update();
|
|
518
519
|
const folder = chrisContext.singleContext.folder;
|
|
519
520
|
if (!folder) {
|
|
520
|
-
|
|
521
|
+
chiliErrLog("ChRIS folder context is undefined, cannot initialize chrisIO.");
|
|
521
522
|
return null;
|
|
522
523
|
}
|
|
523
524
|
// Validate that the folder context exists in CUBE before proceeding
|
|
524
525
|
try {
|
|
525
526
|
const testGroup = await objContext_create("ChRISDirsContext", `folder:${folder}`);
|
|
526
527
|
if (!testGroup) {
|
|
527
|
-
|
|
528
|
+
chiliErrLog(chalk.red(`Folder context '${folder}' does not exist in CUBE. Please specify an existing directory.`));
|
|
528
529
|
return null;
|
|
529
530
|
}
|
|
530
531
|
}
|
|
531
532
|
catch (error) {
|
|
532
|
-
|
|
533
|
+
chiliErrLog(chalk.red(`Folder context '${folder}' does not exist in CUBE. Please specify an existing directory.`));
|
|
533
534
|
return null;
|
|
534
535
|
}
|
|
535
536
|
// Folder exists, so just set chrisFolder without calling init()
|
|
536
537
|
// (init() tries to CREATE the folder which will fail if it already exists)
|
|
537
538
|
chrisIO.chrisFolder = folder;
|
|
538
|
-
|
|
539
|
+
chiliLog(border_draw(chalk.cyan("Scanning files to upload...")));
|
|
539
540
|
const filesToUpload = await filesToUpload_get(options.hostpath, folder);
|
|
540
541
|
let totalSize = 0;
|
|
541
542
|
for (const file of filesToUpload) {
|
|
@@ -643,12 +644,12 @@ async function chris_pull(scanRecord, progressBar) {
|
|
|
643
644
|
}
|
|
644
645
|
else {
|
|
645
646
|
summary.failedCount++;
|
|
646
|
-
|
|
647
|
+
chiliLog(chalk.yellow(`Failed to download: ${file.hostPath}`));
|
|
647
648
|
}
|
|
648
649
|
}
|
|
649
650
|
catch (error) {
|
|
650
651
|
summary.failedCount++;
|
|
651
|
-
|
|
652
|
+
chiliLog(chalk.red(`Error downloading ${file.hostPath}: ${error instanceof Error ? error.message : String(error)}`));
|
|
652
653
|
}
|
|
653
654
|
progressBar.update(index + 1, {
|
|
654
655
|
bytes: bytes_format(summary.transferSize), // Renamed
|
|
@@ -685,12 +686,12 @@ async function chris_push(scanRecord, progressBar) {
|
|
|
685
686
|
}
|
|
686
687
|
else {
|
|
687
688
|
summary.failedCount++;
|
|
688
|
-
|
|
689
|
+
chiliLog(chalk.yellow(`Failed to upload: ${file.hostPath}`));
|
|
689
690
|
}
|
|
690
691
|
}
|
|
691
692
|
catch (error) {
|
|
692
693
|
summary.failedCount++;
|
|
693
|
-
|
|
694
|
+
chiliLog(chalk.red(`Error uploading ${file.hostPath}: ${error instanceof Error ? error.message : String(error)}`));
|
|
694
695
|
}
|
|
695
696
|
progressBar.update(index + 1, {
|
|
696
697
|
bytes: bytes_format(summary.transferSize), // Renamed
|
|
@@ -713,7 +714,7 @@ async function download_handle(options) {
|
|
|
713
714
|
await chrisContext.currentContext_update();
|
|
714
715
|
const folder = chrisContext.singleContext.folder;
|
|
715
716
|
if (!folder) {
|
|
716
|
-
|
|
717
|
+
chiliErrLog(chalk.red("No ChRIS folder context set. Use 'connect' to establish a session."));
|
|
717
718
|
return false;
|
|
718
719
|
}
|
|
719
720
|
const localTarget = path.resolve(options.hostpath || process.cwd());
|
|
@@ -728,7 +729,7 @@ async function download_handle(options) {
|
|
|
728
729
|
}
|
|
729
730
|
catch (error) {
|
|
730
731
|
const msg = error instanceof Error ? error.message : String(error);
|
|
731
|
-
|
|
732
|
+
chiliErrLog(chalk.red(`Download failed: ${msg}`));
|
|
732
733
|
return false;
|
|
733
734
|
}
|
|
734
735
|
}
|
|
@@ -788,7 +789,7 @@ export async function pathCommand_setup(program) {
|
|
|
788
789
|
hostpath: hostpath || process.cwd(),
|
|
789
790
|
force: options.force,
|
|
790
791
|
});
|
|
791
|
-
|
|
792
|
+
chiliLog(result);
|
|
792
793
|
});
|
|
793
794
|
pathCommand
|
|
794
795
|
.command("scan")
|
|
@@ -13,16 +13,27 @@ export declare class PluginContextGroupHandler {
|
|
|
13
13
|
private baseGroupHandler;
|
|
14
14
|
private controller;
|
|
15
15
|
readonly assetName: string;
|
|
16
|
+
private readonly id?;
|
|
16
17
|
private constructor();
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Creates a PluginContextGroupHandler for an asset type. This performs no
|
|
20
|
+
* network work and resolves no plugin context — command registration needs
|
|
21
|
+
* only the asset name; the controller is resolved lazily on first action (see
|
|
22
|
+
* {@link controller_ensure}).
|
|
19
23
|
*
|
|
20
|
-
* @param assetName - The type of plugin context
|
|
21
|
-
* @param id - Optional plugin ID
|
|
22
|
-
* @returns A
|
|
23
|
-
* @throws InitializationError if an unsupported asset type is provided.
|
|
24
|
+
* @param assetName - The type of plugin context ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
|
|
25
|
+
* @param id - Optional plugin ID the context binds to; defaults to the current plugin context.
|
|
26
|
+
* @returns A new PluginContextGroupHandler instance.
|
|
24
27
|
*/
|
|
25
|
-
static handler_create(assetName: string, id?: number | null):
|
|
28
|
+
static handler_create(assetName: string, id?: number | null): PluginContextGroupHandler;
|
|
29
|
+
/**
|
|
30
|
+
* Lazily resolves and memoizes the plugin-context controller against the bound
|
|
31
|
+
* plugin id (or the current ChRIS context), wiring it into the
|
|
32
|
+
* registration-only base handler so its actions can read the context.
|
|
33
|
+
*
|
|
34
|
+
* @returns The controller, or null if the context cannot be resolved here.
|
|
35
|
+
*/
|
|
36
|
+
private controller_ensure;
|
|
26
37
|
/**
|
|
27
38
|
* Lists plugin parameters in a "man page" style format.
|
|
28
39
|
* This is a specialized view for parameters that differs from the standard table view.
|