feat: integrate StarLight updates and improve font settings and skin editor loading
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled

This commit is contained in:
2026-09-19 18:14:41 +08:00
parent a47d309103
commit a477b1fb9a
24 changed files with 665 additions and 74 deletions

View File

@ -62,11 +62,42 @@ for (const target of targets) {
}
}
function digest(asset) {
if (typeof asset.digest !== 'string' || !asset.digest.startsWith('sha256:')) {
throw new Error(`Release asset ${asset.name} has no SHA-256 digest`)
}
return asset.digest.slice('sha256:'.length)
}
const apt = {}
for (const target of [
{ platform: 'linux-x86_64', assetSuffix: '_amd64.deb' },
{ platform: 'linux-aarch64', assetSuffix: '_arm64.deb' },
]) {
const matches = assets.filter((asset) => asset.name?.endsWith(target.assetSuffix))
if (matches.length !== 1) {
throw new Error(
`Expected one release asset ending in ${target.assetSuffix}, found ${matches.length}`,
)
}
const asset = matches[0]
const url = asset.browser_download_url ?? asset.url
if (!url || !Number.isSafeInteger(asset.size) || asset.size <= 0) {
throw new Error(`Release asset ${asset.name} has invalid download metadata`)
}
apt[target.platform] = {
url,
sha256: digest(asset),
size: asset.size,
}
}
const manifest = {
version: tag.replace(/^v/, ''),
notes: release.body ?? '',
pub_date: new Date().toISOString(),
platforms,
apt,
}
fs.writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}\n`)

View File

@ -0,0 +1,70 @@
import assert from 'node:assert/strict'
import crypto from 'node:crypto'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { spawnSync } from 'node:child_process'
const root = path.resolve(import.meta.dirname, '..', '..')
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'starlight-update-manifest-'))
const releasePath = path.join(directory, 'release.json')
const signaturesPath = path.join(directory, 'signatures')
const outputPath = path.join(directory, 'latest.json')
const tag = 'v1.9.7'
const updaterAssets = [
'Axolotl_Launcher_universal.app.tar.gz',
'Axolotl_Launcher_1.9.7_aarch64.AppImage.tar.gz',
'Axolotl_Launcher_1.9.7_amd64.AppImage.tar.gz',
'Axolotl_Launcher_1.9.7_x64-setup.nsis.zip',
]
const debAssets = [
'Axolotl_Launcher_1.9.7_amd64.deb',
'Axolotl_Launcher_1.9.7_arm64.deb',
]
try {
fs.mkdirSync(signaturesPath)
for (const name of updaterAssets) {
fs.writeFileSync(path.join(signaturesPath, `${name}.sig`), 'signature'.repeat(8))
}
fs.writeFileSync(
releasePath,
JSON.stringify({
body: '测试版本',
assets: [...updaterAssets, ...debAssets].map((name, index) => ({
name,
size: index + 1024,
digest: `sha256:${crypto.createHash('sha256').update(name).digest('hex')}`,
browser_download_url: `https://github.com/Mystic-Stars/Axolotl/releases/download/${tag}/${name}`,
})),
}),
)
const create = spawnSync(
process.execPath,
[
'scripts/axolotl/create-update-manifest.mjs',
releasePath,
signaturesPath,
tag,
outputPath,
],
{ cwd: root, encoding: 'utf8' },
)
assert.equal(create.status, 0, create.stderr)
const manifest = JSON.parse(fs.readFileSync(outputPath, 'utf8'))
assert.equal(manifest.version, '1.9.7')
assert.deepEqual(Object.keys(manifest.apt).sort(), ['linux-aarch64', 'linux-x86_64'])
assert.equal(manifest.apt['linux-x86_64'].size, 1028)
assert.match(manifest.apt['linux-aarch64'].sha256, /^[0-9a-f]{64}$/)
const verify = spawnSync(
process.execPath,
['scripts/axolotl/verify-update-manifest.mjs', outputPath, tag],
{ cwd: root, encoding: 'utf8' },
)
assert.equal(verify.status, 0, verify.stderr)
} finally {
fs.rmSync(directory, { recursive: true, force: true })
}

View File

@ -0,0 +1,49 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { runInNewContext } from 'node:vm'
const source = readFileSync(
new URL('../../apps/app/src/skin_editor_bridge.js', import.meta.url),
'utf8',
)
function createFrame(url = 'http://axolotl-skin.localhost/index.html?embed=skin') {
const listeners = new Map()
const messages = []
const window = {
parent: { postMessage: (message) => messages.push(message) },
addEventListener: (type, handler) => listeners.set(type, handler),
}
runInNewContext(source, { window, location: new URL(url), URLSearchParams })
return { window, listeners, messages }
}
test('reports startup exceptions to the launcher', () => {
const frame = createFrame()
frame.listeners.get('error')({ message: 'ReferenceError: missing editor dependency' })
assert.equal(frame.messages[0].type, 'axolotl-skin-load-error')
assert.match(frame.messages[0].error, /missing editor dependency/)
})
test('reports rejected module imports even when the editor handles the rejection', async () => {
const frame = createFrame()
frame.window.blockbenchBundleReady = Promise.reject(new Error('Failed to fetch editor module'))
frame.listeners.get('DOMContentLoaded')()
await Promise.resolve()
assert.equal(frame.messages[0].error, 'Failed to fetch editor module')
})
test('does not install the bridge on unrelated pages', () => {
for (const url of ['https://skin.starlight.cool/', 'http://axolotl-skin.localhost/index.html']) {
assert.equal(createFrame(url).listeners.size, 0)
}
})
test('ignores resize observer notifications', () => {
const frame = createFrame()
frame.listeners.get('error')({
message: 'ResizeObserver loop completed with undelivered notifications.',
})
assert.equal(frame.messages.length, 0)
})

View File

@ -42,4 +42,22 @@ for (const platform of requiredPlatforms) {
}
}
for (const platform of ['linux-aarch64', 'linux-x86_64']) {
const artifact = manifest.apt?.[platform]
if (
!artifact ||
typeof artifact.sha256 !== 'string' ||
!/^[0-9a-f]{64}$/i.test(artifact.sha256) ||
!Number.isSafeInteger(artifact.size) ||
artifact.size <= 0
) {
throw new Error(`Missing Debian update for ${platform}`)
}
const url = new URL(artifact.url)
if (url.protocol !== 'https:') {
throw new Error(`Unexpected Debian update URL for ${platform}: ${artifact.url}`)
}
}
console.log(`Verified signed ${source} updater manifest for ${expectedVersion}`)