github.com/replit/upm@v0.0.0-20240423230255-9ce4fc3ea24c/internal/cli/cli.go (about) 1 // Package cli implements the command-line interface of UPM. 2 package cli 3 4 import ( 5 "fmt" 6 "os" 7 8 "github.com/replit/upm/internal/backends" 9 "github.com/replit/upm/internal/config" 10 "github.com/replit/upm/internal/trace" 11 "github.com/replit/upm/internal/util" 12 "github.com/spf13/cobra" 13 ) 14 15 // parseOutputFormat takes "table" or "json" and returns an 16 // outputFormat enum value. 17 func parseOutputFormat(formatStr string) outputFormat { 18 switch formatStr { 19 case "table": 20 return outputFormatTable 21 case "json": 22 return outputFormatJSON 23 default: 24 util.DieConsistency(`Error: invalid format %#v (must be "table" or "json")`, formatStr) 25 return 0 26 } 27 } 28 29 // version is set at build time to a Git tag or the string 30 // "development version" when not tagging a release. 31 var version = "unknown version" 32 33 // getVersion returns a string that can be printed when calling 'upm 34 // --version'. 35 func getVersion() string { 36 return "upm " + version 37 } 38 39 // DoCLI reads the command-line arguments and runs the appropriate 40 // code, then exits the process (or returns to indicate normal exit). 41 func DoCLI() { 42 cleanupFn := trace.MaybeTrace(getVersion()) 43 if cleanupFn != nil { 44 defer cleanupFn() 45 } 46 47 backends.SetupAll() 48 49 var language string 50 var formatStr string 51 var guess bool 52 var forceLock bool 53 var forceInstall bool 54 var forceGuess bool 55 var all bool 56 var ignoredPackages []string 57 var ignoredPaths []string 58 var upgrade bool 59 var name string 60 61 cobra.EnableCommandSorting = false 62 63 rootCmd := &cobra.Command{ 64 Use: "upm", 65 Version: getVersion(), 66 } 67 rootCmd.SetVersionTemplate(`{{.Version}}` + "\n") 68 // Not sorting the root command options because none of the 69 // documented ways to disable sorting work for it (the root 70 // command itself has the options sorted correctly, but they 71 // are alphabetized in the help strings for subcommands). 72 rootCmd.PersistentFlags().StringVarP( 73 &language, "lang", "l", "", "specify project language(s) manually", 74 ) 75 rootCmd.PersistentFlags().BoolVarP( 76 &config.Quiet, "quiet", "q", false, "don't show what commands are being run", 77 ) 78 rootCmd.PersistentFlags().StringSliceVar( 79 &ignoredPackages, "ignored-packages", []string{}, 80 "packages to ignore when searching, guessing, or adding (comma-separated)", 81 ) 82 rootCmd.PersistentFlags().StringSliceVar( 83 &ignoredPaths, "ignored-paths", []string{}, 84 "paths to ignore when guessing (comma-separated)", 85 ) 86 rootCmd.PersistentFlags().BoolP( 87 "help", "h", false, "display command-line usage", 88 ) 89 rootCmd.PersistentFlags().BoolP( 90 "version", "v", false, "display command version", 91 ) 92 93 cmdWhichLanguage := &cobra.Command{ 94 Use: "which-language", 95 Short: "Query language autodetection", 96 Long: "Ask which language your project is autodetected as", 97 Args: cobra.NoArgs, 98 Run: func(cmd *cobra.Command, args []string) { 99 runWhichLanguage(language) 100 }, 101 } 102 rootCmd.AddCommand(cmdWhichLanguage) 103 104 cmdListLanguages := &cobra.Command{ 105 Use: "list-languages", 106 Short: "List supported languages", 107 Args: cobra.NoArgs, 108 Run: func(cmd *cobra.Command, args []string) { 109 runListLanguages() 110 }, 111 } 112 rootCmd.AddCommand(cmdListLanguages) 113 114 cmdSearch := &cobra.Command{ 115 Use: "search QUERY...", 116 Short: "Search for packages online", 117 Args: cobra.MinimumNArgs(1), 118 Run: func(cmd *cobra.Command, args []string) { 119 queries := args 120 outputFormat := parseOutputFormat(formatStr) 121 runSearch(language, queries, outputFormat, ignoredPackages) 122 }, 123 } 124 cmdSearch.Flags().SortFlags = false 125 cmdSearch.Flags().StringVarP( 126 &formatStr, "format", "f", "table", `output format ("table" or "json")`, 127 ) 128 rootCmd.AddCommand(cmdSearch) 129 130 cmdInfo := &cobra.Command{ 131 Aliases: []string{"show"}, 132 Use: "info PACKAGE", 133 Short: "Show package information from online registry", 134 Args: cobra.ExactArgs(1), 135 Run: func(cmd *cobra.Command, args []string) { 136 pkg := args[0] 137 outputFormat := parseOutputFormat(formatStr) 138 runInfo(language, pkg, outputFormat) 139 }, 140 } 141 cmdInfo.Flags().SortFlags = false 142 cmdInfo.Flags().StringVarP( 143 &formatStr, "format", "f", "table", `output format ("table" or "json")`, 144 ) 145 rootCmd.AddCommand(cmdInfo) 146 147 cmdAdd := &cobra.Command{ 148 Use: `add "PACKAGE[ SPEC]"...`, 149 Short: "Add packages to the specfile", 150 Run: func(cmd *cobra.Command, args []string) { 151 pkgSpecStrs := args 152 runAdd(language, pkgSpecStrs, upgrade, guess, forceGuess, 153 ignoredPackages, forceLock, forceInstall, name) 154 }, 155 } 156 cmdAdd.Flags().SortFlags = false 157 cmdAdd.Flags().BoolVarP( 158 &upgrade, "upgrade", "u", false, "upgrade all packages to latest allowed versions", 159 ) 160 cmdAdd.Flags().BoolVarP( 161 &guess, "guess", "g", false, "guess additional packages to add", 162 ) 163 cmdAdd.Flags().BoolVarP( 164 &forceLock, "force-lock", "f", false, "rewrite lockfile even if up to date", 165 ) 166 cmdAdd.Flags().BoolVarP( 167 &forceInstall, "force-install", "F", false, "reinstall packages even if up to date", 168 ) 169 cmdAdd.Flags().BoolVar( 170 &forceGuess, "force-guess", false, "bypass cache when guessing dependencies", 171 ) 172 cmdAdd.Flags().StringVarP( 173 &name, "name", "n", "", "specify project name", 174 ) 175 rootCmd.AddCommand(cmdAdd) 176 177 cmdRemove := &cobra.Command{ 178 Use: "remove PACKAGE...", 179 Short: "Remove packages from the specfile", 180 Args: cobra.MinimumNArgs(1), 181 Run: func(cmd *cobra.Command, args []string) { 182 pkgs := args 183 runRemove(language, pkgs, upgrade, forceLock, forceInstall) 184 }, 185 } 186 cmdRemove.Flags().SortFlags = false 187 cmdRemove.Flags().BoolVarP( 188 &upgrade, "upgrade", "u", false, "upgrade all packages to latest allowed versions", 189 ) 190 cmdRemove.Flags().BoolVarP( 191 &forceLock, "force-lock", "f", false, "rewrite lockfile even if up to date", 192 ) 193 cmdRemove.Flags().BoolVarP( 194 &forceInstall, "force-install", "F", false, "reinstall packages even if up to date", 195 ) 196 rootCmd.AddCommand(cmdRemove) 197 198 updateAliases := []string{"update", "upgrade"} 199 cmdLock := &cobra.Command{ 200 Aliases: updateAliases, 201 Use: "lock", 202 Short: "Generate the lockfile from the specfile", 203 Args: cobra.NoArgs, 204 Run: func(cmd *cobra.Command, args []string) { 205 for _, updateAlias := range updateAliases { 206 if cmd.CalledAs() == updateAlias { 207 upgrade = true 208 } 209 } 210 runLock(language, upgrade, forceLock, forceInstall) 211 }, 212 } 213 cmdLock.Flags().SortFlags = false 214 cmdLock.Flags().BoolVarP( 215 &upgrade, "upgrade", "u", false, "upgrade all packages to latest allowed versions", 216 ) 217 cmdLock.Flags().BoolVarP( 218 &forceLock, "force-lock", "f", false, "rewrite lockfile even if up to date", 219 ) 220 cmdLock.Flags().BoolVarP( 221 &forceInstall, "force-install", "F", false, "reinstall packages even if up to date", 222 ) 223 rootCmd.AddCommand(cmdLock) 224 225 cmdInstall := &cobra.Command{ 226 Use: "install", 227 Short: "Install packages from the lockfile", 228 Args: cobra.NoArgs, 229 Run: func(cmd *cobra.Command, args []string) { 230 runInstall(language, forceInstall) 231 }, 232 } 233 cmdInstall.Flags().SortFlags = false 234 cmdInstall.Flags().BoolVarP( 235 &forceInstall, "force", "F", false, "reinstall packages even if up to date", 236 ) 237 rootCmd.AddCommand(cmdInstall) 238 239 cmdList := &cobra.Command{ 240 Use: "list", 241 Short: "List packages from the specfile (or lockfile)", 242 Long: "List packages from the specfile", 243 Args: cobra.NoArgs, 244 Run: func(cmd *cobra.Command, args []string) { 245 outputFormat := parseOutputFormat(formatStr) 246 runList(language, all, outputFormat) 247 }, 248 } 249 cmdInstall.Flags().SortFlags = false 250 cmdList.Flags().BoolVarP( 251 &all, "all", "a", false, "list packages from the lockfile instead", 252 ) 253 cmdList.Flags().StringVarP( 254 &formatStr, "format", "f", "table", `output format ("table" or "json")`, 255 ) 256 rootCmd.AddCommand(cmdList) 257 258 cmdGuess := &cobra.Command{ 259 Use: "guess", 260 Short: "Guess what packages are needed by your project", 261 Args: cobra.NoArgs, 262 Run: func(cmd *cobra.Command, args []string) { 263 util.AddIngoredPaths(ignoredPaths) 264 runGuess(language, all, forceGuess, ignoredPackages) 265 }, 266 } 267 cmdGuess.Flags().SortFlags = false 268 cmdGuess.Flags().BoolVarP( 269 &all, "all", "a", false, "list even packages already in the specfile", 270 ) 271 cmdGuess.Flags().BoolVarP( 272 &forceGuess, "force", "f", false, "bypass cache", 273 ) 274 rootCmd.AddCommand(cmdGuess) 275 276 cmdShowSpecfile := &cobra.Command{ 277 Use: "show-specfile", 278 Short: "Print the filename of the specfile", 279 Args: cobra.NoArgs, 280 Run: func(cmd *cobra.Command, args []string) { 281 runShowSpecfile(language) 282 }, 283 } 284 rootCmd.AddCommand(cmdShowSpecfile) 285 286 cmdShowLockfile := &cobra.Command{ 287 Use: "show-lockfile", 288 Short: "Print the filename of the lockfile", 289 Args: cobra.NoArgs, 290 Run: func(cmd *cobra.Command, args []string) { 291 runShowLockfile(language) 292 }, 293 } 294 rootCmd.AddCommand(cmdShowLockfile) 295 296 cmdShowPackageDir := &cobra.Command{ 297 Use: "show-package-dir", 298 Short: "Print the directory where packages are installed", 299 Args: cobra.NoArgs, 300 Run: func(cmd *cobra.Command, args []string) { 301 runShowPackageDir(language) 302 }, 303 } 304 rootCmd.AddCommand(cmdShowPackageDir) 305 306 cmdInstallReplitNixSystemDependencies := &cobra.Command{ 307 Use: `install-replit-nix-system-dependencies "PACKAGE[ SPEC]" ...`, 308 Short: "Install system dependencies into replit.nix using the passed packages and the specfile.", 309 Run: func(cmd *cobra.Command, args []string) { 310 pkgSpecStrs := args 311 runInstallReplitNixSystemDependencies(language, pkgSpecStrs) 312 }, 313 } 314 rootCmd.AddCommand(cmdInstallReplitNixSystemDependencies) 315 316 specialArgs := map[string](func()){} 317 for _, helpFlag := range []string{"-help", "-?"} { 318 specialArgs[helpFlag] = func() { 319 err := rootCmd.Usage() 320 if err != nil { 321 panic(err) 322 } 323 324 os.Exit(0) 325 } 326 } 327 for _, versionFlag := range []string{"-version", "-V"} { 328 specialArgs[versionFlag] = func() { 329 fmt.Println(getVersion()) 330 os.Exit(0) 331 } 332 } 333 334 if len(os.Args) >= 2 { 335 fn, ok := specialArgs[os.Args[1]] 336 if ok { 337 fn() 338 } 339 } 340 341 util.ChdirToUPM() 342 err := rootCmd.Execute() 343 if err != nil { 344 // We don't need to log anything here, 345 // cobra.Command does its own error logging. 346 os.Exit(1) 347 } 348 }