Fix cli version comparison and improve setup command (#3518)

This commit is contained in:
Anbraten 2024-03-28 10:36:39 +01:00 committed by GitHub
parent 0b76e465c1
commit c2a8464512
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 155 additions and 165 deletions

View file

@ -37,7 +37,7 @@ func Before(c *cli.Context) error {
log.Debug().Msg("Checking for updates ...") log.Debug().Msg("Checking for updates ...")
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, true) newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Failed to check for updates") log.Error().Err(err).Msgf("Failed to check for updates")
return return
@ -58,7 +58,7 @@ func After(_ *cli.Context) error {
select { select {
case <-waitForUpdateCheck.Done(): case <-waitForUpdateCheck.Done():
// When the actual command already finished, we still wait 250ms for the update check to finish // When the actual command already finished, we still wait 250ms for the update check to finish
case <-time.After(time.Millisecond * 250): case <-time.After(time.Millisecond * 500):
log.Debug().Msg("Update check stopped due to timeout") log.Debug().Msg("Update check stopped due to timeout")
cancelWaitForUpdate(errors.New("update check timeout")) cancelWaitForUpdate(errors.New("update check timeout"))
} }

View file

@ -15,6 +15,8 @@ import (
var Command = &cli.Command{ var Command = &cli.Command{
Name: "setup", Name: "setup",
Usage: "setup the woodpecker-cli for the first time", Usage: "setup the woodpecker-cli for the first time",
Args: true,
ArgsUsage: "[server-url]",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "server-url", Name: "server-url",
@ -45,6 +47,9 @@ func setup(c *cli.Context) error {
} }
serverURL := c.String("server-url") serverURL := c.String("server-url")
if serverURL == "" {
serverURL = c.Args().First()
}
if serverURL == "" { if serverURL == "" {
serverURL, err = ui.Ask("Enter the URL of the woodpecker server", "https://ci.woodpecker-ci.org", true) serverURL, err = ui.Ask("Enter the URL of the woodpecker server", "https://ci.woodpecker-ci.org", true)

View file

@ -10,6 +10,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/charmbracelet/huh/spinner"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -37,13 +38,27 @@ func receiveTokenFromUI(c context.Context, serverURL string) (string, error) {
return "", err return "", err
} }
spinnerCtx, spinnerDone := context.WithCancelCause(c)
go func() {
err = spinner.New().
Title("Waiting for token ...").
Context(spinnerCtx).
Run()
if err != nil {
return
}
}()
// wait for token to be received or timeout // wait for token to be received or timeout
select { select {
case token := <-tokenReceived: case token := <-tokenReceived:
spinnerDone(nil)
return token, nil return token, nil
case <-c.Done(): case <-c.Done():
spinnerDone(nil)
return "", c.Err() return "", c.Err()
case <-time.After(5 * time.Minute): case <-time.After(5 * time.Minute):
spinnerDone(nil)
return "", errors.New("timed out waiting for token") return "", errors.New("timed out waiting for token")
} }
} }

View file

@ -1,79 +1,26 @@
package ui package ui
import ( import (
"fmt" "errors"
"strings" "strings"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/huh"
tea "github.com/charmbracelet/bubbletea"
) )
type askModel struct {
prompt string
required bool
textInput textinput.Model
err error
}
func (m askModel) Init() tea.Cmd {
return textinput.Blink
}
func (m askModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter:
if !m.required || (m.required && strings.TrimSpace(m.textInput.Value()) != "") {
return m, tea.Quit
}
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
default:
return m, cmd
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}
func (m askModel) View() string {
return fmt.Sprintf(
"%s\n\n%s\n\n%s",
m.prompt,
m.textInput.View(),
"(esc to quit)",
) + "\n"
}
func Ask(prompt, placeholder string, required bool) (string, error) { func Ask(prompt, placeholder string, required bool) (string, error) {
ti := textinput.New() var input string
ti.Placeholder = placeholder err := huh.NewInput().
ti.Focus() Title(prompt).
ti.CharLimit = 156 Value(&input).
ti.Width = 40 Placeholder(placeholder).Validate(func(s string) error {
if required && strings.TrimSpace(s) == "" {
p := tea.NewProgram(askModel{ return errors.New("required")
prompt: prompt, }
textInput: ti, return nil
required: required, }).Run()
err: nil,
})
_m, err := p.Run()
if err != nil { if err != nil {
return "", err return "", err
} }
m, ok := _m.(askModel) return strings.TrimSpace(input), nil
if !ok {
return "", fmt.Errorf("unexpected model: %T", _m)
}
text := strings.TrimSpace(m.textInput.Value())
return text, nil
} }

View file

@ -1,71 +1,19 @@
package ui package ui
import ( import (
"fmt" "github.com/charmbracelet/huh"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
) )
type confirmModel struct {
confirmed bool
prompt string
err error
}
func (m confirmModel) Init() tea.Cmd {
return textinput.Blink
}
func (m confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.Runes != nil {
switch msg.Runes[0] {
case 'y':
m.confirmed = true
return m, tea.Quit
case 'n':
m.confirmed = false
return m, tea.Quit
}
}
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
default:
return m, nil
}
return m, cmd
}
func (m confirmModel) View() string {
return fmt.Sprintf(
"%s y / n (esc to quit)",
m.prompt,
) + "\n"
}
func Confirm(prompt string) (bool, error) { func Confirm(prompt string) (bool, error) {
p := tea.NewProgram(confirmModel{ var confirm bool
prompt: prompt, err := huh.NewConfirm().
err: nil, Title(prompt).
}) Affirmative("Yes!").
Negative("No.").
_m, err := p.Run() Value(&confirm).Run()
if err != nil { if err != nil {
return false, err return false, err
} }
m, ok := _m.(confirmModel) return confirm, err
if !ok {
return false, fmt.Errorf("unexpected model: %T", _m)
}
return m.confirmed, nil
} }

View file

@ -1,11 +1,9 @@
package update package update
type GithubRelease struct { type VersionData struct {
TagName string `json:"tag_name"` Latest string `json:"latest"`
Assets []struct { Next string `json:"next"`
Name string `json:"name"` RC string `json:"rc"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
} }
type NewVersion struct { type NewVersion struct {
@ -13,4 +11,7 @@ type NewVersion struct {
AssetURL string AssetURL string
} }
const githubReleaseURL = "https://api.github.com/repos/woodpecker-ci/woodpecker/releases/latest" const (
woodpeckerVersionURL = "https://woodpecker-ci.org/version.json"
githubBinaryURL = "https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/woodpecker-cli_%s_%s.tar.gz"
)

View file

@ -10,6 +10,7 @@ import (
"os" "os"
"path" "path"
"runtime" "runtime"
"strings"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -17,14 +18,18 @@ import (
) )
func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) { func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
return checkForUpdate(ctx, woodpeckerVersionURL, force)
}
func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVersion, error) {
log.Debug().Msgf("Current version: %s", version.String()) log.Debug().Msgf("Current version: %s", version.String())
if version.String() == "dev" && !force { if (version.String() == "dev" || strings.HasPrefix(version.String(), "next-")) && !force {
log.Debug().Msgf("Skipping update check for development version") log.Debug().Msgf("Skipping update check for development & next versions")
return nil, nil return nil, nil
} }
req, err := http.NewRequestWithContext(ctx, "GET", githubReleaseURL, nil) req, err := http.NewRequestWithContext(ctx, "GET", versionURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,34 +44,32 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
return nil, errors.New("failed to fetch the latest release") return nil, errors.New("failed to fetch the latest release")
} }
var release GithubRelease var versionData VersionData
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { if err := json.NewDecoder(resp.Body).Decode(&versionData); err != nil {
return nil, err return nil, err
} }
upstreamVersion := versionData.Latest
if strings.HasPrefix(version.String(), "next-") {
upstreamVersion = versionData.Next
} else if strings.HasSuffix(version.String(), "rc-") {
upstreamVersion = versionData.RC
}
installedVersion := strings.TrimPrefix(version.Version, "v")
upstreamVersion = strings.TrimPrefix(upstreamVersion, "v")
// using the latest release // using the latest release
if release.TagName == version.String() && !force { if installedVersion == upstreamVersion && !force {
log.Debug().Msgf("No new version available")
return nil, nil return nil, nil
} }
log.Debug().Msgf("Latest version: %s", release.TagName) log.Debug().Msgf("New version available: %s", upstreamVersion)
assetURL := ""
fileName := fmt.Sprintf("woodpecker-cli_%s_%s.tar.gz", runtime.GOOS, runtime.GOARCH)
for _, asset := range release.Assets {
if fileName == asset.Name {
assetURL = asset.BrowserDownloadURL
log.Debug().Msgf("Found asset for the current OS and arch: %s", assetURL)
break
}
}
if assetURL == "" {
return nil, errors.New("no asset found for the current OS")
}
assetURL := fmt.Sprintf(githubBinaryURL, upstreamVersion, runtime.GOOS, runtime.GOARCH)
return &NewVersion{ return &NewVersion{
Version: release.TagName, Version: upstreamVersion,
AssetURL: assetURL, AssetURL: assetURL,
}, nil }, nil
} }

View file

@ -0,0 +1,61 @@
package update
import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
"go.woodpecker-ci.org/woodpecker/v2/version"
)
func TestCheckForUpdate(t *testing.T) {
version.Version = "1.0.0"
fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/version.json" {
http.NotFound(w, r)
return
}
_, _ = io.WriteString(w, `{"latest": "1.0.1", "next": "1.0.2", "rc": "1.0.3"}`)
}
ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
defer ts.Close()
newVersion, err := checkForUpdate(context.Background(), ts.URL+"/version.json", false)
if err != nil {
t.Fatalf("Failed to check for updates: %v", err)
}
if newVersion == nil || newVersion.Version != "1.0.1" {
t.Fatalf("Expected a new version 1.0.1, got: %s", newVersion)
}
}
func TestDownloadNewVersion(t *testing.T) {
downloadFilePath := "/woodpecker-cli_linux_amd64.tar.gz"
fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != downloadFilePath {
http.NotFound(w, r)
return
}
_, _ = io.WriteString(w, `blob`)
}
ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
defer ts.Close()
file, err := downloadNewVersion(context.Background(), ts.URL+downloadFilePath)
if err != nil {
t.Fatalf("Failed to download new version: %v", err)
}
if file == "" {
t.Fatalf("Expected a file path, got: %s", file)
}
_ = os.Remove(file)
}

9
go.mod
View file

@ -12,8 +12,8 @@ require (
github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/caddyserver/certmagic v0.20.0 github.com/caddyserver/certmagic v0.20.0
github.com/cenkalti/backoff/v4 v4.2.1 github.com/cenkalti/backoff/v4 v4.2.1
github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/huh/spinner v0.0.0-20240306161957-71f31c155b08
github.com/distribution/reference v0.5.0 github.com/distribution/reference v0.5.0
github.com/docker/cli v24.0.9+incompatible github.com/docker/cli v24.0.9+incompatible
github.com/docker/docker v24.0.9+incompatible github.com/docker/docker v24.0.9+incompatible
@ -81,7 +81,10 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.10.2 // indirect github.com/bytedance/sonic v1.10.2 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charmbracelet/bubbles v0.18.0 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
@ -137,7 +140,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect

10
go.sum
View file

@ -40,6 +40,8 @@ github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZF
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@ -48,6 +50,10 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE=
github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA=
github.com/charmbracelet/huh/spinner v0.0.0-20240306161957-71f31c155b08 h1:kO5eMMxyCJ6m7gdpGQ7OomrMdfsKVPgC4aB/focl/HE=
github.com/charmbracelet/huh/spinner v0.0.0-20240306161957-71f31c155b08/go.mod h1:nrBG0YEHaxdbqHXW1xvG1hPqkuac9Eg7RTMvogiXuz0=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
@ -345,8 +351,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col gap-4 m-auto"> <div class="flex flex-col gap-4 m-auto">
<div class="text-center text-wp-text-100"> <div class="text-center text-wp-text-100">
<img src="../../assets/logo.svg" alt="CLI" class="w-32 m-auto mb-8" /> <WoodpeckerLogo preserveAspectRatio="xMinYMin slice" class="w-32 m-auto mb-8" />
<template v-if="state === 'confirm'"> <template v-if="state === 'confirm'">
<h1 class="text-4xl font-bold">{{ $t('login_to_cli') }}</h1> <h1 class="text-4xl font-bold">{{ $t('login_to_cli') }}</h1>
<p class="text-2xl">{{ $t('login_to_cli_description') }}</p> <p class="text-2xl">{{ $t('login_to_cli_description') }}</p>
@ -32,6 +32,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import WoodpeckerLogo from '~/assets/logo.svg?component';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';