mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-07 10:47:15 +00:00
fix(panel): recognize this fork's -awg.N tags in version comparison
internal/config/version was never bumped past 3.5.0 when v3.5.0-awg.1 was tagged, so a freshly-updated panel kept reporting its own version as the plain upstream base. On top of that, parseVersionParts (Go and its TypeScript mirror) required exactly 3 dot-separated numeric parts, so it rejected the -awg.N suffix entirely and fell back to a raw string inequality that reports "update available" any time the strings merely differ -- which they always do here, even when already on the latest tag. Bump the embedded version to 3.5.0-awg.1 and extend both parsers to treat "-awg.N" as an optional 4th, lower-priority component (defaulting to 0 for a plain tag), so e.g. 3.5.0-awg.2 > 3.5.0-awg.1 > 3.5.0 and a matching tag compares equal instead of always looking outdated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,15 @@
|
|||||||
// Mirror of web/service/panel.go isNewerVersion: parse a vMAJOR.MINOR.PATCH tag
|
// Mirror of web/service/panel.go isNewerVersion: parse a vMAJOR.MINOR.PATCH tag
|
||||||
// and report whether `latest` is ahead of `current`. When either side isn't a
|
// (or this fork's own "vMAJOR.MINOR.PATCH-awg.N" release tag, see
|
||||||
// clean three-part numeric tag, fall back to a normalized string inequality —
|
// internal/config/version) and report whether `latest` is ahead of `current`.
|
||||||
// the same heuristic the Go side uses so the node "update available" badge
|
// When either side isn't a recognized tag, fall back to a normalized string
|
||||||
// agrees with what the server would decide.
|
// inequality — the same heuristic the Go side uses so the node "update
|
||||||
function parseVersionParts(version: string): [number, number, number] | null {
|
// available" badge agrees with what the server would decide.
|
||||||
const parts = version.trim().replace(/^v/, '').split('.');
|
const VERSION_PARTS_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-awg\.(\d+))?$/;
|
||||||
if (parts.length !== 3) return null;
|
|
||||||
const out: number[] = [];
|
function parseVersionParts(version: string): [number, number, number, number] | null {
|
||||||
for (const part of parts) {
|
const match = VERSION_PARTS_PATTERN.exec(version.trim());
|
||||||
if (!/^\d+$/.test(part)) return null;
|
if (!match) return null;
|
||||||
out.push(Number(part));
|
return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4] || 0)];
|
||||||
}
|
|
||||||
return [out[0], out[1], out[2]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format a panel version for display. Dev builds report a "dev+<commit>"
|
// Format a panel version for display. Dev builds report a "dev+<commit>"
|
||||||
@@ -33,7 +31,7 @@ export function isPanelUpdateAvailable(latest: string, current: string): boolean
|
|||||||
if (!a || !b) {
|
if (!a || !b) {
|
||||||
return latest.trim().replace(/^v/, '') !== current.trim().replace(/^v/, '');
|
return latest.trim().replace(/^v/, '') !== current.trim().replace(/^v/, '');
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
if (a[i] > b[i]) return true;
|
if (a[i] > b[i]) return true;
|
||||||
if (a[i] < b[i]) return false;
|
if (a[i] < b[i]) return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ describe('isPanelUpdateAvailable', () => {
|
|||||||
expect(isPanelUpdateAvailable('nightly-2', 'nightly-1')).toBe(true);
|
expect(isPanelUpdateAvailable('nightly-2', 'nightly-1')).toBe(true);
|
||||||
expect(isPanelUpdateAvailable('nightly-1', 'nightly-1')).toBe(false);
|
expect(isPanelUpdateAvailable('nightly-1', 'nightly-1')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Parity with web/service/panel.go TestIsNewerVersionAwgSuffix -- this
|
||||||
|
// fork's own "-awg.N" release tags (see internal/config/version).
|
||||||
|
it('handles this fork\'s -awg.N release tags', () => {
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0-awg.1')).toBe(false);
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0-awg.2', '3.5.0-awg.1')).toBe(true);
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0-awg.2')).toBe(false);
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.5.0')).toBe(true);
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0', '3.5.0-awg.1')).toBe(false);
|
||||||
|
expect(isPanelUpdateAvailable('v3.6.0', '3.5.0-awg.1')).toBe(true);
|
||||||
|
expect(isPanelUpdateAvailable('v3.5.0-awg.1', '3.6.0')).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('formatPanelVersion', () => {
|
describe('formatPanelVersion', () => {
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// version must be bumped to match the exact tag BEFORE it's pushed -- unlike
|
||||||
|
// buildCommit/buildDate below, nothing stamps this automatically for a
|
||||||
|
// tagged release build, so a forgotten bump here makes the panel misreport
|
||||||
|
// its own version and permanently show a bogus "update available" (the
|
||||||
|
// already-installed tag never matches this stale string). See
|
||||||
|
// isNewerVersion/parseVersionParts in internal/web/service/panel/panel.go.
|
||||||
|
//
|
||||||
//go:embed version
|
//go:embed version
|
||||||
var version string
|
var version string
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.5.0
|
3.5.0-awg.1
|
||||||
@@ -529,13 +529,22 @@ func compareVersionStrings(a string, b string) (int, bool) {
|
|||||||
return 0, true
|
return 0, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVersionParts(version string) ([3]int, bool) {
|
// versionPartsPattern matches a plain "X.Y.Z" tag as well as this fork's own
|
||||||
var result [3]int
|
// "X.Y.Z-awg.N" release tags (see internal/config/version). The awg build
|
||||||
parts := strings.Split(normalizeVersionTag(version), ".")
|
// number is treated as a 4th, lower-priority component that defaults to 0 for
|
||||||
if len(parts) != 3 {
|
// a plain upstream-style tag, so e.g. "3.5.0-awg.2" > "3.5.0-awg.1" > "3.5.0".
|
||||||
|
var versionPartsPattern = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)(?:-awg\.(\d+))?$`)
|
||||||
|
|
||||||
|
func parseVersionParts(version string) ([4]int, bool) {
|
||||||
|
var result [4]int
|
||||||
|
matches := versionPartsPattern.FindStringSubmatch(strings.TrimSpace(version))
|
||||||
|
if matches == nil {
|
||||||
return result, false
|
return result, false
|
||||||
}
|
}
|
||||||
for i, part := range parts {
|
for i, part := range matches[1:] {
|
||||||
|
if part == "" {
|
||||||
|
continue // awg build number omitted -- defaults to 0
|
||||||
|
}
|
||||||
n, err := strconv.Atoi(part)
|
n, err := strconv.Atoi(part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, false
|
return result, false
|
||||||
|
|||||||
@@ -42,6 +42,31 @@ func TestCompareVersionStringsRejectsUnexpectedFormats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIsNewerVersionAwgSuffix covers this fork's own "-awg.N" release tags
|
||||||
|
// (see internal/config/version), which must compare correctly against both
|
||||||
|
// plain upstream-style tags and other awg builds of the same base version.
|
||||||
|
func TestIsNewerVersionAwgSuffix(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
latest string
|
||||||
|
current string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"v3.5.0-awg.1", "3.5.0-awg.1", false},
|
||||||
|
{"v3.5.0-awg.2", "3.5.0-awg.1", true},
|
||||||
|
{"v3.5.0-awg.1", "3.5.0-awg.2", false},
|
||||||
|
{"v3.5.0-awg.1", "3.5.0", true},
|
||||||
|
{"v3.5.0", "3.5.0-awg.1", false},
|
||||||
|
{"v3.6.0", "3.5.0-awg.1", true},
|
||||||
|
{"v3.5.0-awg.1", "3.6.0", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got := isNewerVersion(tc.latest, tc.current); got != tc.want {
|
||||||
|
t.Fatalf("isNewerVersion(%q, %q) = %v, want %v", tc.latest, tc.current, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShellQuote(t *testing.T) {
|
func TestShellQuote(t *testing.T) {
|
||||||
if got := shellQuote("/usr/bin/curl"); got != "'/usr/bin/curl'" {
|
if got := shellQuote("/usr/bin/curl"); got != "'/usr/bin/curl'" {
|
||||||
t.Fatalf("unexpected quote result: %s", got)
|
t.Fatalf("unexpected quote result: %s", got)
|
||||||
|
|||||||
Reference in New Issue
Block a user