87 lines
3.1 KiB
Go
87 lines
3.1 KiB
Go
// 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
|
|
}
|