Skip to content
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

feat: bootstrap command #52

Merged
merged 31 commits into from
Jan 6, 2025
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
07e16db
feat: bootstrap command
PhearZero Dec 23, 2024
6f9b3c6
refactor: to data directory
PhearZero Dec 23, 2024
8450ac0
Merge branch 'main' into feat/bootstrap-command
PhearZero Dec 24, 2024
b8d4f1c
chore: flatten commands
PhearZero Dec 24, 2024
9e0ab19
test(internal): skip fragile tests
PhearZero Dec 24, 2024
7969133
chore(mac): fix command from refactor
PhearZero Dec 24, 2024
abc963f
refactor: convert all clients to data directory
PhearZero Dec 24, 2024
4088acd
ci: dogfood installer
PhearZero Dec 24, 2024
1bfafbd
ci: simplify workflow
PhearZero Dec 24, 2024
f0db7b9
ci: add catchup to tests
PhearZero Dec 24, 2024
a94d31c
ci: sleep before catchup
PhearZero Dec 24, 2024
1674033
ci: test debug commands
PhearZero Dec 24, 2024
dcf0541
Merge branch 'main' into feat/bootstrap-command
PhearZero Dec 24, 2024
ed97a64
chore: update explanations for not installed
PhearZero Dec 26, 2024
4d9f005
chore: update explanations for running error
PhearZero Dec 26, 2024
4491978
chore: update verbiage for catch up in bootstrap
PhearZero Dec 26, 2024
2af63bb
override github channel dev->beta
Jan 2, 2025
91e58d4
Revert "override github channel dev->beta"
Jan 2, 2025
5623ee2
github: only check release when current build channel is "stable" or …
Jan 2, 2025
4c75760
fix: remove error handler for endpoint file
PhearZero Jan 2, 2025
e781e42
feat: add fast-catchup state to status
PhearZero Jan 2, 2025
c322aca
test: add fast-catchup state to status
PhearZero Jan 2, 2025
73c5483
fast catchup modal styling
Jan 2, 2025
f868f9c
Merge branch 'feat/fast-catchup-modal' of github.af:algorandfoundatio…
Jan 2, 2025
66faa10
fix: use auto style for glamour
PhearZero Jan 2, 2025
accf6c0
test: fix acquired block pointers
PhearZero Jan 2, 2025
2d85a9c
Merge pull request #55 from algorandfoundation/feat/fast-catchup-modal
PhearZero Jan 2, 2025
6516303
Merge pull request #54 from algorandfoundation/fix/dev-channel
PhearZero Jan 2, 2025
cdf3d45
fix: clear fast catchup modal
PhearZero Jan 2, 2025
4bb2b44
"node not found" explanation
Jan 6, 2025
1d4b596
go fmt
Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: clear fast catchup modal
PhearZero committed Jan 2, 2025
commit cdf3d45ef8cb8ddd6cccf232300f6ef5307517bb
6 changes: 6 additions & 0 deletions ui/modal/controller.go
Original file line number Diff line number Diff line change
@@ -39,30 +39,36 @@
m.SetShortLink(msg)
m.SetType(app.TransactionModal)
case *algod.StateModel:
// Clear the catchup modal
if msg.Status.State != algod.FastCatchupState && m.Type == app.ExceptionModal && m.title == "Fast Catchup" {
m.Open = false
m.SetType(app.InfoModal)
}

Check warning on line 46 in ui/modal/controller.go

Codecov / codecov/patch

ui/modal/controller.go#L42-L46

Added lines #L42 - L46 were not covered by tests

m.State = msg
m.transactionModal.State = msg
m.infoModal.State = msg

