diff --git a/.github/workflows/cli-tests.yaml b/.github/workflows/cli-tests.yaml index 431fc6a0e04..b72f6933e27 100644 --- a/.github/workflows/cli-tests.yaml +++ b/.github/workflows/cli-tests.yaml @@ -143,7 +143,7 @@ jobs: cat /etc/nix/nix.conf || true echo "::endgroup::" echo "::group::Resolved Nix config" - nix show-config + nix show-config --extra-experimental-features nix-command echo "::endgroup::" go test -v -timeout $DEVBOX_GOLANG_TEST_TIMEOUT ./... @@ -202,7 +202,7 @@ jobs: cat /etc/nix/nix.conf || true echo "::endgroup::" echo "::group::Resolved Nix config" - nix show-config + nix show-config --extra-experimental-features nix-command echo "::endgroup::" devbox install devbox run -- echo "Hello from devbox!" diff --git a/devbox.go b/devbox.go index 469b8064705..8f77543d1b8 100644 --- a/devbox.go +++ b/devbox.go @@ -50,7 +50,7 @@ type Devbox interface { StopServices(ctx context.Context, allProjects bool, services ...string) error ListServices(ctx context.Context) error - Update(ctx context.Context, pkgs ...string) error + Update(ctx context.Context, opts devopt.UpdateOpts) error } // Open opens a devbox by reading the config file in dir. diff --git a/examples/databases/mariadb/devbox.lock b/examples/databases/mariadb/devbox.lock index df6d1997223..32b24bfefcb 100644 --- a/examples/databases/mariadb/devbox.lock +++ b/examples/databases/mariadb/devbox.lock @@ -2,10 +2,25 @@ "lockfile_version": "1", "packages": { "mariadb@latest": { - "last_modified": "2023-05-01T16:53:22Z", + "last_modified": "2023-08-30T00:25:28Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#mariadb", - "version": "10.6.12" + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#mariadb_110", + "source": "devbox-search", + "version": "11.0.3", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/d5nb44vw8yy22lc21ld75nndmn9c3cgr-mariadb-server-11.0.3" + }, + "aarch64-linux": { + "store_path": "/nix/store/sz5n9bkcjxklk4jd0p5h26yi5j79wh7h-mariadb-server-11.0.3" + }, + "x86_64-darwin": { + "store_path": "/nix/store/4l6h83flncplm3kmry1w08msyy7b7vdw-mariadb-server-11.0.3" + }, + "x86_64-linux": { + "store_path": "/nix/store/047g9nxp6jb2bqj1f53qk86sjzrscbmj-mariadb-server-11.0.3" + } + } } } -} \ No newline at end of file +} diff --git a/examples/databases/mysql/devbox.lock b/examples/databases/mysql/devbox.lock index f178c2c9ca8..731e9791b84 100644 --- a/examples/databases/mysql/devbox.lock +++ b/examples/databases/mysql/devbox.lock @@ -2,10 +2,10 @@ "lockfile_version": "1", "packages": { "mysql80@latest": { - "last_modified": "2023-05-01T16:53:22Z", + "last_modified": "2023-06-29T16:20:38Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#mysql80", + "resolved": "github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb#mysql80", "version": "8.0.33" } } -} \ No newline at end of file +} diff --git a/examples/development/nodejs/nodejs-yarn/devbox.lock b/examples/development/nodejs/nodejs-yarn/devbox.lock index 7e803a7c8ef..fa8d113fd1e 100644 --- a/examples/development/nodejs/nodejs-yarn/devbox.lock +++ b/examples/development/nodejs/nodejs-yarn/devbox.lock @@ -22,8 +22,9 @@ } }, "yarn@1.22": { - "last_modified": "2023-05-01T16:53:22Z", - "resolved": "github:NixOS/nixpkgs/844ffa82bbe2a2779c86ab3a72ff1b4176cec467#yarn", + "last_modified": "2023-05-21T19:52:09Z", + "resolved": "github:NixOS/nixpkgs/9356eead97d8d16956b0226d78f76bd66e06cb60#yarn", + "source": "devbox-search", "version": "1.22.19", "systems": { "aarch64-darwin": { diff --git a/examples/stacks/laravel/devbox.lock b/examples/stacks/laravel/devbox.lock index 7a236275d5e..f5795a96a80 100644 --- a/examples/stacks/laravel/devbox.lock +++ b/examples/stacks/laravel/devbox.lock @@ -2,37 +2,127 @@ "lockfile_version": "1", "packages": { "mariadb@latest": { - "last_modified": "2023-05-01T16:53:22Z", + "last_modified": "2023-08-30T00:25:28Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#mariadb", - "version": "10.6.12" + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#mariadb_110", + "source": "devbox-search", + "version": "11.0.3", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/d5nb44vw8yy22lc21ld75nndmn9c3cgr-mariadb-server-11.0.3" + }, + "aarch64-linux": { + "store_path": "/nix/store/sz5n9bkcjxklk4jd0p5h26yi5j79wh7h-mariadb-server-11.0.3" + }, + "x86_64-darwin": { + "store_path": "/nix/store/4l6h83flncplm3kmry1w08msyy7b7vdw-mariadb-server-11.0.3" + }, + "x86_64-linux": { + "store_path": "/nix/store/047g9nxp6jb2bqj1f53qk86sjzrscbmj-mariadb-server-11.0.3" + } + } }, "nginx@latest": { - "last_modified": "2023-05-01T16:53:22Z", + "last_modified": "2023-09-04T16:24:30Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#nginx", - "version": "1.24.0" + "resolved": "github:NixOS/nixpkgs/3c15feef7770eb5500a4b8792623e2d6f598c9c1#nginx", + "source": "devbox-search", + "version": "1.24.0", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/prz9lx44d3hicpmsri5rm9qk44r79ng8-nginx-1.24.0" + }, + "aarch64-linux": { + "store_path": "/nix/store/b2v5yw11gmywahlyhbqajml7hjdkhsar-nginx-1.24.0" + }, + "x86_64-darwin": { + "store_path": "/nix/store/1nyjvgj3hbhck80wkwi0h18561xbp3sy-nginx-1.24.0" + }, + "x86_64-linux": { + "store_path": "/nix/store/vc66rk5b86lx1myxr18qkgzha0fjx2ks-nginx-1.24.0" + } + } }, "nodejs@18": { - "last_modified": "2023-05-01T16:53:22Z", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#nodejs", - "version": "18.16.0" + "last_modified": "2023-08-30T00:25:28Z", + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#nodejs_18", + "source": "devbox-search", + "version": "18.17.1", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/xpqj3zg5lx25abv9qybiqd7gcs8b81fp-nodejs-18.17.1" + }, + "aarch64-linux": { + "store_path": "/nix/store/svc3bwhi6i1jd5387w8dwps23s7d4a62-nodejs-18.17.1" + }, + "x86_64-darwin": { + "store_path": "/nix/store/kjsf1qk9w4rknr02silyfq4lxlkh53xq-nodejs-18.17.1" + }, + "x86_64-linux": { + "store_path": "/nix/store/51nhk6ycfnj895q07v94jsrwmk2jmz8j-nodejs-18.17.1" + } + } }, "php81Extensions.xdebug@latest": { - "last_modified": "2023-05-01T16:53:22Z", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#php81Extensions.xdebug", - "version": "3.2.1" + "last_modified": "2023-08-30T00:25:28Z", + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#php81Extensions.xdebug", + "source": "devbox-search", + "version": "3.2.2", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/isd181vs56d55xarvlzqr0cl2c65skq7-php-xdebug-3.2.2" + }, + "aarch64-linux": { + "store_path": "/nix/store/c0ij58mr8mqcbx6kkrs1wa6x0m433hzc-php-xdebug-3.2.2" + }, + "x86_64-darwin": { + "store_path": "/nix/store/v4i2qbb7aivj3x4i7ipjg1vnfdc3ynvw-php-xdebug-3.2.2" + }, + "x86_64-linux": { + "store_path": "/nix/store/q6gp1rn2p6319l8rbwk0arvbbi8kx6bg-php-xdebug-3.2.2" + } + } }, "php81Packages.composer@latest": { - "last_modified": "2023-05-01T16:53:22Z", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#php81Packages.composer", - "version": "2.5.5" + "last_modified": "2023-08-30T00:25:28Z", + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#php81Packages.composer", + "source": "devbox-search", + "version": "2.5.8", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/7hxmkxa9bmaplyy1ixl0yxv3j0qvxvc3-php-composer-2.5.8" + }, + "aarch64-linux": { + "store_path": "/nix/store/vwrbpz9wr7blr7alx0fl3x79ym3jbdqa-php-composer-2.5.8" + }, + "x86_64-darwin": { + "store_path": "/nix/store/fjy357jpj9q2bfahsgnak4mrh8y07izw-php-composer-2.5.8" + }, + "x86_64-linux": { + "store_path": "/nix/store/6w2vrfjdwhr3mj1magr2rmbycln379f8-php-composer-2.5.8" + } + } }, "php@8.1": { - "last_modified": "2023-05-01T16:53:22Z", + "last_modified": "2023-09-04T16:24:30Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#php", - "version": "8.1.18" + "resolved": "github:NixOS/nixpkgs/3c15feef7770eb5500a4b8792623e2d6f598c9c1#php81", + "source": "devbox-search", + "version": "8.1.23", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/lb1kvhs6brfphpmxa36kn9fki89ijx7f-php-with-extensions-8.1.23" + }, + "aarch64-linux": { + "store_path": "/nix/store/2f935v7hg56faxdp0myam0v8ppdidrd1-php-with-extensions-8.1.23" + }, + "x86_64-darwin": { + "store_path": "/nix/store/v5fdxi8pasc8mxacmg76q9qzqpsr04w6-php-with-extensions-8.1.23" + }, + "x86_64-linux": { + "store_path": "/nix/store/ky5w9izkk4yyhni3nh31nicmcfch5ndz-php-with-extensions-8.1.23" + } + } }, "redis@latest": { "last_modified": "2023-05-01T16:53:22Z", @@ -41,4 +131,4 @@ "version": "7.0.11" } } -} \ No newline at end of file +} diff --git a/examples/stacks/spring/devbox.lock b/examples/stacks/spring/devbox.lock index cfb4e3bdb29..d924136b78f 100644 --- a/examples/stacks/spring/devbox.lock +++ b/examples/stacks/spring/devbox.lock @@ -2,10 +2,25 @@ "lockfile_version": "1", "packages": { "gradle@latest": { - "last_modified": "2023-06-29T16:20:38Z", + "last_modified": "2023-08-30T00:25:28Z", "plugin_version": "0.0.1", - "resolved": "github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb#gradle", - "version": "8.0.1" + "resolved": "github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9#gradle", + "source": "devbox-search", + "version": "8.3", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/4xjjls354jr8w7083qxsrpfs1fgl80fj-gradle-8.3" + }, + "aarch64-linux": { + "store_path": "/nix/store/f0l5fscvcl9yand9jfpk5icr2ac8qx2v-gradle-8.3" + }, + "x86_64-darwin": { + "store_path": "/nix/store/rxm9mpxswrd77fd6lafqcxaxizjlk1l4-gradle-8.3" + }, + "x86_64-linux": { + "store_path": "/nix/store/qvp5jiik6q27qwlgiq80fx96wyh0r0nb-gradle-8.3" + } + } }, "jdk@17": { "last_modified": "2023-06-29T16:20:38Z", @@ -19,4 +34,4 @@ "version": "8.0.33" } } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index c5b7c0387e1..25ae8062e48 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,6 @@ require ( github.com/InVisionApp/go-health/v2 v2.1.3 // indirect github.com/InVisionApp/go-logger v1.0.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect - github.com/adrg/xdg v0.4.0 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect diff --git a/go.sum b/go.sum index e99de721548..b41f1f570a4 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,6 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= @@ -245,7 +243,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -264,8 +261,6 @@ github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBU github.com/zaffka/mongodb-boltdb-mock v0.0.0-20221014194232-b4bb03fbe3a0/go.mod h1:GsDD1qsG+86MeeCG7ndi6Ei3iGthKL3wQ7PTFigDfNY= github.com/zealic/go2node v0.1.0 h1:ofxpve08cmLJBwFdI0lPCk9jfwGWOSD+s6216x0oAaA= github.com/zealic/go2node v0.1.0/go.mod h1:GrkFr+HctXwP7vzcU9RsgtAeJjTQ6Ud0IPCQAqpTfBg= -go.jetpack.io/pkg v0.0.0-20230919193042-473f1790dbf6 h1:X0sXrWsTsQcUcikm+uKOg6hJ7H8g+0SaK05s8kfg7a0= -go.jetpack.io/pkg v0.0.0-20230919193042-473f1790dbf6/go.mod h1:6RVzBortLFlql8s8oKJTX2+H7DDzp8Lr7wiIOI3FauU= go.jetpack.io/pkg v0.0.0-20230920232528-54278537129b h1:8sbFeLQ7GtVP7CxvpmBoOh6w2ZTK4DyZuMkyiIGFdjs= go.jetpack.io/pkg v0.0.0-20230920232528-54278537129b/go.mod h1:drBQ4v8Hxs501Y3KK3vbsNBhn/TEMEDHrdXK7cOb9yg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -300,7 +295,6 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/boxcli/update.go b/internal/boxcli/update.go index 7c7068c31e7..dba96dc5965 100644 --- a/internal/boxcli/update.go +++ b/internal/boxcli/update.go @@ -8,11 +8,13 @@ import ( "github.com/spf13/cobra" "go.jetpack.io/devbox" + "go.jetpack.io/devbox/internal/boxcli/usererr" "go.jetpack.io/devbox/internal/impl/devopt" ) type updateCmdFlags struct { config configFlags + sync bool } func updateCmd() *cobra.Command { @@ -32,6 +34,13 @@ func updateCmd() *cobra.Command { } flags.config.register(command) + command.Flags().BoolVar( + &flags.sync, + "sync-lock", + false, + "Sync all devbox.lock dependencies in multiple projects. "+ + "Dependencies will sync to the latest local version.", + ) return command } @@ -44,5 +53,12 @@ func updateCmdFunc(cmd *cobra.Command, args []string, flags *updateCmdFlags) err return errors.WithStack(err) } - return box.Update(cmd.Context(), args...) + 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, + }) } diff --git a/internal/impl/devopt/devboxopts.go b/internal/impl/devopt/devboxopts.go index 87a4a5dd570..2edc71c2e01 100644 --- a/internal/impl/devopt/devboxopts.go +++ b/internal/impl/devopt/devboxopts.go @@ -37,3 +37,8 @@ type Credentials struct { Email string Sub string } + +type UpdateOpts struct { + Pkgs []string + Sync bool +} diff --git a/internal/impl/update.go b/internal/impl/update.go index 18b33522bfc..584bb9dd780 100644 --- a/internal/impl/update.go +++ b/internal/impl/update.go @@ -9,6 +9,7 @@ import ( "go.jetpack.io/devbox/internal/boxcli/featureflag" "go.jetpack.io/devbox/internal/devpkg" + "go.jetpack.io/devbox/internal/impl/devopt" "go.jetpack.io/devbox/internal/lock" "go.jetpack.io/devbox/internal/nix" "go.jetpack.io/devbox/internal/nix/nixprofile" @@ -18,8 +19,12 @@ import ( "go.jetpack.io/devbox/internal/wrapnix" ) -func (d *Devbox) Update(ctx context.Context, pkgs ...string) error { - inputs, err := d.inputsToUpdate(pkgs...) +func (d *Devbox) Update(ctx context.Context, opts devopt.UpdateOpts) error { + if opts.Sync { + return lock.SyncLockfiles() + } + + inputs, err := d.inputsToUpdate(opts.Pkgs...) if err != nil { return err } diff --git a/internal/lock/package.go b/internal/lock/package.go index 02df9e97ca1..7c065684e39 100644 --- a/internal/lock/package.go +++ b/internal/lock/package.go @@ -17,6 +17,8 @@ type Package struct { Version string `json:"version,omitempty"` // Systems is keyed by the system name Systems map[string]*SystemInfo `json:"systems,omitempty"` + + // NOTE: if you add more fields, please update SyncLockfiles } type SystemInfo struct { diff --git a/internal/lock/sync.go b/internal/lock/sync.go new file mode 100644 index 00000000000..60d10e6efd8 --- /dev/null +++ b/internal/lock/sync.go @@ -0,0 +1,105 @@ +package lock + +import ( + "fmt" + "io/fs" + "path/filepath" + "time" + + "go.jetpack.io/devbox/internal/cuecfg" + "go.jetpack.io/devbox/internal/debug" +) + +func SyncLockfiles() error { + lockfilePaths, err := collectLockfiles() + if err != nil { + return err + } + + latestPackages, err := latestPackages(lockfilePaths) + if err != nil { + return err + } + + for _, lockfilePath := range lockfilePaths { + var lockFile File + if err := cuecfg.ParseFile(lockfilePath, &lockFile); err != nil { + return err + } + + changed := false + for key, latestPkg := range latestPackages { + if pkg, exists := lockFile.Packages[key]; exists { + if pkg.LastModified != latestPkg.LastModified { + lockFile.Packages[key].AllowInsecure = latestPkg.AllowInsecure + lockFile.Packages[key].LastModified = latestPkg.LastModified + // PluginVersion is intentionally omitted + lockFile.Packages[key].Resolved = latestPkg.Resolved + lockFile.Packages[key].Source = latestPkg.Source + lockFile.Packages[key].Version = latestPkg.Version + lockFile.Packages[key].Systems = latestPkg.Systems + changed = true + } + } + } + + if changed { + if err = cuecfg.WriteFile(lockfilePath, lockFile); err != nil { + return err + } + fmt.Printf("Updated: %s\n", lockfilePath) + } + } + + return nil +} + +func latestPackages(lockfilePaths []string) (map[string]*Package, error) { + latestPackages := make(map[string]*Package) + + for _, lockFilePath := range lockfilePaths { + var lockFile File + if err := cuecfg.ParseFile(lockFilePath, &lockFile); err != nil { + return nil, err + } + for key, pkg := range lockFile.Packages { + if latestPkg, exists := latestPackages[key]; exists { + // Ignore error, which makes currentTime.After always false. + currentTime, _ := time.Parse(time.RFC3339, pkg.LastModified) + latestTime, err := time.Parse(time.RFC3339, latestPkg.LastModified) + if err != nil { + return nil, err + } + if currentTime.After(latestTime) { + latestPackages[key] = pkg + } + } else if _, err := time.Parse(time.RFC3339, pkg.LastModified); err == nil { + latestPackages[key] = pkg + } + } + } + + return latestPackages, nil +} + +func collectLockfiles() ([]string, error) { + defer debug.FunctionTimer().End() + + var lockfiles []string + err := filepath.WalkDir( + ".", + func(path string, dirEntry fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !dirEntry.IsDir() && filepath.Base(path) == "devbox.lock" { + lockfiles = append(lockfiles, path) + } + + return nil + }, + ) + + return lockfiles, err +}