@fnndsc/chili 3.4.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.
@@ -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
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { table, getBorderCharacters } from "table";
7
7
  import chalk from "chalk";
8
+ import { chiliLog, chiliErrLog } from "./output.js";
8
9
  /**
9
10
  * Processes raw table input data and headers into a standardized format.
10
11
  *
@@ -60,7 +61,7 @@ function tableContent_pack(tableData, selectedFields) {
60
61
  body = tableData.map((row) => headers.map((field) => (row[field] !== undefined ? row[field] : "")));
61
62
  }
62
63
  else {
63
- console.error("Invalid table data");
64
+ chiliErrLog("Invalid table data");
64
65
  return null;
65
66
  }
66
67
  return { headers, body };
@@ -188,10 +189,10 @@ export function table_display(tableData, headers, options = {}) {
188
189
  },
189
190
  };
190
191
  const result = screen.table_output(processedTableData, tableOptions);
191
- console.log(result);
192
+ chiliLog(result);
192
193
  if (options.pagination && options.pagination.shown < options.pagination.total) {
193
194
  const { shown, total } = options.pagination;
194
- console.log(chalk.dim(` ↓ showing ${shown} of ${total} · --all to fetch all · --limit <n> for page size`));
195
+ chiliLog(chalk.dim(` ↓ showing ${shown} of ${total} · --all to fetch all · --limit <n> for page size`));
195
196
  }
196
197
  return tableObj;
197
198
  }
@@ -200,16 +201,16 @@ export function table_display(tableData, headers, options = {}) {
200
201
  */
