From eb600dde820df49122c8b003bdb489f587c30b57 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:10:26 +0000 Subject: [PATCH 1/7] fix: improve `apps deploy` error message when APP_NAME is missing The old error "accepts 1 arg(s), received 0" was unclear. The new message explains that APP_NAME is required, shows usage, mentions the databricks.yml alternative, and suggests an app name inferred from the current directory. Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 6b1712de563..1a408a49a8f 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "os" + "path/filepath" "strings" "time" @@ -27,12 +29,41 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) } if !hasBundleConfig() && len(args) != 1 { - return fmt.Errorf("accepts 1 arg(s), received %d", len(args)) + return missingAppNameError() } return nil } } +// missingAppNameError returns an error message that explains what the positional +// argument should be, and attempts to infer a suggestion from the local environment. +func missingAppNameError() error { + hint := inferAppNameHint() + msg := `missing required argument: APP_NAME + +Usage: databricks apps APP_NAME + +APP_NAME is the name of the Databricks app to operate on. +Alternatively, run this command from a project directory containing +databricks.yml to auto-detect the app name.` + + if hint != "" { + msg += fmt.Sprintf("\n\nDid you mean?\n databricks apps deploy %s", hint) + } + + return errors.New(msg) +} + +// inferAppNameHint tries to suggest an app name from the local environment. +// It checks the current directory name as a best-effort hint. +func inferAppNameHint() string { + wd, err := os.Getwd() + if err != nil { + return "" + } + return filepath.Base(wd) +} + // getAppNameFromArgs returns the app name from args or detects it from the bundle. // Returns (appName, fromBundle, error). func getAppNameFromArgs(cmd *cobra.Command, args []string) (string, bool, error) { From 9f136adf5cbf5a30c93b77aba81a43c532758586 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:20:09 +0000 Subject: [PATCH 2/7] fix: only show app name hint when cwd contains app.yml/app.yaml Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 1a408a49a8f..4764ccd3b81 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -55,13 +55,21 @@ databricks.yml to auto-detect the app name.` } // inferAppNameHint tries to suggest an app name from the local environment. -// It checks the current directory name as a best-effort hint. +// Only returns a hint if the current directory looks like a Databricks app +// (contains app.yml or app.yaml), using the directory name as the suggestion. func inferAppNameHint() string { wd, err := os.Getwd() if err != nil { return "" } - return filepath.Base(wd) + + for _, filename := range []string{"app.yml", "app.yaml"} { + if _, err := os.Stat(filepath.Join(wd, filename)); err == nil { + return filepath.Base(wd) + } + } + + return "" } // getAppNameFromArgs returns the app name from args or detects it from the bundle. From f75ff9cb6c4d9500da880a97f55101f9c52754c5 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:23:33 +0000 Subject: [PATCH 3/7] test: add tests for inferAppNameHint and missingAppNameError Cover normal cases (app.yml, app.yaml, no config) and edge cases (deleted cwd) to verify graceful degradation. Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers_test.go | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index f772d4f545e..32778e1d9ac 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -2,6 +2,8 @@ package apps import ( "errors" + "os" + "path/filepath" "testing" "github.com/databricks/databricks-sdk-go/service/apps" @@ -105,6 +107,80 @@ func TestFormatAppStatusMessage(t *testing.T) { }) } +func TestInferAppNameHint(t *testing.T) { + t.Run("returns empty when no app config exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + + assert.Equal(t, "", inferAppNameHint()) + }) + + t.Run("returns dir name when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns dir name when app.yaml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns empty when cwd has been deleted", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.Remove(dir) + + assert.Equal(t, "", inferAppNameHint()) + }) +} + +func TestMissingAppNameError(t *testing.T) { + t.Run("includes APP_NAME and usage info", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "APP_NAME") + assert.Contains(t, err.Error(), "databricks.yml") + assert.NotContains(t, err.Error(), "Did you mean") + }) + + t.Run("includes hint when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "Did you mean") + assert.Contains(t, err.Error(), filepath.Base(dir)) + }) + + t.Run("gracefully handles deleted cwd", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.Remove(dir) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "APP_NAME") + assert.NotContains(t, err.Error(), "Did you mean") + }) +} + +// testChdir changes to the given directory for the duration of the test. +func testChdir(t *testing.T, dir string) { + t.Helper() + orig, err := os.Getwd() + assert.NoError(t, err) + assert.NoError(t, os.Chdir(dir)) + t.Cleanup(func() { os.Chdir(orig) }) +} + func TestMakeArgsOptionalWithBundle(t *testing.T) { t.Run("updates command usage", func(t *testing.T) { cmd := &cobra.Command{} From d93ce65a0af10bbd341805cd0311914d92ba2ac5 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:24:02 +0000 Subject: [PATCH 4/7] style: replace fmt.Sprintf with string concatenation Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 4764ccd3b81..04992c592ec 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -48,7 +48,7 @@ Alternatively, run this command from a project directory containing databricks.yml to auto-detect the app name.` if hint != "" { - msg += fmt.Sprintf("\n\nDid you mean?\n databricks apps deploy %s", hint) + msg += "\n\nDid you mean?\n databricks apps deploy " + hint } return errors.New(msg) From 3587a78bd69b6994256512b8f0fb30cdc53507ff Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:49:45 +0000 Subject: [PATCH 5/7] fix: address lint issues in tests - Check os.WriteFile error return values - Use t.Chdir() instead of manual os.Chdir + cleanup - Remove unnecessary testChdir helper Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers_test.go | 34 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index 32778e1d9ac..d146f3001d6 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -109,31 +109,32 @@ func TestFormatAppStatusMessage(t *testing.T) { func TestInferAppNameHint(t *testing.T) { t.Run("returns empty when no app config exists", func(t *testing.T) { - dir := t.TempDir() - testChdir(t, dir) + t.Chdir(t.TempDir()) assert.Equal(t, "", inferAppNameHint()) }) t.Run("returns dir name when app.yml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) assert.Equal(t, filepath.Base(dir), inferAppNameHint()) }) t.Run("returns dir name when app.yaml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) assert.Equal(t, filepath.Base(dir), inferAppNameHint()) }) t.Run("returns empty when cwd has been deleted", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) + t.Chdir(dir) os.Remove(dir) assert.Equal(t, "", inferAppNameHint()) @@ -142,8 +143,7 @@ func TestInferAppNameHint(t *testing.T) { func TestMissingAppNameError(t *testing.T) { t.Run("includes APP_NAME and usage info", func(t *testing.T) { - dir := t.TempDir() - testChdir(t, dir) + t.Chdir(t.TempDir()) err := missingAppNameError() assert.Contains(t, err.Error(), "APP_NAME") @@ -153,8 +153,9 @@ func TestMissingAppNameError(t *testing.T) { t.Run("includes hint when app.yml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, writeErr) err := missingAppNameError() assert.Contains(t, err.Error(), "Did you mean") @@ -163,7 +164,7 @@ func TestMissingAppNameError(t *testing.T) { t.Run("gracefully handles deleted cwd", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) + t.Chdir(dir) os.Remove(dir) err := missingAppNameError() @@ -172,15 +173,6 @@ func TestMissingAppNameError(t *testing.T) { }) } -// testChdir changes to the given directory for the duration of the test. -func testChdir(t *testing.T, dir string) { - t.Helper() - orig, err := os.Getwd() - assert.NoError(t, err) - assert.NoError(t, os.Chdir(dir)) - t.Cleanup(func() { os.Chdir(orig) }) -} - func TestMakeArgsOptionalWithBundle(t *testing.T) { t.Run("updates command usage", func(t *testing.T) { cmd := &cobra.Command{} From 6c3d34765c2e9c817a8f71c507d4a61fbae8849d Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 28 Apr 2026 16:38:54 +0000 Subject: [PATCH 6/7] fix(apps): render verb-specific usage and hint in missingAppNameError The shared zero-arg validator used by deploy/start/stop/delete printed a hardcoded "databricks apps deploy " suggestion and a generic "" placeholder in the usage line, so users running `apps stop` without an APP_NAME were nudged toward `apps deploy` instead. - Thread the cobra command into missingAppNameError and render both the Usage line and the "Did you mean?" hint from cmd.CommandPath(). - Extend bundle_helpers_test.go with table-driven coverage that exercises deploy/start/stop/delete and asserts the hint matches the verb. - inferAppNameHint now requires app.yml/app.yaml to be a regular file, so a directory of that name no longer produces a bogus hint. - Regenerate the no-bundle-no-args acceptance fixture for the new message. Co-authored-by: Isaac --- .../apps/deploy/no-bundle-no-args/output.txt | 8 ++++- cmd/apps/bundle_helpers.go | 23 ++++++++---- cmd/apps/bundle_helpers_test.go | 36 +++++++++++++++++-- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/acceptance/apps/deploy/no-bundle-no-args/output.txt b/acceptance/apps/deploy/no-bundle-no-args/output.txt index affcf94d8f5..0401016dacf 100644 --- a/acceptance/apps/deploy/no-bundle-no-args/output.txt +++ b/acceptance/apps/deploy/no-bundle-no-args/output.txt @@ -1,3 +1,9 @@ -Error: accepts 1 arg(s), received 0 +Error: missing required argument: APP_NAME + +Usage: databricks apps deploy APP_NAME + +APP_NAME is the name of the Databricks app to operate on. +Alternatively, run this command from a project directory containing +databricks.yml to auto-detect the app name. Exit code: 1 diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 04992c592ec..9ad2c5d5210 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -29,7 +29,7 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) } if !hasBundleConfig() && len(args) != 1 { - return missingAppNameError() + return missingAppNameError(cmd) } return nil } @@ -37,18 +37,26 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { // missingAppNameError returns an error message that explains what the positional // argument should be, and attempts to infer a suggestion from the local environment. -func missingAppNameError() error { +// The full subcommand path (e.g. "databricks apps start") is rendered from cmd so +// the usage line and "Did you mean?" hint match the verb the user actually ran. +func missingAppNameError(cmd *cobra.Command) error { hint := inferAppNameHint() - msg := `missing required argument: APP_NAME + commandPath := "databricks apps " + if cmd != nil { + if p := cmd.CommandPath(); p != "" { + commandPath = p + } + } + msg := fmt.Sprintf(`missing required argument: APP_NAME -Usage: databricks apps APP_NAME +Usage: %s APP_NAME APP_NAME is the name of the Databricks app to operate on. Alternatively, run this command from a project directory containing -databricks.yml to auto-detect the app name.` +databricks.yml to auto-detect the app name.`, commandPath) if hint != "" { - msg += "\n\nDid you mean?\n databricks apps deploy " + hint + msg += fmt.Sprintf("\n\nDid you mean?\n %s %s", commandPath, hint) } return errors.New(msg) @@ -64,7 +72,8 @@ func inferAppNameHint() string { } for _, filename := range []string{"app.yml", "app.yaml"} { - if _, err := os.Stat(filepath.Join(wd, filename)); err == nil { + info, err := os.Stat(filepath.Join(wd, filename)) + if err == nil && info.Mode().IsRegular() { return filepath.Base(wd) } } diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index d146f3001d6..ae5ae8e7456 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -145,7 +145,7 @@ func TestMissingAppNameError(t *testing.T) { t.Run("includes APP_NAME and usage info", func(t *testing.T) { t.Chdir(t.TempDir()) - err := missingAppNameError() + err := missingAppNameError(nil) assert.Contains(t, err.Error(), "APP_NAME") assert.Contains(t, err.Error(), "databricks.yml") assert.NotContains(t, err.Error(), "Did you mean") @@ -157,7 +157,7 @@ func TestMissingAppNameError(t *testing.T) { writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) assert.NoError(t, writeErr) - err := missingAppNameError() + err := missingAppNameError(nil) assert.Contains(t, err.Error(), "Did you mean") assert.Contains(t, err.Error(), filepath.Base(dir)) }) @@ -167,10 +167,40 @@ func TestMissingAppNameError(t *testing.T) { t.Chdir(dir) os.Remove(dir) - err := missingAppNameError() + err := missingAppNameError(nil) assert.Contains(t, err.Error(), "APP_NAME") assert.NotContains(t, err.Error(), "Did you mean") }) + + t.Run("renders usage and hint from cmd path per verb", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, writeErr) + + for _, verb := range []string{"deploy", "start", "stop", "delete"} { + t.Run(verb, func(t *testing.T) { + root := &cobra.Command{Use: "databricks"} + apps := &cobra.Command{Use: "apps"} + sub := &cobra.Command{Use: verb} + root.AddCommand(apps) + apps.AddCommand(sub) + + err := missingAppNameError(sub) + assert.Contains(t, err.Error(), "Usage: databricks apps "+verb+" APP_NAME") + assert.Contains(t, err.Error(), "databricks apps "+verb+" "+filepath.Base(dir)) + }) + } + }) + + t.Run("ignores non-regular app.yml entries", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + assert.NoError(t, os.Mkdir(filepath.Join(dir, "app.yml"), 0o755)) + + err := missingAppNameError(nil) + assert.NotContains(t, err.Error(), "Did you mean") + }) } func TestMakeArgsOptionalWithBundle(t *testing.T) { From 41e60d61fe94a4cd215bb3744ce6a1c4aec14d84 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 20 May 2026 17:03:20 +0200 Subject: [PATCH 7/7] fix(apps): align missing app name errors Reuse the shared missing-name validation for logs and render each command's documented positional argument name. --- cmd/apps/bundle_helpers.go | 21 +++++++++++++++++---- cmd/apps/bundle_helpers_test.go | 31 ++++++++++++++++++++++++++----- cmd/apps/logs.go | 11 +---------- cmd/apps/logs_test.go | 11 +++++++++++ 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 9ad2c5d5210..d1e587bebca 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -42,18 +42,22 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { func missingAppNameError(cmd *cobra.Command) error { hint := inferAppNameHint() commandPath := "databricks apps " + argName := "APP_NAME" if cmd != nil { if p := cmd.CommandPath(); p != "" { commandPath = p } + if name := positionalArgName(cmd.Use); name != "" { + argName = name + } } - msg := fmt.Sprintf(`missing required argument: APP_NAME + msg := fmt.Sprintf(`missing required argument: %s -Usage: %s APP_NAME +Usage: %s %s -APP_NAME is the name of the Databricks app to operate on. +%s is the name of the Databricks app to operate on. Alternatively, run this command from a project directory containing -databricks.yml to auto-detect the app name.`, commandPath) +databricks.yml to auto-detect the app name.`, argName, commandPath, argName, argName) if hint != "" { msg += fmt.Sprintf("\n\nDid you mean?\n %s %s", commandPath, hint) @@ -62,6 +66,15 @@ databricks.yml to auto-detect the app name.`, commandPath) return errors.New(msg) } +func positionalArgName(use string) string { + start := strings.Index(use, "[") + end := strings.Index(use, "]") + if start < 0 || end <= start { + return "" + } + return use[start+1 : end] +} + // inferAppNameHint tries to suggest an app name from the local environment. // Only returns a hint if the current directory looks like a Databricks app // (contains app.yml or app.yaml), using the directory name as the suggestion. diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index ae5ae8e7456..9e798d0f830 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -178,17 +178,27 @@ func TestMissingAppNameError(t *testing.T) { writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) assert.NoError(t, writeErr) - for _, verb := range []string{"deploy", "start", "stop", "delete"} { - t.Run(verb, func(t *testing.T) { + for _, tc := range []struct { + verb string + use string + arg string + }{ + {"deploy", "deploy [APP_NAME]", "APP_NAME"}, + {"start", "start [NAME]", "NAME"}, + {"stop", "stop [NAME]", "NAME"}, + {"delete", "delete [NAME]", "NAME"}, + } { + t.Run(tc.verb, func(t *testing.T) { root := &cobra.Command{Use: "databricks"} apps := &cobra.Command{Use: "apps"} - sub := &cobra.Command{Use: verb} + sub := &cobra.Command{Use: tc.use} root.AddCommand(apps) apps.AddCommand(sub) err := missingAppNameError(sub) - assert.Contains(t, err.Error(), "Usage: databricks apps "+verb+" APP_NAME") - assert.Contains(t, err.Error(), "databricks apps "+verb+" "+filepath.Base(dir)) + assert.Contains(t, err.Error(), "missing required argument: "+tc.arg) + assert.Contains(t, err.Error(), "Usage: databricks apps "+tc.verb+" "+tc.arg) + assert.Contains(t, err.Error(), "databricks apps "+tc.verb+" "+filepath.Base(dir)) }) } }) @@ -215,6 +225,17 @@ func TestMakeArgsOptionalWithBundle(t *testing.T) { makeArgsOptionalWithBundle(cmd, "test [NAME]") assert.NotNil(t, cmd.Args) }) + + t.Run("returns missing app name error when no bundle config exists", func(t *testing.T) { + t.Chdir(t.TempDir()) + + cmd := &cobra.Command{} + makeArgsOptionalWithBundle(cmd, "test [NAME]") + + err := cmd.Args(cmd, nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "missing required argument: NAME") + }) } func TestGetAppNameFromArgs(t *testing.T) { diff --git a/cmd/apps/logs.go b/cmd/apps/logs.go index c6cab936521..777a9f3621b 100644 --- a/cmd/apps/logs.go +++ b/cmd/apps/logs.go @@ -46,7 +46,6 @@ func newLogsCommand() *cobra.Command { ) cmd := &cobra.Command{ - Use: "logs [NAME]", Short: "Show Databricks app logs", Long: `Show Databricks app logs. @@ -78,15 +77,6 @@ Examples: # Mirror streamed logs to a local file while following for up to 5 minutes databricks apps logs my-app --follow --timeout 5m --output-file /tmp/my-app.log`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) - } - if !hasBundleConfig() && len(args) != 1 { - return fmt.Errorf("accepts 1 arg(s), received %d", len(args)) - } - return nil - }, PreRunE: root.MustWorkspaceClient, RunE: func(cmd *cobra.Command, args []string) error { appName, fromBundle, err := getAppNameFromArgs(cmd, args) @@ -207,6 +197,7 @@ Examples: }) }, } + makeArgsOptionalWithBundle(cmd, "logs [NAME]") streamGroup := cmdgroup.NewFlagGroup("Streaming") streamGroup.FlagSet().IntVar(&tailLines, "tail-lines", defaultTailLines, "Number of recent log lines to show before streaming. Set to 0 to show everything.") diff --git a/cmd/apps/logs_test.go b/cmd/apps/logs_test.go index ca7ea5e7ef9..2f8d74b11a7 100644 --- a/cmd/apps/logs_test.go +++ b/cmd/apps/logs_test.go @@ -64,6 +64,17 @@ func TestBuildLogsURLRejectsUnknownScheme(t *testing.T) { require.Error(t, err) } +func TestLogsMissingNameError(t *testing.T) { + t.Chdir(t.TempDir()) + + cmd := newLogsCommand() + err := cmd.Args(cmd, nil) + + require.Error(t, err) + assert.Contains(t, err.Error(), "missing required argument: NAME") + assert.Contains(t, err.Error(), "Usage: logs NAME") +} + func TestNormalizeOrigin(t *testing.T) { assert.Equal(t, "https://example.com", normalizeOrigin("https://example.com/foo")) assert.Equal(t, "http://example.com", normalizeOrigin("ws://example.com/foo"))