Final pre-launch version: Update logic
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
.PHONY: all build dev clean test test-cover lint install deps frontend-deps run package help
|
||||
.PHONY: all build dev clean test test-cover lint install deps frontend-deps run package release help
|
||||
|
||||
# Resolve the Wails binary. Prefer wails on PATH; fall back to GOPATH/bin.
|
||||
WAILS := $(or $(shell which wails 2>/dev/null),$(shell go env GOPATH)/bin/wails)
|
||||
VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null || echo dev)-$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
LD_UPDATE_URL := $(if $(NEXUS_UPDATE_URL),-X github.com/andydixon/nexus/internal/api.appUpdateURL=$(NEXUS_UPDATE_URL),)
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
@@ -20,7 +22,7 @@ dev: deps
|
||||
|
||||
# Production build
|
||||
build: deps
|
||||
$(WAILS) build -ldflags "-s -w"
|
||||
$(WAILS) build -ldflags "-s -w -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
@@ -47,6 +49,10 @@ lint:
|
||||
package: build
|
||||
@echo "Binary built at build/bin/"
|
||||
|
||||
# Build and package release artefacts for all platforms
|
||||
release:
|
||||
./release.sh
|
||||
|
||||
# Help
|
||||
help:
|
||||
@echo "Nexus Terminal — Build Targets"
|
||||
@@ -59,4 +65,5 @@ help:
|
||||
@echo " make clean — Clean build artefacts"
|
||||
@echo " make lint — Run linter (requires golangci-lint)"
|
||||
@echo " make package — Build and package for distribution"
|
||||
@echo " make release — Build multi-platform release artefacts"
|
||||
@echo " make help — Show this help"
|
||||
|
||||
+31
-8
@@ -16,6 +16,23 @@ log() { echo -e "${GREEN}[nexus]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[nexus]${NC} $1"; }
|
||||
err() { echo -e "${RED}[nexus]${NC} $1" >&2; }
|
||||
|
||||
build_version() {
|
||||
local tag
|
||||
local commit
|
||||
tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")"
|
||||
commit="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")"
|
||||
echo "${tag}-${commit}"
|
||||
}
|
||||
|
||||
build_ldflags() {
|
||||
local version="$1"
|
||||
local flags="-s -w -X github.com/andydixon/nexus/internal/api.appVersion=${version}"
|
||||
if [ -n "${NEXUS_UPDATE_URL:-}" ]; then
|
||||
flags="${flags} -X github.com/andydixon/nexus/internal/api.appUpdateURL=${NEXUS_UPDATE_URL}"
|
||||
fi
|
||||
echo "${flags}"
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
command -v go >/dev/null 2>&1 || { err "Go is not installed"; exit 1; }
|
||||
command -v node >/dev/null 2>&1 || { err "Node.js is not installed"; exit 1; }
|
||||
@@ -80,6 +97,9 @@ build_target() {
|
||||
local target_os="$1"
|
||||
local target_arch="$2"
|
||||
local host="$3"
|
||||
local version="$4"
|
||||
local ldflags
|
||||
ldflags="$(build_ldflags "${version}")"
|
||||
|
||||
local platform="${target_os}/${target_arch}"
|
||||
local cc=""
|
||||
@@ -94,9 +114,9 @@ build_target() {
|
||||
|
||||
log "Building ${platform}…"
|
||||
if [ -n "$cc" ]; then
|
||||
CC="$cc" CGO_ENABLED=1 wails build -platform "$platform" -ldflags "-s -w"
|
||||
CC="$cc" CGO_ENABLED=1 wails build -platform "$platform" -ldflags "$ldflags"
|
||||
else
|
||||
CGO_ENABLED=1 wails build -platform "$platform" -ldflags "-s -w"
|
||||
CGO_ENABLED=1 wails build -platform "$platform" -ldflags "$ldflags"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -104,6 +124,9 @@ main() {
|
||||
check_deps
|
||||
install_deps
|
||||
|
||||
local version
|
||||
version="$(build_version)"
|
||||
|
||||
local host
|
||||
host="$(host_os)"
|
||||
if [ "$host" = "unknown" ]; then
|
||||
@@ -112,15 +135,15 @@ main() {
|
||||
fi
|
||||
|
||||
# Build host targets first
|
||||
build_target "$host" "amd64" "$host"
|
||||
build_target "$host" "arm64" "$host"
|
||||
build_target "$host" "amd64" "$host" "$version"
|
||||
build_target "$host" "arm64" "$host" "$version"
|
||||
|
||||
# Cross-build Linux and Windows targets where toolchains are available
|
||||
if [ "$host" = "darwin" ] || [ "$host" = "linux" ]; then
|
||||
build_target "linux" "amd64" "$host"
|
||||
build_target "linux" "arm64" "$host"
|
||||
build_target "windows" "amd64" "$host"
|
||||
build_target "windows" "arm64" "$host"
|
||||
build_target "linux" "amd64" "$host" "$version"
|
||||
build_target "linux" "arm64" "$host" "$version"
|
||||
build_target "windows" "amd64" "$host" "$version"
|
||||
build_target "windows" "arm64" "$host" "$version"
|
||||
fi
|
||||
|
||||
log "Builds complete. Output: build/bin/"
|
||||
|
||||
@@ -48,6 +48,32 @@ function Install-Deps {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
function Get-BuildVersion {
|
||||
try {
|
||||
$tag = & git describe --tags --abbrev=0 2>$null
|
||||
if (-not $tag) { $tag = 'dev' }
|
||||
} catch {
|
||||
$tag = 'dev'
|
||||
}
|
||||
|
||||
try {
|
||||
$commit = & git rev-parse --short HEAD 2>$null
|
||||
if (-not $commit) { $commit = 'unknown' }
|
||||
} catch {
|
||||
$commit = 'unknown'
|
||||
}
|
||||
|
||||
return "$tag-$commit"
|
||||
}
|
||||
|
||||
function Get-Ldflags([string]$Version) {
|
||||
$flags = "-s -w -X github.com/andydixon/nexus/internal/api.appVersion=$Version"
|
||||
if ($env:NEXUS_UPDATE_URL) {
|
||||
$flags += " -X github.com/andydixon/nexus/internal/api.appUpdateURL=$($env:NEXUS_UPDATE_URL)"
|
||||
}
|
||||
return $flags
|
||||
}
|
||||
|
||||
function Dev {
|
||||
$wailsPath = Check-Deps
|
||||
Install-Deps
|
||||
@@ -58,10 +84,12 @@ function Dev {
|
||||
function Build {
|
||||
$wailsPath = Check-Deps
|
||||
Install-Deps
|
||||
$version = Get-BuildVersion
|
||||
$ldflags = Get-Ldflags $version
|
||||
|
||||
$env:CGO_ENABLED = '1'
|
||||
Write-Info 'Building production binary (CGO_ENABLED=1)...'
|
||||
& $wailsPath build -ldflags "-s -w"
|
||||
& $wailsPath build -ldflags $ldflags
|
||||
Write-Info 'Build complete: build\bin\'
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,23 @@ log() { echo -e "${GREEN}[nexus]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[nexus]${NC} $1"; }
|
||||
err() { echo -e "${RED}[nexus]${NC} $1" >&2; }
|
||||
|
||||
build_version() {
|
||||
local tag
|
||||
local commit
|
||||
tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")"
|
||||
commit="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")"
|
||||
echo "${tag}-${commit}"
|
||||
}
|
||||
|
||||
build_ldflags() {
|
||||
local version="$1"
|
||||
local flags="-s -w -X github.com/andydixon/nexus/internal/api.appVersion=${version}"
|
||||
if [ -n "${NEXUS_UPDATE_URL:-}" ]; then
|
||||
flags="${flags} -X github.com/andydixon/nexus/internal/api.appUpdateURL=${NEXUS_UPDATE_URL}"
|
||||
fi
|
||||
echo "${flags}"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_deps() {
|
||||
command -v go >/dev/null 2>&1 || { err "Go is not installed"; exit 1; }
|
||||
@@ -62,7 +79,9 @@ build() {
|
||||
check_deps
|
||||
install_deps
|
||||
log "Building production binary…"
|
||||
wails build -ldflags "-s -w"
|
||||
local version
|
||||
version="$(build_version)"
|
||||
wails build -ldflags "$(build_ldflags "${version}")"
|
||||
log "Build complete: build/bin/"
|
||||
}
|
||||
|
||||
|
||||
@@ -269,6 +269,9 @@
|
||||
<span id="status-protocol"></span>
|
||||
<span id="status-session-count">0 sessions</span>
|
||||
<span class="status-spacer"></span>
|
||||
<button class="status-update is-hidden" id="status-update" type="button">
|
||||
Update available
|
||||
</button>
|
||||
<span id="status-vault">🔒 Locked</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +125,27 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-update {
|
||||
border: 1px solid var(--accent);
|
||||
background: rgba(78, 205, 196, 0.12);
|
||||
color: var(--accent);
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
|
||||
}
|
||||
|
||||
.status-update:hover {
|
||||
background: rgba(78, 205, 196, 0.2);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-strong, var(--accent));
|
||||
}
|
||||
|
||||
.status-update.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Status bar indicator dots */
|
||||
.status-bar .status-dot {
|
||||
width: 6px;
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
this.exportImport = new ExportImportUI(this);
|
||||
this.sftp = new SFTPBrowser(this);
|
||||
this.snippets = new SnippetLibrary(this);
|
||||
this.updateInfo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +62,7 @@
|
||||
await this.loadPreferences();
|
||||
await this.checkSecretStore();
|
||||
this.updateStatusBar();
|
||||
this.checkForUpdates(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +256,11 @@
|
||||
);
|
||||
});
|
||||
|
||||
// App-wide commands from the native menu
|
||||
window.runtime.EventsOn("app:open-about", () => {
|
||||
this.settings.openTab("about");
|
||||
});
|
||||
|
||||
// Port-forwarding events
|
||||
window.runtime.EventsOn("session:forward:started", (payload) => {
|
||||
const fwd = payload.forward || {};
|
||||
@@ -503,6 +510,12 @@
|
||||
this._bindClick("btn-empty-connect", () => this.profileEditor.openNew());
|
||||
this._bindClick("btn-empty-quick", () => this.quickConnect());
|
||||
|
||||
const updateBtn = document.getElementById("status-update");
|
||||
if (updateBtn) {
|
||||
updateBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
updateBtn.addEventListener("click", () => this.settings.openTab("about"));
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById("profile-search");
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener("input", (e) =>
|
||||
@@ -851,6 +864,7 @@
|
||||
const connEl = document.getElementById("status-connection");
|
||||
const countEl = document.getElementById("status-session-count");
|
||||
const vaultEl = document.getElementById("status-vault");
|
||||
const updateEl = document.getElementById("status-update");
|
||||
if (!connEl) return;
|
||||
|
||||
const sessions = this.state.getAllSessions();
|
||||
@@ -874,6 +888,44 @@
|
||||
? "🔓 Unlocked"
|
||||
: "🔒 Locked";
|
||||
}
|
||||
|
||||
if (updateEl) {
|
||||
if (this.updateInfo && this.updateInfo.available) {
|
||||
const version = this.updateInfo.latestVersion
|
||||
? ` (${this.updateInfo.latestVersion})`
|
||||
: "";
|
||||
updateEl.textContent = `Update available${version}`;
|
||||
updateEl.classList.remove("is-hidden");
|
||||
} else {
|
||||
updateEl.textContent = "Update available";
|
||||
updateEl.classList.add("is-hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkForUpdates(silent) {
|
||||
if (!window.go || !window.go.api || !window.go.api.Service) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await window.go.api.Service.CheckForUpdates();
|
||||
this.setUpdateInfo(info);
|
||||
if (!silent && info && info.available) {
|
||||
this.notifications.info(
|
||||
`Update available: ${info.latestVersion || "new version"}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!silent) {
|
||||
this.notifications.error("Update check failed: " + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUpdateInfo(info) {
|
||||
this.updateInfo = info || null;
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
// ── Internal helpers ─────────────────────────────────────────────
|
||||
|
||||
@@ -848,7 +848,7 @@
|
||||
body.innerHTML = `
|
||||
<div class="about-section">
|
||||
<h3 class="about-title">Nexus Terminal</h3>
|
||||
<p class="about-version">Version 1.0.0</p>
|
||||
<p class="about-version" data-role="app-version">Version …</p>
|
||||
<p class="about-desc">Developed by Andy Dixon - www.dixon.cx/nexus</p>
|
||||
<div class="about-links">
|
||||
<p>AES-256-GCM encrypted credential vault</p>
|
||||
@@ -864,6 +864,22 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
const versionEl = body.querySelector("[data-role='app-version']");
|
||||
if (versionEl) {
|
||||
if (window.go && window.go.api && window.go.api.Service) {
|
||||
window.go.api.Service.GetAppInfo()
|
||||
.then((info) => {
|
||||
const version = (info && info.version) || "dev";
|
||||
versionEl.textContent = `Version ${version}`;
|
||||
})
|
||||
.catch(() => {
|
||||
versionEl.textContent = "Version unavailable";
|
||||
});
|
||||
} else {
|
||||
versionEl.textContent = "Version unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
const updateBtn = body.querySelector("#settings-check-update");
|
||||
if (updateBtn) {
|
||||
updateBtn.addEventListener("click", async () => {
|
||||
@@ -872,6 +888,9 @@
|
||||
try {
|
||||
const info = await window.go.api.Service.CheckForUpdates();
|
||||
const resultEl = body.querySelector("#settings-update-result");
|
||||
if (this.app && typeof this.app.setUpdateInfo === "function") {
|
||||
this.app.setUpdateInfo(info);
|
||||
}
|
||||
if (info.available) {
|
||||
resultEl.innerHTML =
|
||||
`<span style="color:var(--accent)">Update available: <strong>${info.latestVersion}</strong></span>` +
|
||||
|
||||
+13
-1
@@ -36,6 +36,9 @@ import (
|
||||
// appVersion is set at build time via ldflags.
|
||||
var appVersion = "dev"
|
||||
|
||||
// appUpdateURL is set at build time via ldflags.
|
||||
var appUpdateURL = ""
|
||||
|
||||
// Service is the main API surface exposed to the frontend via Wails bindings.
|
||||
type Service struct {
|
||||
store *storage.Store
|
||||
@@ -1298,8 +1301,17 @@ func (s *Service) ImportSSHConfig(configPath string, folder string) (int, error)
|
||||
|
||||
// CheckForUpdates queries GitHub Releases for a newer version.
|
||||
func (s *Service) CheckForUpdates() (map[string]interface{}, error) {
|
||||
ctx := context.Background()
|
||||
var info *updater.UpdateInfo
|
||||
var err error
|
||||
|
||||
if appUpdateURL != "" {
|
||||
checker := updater.NewURLChecker(appUpdateURL, appVersion)
|
||||
info, err = checker.Check(ctx)
|
||||
} else {
|
||||
checker := updater.NewChecker(appVersion)
|
||||
info, err := checker.Check(context.Background())
|
||||
info, err = checker.Check(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -146,3 +146,27 @@ func (a *App) DomReady(ctx context.Context) {
|
||||
})
|
||||
log.Println("DOM ready – backend:ready event emitted")
|
||||
}
|
||||
|
||||
// EmitOpenAbout tells the frontend to open the About tab.
|
||||
func (a *App) EmitOpenAbout() {
|
||||
a.mu.Lock()
|
||||
ctx := a.ctx
|
||||
a.mu.Unlock()
|
||||
if ctx == nil {
|
||||
log.Println("unable to open About: application context not initialised")
|
||||
return
|
||||
}
|
||||
runtime.EventsEmit(ctx, "app:open-about", nil)
|
||||
}
|
||||
|
||||
// RequestQuit asks the runtime to quit the application.
|
||||
func (a *App) RequestQuit() {
|
||||
a.mu.Lock()
|
||||
ctx := a.ctx
|
||||
a.mu.Unlock()
|
||||
if ctx == nil {
|
||||
log.Println("unable to quit: application context not initialised")
|
||||
return
|
||||
}
|
||||
runtime.Quit(ctx)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -56,6 +57,23 @@ type Checker struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// Manifest describes a self-hosted update manifest.
|
||||
type Manifest struct {
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
Version string `json:"version"`
|
||||
ReleaseURL string `json:"releaseURL"`
|
||||
ReleaseName string `json:"releaseName"`
|
||||
ReleaseNotes string `json:"releaseNotes"`
|
||||
PublishedAt string `json:"publishedAt"`
|
||||
}
|
||||
|
||||
// URLChecker performs version checks against a self-hosted update manifest.
|
||||
type URLChecker struct {
|
||||
url string
|
||||
current string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewChecker creates an update checker for the given current version.
|
||||
// The version string should be a semver tag (e.g. "v1.2.3" or "1.2.3").
|
||||
func NewChecker(currentVersion string) *Checker {
|
||||
@@ -69,6 +87,17 @@ func NewChecker(currentVersion string) *Checker {
|
||||
}
|
||||
}
|
||||
|
||||
// NewURLChecker creates a checker for a self-hosted update manifest URL.
|
||||
func NewURLChecker(url string, currentVersion string) *URLChecker {
|
||||
return &URLChecker{
|
||||
url: url,
|
||||
current: normaliseVersion(currentVersion),
|
||||
client: &http.Client{
|
||||
Timeout: requestTimeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetRepository overrides the GitHub repository to check.
|
||||
func (c *Checker) SetRepository(owner, repo string) {
|
||||
c.owner = owner
|
||||
@@ -134,6 +163,51 @@ func (c *Checker) Check(ctx context.Context) (*UpdateInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check queries the update manifest URL and compares it to the current version.
|
||||
func (c *URLChecker) Check(ctx context.Context) (*UpdateInfo, error) {
|
||||
if c.url == "" {
|
||||
return nil, fmt.Errorf("updater: update manifest URL is empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updater: failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Accept", "application/json, text/plain")
|
||||
req.Header.Set("User-Agent", "NexusTerminal-UpdateChecker")
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updater: request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("updater: update server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updater: failed to read response: %w", err)
|
||||
}
|
||||
|
||||
info, ok := parseManifest(body, c.current)
|
||||
if ok {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
latest := normaliseVersion(strings.TrimSpace(string(body)))
|
||||
if latest == "" {
|
||||
return nil, fmt.Errorf("updater: update manifest is empty")
|
||||
}
|
||||
|
||||
return &UpdateInfo{
|
||||
Available: isNewer(latest, c.current),
|
||||
CurrentVersion: c.current,
|
||||
LatestVersion: latest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// normaliseVersion strips a leading "v" and trims whitespace.
|
||||
func normaliseVersion(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
@@ -141,6 +215,32 @@ func normaliseVersion(v string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
func parseManifest(body []byte, current string) (*UpdateInfo, bool) {
|
||||
var manifest Manifest
|
||||
if err := json.Unmarshal(body, &manifest); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
latest := manifest.LatestVersion
|
||||
if latest == "" {
|
||||
latest = manifest.Version
|
||||
}
|
||||
latest = normaliseVersion(latest)
|
||||
if latest == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &UpdateInfo{
|
||||
Available: isNewer(latest, current),
|
||||
CurrentVersion: current,
|
||||
LatestVersion: latest,
|
||||
ReleaseURL: manifest.ReleaseURL,
|
||||
ReleaseName: manifest.ReleaseName,
|
||||
ReleaseNotes: manifest.ReleaseNotes,
|
||||
PublishedAt: manifest.PublishedAt,
|
||||
}, true
|
||||
}
|
||||
|
||||
// isNewer performs a simple string comparison of dot-separated version parts.
|
||||
// Returns true if latest > current (basic semver, no pre-release handling).
|
||||
func isNewer(latest, current string) bool {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -64,3 +67,40 @@ func TestTruncateNotes(t *testing.T) {
|
||||
t.Errorf("expected 501 runes, got %d", len([]rune(got)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLCheckerManifestJSON(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"latestVersion":"1.2.3","releaseURL":"https://example.com/release"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
checker := NewURLChecker(server.URL, "1.2.0")
|
||||
info, err := checker.Check(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !info.Available {
|
||||
t.Fatalf("expected update to be available")
|
||||
}
|
||||
if info.LatestVersion != "1.2.3" {
|
||||
t.Fatalf("unexpected latest version: %s", info.LatestVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLCheckerPlainText(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("2.0.0"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
checker := NewURLChecker(server.URL, "1.9.0")
|
||||
info, err := checker.Check(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !info.Available {
|
||||
t.Fatalf("expected update to be available")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||
@@ -16,6 +18,31 @@ import (
|
||||
"github.com/andydixon/nexus/frontend"
|
||||
)
|
||||
|
||||
func buildAppMenu(app *application.App) *menu.Menu {
|
||||
mainMenu := menu.NewMenu()
|
||||
|
||||
appMenu := menu.NewMenu()
|
||||
appMenu.AddText("About Nexus Terminal", nil, func(_ *menu.CallbackData) {
|
||||
app.EmitOpenAbout()
|
||||
})
|
||||
appMenu.AddSeparator()
|
||||
appMenu.AddText("Quit Nexus Terminal", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
|
||||
app.RequestQuit()
|
||||
})
|
||||
|
||||
helpMenu := menu.NewMenu()
|
||||
helpMenu.AddText("About Nexus Terminal", nil, func(_ *menu.CallbackData) {
|
||||
app.EmitOpenAbout()
|
||||
})
|
||||
|
||||
mainMenu.Append(menu.SubMenu("Nexus Terminal", appMenu))
|
||||
mainMenu.Append(menu.EditMenu())
|
||||
mainMenu.Append(menu.WindowMenu())
|
||||
mainMenu.Append(menu.SubMenu("Help", helpMenu))
|
||||
|
||||
return mainMenu
|
||||
}
|
||||
|
||||
func main() {
|
||||
nexus := application.NewApp()
|
||||
|
||||
@@ -35,13 +62,9 @@ func main() {
|
||||
OnStartup: nexus.Startup,
|
||||
OnShutdown: nexus.Shutdown,
|
||||
OnDomReady: nexus.DomReady,
|
||||
Menu: buildAppMenu(nexus),
|
||||
// macOS: enable the green fullscreen traffic-light button.
|
||||
Mac: &mac.Options{
|
||||
About: &mac.AboutInfo{
|
||||
Title: "Nexus Terminal",
|
||||
Message: "A premium desktop terminal client",
|
||||
},
|
||||
},
|
||||
Mac: &mac.Options{},
|
||||
// Windows: default decorations include maximise; fullscreen via F11.
|
||||
Windows: &windows.Options{},
|
||||
// Linux: use the default window manager decorations (fullscreen via WM).
|
||||
|
||||
Reference in New Issue
Block a user