tftsr-devops_investigation/node_modules/execa/lib/convert/writable.js

91 lines
3.5 KiB
JavaScript
Raw Normal View History

feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
import {Writable} from 'node:stream';
import {callbackify} from 'node:util';
import {getToStream} from '../arguments/fd-options.js';
import {addConcurrentStream, waitForConcurrentStreams} from './concurrent.js';
import {
safeWaitForSubprocessStdout,
waitForSubprocessStdin,
waitForSubprocess,
destroyOtherStream,
} from './shared.js';
// Create a `Writable` stream that forwards to `stdin` and awaits the subprocess
export const createWritable = ({subprocess, concurrentStreams}, {to} = {}) => {
const {subprocessStdin, waitWritableFinal, waitWritableDestroy} = getSubprocessStdin(subprocess, to, concurrentStreams);
const writable = new Writable({
...getWritableMethods(subprocessStdin, subprocess, waitWritableFinal),
destroy: callbackify(onWritableDestroy.bind(undefined, {
subprocessStdin,
subprocess,
waitWritableFinal,
waitWritableDestroy,
})),
highWaterMark: subprocessStdin.writableHighWaterMark,
objectMode: subprocessStdin.writableObjectMode,
});
onStdinFinished(subprocessStdin, writable);
return writable;
};
// Retrieve `stdin` (or other stream depending on `to`)
export const getSubprocessStdin = (subprocess, to, concurrentStreams) => {
const subprocessStdin = getToStream(subprocess, to);
const waitWritableFinal = addConcurrentStream(concurrentStreams, subprocessStdin, 'writableFinal');
const waitWritableDestroy = addConcurrentStream(concurrentStreams, subprocessStdin, 'writableDestroy');
return {subprocessStdin, waitWritableFinal, waitWritableDestroy};
};
export const getWritableMethods = (subprocessStdin, subprocess, waitWritableFinal) => ({
write: onWrite.bind(undefined, subprocessStdin),
final: callbackify(onWritableFinal.bind(undefined, subprocessStdin, subprocess, waitWritableFinal)),
});
// Forwards data from `writable` to `stdin`
const onWrite = (subprocessStdin, chunk, encoding, done) => {
if (subprocessStdin.write(chunk, encoding)) {
done();
} else {
subprocessStdin.once('drain', done);
}
};
// Ensures that the writable `final` and readable `end` events awaits the subprocess.
// Like this, any subprocess failure is propagated as a stream `error` event, instead of being lost.
// The user does not need to `await` the subprocess anymore, but now needs to await the stream completion or error.
// When multiple writables are targeting the same stream, they wait for each other, unless the subprocess ends first.
const onWritableFinal = async (subprocessStdin, subprocess, waitWritableFinal) => {
if (await waitForConcurrentStreams(waitWritableFinal, subprocess)) {
if (subprocessStdin.writable) {
subprocessStdin.end();
}
await subprocess;
}
};
// When `subprocess.stdin` ends/aborts/errors, do the same on `writable`.
export const onStdinFinished = async (subprocessStdin, writable, subprocessStdout) => {
try {
await waitForSubprocessStdin(subprocessStdin);
if (writable.writable) {
writable.end();
}
} catch (error) {
await safeWaitForSubprocessStdout(subprocessStdout);
destroyOtherWritable(writable, error);
}
};
// When `writable` aborts/errors, do the same on `subprocess.stdin`
export const onWritableDestroy = async ({subprocessStdin, subprocess, waitWritableFinal, waitWritableDestroy}, error) => {
await waitForConcurrentStreams(waitWritableFinal, subprocess);
if (await waitForConcurrentStreams(waitWritableDestroy, subprocess)) {
destroyOtherWritable(subprocessStdin, error);
await waitForSubprocess(subprocess, error);
}
};
const destroyOtherWritable = (stream, error) => {
destroyOtherStream(stream, stream.writable, error);
};