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  }