if m.State.Status.State == algod.FastCatchupState {
m.Open = true
m.SetType(app.ExceptionModal)
m.exceptionModal.Message = style.LightBlue(lipgloss.JoinVertical(lipgloss.Top,
"Please wait while your node syncs with the network.",
"This process can take up to an hour.",
"",
fmt.Sprintf("Accounts Processed: %d / %d", m.State.Status.CatchpointAccountsProcessed, m.State.Status.CatchpointAccountsTotal),
fmt.Sprintf("Accounts Verified: %d / %d", m.State.Status.CatchpointAccountsVerified, m.State.Status.CatchpointAccountsTotal),
fmt.Sprintf("Key Values Processed: %d / %d", m.State.Status.CatchpointKeyValueProcessed, m.State.Status.CatchpointKeyValueTotal),
fmt.Sprintf("Key Values Verified: %d / %d", m.State.Status.CatchpointKeyValueVerified, m.State.Status.CatchpointKeyValueTotal),
fmt.Sprintf("Downloaded blocks: %d / %d", m.State.Status.CatchpointBlocksAcquired, m.State.Status.CatchpointBlocksTotal),
"",
fmt.Sprintf("Sync Time: %ds", m.State.Status.SyncTime/int(time.Second)),
))
m.borderColor = "7"
m.controls = ""
m.title = "Fast Catchup"

} else if m.Type == app.TransactionModal && m.transactionModal.Participation != nil {

Check warning on line 71 in ui/modal/controller.go

Codecov / codecov/patch

ui/modal/controller.go#L52-L71

Added lines #L52 - L71 were not covered by tests
acct, ok := msg.Accounts[m.Address]
// If the previous state is not active
if ok {

Unchanged files with check annotations Beta

Short: bootstrapCmdShort,
Long: bootstrapCmdLong,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
httpPkg := new(api.HttpPkg)
r, _ := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
)
fmt.Print(style.Purple(style.BANNER))
out, err := r.Render(tutorial)
if err != nil {
return err
}
fmt.Println(out)
model := bootstrap.NewModel()
p := tea.NewProgram(model)
var msg *app.BootstrapMsg
go func() {
for {
val := <-model.Outside
switch val.(type) {
case app.BootstrapMsg:
msgVal := val.(app.BootstrapMsg)
msg = &msgVal

Check warning on line 76 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L54-L76

Added lines #L54 - L76 were not covered by tests
}
}
}()
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
if msg == nil {
return nil
}

Check warning on line 86 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L81-L86

Added lines #L81 - L86 were not covered by tests
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))
if msg.Install && !algod.IsInstalled() {
err := algod.Install()
if err != nil {
return err
}

Check warning on line 93 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L88-L93

Added lines #L88 - L93 were not covered by tests
}
// Wait for algod
time.Sleep(10 * time.Second)
if !algod.IsRunning() {
log.Fatal("algod is not running")
}

Check warning on line 101 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L97-L101

Added lines #L97 - L101 were not covered by tests
dataDir, err := algod.GetDataDir("")
if err != nil {
return err
}

Check warning on line 106 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L103-L106

Added lines #L103 - L106 were not covered by tests
// Create the client
client, err := algod.GetClient(dataDir)
if err != nil {
return err
}

Check warning on line 111 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L108-L111

Added lines #L108 - L111 were not covered by tests
if msg.Catchup {
network, err := utils.GetNetworkFromDataDir(dataDir)
if err != nil {
return err
}

Check warning on line 118 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L113-L118

Added lines #L113 - L118 were not covered by tests
// Get the latest catchpoint
catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, network)
if err != nil && err.Error() == api.InvalidNetworkParamMsg {
log.Fatal("This network does not support fast-catchup.")
} else {
log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint))
}

Check warning on line 125 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L120-L125

Added lines #L120 - L125 were not covered by tests
// Start catchup
res, _, err := algod.StartCatchup(ctx, client, catchpoint, nil)
if err != nil {
log.Fatal(err)
}
log.Info(style.Green.Render(res))

Check warning on line 132 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L128-L132

Added lines #L128 - L132 were not covered by tests
}
t := new(system.Clock)
// Fetch the state and handle any creation errors
state, stateResponse, err := algod.NewStateModel(ctx, client, httpPkg)
cmdutils.WithInvalidResponsesExplanations(err, stateResponse, cmd.UsageString())
cobra.CheckErr(err)
// Construct the TUI Model from the State
m, err := ui.NewViewportViewModel(state, client)
cobra.CheckErr(err)
// Construct the TUI Application
p = tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithFPS(120),
)
// Watch for State Updates on a separate thread
// TODO: refactor into context aware watcher without callbacks
go func() {
state.Watch(func(status *algod.StateModel, err error) {
if err == nil {
p.Send(state)
}
if err != nil {
p.Send(state)
p.Send(err)
}

Check warning on line 163 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L136-L163

Added lines #L136 - L163 were not covered by tests
}, ctx, t)
}()
// Execute the TUI Application
_, err = p.Run()
if err != nil {
log.Fatal(err)
}
return nil

Check warning on line 172 in cmd/bootstrap.go

Codecov / codecov/patch

cmd/bootstrap.go#L168-L172

