Fixes for version selection
This commit is contained in:
parent
12b6654c2f
commit
f26928306b
3 changed files with 121 additions and 0 deletions
45
CLAUDE.md
Normal file
45
CLAUDE.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# hyper-quineglobal-com
|
||||
|
||||
Website and auto-update feed server for [Quine Hyper](https://github.com/quine-global/hyper), a fork of the Hyper terminal.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
make dev # start Go server + Tailwind watcher (Ctrl-C stops both)
|
||||
make start # Go server only
|
||||
make test # go test -shuffle on ./...
|
||||
make css # one-shot Tailwind build
|
||||
make lint # golangci-lint
|
||||
make build # Docker image (linux/amd64)
|
||||
make push # build + push to forge.quinefoundation.com
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Pure Go HTTP server (`go-chi/chi`). HTML is rendered server-side with `maragu.dev/gomponents` (no templates). CSS is Tailwind, compiled to `public/styles/app.css`.
|
||||
|
||||
```
|
||||
cmd/app/ entry point
|
||||
http/ route handlers, server, release cache, update-check logic
|
||||
html/ gomponents page renderers and shared types (Release, DownloadProps, etc.)
|
||||
public/ static assets (images, compiled CSS)
|
||||
scripts/ release refresh helper script
|
||||
```
|
||||
|
||||
## Key concepts
|
||||
|
||||
**Release cache** (`http/releases.go`): fetches GitHub releases from the `quine-global/hyper` repo via the GitHub API, caches them in memory. `TryRefresh` is debounced (called per-request, no-ops if cache is fresh or a fetch is already running). `ForceRefresh` is used by the internal `/internal/refresh` endpoint.
|
||||
|
||||
**Asset classification** (`classifyAsset` in `http/releases.go`): maps GitHub asset filenames to `(os, arch)` keys used in `Release.Assets`. Supported platforms: `mac/{arm64,x64}`, `windows/x64`, `linux/{arm64,x64}` (AppImage), `linux-rpm/{arm64,x64}`. armv7l and other unrecognised ARM variants are explicitly excluded (return `ok=false`) so they don't get misclassified as x64.
|
||||
|
||||
**Auto-update feed** (`http/updatecheck.go`): implements the Hazel/Squirrel update protocol originally served at `releases.hyper.is`. Responds to `GET /api/update-check` and `GET /update` with 204 (no update) or 200 JSON `{name, notes, pub_date, url}`. Handles stable and canary tracks; platform strings from the Electron client (`darwin`, `win32`, `deb`, etc.) are normalised via `normalizePlatformForAssets`.
|
||||
|
||||
**Download page** (`http/home.go`, `html/download.go`): detects platform from `User-Agent`, supports manual override via `?os=` and `?arch=` query params. Linux format toggle (AppImage vs RPM) is a separate `?os=linux-rpm` variant.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests live alongside source in `package http` (internal) or `package http_test` (integration). Run with `make test`. Notable test files:
|
||||
|
||||
- `http/releases_test.go` — `classifyAsset` and `parseTag`
|
||||
- `http/updatecheck_test.go` — `semverCmp`, `newerCanary`, `normalizePlatformForAssets`
|
||||
- `http/server_test.go` — integration test that starts the real server
|
||||
|
|
@ -184,6 +184,8 @@ func classifyAsset(name string) (os, arch string, ok bool) {
|
|||
return "mac", "x64", true
|
||||
case strings.Contains(l, "arm64") && strings.HasSuffix(l, ".appimage"):
|
||||
return "linux", "arm64", true
|
||||
case strings.Contains(l, "armv7l") && strings.HasSuffix(l, ".appimage"):
|
||||
return "", "", false
|
||||
case strings.HasSuffix(l, ".appimage"):
|
||||
return "linux", "x64", true
|
||||
case strings.Contains(l, "arm64") && strings.HasSuffix(l, ".rpm"):
|
||||
|
|
|
|||
74
http/releases_test.go
Normal file
74
http/releases_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"maragu.dev/is"
|
||||
)
|
||||
|
||||
func TestClassifyAsset(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
wantOS string
|
||||
wantArch string
|
||||
wantOK bool
|
||||
}{
|
||||
// Windows
|
||||
{"QuineHyper.Setup.4.0.0.exe", "windows", "x64", true},
|
||||
{"QuineHyper.Setup.4.0.0-q.exe", "windows", "x64", true},
|
||||
|
||||
// macOS
|
||||
{"QuineHyper-4.0.0.dmg", "mac", "x64", true},
|
||||
{"QuineHyper-4.0.0-arm64.dmg", "mac", "arm64", true},
|
||||
|
||||
// Linux AppImage x64
|
||||
{"QuineHyper-4.0.0.AppImage", "linux", "x64", true},
|
||||
{"quinehyper-4.0.0.appimage", "linux", "x64", true},
|
||||
|
||||
// Linux AppImage arm64
|
||||
{"QuineHyper-4.0.0-arm64.AppImage", "linux", "arm64", true},
|
||||
|
||||
// Linux AppImage armv7l — must NOT be classified as x64
|
||||
{"QuineHyper-4.0.0-armv7l.AppImage", "", "", false},
|
||||
{"quinehyper-4.0.0-armv7l.appimage", "", "", false},
|
||||
|
||||
// Linux RPM
|
||||
{"QuineHyper-4.0.0.rpm", "linux-rpm", "x64", true},
|
||||
{"QuineHyper-4.0.0-arm64.rpm", "linux-rpm", "arm64", true},
|
||||
|
||||
// Unknown / unrecognised
|
||||
{"QuineHyper-4.0.0.zip", "", "", false},
|
||||
{"latest.yml", "", "", false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
gotOS, gotArch, gotOK := classifyAsset(c.name)
|
||||
is.Equal(t, c.wantOK, gotOK)
|
||||
is.Equal(t, c.wantOS, gotOS)
|
||||
is.Equal(t, c.wantArch, gotArch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTag(t *testing.T) {
|
||||
cases := []struct {
|
||||
tag string
|
||||
wantVer string
|
||||
wantBuild int
|
||||
}{
|
||||
{"4.0.0", "4.0.0", 0},
|
||||
{"4.0.0-q", "4.0.0", 0},
|
||||
{"4.0.0q", "4.0.0", 0},
|
||||
{"4.0.0-q-canary.12", "4.0.0", 12},
|
||||
{"4.0.0-q-canary.1", "4.0.0", 1},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.tag, func(t *testing.T) {
|
||||
gotVer, gotBuild := parseTag(c.tag)
|
||||
is.Equal(t, c.wantVer, gotVer)
|
||||
is.Equal(t, c.wantBuild, gotBuild)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue