feat: restart to apply update, privileged helper, rollback
This commit is contained in:
1 parent
70460e4170
commit
54e8fd5fed
63 files changed
+8635
-164
No files matched your search
@@ -1,11 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
AUTO_CHECK_INTERVAL_MS,
|
||||
browserUpdatesDocsUrl,
|
||||
displayVersion,
|
||||
downloadAnnouncement,
|
||||
formatDownloadProgress,
|
||||
formatLastChecked,
|
||||
formatReleased,
|
||||
formatStartedAt,
|
||||
formatStartedWithAge,
|
||||
formatUpdatedAt,
|
||||
isCheckDue,
|
||||
} from '@/lib/updateFormat'
|
||||
|
||||
@@ -92,3 +96,42 @@ describe('downloadAnnouncement', () => {
|
||||
expect(downloadAnnouncement('2.3.0-beta', percent)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('browserUpdatesDocsUrl', () => {
|
||||
it('points the release url at the Browser updates section of the docs of the same repository', () => {
|
||||
expect(
|
||||
browserUpdatesDocsUrl('https://git.example/fsylvain/RenewCMS/releases/tag/v2.3.0-beta'),
|
||||
).toBe(
|
||||
'https://git.example/fsylvain/RenewCMS/src/branch/main/docs/deployment.md#browser-updates',
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps a url it cannot interpret', () => {
|
||||
expect(browserUpdatesDocsUrl('https://git.example/somewhere')).toBe(
|
||||
'https://git.example/somewhere',
|
||||
)
|
||||
expect(browserUpdatesDocsUrl('not a url')).toBe('not a url')
|
||||
})
|
||||
})
|
||||
|
||||
describe('restart meta lines', () => {
|
||||
const startedAt = '2026-09-30T09:51:00Z'
|
||||
|
||||
it('reads the start time', () => {
|
||||
expect(formatStartedAt(startedAt)).toBe('Started at 09:51')
|
||||
})
|
||||
|
||||
it('adds how long ago it was', () => {
|
||||
expect(formatStartedWithAge(startedAt, new Date('2026-09-30T09:52:30Z'))).toBe(
|
||||
'Started at 09:51 · 1 minute ago',
|
||||
)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['2026-09-30T09:52:00Z', 'Updated today at 09:52'],
|
||||
['2026-09-29T18:12:00Z', 'Updated yesterday at 18:12'],
|
||||
['2026-09-20T18:12:00Z', 'Updated 20 Sep 2026 at 18:12'],
|
||||
])('reads the update time %s as %s', (iso, expected) => {
|
||||
expect(formatUpdatedAt(iso, new Date('2026-09-30T10:00:00Z'))).toBe(expected)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export function reloadPage() {
|
||||
window.location.reload()
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { RestartKind } from '@/composables/useUpdate'
|
||||
|
||||
export const RESTART_MEMORY_KEY = 'renewcms:update-restart'
|
||||
|
||||
export interface RestartMemory {
|
||||
kind: RestartKind
|
||||
started_at: string
|
||||
}
|
||||
|
||||
export function readRestartMemory(): RestartMemory | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(RESTART_MEMORY_KEY)
|
||||
if (raw === null) {
|
||||
return null
|
||||
}
|
||||
const parsed = JSON.parse(raw) as Partial<RestartMemory>
|
||||
const validKind = parsed.kind === 'update' || parsed.kind === 'restart'
|
||||
return validKind && typeof parsed.started_at === 'string'
|
||||
? { kind: parsed.kind as RestartKind, started_at: parsed.started_at }
|
||||
: null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function writeRestartMemory(memory: RestartMemory) {
|
||||
try {
|
||||
sessionStorage.setItem(RESTART_MEMORY_KEY, JSON.stringify(memory))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function clearRestartMemory() {
|
||||
try {
|
||||
sessionStorage.removeItem(RESTART_MEMORY_KEY)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function isLaterThan(candidate: string | null, reference: string) {
|
||||
if (candidate === null) {
|
||||
return false
|
||||
}
|
||||
return new Date(candidate).getTime() > new Date(reference).getTime()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatAbsoluteDateFromIso } from '@/lib/relativeDate'
|
||||
import { formatAbsoluteDateFromIso, formatRelativeDate } from '@/lib/relativeDate'
|
||||
|
||||
export const ENABLE_BROWSER_UPDATES_COMMAND = 'renewcms enable-browser-updates'
|
||||
export const AUTO_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
|
||||
@@ -15,7 +15,7 @@ function isSameLocalDay(a: Date, b: Date) {
|
||||
)
|
||||
}
|
||||
|
||||
function formatClock(date: Date) {
|
||||
export function formatClock(date: Date) {
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${hours}:${minutes}`
|
||||
@@ -69,3 +69,37 @@ export function downloadAnnouncement(version: string, percent: number) {
|
||||
}
|
||||
|
||||
export const VERIFYING_ANNOUNCEMENT = 'Checking the download'
|
||||
|
||||
const BROWSER_UPDATES_DOCS_PATH = '/src/branch/main/docs/deployment.md#browser-updates'
|
||||
const RELEASES_SEGMENT = /^(.*)\/releases\//
|
||||
|
||||
export function browserUpdatesDocsUrl(releaseUrl: string) {
|
||||
try {
|
||||
const url = new URL(releaseUrl)
|
||||
const repository = RELEASES_SEGMENT.exec(url.pathname)
|
||||
return repository ? `${url.origin}${repository[1]}${BROWSER_UPDATES_DOCS_PATH}` : releaseUrl
|
||||
} catch {
|
||||
return releaseUrl
|
||||
}
|
||||
}
|
||||
|
||||
export function formatStartedAt(iso: string) {
|
||||
return `Started at ${formatClock(new Date(iso))}`
|
||||
}
|
||||
|
||||
export function formatStartedWithAge(iso: string, now: Date = new Date()) {
|
||||
return `${formatStartedAt(iso)} · ${formatRelativeDate(iso, now)}`
|
||||
}
|
||||
|
||||
export function formatUpdatedAt(iso: string, now: Date = new Date()) {
|
||||
const date = new Date(iso)
|
||||
const yesterday = new Date(now)
|
||||
yesterday.setDate(now.getDate() - 1)
|
||||
if (isSameLocalDay(date, now)) {
|
||||
return `Updated today at ${formatClock(date)}`
|
||||
}
|
||||
if (isSameLocalDay(date, yesterday)) {
|
||||
return `Updated yesterday at ${formatClock(date)}`
|
||||
}
|
||||
return `Updated ${formatAbsoluteDateFromIso(iso)} at ${formatClock(date)}`
|
||||
}
|
||||
Reference in new issue
Block a user