Files
pack/core/prettyPrint.go
T

300 lines
7.4 KiB
Go

package core
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/railwayapp/railpack/core/logger"
"github.com/railwayapp/railpack/core/plan"
"github.com/railwayapp/railpack/core/resolver"
"github.com/railwayapp/railpack/core/utils"
)
const (
AnsiRed = "1"
AnsiYellow = "3"
AnsiBlue = "4"
AnsiMagenta = "5"
AnsiCyan = "6"
AnsiWhite = "7"
AnsiGray = "8"
AnsiBrightBlue = "12"
AnsiBrightCyan = "14"
AnsiBrightWhite = "15"
AnsiBrightMagenta = "13"
AnsiDarkGray = "238"
AnsiMediumGray = "245"
)
var (
highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
headerStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(highlight).
MarginTop(1).
Padding(0, 1)
sectionHeaderStyle = lipgloss.NewStyle().
Bold(true).
Width(10).
MarginLeft(2).
MarginTop(2).
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color(AnsiDarkGray)).
BorderBottom(true)
packageNameStyle = lipgloss.NewStyle().
MarginLeft(2).
Foreground(lipgloss.Color(AnsiBrightMagenta))
versionStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiBrightCyan))
sourceStyle = lipgloss.NewStyle()
separatorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiDarkGray)).
Margin(0, 2)
indentedStepHeaderStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiBrightMagenta)).
MarginLeft(2)
commandPrefixStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiMediumGray)).
MarginLeft(4)
commandStyle = lipgloss.NewStyle().
Bold(true)
logInfoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiBrightWhite)).
MarginLeft(2)
logWarnStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiYellow)).
MarginLeft(2)
logErrorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiRed)).
Bold(true).
MarginLeft(2)
metadataStyle = lipgloss.NewStyle().
MarginLeft(2)
metadataSeparatorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(AnsiMediumGray)).
MarginRight(1)
metadataValueStyle = lipgloss.NewStyle().
Bold(true)
)
type PrintOptions struct {
Metadata bool
Version string
}
func PrettyPrintBuildResult(buildResult *BuildResult, options ...PrintOptions) {
output := FormatBuildResult(buildResult, options...)
fmt.Print(output)
}
func FormatBuildResult(br *BuildResult, options ...PrintOptions) string {
var opts PrintOptions
if len(options) > 0 {
opts = options[0]
}
var output strings.Builder
formatHeader(&output, opts.Version)
formatLogs(&output, br.Logs)
formatPackages(&output, br.ResolvedPackages)
formatSteps(&output, br)
formatDeploy(&output, br)
formatMetadata(&output, br.Metadata, opts.Metadata)
output.WriteString("\n\n")
return output.String()
}
func formatHeader(output *strings.Builder, version string) {
header := fmt.Sprintf("Railpack %s", version)
output.WriteString(headerStyle.Render(header))
output.WriteString("\n")
}
func formatLogs(output *strings.Builder, logs []logger.Msg) {
if len(logs) == 0 {
return
}
output.WriteString("\n")
for _, log := range logs {
msg := utils.CapitalizeFirst(log.Msg)
switch log.Level {
case logger.Info:
output.WriteString(logInfoStyle.Render(fmt.Sprintf("↳ %s", msg)))
case logger.Warn:
output.WriteString(logWarnStyle.Render(fmt.Sprintf("⚠ %s", msg)))
case logger.Error:
lines := strings.Split(msg, "\n")
for i, line := range lines {
if i == 0 {
output.WriteString(logErrorStyle.Render(fmt.Sprintf("✖ %s", line)))
} else {
output.WriteString(fmt.Sprintf(" %s", line))
}
if i < len(lines)-1 {
output.WriteString("\n")
}
}
default:
output.WriteString(logInfoStyle.Render(fmt.Sprintf("• %s", msg)))
}
output.WriteString("\n")
}
}
func formatPackages(output *strings.Builder, packages map[string]*resolver.ResolvedPackage) {
if len(packages) == 0 {
return
}
output.WriteString(sectionHeaderStyle.MarginTop(1).Render("Packages"))
output.WriteString("\n")
nameWidth, versionWidth := 1, 1
for _, pkg := range packages {
nameWidth = max(nameWidth, len(pkg.Name))
if pkg.ResolvedVersion != nil {
versionWidth = max(versionWidth, len(*pkg.ResolvedVersion))
}
}
localPackageNameStyle := packageNameStyle.Width(nameWidth).MaxWidth(20)
localVersionStyle := versionStyle.Width(versionWidth).MaxWidth(20)
separator := separatorStyle.Render("│")
for _, pkg := range packages {
name := localPackageNameStyle.Render(pkg.Name)
version := "-"
if pkg.ResolvedVersion != nil {
version = *pkg.ResolvedVersion
}
version = localVersionStyle.Render(version)
source := sourceStyle.Render(formatSource(pkg))
output.WriteString(fmt.Sprintf("%s%s%s%s%s", name, separator, version, separator, source))
output.WriteString("\n")
}
}
func formatSteps(output *strings.Builder, br *BuildResult) {
stepsToPrint := getStepsToPrint(br)
if len(stepsToPrint) == 0 {
return
}
output.WriteString(sectionHeaderStyle.MarginTop(1).Render("Steps"))
output.WriteString("\n")
for i, step := range stepsToPrint {
commands := getCommandsToPrint(step.Commands)
if len(commands) == 0 {
continue
}
currentStepStyle := indentedStepHeaderStyle
if i > 0 {
currentStepStyle = currentStepStyle.MarginTop(1)
}
output.WriteString(currentStepStyle.Render(fmt.Sprintf("▸ %s", step.Name)))
output.WriteString("\n")
for _, cmd := range commands {
cmdText := cmd.Cmd
if cmd.CustomName != "" {
cmdText = cmd.CustomName
}
output.WriteString(fmt.Sprintf("%s %s", commandPrefixStyle.Render("$"), commandStyle.Render(cmdText)))
output.WriteString("\n")
}
}
}
func formatDeploy(output *strings.Builder, br *BuildResult) {
if br.Plan != nil && br.Plan.Deploy.StartCmd != "" {
output.WriteString(sectionHeaderStyle.MarginTop(1).Render("Deploy"))
output.WriteString("\n")
output.WriteString(fmt.Sprintf("%s %s", commandPrefixStyle.Render("$"), commandStyle.Render(br.Plan.Deploy.StartCmd)))
}
}
func formatMetadata(output *strings.Builder, metadata map[string]string, showMetadata bool) {
if !showMetadata || metadata == nil || len(metadata) == 0 {
return
}
output.WriteString(sectionHeaderStyle.MarginTop(2).Render("Metadata"))
output.WriteString("\n")
separator := metadataSeparatorStyle.Render(":")
for key, value := range metadata {
output.WriteString(metadataStyle.Render(fmt.Sprintf("%s%s%s", key, separator, metadataValueStyle.Render(value))))
output.WriteString("\n")
}
}
func getStepsToPrint(br *BuildResult) []*plan.Step {
execSteps := []*plan.Step{}
if br.Plan == nil {
return execSteps
}
for _, step := range br.Plan.Steps {
if !strings.HasPrefix(step.Name, "packages") && step.Commands != nil {
commands := getCommandsToPrint(step.Commands)
if len(commands) > 0 {
execSteps = append(execSteps, &step)
}
}
}
return execSteps
}
func getCommandsToPrint(commands []plan.Command) []plan.ExecCommand {
if commands == nil {
return []plan.ExecCommand{}
}
execCommands := []plan.ExecCommand{}
for _, cmd := range commands {
if execCmd, ok := cmd.(plan.ExecCommand); ok {
execCommands = append(execCommands, execCmd)
}
}
return execCommands
}
func formatSource(pkg *resolver.ResolvedPackage) string {
if pkg.RequestedVersion != nil {
return fmt.Sprintf("%s (%s)", pkg.Source, *pkg.RequestedVersion)
}
return pkg.Source
}
func max(a, b int) int {
if a > b {
return a
}
return b
}