What: Update, Update all, the hourly auto-update and the move's install ask npm for the plugin's newest version and have bun install that exact version (installAt), then check it is the one installed. When bun won't, the error says why, reason first for the status pill: "<pkg> 0.1.21 isn't installed yet: Bun's minimumReleaseAge (in a .bunfig.toml) holds back versions published less than 2 days ago, and magpie installs it once it is older (bun's own words)". Another bun error (a registry of the user's own without the version) is "<pkg> <v> isn't installed: <bun's error: lines>". Specs stay as they were (name@latest); pinned and git plugins, and npm not answering, install as their spec says, as before. Why: the Factory card stayed at 0.1.18 with 0.1.21 out, through clicking update and restarts. bun add pkg@latest resolves "latest" by bun's own config: a minimumReleaseAge in the user's ~/.bunfig.toml or $XDG_CONFIG_HOME/.bunfig.toml makes it install the newest version older than that and exit 0. 172800s (2 days) gives exactly 0.1.18 (0.1.19-21 were published 10-08). So every update path succeeded with nothing changed, and the page said "updated to v0.1.21". Asked for 0.1.21 itself, bun fails with "blocked by minimum-release-age: 172800 seconds". The same code serves every moved plugin (Cursor, Devin, Kiro, Qoder, ...), so all of them had it. npm view: 0.1.21 is latest; npmmirror has it too. Run: - TestUpdateBunHoldsBackSaysWhy (new): real bun 1.4.2 against a fake registry with 0.1.18 (3 days old) and 0.1.21 (1 hour), a bunfig with minimumReleaseAge = 172800. On the old code it fails: "Update with 0.1.21 held back by Bun: <nil>, want bun's reason (installed 0.1.18)". Breaking Update alone fails at "Update all: <nil>", breaking the hourly update alone at "the hourly update: <nil>". Without the bunfig it installs 0.1.21 and keeps the spec @latest. - Real use: magpie web (nogui build) under a sandbox HOME, real npm, with the bunfig: add factory-auth@0.1.18, spec @latest, POST /api/plugins/upgrade and /update return the reason above, the card stays 0.1.18; bunfig removed, upgrade installs 0.1.21. - Ruled out on macOS and the Windows box: plain upgrade 0.1.18 -> 0.1.21 works there without a bunfig, host running or not. - gofmt -l; go vet (darwin, GOOS=linux, GOOS=windows, -tags nogui) on internal/plugin, internal/provider, internal/gui; go build (darwin), GOOS=linux and GOOS=windows -tags nogui builds; go test -tags nogui ./... under a temp HOME, all ok.
136 lines
4.7 KiB
Go
136 lines
4.7 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Check for updates asks npm now, whatever it said within the hour: each
|
|
// plugin says whether npm has a newer version, and why it couldn't be
|
|
// checked when npm didn't say; someone else's update found waits for the
|
|
// reader (the dot on Plugins), and nothing is installed.
|
|
func TestCheckNow(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("HOME", dir)
|
|
t.Setenv("USERPROFILE", dir)
|
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
|
|
const (
|
|
ours = "@magpie-community/opencode-zed-auth"
|
|
theirs = "opencode-gemini-auth"
|
|
current = "@magpie-community/opencode-kiro-auth"
|
|
gone = "opencode-gone-auth"
|
|
limited = "opencode-busy-auth"
|
|
broken = "opencode-broken-auth"
|
|
)
|
|
answers := map[string]string{ours: "0.1.4", theirs: "1.0.0", current: "0.2.0"}
|
|
fakeNPM(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/downloads/") {
|
|
w.Write([]byte(`{"downloads":1}`))
|
|
return
|
|
}
|
|
name := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), "/latest")
|
|
switch name {
|
|
case limited:
|
|
http.Error(w, "slow down", http.StatusTooManyRequests)
|
|
case broken:
|
|
http.Error(w, "oops", http.StatusInternalServerError)
|
|
case gone:
|
|
http.NotFound(w, r)
|
|
default:
|
|
w.Write([]byte(`{"version":"` + answers[name] + `"}`))
|
|
}
|
|
})
|
|
folder := filepath.Join(dir, "myplugin")
|
|
if err := save(List{Plugins: []Entry{{Spec: ours}, {Spec: theirs + "@latest"}, {Spec: current}, {Spec: gone}, {Spec: limited}, {Spec: broken}, {Spec: "github:me/opencode-x-auth"}, {Spec: folder}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, p := range []string{ours, theirs, gone, limited, broken} {
|
|
installedAt(t, p, "0.1.2")
|
|
}
|
|
installedAt(t, current, "0.2.0")
|
|
// what npm said within the hour, before the new versions came out
|
|
npmMu.Lock()
|
|
for _, p := range []string{ours, theirs} {
|
|
npmCached()[p] = npmEntry{NPM{Version: "0.1.2"}, time.Now().Add(-time.Minute)}
|
|
}
|
|
npmMu.Unlock()
|
|
was := installNewest
|
|
t.Cleanup(func() { installNewest = was })
|
|
installNewest = func(_ context.Context, pkg, _ string) error { t.Errorf("a check installed %s", pkg); return nil }
|
|
|
|
c := CheckNow(context.Background())
|
|
got := map[string]string{}
|
|
for _, p := range c.Plugins {
|
|
got[p.Package] = p.Status + " " + p.Why + " " + p.Version + ">" + p.Latest
|
|
}
|
|
want := map[string]string{
|
|
ours: "update 0.1.2>0.1.4",
|
|
theirs: "update 0.1.2>1.0.0",
|
|
current: "current 0.2.0>0.2.0",
|
|
gone: "unknown missing 0.1.2>",
|
|
limited: "unknown limited 0.1.2>",
|
|
broken: "unknown registry 0.1.2>",
|
|
"github:me/opencode-x-auth": "git >",
|
|
folder: "folder >",
|
|
}
|
|
for k, v := range want {
|
|
if got[k] != v {
|
|
t.Errorf("%s: %q, want %q", k, got[k], v)
|
|
}
|
|
}
|
|
if c.At.IsZero() || len(c.Plugins) != len(want) {
|
|
t.Fatalf("checked %+v", c)
|
|
}
|
|
for _, p := range c.Plugins {
|
|
if p.Auto != (p.Package == ours || p.Package == current) {
|
|
t.Errorf("%s: Auto %v", p.Package, p.Auto)
|
|
}
|
|
}
|
|
// what npm said now is what the page reads, not what it said before
|
|
if v := InfoCached([]string{ours})[ours].Version; v != "0.1.4" {
|
|
t.Errorf("kept %s, want 0.1.4", v)
|
|
}
|
|
// someone else's update waits for the reader; the community's comes
|
|
// by itself
|
|
if p := PendingUpdates(); len(p.Waiting) != 1 || p.Waiting[0].Package != theirs || p.Waiting[0].Latest != "1.0.0" || p.Checked.IsZero() {
|
|
t.Fatalf("waiting %+v", p)
|
|
}
|
|
}
|
|
|
|
// npm not reached: every plugin says it couldn't be checked, offline, and
|
|
// what waited for the reader before still waits.
|
|
func TestCheckNowOffline(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("HOME", dir)
|
|
t.Setenv("USERPROFILE", dir)
|
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
|
|
fakeNPM(t, func(http.ResponseWriter, *http.Request) {})
|
|
down := httptest.NewServer(http.NotFoundHandler())
|
|
npmRegistry = down.URL
|
|
down.Close()
|
|
const theirs = "opencode-gemini-auth"
|
|
if err := save(List{Plugins: []Entry{{Spec: theirs}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
installedAt(t, theirs, "0.1.2")
|
|
if err := os.MkdirAll(filepath.Dir(updatesPath()), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(updatesPath(), []byte(`{"waiting":[{"spec":"`+theirs+`","package":"`+theirs+`","version":"0.1.2","latest":"0.2.0"}]}`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := CheckNow(context.Background())
|
|
if len(c.Plugins) != 1 || c.Plugins[0].Status != "unknown" || c.Plugins[0].Why != "offline" || c.Plugins[0].Error == "" {
|
|
t.Fatalf("offline: %+v", c.Plugins)
|
|
}
|
|
if p := PendingUpdates(); len(p.Waiting) != 1 || p.Waiting[0].Latest != "0.2.0" {
|
|
t.Fatalf("waiting %+v", p.Waiting)
|
|
}
|
|
}
|