Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 33 additions & 7 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,27 @@ type Client struct {
ghHTTPSAuth *sshgit.Password
}

// Option configures a Client.
type Option func(*clientOptions)

type clientOptions struct {
noColor bool
}

// WithNoColor disables ANSI color codes in verbose output.
func WithNoColor() Option {
return func(o *clientOptions) {
o.noColor = true
}
}

// New constructs a Client from cfg, initialising SSH auth, HTTPS auth, the GitHub API client, and rate limiter.
func New(cfg *config.Config) (*Client, error) {
func New(cfg *config.Config, opts ...Option) (*Client, error) {
co := &clientOptions{}
for _, o := range opts {
o(co)
}

pool := trust.New()

certs, err := pool.CACerts()
Expand Down Expand Up @@ -109,12 +128,19 @@ func New(cfg *config.Config) (*Client, error) {
}
}

t := &scribe.Theme{
Describe: scribe.NoopDecorator,
Print: scribe.NoopDecorator,
Error: func(err error) string {
return fmt.Sprintf("%s %s\n", color.RedFg("Error:"), err)
},
var t *scribe.Theme
if co.noColor {
t = scribe.DefaultTheme
} else {
t = &scribe.Theme{
Describe: func(s string) string {
return color.CyanFg(s)
},
Print: scribe.NoopDecorator,
Error: func(err error) string {
return fmt.Sprintf("%s %s\n", color.RedFg("Error:"), err)
},
}
}

scrb, err := scribe.NewScribe(os.Stdout, t)
Expand Down
156 changes: 107 additions & 49 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,73 +172,131 @@ func authHandler(ctx context.Context, conf *oauth2.Config, state string, token c
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Align — Authorized</title>
<style>
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--card-bg: #161b22;
--border: #30363d;
--text: #e6edf3;
--muted: #8b949e;
--accent: #3fb950;
}
}
@media (prefers-color-scheme: light) {
:root {
--bg: #f6f8fa;
--card-bg: #ffffff;
--border: #d0d7de;
--text: #1f2328;
--muted: #636c76;
--accent: #1a7f37;
}
:root {
color-scheme: light dark;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
background-color: light-dark(#f6f8fa, #0d1117);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 48px 56px;
max-width: 420px;
.header {
background-color: #161b22;
padding: 20px 24px 80px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.logo {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
font-size: 22px;
font-weight: 700;
color: #e6edf3;
letter-spacing: -0.5px;
}
.title {
color: #e6edf3;
margin: 0 0 -30px;
width: 100%;
font-weight: 700;
font-size: 24px;
line-height: 30px;
text-align: center;
box-shadow: 0 4px 24px rgba(0,0,0,0.12);
letter-spacing: -0.48px;
}
.icon {
font-size: 48px;
line-height: 1;
margin-bottom: 20px;
.main {
flex: 1;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 0 20px 60px;
margin-top: -30px;
}
h1 {
font-size: 22px;
font-weight: 600;
margin-bottom: 12px;
color: var(--accent);
.container {
background-color: light-dark(#ffffff, #161b22);
border-radius: 16px;
padding: 20px;
max-width: 600px;
width: 100%;
border: 1px solid light-dark(#d0d7de, #30363d);
}
p {
.success-text,
.error-text {
color: light-dark(#1f2328, #c9d1d9);
font-size: 14px;
color: var(--muted);
line-height: 1.6;
line-height: 20px;
}
.success-content {
background-color: light-dark(#dafbe1, #0d1f17);
border: 2px solid light-dark(#1a7f37, #3fb950);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.error-content {
background-color: light-dark(#fff5f5, #1f0000);
border: 2px solid light-dark(#cf222e, #f85149);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.success-content svg,
.error-content svg {
flex-shrink: 0;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="card">
<div class="icon">✅</div>
<h1>Authorization complete</h1>
<p>Align is connected to GitHub. You can close this tab and return to your terminal.</p>
</div>
<header class="header">
<div class="logo">align</div>
<h1 class="title">GitHub Authorization</h1>
</header>
<main class="main">
<div class="container">
<div id="success-message" class="success-content">
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true">
<circle cx="8" cy="8" r="7" fill="none" stroke="light-dark(#1a7f37, #3fb950)" stroke-width="2"></circle>
<path d="M4.5 7.5 7 10l4-5" fill="none" stroke-linejoin="round" stroke="light-dark(#1a7f37, #3fb950)" stroke-width="2"></path>
</svg>
<div class="success-text">
Authorization complete. Align is connected to GitHub. You can close this tab and return to your terminal.
</div>
</div>
<div id="error-message" class="error-content hidden">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true">
<circle cx="8" cy="8" r="7" fill="none" stroke="light-dark(#cf222e, #f85149)" stroke-width="2"></circle>
<path d="m5.5 5.5 5 5M10.5 5.5l-5 5" stroke="light-dark(#cf222e, #f85149)" stroke-width="2"></path>
</svg>
<div class="error-text">Authorization failed: <span id="error-text"></span></div>
</div>
</div>
</main>
<script>
window.onload = () => {
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
if (error) {
document.getElementById('success-message').classList.add('hidden');
document.getElementById('error-message').classList.remove('hidden');
document.getElementById('error-text').innerText = error;
}
};
</script>
</body>
</html>`

Expand Down
14 changes: 13 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,19 @@ func init() {
RootCmd.AddCommand(stash.StashCmd)

RootCmd.PersistentFlags().BoolP("verbose", "v", false, "show more verbose output")
RootCmd.PersistentFlags().Bool("no-color", false, "disable color in verbose output")

err := viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose"))
if err != nil {
fmt.Printf("Error setting up: %s\n", err)
os.Exit(1)
}

err = viper.BindPFlag("no-color", RootCmd.PersistentFlags().Lookup("no-color"))
if err != nil {
fmt.Printf("Error setting up: %s\n", err)
os.Exit(1)
}
}

func initEnvs() {
Expand Down Expand Up @@ -70,7 +77,12 @@ func setupClient(cmd *cobra.Command, args []string) {
os.Exit(1)
}

clt, err = client.New(c)
var opts []client.Option
if viper.GetBool("no-color") {
opts = append(opts, client.WithNoColor())
}

clt, err = client.New(c, opts...)
if err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
Expand Down
Loading