From 2bd66ffe198208754c19365337e3c66bb53641a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 20 Jun 2026 23:26:13 +0900 Subject: [PATCH] fix: guard packaged AstrBot runtime version --- scripts/prepare-resources.mjs | 5 ++ scripts/prepare-resources/version-sync.mjs | 31 ++++++++++ .../prepare-resources/version-sync.test.mjs | 57 +++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index a183c4d5..2e98fd59 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import { readAstrbotVersionFromPyproject, syncDesktopVersionFiles, + validateAstrbotRuntimeVersion, } from './prepare-resources/version-sync.mjs'; import { ensureSourceRepo, @@ -60,6 +61,10 @@ const main = async () => { const astrbotVersion = desktopVersionOverride || (await readAstrbotVersionFromPyproject({ sourceDir })); + if (!desktopVersionOverride) { + await validateAstrbotRuntimeVersion({ sourceDir, expectedVersion: astrbotVersion }); + } + if (desktopVersionOverride && needsSourceRepo) { const sourceVersion = await readAstrbotVersionFromPyproject({ sourceDir }); if (sourceVersion !== desktopVersionOverride) { diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 0dc62b8d..29d3e335 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -49,6 +49,37 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { throw new Error(`Cannot resolve [project].version from ${pyprojectPath}`); }; +export const readAstrbotRuntimeVersion = async ({ sourceDir }) => { + const defaultConfigPath = path.join(sourceDir, 'astrbot', 'core', 'config', 'default.py'); + if (!existsSync(defaultConfigPath)) { + throw new Error(`Cannot find AstrBot runtime version file: ${defaultConfigPath}`); + } + + const content = await readFile(defaultConfigPath, 'utf8'); + const match = /^\s*VERSION\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m.exec(content); + if (!match) { + throw new Error(`Cannot resolve AstrBot runtime VERSION from ${defaultConfigPath}`); + } + + return match[1].trim(); +}; + +export const validateAstrbotRuntimeVersion = async ({ sourceDir, expectedVersion }) => { + const runtimeVersion = await readAstrbotRuntimeVersion({ sourceDir }); + if (runtimeVersion === '0.0.0') { + throw new Error( + `AstrBot runtime VERSION resolved to 0.0.0 in ${sourceDir}. Use an AstrBot source ref that contains the static runtime VERSION fix.`, + ); + } + if (runtimeVersion !== expectedVersion) { + throw new Error( + `AstrBot version mismatch in ${sourceDir}: pyproject.toml has ${expectedVersion}, but runtime VERSION is ${runtimeVersion}.`, + ); + } + + return runtimeVersion; +}; + const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const CARGO_LOCK_PACKAGE_HEADER = /^\s*\[\[package\]\]\s*(?:#.*)?$/; const CARGO_LOCK_ANY_HEADER = /^\s*\[\[/; diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index 9fb75049..98adfe4c 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -7,8 +7,10 @@ import assert from 'node:assert/strict'; import { DESKTOP_TAURI_CRATE_NAME, normalizeDesktopVersionOverride, + readAstrbotRuntimeVersion, readAstrbotVersionFromPyproject, syncDesktopVersionFiles, + validateAstrbotRuntimeVersion, } from './version-sync.mjs'; const createTempDesktopProject = async ({ cargoLockContents, version = '0.1.0' }) => { @@ -39,6 +41,23 @@ const createTempDesktopProject = async ({ cargoLockContents, version = '0.1.0' } return { tempDir, srcTauriDir }; }; +const createTempAstrBotSource = async ({ pyprojectVersion = '1.2.3', runtimeVersion = '1.2.3' }) => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-source-')); + const configDir = path.join(tempDir, 'astrbot', 'core', 'config'); + await mkdir(configDir, { recursive: true }); + await writeFile( + path.join(tempDir, 'pyproject.toml'), + `[project]\nname = "AstrBot"\nversion = "${pyprojectVersion}"\n`, + 'utf8', + ); + await writeFile( + path.join(configDir, 'default.py'), + `import os\n\nVERSION = "${runtimeVersion}"\n`, + 'utf8', + ); + return tempDir; +}; + test('normalizeDesktopVersionOverride trims and strips leading v', () => { assert.equal(normalizeDesktopVersionOverride(' v1.2.3 '), '1.2.3'); assert.equal(normalizeDesktopVersionOverride('2.0.0'), '2.0.0'); @@ -68,6 +87,44 @@ version = "1.9.1" } }); +test('readAstrbotRuntimeVersion reads static VERSION from default.py', async () => { + const tempDir = await createTempAstrBotSource({ runtimeVersion: '4.26.0-beta.10' }); + try { + const version = await readAstrbotRuntimeVersion({ sourceDir: tempDir }); + assert.equal(version, '4.26.0-beta.10'); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); + +test('validateAstrbotRuntimeVersion rejects 0.0.0 runtime version', async () => { + const tempDir = await createTempAstrBotSource({ runtimeVersion: '0.0.0' }); + try { + await assert.rejects( + validateAstrbotRuntimeVersion({ sourceDir: tempDir, expectedVersion: '4.26.0-beta.10' }), + /runtime VERSION resolved to 0\.0\.0/, + ); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); + +test('validateAstrbotRuntimeVersion rejects runtime version drift', async () => { + const tempDir = await createTempAstrBotSource({ + pyprojectVersion: '4.26.0-beta.10', + runtimeVersion: '4.26.0-beta.9', + }); + try { + const expectedVersion = await readAstrbotVersionFromPyproject({ sourceDir: tempDir }); + await assert.rejects( + validateAstrbotRuntimeVersion({ sourceDir: tempDir, expectedVersion }), + /pyproject\.toml has 4\.26\.0-beta\.10, but runtime VERSION is 4\.26\.0-beta\.9/, + ); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); + test('syncDesktopVersionFiles updates package.json, tauri.conf.json, Cargo.toml and Cargo.lock', async () => { let tempDir; let srcTauriDir;