201
202
  export class Screen {
202
203
  log(...args) {
203
- console.log(...args);
204
+ chiliLog(...args);
204
205
  }
205
206
  error(...args) {
206
- console.error(chalk.red(...args));
207
+ chiliErrLog(chalk.red(...args));
207
208
  }
208
209
  warn(...args) {
209
- console.warn(chalk.yellow(...args));
210
+ chiliErrLog(chalk.yellow(...args));
210
211
  }
211
212
  info(...args) {
212
- console.info(chalk.blue(...args));
213
+ chiliLog(chalk.blue(...args));
213
214
  }
214
215
  /**
215
216
  * Generates a formatted table output string.
@@ -242,7 +243,7 @@ export class Screen {
242
243
  : output;
243
244
  }
244
245
  catch (error) {
245
- console.error("Error in table_output method:", error);
246
+ chiliErrLog("Error in table_output method:", error);
246
247
  return "Error generating table";
247
248
  }
248
249
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import * as readline from 'readline';
11
11
  import { Writable } from 'stream';
12
+ import { chiliLog, chiliWrite } from "../screen/output.js";
12
13
  /** Injected by the host (chell) so prompts route through the active readline. */
13
14
  let _askFn = null;
14
15
  let _askHiddenFn = null;
@@ -35,24 +36,24 @@ export function adminPrompt_register(askFn, askHiddenFn) {
35
36
  */
36
37
  export async function adminCredentials_prompt(attempt = 1, maxAttempts = 3) {
37
38
  if (attempt === 1) {
38
- console.log('\nAdmin credentials required to register plugins.');
39
- console.log('You can provide these via --adminUser and --adminPassword flags.');
40
- console.log('');
39
+ chiliLog('\nAdmin credentials required to register plugins.');
40
+ chiliLog('You can provide these via --adminUser and --adminPassword flags.');
41
+ chiliLog('');
41
42
  }
42
43
  else {
43
- console.log(`\nAuthentication failed. Attempt ${attempt} of ${maxAttempts}.`);
44
- console.log('');
44
+ chiliLog(`\nAuthentication failed. Attempt ${attempt} of ${maxAttempts}.`);
45
+ chiliLog('');
45
46
  }
46
47
  const askFn = _askFn ?? ask_fallback;
47
48
  const askHiddenFn = _askHiddenFn ?? askHidden_fallback;
48
49
  const username = await askFn('Username: ');
49
50
  if (!username) {
50
- console.log('Username cannot be empty.');
51
+ chiliLog('Username cannot be empty.');
51
52
  return null;
52
53
  }
53
54
  const password = await askHiddenFn('Password: ');
54
55
  if (!password) {
55
- console.log('Password cannot be empty.');
56
+ chiliLog('Password cannot be empty.');
56
57
  return null;
57
58
  }
58
59
  return { username, password };
@@ -82,9 +83,9 @@ function askHidden_fallback(prompt) {
82
83
  output: muted,
83
84
  terminal: true,
84
85
  });
85
- process.stdout.write(prompt);
86
+ chiliWrite(prompt);
86
87
  rl.question('', (answer) => {
87
- process.stdout.write('\n');
88
+ chiliWrite('\n');
88
89
  rl.close();
89
90
  resolve(answer.trim());
90
91
  });
@@ -7,6 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import { exec } from "child_process";
10
+ import { chiliErrLog, chiliLog } from "../screen/output.js";
10
11
  /**
11
12
  * Promisified version of child_process.exec.
12
13
  * @param command - The command string to execute.
@@ -33,13 +34,13 @@ export async function shellCommand_run(command) {
33
34
  try {
34
35
  const { stdout, stderr } = await childProcess_exec(command);
35
36
  if (stderr) {
36
- // console.warn(`Command stderr: ${stderr.trim()}`); // Log stderr as warning, not necessarily an error
37
+ // chiliErrLog(`Command stderr: ${stderr.trim()}`); // Log stderr as warning, not necessarily an error
37
38
  }
38
39
  return stdout.trim();
39
40
  }
40
41
  catch (error) {
41
- // console.error(`Command failed: ${command}`);
42
- // console.error(`Error: ${error.message}`);
42
+ // chiliErrLog(`Command failed: ${command}`);
43
+ // chiliErrLog(`Error: ${error.message}`);
43
44
  return null;
44
45
  }
45
46
  }
@@ -77,8 +78,8 @@ export async function docker_checkAvailability() {
77
78
  if (result === "OK") {
78
79
  return true;
79
80
  }
80
- console.error("Error: Docker is not installed or not running.");
81
- console.error("Please ensure Docker is properly set up on your system to add plugins.");
81
+ chiliErrLog("Error: Docker is not installed or not running.");
82
+ chiliErrLog("Please ensure Docker is properly set up on your system to add plugins.");
82
83
  return false;
83
84
  }
84
85
  /**
@@ -112,17 +113,17 @@ export async function docker_imageExistsLocally(image) {
112
113
  export async function docker_pullImage(image, quiet = true) {
113
114
  const existsLocally = await docker_imageExistsLocally(image);
114
115
  if (existsLocally) {
115
- console.log(`Docker image ${image} already exists locally.`);
116
+ chiliLog(`Docker image ${image} already exists locally.`);
116
117
  return true;
117
118
  }
118
- console.log(`Pulling Docker image: ${image}...`);
119
+ chiliLog(`Pulling Docker image: ${image}...`);
119
120
  const quietFlag = quiet ? '--quiet' : '';
120
121
  const result = await shellCommand_run(`docker pull ${quietFlag} ${image}`);
121
122
  if (result !== null) {
122
- console.log(`Successfully pulled ${image}`);
123
+ chiliLog(`Successfully pulled ${image}`);
123
124
  return true;
124
125
  }
125
- console.error(`Failed to pull Docker image: ${image}`);
126
+ chiliErrLog(`Failed to pull Docker image: ${image}`);
126
127
  return false;
127
128
  }
128
129
  /**
@@ -4,6 +4,7 @@
4
4
  * @module
5
5
  */
6
6
  import chalk from 'chalk';
7
+ import { chiliLog } from "../screen/output.js";
7
8
  /**
8
9
  * Renders plugin parameters in a "man page" style format.
9
10
  *
@@ -15,7 +16,7 @@ import chalk from 'chalk';
15
16
  * @param data - The filtered resource data containing plugin parameters.
16
17
  */
17
18
  export function pluginParameters_renderMan(data) {
18
- console.log(pluginParameters_manRender(data));
19
+ chiliLog(pluginParameters_manRender(data));
19
20
  }
20
21
  /**
21
22
  * Builds the "man page" style rendering of plugin parameters as a string.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnndsc/chili",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "ChILI handles Intelligent Line Interactions: A CLI and library for ChRIS, acting as the controller layer for business logic and user presentation.",
5
5
  "repository": {
6
6
  "type": "git",