Repository navigation
Expand file tree
/
Copy pathroot.go
More file actions
170 lines (156 loc) · 4.87 KB
/
Copy pathroot.go
File metadata and controls
170 lines (156 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Package cli builds the cobra command tree, owns the global flag/error/log
// contract (spec 07, ARCHITECTURE §7.6/§7.9), and is wrapped by fang for styled
// help/errors, auto --version, man, and completions. Commands stay vanilla cobra
// so fang is removable in a day.
package cli
import (
"context"
"fmt"
"io"
"log/slog"
fang "charm.land/fang/v2"
"github.com/spf13/cobra"
"github.com/open-source-cloud/devstack/internal/alias"
"github.com/open-source-cloud/devstack/internal/branding"
"github.com/open-source-cloud/devstack/internal/selfupdate"
"github.com/open-source-cloud/devstack/internal/version"
"github.com/open-source-cloud/devstack/internal/xdg"
)
// GlobalOpts holds the global flags every command renders through.
type GlobalOpts struct {
JSON bool
Quiet bool
Debug bool
Verbose bool
}
// Options parameterize the root command.
type Options struct {
// InvokedAs is the effective name (argv[0] base or --as override) used for
// per-alias branding. The command tree is identical regardless.
InvokedAs string
}
// NewRootCmd assembles the full command tree.
func NewRootCmd(opts Options) *cobra.Command {
if opts.InvokedAs == "" {
opts.InvokedAs = alias.Canonical
}
brand := alias.BrandFor(opts.InvokedAs)
g := &GlobalOpts{}
root := &cobra.Command{
Use: brand.Name,
Short: "Docker-based dev environments with infrastructure shared across projects",
// fang renders Long at the top of help; it exposes no logo option, so the
// ASCII logo lives here (the version line follows it).
Long: branding.Logo() + "\n\n" + brand.Name + " " + version.Version + "\n\n" +
"devstack manages Docker-based development environments and shares infrastructure\n" +
"(one warm Postgres/Redis/MinIO) across many project stacks in a workspace.",
Version: version.String(),
SilenceUsage: true, // fang renders errors; don't dump usage on every failure
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
setupLogging(cmd.ErrOrStderr(), g)
return nil
},
// Fail-silent, throttled update notice on stderr (spec 14). Skipped for
// --json/--quiet and dev builds; at most one network check per day.
PersistentPostRun: func(cmd *cobra.Command, _ []string) {
if g.JSON || g.Quiet {
return
}
selfupdate.Notifier{Current: version.Version, CacheDir: xdg.CacheHome()}.
Notify(cmd.Context(), cmd.ErrOrStderr())
},
}
pf := root.PersistentFlags()
pf.BoolVar(&g.JSON, "json", false, "machine-readable JSON output where supported")
pf.BoolVar(&g.Quiet, "quiet", false, "suppress non-essential output")
pf.BoolVar(&g.Debug, "debug", false, "log every external command (docker/git/sql) and internals")
pf.BoolVar(&g.Verbose, "verbose", false, "more detailed human output")
// Stash globals so subcommands can read them via cmd.Context().
cobra.OnInitialize()
root.AddCommand(
newUpCmd(g),
newDownCmd(g),
newShellCmd(g),
newStatusCmd(g),
newUseCmd(g),
newContextCmd(g),
newRunCmd(g),
newExposeCmd(g),
newPortsCmd(g),
newShellInitCmd(g),
newProjectCmd(g),
newEnvCmd(g),
newLogsCmd(g),
newDashboardCmd(g),
newDnsCmd(g),
newTrustCmd(g),
newTunnelCmd(g),
newSecretsCmd(g),
newDoctorCmd(g),
newConfigCmd(g),
newInitCmd(g),
newGenerateCmd(g),
newIdeCmd(g),
newTemplateCmd(g),
newSharedCmd(g),
newResourceCmd(g),
newDbCmd(g),
newS3Cmd(g),
newQueueCmd(g),
newTopicCmd(g),
newStreamCmd(g),
newAwsCmd(g),
newWsCmd(g),
newWorkspaceCmd(g),
newUninstallCmd(g),
newImportCmd(g),
newSelfCmd(g),
newStoreCmd(g),
newAliasCmd(g),
newTelemetryCmd(g),
newAiCmd(g),
newVersionCmd(),
)
addStubCommands(root, g)
return root
}
// Execute wraps the root command with fang and runs it. The version/commit are
// passed explicitly because fang's `-v`/`--version` derives from debug build info
// otherwise — which renders "unknown (built from source)" for an ldflags-stamped
// binary instead of our actual version.
func Execute(ctx context.Context, root *cobra.Command) error {
return fang.Execute(ctx, root,
fang.WithVersion(version.Version),
fang.WithCommit(version.Commit),
)
}
// setupLogging configures slog based on the global flags. --debug → Debug level
// with source; --quiet → Error only; default → Warn (human output goes through
// command stdout, not the logger).
func setupLogging(w io.Writer, g *GlobalOpts) {
level := slog.LevelWarn
switch {
case g.Debug:
level = slog.LevelDebug
case g.Verbose:
level = slog.LevelInfo
case g.Quiet:
level = slog.LevelError
}
h := slog.NewTextHandler(w, &slog.HandlerOptions{
Level: level,
AddSource: g.Debug,
})
slog.SetDefault(slog.New(h))
}
func newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print version, commit and build date",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
fmt.Fprintln(cmd.OutOrStdout(), version.String())
},
}
}