Files
bare-operating-system/packages/bare-os-booter/scripts/fish-tty-repro.js
T
2026-08-18 18:11:34 -04:00

159 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Standalone TTY repro for fish-style readline (lone `>`, missing `[user@host:path]`).
*
* Run in a real terminal (not piped):
* cd packages/bare-os-booter && node scripts/fish-tty-repro.js
* # or: npm run repro:fish-tty -w bare-os-booter
*
* Automated PTY (drains stdout so Node does not block; checks for green prompt):
* python3 scripts/run-fish-repro-pty.py
* # or: npm run repro:fish-tty:pty -w bare-os-booter
*
* Env:
* BARE_OS_REPRO_SKIP_STDERR=1 — omit stderr preamble (isolates stdout/stderr ordering).
* BARE_OS_REPRO_SKIP_PREAMBLE=1 — skip fake kernel lines; only fish prompt.
* BARE_OS_FISH_RESYNC_LINE=0 — disable leading newline before each prompt (default is on).
* DEBUG=1 — verbose fish/TTY logs on the same terminal as the shell (debug-repl.js).
*
* Type a few commands; use `exit` or Ctrl+D to quit. Compare first prompt vs after ^C.
*/
import b4a from 'b4a'
import {
bindReplDebugStream,
unbindReplDebugStream
} from '../lib/cli/debug-repl.js'
import {
createFishReadLine,
disableFishRawMode,
releaseFishStdin
} from '../lib/cli/fish-readline.js'
import { XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT } from '../lib/cli/terminal-escapes.js'
function mockCtx() {
return {
vfs: {
getcwd: () => '/home/user',
resolveLogical: (p) => (p === '' ? '/' : p),
async readdir() {
return []
}
},
drive: {
readdir() {
async function* empty() {
/* no /bin entries */
}
return empty()
}
},
personalDrive: {
async get() {
return null
},
async put() {}
},
b4a,
env: {
HOME: '/home/user',
USER: 'user',
HOSTNAME: 'bare-os',
PATH: '/bin',
PWD: '/home/user'
}
}
}
/** Same idea as executeKernel writeScreen (TTY clear hook). */
function makeWriteScreen(stdout) {
return (chunk) => {
const s = typeof chunk === 'string' ? chunk : String(chunk)
stdout.write(s)
}
}
function kernelPreambleToStdout(stdout) {
const lines = [
'NAME="BareOS"',
'VERSION="0.1.0"',
'VARIANT="hyperdrive-only"',
'',
'Bare operating system — POSIX-ish shell: cd, export, exit, quit | try: help, ls /bin, pwd'
]
for (const line of lines) stdout.write(line + '\n')
}
async function main() {
const stdin = process.stdin
const stdout = process.stdout
if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
process.stderr.write(
'fish-tty-repro: need a TTY with setRawMode (run in a terminal, not a pipe).\n'
)
process.exit(1)
}
const skipStderr = process.env.BARE_OS_REPRO_SKIP_STDERR === '1'
const skipPreamble = process.env.BARE_OS_REPRO_SKIP_PREAMBLE === '1'
if (!skipStderr) {
process.stderr.write(
'\x1b[0m[bare-os-booter] Fish-style TTY line editor active\n'
)
}
const ctx = mockCtx()
const writeScreen = makeWriteScreen(stdout)
if (process.env.DEBUG === '1') bindReplDebugStream(stdout)
const fishRead = await createFishReadLine(ctx, {
stdin,
stdout,
writeScreen
})
if (!fishRead) {
process.stderr.write('createFishReadLine returned null.\n')
process.exit(1)
}
if (!skipPreamble) {
kernelPreambleToStdout(stdout)
}
const readLine = (prompt) => fishRead(prompt)
process.stderr.write(
'\n--- repro ready: first prompts should show full [user@bare-os:~] > if healthy ---\n'
)
try {
while (true) {
const line = await readLine('repro> ')
if (line == null) {
stdout.write('\n')
break
}
const t = line.trim()
if (t === 'exit' || t === 'quit') break
if (t === 'cls' || t === 'clear') {
writeScreen(XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT)
continue
}
if (t) stdout.write(`(cmd) ${t}\n`)
}
} finally {
disableFishRawMode(stdin)
releaseFishStdin(stdin)
unbindReplDebugStream()
}
}
main().catch((err) => {
process.stderr.write(String(err?.stack || err) + '\n')
process.exit(1)
})