procband 0.3.6 → 0.3.7

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 CHANGED
@@ -85,6 +85,24 @@ If your script never awaits the process or calls `wait()`, an unobserved failed
85
85
  terminal exit sets the parent `process.exitCode` and starts stopping other live
86
86
  `procband` processes in the same parent script.
87
87
 
88
+ ## Prefix an Existing Stream
89
+
90
+ Use `createPrefixStream()` when another API already owns a process lifecycle
91
+ but its output should use procband's label and color formatting:
92
+
93
+ ```ts
94
+ import { createPrefixStream } from 'procband'
95
+
96
+ const output = createPrefixStream({ label: 'postgres' })
97
+ output.pipe(process.stdout)
98
+ postgres.stdout.pipe(output)
99
+ ```
100
+
101
+ The transform preserves stream backpressure, buffers partial lines and UTF-8
102
+ characters across chunks, and flushes a final unterminated line when input ends.
103
+ Pass `color: [red, green, blue]` to select a prefix color; otherwise procband
104
+ assigns the next color from its shared palette.
105
+
88
106
  ## Documentation
89
107
 
90
108
  Start with the page that matches the decision in front of you:
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
+ import { Readable, Transform, Writable } from "node:stream";
1
2
  import { ChildProcess } from "node:child_process";
2
- import { Readable, Writable } from "node:stream";
3
3
 
4
4
  //#region src/types.d.ts
5
5
  /**
@@ -110,6 +110,20 @@ interface ProcessConfig {
110
110
  * A 24-bit RGB color tuple.
111
111
  */
112
112
  type RgbColor = readonly [red: number, green: number, blue: number];
113
+ /**
114
+ * Options for `createPrefixStream()`.
115
+ */
116
+ interface PrefixStreamOptions {
117
+ /** Human-facing label added to each output line. */
118
+ label: string;
119
+ /**
120
+ * RGB color for the prefix.
121
+ *
122
+ * When omitted, `procband` assigns the next color from its default palette.
123
+ * The reserved `stderr` red cannot be used here.
124
+ */
125
+ color?: RgbColor;
126
+ }
113
127
  /**
114
128
  * Controls whether and how a supervised process restarts after exit.
115
129
  */
@@ -311,6 +325,29 @@ declare class ProcessExitError extends Error {
311
325
  constructor(config: ProcessConfig, result: ProcessResult);
312
326
  }
313
327
  //#endregion
328
+ //#region src/prefix-stream.d.ts
329
+ /**
330
+ * Create a transform that prefixes arbitrary output with a procband label.
331
+ *
332
+ * The transform buffers incomplete lines and UTF-8 sequences across chunks.
333
+ * CRLF input is normalized to LF, matching supervised process output, and a
334
+ * final unterminated line is emitted when the writable side ends.
335
+ *
336
+ * @param options Label and optional prefix color.
337
+ * @returns A backpressure-aware Node.js transform stream.
338
+ * @throws When `options.color` is invalid or uses procband's reserved stderr
339
+ * color.
340
+ * @example
341
+ * ```ts
342
+ * import { createPrefixStream } from 'procband'
343
+ *
344
+ * const output = createPrefixStream({ label: 'postgres' })
345
+ * output.pipe(process.stdout)
346
+ * postgres.stdout.pipe(output)
347
+ * ```
348
+ */
349
+ declare function createPrefixStream(options: PrefixStreamOptions): Transform;
350
+ //#endregion
314
351
  //#region src/process.d.ts
315
352
  /**
316
353
  * Start supervising a single subprocess.
@@ -341,4 +378,4 @@ declare class ProcessExitError extends Error {
341
378
  */
342
379
  declare function supervise(config: ProcessConfig): ProcbandProcess;
343
380
  //#endregion
344
- export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, ProcessExitError, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, type WaitOptions, supervise };
381
+ export { type MatchCallback, type MatchEvent, type MatchOptions, type PrefixStreamOptions, type ProcbandProcess, type ProcessConfig, ProcessExitError, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, type WaitOptions, createPrefixStream, supervise };
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
+ import { StringDecoder } from "node:string_decoder";
2
+ import { Transform } from "node:stream";
3
+ import ansiStyles from "ansi-styles";
1
4
  import { spawn } from "node:child_process";
2
5
  import { EventEmitter } from "node:events";
3
6
  import { constants } from "node:os";
4
7
  import process from "node:process";
5
- import ansiStyles from "ansi-styles";
6
8
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
9
  import treeKill, { treeKillSync } from "@alloc/tree-kill";
8
10
  //#region src/errors.ts
@@ -109,24 +111,87 @@ function resolveProcessColor(color) {
109
111
  nextPaletteIndex += 1;
110
112
  return [...paletteColor];
111
113
  }
