-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: unconnected CliIoHost logger-only implementation (#32503)
### Issue #32345 Closes #32345 ### Reason for this change Setting the ground work for our [Programmatic Toolkit](aws/aws-cdk-rfcs#654) ### Description of changes Created an unconnected CLIIoHost with a singular initial action available `notify`. In this implementation of the soon to be defined IoHost we are only writing logs to stdout and stderr. ### Description of how you validated changes Verified via unit testing as this is currently unconnected to the greater AWS CDK CLI ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
Showing
2 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import * as chalk from 'chalk'; | ||
|
||
/** | ||
* Basic message structure for toolkit notifications. | ||
* Messages are emitted by the toolkit and handled by the IoHost. | ||
*/ | ||
interface IoMessage { | ||
/** | ||
* The time the message was emitted. | ||
*/ | ||
readonly time: Date; | ||
|
||
/** | ||
* The log level of the message. | ||
*/ | ||
readonly level: IoMessageLevel; | ||
|
||
/** | ||
* The action that triggered the message. | ||
*/ | ||
readonly action: IoAction; | ||
|
||
/** | ||
* A short code uniquely identifying message type. | ||
*/ | ||
readonly code: string; | ||
|
||
/** | ||
* The message text. | ||
*/ | ||
readonly message: string; | ||
|
||
/** | ||
* If true, the message will be written to stdout | ||
* regardless of any other parameters. | ||
* | ||
* @default false | ||
*/ | ||
readonly forceStdout?: boolean; | ||
} | ||
|
||
export type IoMessageLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; | ||
|
||
export type IoAction = 'synth' | 'list' | 'deploy' | 'destroy'; | ||
|
||
/** | ||
* Options for the CLI IO host. | ||
*/ | ||
interface CliIoHostOptions { | ||
/** | ||
* If true, the host will use TTY features like color. | ||
*/ | ||
useTTY?: boolean; | ||
|
||
/** | ||
* Flag representing whether the current process is running in a CI environment. | ||
* If true, the host will write all messages to stdout, unless log level is 'error'. | ||
* | ||
* @default false | ||
*/ | ||
ci?: boolean; | ||
} | ||
|
||
/** | ||
* A simple IO host for the CLI that writes messages to the console. | ||
*/ | ||
export class CliIoHost { | ||
private readonly pretty_messages: boolean; | ||
private readonly ci: boolean; | ||
|
||
constructor(options: CliIoHostOptions) { | ||
this.pretty_messages = options.useTTY ?? process.stdout.isTTY ?? false; | ||
this.ci = options.ci ?? false; | ||
} | ||
|
||
/** | ||
* Notifies the host of a message. | ||
* The caller waits until the notification completes. | ||
*/ | ||
async notify(msg: IoMessage): Promise<void> { | ||
const output = this.formatMessage(msg); | ||
|
||
const stream = this.getStream(msg.level, msg.forceStdout ?? false); | ||
|
||
return new Promise((resolve, reject) => { | ||
stream.write(output, (err) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Determines which output stream to use based on log level and configuration. | ||
*/ | ||
private getStream(level: IoMessageLevel, forceStdout: boolean) { | ||
// For legacy purposes all log streams are written to stderr by default, unless | ||
// specified otherwise, by passing `forceStdout`, which is used by the `data()` logging function, or | ||
// if the CDK is running in a CI environment. This is because some CI environments will immediately | ||
// fail if stderr is written to. In these cases, we detect if we are in a CI environment and | ||
// write all messages to stdout instead. | ||
if (forceStdout) { | ||
return process.stdout; | ||
} | ||
if (level == 'error') return process.stderr; | ||
return this.ci ? process.stdout : process.stderr; | ||
} | ||
|
||
/** | ||
* Formats a message for console output with optional color support | ||
*/ | ||
private formatMessage(msg: IoMessage): string { | ||
// apply provided style or a default style if we're in TTY mode | ||
let message_text = this.pretty_messages | ||
? styleMap[msg.level](msg.message) | ||
: msg.message; | ||
|
||
// prepend timestamp if IoMessageLevel is DEBUG or TRACE. Postpend a newline. | ||
return ((msg.level === 'debug' || msg.level === 'trace') | ||
? `[${this.formatTime(msg.time)}] ${message_text}` | ||
: message_text) + '\n'; | ||
} | ||
|
||
/** | ||
* Formats date to HH:MM:SS | ||
*/ | ||
private formatTime(d: Date): string { | ||
const pad = (n: number): string => n.toString().padStart(2, '0'); | ||
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; | ||
} | ||
} | ||
|
||
export const styleMap: Record<IoMessageLevel, (str: string) => string> = { | ||
error: chalk.red, | ||
warn: chalk.yellow, | ||
info: chalk.white, | ||
debug: chalk.gray, | ||
trace: chalk.gray, | ||
}; |
Oops, something went wrong.