Added lines #L168 - L172 were not covered by tests
},
}
status, response, err := algod.NewStatus(ctx, client, httpPkg)
utils.WithInvalidResponsesExplanations(err, response, cmd.UsageString())
if status.State == algod.FastCatchupState {
log.Fatal(style.Red.Render("Node is currently catching up"))
}

Check warning on line 55 in cmd/catchup/catchup.go

Codecov / codecov/patch

cmd/catchup/catchup.go#L54-L55

Added lines #L54 - L55 were not covered by tests
// Get the Latest Catchpoint
catchpoint, _, err := algod.GetLatestCatchpoint(httpPkg, status.Network)
if err != nil {
log.Fatal(err)
}

Check warning on line 61 in cmd/catchup/catchup.go

Codecov / codecov/patch

cmd/catchup/catchup.go#L60-L61

Added lines #L60 - L61 were not covered by tests
log.Info(style.Green.Render("Latest Catchpoint: " + catchpoint))
// Submit the Catchpoint to the Algod Node, using the StartCatchupParams to skip
res, _, err := algod.StartCatchup(ctx, client, catchpoint, &api.StartCatchupParams{Min: &defaultLag})
if err != nil {
log.Fatal(err)
}

Check warning on line 68 in cmd/catchup/catchup.go

Codecov / codecov/patch

cmd/catchup/catchup.go#L67-L68

Added lines #L67 - L68 were not covered by tests
log.Info(style.Green.Render(res))
},
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
httpPkg := new(api.HttpPkg)
client, err := algod.GetClient(dataDir)

Check warning on line 36 in cmd/catchup/start.go

Codecov / codecov/patch

cmd/catchup/start.go#L36

Added line #L36 was not covered by tests
cobra.CheckErr(err)
status, response, err := algod.NewStatus(ctx, client, httpPkg)
utils.WithInvalidResponsesExplanations(err, response, cmd.UsageString())
if status.State == algod.FastCatchupState {
log.Fatal(style.Red.Render("Node is currently catching up."))

Check warning on line 43 in cmd/catchup/start.go

Codecov / codecov/patch

cmd/catchup/start.go#L43

Added line #L43 was not covered by tests
}
// Get the latest catchpoint
dataDir, err := algod.GetDataDir("")
if err != nil {
return err
}

Check warning on line 56 in cmd/debug.go

Codecov / codecov/patch

cmd/debug.go#L55-L56

Added lines #L55 - L56 were not covered by tests
folderDebug, err := utils.ToDataFolderConfig(dataDir)
if err != nil {
return err
}

Check warning on line 60 in cmd/debug.go

Codecov / codecov/patch

cmd/debug.go#L59-L60

Added lines #L59 - L60 were not covered by tests
info := DebugInfo{
InPath: system.CmdExists("algod"),
IsRunning: algod.IsRunning(),
log.SetOutput(cmd.OutOrStdout())
// Create the dependencies
ctx := context.Background()
client, err := algod.GetClient(algodData)

Check warning on line 58 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L58

Added line #L58 was not covered by tests
cobra.CheckErr(err)
httpPkg := new(api.HttpPkg)
t := new(system.Clock)
// NeedsToBeRunning ensures the Algod software is installed and running before executing the associated Cobra command.
func NeedsToBeRunning(cmd *cobra.Command, args []string) {
if force {
return
}

Check warning on line 105 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L104-L105

Added lines #L104 - L105 were not covered by tests
if !algod.IsInstalled() {
log.Fatal(explanations.NotInstalledErrorMsg)
}

Check warning on line 108 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L107-L108

Added lines #L107 - L108 were not covered by tests
if !algod.IsRunning() {
log.Fatal(explanations.NotRunningErrorMsg)
}

Check warning on line 111 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L110-L111

Added lines #L110 - L111 were not covered by tests
}
// NeedsToBeStopped ensures the operation halts if Algod is not installed or is currently running, unless forced.
func NeedsToBeStopped(cmd *cobra.Command, args []string) {
if force {
return

Check warning on line 117 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L117

Added line #L117 was not covered by tests
}
if !algod.IsInstalled() {
log.Fatal(explanations.NotInstalledErrorMsg)
}

Check warning on line 121 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L120-L121

Added lines #L120 - L121 were not covered by tests
if algod.IsRunning() {
log.Fatal(explanations.RunningErrorMsg)
}

Check warning on line 124 in cmd/root.go

Codecov / codecov/patch

cmd/root.go#L123-L124

Added lines #L123 - L124 were not covered by tests
}
// init initializes the application, setting up logging, commands, and version information.
Run: func(cmd *cobra.Command, args []string) {
log.Info(style.Green.Render("Starting Algod 🚀"))
// Warn user for prompt
log.Warn(style.Yellow.Render(explanations.SudoWarningMsg))

Check warning on line 21 in cmd/start.go

Codecov / codecov/patch

cmd/start.go#L21

Added line #L21 was not covered by tests
err := algod.Start()
if err != nil {
log.Fatal(err)
if viper.GetString("datadir") != "" {
cmd.Long +=
style.LightBlue(" Data: ") + viper.GetString("datadir") + "\n"

Check warning on line 36 in cmd/utils/flags.go

Codecov / codecov/patch

cmd/utils/flags.go#L36

Added line #L36 was not covered by tests
}
return cmd
return "", response, errors.New(response.Status())
}
if response.StatusCode() == 200 {
return response.JSON200.CatchupMessage, response, nil
}

Check warning on line 22 in internal/algod/catchpoint.go

Codecov / codecov/patch

internal/algod/catchpoint.go#L21-L22

Added lines #L21 - L22 were not covered by tests
return response.JSON201.CatchupMessage, response, nil
}
defaultDataDir = filepath.Join(os.Getenv("HOME"), ".algorand")
case "linux":
defaultDataDir = "/var/lib/algorand"
default:
return "", errors.New(UnsupportedOSError)

Check warning on line 25 in internal/algod/client.go

Codecov / codecov/patch

internal/algod/client.go#L24-L25

Added lines #L24 - L25 were not covered by tests
}
var resolvedDir string
if envDataDir == "" {
resolvedDir = defaultDataDir
} else {
resolvedDir = envDataDir
}
} else {
resolvedDir = dataDir
}

