mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-07 20:24:39 +00:00
release build and other CI
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare const __ENABLE_GH_RELEASE_UPDATE_CHECK__: boolean;
|
||||
declare const __GH_RELEASE_REPO__: string;
|
||||
declare const __UPDATE_CHANNEL__: "stable" | "nightly";
|
||||
declare const __BUILD_LABEL__: string;
|
||||
@@ -19,6 +19,12 @@
|
||||
import CloudPanel from "../components/CloudPanel.svelte";
|
||||
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
||||
import { settingsPopup } from "../hooks/SettingsPopup";
|
||||
import {
|
||||
checkGithubReleaseUpdate,
|
||||
dismissNightlyUpdate,
|
||||
isGhReleaseUpdateCheckEnabled,
|
||||
type GhReleaseUpdateInfo,
|
||||
} from "@/utils/githubReleaseUpdate";
|
||||
|
||||
let devModeSequence = "";
|
||||
let settingsActiveTab = $state(0);
|
||||
@@ -26,6 +32,18 @@
|
||||
let disclaimerCallbacks = $state<{ onConfirm: () => void, onCancel: () => void } | null>(null);
|
||||
let disclaimerTitle = $state("Confirm");
|
||||
let disclaimerMessage = $state("");
|
||||
const ghReleaseUpdateEnabled = isGhReleaseUpdateCheckEnabled();
|
||||
let ghReleaseUpdate = $state<GhReleaseUpdateInfo | null>(null);
|
||||
|
||||
const openGhRelease = () => {
|
||||
const url = ghReleaseUpdate?.url
|
||||
?? "https://github.com/BetterSEQTA/BetterSEQTA-Plus/releases";
|
||||
if (ghReleaseUpdate?.available) {
|
||||
dismissNightlyUpdate();
|
||||
}
|
||||
window.open(url, "_blank");
|
||||
closeExtensionPopup();
|
||||
};
|
||||
|
||||
const handleDevModeToggle = () => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -98,6 +116,12 @@
|
||||
if (standalone) {
|
||||
StandaloneStore.setStandalone(true);
|
||||
}
|
||||
|
||||
if (ghReleaseUpdateEnabled) {
|
||||
void checkGithubReleaseUpdate().then((info) => {
|
||||
ghReleaseUpdate = info;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -134,7 +158,25 @@
|
||||
/>
|
||||
|
||||
{#if !standalone}
|
||||
<div class="flex absolute top-1 right-1 gap-1 items-center">
|
||||
<div class="flex absolute top-1 right-1 gap-1 items-start">
|
||||
{#if ghReleaseUpdateEnabled}
|
||||
<div class="flex flex-col items-end gap-0.5 max-w-[9rem] mr-0.5">
|
||||
{#if ghReleaseUpdate?.available}
|
||||
<button
|
||||
type="button"
|
||||
onclick={openGhRelease}
|
||||
class="px-1.5 py-0.5 text-[10px] font-semibold leading-tight text-white rounded-full bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-500"
|
||||
title="Open GitHub release"
|
||||
>
|
||||
Update available — {ghReleaseUpdate.label}
|
||||
</button>
|
||||
{/if}
|
||||
<p class="text-[9px] leading-tight text-right text-zinc-500 dark:text-zinc-400">
|
||||
GitHub release build — do not upload to extension stores.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex gap-1 items-center">
|
||||
<button
|
||||
onclick={openAbout}
|
||||
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||
@@ -156,6 +198,7 @@
|
||||
>
|
||||
{"\uecba"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
onclick={openMinecraftServer}
|
||||
|
||||
@@ -585,6 +585,21 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-4 py-3">
|
||||
<div>
|
||||
<h2 class="text-sm font-bold">GitHub latest version override</h2>
|
||||
<p class="text-xs">Pretend a newer GitHub release exists to test the update badge. Only applies when dev mode is on.</p>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. 9.9.9"
|
||||
value={$settingsState.devGhReleaseVersionOverride ?? ""}
|
||||
oninput={(e) => {
|
||||
settingsState.devGhReleaseVersionOverride = e.currentTarget.value;
|
||||
}}
|
||||
class="px-2 py-1 text-xs rounded border bg-white dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -49,6 +49,10 @@ export interface SettingsState {
|
||||
animations: boolean;
|
||||
defaultPage: string;
|
||||
devMode?: boolean;
|
||||
/** Dev-only: pretend this is the latest GitHub release version for update badge testing. */
|
||||
devGhReleaseVersionOverride?: string;
|
||||
/** ISO timestamp of the last acknowledged nightly release publish time. */
|
||||
lastSeenNightlyPublishedAt?: string;
|
||||
originalDarkMode?: boolean;
|
||||
newsSource?: string;
|
||||
mockNotices?: boolean;
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import semver from "semver";
|
||||
import browser from "webextension-polyfill";
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
|
||||
const CHECK_THROTTLE_MS = 6 * 60 * 60 * 1000;
|
||||
const LAST_CHECK_KEY = "bsplus_lastGhReleaseCheck";
|
||||
const NIGHTLY_TAG = "nightly";
|
||||
|
||||
let cachedResult: GhReleaseUpdateInfo | null = null;
|
||||
|
||||
export interface GhReleaseUpdateInfo {
|
||||
available: boolean;
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
function isUpdateCheckEnabled(): boolean {
|
||||
return typeof __ENABLE_GH_RELEASE_UPDATE_CHECK__ !== "undefined"
|
||||
&& __ENABLE_GH_RELEASE_UPDATE_CHECK__;
|
||||
}
|
||||
|
||||
function getRepoSlug(): string {
|
||||
return typeof __GH_RELEASE_REPO__ !== "undefined"
|
||||
? __GH_RELEASE_REPO__
|
||||
: "BetterSEQTA/BetterSEQTA-Plus";
|
||||
}
|
||||
|
||||
function getUpdateChannel(): "stable" | "nightly" {
|
||||
return typeof __UPDATE_CHANNEL__ !== "undefined"
|
||||
? __UPDATE_CHANNEL__
|
||||
: "stable";
|
||||
}
|
||||
|
||||
function getBuildLabel(): string {
|
||||
return typeof __BUILD_LABEL__ !== "undefined" ? __BUILD_LABEL__ : "";
|
||||
}
|
||||
|
||||
function getCurrentVersion(): string {
|
||||
return browser.runtime.getManifest().version;
|
||||
}
|
||||
|
||||
function releasesBaseUrl(): string {
|
||||
return `https://github.com/${getRepoSlug()}/releases`;
|
||||
}
|
||||
|
||||
function shouldThrottleCheck(): boolean {
|
||||
try {
|
||||
const last = localStorage.getItem(LAST_CHECK_KEY);
|
||||
if (!last) return false;
|
||||
return Date.now() - Number(last) < CHECK_THROTTLE_MS;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function markChecked(): void {
|
||||
try {
|
||||
localStorage.setItem(LAST_CHECK_KEY, String(Date.now()));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function getDevOverrideVersion(): string | null {
|
||||
if (!settingsState.devMode) return null;
|
||||
const override = settingsState.devGhReleaseVersionOverride?.trim();
|
||||
return override || null;
|
||||
}
|
||||
|
||||
function compareWithOverride(current: string): GhReleaseUpdateInfo | null {
|
||||
const override = getDevOverrideVersion();
|
||||
if (!override) return null;
|
||||
|
||||
const currentCoerced = semver.coerce(current);
|
||||
const overrideCoerced = semver.coerce(override);
|
||||
if (!currentCoerced || !overrideCoerced) return null;
|
||||
|
||||
if (semver.gt(overrideCoerced, currentCoerced)) {
|
||||
return {
|
||||
available: true,
|
||||
label: override,
|
||||
url: releasesBaseUrl(),
|
||||
};
|
||||
}
|
||||
|
||||
return { available: false, label: "", url: releasesBaseUrl() };
|
||||
}
|
||||
|
||||
interface GhRelease {
|
||||
tag_name: string;
|
||||
published_at: string;
|
||||
prerelease: boolean;
|
||||
}
|
||||
|
||||
async function fetchJson<T>(url: string): Promise<T | null> {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: { Accept: "application/vnd.github+json" },
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
return (await response.json()) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isStableSemverTag(tag: string): boolean {
|
||||
if (tag === NIGHTLY_TAG) return false;
|
||||
return semver.valid(semver.coerce(tag)) !== null;
|
||||
}
|
||||
|
||||
async function checkStableUpdate(current: string): Promise<GhReleaseUpdateInfo> {
|
||||
const url = releasesBaseUrl();
|
||||
const releases = await fetchJson<GhRelease[]>(
|
||||
`https://api.github.com/repos/${getRepoSlug()}/releases`,
|
||||
);
|
||||
|
||||
if (!releases?.length) {
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
let latestTag: string | null = null;
|
||||
let latestVersion: semver.SemVer | null = null;
|
||||
|
||||
for (const release of releases) {
|
||||
const tag = release.tag_name;
|
||||
if (!isStableSemverTag(tag)) continue;
|
||||
|
||||
const coerced = semver.coerce(tag);
|
||||
if (!coerced) continue;
|
||||
|
||||
if (!latestVersion || semver.gt(coerced, latestVersion)) {
|
||||
latestVersion = coerced;
|
||||
latestTag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
const currentCoerced = semver.coerce(current);
|
||||
if (!latestTag || !latestVersion || !currentCoerced) {
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
if (semver.gt(latestVersion, currentCoerced)) {
|
||||
return { available: true, label: latestTag, url: `${url}/tag/${latestTag}` };
|
||||
}
|
||||
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
async function checkNightlyUpdate(): Promise<GhReleaseUpdateInfo> {
|
||||
const url = `${releasesBaseUrl()}/tag/${NIGHTLY_TAG}`;
|
||||
const release = await fetchJson<GhRelease>(
|
||||
`https://api.github.com/repos/${getRepoSlug()}/releases/tags/${NIGHTLY_TAG}`,
|
||||
);
|
||||
|
||||
if (!release?.published_at) {
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
const lastSeen = settingsState.lastSeenNightlyPublishedAt;
|
||||
const buildLabel = getBuildLabel();
|
||||
const label = buildLabel ? `nightly #${buildLabel}` : "nightly";
|
||||
|
||||
if (!lastSeen) {
|
||||
settingsState.lastSeenNightlyPublishedAt = release.published_at;
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
if (new Date(release.published_at) > new Date(lastSeen)) {
|
||||
return { available: true, label, url };
|
||||
}
|
||||
|
||||
return { available: false, label: "", url };
|
||||
}
|
||||
|
||||
export function isGhReleaseUpdateCheckEnabled(): boolean {
|
||||
return isUpdateCheckEnabled();
|
||||
}
|
||||
|
||||
export async function checkGithubReleaseUpdate(): Promise<GhReleaseUpdateInfo> {
|
||||
const fallback = { available: false, label: "", url: releasesBaseUrl() };
|
||||
|
||||
if (!isUpdateCheckEnabled()) return fallback;
|
||||
|
||||
const current = getCurrentVersion();
|
||||
const overrideResult = compareWithOverride(current);
|
||||
if (overrideResult) return overrideResult;
|
||||
|
||||
if (shouldThrottleCheck()) {
|
||||
return cachedResult ?? fallback;
|
||||
}
|
||||
|
||||
markChecked();
|
||||
|
||||
const result =
|
||||
getUpdateChannel() === "nightly"
|
||||
? await checkNightlyUpdate()
|
||||
: await checkStableUpdate(current);
|
||||
|
||||
cachedResult = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function dismissNightlyUpdate(): void {
|
||||
void (async () => {
|
||||
const release = await fetchJson<GhRelease>(
|
||||
`https://api.github.com/repos/${getRepoSlug()}/releases/tags/${NIGHTLY_TAG}`,
|
||||
);
|
||||
if (release?.published_at) {
|
||||
settingsState.lastSeenNightlyPublishedAt = release.published_at;
|
||||
}
|
||||
})();
|
||||
}
|
||||
Reference in New Issue
Block a user