first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
// This file implements the update operation (#164): bring an installed package
// up to the latest version published on npm. Update builds on the pieces the
// earlier issues provided — the lockfile (#154) records what is installed and
// from which source, Fetch (#156) pulls the latest tarball, and the shared
// installFetched path (#162) re-classifies and re-distributes.
//
// The flow per package is: look up its recorded source, fetch the latest
// version, and compare against the installed version. If unchanged, it is a
// no-op ("up to date"). Otherwise the old files are removed and the freshly
// fetched version is distributed and recorded. Fetch/classify happen before any
// removal, so a fetch failure leaves the existing install untouched.
package pkgmgr
import (
"fmt"
"io"
)
// UpdateResult reports what Update did for one package.
type UpdateResult struct {
Name string
// OldVersion is the version that was installed before the update.
OldVersion string
// NewVersion is the version after the update (== OldVersion when no-op).
NewVersion string
// Updated is true when a newer version was fetched and installed.
Updated bool
}
// Update brings the single installed package name up to the latest version from
// its recorded source. Progress is written to logw when non-nil. Updating a
// package that is not installed is an error. When the installed version is
// already latest, it is a no-op and Updated is false.
//
// The latest tarball is fetched and classified before the old files are
// removed, so a fetch or classify failure leaves the prior install intact.
func Update(name, lockfilePath string, logw io.Writer) (UpdateResult, error) {
logf := func(format string, a ...any) {
if logw != nil {
fmt.Fprintf(logw, format, a...)
}
}
lf, err := Load(lockfilePath)
if err != nil {
return UpdateResult{}, err
}
p, ok := lf.Get(name)
if !ok {
return UpdateResult{}, fmt.Errorf("package not installed: %s", name)
}
// Resolve the source to its latest version by dropping any pinned version,
// so `update` always targets the newest published release.
ref, err := ParsePackageRef(p.Source)
if err != nil {
return UpdateResult{}, fmt.Errorf("pkgmgr: bad recorded source for %s: %w", name, err)
}
ref.Version = ""
logf("Fetching %s ...\n", ref.String())
fetched, err := Fetch(ref)
if err != nil {
return UpdateResult{}, err // old install untouched
}
defer fetched.Cleanup()
_, newVersion, _, err := Classify(fetched.Dir)
if err != nil {
return UpdateResult{}, err // old install untouched
}
if newVersion == p.Version {
logf("%s is up to date\n", name)
return UpdateResult{Name: name, OldVersion: p.Version, NewVersion: p.Version, Updated: false}, nil
}
// Newer version fetched: remove the old files, then distribute the new one.
if err := Uninstall(name, lockfilePath, logw); err != nil {
return UpdateResult{}, err
}
if _, err := installFetched(fetched.Dir, ref, lockfilePath, logf); err != nil {
return UpdateResult{}, err
}
logf("Updated %s %s -> %s\n", name, p.Version, newVersion)
return UpdateResult{Name: name, OldVersion: p.Version, NewVersion: newVersion, Updated: true}, nil
}