Check warning on line 38 in internal/algod/client.go

Codecov / codecov/patch

internal/algod/client.go#L34-L38

Added lines #L34 - L38 were not covered by tests
return resolvedDir, nil
}
func GetClient(dataDir string) (*api.ClientWithResponses, error) {
resolvedDir, err := GetDataDir(dataDir)
if err != nil {
return nil, err
}

Check warning on line 48 in internal/algod/client.go

Codecov / codecov/patch

internal/algod/client.go#L47-L48

Added lines #L47 - L48 were not covered by tests
config, err := utils.ToDataFolderConfig(resolvedDir)
if err != nil {
return nil, err
}

Check warning on line 52 in internal/algod/client.go

Codecov / codecov/patch

internal/algod/client.go#L51-L52

Added lines #L51 - L52 were not covered by tests
apiToken, err := securityprovider.NewSecurityProviderApiKey("header", "X-Algo-API-Token", config.Token)
if err != nil {
}
// Abort on Fast-Catchup
if s.Status.State == FastCatchupState {
// Update current render
cb(s, nil)
// Wait for a while
time.Sleep(time.Second * 2)
// Check status

Check warning on line 125 in internal/algod/state.go

Codecov / codecov/patch

internal/algod/state.go#L121-L125

Added lines #L121 - L125 were not covered by tests
s.Status, _, err = s.Status.Get(ctx)
// Report errors

Check warning on line 127 in internal/algod/state.go

Codecov / codecov/patch

internal/algod/state.go#L127

Added line #L127 was not covered by tests
if err != nil {
cb(nil, err)
}
// Update render after status fetch
cb(s, nil)

Check warning on line 132 in internal/algod/state.go

Codecov / codecov/patch

internal/algod/state.go#L132

Added line #L132 was not covered by tests
continue
}
releaseResponse, err := api.GetGoAlgorandReleaseWithResponse(httpPkg, v.Channel)
// Return the error and response
if err != nil {
return status, releaseResponse, err
}

Check warning on line 170 in internal/algod/status.go

Codecov / codecov/patch

internal/algod/status.go#L169-L170

Added lines #L169 - L170 were not covered by tests
// Update status update field
if releaseResponse != nil && status.Version != releaseResponse.JSON200 {
status.NeedsUpdate = true
var dataFolderConfig DataFolderConfig
var err error
if !IsDataDir(path) {
return dataFolderConfig, nil
}

Check warning on line 30 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L29-L30

Added lines #L29 - L30 were not covered by tests
dataFolderConfig.Path = path
dataFolderConfig.Token, err = GetTokenFromDataDir(path)
if err != nil {
return dataFolderConfig, err
}

Check warning on line 35 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L34-L35

Added lines #L34 - L35 were not covered by tests
dataFolderConfig.Network, err = GetNetworkFromDataDir(path)
if err != nil {
return dataFolderConfig, err
}

Check warning on line 39 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L38-L39

Added lines #L38 - L39 were not covered by tests
dataFolderConfig.Endpoint, _ = GetEndpointFromDataDir(path)
dataFolderConfig.PID, _ = GetPidFromDataDir(path)
file, err := os.ReadFile(path + "/algod.admin.token")
if err != nil {
return token, err
}

Check warning on line 113 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L112-L113

Added lines #L112 - L113 were not covered by tests
token = strings.Replace(string(file), "\n", "", -1)
return token, nil
var network string
file, err := os.ReadFile(path + "/genesis.json")
if err != nil {
return network, err
}

Check warning on line 124 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L123-L124

Added lines #L123 - L124 were not covered by tests
var result map[string]interface{}
err = json.Unmarshal(file, &result)
if err != nil {
return "", err
}

Check warning on line 129 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L128-L129

Added lines #L128 - L129 were not covered by tests
network = fmt.Sprintf("%s-%s", result["network"].(string), result["id"].(string))
pid, err = strconv.Atoi(strings.Replace(string(file), "\n", "", -1))
if err != nil {
return pid, err
}

Check warning on line 146 in internal/algod/utils/utils.go

Codecov / codecov/patch

internal/algod/utils/utils.go#L145-L146

Added lines #L145 - L146 were not covered by tests
return pid, nil
}
type BoostrapSelected BootstrapMsg
// EmitBootstrapSelection waits for and retrieves a new set of table rows from a given channel.
func EmitBootstrapSelection(selection BoostrapSelected) tea.Cmd {
return func() tea.Msg {
return selection
}

Check warning on line 18 in ui/app/bootstrap.go

Codecov / codecov/patch

ui/app/bootstrap.go#L15-L18

Added lines #L15 - L18 were not covered by tests
}
return make(chan tea.Msg)
}
func (o Outside) Emit(msg tea.Msg) tea.Cmd {
return func() tea.Msg {
o <- msg
return nil
}

Check warning on line 15 in ui/app/emitter.go

Codecov / codecov/patch

ui/app/emitter.go#L11-L15

Added lines #L11 - L15 were not covered by tests
}
Question Question
}
func NewModel() Model {
return Model{
Outside: make(app.Outside),
Question: InstallQuestion,
BootstrapMsg: app.BootstrapMsg{
Install: false,
Catchup: false,
},
}

Check warning on line 44 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L36-L44

Added lines #L36 - L44 were not covered by tests
}
func (m Model) Init() tea.Cmd {
return textinput.Blink

Check warning on line 47 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L46-L47

Added lines #L46 - L47 were not covered by tests
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
m.Outside.Emit(msg)
if m.Question == WaitingQuestion {
return m, tea.Sequence(m.Outside.Emit(m.BootstrapMsg), tea.Quit)
}
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "y":
{
switch m.Question {
case InstallQuestion:
m.Question = CatchupQuestion
m.BootstrapMsg.Install = true
case CatchupQuestion:
m.BootstrapMsg.Catchup = true
m.Question = WaitingQuestion
return m, app.EmitBootstrapSelection(app.BoostrapSelected(m.BootstrapMsg))

Check warning on line 67 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L49-L67

Added lines #L49 - L67 were not covered by tests
}
}
case "n":
{
switch m.Question {
case InstallQuestion:
m.Question = CatchupQuestion
m.BootstrapMsg.Install = true
case CatchupQuestion:
m.Question = WaitingQuestion
m.BootstrapMsg.Catchup = true
case WaitingQuestion:
return m, tea.Sequence(m.Outside.Emit(m.BootstrapMsg), tea.Quit)

Check warning on line 81 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L71-L81

Added lines #L71 - L81 were not covered by tests
}
}
case "ctrl+c", "esc", "q":
return m, tea.Quit

Check warning on line 87 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L86-L87

Added lines #L86 - L87 were not covered by tests
}
}
return m, cmd

Check warning on line 90 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L90

Added line #L90 was not covered by tests
}
func (m Model) View() string {
var str string
switch m.Question {
case InstallQuestion:
str = InstallQuestionMsg
case CatchupQuestion:
str = CatchupQuestionMsg

Check warning on line 99 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L93-L99

Added lines #L93 - L99 were not covered by tests
}
msg, _ := glamour.Render(str, "dark")
return msg

Check warning on line 102 in ui/bootstrap/model.go

Codecov / codecov/patch

ui/bootstrap/model.go#L101-L102

Added lines #L101 - L102 were not covered by tests
}