UNPKG

aws-cdk

Version:

AWS CDK CLI, the command line tool for CDK apps

464 lines (463 loc) 19.5 kB
import type { Agent } from 'node:https'; import { RequireApproval } from '@aws-cdk/cloud-assembly-schema'; import type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest, ToolkitAction } from '@aws-cdk/toolkit-lib'; import type { IoHelper, IoMessageMaker, IoRequestMaker, IoDefaultMessages } from '../../../lib/api-private'; import type { Context } from '../../api/context'; import { StackActivityProgress } from '../../commands/deploy'; import { TelemetrySession } from '../telemetry/session'; export type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest }; /** * The current action being performed by the CLI. 'none' represents the absence of an action. */ type CliAction = ToolkitAction | 'context' | 'docs' | 'flags' | 'notices' | 'version' | 'cli-telemetry' | 'none'; export interface CliIoHostProps { /** * The initial Toolkit action the hosts starts with. * * @default 'none' */ readonly currentAction?: CliAction; /** * Determines the verbosity of the output. * * The CliIoHost will still receive all messages and requests, * but only the messages included in this level will be printed. * * @default 'info' */ readonly logLevel?: IoMessageLevel; /** * Overrides the automatic TTY detection. * * When TTY is disabled, the CLI will have no interactions or color. * * @default - determined from the current process */ readonly isTTY?: boolean; /** * Whether the CliIoHost is running in CI mode. * * In CI mode, all non-error output goes to stdout instead of stderr. * Set to false in the CliIoHost constructor it will be overwritten if the CLI CI argument is passed * * @default - determined from the environment, specifically based on `process.env.CI` */ readonly isCI?: boolean; /** * In what scenarios should the CliIoHost ask for approval * * @default RequireApproval.BROADENING */ readonly requireDeployApproval?: RequireApproval; /** * The initial Toolkit action the hosts starts with. * * @default StackActivityProgress.BAR */ readonly stackProgress?: StackActivityProgress; /** * Whether the CLI should attempt to automatically respond to prompts. * * When true, operation will usually proceed without interactive confirmation. * Confirmations are responded to with yes. Other prompts will respond with the default value. * * @default false */ readonly autoRespond?: boolean; } /** * A type for configuring a target stream */ export type TargetStream = 'stdout' | 'stderr' | 'drop'; /** * The result a message listener may return to influence how a message is handled. * * A listener may update the message _text_ and/or its _level_; it cannot change * any other field of the message (such as its `code`), which keeps the * code-keyed listener registry valid. */ export interface MessageListenerResult { /** * Replace the text that is printed for this message. * * @default - the message text is left unchanged */ readonly message?: string; /** * Override the level of this message. * * The new level is used for both verbosity filtering and stream selection, so * this can move a message between stdout/stderr (e.g. downgrade a `result` to * `info`). The `code` is intentionally left unchanged. * * @default - the message level is left unchanged */ readonly level?: IoMessageLevel; /** * Skip the default handling of the message. * * For a notification this means it is not written to a stream. For a request * it stops processing entirely: the user is not prompted, nothing is written, * and the request resolves with its (possibly `respond`-overridden) default * response. * * @default false */ readonly preventDefault?: boolean; /** * For requests only: the value to resolve the request with. It is folded into * the request's default response and skips the prompt (the request is treated * as not promptable). The question is still written unless `preventDefault` is * also set. Ignored for plain notifications. * * The presence of the key is what matters, so `false`/`0`/`''` are valid * answers. Use the `respond`/`respondOnce` helpers for the common case. * * @default - this listener does not supply a response */ readonly respond?: unknown; } /** * What a message listener may return: nothing, a `MessageListenerResult`, or a * `Promise` of either. * * Listeners may be async. The host awaits each listener before running the * next, so registration order — and the cumulative effect on the message — is * preserved regardless of whether listeners are sync or async. */ export type MessageListenerResultOrPromise = void | MessageListenerResult | Promise<void | MessageListenerResult>; /** * Selects which messages a listener applies to. * * Either a message/request *maker* — the listener fires for messages with that * maker's `code` (the original behavior) — or a custom *predicate* over the * message. A maker's `.is` type guard (e.g. `IO.CDK_TOOLKIT_I7010.is`) is a * convenient predicate, but any `(msg) => boolean` works (e.g. to match a family * of codes, or on the message level). */ export type MessageSelector<T> = IoMessageMaker<T> | IoRequestMaker<T, any> | ((msg: IoMessage<any>) => boolean); /** * How an IoHost processed a single message or request. * * This describes the message *as the host handled it*, which can differ from * what was emitted: listeners may rewrite the text or level, prevent it from * being written at all, or (for requests) answer it on the user's behalf. * * Both notifications (`notify`) and requests (`requestResponse`) are reported, * so an observer sees the complete, ordered stream the host handled. Use * `type` to tell them apart. */ export interface IoMessageObservation { /** * Whether this observation describes a plain notification (`notify`) or a * request that asked for a response (`requestResponse`). */ readonly type: 'notify' | 'request'; /** * The message exactly as it was emitted to the host (before any listeners). */ readonly emitted: IoMessage<unknown>; /** * The message after the host's listeners ran (text and/or level may differ). */ readonly effective: IoMessage<unknown>; /** * Whether a listener prevented this message from being written, i.e. the user * would not see it. Always `false` for requests (a request is reported once * it has been resolved, regardless of how it was answered). */ readonly dropped: boolean; } /** * An IoHost whose message handling can be observed. * * This is a CLI-internal contract used by tests to record the *effective*, * user-facing message stream (after listeners) without reaching into host * internals. It is intentionally separate from `IIoHost` so that the recorder * can work with any `IIoHost` and only enrich its output when the host also * implements this interface. */ export interface ObservableIoHost { /** * Register an observer that is invoked for every message the host handles — * both notifications and requests — with the disposition the host computed * for it (its effective form after listeners and whether it was dropped). For * a request, the resolved answer is the effective message's `defaultResponse`. * Returns a function that removes the observer again. */ observeMessages(observer: (observation: IoMessageObservation) => void): () => void; } /** * A simple IO host for the CLI that writes messages to the console. */ export declare class CliIoHost implements IIoHost, ObservableIoHost { /** * Returns the singleton instance */ static instance(props?: CliIoHostProps, forceNew?: boolean): CliIoHost; /** * Returns the singleton instance if it exists */ static get(): CliIoHost | undefined; /** * Singleton instance of the CliIoHost */ private static _instance; /** * The current action being performed by the CLI. */ currentAction: CliAction; /** * Whether the CliIoHost is running in CI mode. * * In CI mode, all non-error output goes to stdout instead of stderr. */ isCI: boolean; /** * Whether the host can use interactions and message styling. */ isTTY: boolean; /** * The current threshold. * * Messages with a lower priority level will be ignored. */ logLevel: IoMessageLevel; /** * The conditions for requiring approval in this CliIoHost. */ requireDeployApproval: RequireApproval; /** * Configure the target stream for notices * * (Not a setter because there's no need for additional logic when this value * is changed yet) */ noticesDestination: TargetStream; private _progress; private activityPrinter?; private corkedCounter; private readonly corkedLoggingBuffer; private readonly messageListeners; private readonly messageObservers; private corkReplaying; private readonly autoRespond; /** * The telemetry session object * * Will remain `undefined` if the user has disabled telemetry. */ telemetry?: TelemetrySession; private constructor(); startTelemetry(args: any, context: Context, proxyAgent?: Agent): Promise<void>; /** * Update the stackProgress preference. */ set stackProgress(type: StackActivityProgress); /** * Gets the stackProgress value. * * This takes into account other state of the ioHost, * like if isTTY and isCI. */ get stackProgress(): StackActivityProgress; get defaults(): IoDefaultMessages; asIoHelper(): IoHelper; /** * Executes a block of code with corked logging. All log messages during execution * are buffered and only written when all nested cork blocks complete (when CORK_COUNTER reaches 0). * The corking is bound to the specific instance of the CliIoHost. * * @param block - Async function to execute with corked logging * @returns Promise that resolves with the block's return value */ withCorkedLogging<T>(block: () => Promise<T>): Promise<T>; /** * Register a listener that is invoked for every message with the given code. * * The listener may return a `MessageListenerResult` to update the message * text and/or prevent the default processing (writing it to a stream); * returning nothing leaves the message untouched. The listener may be async * (return a `Promise`); the host awaits it before processing the message * further. Returns a function that removes the listener again. * * @example * const dispose = ioHost.on(IO.CDK_TOOLKIT_I2901, async (msg) => { * myCount += msg.data.stacks.length; * await persist(myCount); * }); * * @example * // Match with a custom predicate instead of a code, e.g. a maker's `.is`: * const dispose = ioHost.on(IO.CDK_TOOLKIT_I7010.is, (msg) => ({ respond: true })); */ on<T>(selector: IoMessageMaker<T> | IoRequestMaker<T, any> | ((msg: IoMessage<any>) => msg is IoMessage<T>), listener: (msg: IoMessage<T>) => MessageListenerResultOrPromise): () => void; on(predicate: (msg: IoMessage<any>) => boolean, listener: (msg: IoMessage<unknown>) => MessageListenerResultOrPromise): () => void; /** * Register an observer that is invoked for every message the host handles — * both notifications and requests — with the disposition the host computed * for it (its effective form after listeners and whether it was dropped). For * a request, the resolved answer is the effective message's `defaultResponse`. * Returns a function that removes the observer. * * @see ObservableIoHost */ observeMessages(observer: (observation: IoMessageObservation) => void): () => void; /** * Like `on`, but the listener is automatically removed after it has been * invoked once. */ once<T>(selector: IoMessageMaker<T> | IoRequestMaker<T, any> | ((msg: IoMessage<any>) => msg is IoMessage<T>), listener: (msg: IoMessage<T>) => MessageListenerResultOrPromise): () => void; once(predicate: (msg: IoMessage<any>) => boolean, listener: (msg: IoMessage<unknown>) => MessageListenerResultOrPromise): () => void; /** * Remove every message listener registered via `on`/`once`/`rewrite`/`respond`. * * The host's own internal listeners (such as stack-activity routing) are kept, * so the host keeps working afterwards. Message observers registered via * `observeMessages` are a separate mechanism and are not affected. * * This is mainly useful for tests that share the singleton host and need to * reset listener state between cases (a leftover listener would otherwise * leak into the next test). */ removeAllListeners(): void; /** * Answer a request (by its code) on the user's behalf with a fixed value, so * the host does not prompt. Syntactic sugar for an `on` listener returning * `{ respond: value, preventDefault: suppressQuestion }`; for conditional * answers or to also reword the question, use `on`/`once` directly. Returns a * function that removes the responder again. * * @param suppressQuestion - whether to also suppress writing the question text. * Defaults to `true` (answer silently). Pass `false` to still surface the * question while answering it. * * @example * // Under --force, auto-confirm the destroy prompt without prompting. * const dispose = ioHost.respond(IO.CDK_TOOLKIT_I7010, true); */ respond<T, U>(code: IoRequestMaker<T, U>, value: U, suppressQuestion?: boolean): () => void; /** * Like `respond`, but the answer is given only once and then removed. */ respondOnce<T, U>(code: IoRequestMaker<T, U>, value: U, suppressQuestion?: boolean): () => void; /** * Register a formatter that replaces the printed text of messages with the * given code. This lets a caller define _how_ a toolkit message is presented * without the IoHost needing to know about it. * * Optionally pass a `level` to also override the message's level (which moves * it between stdout/stderr and changes verbosity filtering). For the rarer * case of overriding only the level, use `on`/`once` returning `{ level }`. * * Syntactic sugar for an `on` listener that returns `{ message, level? }`. * Returns a function that removes the formatter again. * * @example * const dispose = ioHost.rewrite(IO.CDK_TOOLKIT_I2901, (msg) => * serializeStructure(msg.data.stacks, true)); */ rewrite<T>(code: IoMessageMaker<T> | IoRequestMaker<T, any>, formatter: (msg: IoMessage<T>) => string, level?: IoMessageLevel): () => void; /** * Like `rewrite`, but the formatter is automatically removed after it has * been applied once. */ rewriteOnce<T>(code: IoMessageMaker<T> | IoRequestMaker<T, any>, formatter: (msg: IoMessage<T>) => string, level?: IoMessageLevel): () => void; /** * Add a listener to the registry and return a function that removes it. */ private addMessageListener; /** * Run every registered listener that matches the message, in registration * order. A listener matches by its code (maker) or its custom predicate. * * A listener may update the message text/level (passed on to subsequent * listeners and the rest of the pipeline), prevent the default processing, or * (for requests) answer it. `once` listeners are removed after they have run. * Matching is decided against the message as emitted, so a rewrite by an * earlier listener does not change which later listeners apply. * * Returns the (possibly updated) message, whether the default processing was * prevented, and whether a listener answered the request (and with what). */ private applyMessageListeners; /** * Notifies the host of a message. * The caller waits until the notification completes. */ notify(msg: IoMessage<unknown>): Promise<void>; /** * Notify every registered message observer of how a message or request was * handled. A no-op when nothing is observing (i.e. outside of tests), so the * surrounding hot paths pay nothing in production. */ private notifyObservers; /** * Write a (already listener-processed) message to its target stream, honoring * the log level and corked-logging buffer. Shared by `notify` and the * non-prompting `requestResponse` path. */ private writeMessage; private maybeEmitTelemetry; /** * Route stack-activity messages to the activity printer (progress bar or * event list) rather than writing them to a stream. * * Implemented as listeners that handle the message via the printer and * prevent the default processing, so the rest of the pipeline does not also * emit them. The printer is created lazily on the first stack-activity message. */ private routeStackActivityToPrinter; /** * Detect special messages encode information about whether or not * they require approval */ private skipApprovalStep; /** * Determines the output stream, based on message and configuration. */ private selectStream; /** * Determines the output stream, based on message level and configuration. */ private selectStreamFromLevel; /** * Notifies the host of a message that requires a response. * * Registered listeners run first: a listener may reword the question (its text * or level) or answer it outright via `respond` (e.g. `--force` auto-confirms * a destroy). If no listener answers and the host cannot prompt, the suggested * default response is used. */ requestResponse<DataType, ResponseType>(msg: IoRequest<DataType, ResponseType>): Promise<ResponseType>; /** * Resolve a request to its response: a listener's answer if one was given, * otherwise the answer prompted from the user, otherwise the suggested * default when the host cannot prompt. * * Kept separate from `requestResponse` so the response can be observed in a * single place regardless of which of these paths produced it. */ private resolveRequest; /** * Formats a message for console output with optional color support */ private formatMessage; /** * Formats date to HH:MM:SS */ private formatTime; /** * Get an instance of the ActivityPrinter */ private makeActivityPrinter; } /** * Combine several selectors into a single predicate that matches a message when * *any* of them matches. Each selector may be a maker (matched by its `code`) * or a predicate. * * Useful for one listener that spans multiple codes, e.g. * `ioHost.on(matchAny(IO.CDK_TOOLKIT_I5501, IO.CDK_TOOLKIT_I5502), listener)`. */ export declare function matchAny(...selectors: MessageSelector<any>[]): (msg: IoMessage<unknown>) => boolean;