diff --git a/internal/boxcli/multi/multi.go b/internal/boxcli/multi/multi.go new file mode 100644 index 00000000000..3f4c34a8911 --- /dev/null +++ b/internal/boxcli/multi/multi.go @@ -0,0 +1,38 @@ +package multi + +import ( + "io/fs" + "path/filepath" + + "go.jetpack.io/devbox" + "go.jetpack.io/devbox/internal/debug" + "go.jetpack.io/devbox/internal/impl/devopt" +) + +func Open(opts *devopt.Opts) ([]devbox.Devbox, error) { + defer debug.FunctionTimer().End() + + var boxes []devbox.Devbox + err := filepath.WalkDir( + ".", + func(path string, dirEntry fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !dirEntry.IsDir() && filepath.Base(path) == "devbox.json" { + optsCopy := *opts + optsCopy.Dir = path + box, err := devbox.Open(&optsCopy) + if err != nil { + return err + } + boxes = append(boxes, box) + } + + return nil + }, + ) + + return boxes, err +} diff --git a/internal/lock/sync.go b/internal/boxcli/multi/sync.go similarity index 80% rename from internal/lock/sync.go rename to internal/boxcli/multi/sync.go index 60d10e6efd8..ac43ea8d2b7 100644 --- a/internal/lock/sync.go +++ b/internal/boxcli/multi/sync.go @@ -1,4 +1,4 @@ -package lock +package multi import ( "fmt" @@ -8,9 +8,11 @@ import ( "go.jetpack.io/devbox/internal/cuecfg" "go.jetpack.io/devbox/internal/debug" + "go.jetpack.io/devbox/internal/lock" + "go.jetpack.io/devbox/internal/searcher" ) -func SyncLockfiles() error { +func SyncLockfiles(pkgs []string) error { lockfilePaths, err := collectLockfiles() if err != nil { return err @@ -21,8 +23,13 @@ func SyncLockfiles() error { return err } + pkgMap := make(map[string]bool) + for _, pkg := range pkgs { + pkgMap[pkg] = true + } + for _, lockfilePath := range lockfilePaths { - var lockFile File + var lockFile lock.File if err := cuecfg.ParseFile(lockfilePath, &lockFile); err != nil { return err } @@ -30,6 +37,10 @@ func SyncLockfiles() error { changed := false for key, latestPkg := range latestPackages { if pkg, exists := lockFile.Packages[key]; exists { + name, _, found := searcher.ParseVersionedPackage(key) + if len(pkgMap) > 0 && (!pkgMap[key] && (found && !pkgMap[name])) { + continue + } if pkg.LastModified != latestPkg.LastModified { lockFile.Packages[key].AllowInsecure = latestPkg.AllowInsecure lockFile.Packages[key].LastModified = latestPkg.LastModified @@ -54,11 +65,11 @@ func SyncLockfiles() error { return nil } -func latestPackages(lockfilePaths []string) (map[string]*Package, error) { - latestPackages := make(map[string]*Package) +func latestPackages(lockfilePaths []string) (map[string]*lock.Package, error) { + latestPackages := make(map[string]*lock.Package) for _, lockFilePath := range lockfilePaths { - var lockFile File + var lockFile lock.File if err := cuecfg.ParseFile(lockFilePath, &lockFile); err != nil { return nil, err } diff --git a/internal/boxcli/update.go b/internal/boxcli/update.go index 5f80cd5bd82..7578e9a8e99 100644 --- a/internal/boxcli/update.go +++ b/internal/boxcli/update.go @@ -8,13 +8,15 @@ import ( "github.com/spf13/cobra" "go.jetpack.io/devbox" + "go.jetpack.io/devbox/internal/boxcli/multi" "go.jetpack.io/devbox/internal/boxcli/usererr" "go.jetpack.io/devbox/internal/impl/devopt" ) type updateCmdFlags struct { - config configFlags - sync bool + config configFlags + sync bool + allProjects bool } func updateCmd() *cobra.Command { @@ -41,10 +43,28 @@ func updateCmd() *cobra.Command { "Sync all devbox.lock dependencies in multiple projects. "+ "Dependencies will sync to the latest local version.", ) + command.Flags().BoolVar( + &flags.allProjects, + "all-projects", + false, + "Update all projects in the working directory, recursively.", + ) return command } func updateCmdFunc(cmd *cobra.Command, args []string, flags *updateCmdFlags) error { + if len(args) > 0 && flags.sync { + return usererr.New("cannot specify both a package and --sync") + } + + if flags.allProjects { + return updateAllProjects(cmd, args) + } + + if flags.sync { + return multi.SyncLockfiles(args) + } + box, err := devbox.Open(&devopt.Opts{ Dir: flags.config.path, Stderr: cmd.ErrOrStderr(), @@ -53,12 +73,25 @@ func updateCmdFunc(cmd *cobra.Command, args []string, flags *updateCmdFlags) err return errors.WithStack(err) } - if len(args) > 0 && flags.sync { - return usererr.New("cannot specify both a package and --sync") - } - return box.Update(cmd.Context(), devopt.UpdateOpts{ Pkgs: args, - Sync: flags.sync, }) } + +func updateAllProjects(cmd *cobra.Command, args []string) error { + boxes, err := multi.Open(&devopt.Opts{ + Stderr: cmd.ErrOrStderr(), + }) + if err != nil { + return errors.WithStack(err) + } + for _, box := range boxes { + if err := box.Update(cmd.Context(), devopt.UpdateOpts{ + Pkgs: args, + IgnoreMissingPackages: true, + }); err != nil { + return err + } + } + return multi.SyncLockfiles(args) +} diff --git a/internal/impl/devbox.go b/internal/impl/devbox.go index 93583e9f8f5..33e59d40f84 100644 --- a/internal/impl/devbox.go +++ b/internal/impl/devbox.go @@ -1023,7 +1023,8 @@ func (d *Devbox) findPackageByName(name string) (*devpkg.Package, error) { ) } if len(results) == 0 { - return nil, usererr.New("no package found with name %s", name) + return nil, usererr.WithUserMessage( + searcher.ErrNotFound, "no package found with name %s", name) } return lo.Keys(results)[0], nil } diff --git a/internal/impl/devopt/devboxopts.go b/internal/impl/devopt/devboxopts.go index 2d7f45a30f2..041b86fb1db 100644 --- a/internal/impl/devopt/devboxopts.go +++ b/internal/impl/devopt/devboxopts.go @@ -39,6 +39,6 @@ type Credentials struct { } type UpdateOpts struct { - Pkgs []string - Sync bool + Pkgs []string + IgnoreMissingPackages bool } diff --git a/internal/impl/update.go b/internal/impl/update.go index aedd7981c7f..69db9d270de 100644 --- a/internal/impl/update.go +++ b/internal/impl/update.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + "github.com/pkg/errors" "go.jetpack.io/devbox/internal/boxcli/featureflag" "go.jetpack.io/devbox/internal/devpkg" "go.jetpack.io/devbox/internal/impl/devopt" @@ -20,11 +21,7 @@ import ( ) func (d *Devbox) Update(ctx context.Context, opts devopt.UpdateOpts) error { - if opts.Sync { - return lock.SyncLockfiles() - } - - inputs, err := d.inputsToUpdate(opts.Pkgs...) + inputs, err := d.inputsToUpdate(opts) if err != nil { return err } @@ -77,15 +74,19 @@ func (d *Devbox) Update(ctx context.Context, opts devopt.UpdateOpts) error { return nix.FlakeUpdate(shellgen.FlakePath(d)) } -func (d *Devbox) inputsToUpdate(pkgs ...string) ([]*devpkg.Package, error) { - if len(pkgs) == 0 { +func (d *Devbox) inputsToUpdate( + opts devopt.UpdateOpts, +) ([]*devpkg.Package, error) { + if len(opts.Pkgs) == 0 { return d.configPackages(), nil } var pkgsToUpdate []*devpkg.Package - for _, pkg := range pkgs { + for _, pkg := range opts.Pkgs { found, err := d.findPackageByName(pkg) - if err != nil { + if opts.IgnoreMissingPackages && errors.Is(err, searcher.ErrNotFound) { + continue + } else if err != nil { return nil, err } pkgsToUpdate = append(pkgsToUpdate, found)