Skip to content

Commit

Permalink
fix(cli): relevant usage on CLI errors, remove app archive on `deploy…
Browse files Browse the repository at this point in the history
…`, `app list` use configured organization (#62)

* fix(cli): show proper usage message on errors

* Show proper sub-commands usage text instead of the root usage when errors occur
* Store CLI arguments in objects, to avoid accidentally referring to them within the packages
* Extract repeated code for app identifier arguments
* Move logic for refreshing access tokens to the `auth` package
* Move logic for validating app identifiers into the `appident` package
* Move organization creation wizard logic into the `cmd/organization/create` package to simplify
* Split several commands logic, so that `cmd.go` contains the actual command definition

* app list: use configured organization and fix organization flag usage text

* refactor root command
  • Loading branch information
jfeodor authored Dec 10, 2024
1 parent d5e057a commit b6df1a0
Show file tree
Hide file tree
Showing 41 changed files with 685 additions and 585 deletions.
36 changes: 24 additions & 12 deletions cmd/app/list/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,41 @@ import (
"numerous.com/cli/cmd/errorhandling"
"numerous.com/cli/cmd/output"
"numerous.com/cli/internal/app"
"numerous.com/cli/internal/config"
"numerous.com/cli/internal/gql"
)

var argOrganizationSlug string
var cmdArgs struct{ organizationSlug string }

var Cmd = &cobra.Command{
Use: "list",
Short: "List all your apps (login required)",
RunE: func(cmd *cobra.Command, args []string) error {
if argOrganizationSlug == "" {
output.PrintError("Missing organization argument.", "")
cmd.Usage() // nolint:errcheck
RunE: func(cmd *cobra.Command, args []string) error { return run(cmd) },
}

func run(cmd *cobra.Command) error {
orgSlug := cmdArgs.organizationSlug
if orgSlug == "" {
orgSlug = config.OrganizationSlug()
}

if orgSlug == "" {
output.PrintError(
"No organization provided or configured",
"Specify an organization with the --organization flag, or configure one with \"numerous config\".",
)
cmd.Usage() // nolint:errcheck

return errorhandling.ErrAlreadyPrinted
}
return errorhandling.ErrAlreadyPrinted
}

service := app.New(gql.NewClient(), nil, http.DefaultClient)
err := list(cmd.Context(), service, AppListInput{OrganizationSlug: argOrganizationSlug})
service := app.New(gql.NewClient(), nil, http.DefaultClient)
err := list(cmd.Context(), service, AppListInput{OrganizationSlug: cmdArgs.organizationSlug})

return errorhandling.ErrorAlreadyPrinted(err)
},
return errorhandling.ErrorAlreadyPrinted(err)
}

func init() {
Cmd.Flags().StringVarP(&argOrganizationSlug, "organization", "o", "", "The organization slug identifier to list app from.")
flags := Cmd.Flags()
flags.StringVarP(&cmdArgs.organizationSlug, "organization", "o", "", "The organization slug identifier to list apps from. List available organizations with 'numerous organization list'.")
}
24 changes: 14 additions & 10 deletions cmd/app/share/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,30 @@ const longFormat string = `Creates a shared URL for the specified app.
%s
`

var long string = fmt.Sprintf(longFormat, usage.AppIdentifier("to create a shared URL for"), usage.AppDirectoryArgument)
var cmdActionText = "to create a shared URL for"

var long string = fmt.Sprintf(longFormat, usage.AppIdentifier(cmdActionText), usage.AppDirectoryArgument)

var Cmd = &cobra.Command{
Use: "share [app directory]",
RunE: run,
Short: "Create app shared URL",
Long: long,
Args: args.OptionalAppDir(&appDir),
Args: args.OptionalAppDir(&cmdArgs.appDir),
}

var (
orgSlug string
appSlug string
appDir string
)
var cmdArgs struct {
appIdent args.AppIdentifierArg
appDir string
}

func run(cmd *cobra.Command, args []string) error {
service := app.New(gql.NewClient(), nil, http.DefaultClient)
input := Input{AppDir: appDir, AppSlug: appSlug, OrgSlug: orgSlug}
input := Input{
AppDir: cmdArgs.appDir,
AppSlug: cmdArgs.appIdent.AppSlug,
OrgSlug: cmdArgs.appIdent.OrganizationSlug,
}

err := shareApp(cmd.Context(), service, input)

Expand All @@ -46,6 +51,5 @@ func run(cmd *cobra.Command, args []string) error {

func init() {
flags := Cmd.Flags()
flags.StringVarP(&orgSlug, "organization", "o", "", "The organization slug identifier of the app to create a shared URL for. List available organizations with 'numerous organization list'.")
flags.StringVarP(&appSlug, "app", "a", "", "An app slug identifier of the app to create a shared URL for.")
cmdArgs.appIdent.AddAppIdentifierFlags(flags, cmdActionText)
}
25 changes: 15 additions & 10 deletions cmd/app/unshare/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,31 @@ const longFormat string = `Removes a shared URL for the specified app.
%s
`

var long string = fmt.Sprintf(longFormat, usage.AppIdentifier("to remove a shared URL for"), usage.AppDirectoryArgument)
var (
cmdActionText = "to remove a shared URL for"
long string = fmt.Sprintf(longFormat, usage.AppIdentifier(cmdActionText), usage.AppDirectoryArgument)
)

var Cmd = &cobra.Command{
Use: "unshare [app directory]",
RunE: run,
Short: "Remove app shared URL",
Long: long,
Args: args.OptionalAppDir(&appDir),
Args: args.OptionalAppDir(&cmdArgs.appDir),
}

var (
orgSlug string
appSlug string
appDir string
)
var cmdArgs struct {
appIdent args.AppIdentifierArg
appDir string
}

func run(cmd *cobra.Command, args []string) error {
service := app.New(gql.NewClient(), nil, http.DefaultClient)
input := Input{AppDir: appDir, AppSlug: appSlug, OrgSlug: orgSlug}
input := Input{
AppDir: cmdArgs.appDir,
AppSlug: cmdArgs.appIdent.AppSlug,
OrgSlug: cmdArgs.appIdent.OrganizationSlug,
}

err := unshareApp(cmd.Context(), service, input)

Expand All @@ -46,6 +52,5 @@ func run(cmd *cobra.Command, args []string) error {

func init() {
flags := Cmd.Flags()
flags.StringVarP(&orgSlug, "organization", "o", "", "The organization slug identifier of the app to remove a shared URL for. List available organizations with 'numerous organization list'.")
flags.StringVarP(&appSlug, "app", "a", "", "An app slug identifier of the app to remove a shared URL for.")
cmdArgs.appIdent.AddAppIdentifierFlags(flags, cmdActionText)
}
15 changes: 15 additions & 0 deletions cmd/args/appident.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package args

import (
"github.com/spf13/pflag"
)

type AppIdentifierArg struct {
OrganizationSlug string
AppSlug string
}

func (a *AppIdentifierArg) AddAppIdentifierFlags(flags *pflag.FlagSet, action string) {
flags.StringVarP(&a.OrganizationSlug, "organization", "o", "", "The organization slug identifier of the app "+action+". List available organizations with 'numerous organization list'.")
flags.StringVarP(&a.AppSlug, "app", "a", "", "An app slug identifier of the app "+action+".")
}
28 changes: 15 additions & 13 deletions cmd/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,24 @@ var Cmd = &cobra.Command{
Use: "config",
Short: "Configure the Numerous CLI",
Long: "Set configuration values, or print the entire configuration.",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
// do nothing if subcommand is running
return nil
}
RunE: run,
}

func run(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
// do nothing if subcommand is running
return nil
}

cfg := config.Config{}
if err := cfg.Load(); err != nil {
output.PrintErrorDetails("Error loading configuration", err)
return err
}
cfg := config.Config{}
if err := cfg.Load(); err != nil {
output.PrintErrorDetails("Error loading configuration", err)
return err
}

cfg.Print()
cfg.Print()

return nil
},
return nil
}

func init() {
Expand Down
22 changes: 10 additions & 12 deletions cmd/deletecmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var Cmd = &cobra.Command{
GroupID: group.AppCommandsGroupID,
Long: long,
Example: example,
Args: args.OptionalAppDir(&appDir),
Args: args.OptionalAppDir(&cmdArgs.appDir),
}

const longFormat = `Deletes the specified app from the organization.
Expand All @@ -45,26 +45,24 @@ Otherwise, assuming an app has been initialized in the directory
numerous delete my_project/my_app
`

var (
orgSlug string
appSlug string
appDir string = "."
)
var cmdArgs struct {
appIdent args.AppIdentifierArg
appDir string
}

func run(cmd *cobra.Command, args []string) error {
if exists, _ := dir.AppIDExists(appDir); exists {
func run(cmd *cobra.Command, _ []string) error {
if exists, _ := dir.AppIDExists(cmdArgs.appDir); exists {
output.NotifyCmdChanged("numerous delete", "numerous legacy delete")
println()
}

service := app.New(gql.NewClient(), nil, http.DefaultClient)
err := Delete(cmd.Context(), service, appDir, orgSlug, appSlug)
err := deleteApp(cmd.Context(), service, cmdArgs.appDir, cmdArgs.appIdent.OrganizationSlug, cmdArgs.appIdent.AppSlug)

return errorhandling.ErrorAlreadyPrinted(err)
}

func init() {
flags := Cmd.Flags()
flags.StringVarP(&orgSlug, "organization", "o", "", "The organization slug identifier of the app to read logs from.")
flags.StringVarP(&appSlug, "app", "a", "", "The app slug identifier of the app to read logs from.")
f := Cmd.Flags()
cmdArgs.appIdent.AddAppIdentifierFlags(f, "")
}
2 changes: 1 addition & 1 deletion cmd/deletecmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type AppService interface {
Delete(ctx context.Context, input app.DeleteAppInput) error
}

func Delete(ctx context.Context, apps AppService, appDir, orgSlug, appSlug string) error {
func deleteApp(ctx context.Context, apps AppService, appDir, orgSlug, appSlug string) error {
ai, err := appident.GetAppIdentifier(appDir, nil, orgSlug, appSlug)
if err != nil {
appident.PrintGetAppIdentifierError(err, appDir, ai)
Expand Down
6 changes: 3 additions & 3 deletions cmd/deletecmd/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestDelete(t *testing.T) {
expectedInput := app.DeleteAppInput{OrganizationSlug: "organization-slug-in-manifest", AppSlug: "app-slug-in-manifest"}
service.On("Delete", mock.Anything, expectedInput).Return(nil)

err := Delete(context.TODO(), service, appDir, "", "")
err := deleteApp(context.TODO(), service, appDir, "", "")
assert.NoError(t, err)
})

Expand All @@ -36,7 +36,7 @@ func TestDelete(t *testing.T) {
expectedInput := app.DeleteAppInput{OrganizationSlug: slug, AppSlug: appSlug}
service.On("Delete", mock.Anything, expectedInput).Return(nil)

err := Delete(context.TODO(), service, appDir, slug, appSlug)
err := deleteApp(context.TODO(), service, appDir, slug, appSlug)
assert.NoError(t, err)
})

Expand All @@ -47,7 +47,7 @@ func TestDelete(t *testing.T) {
expectedInput := app.DeleteAppInput{OrganizationSlug: slug, AppSlug: appSlug}
service.On("Delete", mock.Anything, expectedInput).Return(testError)

err := Delete(context.TODO(), service, appDir, slug, appSlug)
err := deleteApp(context.TODO(), service, appDir, slug, appSlug)

assert.ErrorIs(t, err, testError)
})
Expand Down
45 changes: 23 additions & 22 deletions cmd/deploy/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ organization's apps page.
%s
`

var long string = fmt.Sprintf(longFormat, usage.AppIdentifier("to deploy"), usage.AppDirectoryArgument)
var (
cmdActionText = "to deploy"
long string = fmt.Sprintf(longFormat, usage.AppIdentifier(cmdActionText), usage.AppDirectoryArgument)
)

var Cmd = &cobra.Command{
Use: "deploy [app directory]",
Expand All @@ -39,43 +42,41 @@ be pushed to the organization "organization-slug-a2ecf59b", and the app slug
numerous deploy --organization "organization-slug-a2ecf59b" --app "my-app"
`,
Args: args.OptionalAppDir(&appDir),
Args: args.OptionalAppDir(&cmdArgs.appDir),
}

var (
orgSlug string
appSlug string
var cmdArgs struct {
appIdent args.AppIdentifierArg
verbose bool
appDir string = "."
projectDir string = ""
appDir string
projectDir string
message string
version string
follow bool
)
}

func run(cmd *cobra.Command, args []string) error {
sc := gql.NewSubscriptionClient().WithSyncMode(true)
service := app.New(gql.NewClient(), sc, http.DefaultClient)
input := DeployInput{
AppDir: appDir,
ProjectDir: projectDir,
OrgSlug: orgSlug,
AppSlug: appSlug,
Message: message,
Version: version,
Verbose: verbose,
Follow: follow,
AppDir: cmdArgs.appDir,
ProjectDir: cmdArgs.projectDir,
OrgSlug: cmdArgs.appIdent.OrganizationSlug,
AppSlug: cmdArgs.appIdent.AppSlug,
Message: cmdArgs.message,
Version: cmdArgs.version,
Verbose: cmdArgs.verbose,
Follow: cmdArgs.follow,
}
err := Deploy(cmd.Context(), service, input)
err := deploy(cmd.Context(), service, input)

return errorhandling.ErrorAlreadyPrinted(err)
}

func init() {
flags := Cmd.Flags()
flags.StringVarP(&orgSlug, "organization", "o", "", "The organization slug identifier of the app to deploy to. List available organizations with 'numerous organization list'.")
flags.StringVarP(&appSlug, "app", "a", "", "A app slug identifier of the app to deploy to.")
flags.BoolVarP(&verbose, "verbose", "v", false, "Display detailed information about the app deployment.")
flags.BoolVarP(&follow, "follow", "f", false, "Follow app deployment logs after deployment has succeeded.")
flags.StringVarP(&projectDir, "project-dir", "p", "", "The project directory, which is the build context if using a custom Dockerfile.")
cmdArgs.appIdent.AddAppIdentifierFlags(flags, cmdActionText)
flags.BoolVarP(&cmdArgs.verbose, "verbose", "v", false, "Display detailed information about the app deployment.")
flags.BoolVarP(&cmdArgs.follow, "follow", "f", false, "Follow app deployment logs after deployment has succeeded.")
flags.StringVarP(&cmdArgs.projectDir, "project-dir", "p", "", "The project directory, which is the build context if using a custom Dockerfile.")
}
Loading

0 comments on commit b6df1a0

Please sign in to comment.