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
93 changes: 48 additions & 45 deletions cmd/cliflags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,55 +27,58 @@ const (
DevStreamURIDefault = "https://stream.launchdarkly.com"
PortDefault = "8765"

AccessTokenFlag = "access-token"
AnalyticsOptOut = "analytics-opt-out"
BaseURIFlag = "base-uri"
CorsEnabledFlag = "cors-enabled"
CorsOriginFlag = "cors-origin"
DataFlag = "data"
DryRunFlag = "dry-run"
DevStreamURIFlag = "dev-stream-uri"
EmailsFlag = "emails"
EnvironmentFlag = "environment"
FieldsFlag = "fields"
FlagFlag = "flag"
JSONFlag = "json"
OutputFlag = "output"
PortFlag = "port"
ProjectFlag = "project"
RoleFlag = "role"
SyncOnceFlag = "sync-once"
AccessTokenFlag = "access-token"
AnalyticsOptOut = "analytics-opt-out"
BaseURIFlag = "base-uri"
CorsEnabledFlag = "cors-enabled"
CorsOriginFlag = "cors-origin"
DataFlag = "data"
DryRunFlag = "dry-run"
DevStreamURIFlag = "dev-stream-uri"
EmailsFlag = "emails"
EnvironmentFlag = "environment"
FieldsFlag = "fields"
FlagFlag = "flag"
JSONFlag = "json"
OutputFlag = "output"
PortFlag = "port"
ProjectFlag = "project"
RoleFlag = "role"
SyncOnceFlag = "sync-once"
UpdateCheckOptOut = "update-check-opt-out"

AccessTokenFlagDescription = "LaunchDarkly access token with write-level access"
AnalyticsOptOutDescription = "Opt out of analytics tracking"
BaseURIFlagDescription = "LaunchDarkly base URI"
CorsEnabledFlagDescription = "Enable CORS headers for browser-based developer tools (default: false)"
CorsOriginFlagDescription = "Allowed CORS origin. Use '*' for all origins (default: '*')"
DevStreamURIDescription = "Streaming service endpoint that the dev server uses to obtain authoritative flag data. This may be a LaunchDarkly or Relay Proxy endpoint"
DryRunFlagDescription = "Validate the change without persisting it. Returns a preview of the result."
EnvironmentFlagDescription = "Default environment key"
FieldsFlagDescription = "Comma-separated list of top-level fields to include in JSON output (e.g., --fields key,name,kind)"
FlagFlagDescription = "Default feature flag key"
JSONFlagDescription = "Output JSON format (shorthand for --output json)"
OutputFlagDescription = "Output format: json, plaintext, or markdown (default: plaintext in a terminal, json otherwise)"
PortFlagDescription = "Port for the dev server to run on"
ProjectFlagDescription = "Default project key"
SyncOnceFlagDescription = "Only sync new projects. Existing projects will neither be resynced nor have overrides specified by CLI flags applied."
AccessTokenFlagDescription = "LaunchDarkly access token with write-level access"
AnalyticsOptOutDescription = "Opt out of analytics tracking"
BaseURIFlagDescription = "LaunchDarkly base URI"
CorsEnabledFlagDescription = "Enable CORS headers for browser-based developer tools (default: false)"
CorsOriginFlagDescription = "Allowed CORS origin. Use '*' for all origins (default: '*')"
DevStreamURIDescription = "Streaming service endpoint that the dev server uses to obtain authoritative flag data. This may be a LaunchDarkly or Relay Proxy endpoint"
DryRunFlagDescription = "Validate the change without persisting it. Returns a preview of the result."
EnvironmentFlagDescription = "Default environment key"
FieldsFlagDescription = "Comma-separated list of top-level fields to include in JSON output (e.g., --fields key,name,kind)"
FlagFlagDescription = "Default feature flag key"
JSONFlagDescription = "Output JSON format (shorthand for --output json)"
OutputFlagDescription = "Output format: json, plaintext, or markdown (default: plaintext in a terminal, json otherwise)"
PortFlagDescription = "Port for the dev server to run on"
ProjectFlagDescription = "Default project key"
SyncOnceFlagDescription = "Only sync new projects. Existing projects will neither be resynced nor have overrides specified by CLI flags applied."
UpdateCheckOptOutDescription = "Opt out of update check"
)

func AllFlagsHelp() map[string]string {
return map[string]string{
AccessTokenFlag: AccessTokenFlagDescription,
AnalyticsOptOut: AnalyticsOptOutDescription,
BaseURIFlag: BaseURIFlagDescription,
CorsEnabledFlag: CorsEnabledFlagDescription,
CorsOriginFlag: CorsOriginFlagDescription,
DevStreamURIFlag: DevStreamURIDescription,
EnvironmentFlag: EnvironmentFlagDescription,
FlagFlag: FlagFlagDescription,
OutputFlag: OutputFlagDescription,
PortFlag: PortFlagDescription,
ProjectFlag: ProjectFlagDescription,
SyncOnceFlag: SyncOnceFlagDescription,
AccessTokenFlag: AccessTokenFlagDescription,
AnalyticsOptOut: AnalyticsOptOutDescription,
BaseURIFlag: BaseURIFlagDescription,
CorsEnabledFlag: CorsEnabledFlagDescription,
CorsOriginFlag: CorsOriginFlagDescription,
DevStreamURIFlag: DevStreamURIDescription,
EnvironmentFlag: EnvironmentFlagDescription,
FlagFlag: FlagFlagDescription,
OutputFlag: OutputFlagDescription,
PortFlag: PortFlagDescription,
ProjectFlag: ProjectFlagDescription,
SyncOnceFlag: SyncOnceFlagDescription,
UpdateCheckOptOut: UpdateCheckOptOutDescription,
}
}
1 change: 1 addition & 0 deletions cmd/config/testdata/help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Supported settings:
- `port`: Port for the dev server to run on
- `project`: Default project key
- `sync-once`: Only sync new projects. Existing projects will neither be resynced nor have overrides specified by CLI flags applied.
- `update-check-opt-out`: Opt out of update check

