Add native runtime update notifications
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
name: Runtime release watch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
php-releases:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Check official PHP releases
|
||||
id: check
|
||||
run: |
|
||||
RESULT=$(node scripts/check-php-releases.mjs --report "$RUNNER_TEMP/php-runtime-report.md")
|
||||
echo "count=$(node -e 'const value=JSON.parse(process.argv[1]); console.log(value.updates.length)' "$RESULT")" >> "$GITHUB_OUTPUT"
|
||||
- name: Create or refresh packaging issue
|
||||
if: steps.check.outputs.count != '0'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
TITLE="Runtime update: new PHP release needs packaging"
|
||||
EXISTING=$(gh issue list --state open --search "$TITLE in:title" --json number --jq '.[0].number // empty')
|
||||
if [ -n "$EXISTING" ]; then
|
||||
gh issue edit "$EXISTING" --body-file "$RUNNER_TEMP/php-runtime-report.md"
|
||||
else
|
||||
gh issue create --title "$TITLE" --body-file "$RUNNER_TEMP/php-runtime-report.md"
|
||||
fi
|
||||
@@ -26,3 +26,9 @@ Every project receives reserved loopback ports, generated service configuration,
|
||||
6. Additional PHP/database versions, Apache, Drupal, Node.js, and developer services.
|
||||
|
||||
Native project creation must stay disabled until the platform bundle passes executable, service-health, database, routing, and cleanup checks. Existing projects default to the container engine for backward compatibility.
|
||||
|
||||
## Runtime update notifications
|
||||
|
||||
Dockside reads the bundled runtime catalog at startup and checks again every six hours. A remote catalog is accepted only when `AURORA_RUNTIME_CATALOG_PUBLIC_KEY` contains the Ed25519 public key and the adjacent `catalog.json.sig` validates. The last verified remote catalog is cached; otherwise Dockside falls back to its bundled trusted catalog.
|
||||
|
||||
The scheduled `runtime-release-watch.yml` workflow checks PHP's official JSON release feed every six hours. When a tracked PHP branch changes, it creates or updates a GitHub issue. It does not publish a runtime automatically: each platform package must be built, checksummed, tested, added to the catalog, and signed before users see it.
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"build:modules": "node scripts/package-modules.cjs",
|
||||
"build:connector": "node scripts/package-connector.cjs",
|
||||
"build:native-runtime": "node scripts/package-native-runtime.cjs",
|
||||
"check:php-releases": "node scripts/check-php-releases.mjs",
|
||||
"module:new": "node scripts/create-module.cjs",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"generatedAt": "2026-08-22T00:00:00.000Z",
|
||||
"releases": []
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"checkedAt": "2026-08-22T00:00:00.000Z",
|
||||
"php": {
|
||||
"8.2": "8.2.33",
|
||||
"8.3": "8.3.33",
|
||||
"8.4": "8.4.24",
|
||||
"8.5": "8.5.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const sourceUrl = 'https://www.php.net/releases/index.php?json&version=8&max=30'
|
||||
const baselinePath = resolve('runtime/upstream-baseline.json')
|
||||
const reportArgument = process.argv.indexOf('--report')
|
||||
const reportPath = reportArgument >= 0 ? process.argv[reportArgument + 1] : undefined
|
||||
|
||||
function latestByBranch(releases) {
|
||||
const result = {}
|
||||
for (const version of Object.keys(releases)) {
|
||||
if (!/^8\.[2-5]\.\d+$/.test(version)) continue
|
||||
const branch = version.split('.').slice(0, 2).join('.')
|
||||
if (!result[branch] || Number(version.split('.')[2]) > Number(result[branch].split('.')[2])) result[branch] = version
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const baseline = JSON.parse(await readFile(baselinePath, 'utf8'))
|
||||
const response = await fetch(sourceUrl, { headers: { 'user-agent': 'Aurora-Dockside-Runtime-Watcher/1.0' } })
|
||||
if (!response.ok) throw new Error(`PHP release feed returned HTTP ${response.status}.`)
|
||||
const releases = await response.json()
|
||||
const latest = latestByBranch(releases)
|
||||
const updates = Object.entries(latest)
|
||||
.filter(([branch, version]) => baseline.php[branch] !== version)
|
||||
.map(([branch, version]) => ({ branch, packaged: baseline.php[branch] || 'not tracked', upstream: version, security: releases[version]?.tags?.includes('security') === true }))
|
||||
|
||||
const lines = updates.length
|
||||
? ['# PHP runtime releases need packaging', '', 'The official PHP release feed contains versions newer than Aurora’s tracked packaging baseline.', '', '| Branch | Aurora baseline | Upstream | Security release |', '| --- | --- | --- | --- |', ...updates.map((update) => `| ${update.branch} | ${update.packaged} | ${update.upstream} | ${update.security ? 'Yes' : 'No'} |`), '', `Source: ${sourceUrl}`, '', 'After packages pass platform tests, update `runtime/upstream-baseline.json` and the signed runtime catalog.']
|
||||
: ['# PHP runtime release check', '', 'No untracked PHP releases were found.']
|
||||
|
||||
if (reportPath) await writeFile(reportPath, `${lines.join('\n')}\n`, 'utf8')
|
||||
process.stdout.write(`${JSON.stringify({ updates, latest })}\n`)
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { getRuntimeStatus } from '../nativeRuntime'
|
||||
import { getRuntimeUpdates } from '../runtimeCatalog'
|
||||
|
||||
export function registerRuntimeIpc(): void {
|
||||
ipcMain.handle('runtime:status', () => getRuntimeStatus())
|
||||
ipcMain.handle('runtime:updates', async () => {
|
||||
const status = await getRuntimeStatus()
|
||||
return getRuntimeUpdates(status.native.components)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { findRuntimeUpdates, validateRuntimeCatalog } from './runtimeCatalog'
|
||||
|
||||
const checksum = 'a'.repeat(64)
|
||||
|
||||
describe('runtime catalog', () => {
|
||||
it('selects the newest compatible component release', () => {
|
||||
const catalog = validateRuntimeCatalog({ schema: 1, generatedAt: '2026-08-22T00:00:00.000Z', releases: [
|
||||
{ component: 'php', version: '8.5.8', platform: 'linux', arch: 'x64', channel: 'stable', url: 'https://auroradockside.com/php-8.5.8.tar.zst', sha256: checksum },
|
||||
{ component: 'php', version: '8.5.9', platform: 'linux', arch: 'x64', channel: 'security', url: 'https://auroradockside.com/php-8.5.9.tar.zst', sha256: checksum },
|
||||
{ component: 'php', version: '8.6.0', platform: 'darwin', arch: 'arm64', channel: 'stable', url: 'https://auroradockside.com/php-8.6.0.tar.zst', sha256: checksum }
|
||||
] })
|
||||
expect(findRuntimeUpdates(catalog, [{ id: 'php', version: '8.5.7', executable: 'bin/php', sha256: checksum }], 'linux', 'x64')).toEqual([
|
||||
{ component: 'php', installedVersion: '8.5.7', availableVersion: '8.5.9', channel: 'security' }
|
||||
])
|
||||
})
|
||||
|
||||
it('rejects insecure release URLs', () => {
|
||||
expect(() => validateRuntimeCatalog({ schema: 1, generatedAt: 'now', releases: [{ component: 'php', version: '8.5.9', platform: 'linux', arch: 'x64', channel: 'stable', url: 'http://example.test/php', sha256: checksum }] })).toThrow('Invalid runtime catalog release')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import { app } from 'electron'
|
||||
import { createPublicKey, verify } from 'crypto'
|
||||
import { mkdir, readFile, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import type {
|
||||
AuroraNativeRuntimeComponent,
|
||||
AuroraRuntimeCatalog,
|
||||
AuroraRuntimeCatalogRelease,
|
||||
AuroraRuntimeUpdate,
|
||||
AuroraRuntimeUpdateStatus
|
||||
} from '../shared/types'
|
||||
|
||||
const catalogUrl = process.env.AURORA_RUNTIME_CATALOG_URL || 'https://auroradockside.com/runtime/catalog.json'
|
||||
const publicKeyPem = process.env.AURORA_RUNTIME_CATALOG_PUBLIC_KEY
|
||||
|
||||
function compareVersions(left: string, right: string): number {
|
||||
const a = left.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0)
|
||||
const b = right.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0)
|
||||
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
||||
if ((a[index] || 0) !== (b[index] || 0)) return (a[index] || 0) - (b[index] || 0)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export function validateRuntimeCatalog(value: unknown): AuroraRuntimeCatalog {
|
||||
if (!value || typeof value !== 'object') throw new Error('Runtime catalog must be an object.')
|
||||
const catalog = value as Record<string, unknown>
|
||||
if (catalog.schema !== 1 || typeof catalog.generatedAt !== 'string' || !Array.isArray(catalog.releases))
|
||||
throw new Error('Unsupported runtime catalog.')
|
||||
const releases = catalog.releases.map((entry) => {
|
||||
if (!entry || typeof entry !== 'object') throw new Error('Invalid runtime catalog release.')
|
||||
const release = entry as Record<string, unknown>
|
||||
if (
|
||||
!['php', 'nginx', 'apache', 'mariadb', 'mysql', 'postgres', 'node', 'composer', 'wp-cli', 'drush'].includes(String(release.component)) ||
|
||||
typeof release.version !== 'string' ||
|
||||
!['linux', 'darwin', 'win32'].includes(String(release.platform)) ||
|
||||
!['x64', 'arm64'].includes(String(release.arch)) ||
|
||||
!['stable', 'security'].includes(String(release.channel)) ||
|
||||
typeof release.url !== 'string' ||
|
||||
!release.url.startsWith('https://') ||
|
||||
typeof release.sha256 !== 'string' ||
|
||||
!/^[a-f0-9]{64}$/i.test(release.sha256)
|
||||
) throw new Error('Invalid runtime catalog release.')
|
||||
return release as unknown as AuroraRuntimeCatalogRelease
|
||||
})
|
||||
return { schema: 1, generatedAt: catalog.generatedAt, releases }
|
||||
}
|
||||
|
||||
export function findRuntimeUpdates(
|
||||
catalog: AuroraRuntimeCatalog,
|
||||
installed: AuroraNativeRuntimeComponent[],
|
||||
platform: NodeJS.Platform,
|
||||
arch: string
|
||||
): AuroraRuntimeUpdate[] {
|
||||
return installed.flatMap((component) => {
|
||||
const candidates = catalog.releases.filter((release) =>
|
||||
release.component === component.id && release.platform === platform && release.arch === arch
|
||||
)
|
||||
const latest = candidates.sort((a, b) => compareVersions(b.version, a.version))[0]
|
||||
return latest && compareVersions(latest.version, component.version) > 0
|
||||
? [{ component: component.id, installedVersion: component.version, availableVersion: latest.version, channel: latest.channel }]
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
||||
function bundledCatalogPath(): string {
|
||||
return join(app.getAppPath(), 'resources', 'runtime-catalog', 'catalog.json')
|
||||
}
|
||||
|
||||
async function remoteCatalog(): Promise<AuroraRuntimeCatalog> {
|
||||
if (!publicKeyPem) throw new Error('Remote runtime catalog verification is not configured yet.')
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(() => controller.abort(), 8000)
|
||||
try {
|
||||
const [catalogResponse, signatureResponse] = await Promise.all([
|
||||
fetch(catalogUrl, { signal: controller.signal }),
|
||||
fetch(`${catalogUrl}.sig`, { signal: controller.signal })
|
||||
])
|
||||
if (!catalogResponse.ok || !signatureResponse.ok) throw new Error('Runtime catalog server is unavailable.')
|
||||
const body = Buffer.from(await catalogResponse.arrayBuffer())
|
||||
const signature = Buffer.from((await signatureResponse.text()).trim(), 'base64')
|
||||
if (!verify(null, body, createPublicKey(publicKeyPem), signature))
|
||||
throw new Error('Runtime catalog signature is invalid.')
|
||||
return validateRuntimeCatalog(JSON.parse(body.toString('utf8')))
|
||||
} finally {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRuntimeUpdates(installed: AuroraNativeRuntimeComponent[]): Promise<AuroraRuntimeUpdateStatus> {
|
||||
const cacheDirectory = join(app.getPath('userData'), 'runtime-catalog')
|
||||
const cachePath = join(cacheDirectory, 'catalog.json')
|
||||
let source: AuroraRuntimeUpdateStatus['source'] = 'bundled'
|
||||
let message: string | undefined
|
||||
let catalog: AuroraRuntimeCatalog
|
||||
try {
|
||||
catalog = await remoteCatalog()
|
||||
source = 'remote'
|
||||
await mkdir(cacheDirectory, { recursive: true })
|
||||
await writeFile(cachePath, JSON.stringify(catalog, null, 2), 'utf8')
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : 'Runtime update check failed.'
|
||||
try {
|
||||
catalog = validateRuntimeCatalog(JSON.parse(await readFile(cachePath, 'utf8')))
|
||||
source = 'cache'
|
||||
} catch {
|
||||
catalog = validateRuntimeCatalog(JSON.parse(await readFile(bundledCatalogPath(), 'utf8')))
|
||||
}
|
||||
}
|
||||
return {
|
||||
checkedAt: new Date().toISOString(),
|
||||
source,
|
||||
updates: findRuntimeUpdates(catalog, installed, process.platform, process.arch),
|
||||
message
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
AuroraRemoteSiteProfile,
|
||||
AuroraRemoteSiteStatus,
|
||||
AuroraRuntimeStatus,
|
||||
AuroraRuntimeUpdateStatus,
|
||||
AuroraStackOptions,
|
||||
EnvironmentUpdate,
|
||||
LogDataEvent,
|
||||
@@ -157,7 +158,8 @@ const api = {
|
||||
ipcRenderer.invoke('remote:pull', operationId, name)
|
||||
},
|
||||
runtime: {
|
||||
status: (): Promise<AuroraRuntimeStatus> => ipcRenderer.invoke('runtime:status')
|
||||
status: (): Promise<AuroraRuntimeStatus> => ipcRenderer.invoke('runtime:status'),
|
||||
updates: (): Promise<AuroraRuntimeUpdateStatus> => ipcRenderer.invoke('runtime:updates')
|
||||
},
|
||||
zoom: {
|
||||
in: (): Promise<number> => ipcRenderer.invoke('window:zoomIn'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Anchor, Boxes, FolderOpen, Plus, Settings, Sparkles, TerminalSquare } from 'lucide-react'
|
||||
import { Anchor, Bell, Boxes, FolderOpen, Plus, Settings, Sparkles, TerminalSquare } from 'lucide-react'
|
||||
import { ProjectDetail } from './components/projects/ProjectDetail'
|
||||
import { ProjectList } from './components/projects/ProjectList'
|
||||
import { TerminalPanel } from './components/terminal/TerminalPanel'
|
||||
@@ -13,12 +13,14 @@ import { useTerminalEvents } from './hooks/useTerminalEvents'
|
||||
import { useAppliedTheme } from './hooks/useAppliedTheme'
|
||||
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
|
||||
import docksideIcon from './assets/dockside-icon.png'
|
||||
import { useRuntimeUpdates } from './hooks/useRuntime'
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
const selectedProjectName = useAppStore((s) => s.selectedProjectName)
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false)
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false)
|
||||
const [isModulesOpen, setIsModulesOpen] = useState(false)
|
||||
const { data: runtimeUpdates } = useRuntimeUpdates()
|
||||
useTerminalEvents()
|
||||
useAppliedTheme()
|
||||
useKeyboardShortcuts({
|
||||
@@ -53,6 +55,17 @@ function App(): React.JSX.Element {
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
{runtimeUpdates && runtimeUpdates.updates.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
title={`${runtimeUpdates.updates.length} runtime update${runtimeUpdates.updates.length === 1 ? '' : 's'} available`}
|
||||
className="relative rounded-md p-1.5 text-amber-600 transition hover:bg-amber-50 dark:text-amber-300 dark:hover:bg-amber-400/10"
|
||||
>
|
||||
<Bell size={16} />
|
||||
<span className="absolute right-0 top-0 size-2 rounded-full bg-amber-500" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Minus, Plus, RotateCcw, X } from 'lucide-react'
|
||||
import { CheckCircle2, Minus, Plus, RefreshCw, RotateCcw, X } from 'lucide-react'
|
||||
import { clsx } from 'clsx'
|
||||
import { useThemeStore, type Theme } from '../../stores/themeStore'
|
||||
import { useRuntimeUpdates } from '../../hooks/useRuntime'
|
||||
|
||||
const THEMES: { value: Theme; label: string }[] = [
|
||||
{ value: 'light', label: 'Light' },
|
||||
@@ -18,6 +19,7 @@ const SHORTCUTS = [
|
||||
export function SettingsModal({ onClose }: { onClose: () => void }): React.JSX.Element {
|
||||
const theme = useThemeStore((s) => s.theme)
|
||||
const setTheme = useThemeStore((s) => s.setTheme)
|
||||
const runtimeUpdates = useRuntimeUpdates()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-8">
|
||||
@@ -34,6 +36,20 @@ export function SettingsModal({ onClose }: { onClose: () => void }): React.JSX.E
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6 p-4">
|
||||
<section>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h3 className="text-xs font-medium text-neutral-500 dark:text-neutral-400">Runtime updates</h3>
|
||||
<button type="button" onClick={() => runtimeUpdates.refetch()} disabled={runtimeUpdates.isFetching} className="inline-flex items-center gap-1 rounded px-2 py-1 text-xs text-cyan-700 hover:bg-cyan-50 disabled:opacity-50 dark:text-cyan-300 dark:hover:bg-cyan-400/10"><RefreshCw size={12} className={runtimeUpdates.isFetching ? 'animate-spin' : ''}/>Check now</button>
|
||||
</div>
|
||||
<div className="rounded-lg border border-neutral-200 p-3 text-sm dark:border-neutral-700">
|
||||
{runtimeUpdates.isLoading ? <p className="text-neutral-500">Checking trusted runtime catalog…</p> : runtimeUpdates.data?.updates.length ? (
|
||||
<div className="space-y-2">{runtimeUpdates.data.updates.map((update) => <div key={update.component} className="flex items-center justify-between"><span className="font-medium uppercase">{update.component}</span><span className="text-xs text-amber-700 dark:text-amber-300">{update.installedVersion} → {update.availableVersion}</span></div>)}</div>
|
||||
) : <p className="flex items-center gap-2 text-neutral-600 dark:text-neutral-300"><CheckCircle2 size={15} className="text-emerald-600"/>Installed Aurora runtimes are current.</p>}
|
||||
{runtimeUpdates.data?.message && <p className="mt-2 text-xs text-neutral-500">Using the {runtimeUpdates.data.source} catalog. {runtimeUpdates.data.message}</p>}
|
||||
<p className="mt-2 text-xs text-neutral-400">Updates are announced automatically. Installation remains a user-approved action.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-medium text-neutral-500 dark:text-neutral-400">
|
||||
Theme
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
|
||||
import type { AuroraRuntimeStatus } from '@shared/types'
|
||||
import type { AuroraRuntimeStatus, AuroraRuntimeUpdateStatus } from '@shared/types'
|
||||
|
||||
export function useRuntimeStatus(): UseQueryResult<AuroraRuntimeStatus, Error> {
|
||||
return useQuery({
|
||||
@@ -8,3 +8,13 @@ export function useRuntimeStatus(): UseQueryResult<AuroraRuntimeStatus, Error> {
|
||||
staleTime: 30000
|
||||
})
|
||||
}
|
||||
|
||||
export function useRuntimeUpdates(): UseQueryResult<AuroraRuntimeUpdateStatus, Error> {
|
||||
return useQuery({
|
||||
queryKey: ['runtime', 'updates'],
|
||||
queryFn: () => window.api.runtime.updates(),
|
||||
staleTime: 60 * 60 * 1000,
|
||||
refetchInterval: 6 * 60 * 60 * 1000,
|
||||
retry: 1
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,38 @@ export interface AuroraRuntimeStatus {
|
||||
native: { available: boolean; platform: string; arch: string; runtimeVersion?: string; components: AuroraNativeRuntimeComponent[]; reason?: string }
|
||||
}
|
||||
|
||||
export type AuroraRuntimeComponentId = AuroraNativeRuntimeComponent['id']
|
||||
|
||||
export interface AuroraRuntimeCatalogRelease {
|
||||
component: AuroraRuntimeComponentId
|
||||
version: string
|
||||
platform: AuroraNativeRuntimeManifest['platform']
|
||||
arch: AuroraNativeRuntimeManifest['arch']
|
||||
channel: 'stable' | 'security'
|
||||
url: string
|
||||
sha256: string
|
||||
}
|
||||
|
||||
export interface AuroraRuntimeCatalog {
|
||||
schema: 1
|
||||
generatedAt: string
|
||||
releases: AuroraRuntimeCatalogRelease[]
|
||||
}
|
||||
|
||||
export interface AuroraRuntimeUpdate {
|
||||
component: AuroraRuntimeComponentId
|
||||
installedVersion: string
|
||||
availableVersion: string
|
||||
channel: AuroraRuntimeCatalogRelease['channel']
|
||||
}
|
||||
|
||||
export interface AuroraRuntimeUpdateStatus {
|
||||
checkedAt: string
|
||||
source: 'bundled' | 'remote' | 'cache'
|
||||
updates: AuroraRuntimeUpdate[]
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface AuroraProjectSummary {
|
||||
name: string
|
||||
status: ProjectStatus
|
||||
|
||||
Reference in New Issue
Block a user