112
- function validateProcessColor(color) {
114
+ function validateProcessColor(color, optionName = "ProcessConfig.color") {
113
115
  if (!color) return;
114
116
  const [red, green, blue] = color;
115
117
  for (const value of [
116
118
  red,
117
119
  green,
118
120
  blue
119
- ]) if (!Number.isInteger(value) || value < 0 || value > 255) throw new Error("ProcessConfig.color must contain integer RGB values between 0 and 255");
120
- if (red === stderrColor[0] && green === stderrColor[1] && blue === stderrColor[2]) throw new Error("ProcessConfig.color cannot use the reserved stderr color");
121
+ ]) if (!Number.isInteger(value) || value < 0 || value > 255) throw new Error(`${optionName} must contain integer RGB values between 0 and 255`);
122
+ if (red === stderrColor[0] && green === stderrColor[1] && blue === stderrColor[2]) throw new Error(`${optionName} cannot use the reserved stderr color`);
121
123
  }
122
124
  function writePrefixedLine(target, label, color, line, appendNewline) {
123
- const prefix = label == null ? "" : formatPrefix(label, color);
124
- target.write(prefix + line + (appendNewline ? "\n" : ""));
125
+ target.write(formatPrefixedLine(label, color, line, appendNewline));
126
+ }
127
+ function formatPrefixedLine(label, color, line, appendNewline) {
128
+ return (label == null ? "" : formatPrefix(label, color)) + line + (appendNewline ? "\n" : "");
125
129
  }
126
130
  function formatPrefix(label, color) {
127
131
  return `${ansiStyles.color.ansi16m(...color)}[${label}]${ansiStyles.color.close} `;
128
132
  }
129
133
  //#endregion
134
+ //#region src/prefix-stream.ts
135
+ /**
136
+ * Create a transform that prefixes arbitrary output with a procband label.
137
+ *
138
+ * The transform buffers incomplete lines and UTF-8 sequences across chunks.
139
+ * CRLF input is normalized to LF, matching supervised process output, and a
140
+ * final unterminated line is emitted when the writable side ends.
141
+ *
142
+ * @param options Label and optional prefix color.
143
+ * @returns A backpressure-aware Node.js transform stream.
144
+ * @throws When `options.color` is invalid or uses procband's reserved stderr
145
+ * color.
146
+ * @example
147
+ * ```ts
148
+ * import { createPrefixStream } from 'procband'
149
+ *
150
+ * const output = createPrefixStream({ label: 'postgres' })
151
+ * output.pipe(process.stdout)
152
+ * postgres.stdout.pipe(output)
153
+ * ```
154
+ */
155
+ function createPrefixStream(options) {
156
+ validateProcessColor(options.color, "PrefixStreamOptions.color");
157
+ return new PrefixTransform(options.label, resolveProcessColor(options.color));
158
+ }
159
+ var PrefixTransform = class extends Transform {
160
+ decoder = new StringDecoder("utf8");
161
+ label;
162
+ color;
163
+ bufferedText = "";
164
+ constructor(label, color) {
165
+ super();
166
+ this.label = label;
167
+ this.color = color;
168
+ }
169
+ _transform(chunk, _encoding, callback) {
170
+ this.consume(this.decoder.write(chunk));
171
+ callback();
172
+ }
173
+ _flush(callback) {
174
+ this.consume(this.decoder.end());
175
+ if (this.bufferedText.length > 0) {
176
+ this.push(formatPrefixedLine(this.label, this.color, this.bufferedText, false));
177
+ this.bufferedText = "";
178
+ }
179
+ callback();
180
+ }
181
+ consume(decodedText) {
182
+ let text = this.bufferedText + decodedText;
183
+ let newlineIndex = text.indexOf("\n");
184
+ while (newlineIndex >= 0) {
185
+ const rawLine = text.slice(0, newlineIndex);
186
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
187
+ this.push(formatPrefixedLine(this.label, this.color, line, true));
188
+ text = text.slice(newlineIndex + 1);
189
+ newlineIndex = text.indexOf("\n");
190
+ }
191
+ this.bufferedText = text;
192
+ }
193
+ };
194
+ //#endregion
130
195
  //#region src/matching.ts
131
196
  var MatchRegistry = class {
132
197
  subscriptions = /* @__PURE__ */ new Set();
@@ -809,4 +874,4 @@ function propagateFailure(exitCode) {
809
874
  });
810
875
  }
811
876
  //#endregion
812
- export { ProcessExitError, supervise };
877
+ export { ProcessExitError, createPrefixStream, supervise };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procband",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Supervise subprocesses from TypeScript: prefix logs, wait for output patterns, and manage lifecycle/restarts.",
5
5
  "packageManager": "pnpm@11.5.1",
6
6
  "license": "MIT",