Usage:
ldcli config [flags]
Expand Down
41 changes: 41 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/google/uuid"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -39,6 +40,7 @@ import (
"github.com/launchdarkly/ldcli/internal/projects"
"github.com/launchdarkly/ldcli/internal/resources"
"github.com/launchdarkly/ldcli/internal/setup"
"github.com/launchdarkly/ldcli/internal/update"
)

type APIClients struct {
Expand Down Expand Up @@ -362,15 +364,52 @@ See each command's help for details on how to use the generated script.`, rootCm

rootCmd.cmd.SetUsageTemplate(getUsageTemplate())

// Start update check in the background so it runs in parallel with command execution.
type updateResult struct {
info *update.UpdateInfo
}
updateCh := make(chan updateResult, 1)
skipUpdateCheck := viper.GetBool(cliflags.UpdateCheckOptOut) ||
!term.IsTerminal(int(os.Stderr.Fd()))
if !skipUpdateCheck {
go func() {
updateCh <- updateResult{info: update.CheckForUpdate(version)}
}()
}

err = rootCmd.Execute()

const updateCheckTimeout = time.Second
waitForUpdateNotice := func() {
if skipUpdateCheck {
return
}
// Already know there's a newer version? Just say so.
if info := update.CachedUpdate(version); info != nil {
fmt.Fprint(os.Stderr, update.NotificationMessage(info))
return
}
// Otherwise give this check a second. The cache is already on
// disk, so the next command can still show it if we time out.
select {
case result := <-updateCh:
if result.info != nil && result.info.IsNewer {
fmt.Fprint(os.Stderr, update.NotificationMessage(result.info))
}
case <-time.After(updateCheckTimeout):
}
}

var outcome string
switch {
case rootCmd.HelpCalled():
outcome = analytics.HELP
case err != nil:
outcome = analytics.ERROR
fmt.Fprintln(os.Stderr, err.Error())
// Give the background check a moment to write the cache. os.Exit
// would otherwise kill it immediately.
waitForUpdateNotice()
os.Exit(1)
default:
outcome = analytics.SUCCESS
Expand All @@ -392,6 +431,8 @@ See each command's help for details on how to use the generated script.`, rootCm
}

analyticsClient.Wait()

waitForUpdateNotice()
}

// setFlagsFromConfig reads in the config file if it exists and uses any flag values for commands.
Expand Down
24 changes: 16 additions & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ type ReadFile func(name string) ([]byte, error)

// Config represents the data stored in the config file.
type Config struct {
AccessToken string `json:"access-token,omitempty" yaml:"access-token,omitempty"`
AnalyticsOptOut *bool `json:"analytics-opt-out,omitempty" yaml:"analytics-opt-out,omitempty"`
BaseURI string `json:"base-uri,omitempty" yaml:"base-uri,omitempty"`
DevStreamURI string `json:"dev-stream-uri,omitempty" yaml:"dev-stream-uri,omitempty"`
Environment string `json:"environment,omitempty" yaml:"environment,omitempty"`
Flag string `json:"flag,omitempty" yaml:"flag,omitempty"`
Output string `json:"output,omitempty" yaml:"output,omitempty"`
Project string `json:"project,omitempty" yaml:"project,omitempty"`
AccessToken string `json:"access-token,omitempty" yaml:"access-token,omitempty"`
AnalyticsOptOut *bool `json:"analytics-opt-out,omitempty" yaml:"analytics-opt-out,omitempty"`
BaseURI string `json:"base-uri,omitempty" yaml:"base-uri,omitempty"`
DevStreamURI string `json:"dev-stream-uri,omitempty" yaml:"dev-stream-uri,omitempty"`
Environment string `json:"environment,omitempty" yaml:"environment,omitempty"`
Flag string `json:"flag,omitempty" yaml:"flag,omitempty"`
Output string `json:"output,omitempty" yaml:"output,omitempty"`
Project string `json:"project,omitempty" yaml:"project,omitempty"`
UpdateCheckOptOut *bool `json:"update-check-opt-out,omitempty" yaml:"update-check-opt-out,omitempty"`
}

func New(filename string, readFile ReadFile) (Config, error) {
Expand Down Expand Up @@ -131,6 +132,13 @@ func (c Config) Update(kvs []string) (Config, []string, error) {
c.Output = val.String()
case cliflags.ProjectFlag:
c.Project = v
case cliflags.UpdateCheckOptOut:
val, err := strconv.ParseBool(v)
if err != nil {
return Config{}, nil, errors.NewError("update-check-opt-out must be true or false")
}

c.UpdateCheckOptOut = &val
}
}
}
Expand Down
Loading
Loading