|
1 | 1 | import { runDockerPsNames } from "@effect-template/lib/shell/docker" |
2 | 2 | import { type InputCancelledError, InputReadError } from "@effect-template/lib/shell/errors" |
3 | 3 | import { type AppError, renderError } from "@effect-template/lib/usecases/errors" |
4 | | -import { listProjectItems } from "@effect-template/lib/usecases/projects" |
| 4 | +import { listProjectItems, listProjectStatus } from "@effect-template/lib/usecases/projects" |
5 | 5 | import { NodeContext } from "@effect/platform-node" |
6 | 6 | import { Effect, pipe } from "effect" |
7 | 7 | import { render, useApp, useInput } from "ink" |
@@ -286,38 +286,34 @@ const TuiApp = () => { |
286 | 286 | // CHANGE: guard against non-TTY environments (Docker without -t) |
287 | 287 | // WHY: Ink calls setRawMode(true) on mount — without a TTY stdin does not support |
288 | 288 | // raw mode, causing an unhandled error and a hang in waitUntilExit(). |
289 | | -// Fail fast with a descriptive error instead. |
| 289 | +// Fall back to listProjectStatus in non-interactive environments. |
290 | 290 | // QUOTE(ТЗ): "вечный цикл зависания на TUI из за ошибки Raw mode is not supported" |
291 | 291 | // REF: issue-100 |
292 | 292 | // SOURCE: https://github.com/vadimdemedes/ink/#israwmodesupported |
293 | | -// FORMAT THEOREM: ∀ env: ¬isTTY(env) → fail(InputReadError) ∧ ¬hang |
| 293 | +// FORMAT THEOREM: ∀ env: isTTY(env) → renderTui ∧ ¬isTTY(env) → listProjectStatus |
294 | 294 | // INVARIANT: render() is only called when stdin.isTTY ∧ setRawMode ∈ stdin |
295 | | -export const runMenu = pipe( |
296 | | - Effect.sync(() => { |
297 | | - resumeTui() |
298 | | - }), |
299 | | - Effect.zipRight( |
300 | | - Effect.suspend(() => { |
301 | | - if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") { |
302 | | - return Effect.fail( |
303 | | - new InputReadError({ |
304 | | - message: |
305 | | - "TUI requires a TTY. Attach a terminal: ssh into the container or use `docker run -it`." |
306 | | - }) |
307 | | - ) |
308 | | - } |
309 | | - return Effect.tryPromise({ |
| 295 | +export const runMenu = Effect.suspend(() => { |
| 296 | + if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") { |
| 297 | + return listProjectStatus |
| 298 | + } |
| 299 | + |
| 300 | + return pipe( |
| 301 | + Effect.sync(() => { |
| 302 | + resumeTui() |
| 303 | + }), |
| 304 | + Effect.zipRight( |
| 305 | + Effect.tryPromise({ |
310 | 306 | try: () => render(React.createElement(TuiApp)).waitUntilExit(), |
311 | 307 | catch: (error) => new InputReadError({ message: error instanceof Error ? error.message : String(error) }) |
312 | 308 | }) |
313 | | - }) |
314 | | - ), |
315 | | - Effect.ensuring( |
316 | | - Effect.sync(() => { |
317 | | - leaveTui() |
318 | | - }) |
319 | | - ), |
320 | | - Effect.asVoid |
321 | | -) |
| 309 | + ), |
| 310 | + Effect.ensuring( |
| 311 | + Effect.sync(() => { |
| 312 | + leaveTui() |
| 313 | + }) |
| 314 | + ), |
| 315 | + Effect.asVoid |
| 316 | + ) |
| 317 | +}) |
322 | 318 |
|
323 | 319 | export type MenuError = AppError | InputCancelledError |
0 commit comments