forked from boos/BoosAPI
[codex] add automatic update-center reminders (#333)
* add automatic update-center reminders * fix update-center reminder review issues
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
const {
|
||||
buildUpdateCenterStatusMock,
|
||||
sendNotificationMock,
|
||||
} = vi.hoisted(() => ({
|
||||
buildUpdateCenterStatusMock: vi.fn(),
|
||||
sendNotificationMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./updateCenterStatusService.js', () => ({
|
||||
buildUpdateCenterStatus: (...args: unknown[]) => buildUpdateCenterStatusMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./notifyService.js', () => ({
|
||||
sendNotification: (...args: unknown[]) => sendNotificationMock(...args),
|
||||
}));
|
||||
|
||||
type DbModule = typeof import('../db/index.js');
|
||||
type PollingModule = typeof import('./updateCenterPollingService.js');
|
||||
type RuntimeStateModule = typeof import('./updateCenterRuntimeStateService.js');
|
||||
|
||||
describe('updateCenterPollingService', () => {
|
||||
let dataDir = '';
|
||||
let db: DbModule['db'];
|
||||
let schema: DbModule['schema'];
|
||||
let closeDbConnections: DbModule['closeDbConnections'];
|
||||
let startUpdateCenterPolling: PollingModule['startUpdateCenterPolling'];
|
||||
let stopUpdateCenterPolling: PollingModule['stopUpdateCenterPolling'];
|
||||
let loadUpdateCenterRuntimeState: RuntimeStateModule['loadUpdateCenterRuntimeState'];
|
||||
|
||||
beforeAll(async () => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'metapi-update-center-polling-'));
|
||||
process.env.DATA_DIR = dataDir;
|
||||
|
||||
await import('../db/migrate.js');
|
||||
const dbModule = await import('../db/index.js');
|
||||
const pollingModule = await import('./updateCenterPollingService.js');
|
||||
const runtimeStateModule = await import('./updateCenterRuntimeStateService.js');
|
||||
|
||||
db = dbModule.db;
|
||||
schema = dbModule.schema;
|
||||
closeDbConnections = dbModule.closeDbConnections;
|
||||
startUpdateCenterPolling = pollingModule.startUpdateCenterPolling;
|
||||
stopUpdateCenterPolling = pollingModule.stopUpdateCenterPolling;
|
||||
loadUpdateCenterRuntimeState = runtimeStateModule.loadUpdateCenterRuntimeState;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers();
|
||||
buildUpdateCenterStatusMock.mockReset();
|
||||
sendNotificationMock.mockReset();
|
||||
|
||||
await db.delete(schema.events).run();
|
||||
await db.delete(schema.settings).run();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stopUpdateCenterPolling();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (typeof closeDbConnections === 'function') {
|
||||
await closeDbConnections();
|
||||
}
|
||||
if (dataDir) {
|
||||
try {
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
}
|
||||
delete process.env.DATA_DIR;
|
||||
});
|
||||
|
||||
it('runs immediately, writes one event, and only notifies once for the same candidate', async () => {
|
||||
buildUpdateCenterStatusMock.mockResolvedValue({
|
||||
currentVersion: '1.2.3',
|
||||
githubRelease: {
|
||||
normalizedVersion: '1.3.0',
|
||||
displayVersion: '1.3.0',
|
||||
tagName: 'v1.3.0',
|
||||
},
|
||||
dockerHubTag: null,
|
||||
helper: {
|
||||
imageTag: '1.2.3',
|
||||
},
|
||||
});
|
||||
|
||||
startUpdateCenterPolling(60_000);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(sendNotificationMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendNotificationMock.mock.calls[0]?.[0]).toContain('更新中心');
|
||||
expect(sendNotificationMock.mock.calls[0]?.[1]).toContain('1.3.0');
|
||||
|
||||
let events = await db.select().from(schema.events).all();
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toMatchObject({
|
||||
type: 'status',
|
||||
relatedType: 'update_center',
|
||||
level: 'info',
|
||||
});
|
||||
|
||||
expect(await loadUpdateCenterRuntimeState()).toEqual(expect.objectContaining({
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: 'github-release',
|
||||
lastResolvedCandidateKey: 'github-release:v1.3.0',
|
||||
lastNotifiedCandidateKey: 'github-release:v1.3.0',
|
||||
}));
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
|
||||
expect(sendNotificationMock).toHaveBeenCalledTimes(1);
|
||||
events = await db.select().from(schema.events).all();
|
||||
expect(events).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('stores the latest check error without creating events or notifications when the background check fails', async () => {
|
||||
buildUpdateCenterStatusMock.mockRejectedValue(new Error('GitHub releases lookup timed out'));
|
||||
|
||||
startUpdateCenterPolling(60_000);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(sendNotificationMock).not.toHaveBeenCalled();
|
||||
expect(await db.select().from(schema.events).all()).toEqual([]);
|
||||
expect(await loadUpdateCenterRuntimeState()).toEqual(expect.objectContaining({
|
||||
lastCheckError: 'GitHub releases lookup timed out',
|
||||
lastNotifiedCandidateKey: null,
|
||||
}));
|
||||
});
|
||||
|
||||
it('persists the notified candidate even when the downstream notification send fails', async () => {
|
||||
buildUpdateCenterStatusMock.mockImplementation(async () => ({
|
||||
currentVersion: '1.2.3',
|
||||
githubRelease: {
|
||||
normalizedVersion: '1.3.0',
|
||||
displayVersion: '1.3.0',
|
||||
tagName: 'v1.3.0',
|
||||
},
|
||||
dockerHubTag: null,
|
||||
helper: {
|
||||
imageTag: '1.2.3',
|
||||
},
|
||||
runtime: await loadUpdateCenterRuntimeState(),
|
||||
}));
|
||||
sendNotificationMock.mockRejectedValue(new Error('notification downstream failed'));
|
||||
|
||||
startUpdateCenterPolling(60_000);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
|
||||
expect(sendNotificationMock).toHaveBeenCalledTimes(1);
|
||||
expect(await db.select().from(schema.events).all()).toHaveLength(1);
|
||||
expect(await loadUpdateCenterRuntimeState()).toEqual(expect.objectContaining({
|
||||
lastCheckError: null,
|
||||
lastResolvedCandidateKey: 'github-release:v1.3.0',
|
||||
lastNotifiedCandidateKey: 'github-release:v1.3.0',
|
||||
lastNotifiedAt: expect.any(String),
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { db, schema } from '../db/index.js';
|
||||
import { formatUtcSqlDateTime } from './localTimeService.js';
|
||||
import { sendNotification } from './notifyService.js';
|
||||
import { buildUpdateCenterStatus } from './updateCenterStatusService.js';
|
||||
import { loadUpdateCenterRuntimeState, saveUpdateCenterRuntimeState } from './updateCenterRuntimeStateService.js';
|
||||
import { resolveUpdateReminderCandidate } from './updateCenterReminderService.js';
|
||||
|
||||
const DEFAULT_UPDATE_CENTER_INTERVAL_MS = 15 * 60 * 1000;
|
||||
|
||||
let pollingTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let syncRunning = false;
|
||||
|
||||
function summarizeError(error: unknown) {
|
||||
if (error instanceof Error && error.message) return error.message;
|
||||
return String(error || 'unknown error');
|
||||
}
|
||||
|
||||
function buildReminderEvent(candidate: ReturnType<typeof resolveUpdateReminderCandidate>) {
|
||||
if (!candidate) return null;
|
||||
const title = candidate.kind === 'new-digest'
|
||||
? '更新中心发现新 digest'
|
||||
: '更新中心发现新版本';
|
||||
const message = candidate.kind === 'new-digest'
|
||||
? `检测到 ${candidate.displayVersion} 指向了新的镜像 digest,可前往更新中心查看并手动部署。`
|
||||
: `检测到 ${candidate.displayVersion} 已可部署,可前往更新中心查看并手动部署。`;
|
||||
return {
|
||||
title,
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
async function runSyncOnce() {
|
||||
if (syncRunning) return;
|
||||
syncRunning = true;
|
||||
const checkedAt = formatUtcSqlDateTime(new Date());
|
||||
|
||||
try {
|
||||
const status = await buildUpdateCenterStatus();
|
||||
const previousRuntime = status.runtime || await loadUpdateCenterRuntimeState();
|
||||
const candidate = resolveUpdateReminderCandidate({
|
||||
currentVersion: status.currentVersion,
|
||||
helper: status.helper as { imageTag?: string | null; imageDigest?: string | null } | null,
|
||||
githubRelease: status.githubRelease,
|
||||
dockerHubTag: status.dockerHubTag,
|
||||
});
|
||||
|
||||
const nextRuntime = {
|
||||
...previousRuntime,
|
||||
lastCheckedAt: checkedAt,
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: candidate?.source || null,
|
||||
lastResolvedDisplayVersion: candidate?.displayVersion || null,
|
||||
lastResolvedCandidateKey: candidate?.candidateKey || null,
|
||||
};
|
||||
|
||||
if (candidate && candidate.candidateKey !== previousRuntime.lastNotifiedCandidateKey) {
|
||||
const reminderEvent = buildReminderEvent(candidate);
|
||||
if (reminderEvent) {
|
||||
await db.insert(schema.events).values({
|
||||
type: 'status',
|
||||
title: reminderEvent.title,
|
||||
message: reminderEvent.message,
|
||||
level: 'info',
|
||||
relatedType: 'update_center',
|
||||
createdAt: checkedAt,
|
||||
}).run();
|
||||
nextRuntime.lastNotifiedCandidateKey = candidate.candidateKey;
|
||||
nextRuntime.lastNotifiedAt = checkedAt;
|
||||
await saveUpdateCenterRuntimeState(nextRuntime);
|
||||
await sendNotification(reminderEvent.title, reminderEvent.message, 'info', {
|
||||
bypassThrottle: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await saveUpdateCenterRuntimeState(nextRuntime);
|
||||
} catch (error) {
|
||||
const previousRuntime = await loadUpdateCenterRuntimeState();
|
||||
await saveUpdateCenterRuntimeState({
|
||||
...previousRuntime,
|
||||
lastCheckedAt: checkedAt,
|
||||
lastCheckError: summarizeError(error),
|
||||
});
|
||||
} finally {
|
||||
syncRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function startUpdateCenterPolling(intervalMs = DEFAULT_UPDATE_CENTER_INTERVAL_MS) {
|
||||
stopUpdateCenterPolling();
|
||||
pollingTimer = setInterval(() => {
|
||||
void runSyncOnce();
|
||||
}, Math.max(10_000, intervalMs));
|
||||
pollingTimer.unref?.();
|
||||
void runSyncOnce();
|
||||
return { intervalMs: Math.max(10_000, intervalMs) };
|
||||
}
|
||||
|
||||
export function stopUpdateCenterPolling() {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
pollingTimer = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export type {
|
||||
UpdateHelperRuntimeLike,
|
||||
UpdateReminderCandidate,
|
||||
UpdateVersionCandidateLike,
|
||||
} from '../shared/updateCenterReminder.js';
|
||||
export { resolveUpdateReminderCandidate } from '../shared/updateCenterReminder.js';
|
||||
@@ -0,0 +1,81 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
type DbModule = typeof import('../db/index.js');
|
||||
type RuntimeStateModule = typeof import('./updateCenterRuntimeStateService.js');
|
||||
|
||||
describe('updateCenterRuntimeStateService', () => {
|
||||
let dataDir = '';
|
||||
let db: DbModule['db'];
|
||||
let schema: DbModule['schema'];
|
||||
let closeDbConnections: DbModule['closeDbConnections'];
|
||||
let loadUpdateCenterRuntimeState: RuntimeStateModule['loadUpdateCenterRuntimeState'];
|
||||
let saveUpdateCenterRuntimeState: RuntimeStateModule['saveUpdateCenterRuntimeState'];
|
||||
|
||||
beforeAll(async () => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'metapi-update-center-runtime-state-'));
|
||||
process.env.DATA_DIR = dataDir;
|
||||
|
||||
await import('../db/migrate.js');
|
||||
const dbModule = await import('../db/index.js');
|
||||
const runtimeStateModule = await import('./updateCenterRuntimeStateService.js');
|
||||
|
||||
db = dbModule.db;
|
||||
schema = dbModule.schema;
|
||||
closeDbConnections = dbModule.closeDbConnections;
|
||||
loadUpdateCenterRuntimeState = runtimeStateModule.loadUpdateCenterRuntimeState;
|
||||
saveUpdateCenterRuntimeState = runtimeStateModule.saveUpdateCenterRuntimeState;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.delete(schema.settings).run();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (typeof closeDbConnections === 'function') {
|
||||
await closeDbConnections();
|
||||
}
|
||||
if (dataDir) {
|
||||
try {
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
}
|
||||
delete process.env.DATA_DIR;
|
||||
});
|
||||
|
||||
it('returns an empty default state when nothing has been persisted yet', async () => {
|
||||
await expect(loadUpdateCenterRuntimeState()).resolves.toEqual({
|
||||
lastCheckedAt: null,
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: null,
|
||||
lastResolvedDisplayVersion: null,
|
||||
lastResolvedCandidateKey: null,
|
||||
lastNotifiedCandidateKey: null,
|
||||
lastNotifiedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('persists and reloads reminder runtime metadata', async () => {
|
||||
await saveUpdateCenterRuntimeState({
|
||||
lastCheckedAt: '2026-03-30 20:30:00',
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: 'docker-hub-tag',
|
||||
lastResolvedDisplayVersion: 'latest @ sha256:efb2ee655386',
|
||||
lastResolvedCandidateKey: 'docker-hub-tag:latest@sha256:efb2ee6553866bd3268dcc54c02fa5f9789728c51ed4af63328aaba6da67df35',
|
||||
lastNotifiedCandidateKey: 'docker-hub-tag:latest@sha256:efb2ee6553866bd3268dcc54c02fa5f9789728c51ed4af63328aaba6da67df35',
|
||||
lastNotifiedAt: '2026-03-30 20:31:00',
|
||||
});
|
||||
|
||||
await expect(loadUpdateCenterRuntimeState()).resolves.toEqual({
|
||||
lastCheckedAt: '2026-03-30 20:30:00',
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: 'docker-hub-tag',
|
||||
lastResolvedDisplayVersion: 'latest @ sha256:efb2ee655386',
|
||||
lastResolvedCandidateKey: 'docker-hub-tag:latest@sha256:efb2ee6553866bd3268dcc54c02fa5f9789728c51ed4af63328aaba6da67df35',
|
||||
lastNotifiedCandidateKey: 'docker-hub-tag:latest@sha256:efb2ee6553866bd3268dcc54c02fa5f9789728c51ed4af63328aaba6da67df35',
|
||||
lastNotifiedAt: '2026-03-30 20:31:00',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
import { db, schema } from '../db/index.js';
|
||||
import { upsertSetting } from '../db/upsertSetting.js';
|
||||
import type { UpdateCenterVersionSource } from './updateCenterVersionService.js';
|
||||
|
||||
export type UpdateCenterRuntimeState = {
|
||||
lastCheckedAt: string | null;
|
||||
lastCheckError: string | null;
|
||||
lastResolvedSource: UpdateCenterVersionSource | null;
|
||||
lastResolvedDisplayVersion: string | null;
|
||||
lastResolvedCandidateKey: string | null;
|
||||
lastNotifiedCandidateKey: string | null;
|
||||
lastNotifiedAt: string | null;
|
||||
};
|
||||
|
||||
export const UPDATE_CENTER_RUNTIME_STATE_SETTING_KEY = 'update_center_runtime_state_v1';
|
||||
|
||||
export function getDefaultUpdateCenterRuntimeState(): UpdateCenterRuntimeState {
|
||||
return {
|
||||
lastCheckedAt: null,
|
||||
lastCheckError: null,
|
||||
lastResolvedSource: null,
|
||||
lastResolvedDisplayVersion: null,
|
||||
lastResolvedCandidateKey: null,
|
||||
lastNotifiedCandidateKey: null,
|
||||
lastNotifiedAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeNullableString(value: unknown): string | null {
|
||||
if (typeof value !== 'string') return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
function normalizeNullableSource(value: unknown): UpdateCenterVersionSource | null {
|
||||
return value === 'docker-hub-tag' || value === 'github-release' ? value : null;
|
||||
}
|
||||
|
||||
export function normalizeUpdateCenterRuntimeState(input: unknown): UpdateCenterRuntimeState {
|
||||
const defaults = getDefaultUpdateCenterRuntimeState();
|
||||
const record = input && typeof input === 'object' ? input as Record<string, unknown> : {};
|
||||
return {
|
||||
lastCheckedAt: normalizeNullableString(record.lastCheckedAt) ?? defaults.lastCheckedAt,
|
||||
lastCheckError: normalizeNullableString(record.lastCheckError) ?? defaults.lastCheckError,
|
||||
lastResolvedSource: normalizeNullableSource(record.lastResolvedSource) ?? defaults.lastResolvedSource,
|
||||
lastResolvedDisplayVersion: normalizeNullableString(record.lastResolvedDisplayVersion) ?? defaults.lastResolvedDisplayVersion,
|
||||
lastResolvedCandidateKey: normalizeNullableString(record.lastResolvedCandidateKey) ?? defaults.lastResolvedCandidateKey,
|
||||
lastNotifiedCandidateKey: normalizeNullableString(record.lastNotifiedCandidateKey) ?? defaults.lastNotifiedCandidateKey,
|
||||
lastNotifiedAt: normalizeNullableString(record.lastNotifiedAt) ?? defaults.lastNotifiedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadUpdateCenterRuntimeState(): Promise<UpdateCenterRuntimeState> {
|
||||
const row = await db.select().from(schema.settings).where(eq(schema.settings.key, UPDATE_CENTER_RUNTIME_STATE_SETTING_KEY)).get();
|
||||
if (!row?.value) {
|
||||
return getDefaultUpdateCenterRuntimeState();
|
||||
}
|
||||
|
||||
try {
|
||||
return normalizeUpdateCenterRuntimeState(JSON.parse(row.value));
|
||||
} catch {
|
||||
return getDefaultUpdateCenterRuntimeState();
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveUpdateCenterRuntimeState(input: unknown): Promise<UpdateCenterRuntimeState> {
|
||||
const next = normalizeUpdateCenterRuntimeState(input);
|
||||
await upsertSetting(UPDATE_CENTER_RUNTIME_STATE_SETTING_KEY, next);
|
||||
return next;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { config as runtimeConfig } from '../config.js';
|
||||
import { listBackgroundTasks } from './backgroundTaskService.js';
|
||||
import {
|
||||
fetchLatestDockerHubTag,
|
||||
fetchLatestStableGitHubRelease,
|
||||
getCurrentRuntimeVersion,
|
||||
type UpdateCenterVersionCandidate,
|
||||
} from './updateCenterVersionService.js';
|
||||
import {
|
||||
loadUpdateCenterConfig,
|
||||
} from './updateCenterConfigService.js';
|
||||
import {
|
||||
getUpdateCenterHelperStatus,
|
||||
} from './updateCenterHelperClient.js';
|
||||
import { loadUpdateCenterRuntimeState } from './updateCenterRuntimeStateService.js';
|
||||
import { UPDATE_CENTER_DEPLOY_TASK_TYPE } from './updateCenterTaskConstants.js';
|
||||
|
||||
function getUpdateCenterHelperToken(): string {
|
||||
return String(
|
||||
runtimeConfig.deployHelperToken
|
||||
|| process.env.DEPLOY_HELPER_TOKEN
|
||||
|| process.env.UPDATE_CENTER_HELPER_TOKEN
|
||||
|| '',
|
||||
).trim();
|
||||
}
|
||||
|
||||
function summarizeHelperError(error: unknown) {
|
||||
if (error instanceof Error && error.message) return error.message;
|
||||
return String(error || 'unknown helper error');
|
||||
}
|
||||
|
||||
async function settleOptional<T>(enabled: boolean, loader: () => Promise<T>): Promise<{
|
||||
value: T | null;
|
||||
error: string | null;
|
||||
}> {
|
||||
if (!enabled) {
|
||||
return {
|
||||
value: null,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
value: await loader(),
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
value: null,
|
||||
error: summarizeHelperError(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getDeployTasks() {
|
||||
return listBackgroundTasks(50).filter((task) => task.type === UPDATE_CENTER_DEPLOY_TASK_TYPE);
|
||||
}
|
||||
|
||||
export async function buildUpdateCenterStatus() {
|
||||
const config = await loadUpdateCenterConfig();
|
||||
const helperToken = getUpdateCenterHelperToken();
|
||||
|
||||
const [githubLookup, dockerLookup, helperLookup, runtime] = await Promise.all([
|
||||
settleOptional(config.githubReleasesEnabled, async () => await fetchLatestStableGitHubRelease()),
|
||||
settleOptional(config.dockerHubTagsEnabled, async () => await fetchLatestDockerHubTag()),
|
||||
settleOptional(!!config.helperBaseUrl, async () => {
|
||||
if (!helperToken) {
|
||||
throw new Error('DEPLOY_HELPER_TOKEN is required');
|
||||
}
|
||||
return await getUpdateCenterHelperStatus(config, helperToken);
|
||||
}),
|
||||
loadUpdateCenterRuntimeState(),
|
||||
]);
|
||||
|
||||
const githubRelease = githubLookup.value as UpdateCenterVersionCandidate | null;
|
||||
const dockerHubTag = dockerLookup.value as UpdateCenterVersionCandidate | null;
|
||||
const helper: Record<string, unknown> = helperLookup.value || {
|
||||
ok: false,
|
||||
healthy: false,
|
||||
error: helperLookup.error,
|
||||
};
|
||||
|
||||
const tasks = getDeployTasks();
|
||||
const runningTask = tasks.find((task) => task.status === 'pending' || task.status === 'running') || null;
|
||||
const lastFinishedTask = tasks.find((task) => task.status === 'succeeded' || task.status === 'failed') || null;
|
||||
|
||||
return {
|
||||
currentVersion: getCurrentRuntimeVersion(),
|
||||
config,
|
||||
githubRelease,
|
||||
dockerHubTag,
|
||||
helper,
|
||||
runningTask,
|
||||
lastFinishedTask,
|
||||
runtime,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const UPDATE_CENTER_DEPLOY_TASK_TYPE = 'update-center.deploy';
|
||||
export const UPDATE_CENTER_DEPLOY_DEDUPE_KEY = UPDATE_CENTER_DEPLOY_TASK_TYPE;
|
||||
Reference in New Issue
Block a user