@@ -46,6 +46,12 @@ func processCustomCommands(
4646 var command * cobra.Command
4747 existingTopLevelCommands := make (map [string ]* cobra.Command )
4848
49+ // Build commands and their hierarchy from the alias map
50+ for alias , fullCmd := range atmosConfig .CommandAliases {
51+ parts := strings .Fields (fullCmd )
52+ addCommandWithAlias (RootCmd , alias , parts )
53+ }
54+
4955 if topLevel {
5056 existingTopLevelCommands = getTopLevelCommands ()
5157 }
@@ -113,6 +119,35 @@ func processCustomCommands(
113119 return nil
114120}
115121
122+ // addCommandWithAlias adds a command hierarchy based on the full command
123+ func addCommandWithAlias (parentCmd * cobra.Command , alias string , parts []string ) {
124+ if len (parts ) == 0 {
125+ return
126+ }
127+
128+ // Check if a command with the current part already exists
129+ var cmd * cobra.Command
130+ for _ , c := range parentCmd .Commands () {
131+ if c .Use == parts [0 ] {
132+ cmd = c
133+ break
134+ }
135+ }
136+
137+ // If the command doesn't exist, create it
138+ if cmd == nil {
139+ u .LogErrorAndExit (fmt .Errorf ("subcommand `%s` not found for alias `%s`" , parts [0 ], alias ))
140+ }
141+
142+ // If there are more parts, recurse for the next level
143+ if len (parts ) > 1 {
144+ addCommandWithAlias (cmd , alias , parts [1 :])
145+ } else if ! Contains (cmd .Aliases , alias ) {
146+ // This is the last part of the command, add the alias
147+ cmd .Aliases = append (cmd .Aliases , alias )
148+ }
149+ }
150+
116151// processCommandAliases processes the command aliases
117152func processCommandAliases (
118153 atmosConfig schema.AtmosConfiguration ,
@@ -187,10 +222,9 @@ func preCustomCommand(
187222 os .Exit (1 )
188223 } else {
189224 // truly invalid, nothing to do
190- u .LogError (errors .New (
191- "invalid command: no args, no steps, no sub-commands" ,
192- ))
193- os .Exit (1 )
225+ u .PrintErrorMarkdownAndExit ("Invalid command" , errors .New (
226+ fmt .Sprintf ("The `%s` command has no steps or subcommands configured." , cmd .CommandPath ()),
227+ ), "https://atmos.tools/cli/configuration/commands" )
194228 }
195229 }
196230
@@ -583,37 +617,31 @@ func handleHelpRequest(cmd *cobra.Command, args []string) {
583617 }
584618}
585619
620+ // showUsageAndExit we display the markdown usage or fallback to our custom usage
621+ // Markdown usage is not compatible with all outputs. We should therefore have fallback option.
586622func showUsageAndExit (cmd * cobra.Command , args []string ) {
587- var suggestions [] string
588- unknownCommand := fmt . Sprintf ( "Error: Unknown command: %q \n \n " , cmd . CommandPath () )
589-
623+ if len ( args ) == 0 {
624+ showErrorExampleFromMarkdown ( cmd , "" )
625+ }
590626 if len (args ) > 0 {
591- suggestions = cmd .SuggestionsFor (args [0 ])
592- unknownCommand = fmt .Sprintf ("Error: Unknown command %q for %q\n \n " , args [0 ], cmd .CommandPath ())
627+ showErrorExampleFromMarkdown (cmd , args [0 ])
593628 }
594- u .PrintErrorInColor (unknownCommand )
595- if len (suggestions ) > 0 {
596- u .PrintMessage ("Did you mean this?" )
597- for _ , suggestion := range suggestions {
598- u .PrintMessage (fmt .Sprintf (" %s\n " , suggestion ))
599- }
600- } else {
601- // Retrieve valid subcommands dynamically
602- validSubcommands := []string {}
603- for _ , subCmd := range cmd .Commands () {
604- validSubcommands = append (validSubcommands , subCmd .Name ())
605- }
606- if len (validSubcommands ) > 0 {
607- u .PrintMessage ("Valid subcommands are:" )
608- for _ , sub := range validSubcommands {
609- u .PrintMessage (fmt .Sprintf (" %s" , sub ))
610- }
629+ os .Exit (1 )
630+ }
631+
632+ func showFlagUsageAndExit (cmd * cobra.Command , err error ) error {
633+ unknownCommand := fmt .Sprintf ("%v for command `%s`\n \n " , err .Error (), cmd .CommandPath ())
634+ args := strings .Split (err .Error (), ": " )
635+ if len (args ) == 2 {
636+ if strings .Contains (args [0 ], "flag needs an argument" ) {
637+ unknownCommand = fmt .Sprintf ("`%s` %s for command `%s`\n \n " , args [1 ], args [0 ], cmd .CommandPath ())
611638 } else {
612- u . PrintMessage ( "No valid subcommands found" )
639+ unknownCommand = fmt . Sprintf ( "%s `%s` for command `%s` \n \n " , args [ 0 ], args [ 1 ], cmd . CommandPath () )
613640 }
614641 }
615- u . PrintMessage ( fmt . Sprintf ( " \n Run '%s --help' for usage" , cmd . CommandPath ()) )
642+ showUsageExample ( cmd , unknownCommand )
616643 os .Exit (1 )
644+ return nil
617645}
618646
619647// getConfigAndStacksInfo processes the CLI config and stacks
@@ -637,6 +665,45 @@ func getConfigAndStacksInfo(commandName string, cmd *cobra.Command, args []strin
637665 return info
638666}
639667
668+ func showErrorExampleFromMarkdown (cmd * cobra.Command , arg string ) {
669+ commandPath := cmd .CommandPath ()
670+ suggestions := []string {}
671+ details := fmt .Sprintf ("The command `%s` is not valid usage\n " , commandPath )
672+ if len (arg ) > 0 {
673+ details = fmt .Sprintf ("Unknown command `%s` for `%s`\n " , arg , commandPath )
674+ } else if len (cmd .Commands ()) != 0 && arg == "" {
675+ details = fmt .Sprintf ("The command `%s` requires a subcommand\n " , commandPath )
676+ }
677+ if len (arg ) > 0 {
678+ suggestions = cmd .SuggestionsFor (arg )
679+ }
680+ if len (suggestions ) > 0 {
681+ details = details + "Did you mean this?\n "
682+ for _ , suggestion := range suggestions {
683+ details += "* " + suggestion + "\n "
684+ }
685+ } else {
686+ if len (cmd .Commands ()) > 0 {
687+ details += "\n Valid subcommands are:\n "
688+ }
689+ // Retrieve valid subcommands dynamically
690+ for _ , subCmd := range cmd .Commands () {
691+ details = details + "* " + subCmd .Name () + "\n "
692+ }
693+ }
694+ showUsageExample (cmd , details )
695+ }
696+
697+ func showUsageExample (cmd * cobra.Command , details string ) {
698+ contentName := strings .ReplaceAll (cmd .CommandPath (), " " , "_" )
699+ suggestion := fmt .Sprintf ("\n \n Run `%s --help` for usage" , cmd .CommandPath ())
700+ if exampleContent , ok := examples [contentName ]; ok {
701+ suggestion = exampleContent .Suggestion
702+ details += "\n ## Usage Examples:\n " + exampleContent .Content
703+ }
704+ u .PrintInvalidUsageErrorAndExit (errors .New (details ), suggestion )
705+ }
706+
640707func stackFlagCompletion (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
641708 output , err := listStacks (cmd )
642709 if err != nil {
0 commit comments