@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
|
@@ -8,39 +8,50 @@ import { options_toParams } from "../utils/cli.js";
|
|
|
8
8
|
import { PluginContextController } from "../controllers/pluginContextController.js";
|
|
9
9
|
import { pluginParameters_renderMan, pluginParameters_manRender } from "../views/pluginParameters.js";
|
|
10
10
|
import { envelope_ok, envelope_error } from "@fnndsc/cumin";
|
|
11
|
-
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "InitializationError";
|
|
15
|
-
}
|
|
16
|
-
}
|
|
11
|
+
import { chiliErrLog, chiliLog } from "../screen/output.js";
|
|
17
12
|
/**
|
|
18
13
|
* Handles commands related to groups of plugin contexts (computes, instances, parameters).
|
|
19
14
|
*/
|
|
20
15
|
export class PluginContextGroupHandler {
|
|
21
|
-
constructor(
|
|
22
|
-
|
|
23
|
-
this.controller =
|
|
16
|
+
constructor(assetName, id) {
|
|
17
|
+
// Resolved lazily on first action.
|
|
18
|
+
this.controller = null;
|
|
24
19
|
this.assetName = assetName;
|
|
25
|
-
|
|
26
|
-
this.baseGroupHandler = new BaseGroupHandler(this.assetName
|
|
20
|
+
this.id = id;
|
|
21
|
+
this.baseGroupHandler = new BaseGroupHandler(this.assetName);
|
|
27
22
|
}
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Creates a PluginContextGroupHandler for an asset type. This performs no
|
|
25
|
+
* network work and resolves no plugin context — command registration needs
|
|
26
|
+
* only the asset name; the controller is resolved lazily on first action (see
|
|
27
|
+
* {@link controller_ensure}).
|
|
30
28
|
*
|
|
31
|
-
* @param assetName - The type of plugin context
|
|
32
|
-
* @param id - Optional plugin ID
|
|
33
|
-
* @returns A
|
|
34
|
-
* @throws InitializationError if an unsupported asset type is provided.
|
|
29
|
+
* @param assetName - The type of plugin context ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
|
|
30
|
+
* @param id - Optional plugin ID the context binds to; defaults to the current plugin context.
|
|
31
|
+
* @returns A new PluginContextGroupHandler instance.
|
|
35
32
|
*/
|
|
36
|
-
static
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
static handler_create(assetName, id) {
|
|
34
|
+
return new PluginContextGroupHandler(assetName, id);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Lazily resolves and memoizes the plugin-context controller against the bound
|
|
38
|
+
* plugin id (or the current ChRIS context), wiring it into the
|
|
39
|
+
* registration-only base handler so its actions can read the context.
|
|
40
|
+
*
|
|
41
|
+
* @returns The controller, or null if the context cannot be resolved here.
|
|
42
|
+
*/
|
|
43
|
+
async controller_ensure() {
|
|
44
|
+
if (!this.controller) {
|
|
45
|
+
try {
|
|
46
|
+
this.controller = await PluginContextController.controller_create(this.assetName, this.id);
|
|
47
|
+
this.baseGroupHandler.chrisObject =
|
|
48
|
+
this.controller.chrisObject;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
this.controller = null;
|
|
52
|
+
}
|
|
43
53
|
}
|
|
54
|
+
return this.controller;
|
|
44
55
|
}
|
|
45
56
|
/**
|
|
46
57
|
* Lists plugin parameters in a "man page" style format.
|
|
@@ -50,10 +61,15 @@ export class PluginContextGroupHandler {
|
|
|
50
61
|
*/
|
|
51
62
|
async parameters_listMan(options) {
|
|
52
63
|
try {
|
|
64
|
+
const controller = await this.controller_ensure();
|
|
65
|
+
if (!controller) {
|
|
66
|
+
chiliErrLog(`No plugin context is available for ${this.assetName} in the current context.`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
53
69
|
// We access the asset directly from the controller
|
|
54
|
-
const asset =
|
|
70
|
+
const asset = controller.chrisObject.asset;
|
|
55
71
|
if (!asset || typeof asset.resources_listAndFilterByOptions !== 'function') {
|
|
56
|
-
|
|
72
|
+
chiliErrLog("Underlying resource does not support listing.");
|
|
57
73
|
return;
|
|
58
74
|
}
|
|
59
75
|
const params = options_toParams(options);
|
|
@@ -62,20 +78,23 @@ export class PluginContextGroupHandler {
|
|
|
62
78
|
pluginParameters_renderMan(results);
|
|
63
79
|
}
|
|
64
80
|
else {
|
|
65
|
-
|
|
81
|
+
chiliLog("No parameters found.");
|
|
66
82
|
}
|
|
67
83
|
}
|
|
68
84
|
catch (error) {
|
|
69
|
-
|
|
85
|
+
chiliErrLog(`Error listing parameters: ${error}`);
|
|
70
86
|
}
|
|
71
87
|
}
|
|
72
88
|
/**
|
|
73
89
|
* Lists available fields for the current plugin context resource.
|
|
74
90
|
*/
|
|
75
91
|
async parameters_fieldsList() {
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
const controller = await this.controller_ensure();
|
|
93
|
+
if (!controller) {
|
|
94
|
+
chiliErrLog(`No plugin context is available for ${this.assetName} in the current context.`);
|
|
95
|
+
return;
|
|
78
96
|
}
|
|
97
|
+
await this.baseGroupHandler.resourceFields_list();
|
|
79
98
|
}
|
|
80
99
|
/**
|
|
81
100
|
* Renders plugin parameters in "man page" style as an envelope.
|
|
@@ -87,7 +106,11 @@ export class PluginContextGroupHandler {
|
|
|
87
106
|
*/
|
|
88
107
|
async parameters_listManRender(options) {
|
|
89
108
|
try {
|
|
90
|
-
const
|
|
109
|
+
const controller = await this.controller_ensure();
|
|
110
|
+
if (!controller) {
|
|
111
|
+
return envelope_error('', undefined, `No plugin context is available for ${this.assetName} in the current context.\n`);
|
|
112
|
+
}
|
|
113
|
+
const asset = controller.chrisObject.asset;
|
|
91
114
|
if (!asset || typeof asset.resources_listAndFilterByOptions !== 'function') {
|
|
92
115
|
return envelope_error('', undefined, "Underlying resource does not support listing.\n");
|
|
93
116
|
}
|
|
@@ -110,10 +133,11 @@ export class PluginContextGroupHandler {
|
|
|
110
133
|
* @returns An envelope carrying the field listing.
|
|
111
134
|
*/
|
|
112
135
|
async parameters_fieldsRender() {
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
const controller = await this.controller_ensure();
|
|
137
|
+
if (!controller) {
|
|
138
|
+
return envelope_ok('');
|
|
115
139
|
}
|
|
116
|
-
return
|
|
140
|
+
return this.baseGroupHandler.resourceFields_render();
|
|
117
141
|
}
|
|
118
142
|
/**
|
|
119
143
|
* Sets up the Commander.js commands for plugin context group operations.
|
|
@@ -121,9 +145,6 @@ export class PluginContextGroupHandler {
|
|
|
121
145
|
* @param program - The Commander.js program instance.
|
|
122
146
|
*/
|
|
123
147
|
pluginContextGroupCommand_setup(program) {
|
|
124
|
-
|
|
125
|
-
this.baseGroupHandler.command_setup(program);
|
|
126
|
-
}
|
|
127
|
-
const fileGroupCommand = program.commands.find((cmd) => cmd.name() === this.assetName);
|
|
148
|
+
this.baseGroupHandler.command_setup(program);
|
|
128
149
|
}
|
|
129
150
|
}
|
|
@@ -13,6 +13,7 @@ import { pluginIds_resolve } from "../commands/plugin/search.js";
|
|
|
13
13
|
import { pluginRun_render } from "../views/plugin.js"; // Still needed for pluginRun_render
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import { pluginReadme_render } from "../commands/plugin/readme.js";
|
|
16
|
+
import { chiliErrLog, chiliLog } from "../screen/output.js";
|
|
16
17
|
/**
|
|
17
18
|
* Handles commands related to groups of ChRIS plugins.
|
|
18
19
|
*/
|
|
@@ -34,10 +35,10 @@ export class PluginGroupHandler {
|
|
|
34
35
|
// async plugins_list(options: CLIoptions): Promise<void> {
|
|
35
36
|
// try {
|
|
36
37
|
// const { plugins, selectedFields } = await plugins_fetchList(options);
|
|
37
|
-
//
|
|
38
|
+
// chiliLog(pluginList_render(plugins, selectedFields, { table: options.table, csv: options.csv }));
|
|
38
39
|
// } catch (error: unknown) {
|
|
39
40
|
// const msg = error instanceof Error ? error.message : String(error);
|
|
40
|
-
//
|
|
41
|
+
// chiliErrLog(msg);
|
|
41
42
|
// }
|
|
42
43
|
// }
|
|
43
44
|
/**
|
|
@@ -50,11 +51,11 @@ export class PluginGroupHandler {
|
|
|
50
51
|
table_display(fields, ["fields"]);
|
|
51
52
|
}
|
|
52
53
|
else {
|
|
53
|
-
|
|
54
|
+
chiliLog(`No resource fields found for ${this.assetName}.`);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
catch (error) {
|
|
57
|
-
|
|
58
|
+
chiliLog(errorStack.stack_search(this.assetName)[0]);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
@@ -67,12 +68,12 @@ export class PluginGroupHandler {
|
|
|
67
68
|
for (const search of searchables) {
|
|
68
69
|
const items = await plugins_searchByTerm(search.raw);
|
|
69
70
|
if (items.length === 0) {
|
|
70
|
-
|
|
71
|
+
chiliLog(`No plugins found matching: ${search.raw}`);
|
|
71
72
|
continue;
|
|
72
73
|
}
|
|
73
74
|
for (const item of items) {
|
|
74
75
|
// Show item info - reusing table_display for single item details if possible, or simple log
|
|
75
|
-
|
|
76
|
+
chiliLog(`Preparing to delete Plugin: ID=${item.id}, Name=${item.name}, Version=${item.version}`);
|
|
76
77
|
if (!options.force) {
|
|
77
78
|
const confirmed = await prompt_confirm(`Are you sure you want to delete plugin ${item.name} (ID: ${item.id})?`);
|
|
78
79
|
if (!confirmed)
|
|
@@ -80,10 +81,10 @@ export class PluginGroupHandler {
|
|
|
80
81
|
}
|
|
81
82
|
const success = await plugin_deleteById(item.id);
|
|
82
83
|
if (success) {
|
|
83
|
-
|
|
84
|
+
chiliLog(`Deleted plugin ${item.id}`);
|
|
84
85
|
}
|
|
85
86
|
else {
|
|
86
|
-
|
|
87
|
+
chiliErrLog(`Failed to delete plugin ${item.id}`);
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
}
|
|
@@ -161,11 +162,11 @@ export class PluginMemberHandler {
|
|
|
161
162
|
async readme_print(repoUrl) {
|
|
162
163
|
const content = await this.controller.readmeContent_fetch(repoUrl);
|
|
163
164
|
if (content) {
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
chiliLog(chalk.green.bold("\nREADME Content:"));
|
|
166
|
+
chiliLog(pluginReadme_render(content));
|
|
166
167
|
}
|
|
167
168
|
else {
|
|
168
|
-
|
|
169
|
+
chiliLog(chalk.red("README not found in the repository."));
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
@@ -176,21 +177,21 @@ export class PluginMemberHandler {
|
|
|
176
177
|
*/
|
|
177
178
|
async plugin_readme(pluginId) {
|
|
178
179
|
try {
|
|
179
|
-
|
|
180
|
+
chiliLog(`Fetching info for plugin with ID: ${pluginId}`);
|
|
180
181
|
const documentation = await this.controller.documentationUrl_get(pluginId);
|
|
181
182
|
if (!documentation) {
|
|
182
183
|
return null;
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
+
chiliLog(documentation);
|
|
185
186
|
await this.readme_print(documentation);
|
|
186
187
|
return documentation;
|
|
187
188
|
}
|
|
188
189
|
catch (error) {
|
|
189
190
|
if (error instanceof Error) {
|
|
190
|
-
|
|
191
|
+
chiliErrLog(`Error fetching plugin info: ${error.message}`);
|
|
191
192
|
}
|
|
192
193
|
else {
|
|
193
|
-
|
|
194
|
+
chiliErrLog("An unknown error occurred while fetching plugin info");
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
return null;
|
|
@@ -214,15 +215,15 @@ export class PluginMemberHandler {
|
|
|
214
215
|
try {
|
|
215
216
|
const instance = await plugin_execute(searchable, params);
|
|
216
217
|
if (!instance) {
|
|
217
|
-
|
|
218
|
+
chiliLog(errorStack.messagesOfType_search("error", "plugin"));
|
|
218
219
|
return null;
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
+
chiliLog(pluginRun_render(instance));
|
|
221
222
|
return instance.id;
|
|
222
223
|
}
|
|
223
224
|
catch (e) {
|
|
224
225
|
const message = e instanceof Error ? e.message : String(e);
|
|
225
|
-
|
|
226
|
+
chiliErrLog(message);
|
|
226
227
|
return null;
|
|
227
228
|
}
|
|
228
229
|
}
|
|
@@ -234,7 +235,7 @@ export class PluginMemberHandler {
|
|
|
234
235
|
if (!hits) {
|
|
235
236
|
return null;
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
+
chiliLog(hits);
|
|
238
239
|
return hits;
|
|
239
240
|
}
|
|
240
241
|
/**
|
|
@@ -270,10 +271,10 @@ export class PluginMemberHandler {
|
|
|
270
271
|
else {
|
|
271
272
|
const warnings = errorStack_getAllOfType('warning');
|
|
272
273
|
if (warnings && warnings.length > 0) {
|
|
273
|
-
warnings.forEach(warning =>
|
|
274
|
+
warnings.forEach(warning => chiliErrLog(chalk.yellow(warning)));
|
|
274
275
|
}
|
|
275
276
|
else {
|
|
276
|
-
|
|
277
|
+
chiliErrLog(chalk.red("Plugin not found."));
|
|
277
278
|
}
|
|
278
279
|
}
|
|
279
280
|
});
|
|
@@ -297,7 +298,7 @@ export class PluginMemberHandler {
|
|
|
297
298
|
});
|
|
298
299
|
}
|
|
299
300
|
else {
|
|
300
|
-
|
|
301
|
+
chiliErrLog(`Failed to find '${this.assetName}' command. The 'readme' subcommand was not added.`);
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
}
|
package/dist/run.d.ts
CHANGED
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
+
import { type ChiliCaptured } from "./screen/output.js";
|
|
13
|
+
/**
|
|
14
|
+
* The set of top-level chili command names. Builds a fresh program and registers
|
|
15
|
+
* every handler — now a cheap, network-free operation — then reads the
|
|
16
|
+
* registered command names. A host (the brasa engine) uses this to decide
|
|
17
|
+
* whether an unknown chell command is worth delegating to chili at all.
|
|
18
|
+
*
|
|
19
|
+
* @returns The registered top-level command names.
|
|
20
|
+
*/
|
|
21
|
+
export declare function commandNames_get(): Promise<Set<string>>;
|
|
12
22
|
/**
|
|
13
23
|
* Executes a single chili command in the current process.
|
|
14
24
|
*
|
|
@@ -22,3 +32,15 @@
|
|
|
22
32
|
* the leading `node`/script entries).
|
|
23
33
|
*/
|
|
24
34
|
export declare function run(argv: string[]): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Executes a single chili command with its output captured instead of printed.
|
|
37
|
+
*
|
|
38
|
+
* Runs {@link run} inside {@link chili_capture}, so everything the command
|
|
39
|
+
* would have written to the output and error channels is collected and
|
|
40
|
+
* returned. This is how an in-process host (the brasa engine) drives chili
|
|
41
|
+
* headless and carries the result in an envelope, with no console monkeypatch.
|
|
42
|
+
*
|
|
43
|
+
* @param argv - The chili arguments, e.g. `["feeds", "list", "-s"]`.
|
|
44
|
+
* @returns The captured output and error text.
|
|
45
|
+
*/
|
|
46
|
+
export declare function run_capture(argv: string[]): Promise<ChiliCaptured>;
|
package/dist/run.js
CHANGED
|
@@ -25,8 +25,9 @@ import { chefsCommand_setup } from "./chefs/chefs.js";
|
|
|
25
25
|
import { PACSServerGroupHandler } from "./pacs/pacsServerHandler.js";
|
|
26
26
|
import { PACSQueryGroupHandler } from "./pacs/pacsQueryHandler.js";
|
|
27
27
|
import { PACSRetrieveGroupHandler } from "./pacs/pacsRetrieveHandler.js";
|
|
28
|
-
import { chrisConnection_init, NodeStorageProvider,
|
|
28
|
+
import { chrisConnection_init, NodeStorageProvider, } from "@fnndsc/cumin";
|
|
29
29
|
import { FileGroupHandler } from "./filesystem/fileGroupHandler.js";
|
|
30
|
+
import { chiliErrLog, chiliLog, chili_capture } from "./screen/output.js";
|
|
30
31
|
/**
|
|
31
32
|
* Suppress the DEP0169 (`url.parse()`) warning emitted transitively by
|
|
32
33
|
* axios -> proxy-from-env, which we do not control. Installed once at import.
|
|
@@ -72,51 +73,32 @@ async function handlers_initialize(program) {
|
|
|
72
73
|
feedMemberHandler.feedCommand_setup(program);
|
|
73
74
|
const pluginMemberHandler = new PluginMemberHandler();
|
|
74
75
|
pluginMemberHandler.pluginCommand_setup(program);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
const err = e instanceof Error ? e.message : String(e);
|
|
85
|
-
const errors = errorStack_getAllOfType("error");
|
|
86
|
-
const warnings = errorStack_getAllOfType("warning");
|
|
87
|
-
console.log(`Note: File group commands (files, dirs, links) are unavailable. Reason: ${err}`);
|
|
88
|
-
if (errors.length > 0) {
|
|
89
|
-
console.log("Errors:");
|
|
90
|
-
errors.forEach((msg) => console.log(` - ${msg}`));
|
|
91
|
-
}
|
|
92
|
-
if (warnings.length > 0) {
|
|
93
|
-
console.log("Warnings:");
|
|
94
|
-
warnings.forEach((msg) => console.log(` - ${msg}`));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const computesOfPluginHandler = await PluginContextGroupHandler.handler_create("computesofplugin");
|
|
99
|
-
computesOfPluginHandler.pluginContextGroupCommand_setup(program);
|
|
100
|
-
const pluginInstancesHandler = await PluginContextGroupHandler.handler_create("instancesofplugin");
|
|
101
|
-
pluginInstancesHandler.pluginContextGroupCommand_setup(program);
|
|
102
|
-
const pluginParametersHandler = await PluginContextGroupHandler.handler_create("parametersofplugin");
|
|
103
|
-
pluginParametersHandler.pluginContextGroupCommand_setup(program);
|
|
76
|
+
// File-group and plugin-context commands register with no ChRIS context: each
|
|
77
|
+
// handler resolves its context lazily, only when one of its actions runs. This
|
|
78
|
+
// keeps setup free of network work, so an unrelated command (or a directory
|
|
79
|
+
// that is not a ChRIS folder) pays no file-context cost and produces no
|
|
80
|
+
// context-init noise.
|
|
81
|
+
for (const assetName of ["files", "links", "dirs"]) {
|
|
82
|
+
FileGroupHandler.handler_create(assetName).fileGroupCommand_setup(program);
|
|
104
83
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const errors = errorStack_getAllOfType("error");
|
|
108
|
-
const warnings = errorStack_getAllOfType("warning");
|
|
109
|
-
console.log(`Note: Plugin context commands are unavailable. Reason: ${err}`);
|
|
110
|
-
if (errors.length > 0) {
|
|
111
|
-
console.log("Errors:");
|
|
112
|
-
errors.forEach((msg) => console.log(` - ${msg}`));
|
|
113
|
-
}
|
|
114
|
-
if (warnings.length > 0) {
|
|
115
|
-
console.log("Warnings:");
|
|
116
|
-
warnings.forEach((msg) => console.log(` - ${msg}`));
|
|
117
|
-
}
|
|
84
|
+
for (const assetName of ["computesofplugin", "instancesofplugin", "parametersofplugin"]) {
|
|
85
|
+
PluginContextGroupHandler.handler_create(assetName).pluginContextGroupCommand_setup(program);
|
|
118
86
|
}
|
|
119
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The set of top-level chili command names. Builds a fresh program and registers
|
|
90
|
+
* every handler — now a cheap, network-free operation — then reads the
|
|
91
|
+
* registered command names. A host (the brasa engine) uses this to decide
|
|
92
|
+
* whether an unknown chell command is worth delegating to chili at all.
|
|
93
|
+
*
|
|
94
|
+
* @returns The registered top-level command names.
|
|
95
|
+
*/
|
|
96
|
+
export async function commandNames_get() {
|
|
97
|
+
const program = new Command();
|
|
98
|
+
connectCommand_setup(program);
|
|
99
|
+
await handlers_initialize(program);
|
|
100
|
+
return new Set(program.commands.map((cmd) => cmd.name()));
|
|
101
|
+
}
|
|
120
102
|
/**
|
|
121
103
|
* Extracts a leading `key=value` context string from an argv array.
|
|
122
104
|
*
|
|
@@ -155,7 +137,7 @@ export async function run(argv) {
|
|
|
155
137
|
if (context) {
|
|
156
138
|
const contextSetSuccess = await connection.context_set(context);
|
|
157
139
|
if (!contextSetSuccess) {
|
|
158
|
-
|
|
140
|
+
chiliErrLog("Failed to set context.");
|
|
159
141
|
return;
|
|
160
142
|
}
|
|
161
143
|
fullArgv = newArgs;
|
|
@@ -174,8 +156,8 @@ export async function run(argv) {
|
|
|
174
156
|
program.parseOptions(fullArgv);
|
|
175
157
|
const options = program.opts();
|
|
176
158
|
if (!options.nosplash) {
|
|
177
|
-
|
|
178
|
-
|
|
159
|
+
chiliLog(figlet.textSync("ChILI"));
|
|
160
|
+
chiliLog("ChILI handles Intelligent Line Interactions");
|
|
179
161
|
}
|
|
180
162
|
try {
|
|
181
163
|
await program.parseAsync(fullArgv);
|
|
@@ -190,3 +172,17 @@ export async function run(argv) {
|
|
|
190
172
|
throw err;
|
|
191
173
|
}
|
|
192
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Executes a single chili command with its output captured instead of printed.
|
|
177
|
+
*
|
|
178
|
+
* Runs {@link run} inside {@link chili_capture}, so everything the command
|
|
179
|
+
* would have written to the output and error channels is collected and
|
|
180
|
+
* returned. This is how an in-process host (the brasa engine) drives chili
|
|
181
|
+
* headless and carries the result in an envelope, with no console monkeypatch.
|
|
182
|
+
*
|
|
183
|
+
* @param argv - The chili arguments, e.g. `["feeds", "list", "-s"]`.
|
|
184
|
+
* @returns The captured output and error text.
|
|
185
|
+
*/
|
|
186
|
+
export async function run_capture(argv) {
|
|
187
|
+
return chili_capture(() => run(argv));
|
|
188
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file chili's output seam.
|
|
3
|
+
*
|
|
4
|
+
* chili historically printed straight to `console.log`/`console.error`, which
|
|
5
|
+
* fused its command layer to a terminal and forced any in-process host (the
|
|
6
|
+
* brasa engine, which drives chili headless) to capture output with a console
|
|
7
|
+
* monkeypatch. This seam inverts that: command output is written to whatever
|
|
8
|
+
* writer is installed, and by default that writer simply delegates to the
|
|
9
|
+
* process console — so chili's standalone CLI behaves exactly as it always has.
|
|
10
|
+
*
|
|
11
|
+
* A host captures a run's output with {@link chili_capture}, which swaps in a
|
|
12
|
+
* buffering writer for the duration of a call and returns the collected text.
|
|
13
|
+
* This is explicit dependency injection through a module seam, not a runtime
|
|
14
|
+
* override of the global `console`: the default writer calls `console.log`
|
|
15
|
+
* normally, and capture bypasses the console entirely.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Destination for chili's command output.
|
|
21
|
+
*/
|
|
22
|
+
export interface ChiliWriter {
|
|
23
|
+
/**
|
|
24
|
+
* Writes to the output channel with `console.log` semantics.
|
|
25
|
+
*
|
|
26
|
+
* @param args - The values to format, exactly as passed to `console.log`.
|
|
27
|
+
*/
|
|
28
|
+
log(...args: unknown[]): void;
|
|
29
|
+
/**
|
|
30
|
+
* Writes to the error channel with `console.error` semantics.
|
|
31
|
+
*
|
|
32
|
+
* @param args - The values to format, exactly as passed to `console.error`.
|
|
33
|
+
*/
|
|
34
|
+
errLog(...args: unknown[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Writes raw text or bytes to the output channel without adding a newline.
|
|
37
|
+
*
|
|
38
|
+
* @param chunk - The text or bytes to write.
|
|
39
|
+
*/
|
|
40
|
+
write(chunk: string | Buffer): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Installs a writer as the active output destination.
|
|
44
|
+
*
|
|
45
|
+
* @param writer - The writer to install.
|
|
46
|
+
* @returns The previously installed writer, so callers can restore it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function chiliWriter_set(writer: ChiliWriter): ChiliWriter;
|
|
49
|
+
/**
|
|
50
|
+
* Writes to the output channel with `console.log` semantics.
|
|
51
|
+
*
|
|
52
|
+
* @param args - The values to format, exactly as passed to `console.log`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function chiliLog(...args: unknown[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Writes to the error channel with `console.error` semantics.
|
|
57
|
+
*
|
|
58
|
+
* @param args - The values to format, exactly as passed to `console.error`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function chiliErrLog(...args: unknown[]): void;
|
|
61
|
+
/**
|
|
62
|
+
* Writes raw text or bytes to the output channel without adding a newline —
|
|
63
|
+
* the replacement for direct `process.stdout.write` calls.
|
|
64
|
+
*
|
|
65
|
+
* @param chunk - The text or bytes to write.
|
|
66
|
+
*/
|
|
67
|
+
export declare function chiliWrite(chunk: string | Buffer): void;
|
|
68
|
+
/**
|
|
69
|
+
* The text captured from a run, one string per channel.
|
|
70
|
+
*/
|
|
71
|
+
export interface ChiliCaptured {
|
|
72
|
+
/** Everything written to the output channel. */
|
|
73
|
+
out: string;
|
|
74
|
+
/** Everything written to the error channel. */
|
|
75
|
+
err: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Runs `fn` with both channels captured into memory and returns the collected
|
|
79
|
+
* text, restoring the previous writer afterwards even if `fn` throws.
|
|
80
|
+
*
|
|
81
|
+
* Formatting matches `console.log`: arguments are rendered with `util.format`
|
|
82
|
+
* and each `log`/`errLog` call contributes a trailing newline, exactly as the
|
|
83
|
+
* console would have written.
|
|
84
|
+
*
|
|
85
|
+
* @param fn - The work whose output should be captured.
|
|
86
|
+
* @returns The captured output and error text.
|
|
87
|
+
*/
|
|
88
|
+
export declare function chili_capture(fn: () => Promise<void>): Promise<ChiliCaptured>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file chili's output seam.
|
|
3
|
+
*
|
|
4
|
+
* chili historically printed straight to `console.log`/`console.error`, which
|
|
5
|
+
* fused its command layer to a terminal and forced any in-process host (the
|
|
6
|
+
* brasa engine, which drives chili headless) to capture output with a console
|
|
7
|
+
* monkeypatch. This seam inverts that: command output is written to whatever
|
|
8
|
+
* writer is installed, and by default that writer simply delegates to the
|
|
9
|
+
* process console — so chili's standalone CLI behaves exactly as it always has.
|
|
10
|
+
*
|
|
11
|
+
* A host captures a run's output with {@link chili_capture}, which swaps in a
|
|
12
|
+
* buffering writer for the duration of a call and returns the collected text.
|
|
13
|
+
* This is explicit dependency injection through a module seam, not a runtime
|
|
14
|
+
* override of the global `console`: the default writer calls `console.log`
|
|
15
|
+
* normally, and capture bypasses the console entirely.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { format } from "util";
|
|
20
|
+
/**
|
|
21
|
+
* The default writer: delegates to the process console. Installed at import so
|
|
22
|
+
* the standalone CLI prints exactly as before.
|
|
23
|
+
*/
|
|
24
|
+
const consoleWriter = {
|
|
25
|
+
log: (...args) => { console.log(...args); },
|
|
26
|
+
errLog: (...args) => { console.error(...args); },
|
|
27
|
+
write: (chunk) => { process.stdout.write(chunk); },
|
|
28
|
+
};
|
|
29
|
+
/** The writer in effect. Swapped for the duration of a captured run. */
|
|
30
|
+
let activeWriter = consoleWriter;
|
|
31
|
+
/**
|
|
32
|
+
* Installs a writer as the active output destination.
|
|
33
|
+
*
|
|
34
|
+
* @param writer - The writer to install.
|
|
35
|
+
* @returns The previously installed writer, so callers can restore it.
|
|
36
|
+
*/
|
|
37
|
+
export function chiliWriter_set(writer) {
|
|
38
|
+
const previous = activeWriter;
|
|
39
|
+
activeWriter = writer;
|
|
40
|
+
return previous;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Writes to the output channel with `console.log` semantics.
|
|
44
|
+
*
|
|
45
|
+
* @param args - The values to format, exactly as passed to `console.log`.
|
|
46
|
+
*/
|
|
47
|
+
export function chiliLog(...args) {
|
|
48
|
+
activeWriter.log(...args);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Writes to the error channel with `console.error` semantics.
|
|
52
|
+
*
|
|
53
|
+
* @param args - The values to format, exactly as passed to `console.error`.
|
|
54
|
+
*/
|
|
55
|
+
export function chiliErrLog(...args) {
|
|
56
|
+
activeWriter.errLog(...args);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Writes raw text or bytes to the output channel without adding a newline —
|
|
60
|
+
* the replacement for direct `process.stdout.write` calls.
|
|
61
|
+
*
|
|
62
|
+
* @param chunk - The text or bytes to write.
|
|
63
|
+
*/
|
|
64
|
+
export function chiliWrite(chunk) {
|
|
65
|
+
activeWriter.write(chunk);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Runs `fn` with both channels captured into memory and returns the collected
|
|
69
|
+
* text, restoring the previous writer afterwards even if `fn` throws.
|
|
70
|
+
*
|
|
71
|
+
* Formatting matches `console.log`: arguments are rendered with `util.format`
|
|
72
|
+
* and each `log`/`errLog` call contributes a trailing newline, exactly as the
|
|
73
|
+
* console would have written.
|
|
74
|
+
*
|
|
75
|
+
* @param fn - The work whose output should be captured.
|
|
76
|
+
* @returns The captured output and error text.
|
|
77
|
+
*/
|
|
78
|
+
export async function chili_capture(fn) {
|
|
79
|
+
const outChunks = [];
|
|
80
|
+
const errChunks = [];
|
|
81
|
+
const previous = chiliWriter_set({
|
|
82
|
+
log: (...args) => { outChunks.push(`${format(...args)}\n`); },
|
|
83
|
+
errLog: (...args) => { errChunks.push(`${format(...args)}\n`); },
|
|
84
|
+
write: (chunk) => {
|
|
85
|
+
outChunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf-8"));
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
await fn();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
chiliWriter_set(previous);
|
|
93
|
+
}
|
|
94
|
+
return { out: outChunks.join(""), err: errChunks.join("") };
|
|
95
|
+
}
|