Files
bare-operating-system/packages/bare-os-booter/lib/host/booter-host-session.js
T
2026-08-18 18:11:28 -04:00

171 lines
4.3 KiB
JavaScript

/**
* Booter session helpers: guest env prefix maps and TTY readline setup.
*/
import {
createBareReadlineQuestion,
createStreamLineReader,
looksLikeInteractiveStdin
} from './cli-readline.js'
import { resolveStdio } from './resolve-stdio.js'
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
/**
* @param {Record<string, string>} env
* @returns {string[]}
*/
export function parseUnionReadPrefixes(env) {
const raw = env && env.BARE_OS_VFS_UNION_PREFIXES
if (raw == null || !String(raw).trim()) return []
return String(raw)
.split(/[,:]+/)
.map((s) => s.trim())
.filter((s) => s.startsWith('/'))
}
/**
* @param {Record<string, string>} env
* @returns {string[]}
*/
export function parseUnionWriteDenyPrefixes(env) {
const raw = env && env.BARE_OS_VFS_UNION_WRITE_DENY
if (raw == null || !String(raw).trim()) return []
return String(raw)
.split(/[,:]+/)
.map((s) => s.trim())
.filter((s) => s.startsWith('/'))
}
/**
* @param {Record<string, string>} env
* @returns {Map<string, number> | null}
*/
export function parseIpcChannelMaxBytesMap(env) {
const raw = env && env.BARE_OS_IPC_CHANNEL_MAX_BYTES
if (raw == null || !String(raw).trim()) return null
try {
const o = JSON.parse(String(raw))
if (!o || typeof o !== 'object') return null
const m = new Map()
for (const [k, v] of Object.entries(o)) {
const n = Number(v)
if (
typeof k === 'string' &&
/^[a-zA-Z0-9._-]+$/.test(k) &&
Number.isFinite(n) &&
n > 0
) {
m.set(k, Math.min(Math.floor(n), 16 * 1024 * 1024))
}
}
return m.size ? m : null
} catch {
return null
}
}
/**
* @returns {Promise<{
* readLine: (p: string) => Promise<string | null>,
* interactiveAvailable: boolean,
* skipInteractive: boolean,
* stdout: import('stream').Writable | null,
* stdin: import('stream').Readable | null
* }>}
*/
export async function createReadLine() {
const { stdin, stdout } = await resolveStdio()
const skipInteractive = globalThis.process?.env?.BARE_OS_SKIP_REPL === '1'
if (skipInteractive) {
return {
readLine: async () => null,
interactiveAvailable: false,
skipInteractive: true,
stdout,
stdin
}
}
if (!stdin || !stdout) {
bareOsHostBooterWarn(
'stdio_missing',
'No stdin/stdout (neither process nor bare-stdio). Cannot run interactive shell.'
)
return {
readLine: async () => null,
interactiveAvailable: false,
skipInteractive: false,
stdout,
stdin
}
}
const ttyRawCapable =
Boolean(stdin.isTTY) &&
typeof stdin.setRawMode === 'function' &&
typeof stdout.write === 'function'
/** Prefer fish-style raw editor on TTY — bare-readline / stream reader only (no Node readline). */
if (ttyRawCapable) {
/** Lazy: avoid bare-readline attaching to stdin before createFishReadLine runs. */
let ttyFallback = undefined
const lazyRawReadLine = async (prompt) => {
if (ttyFallback === undefined) {
try {
ttyFallback = await createBareReadlineQuestion(stdin, stdout)
} catch {
ttyFallback =
looksLikeInteractiveStdin(stdin) &&
typeof stdout.write === 'function'
? createStreamLineReader(stdin, stdout)
: false
}
}
if (ttyFallback === false) return null
return ttyFallback(prompt)
}
return {
readLine: lazyRawReadLine,
interactiveAvailable: true,
skipInteractive: false,
stdout,
stdin
}
}
try {
const readLine = await createBareReadlineQuestion(stdin, stdout)
return {
readLine,
interactiveAvailable: true,
skipInteractive: false,
stdout,
stdin
}
} catch {
/* bare-readline failed to load */
}
if (looksLikeInteractiveStdin(stdin) && typeof stdout.write === 'function') {
return {
readLine: createStreamLineReader(stdin, stdout),
interactiveAvailable: true,
skipInteractive: false,
stdout,
stdin
}
}
bareOsHostBooterWarn(
'line_input_unavailable',
'No line input. Set BARE_OS_SKIP_REPL=1 to exit after boot.'
)
return {
readLine: async () => null,
interactiveAvailable: false,
skipInteractive: false,
stdout,
stdin
}
}