diff --git a/internal/boxcli/list.go b/internal/boxcli/list.go index 48c33707b79..bac642d58e5 100644 --- a/internal/boxcli/list.go +++ b/internal/boxcli/list.go @@ -1,4 +1,4 @@ -// Copyright 2024 Jetify Inc. and contributors. All rights reserved. +// Copyright 2025 Jetify Inc. and contributors. All rights reserved. // Use of this source code is governed by the license in the LICENSE file. package boxcli @@ -15,7 +15,8 @@ import ( ) type listCmdFlags struct { - config configFlags + config configFlags + outdated bool } func listCmd() *cobra.Command { @@ -34,6 +35,10 @@ func listCmd() *cobra.Command { return errors.WithStack(err) } + if flags.outdated { + return printOutdatedPackages(cmd, box) + } + for _, pkg := range box.AllPackagesIncludingRemovedTriggerPackages() { resolvedVersion, err := pkg.ResolvedVersion() if err != nil { @@ -57,6 +62,28 @@ func listCmd() *cobra.Command { return nil }, } + + cmd.Flags().BoolVar(&flags.outdated, "outdated", false, "List outdated packages") flags.config.register(cmd) return cmd } + +// printOutdatedPackages prints a list of outdated packages. +func printOutdatedPackages(cmd *cobra.Command, box *devbox.Devbox) error { + results, err := box.Outdated(cmd.Context()) + if err != nil { + return errors.WithStack(err) + } + + if len(results) == 0 { + cmd.Println("Your packages are up to date!") + return nil + } + + cmd.Println("The following packages can be updated:") + for pkg, version := range results { + cmd.Printf(" * %-30s %s -> %s\n", pkg, version.Current, version.Latest) + } + + return nil +} diff --git a/internal/devbox/packages.go b/internal/devbox/packages.go index 83d1fba7450..890ae06d307 100644 --- a/internal/devbox/packages.go +++ b/internal/devbox/packages.go @@ -43,6 +43,37 @@ const StateOutOfDateMessage = "Your devbox environment may be out of date. Run % // packages.go has functions for adding, removing and getting info about nix // packages +type UpdateVersion struct { + Current string + Latest string +} + +// Outdated returns a map of package names to their available latest version. +func (d *Devbox) Outdated(ctx context.Context) (map[string]UpdateVersion, error) { + lockfile := d.Lockfile() + outdatedPackages := map[string]UpdateVersion{} + + for _, pkg := range d.AllPackages() { + // For non-devbox packages, like flakes or runx, we can skip for now + if !pkg.IsDevboxPackage { + continue + } + + lockPackage, err := lockfile.FetchResolvedPackage(pkg.Versioned()) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch resolved package") + } + existingLockPackage := lockfile.Packages[pkg.Raw] + if lockPackage.Version == existingLockPackage.Version { + continue + } + + outdatedPackages[pkg.Versioned()] = UpdateVersion{Current: existingLockPackage.Version, Latest: lockPackage.Version} + } + + return outdatedPackages, nil +} + // Add adds the `pkgs` to the config (i.e. devbox.json) and nix profile for this // devbox project func (d *Devbox) Add(ctx context.Context, pkgsNames []string, opts devopt.AddOpts) error {