From bcce7387f96c97b6c888f8add75bb42bd5c5d9a7 Mon Sep 17 00:00:00 2001 From: Zied Yousfi Date: Sat, 9 May 2026 21:16:26 +0200 Subject: [PATCH 1/2] feat(npm.go): fetch package versions concurrently 14-make-fetching-packages-version-concurrent --- go.mod | 1 + go.sum | 2 ++ npm.go | 106 +++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 9bc0287..a29f2a7 100644 --- a/go.mod +++ b/go.mod @@ -18,5 +18,6 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.30.0 // indirect ) diff --git a/go.sum b/go.sum index 322043b..184d10c 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/npm.go b/npm.go index 86ab0c4..20182a6 100644 --- a/npm.go +++ b/npm.go @@ -1,15 +1,18 @@ package main import ( + "context" "encoding/json" "fmt" "net/http" "net/url" "os" "strings" + "sync" "time" "github.com/charmbracelet/log" + "golang.org/x/sync/errgroup" ) type PackageJSONRaw struct { @@ -156,52 +159,89 @@ func getOtherNPMPackageVersions(packageName string) ([]string, error) { } func updateDependencies(deps []DependencyJSON) ([]DependencyUpdate, error) { - updates := make([]DependencyUpdate, 0) + var ( + mu sync.Mutex + updates = make([]DependencyUpdate, 0) + ) - for i, dep := range deps { - log.Debugf("Dependency : %s, version : %s", dep.Name, dep.Version.String()) - if dep.Name == "" { - log.Warnf("Dependency name is empty, skipping...") - continue - } + g, _ := errgroup.WithContext(context.Background()) - latestVersionString, err := getNPMPackageLatestVersion(dep.Name) - if err != nil { - return nil, fmt.Errorf("failed to fetch latest version for %s: %w", dep.Name, err) - } + // Rate limits hihi + g.SetLimit(8) - latestVersion := parseDependencyVersion(latestVersionString) - log.Debugf("Latest version of %s : %s", dep.Name, latestVersion.String()) + for i := range deps { + i := i - changeType, shouldUpdate := classifyDependencyUpdate(dep.Version, latestVersion) - if changeType == SemverChangeDowngrade { - log.Warnf("Dependency %s current version %s is newer than registry latest %s, keeping current version", dep.Name, dep.Version.String(), latestVersion.String()) - continue - } + g.Go(func() error { + dep := deps[i] - if !shouldUpdate { - if !Ctx.PatchOnly || !dep.Version.HasSemver { - log.Debugf("Dependency %s won't update (%s)", dep.Name, changeType) - continue + log.Debugf("Dependency : %s, version : %s", dep.Name, dep.Version.String()) + + if dep.Name == "" { + log.Warnf("Dependency name is empty, skipping...") + return nil } - patchVersion, ok, err := getLatestPatchNPMPackageVersion(dep.Name, dep.Version) + latestVersionString, err := getNPMPackageLatestVersion(dep.Name) if err != nil { - return nil, fmt.Errorf("failed to fetch other versions for %s: %w", dep.Name, err) + return fmt.Errorf("failed to fetch latest version for %s: %w", dep.Name, err) } - if !ok { - log.Debugf("Dependency %s has no available patch update", dep.Name) - continue + + latestVersion := parseDependencyVersion(latestVersionString) + log.Debugf("Latest version of %s : %s", dep.Name, latestVersion.String()) + + changeType, shouldUpdate := classifyDependencyUpdate(dep.Version, latestVersion) + if changeType == SemverChangeDowngrade { + log.Warnf( + "Dependency %s current version %s is newer than registry latest %s, keeping current version", + dep.Name, + dep.Version.String(), + latestVersion.String(), + ) + return nil } - latestVersion = patchVersion - } + if !shouldUpdate { + if !Ctx.PatchOnly || !dep.Version.HasSemver { + log.Debugf("Dependency %s won't update (%s)", dep.Name, changeType) + return nil + } + + patchVersion, ok, err := getLatestPatchNPMPackageVersion(dep.Name, dep.Version) + if err != nil { + return fmt.Errorf("failed to fetch other versions for %s: %w", dep.Name, err) + } + if !ok { + log.Debugf("Dependency %s has no available patch update", dep.Name) + return nil + } + + latestVersion = patchVersion + } - updatedVersion := mergeDependencyVersion(dep.Version, latestVersion) - updates = append(updates, DependencyUpdate{Name: dep.Name, Before: dep.Version, After: updatedVersion}) - dep.Version = updatedVersion - deps[i] = dep + updatedVersion := mergeDependencyVersion(dep.Version, latestVersion) + + update := DependencyUpdate{ + Name: dep.Name, + Before: dep.Version, + After: updatedVersion, + } + + dep.Version = updatedVersion + + mu.Lock() + deps[i] = dep + updates = append(updates, update) + mu.Unlock() + + return nil + }) + } + + if err := g.Wait(); err != nil { + return nil, err } + return updates, nil } From 0fbe4287feb07069598dae8cfa405df3cbed1c90 Mon Sep 17 00:00:00 2001 From: Zied Yousfi Date: Sat, 9 May 2026 22:54:33 +0200 Subject: [PATCH 2/2] fix(npm.go): make package version fetching concurrent Cap npm registry requests with errgroup while avoiding the extra context wrapper. --- npm.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/npm.go b/npm.go index 20182a6..d8a9642 100644 --- a/npm.go +++ b/npm.go @@ -1,7 +1,6 @@ package main import ( - "context" "encoding/json" "fmt" "net/http" @@ -164,14 +163,12 @@ func updateDependencies(deps []DependencyJSON) ([]DependencyUpdate, error) { updates = make([]DependencyUpdate, 0) ) - g, _ := errgroup.WithContext(context.Background()) + var g errgroup.Group - // Rate limits hihi + // Cap concurrent npm registry requests to avoid overwhelming the host. g.SetLimit(8) for i := range deps { - i := i - g.Go(func() error { dep := deps[i]