commands.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "github.com/spf13/cobra"
  8. )
  9. var rootCmd = &cobra.Command{
  10. Use: "dev",
  11. Short: "Development script runner",
  12. Long: "A CLI tool for managing and running development scripts",
  13. RunE: func(cmd *cobra.Command, args []string) error {
  14. return cmd.Help()
  15. },
  16. }
  17. var runCmd = &cobra.Command{
  18. Use: "run [script-filters...] [-- script-args...]",
  19. Short: "Run scripts matching filters (or all if no filters)",
  20. Long: `Run scripts matching filters. Use -- to pass arguments to scripts.
  21. Scripts marked with "# REQUIRES: sudo" will automatically run with elevated privileges.
  22. Examples:
  23. dev run tools/dev.sh # Run specific script
  24. dev run tools/dev.sh -- arg1 arg2 # Run with arguments
  25. dev run --verbose install -- --force # Run with flags
  26. `,
  27. ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  28. if err := ensureRunsDir(); err != nil {
  29. return nil, cobra.ShellCompDirectiveNoFileComp
  30. }
  31. scripts, err := findScripts(nil)
  32. if err != nil {
  33. return nil, cobra.ShellCompDirectiveNoFileComp
  34. }
  35. var completions []string
  36. for _, script := range scripts {
  37. desc := script.Desc
  38. if script.RequiresSudo {
  39. desc = desc + " [SUDO]"
  40. }
  41. completions = append(completions, fmt.Sprintf("%s\t%s", script.RelPath, desc))
  42. }
  43. return completions, cobra.ShellCompDirectiveNoFileComp
  44. },
  45. RunE: func(cmd *cobra.Command, args []string) error {
  46. // Parse arguments manually to handle -- separator
  47. var filters []string
  48. var scriptArgs []string
  49. // Find "run" command position and -- separator in raw args
  50. cmdPos := -1
  51. separatorPos := -1
  52. rawArgs := os.Args
  53. for i, arg := range rawArgs {
  54. if arg == "run" {
  55. cmdPos = i
  56. break
  57. }
  58. }
  59. if cmdPos >= 0 {
  60. for i := cmdPos + 1; i < len(rawArgs); i++ {
  61. if rawArgs[i] == "--" {
  62. separatorPos = i
  63. break
  64. }
  65. }
  66. }
  67. if separatorPos >= 0 {
  68. // Get everything between 'run' and '--' as filters
  69. filters = rawArgs[cmdPos+1 : separatorPos]
  70. // Remove any flags from filters
  71. for i := 0; i < len(filters); i++ {
  72. if strings.HasPrefix(filters[i], "-") {
  73. filters = append(filters[:i], filters[i+1:]...)
  74. i--
  75. }
  76. }
  77. // Get everything after '--' as script args
  78. scriptArgs = rawArgs[separatorPos+1:]
  79. } else {
  80. // No '--' separator found, consider all non-flag args as filters
  81. for _, arg := range args {
  82. if !strings.HasPrefix(arg, "-") {
  83. filters = append(filters, arg)
  84. }
  85. }
  86. }
  87. if err := ensureRunsDir(); err != nil {
  88. return err
  89. }
  90. scripts, err := findScripts(filters)
  91. if err != nil {
  92. return err
  93. }
  94. if len(scripts) == 0 {
  95. if len(filters) > 0 {
  96. warn("No scripts match filters: %s", strings.Join(filters, ", "))
  97. } else {
  98. warn("No scripts found. Use 'dev new <name>' to create one")
  99. }
  100. return nil
  101. }
  102. for _, script := range scripts {
  103. if config.DryRun {
  104. argsStr := ""
  105. if len(scriptArgs) > 0 {
  106. argsStr = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
  107. }
  108. sudoStr := ""
  109. if script.RequiresSudo {
  110. sudoStr = " (with sudo)"
  111. }
  112. log("Would run: %s%s%s", script.RelPath, argsStr, sudoStr)
  113. } else {
  114. if err := executeScript(script, scriptArgs, config.Verbose); err != nil {
  115. errorLog("❌ %s failed", script.RelPath)
  116. if !config.Verbose {
  117. fmt.Printf(" Check log: %s\n", filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
  118. }
  119. } else {
  120. if script.RequiresSudo {
  121. sudoLog("✅ %s completed (elevated)", script.RelPath)
  122. } else {
  123. log("✅ %s completed", script.RelPath)
  124. }
  125. }
  126. }
  127. }
  128. return nil
  129. },
  130. }
  131. var listCmd = &cobra.Command{
  132. Use: "ls",
  133. Aliases: []string{"list"},
  134. Short: "List all available scripts",
  135. RunE: func(cmd *cobra.Command, args []string) error {
  136. if err := ensureRunsDir(); err != nil {
  137. return err
  138. }
  139. scripts, err := findScripts(nil)
  140. if err != nil {
  141. return err
  142. }
  143. if len(scripts) == 0 {
  144. warn("No scripts found in %s", config.RunsDir)
  145. fmt.Println("Use 'dev new <n>' to create a new script")
  146. return nil
  147. }
  148. fmt.Printf("Available scripts in %s:\n\n", config.RunsDir)
  149. for _, script := range scripts {
  150. if script.RequiresSudo {
  151. fmt.Printf(" %s%s%s - %s %s[SUDO]%s\n",
  152. Blue, script.RelPath, NC, script.Desc, Purple, NC)
  153. } else {
  154. fmt.Printf(" %s%s%s - %s\n",
  155. Blue, script.RelPath, NC, script.Desc)
  156. }
  157. }
  158. sudoCount := 0
  159. for _, script := range scripts {
  160. if script.RequiresSudo {
  161. sudoCount++
  162. }
  163. }
  164. fmt.Printf("\nTotal: %d scripts", len(scripts))
  165. if sudoCount > 0 {
  166. fmt.Printf(" (%d require elevated privileges)", sudoCount)
  167. }
  168. fmt.Println()
  169. return nil
  170. },
  171. }
  172. var newCmd = &cobra.Command{
  173. Use: "new <name>",
  174. Short: "Create a new script template",
  175. Args: cobra.ExactArgs(1),
  176. RunE: func(cmd *cobra.Command, args []string) error {
  177. return createNewScript(args[0])
  178. },
  179. }
  180. var pushCmd = &cobra.Command{
  181. Use: "push",
  182. Aliases: []string{"u", "ush"},
  183. Short: "Commit, scan for secrets, and push to git origin",
  184. RunE: func(cmd *cobra.Command, args []string) error {
  185. return handlePush()
  186. },
  187. }
  188. var depsCmd = &cobra.Command{
  189. Use: "deps",
  190. Short: "Install dependencies",
  191. RunE: func(cmd *cobra.Command, args []string) error {
  192. return checkDependencies()
  193. },
  194. }
  195. var completionCmd = &cobra.Command{
  196. Use: "completion [bash|zsh|fish]",
  197. Short: "Generate completion script",
  198. Long: `To load completions:
  199. Bash:
  200. $ source <(dev completion bash)
  201. # To load completions for each session, execute once:
  202. # Linux:
  203. $ dev completion bash > /etc/bash_completion.d/dev
  204. # macOS:
  205. $ dev completion bash > $(brew --prefix)/etc/bash_completion.d/dev
  206. Zsh:
  207. # If shell completion is not already enabled in your environment,
  208. # you will need to enable it. You can execute the following once:
  209. $ echo "autoload -U compinit; compinit" >> ~/.zshrc
  210. # To load completions for each session, execute once:
  211. $ dev completion zsh > "${fpath[1]}/_dev"
  212. # You will need to start a new shell for this setup to take effect.
  213. `,
  214. DisableFlagsInUseLine: true,
  215. ValidArgs: []string{"bash", "zsh", "fish"},
  216. Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
  217. RunE: func(cmd *cobra.Command, args []string) error {
  218. switch args[0] {
  219. case "bash":
  220. return rootCmd.GenBashCompletion(os.Stdout)
  221. case "zsh":
  222. return rootCmd.GenZshCompletion(os.Stdout)
  223. case "fish":
  224. return rootCmd.GenFishCompletion(os.Stdout, true)
  225. }
  226. return nil
  227. },
  228. }