-
Notifications
You must be signed in to change notification settings - Fork 12
prisma-next repl: interactive query console (psql replacement) #907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8e6cfe1
62eea8f
67a0a12
ebed124
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,2 @@ | ||
| mp4/ | ||
| tapes/ | ||
| .bin/ | ||
| .cache.json | ||
| mp4/* | ||
| !mp4/repl-demo.mp4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { Command } from 'commander'; | ||
| import { | ||
| addGlobalOptions, | ||
| setCommandDescriptions, | ||
| setCommandExamples, | ||
| } from '../utils/command-helpers'; | ||
| import type { CommonCommandOptions } from '../utils/global-flags'; | ||
| import { parseGlobalFlagsOrExit } from '../utils/global-flags'; | ||
| import { handleResult } from '../utils/result-handler'; | ||
| import { createTerminalUI } from '../utils/terminal-ui'; | ||
|
|
||
| interface ReplCommandOptions extends CommonCommandOptions { | ||
| readonly db?: string; | ||
| readonly config?: string; | ||
| } | ||
|
|
||
| /** Resolves once queued stdout writes have reached the OS, then exits. */ | ||
| function flushAndExit(code: number): void { | ||
| process.exitCode = code; | ||
| process.stdout.write('', () => process.exit(code)); | ||
| } | ||
|
|
||
| export function createReplCommand(): Command { | ||
| const command = new Command('repl'); | ||
| setCommandDescriptions( | ||
| command, | ||
| 'Interactive query console', | ||
| 'Starts an interactive console connected to your database. Type any Prisma Next\n' + | ||
| 'query — SQL lane or ORM lane — and it executes on Enter; builders and plans run\n' + | ||
| 'without .build() or execute(). Tab completes tables, columns, and methods from\n' + | ||
| 'your contract. Plain TypeScript works too. When stdin is piped, each line is\n' + | ||
| 'evaluated in order, results stream to stdout, and the exit code is 1 when any\n' + | ||
| 'line fails.', | ||
| ); | ||
| setCommandExamples(command, [ | ||
| 'prisma-next repl', | ||
| 'prisma-next repl --db $DATABASE_URL', | ||
| `echo "db.sql.public.user.select('id').limit(5)" | prisma-next repl`, | ||
| ]); | ||
| addGlobalOptions(command) | ||
| .option('--db <url>', 'Database connection string') | ||
| .option('--config <path>', 'Path to prisma-next.config.ts') | ||
| .action(async (options: ReplCommandOptions) => { | ||
| const flags = parseGlobalFlagsOrExit(options); | ||
| const ui = createTerminalUI(flags); | ||
|
|
||
| // Loaded lazily so the repl's heavier dependencies (esbuild for TS | ||
| // stripping, the line editor) never tax the startup time of other | ||
| // commands bundled into the same CLI entry. | ||
| const [{ loadReplContext }, { runInteractiveSession }, { runBatchSession }] = | ||
| await Promise.all([ | ||
| import('../repl/load-repl-context'), | ||
| import('../repl/session'), | ||
| import('../repl/batch'), | ||
| ]); | ||
|
|
||
| const result = await loadReplContext({ | ||
| ...(options.db !== undefined ? { db: options.db } : {}), | ||
| ...(options.config !== undefined ? { config: options.config } : {}), | ||
| }); | ||
|
|
||
| if (!result.ok) { | ||
| const exitCode = handleResult(result, flags, ui, () => undefined); | ||
| process.exit(exitCode); | ||
| } | ||
|
|
||
| const context = result.value; | ||
| const interactive = | ||
| flags.interactive !== false && | ||
| process.stdin.isTTY === true && | ||
| process.stdout.isTTY === true; | ||
| const color = flags.color === true; | ||
|
|
||
| let exitCode = 0; | ||
| try { | ||
| if (interactive) { | ||
| await runInteractiveSession({ | ||
| context, | ||
| input: process.stdin, | ||
| output: process.stdout, | ||
| color, | ||
| }); | ||
| } else { | ||
| const { failures } = await runBatchSession({ | ||
| context, | ||
| input: process.stdin, | ||
| output: process.stdout, | ||
| color, | ||
| echo: true, | ||
| }); | ||
| if (failures > 0) exitCode = 1; | ||
| } | ||
| } catch (error) { | ||
| ui.error(error instanceof Error ? error.message : String(error)); | ||
| exitCode = 1; | ||
| } finally { | ||
| await context.close(); | ||
| } | ||
| flushAndExit(exitCode); | ||
| }); | ||
|
|
||
| return command; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,149 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Shared evaluate-and-print pipeline plus the non-interactive (piped stdin) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * batch mode. Stream-parameterized so unit tests can drive it with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * PassThrough streams and a stubbed context. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createReplEvaluator, type ReplEvaluator } from './evaluator'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ReplContext } from './load-repl-context'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { materializeResult } from './materialize'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { runMetaCommand } from './meta-commands'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { replPalette } from './palette'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { renderResultValue } from './render'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Thrown by the print pipeline when a meta command requests exit. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class ExitSignal extends Error {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface EvaluatePrintOptions { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly context: ReplContext; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly output: NodeJS.WritableStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly color: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Gates terminal-only behavior like the .clear escape sequence. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly interactive: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function createSessionEvaluator(context: ReplContext): ReplEvaluator { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createReplEvaluator({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db: context.db, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sql: context.db.sql, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| orm: context.db.orm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enums: context.db.enums, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw: context.db.raw, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function formatError(error: unknown, color: boolean): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const palette = replPalette(color); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof error === 'object' && error !== null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const structured = error as { code?: unknown; message?: unknown }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof structured.code === 'string' && typeof structured.message === 'string') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return palette.red(`✗ ${structured.code}: ${structured.message}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error instanceof Error || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (typeof error === 'object' && error !== null && 'message' in error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const err = error as { name?: string; message?: string }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return palette.red(`✗ ${err.name ?? 'Error'}: ${err.message ?? String(error)}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return palette.red(`✗ ${String(error)}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Bare
As per coding guidelines: "No bare ♻️ Proposed fix using `castAs`+import { castAs } from '`@prisma-next/utils/casts`';
+
export function formatError(error: unknown, color: boolean): string {
const palette = replPalette(color);
if (typeof error === 'object' && error !== null) {
- const structured = error as { code?: unknown; message?: unknown };
+ const structured = castAs<{ code?: unknown; message?: unknown }>(error);
if (typeof structured.code === 'string' && typeof structured.message === 'string') {
return palette.red(`✗ ${structured.code}: ${structured.message}`);
}
}
if (
error instanceof Error ||
(typeof error === 'object' && error !== null && 'message' in error)
) {
- const err = error as { name?: string; message?: string };
+ const err = castAs<{ name?: string; message?: string }>(error);
return palette.red(`✗ ${err.name ?? 'Error'}: ${err.message ?? String(error)}`);
}
return palette.red(`✗ ${String(error)}`);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Evaluates one submission and writes the outcome. Returns true when the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * submission failed (evaluation or execution error). Throws {@link ExitSignal} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * when a meta command requests exit. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function evaluateAndPrint( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evaluator: ReplEvaluator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options: EvaluatePrintOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { context, output, color, interactive } = options; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const meta = runMetaCommand(input, context.schema, { color }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (meta.handled) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (meta.clear && interactive) output.write('\x1b[2J\x1b[H'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (meta.output) output.write(`${meta.output}\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (meta.exit) throw new ExitSignal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await evaluator.evaluate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!result.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.write(`${formatError(result.error, color)}\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Timed around materialization only, so the figure reflects query | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // execution rather than esbuild/vm overhead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const started = performance.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const materialized = await materializeResult(result.value, context.executePlan); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const elapsedMs = performance.now() - started; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rendered = materialized.executed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? renderResultValue(materialized.value, { color, elapsedMs }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : renderResultValue(materialized.value, { color }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.write(`${rendered}\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.write(`${formatError(error, color)}\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface BatchSessionOptions { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly context: ReplContext; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly input: NodeJS.ReadStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly output: NodeJS.WriteStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly color: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Echo inputs before results. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly echo?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface BatchSessionResult { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly failures: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Batch mode: reads full stdin, evaluates statement per line (blank lines | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * and `//` comments skipped), and prints each result. Powers piping: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * `echo "db.sql.public.user.select('id')" | prisma-next repl`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function runBatchSession(options: BatchSessionOptions): Promise<BatchSessionResult> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { context, input, output, color } = options; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const evaluator = createSessionEvaluator(context); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const palette = replPalette(color); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (input.isTTY) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.stderr.write('reading input from stdin — end with Ctrl+D\n'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chunks: Buffer[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for await (const chunk of input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chunks.push(Buffer.from(chunk)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const source = Buffer.concat(chunks).toString('utf8'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let failures = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const line of source.split('\n')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const trimmed = line.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (trimmed.length === 0 || trimmed.startsWith('//')) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (options.echo) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.write(`${palette.dim(`› ${trimmed}`)}\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const failed = await evaluateAndPrint(trimmed, evaluator, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interactive: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (failed) failures++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error instanceof ExitSignal) return { failures }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { failures }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Cleanup failure in
context.close()bypasses exit handling.If
context.close()throws insidefinally, the error escapes uncaught past this wholetry/catch/finally, skippingui.error(...)andflushAndExit(exitCode)entirely — the process's exit code/message reporting becomes inconsistent with every other failure path in this function.🛡️ Proposed fix
} finally { - await context.close(); + try { + await context.close(); + } catch (closeError) { + ui.error(closeError instanceof Error ? closeError.message : String(closeError)); + exitCode = exitCode || 1; + } }📝 Committable suggestion
🤖 Prompt for AI Agents