tftsr-devops_investigation/node_modules/execa/lib/stdio/native.js
Shaun Arman 8839075805 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-14 22:36:25 -05:00

107 lines
3.7 KiB
JavaScript

import {readFileSync} from 'node:fs';
import tty from 'node:tty';
import {isStream as isNodeStream} from 'is-stream';
import {STANDARD_STREAMS} from '../utils/standard-stream.js';
import {bufferToUint8Array} from '../utils/uint-array.js';
import {serializeOptionValue} from '../arguments/fd-options.js';
// When we use multiple `stdio` values for the same streams, we pass 'pipe' to `child_process.spawn()`.
// We then emulate the piping done by core Node.js.
// To do so, we transform the following values:
// - Node.js streams are marked as `type: nodeStream`
// - 'inherit' becomes `process.stdin|stdout|stderr`
// - any file descriptor integer becomes `process.stdio[fdNumber]`
// All of the above transformations tell Execa to perform manual piping.
export const handleNativeStream = ({stdioItem, stdioItem: {type}, isStdioArray, fdNumber, direction, isSync}) => {
if (!isStdioArray || type !== 'native') {
return stdioItem;
}
return isSync
? handleNativeStreamSync({stdioItem, fdNumber, direction})
: handleNativeStreamAsync({stdioItem, fdNumber});
};
// Synchronous methods use a different logic.
// 'inherit', file descriptors and process.std* are handled by readFileSync()/writeFileSync().
const handleNativeStreamSync = ({stdioItem, stdioItem: {value, optionName}, fdNumber, direction}) => {
const targetFd = getTargetFd({
value,
optionName,
fdNumber,
direction,
});
if (targetFd !== undefined) {
return targetFd;
}
if (isNodeStream(value, {checkOpen: false})) {
throw new TypeError(`The \`${optionName}: Stream\` option cannot both be an array and include a stream with synchronous methods.`);
}
return stdioItem;
};
const getTargetFd = ({value, optionName, fdNumber, direction}) => {
const targetFdNumber = getTargetFdNumber(value, fdNumber);
if (targetFdNumber === undefined) {
return;
}
if (direction === 'output') {
return {type: 'fileNumber', value: targetFdNumber, optionName};
}
if (tty.isatty(targetFdNumber)) {
throw new TypeError(`The \`${optionName}: ${serializeOptionValue(value)}\` option is invalid: it cannot be a TTY with synchronous methods.`);
}
return {type: 'uint8Array', value: bufferToUint8Array(readFileSync(targetFdNumber)), optionName};
};
const getTargetFdNumber = (value, fdNumber) => {
if (value === 'inherit') {
return fdNumber;
}
if (typeof value === 'number') {
return value;
}
const standardStreamIndex = STANDARD_STREAMS.indexOf(value);
if (standardStreamIndex !== -1) {
return standardStreamIndex;
}
};
const handleNativeStreamAsync = ({stdioItem, stdioItem: {value, optionName}, fdNumber}) => {
if (value === 'inherit') {
return {type: 'nodeStream', value: getStandardStream(fdNumber, value, optionName), optionName};
}
if (typeof value === 'number') {
return {type: 'nodeStream', value: getStandardStream(value, value, optionName), optionName};
}
if (isNodeStream(value, {checkOpen: false})) {
return {type: 'nodeStream', value, optionName};
}
return stdioItem;
};
// Node.js does not allow to easily retrieve file descriptors beyond stdin/stdout/stderr as streams.
// - `fs.createReadStream()`/`fs.createWriteStream()` with the `fd` option do not work with character devices that use blocking reads/writes (such as interactive TTYs).
// - Using a TCP `Socket` would work but be rather complex to implement.
// Since this is an edge case, we simply throw an error message.
// See https://github.com/sindresorhus/execa/pull/643#discussion_r1435905707
const getStandardStream = (fdNumber, value, optionName) => {
const standardStream = STANDARD_STREAMS[fdNumber];
if (standardStream === undefined) {
throw new TypeError(`The \`${optionName}: ${value}\` option is invalid: no such standard stream.`);
}
return standardStream;
};