mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
* fix(daemon): keep lifecycle operations on the selected instance Restart reconstructed launch inputs and address fallback could redirect a home-selected shutdown to another daemon. Let supervisors publish bound endpoints, retain their worker restart contract, and share captured local lifecycle ownership between CLI and Desktop. * test(daemon): verify bounded desktop readiness and cancellable artifacts * test(cli): verify reload target hints and durable offline pairing * test(daemon): align lifecycle fixtures with ownership and targeting * fix(daemon): preserve reachability when status details time out Provider discovery can outlast a status request after authentication succeeds. Keep local connection facts separate from unavailable details, while explicit remote queries retain their error outcome. Migrate CLI and packaged Desktop fixtures to persistent managed configuration and explicit targets. Isolate generic Git observation from forge discovery and reuse disposable CLI homes on Windows. * test(daemon): observe restart through the worker status RPC The bounded CLI status observation may omit worker details while an authenticated connection remains healthy. Exercise the app-style restart through its full RPC and retain the replacement observation, with a real slow provider probe covering the distinction.
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
/**
|
|
* Command wrapper for automatic output rendering.
|
|
*
|
|
* Wraps command handlers to automatically render results and handle errors.
|
|
*/
|
|
|
|
import type { Command } from "commander";
|
|
import type { AnyCommandResult, CommandError, OutputOptions } from "./types.js";
|
|
import { render, renderError, toCommandError, defaultOutputOptions } from "./render.js";
|
|
import { withGlobalOptions } from "../utils/command-options.js";
|
|
|
|
/** Options that include output settings from global options */
|
|
export interface CommandOptions extends Partial<OutputOptions> {
|
|
daemonTarget: import("../utils/daemon-target.js").DaemonTarget;
|
|
home?: string;
|
|
/** Daemon host target from --host option */
|
|
host?: string;
|
|
/** JSON output flag from --json option */
|
|
json?: boolean;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
function normalizeFormat(raw: unknown): OutputOptions["format"] {
|
|
const value = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
|
|
// Common user expectation: "cli" means "table/human"
|
|
if (value === "cli") return "table";
|
|
|
|
if (value === "table" || value === "json" || value === "yaml") return value;
|
|
|
|
const error: CommandError = {
|
|
code: "INVALID_FORMAT",
|
|
message: `Unsupported output format: ${String(raw)}`,
|
|
details: "Supported formats: table, json, yaml",
|
|
};
|
|
throw error;
|
|
}
|
|
|
|
/** Extract output options from command options */
|
|
function extractOutputOptions(options: CommandOptions): OutputOptions {
|
|
const hasStructuredOutputSchema =
|
|
typeof options.outputSchema === "string" && options.outputSchema.trim().length > 0;
|
|
|
|
if (hasStructuredOutputSchema) {
|
|
return {
|
|
format: "json",
|
|
quiet: false,
|
|
noHeaders: options.headers === false,
|
|
noColor: options.color === false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
format: options.json ? "json" : normalizeFormat(options.format ?? defaultOutputOptions.format),
|
|
quiet: options.quiet ?? defaultOutputOptions.quiet,
|
|
noHeaders: options.headers === false, // Commander uses --no-headers -> headers: false
|
|
noColor: options.color === false, // Commander uses --no-color -> color: false
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Wrap a command handler to automatically render output.
|
|
*
|
|
* The wrapped handler should return a CommandResult. The wrapper will:
|
|
* 1. Call the handler
|
|
* 2. Render the result using the appropriate format
|
|
* 3. Write to stdout
|
|
* 4. Handle errors by rendering to stderr and exiting with code 1
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* program
|
|
* .command('list')
|
|
* .action(withOutput(async (options) => {
|
|
* const data = await fetchData()
|
|
* return { type: 'list', data, schema }
|
|
* }))
|
|
* ```
|
|
*/
|
|
export function withOutput<T, Args extends unknown[]>(
|
|
handler: (...args: [...Args, CommandOptions, Command]) => Promise<AnyCommandResult<T>>,
|
|
): (...args: [...Args, CommandOptions, Command]) => Promise<void> {
|
|
return withGlobalOptions(async (...args) => {
|
|
const options = args[args.length - 2] as CommandOptions;
|
|
const outputOptions = extractOutputOptions(options);
|
|
|
|
try {
|
|
const result = await handler(...args);
|
|
const output = render(result, outputOptions);
|
|
|
|
if (output) {
|
|
process.stdout.write(output + "\n");
|
|
}
|
|
} catch (error) {
|
|
const commandError = toCommandError(error);
|
|
const errorOutput = renderError(commandError, outputOptions);
|
|
process.stderr.write(errorOutput + "\n");
|
|
process.exit(1);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper to create output options from partial input.
|
|
* Useful for testing or manual rendering.
|
|
*/
|
|
export function createOutputOptions(partial: Partial<OutputOptions> = {}): OutputOptions {
|
|
return { ...defaultOutputOptions, ...partial };
|
|
}
|