From a36c3155976e757163f6d4bb78f0fe3a54c9deb1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:13:30 +0000 Subject: [PATCH 01/11] chore(version): sync desktop version to v4.20.1 --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2af997e..dfd3a53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "astrbot-desktop-tauri", - "version": "4.20.0", + "version": "4.20.1", "description": "AstrBot desktop shell powered by Tauri", "private": true, "packageManager": "pnpm@10.28.2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1604293..aa260f1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astrbot-desktop-tauri" -version = "4.20.0" +version = "4.20.1" description = "AstrBot desktop shell powered by Tauri" authors = ["AstrBot"] license = "AGPL-3.0" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7c42dee..d09e89e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "AstrBot", - "version": "4.20.0", + "version": "4.20.1", "identifier": "com.astrbot.desktop.tauri", "build": { "beforeDevCommand": "", From a98f153980034da86b1e8f00c9e20c35b82e5ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 21:57:29 +0900 Subject: [PATCH 02/11] ci: add custom source-ref desktop builds --- .github/workflows/build-desktop-tauri.yml | 10 +++--- scripts/ci/fixtures/fake-git.sh | 7 +++++ scripts/ci/resolve-build-context.sh | 37 +++++++++++++++++++++-- scripts/ci/resolve-build-context.test.mjs | 35 +++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-desktop-tauri.yml b/.github/workflows/build-desktop-tauri.yml index 9b05a58..ba18835 100644 --- a/.github/workflows/build-desktop-tauri.yml +++ b/.github/workflows/build-desktop-tauri.yml @@ -8,7 +8,7 @@ on: required: false default: https://github.com/AstrBotDevs/AstrBot.git source_git_ref: - description: Optional source ref override for `tag-poll` (branch/tag/commit SHA). Ignored in `nightly`. + description: Optional source ref override for `tag-poll` or required explicit source ref for `custom` (branch/tag/commit SHA). required: false default: "" publish_release: @@ -18,14 +18,15 @@ on: default: true build_mode: description: >- - Build mode (`tag-poll` | `nightly`): `nightly` (default) always builds latest upstream commit, - `tag-poll` builds latest upstream tag (or `source_git_ref` override) + Build mode (`tag-poll` | `nightly` | `custom`): `nightly` (default) always builds latest upstream commit, + `tag-poll` builds latest upstream tag (or `source_git_ref` override), `custom` builds the explicit `source_git_ref` required: false type: choice default: nightly options: - tag-poll - nightly + - custom schedule: # Hourly tag-poll (exclude the dedicated nightly window at 03:xx UTC). - cron: '0 0-2,4-23 * * *' @@ -551,6 +552,7 @@ jobs: python3 scripts/ci/validate-release-artifacts.py release-artifacts - name: Generate Tauri updater manifest + if: ${{ needs.resolve_build_context.outputs.build_mode != 'custom' }} env: RELEASE_TAG: ${{ needs.resolve_build_context.outputs.release_tag }} RELEASE_VERSION: ${{ needs.resolve_build_context.outputs.astrbot_version }} @@ -600,7 +602,7 @@ jobs: fail_on_unmatched_files: true - name: Demote previous prerelease marker - if: ${{ needs.resolve_build_context.outputs.release_prerelease == 'true' }} + if: ${{ needs.resolve_build_context.outputs.release_prerelease == 'true' && needs.resolve_build_context.outputs.build_mode == 'nightly' }} env: GH_TOKEN: ${{ github.token }} CURRENT_RELEASE_TAG: ${{ needs.resolve_build_context.outputs.release_tag }} diff --git a/scripts/ci/fixtures/fake-git.sh b/scripts/ci/fixtures/fake-git.sh index 411676a..71956f5 100644 --- a/scripts/ci/fixtures/fake-git.sh +++ b/scripts/ci/fixtures/fake-git.sh @@ -51,6 +51,13 @@ case "${command_name}" in version = "${ASTRBOT_TEST_FETCHED_VERSION:-4.19.0}" EOF ;; + rev-parse) + if [ "${1-}" != "HEAD" ]; then + printf 'unexpected git rev-parse arg: %s\n' "${1-}" >&2 + exit 1 + fi + printf '%s\n' "${ASTRBOT_TEST_FETCHED_SHA:-3333333333333333333333333333333333333333}" + ;; *) printf 'unexpected git command: %s %s\n' "${command_name}" "$*" >&2 exit 1 diff --git a/scripts/ci/resolve-build-context.sh b/scripts/ci/resolve-build-context.sh index 323041b..e59e5ab 100755 --- a/scripts/ci/resolve-build-context.sh +++ b/scripts/ci/resolve-build-context.sh @@ -179,10 +179,10 @@ workflow_source_git_ref_provided="false" latest_upstream_tag="" case "${requested_build_mode}" in - auto|tag-poll|nightly) ;; + auto|tag-poll|nightly|custom) ;; *) if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - echo "::error::invalid build_mode input '${requested_build_mode}'; expected tag-poll/nightly (auto is deprecated but still accepted and normalized to tag-poll for backward compatibility)." + echo "::error::invalid build_mode input '${requested_build_mode}'; expected tag-poll/nightly/custom (auto is deprecated but still accepted and normalized to tag-poll for backward compatibility)." else echo "::error::invalid build_mode input '${requested_build_mode}'; expected auto/tag-poll/nightly." fi @@ -234,12 +234,22 @@ case "${GITHUB_EVENT_NAME}" in else build_mode="${requested_build_mode}" fi + if [ "${build_mode}" = "custom" ] && [ "${workflow_source_git_ref_provided}" != "true" ]; then + echo "::error::workflow_dispatch custom mode requires source_git_ref." >&2 + exit 1 + fi if [ "${build_mode}" = "tag-poll" ]; then echo "::notice::workflow_dispatch tag-poll selected. Prefer schedule runs for routine tag polling." + elif [ "${build_mode}" = "custom" ]; then + echo "::notice::workflow_dispatch custom mode selected. Build will use the explicit source ref override." fi ;; schedule) publish_release="true" + if [ "${requested_build_mode}" = "custom" ]; then + echo "::error::schedule runs do not support build_mode=custom." >&2 + exit 1 + fi if [ "${requested_build_mode}" = "auto" ]; then if [ -n "${event_schedule_raw}" ] && [ -n "${nightly_schedule_cron}" ]; then if cron_expressions_match "${event_schedule_raw}" "${nightly_schedule_cron}"; then @@ -275,6 +285,10 @@ case "${GITHUB_EVENT_NAME}" in fi ;; *) + if [ "${requested_build_mode}" = "custom" ]; then + echo "::error::${GITHUB_EVENT_NAME} runs do not support build_mode=custom." >&2 + exit 1 + fi if [ "${requested_build_mode}" = "auto" ]; then build_mode="tag-poll" echo "::notice::${GITHUB_EVENT_NAME} build_mode=auto normalized to tag-poll." @@ -378,6 +392,15 @@ if [ "${should_build}" = "true" ]; then git -C "${repo_dir}" remote add origin "${source_git_url}" git -C "${repo_dir}" fetch --depth 1 origin "${source_git_ref}" git -C "${repo_dir}" checkout --detach FETCH_HEAD + if [ "${build_mode}" = "custom" ]; then + resolved_source_sha="$(git -C "${repo_dir}" rev-parse HEAD)" + if [ -z "${resolved_source_sha}" ]; then + echo "Unable to resolve a pinned commit SHA for custom source ref '${source_git_ref}'." >&2 + exit 1 + fi + echo "Custom source ref ${source_git_ref} resolved to ${resolved_source_sha}." + source_git_ref="${resolved_source_sha}" + fi version="$(python3 scripts/ci/read-project-version.py "${repo_dir}/pyproject.toml")" fi else @@ -396,6 +419,16 @@ if [ "${build_mode}" = "nightly" ] && [ "${should_build}" = "true" ]; then release_tag="nightly" release_name="AstrBot Desktop v${base_version}-nightly-${short_sha}" release_prerelease="true" +elif [ "${build_mode}" = "custom" ] && [ "${should_build}" = "true" ]; then + base_version="${version}" + custom_date="$(date -u +%Y%m%d)" + short_sha="$(printf '%s' "${source_git_ref}" | cut -c1-8)" + version="${version}-custom.${custom_date}.${short_sha}" + if [ "${publish_release}" = "true" ]; then + release_tag="custom-${custom_date}-${short_sha}" + release_name="AstrBot Desktop v${base_version}-custom-${short_sha}" + release_prerelease="true" + fi elif [ "${publish_release}" = "true" ] && [ "${should_build}" = "true" ]; then release_tag="v${version}" release_name="AstrBot Desktop v${version}" diff --git a/scripts/ci/resolve-build-context.test.mjs b/scripts/ci/resolve-build-context.test.mjs index 5b840c5..13a4c46 100644 --- a/scripts/ci/resolve-build-context.test.mjs +++ b/scripts/ci/resolve-build-context.test.mjs @@ -48,6 +48,15 @@ const makeNightlyEnv = (overrides = {}) => ({ ...overrides, }); +const makeCustomEnv = (overrides = {}) => ({ + ...baseEnv, + WORKFLOW_BUILD_MODE: 'custom', + WORKFLOW_PUBLISH_RELEASE: 'true', + ASTRBOT_TEST_FETCHED_VERSION: '4.19.0', + ASTRBOT_TEST_FETCHED_SHA: '4444444444444444444444444444444444444444', + ...overrides, +}); + const parseGithubOutput = async (outputPath) => { const raw = await readFile(outputPath, 'utf8'); const entries = raw @@ -197,3 +206,29 @@ test('workflow_dispatch nightly never marks latest', async () => { assert.equal(outputs.release_prerelease, 'true'); assert.equal(outputs.release_make_latest, 'false'); }); + +test('workflow_dispatch custom resolves explicit source ref to a pinned commit SHA', async () => { + const { result, outputs } = await runResolveBuildContext(makeCustomEnv({ + WORKFLOW_SOURCE_GIT_REF: 'fix/windows-packaged-pip-build-env', + })); + + assert.equal(result.status, 0, result.stderr); + assert.equal(outputs.build_mode, 'custom'); + assert.equal(outputs.source_git_ref, '4444444444444444444444444444444444444444'); + assert.match( + outputs.astrbot_version, + /^4\.19\.0-custom\.\d{8}\.44444444$/, + ); + assert.equal(outputs.release_prerelease, 'true'); + assert.equal(outputs.release_make_latest, 'false'); + assert.match(outputs.release_tag, /^custom-\d{8}-44444444$/); +}); + +test('workflow_dispatch custom requires an explicit source ref', async () => { + const { result } = await runResolveBuildContext(makeCustomEnv({ + WORKFLOW_SOURCE_GIT_REF: '', + })); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, /workflow_dispatch custom mode requires source_git_ref/i); +}); From ea9650d23d975b585e6e585be2ca39932aa86b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:04:27 +0900 Subject: [PATCH 03/11] fix(ci): sync Cargo.lock during version bumps --- .github/workflows/build-desktop-tauri.yml | 4 ++-- scripts/prepare-resources/version-sync.mjs | 14 ++++++++++++ .../prepare-resources/version-sync.test.mjs | 22 ++++++++++++++++++- src-tauri/Cargo.lock | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-desktop-tauri.yml b/.github/workflows/build-desktop-tauri.yml index ba18835..392a7ab 100644 --- a/.github/workflows/build-desktop-tauri.yml +++ b/.github/workflows/build-desktop-tauri.yml @@ -131,7 +131,7 @@ jobs: run: | set -euo pipefail - changed_files="$(git status --porcelain -- package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json)" + changed_files="$(git status --porcelain -- package.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json)" if [ -z "${changed_files}" ]; then echo "Version files are already up to date. Nothing to commit." exit 0 @@ -139,7 +139,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json + git add package.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json git commit -m "chore(version): sync desktop version to v${ASTRBOT_VERSION}" git fetch origin "${TARGET_REF_NAME}" diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 286dcf4..7143065 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -51,6 +51,7 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { const packageJsonPath = path.join(projectRoot, 'package.json'); const tauriConfigPath = path.join(projectRoot, 'src-tauri', 'tauri.conf.json'); const cargoTomlPath = path.join(projectRoot, 'src-tauri', 'Cargo.toml'); + const cargoLockPath = path.join(projectRoot, 'src-tauri', 'Cargo.lock'); const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); if (packageJson.version !== version) { @@ -73,4 +74,17 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { if (updatedCargoToml !== cargoToml) { await writeFile(cargoTomlPath, updatedCargoToml, 'utf8'); } + + if (existsSync(cargoLockPath)) { + const cargoLock = await readFile(cargoLockPath, 'utf8'); + const cargoLockVersionPattern = + /(\[\[package\]\]\s+name = "astrbot-desktop-tauri"\s+version = ")[^"]+(")/m; + if (!cargoLockVersionPattern.test(cargoLock)) { + throw new Error(`Cannot update Cargo.lock package version in ${cargoLockPath}`); + } + const updatedCargoLock = cargoLock.replace(cargoLockVersionPattern, `$1${version}$2`); + if (updatedCargoLock !== cargoLock) { + await writeFile(cargoLockPath, updatedCargoLock, 'utf8'); + } + } }; diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index cfc165b..9d8ca79 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -39,7 +39,7 @@ version = "1.9.1" } }); -test('syncDesktopVersionFiles updates package.json, tauri.conf.json and Cargo.toml', async () => { +test('syncDesktopVersionFiles updates package.json, tauri.conf.json, Cargo.toml and Cargo.lock', async () => { const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); try { const srcTauriDir = path.join(tempDir, 'src-tauri'); @@ -60,16 +60,36 @@ test('syncDesktopVersionFiles updates package.json, tauri.conf.json and Cargo.to `[package]\nname = "astrbot-desktop-tauri"\nversion = "0.1.0"\n`, 'utf8', ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] +name = "astrbot-desktop-tauri" +version = "0.1.0" + +[[package]] +name = "dep" +version = "9.9.9" +`, + 'utf8', + ); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); const packageJson = JSON.parse(await readFile(path.join(tempDir, 'package.json'), 'utf8')); const tauriConfig = JSON.parse(await readFile(path.join(srcTauriDir, 'tauri.conf.json'), 'utf8')); const cargoToml = await readFile(path.join(srcTauriDir, 'Cargo.toml'), 'utf8'); + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); assert.equal(packageJson.version, '2.3.4'); assert.equal(tauriConfig.version, '2.3.4'); assert.match(cargoToml, /version\s*=\s*"2.3.4"/); + assert.match( + cargoLock, + /\[\[package\]\]\nname = "astrbot-desktop-tauri"\nversion = "2.3.4"/, + ); + assert.match(cargoLock, /\[\[package\]\]\nname = "dep"\nversion = "9.9.9"/); } finally { await rm(tempDir, { recursive: true, force: true }); } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4625301..6e1867b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -58,7 +58,7 @@ dependencies = [ [[package]] name = "astrbot-desktop-tauri" -version = "4.20.0" +version = "4.20.1" dependencies = [ "chrono", "home", From 50b7f86f9ddac9066b7b7f3e943c7573399fa9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:08:56 +0900 Subject: [PATCH 04/11] fix(ci): relax Cargo.lock version sync parsing --- scripts/prepare-resources/version-sync.mjs | 39 +++++++++++-- .../prepare-resources/version-sync.test.mjs | 58 +++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 7143065..7078f69 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -47,6 +47,34 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { throw new Error(`Cannot resolve [project].version from ${pyprojectPath}`); }; +const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { + const blockPattern = /(^|\n)(\[\[package\]\][\s\S]*?)(?=\n\[\[package\]\]|\s*$)/g; + + let foundTargetPackage = false; + let foundTargetVersion = false; + + const updatedCargoLock = cargoLock.replace(blockPattern, (match, prefix, block) => { + if (!new RegExp(`^name = "${packageName}"$`, 'm').test(block)) { + return match; + } + + foundTargetPackage = true; + const versionPattern = /^version = "[^"]+"$/m; + if (!versionPattern.test(block)) { + return match; + } + foundTargetVersion = true; + const updatedBlock = block.replace(versionPattern, `version = "${version}"`); + return `${prefix}${updatedBlock}`; + }); + + if (!foundTargetPackage || !foundTargetVersion) { + throw new Error(`Cannot update Cargo.lock package version for ${packageName}`); + } + + return updatedCargoLock; +}; + export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { const packageJsonPath = path.join(projectRoot, 'package.json'); const tauriConfigPath = path.join(projectRoot, 'src-tauri', 'tauri.conf.json'); @@ -77,12 +105,11 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { if (existsSync(cargoLockPath)) { const cargoLock = await readFile(cargoLockPath, 'utf8'); - const cargoLockVersionPattern = - /(\[\[package\]\]\s+name = "astrbot-desktop-tauri"\s+version = ")[^"]+(")/m; - if (!cargoLockVersionPattern.test(cargoLock)) { - throw new Error(`Cannot update Cargo.lock package version in ${cargoLockPath}`); - } - const updatedCargoLock = cargoLock.replace(cargoLockVersionPattern, `$1${version}$2`); + const updatedCargoLock = updateCargoLockPackageVersion({ + cargoLock, + packageName: 'astrbot-desktop-tauri', + version, + }); if (updatedCargoLock !== cargoLock) { await writeFile(cargoLockPath, updatedCargoLock, 'utf8'); } diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index 9d8ca79..5ea00ab 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -94,3 +94,61 @@ version = "9.9.9" await rm(tempDir, { recursive: true, force: true }); } }); + +test('syncDesktopVersionFiles tolerates extra Cargo.lock fields between package name and version', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "astrbot-desktop-tauri"\nversion = "0.1.0"\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] +name = "astrbot-desktop-tauri" +source = "path+file:///workspace/src-tauri" +version = "0.1.0" +dependencies = [ + "dep", +] + +[[package]] +name = "dep" +version = "9.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +`, + 'utf8', + ); + + await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); + + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); + + assert.match( + cargoLock, + /\[\[package\]\]\nname = "astrbot-desktop-tauri"\nsource = "path\+file:\/\/\/workspace\/src-tauri"\nversion = "2.3.4"/, + ); + assert.match( + cargoLock, + /\[\[package\]\]\nname = "dep"\nversion = "9.9.9"\nsource = "registry\+https:\/\/github.com\/rust-lang\/crates.io-index"/, + ); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); From 9d003a24bbdd055eb3fa65be0d4bd53212b2bc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:16:14 +0900 Subject: [PATCH 05/11] fix(ci): harden version sync test fixtures --- scripts/ci/fixtures/fake-git.sh | 4 +- scripts/ci/resolve-build-context.test.mjs | 20 ++++++ scripts/prepare-resources/version-sync.mjs | 66 +++++++++++++++---- .../prepare-resources/version-sync.test.mjs | 66 +++++++++++++++++-- 4 files changed, 135 insertions(+), 21 deletions(-) diff --git a/scripts/ci/fixtures/fake-git.sh b/scripts/ci/fixtures/fake-git.sh index 71956f5..854a3ea 100644 --- a/scripts/ci/fixtures/fake-git.sh +++ b/scripts/ci/fixtures/fake-git.sh @@ -52,8 +52,8 @@ version = "${ASTRBOT_TEST_FETCHED_VERSION:-4.19.0}" EOF ;; rev-parse) - if [ "${1-}" != "HEAD" ]; then - printf 'unexpected git rev-parse arg: %s\n' "${1-}" >&2 + if [ -z "${1-}" ]; then + printf 'git rev-parse expected a ref argument\n' >&2 exit 1 fi printf '%s\n' "${ASTRBOT_TEST_FETCHED_SHA:-3333333333333333333333333333333333333333}" diff --git a/scripts/ci/resolve-build-context.test.mjs b/scripts/ci/resolve-build-context.test.mjs index 13a4c46..aeefbac 100644 --- a/scripts/ci/resolve-build-context.test.mjs +++ b/scripts/ci/resolve-build-context.test.mjs @@ -232,3 +232,23 @@ test('workflow_dispatch custom requires an explicit source ref', async () => { assert.notEqual(result.status, 0); assert.match(result.stderr, /workflow_dispatch custom mode requires source_git_ref/i); }); + +test('fake git rev-parse returns the configured SHA for arbitrary refs', async () => { + await withSandbox( + { + ...baseEnv, + ASTRBOT_TEST_FETCHED_SHA: '5555555555555555555555555555555555555555', + }, + async (sandbox) => { + const gitPath = path.join(sandbox.tempDir, 'bin', 'git'); + const repoDir = path.join(sandbox.tempDir, 'repo'); + const result = spawnSync(gitPath, ['-C', repoDir, 'rev-parse', 'FETCH_HEAD'], { + encoding: 'utf8', + env: sandbox.env, + }); + + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stdout.trim(), '5555555555555555555555555555555555555555'); + }, + ); +}); diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 7078f69..f465efe 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -2,6 +2,8 @@ import { existsSync } from 'node:fs'; import { readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; +export const DESKTOP_TAURI_CRATE_NAME = 'astrbot-desktop-tauri'; + export const normalizeDesktopVersionOverride = (version) => { const trimmed = typeof version === 'string' ? version.trim() : ''; if (!trimmed) { @@ -47,32 +49,70 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { throw new Error(`Cannot resolve [project].version from ${pyprojectPath}`); }; +const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { - const blockPattern = /(^|\n)(\[\[package\]\][\s\S]*?)(?=\n\[\[package\]\]|\s*$)/g; + const packageHeaderPattern = /^\s*\[\[package\]\]\s*(?:#.*)?$/; + const packageNamePattern = new RegExp( + `^\\s*name\\s*=\\s*"${escapeRegExp(packageName)}"\\s*(?:#.*)?$`, + ); + const packageVersionPattern = /^(\s*version\s*=\s*")[^"]+(")(\s*(?:#.*)?)$/; + const lines = cargoLock.split(/\r?\n/); let foundTargetPackage = false; let foundTargetVersion = false; - const updatedCargoLock = cargoLock.replace(blockPattern, (match, prefix, block) => { - if (!new RegExp(`^name = "${packageName}"$`, 'm').test(block)) { - return match; + const updateBlock = (startIndex, endIndex) => { + let packageNameLineIndex = -1; + + for (let index = startIndex + 1; index < endIndex; index += 1) { + if (packageNamePattern.test(lines[index])) { + packageNameLineIndex = index; + break; + } + } + + if (packageNameLineIndex === -1) { + return false; } foundTargetPackage = true; - const versionPattern = /^version = "[^"]+"$/m; - if (!versionPattern.test(block)) { - return match; + + for (let index = packageNameLineIndex + 1; index < endIndex; index += 1) { + if (packageHeaderPattern.test(lines[index])) { + break; + } + if (!packageVersionPattern.test(lines[index])) { + continue; + } + + foundTargetVersion = true; + lines[index] = lines[index].replace(packageVersionPattern, `$1${version}$2$3`); + return true; + } + + return false; + }; + + let blockStartIndex = -1; + for (let index = 0; index <= lines.length; index += 1) { + const isBoundary = index === lines.length || packageHeaderPattern.test(lines[index]); + if (!isBoundary) { + continue; } - foundTargetVersion = true; - const updatedBlock = block.replace(versionPattern, `version = "${version}"`); - return `${prefix}${updatedBlock}`; - }); + + if (blockStartIndex !== -1 && updateBlock(blockStartIndex, index)) { + break; + } + + blockStartIndex = index; + } if (!foundTargetPackage || !foundTargetVersion) { throw new Error(`Cannot update Cargo.lock package version for ${packageName}`); } - return updatedCargoLock; + return lines.join(cargoLock.includes('\r\n') ? '\r\n' : '\n'); }; export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { @@ -107,7 +147,7 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { const cargoLock = await readFile(cargoLockPath, 'utf8'); const updatedCargoLock = updateCargoLockPackageVersion({ cargoLock, - packageName: 'astrbot-desktop-tauri', + packageName: DESKTOP_TAURI_CRATE_NAME, version, }); if (updatedCargoLock !== cargoLock) { diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index 5ea00ab..9c3e787 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -5,6 +5,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { + DESKTOP_TAURI_CRATE_NAME, normalizeDesktopVersionOverride, readAstrbotVersionFromPyproject, syncDesktopVersionFiles, @@ -57,7 +58,7 @@ test('syncDesktopVersionFiles updates package.json, tauri.conf.json, Cargo.toml ); await writeFile( path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "astrbot-desktop-tauri"\nversion = "0.1.0"\n`, + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, 'utf8', ); await writeFile( @@ -65,7 +66,7 @@ test('syncDesktopVersionFiles updates package.json, tauri.conf.json, Cargo.toml `version = 4 [[package]] -name = "astrbot-desktop-tauri" +name = "${DESKTOP_TAURI_CRATE_NAME}" version = "0.1.0" [[package]] @@ -87,7 +88,7 @@ version = "9.9.9" assert.match(cargoToml, /version\s*=\s*"2.3.4"/); assert.match( cargoLock, - /\[\[package\]\]\nname = "astrbot-desktop-tauri"\nversion = "2.3.4"/, + new RegExp(`\\[\\[package\\]\\]\\nname = "${DESKTOP_TAURI_CRATE_NAME}"\\nversion = "2.3.4"`), ); assert.match(cargoLock, /\[\[package\]\]\nname = "dep"\nversion = "9.9.9"/); } finally { @@ -113,7 +114,7 @@ test('syncDesktopVersionFiles tolerates extra Cargo.lock fields between package ); await writeFile( path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "astrbot-desktop-tauri"\nversion = "0.1.0"\n`, + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, 'utf8', ); await writeFile( @@ -121,7 +122,7 @@ test('syncDesktopVersionFiles tolerates extra Cargo.lock fields between package `version = 4 [[package]] -name = "astrbot-desktop-tauri" +name = "${DESKTOP_TAURI_CRATE_NAME}" source = "path+file:///workspace/src-tauri" version = "0.1.0" dependencies = [ @@ -142,7 +143,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" assert.match( cargoLock, - /\[\[package\]\]\nname = "astrbot-desktop-tauri"\nsource = "path\+file:\/\/\/workspace\/src-tauri"\nversion = "2.3.4"/, + new RegExp( + String.raw`\[\[package\]\]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nsource = "path\+file:///workspace/src-tauri"\nversion = "2.3.4"`, + ), ); assert.match( cargoLock, @@ -152,3 +155,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" await rm(tempDir, { recursive: true, force: true }); } }); + +test('syncDesktopVersionFiles preserves trailing Cargo.lock comments and spaces on name/version lines', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] # package header +name = "${DESKTOP_TAURI_CRATE_NAME}" # desktop package +version = "0.1.0" # keep this comment + +[[package]] +name = "dep" +version = "9.9.9" +`, + 'utf8', + ); + + await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); + + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); + + assert.match( + cargoLock, + new RegExp( + String.raw`\[\[package\]\]\s+# package header\nname = "${DESKTOP_TAURI_CRATE_NAME}"\s+# desktop package\nversion = "2.3.4"\s+# keep this comment`, + ), + ); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); From db73c14dae89772d967c9d9aacc6621be94541c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:21:07 +0900 Subject: [PATCH 06/11] fix(ci): tolerate missing Cargo.lock package --- scripts/prepare-resources/version-sync.mjs | 91 +++++++++-------- .../prepare-resources/version-sync.test.mjs | 99 +++++++++++++++++++ 2 files changed, 151 insertions(+), 39 deletions(-) diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index f465efe..c922c61 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -50,6 +50,14 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { }; const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +const CARGO_LOCK_PACKAGE_NOT_FOUND = 'cargo-lock-package-not-found'; +const CARGO_LOCK_VERSION_NOT_FOUND = 'cargo-lock-version-not-found'; + +const createCargoLockUpdateError = (message, code) => { + const error = new Error(message); + error.code = code; + return error; +}; const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { const packageHeaderPattern = /^\s*\[\[package\]\]\s*(?:#.*)?$/; @@ -59,57 +67,53 @@ const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { const packageVersionPattern = /^(\s*version\s*=\s*")[^"]+(")(\s*(?:#.*)?)$/; const lines = cargoLock.split(/\r?\n/); + let inPackageBlock = false; + let inTargetPackage = false; let foundTargetPackage = false; let foundTargetVersion = false; - const updateBlock = (startIndex, endIndex) => { - let packageNameLineIndex = -1; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; - for (let index = startIndex + 1; index < endIndex; index += 1) { - if (packageNamePattern.test(lines[index])) { - packageNameLineIndex = index; - break; - } + if (packageHeaderPattern.test(line)) { + inPackageBlock = true; + inTargetPackage = false; + continue; } - if (packageNameLineIndex === -1) { - return false; + if (!inPackageBlock) { + continue; } - foundTargetPackage = true; - - for (let index = packageNameLineIndex + 1; index < endIndex; index += 1) { - if (packageHeaderPattern.test(lines[index])) { - break; - } - if (!packageVersionPattern.test(lines[index])) { - continue; + if (!inTargetPackage) { + if (packageNamePattern.test(line)) { + inTargetPackage = true; + foundTargetPackage = true; } - - foundTargetVersion = true; - lines[index] = lines[index].replace(packageVersionPattern, `$1${version}$2$3`); - return true; + continue; } - return false; - }; - - let blockStartIndex = -1; - for (let index = 0; index <= lines.length; index += 1) { - const isBoundary = index === lines.length || packageHeaderPattern.test(lines[index]); - if (!isBoundary) { + if (!packageVersionPattern.test(line)) { continue; } - if (blockStartIndex !== -1 && updateBlock(blockStartIndex, index)) { - break; - } + foundTargetVersion = true; + lines[index] = line.replace(packageVersionPattern, `$1${version}$2$3`); + break; + } - blockStartIndex = index; + if (!foundTargetPackage) { + throw createCargoLockUpdateError( + `Cannot update Cargo.lock: package "${packageName}" not found`, + CARGO_LOCK_PACKAGE_NOT_FOUND, + ); } - if (!foundTargetPackage || !foundTargetVersion) { - throw new Error(`Cannot update Cargo.lock package version for ${packageName}`); + if (!foundTargetVersion) { + throw createCargoLockUpdateError( + `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, + CARGO_LOCK_VERSION_NOT_FOUND, + ); } return lines.join(cargoLock.includes('\r\n') ? '\r\n' : '\n'); @@ -145,11 +149,20 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { if (existsSync(cargoLockPath)) { const cargoLock = await readFile(cargoLockPath, 'utf8'); - const updatedCargoLock = updateCargoLockPackageVersion({ - cargoLock, - packageName: DESKTOP_TAURI_CRATE_NAME, - version, - }); + let updatedCargoLock = cargoLock; + try { + updatedCargoLock = updateCargoLockPackageVersion({ + cargoLock, + packageName: DESKTOP_TAURI_CRATE_NAME, + version, + }); + } catch (error) { + if (error?.code === CARGO_LOCK_PACKAGE_NOT_FOUND) { + console.warn(`${cargoLockPath}: ${error.message}. Skipping Cargo.lock version sync.`); + } else { + throw error; + } + } if (updatedCargoLock !== cargoLock) { await writeFile(cargoLockPath, updatedCargoLock, 'utf8'); } diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index 9c3e787..3dc8382 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -206,3 +206,102 @@ version = "9.9.9" await rm(tempDir, { recursive: true, force: true }); } }); + +test('syncDesktopVersionFiles skips Cargo.lock updates when the desktop crate is missing', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + const warnings = []; + const originalWarn = console.warn; + console.warn = (message) => { + warnings.push(String(message)); + }; + + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, + 'utf8', + ); + + const originalCargoLock = `version = 4 + +[[package]] +name = "dep" +version = "9.9.9" +`; + await writeFile(path.join(srcTauriDir, 'Cargo.lock'), originalCargoLock, 'utf8'); + + await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); + + const packageJson = JSON.parse(await readFile(path.join(tempDir, 'package.json'), 'utf8')); + const tauriConfig = JSON.parse(await readFile(path.join(srcTauriDir, 'tauri.conf.json'), 'utf8')); + const cargoToml = await readFile(path.join(srcTauriDir, 'Cargo.toml'), 'utf8'); + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); + + assert.equal(packageJson.version, '2.3.4'); + assert.equal(tauriConfig.version, '2.3.4'); + assert.match(cargoToml, /version\s*=\s*"2.3.4"/); + assert.equal(cargoLock, originalCargoLock); + assert.equal(warnings.length, 1); + assert.match(warnings[0], new RegExp(`package "${DESKTOP_TAURI_CRATE_NAME}" not found`)); + } finally { + console.warn = originalWarn; + await rm(tempDir, { recursive: true, force: true }); + } +}); + +test('syncDesktopVersionFiles throws a specific error when the desktop crate version entry is malformed', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] +name = "${DESKTOP_TAURI_CRATE_NAME}" +source = "path+file:///workspace/src-tauri" +checksum = "unexpected-layout" +`, + 'utf8', + ); + + await assert.rejects( + syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }), + new RegExp( + `version entry for package "${DESKTOP_TAURI_CRATE_NAME}" not found or has an unexpected layout`, + ), + ); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); From 4df7c71c61768de31ac0de09065e38909abfd59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:24:41 +0900 Subject: [PATCH 07/11] refactor(ci): simplify Cargo.lock version sync --- scripts/prepare-resources/version-sync.mjs | 126 +++++++++++++-------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index c922c61..96fb1a0 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -50,73 +50,107 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { }; const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -const CARGO_LOCK_PACKAGE_NOT_FOUND = 'cargo-lock-package-not-found'; -const CARGO_LOCK_VERSION_NOT_FOUND = 'cargo-lock-version-not-found'; +const CARGO_LOCK_PACKAGE_HEADER = /^\s*\[\[package\]\]\s*(?:#.*)?$/; -const createCargoLockUpdateError = (message, code) => { - const error = new Error(message); - error.code = code; - return error; +class CargoLockPackageNotFoundError extends Error { + constructor(packageName) { + super(`Cannot update Cargo.lock: package "${packageName}" not found`); + this.name = 'CargoLockPackageNotFoundError'; + } +} + +const findCargoLockPackageBlocks = (lines) => { + const blocks = []; + let start = null; + + for (let index = 0; index < lines.length; index += 1) { + if (!CARGO_LOCK_PACKAGE_HEADER.test(lines[index])) { + continue; + } + + if (start !== null) { + blocks.push({ start, end: index }); + } + start = index; + } + + if (start !== null) { + blocks.push({ start, end: lines.length }); + } + + return blocks; +}; + +const updateVersionLine = (line, version) => { + const commentIndex = line.indexOf('#'); + const beforeComment = commentIndex === -1 ? line : line.slice(0, commentIndex); + const comment = commentIndex === -1 ? '' : line.slice(commentIndex); + const separatorIndex = beforeComment.indexOf('='); + + if (separatorIndex === -1) { + return null; + } + + const left = beforeComment.slice(0, separatorIndex).trimEnd(); + const right = beforeComment.slice(separatorIndex + 1); + if (!right.trim()) { + return null; + } + + const quote = right.includes("'") ? "'" : '"'; + const trailingWhitespace = beforeComment.match(/\s*$/u)?.[0] ?? ''; + const updatedLine = `${left} = ${quote}${version}${quote}`; + + if (!comment) { + return `${updatedLine}${trailingWhitespace}`; + } + + return `${updatedLine}${trailingWhitespace}${comment}`; }; const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { - const packageHeaderPattern = /^\s*\[\[package\]\]\s*(?:#.*)?$/; - const packageNamePattern = new RegExp( + const lines = cargoLock.split(/\r?\n/); + const packageBlocks = findCargoLockPackageBlocks(lines); + const packageNameLinePattern = new RegExp( `^\\s*name\\s*=\\s*"${escapeRegExp(packageName)}"\\s*(?:#.*)?$`, ); - const packageVersionPattern = /^(\s*version\s*=\s*")[^"]+(")(\s*(?:#.*)?)$/; - const lines = cargoLock.split(/\r?\n/); - let inPackageBlock = false; - let inTargetPackage = false; - let foundTargetPackage = false; - let foundTargetVersion = false; + for (const { start, end } of packageBlocks) { + let packageNameLineIndex = -1; - for (let index = 0; index < lines.length; index += 1) { - const line = lines[index]; + for (let index = start + 1; index < end; index += 1) { + if (!packageNameLinePattern.test(lines[index])) { + continue; + } - if (packageHeaderPattern.test(line)) { - inPackageBlock = true; - inTargetPackage = false; - continue; + packageNameLineIndex = index; + break; } - if (!inPackageBlock) { + if (packageNameLineIndex === -1) { continue; } - if (!inTargetPackage) { - if (packageNamePattern.test(line)) { - inTargetPackage = true; - foundTargetPackage = true; + for (let index = packageNameLineIndex + 1; index < end; index += 1) { + if (!lines[index].trimStart().startsWith('version')) { + continue; } - continue; - } - if (!packageVersionPattern.test(line)) { - continue; - } - - foundTargetVersion = true; - lines[index] = line.replace(packageVersionPattern, `$1${version}$2$3`); - break; - } + const updatedLine = updateVersionLine(lines[index], version); + if (updatedLine === null) { + break; + } - if (!foundTargetPackage) { - throw createCargoLockUpdateError( - `Cannot update Cargo.lock: package "${packageName}" not found`, - CARGO_LOCK_PACKAGE_NOT_FOUND, - ); - } + lines[index] = updatedLine; + return lines.join(cargoLock.includes('\r\n') ? '\r\n' : '\n'); + } - if (!foundTargetVersion) { - throw createCargoLockUpdateError( + throw new Error( `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, - CARGO_LOCK_VERSION_NOT_FOUND, ); } - return lines.join(cargoLock.includes('\r\n') ? '\r\n' : '\n'); + throw new CargoLockPackageNotFoundError(packageName); }; export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { @@ -157,7 +191,7 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { version, }); } catch (error) { - if (error?.code === CARGO_LOCK_PACKAGE_NOT_FOUND) { + if (error instanceof CargoLockPackageNotFoundError) { console.warn(`${cargoLockPath}: ${error.message}. Skipping Cargo.lock version sync.`); } else { throw error; From a05f4a53f0cb47105c77d4121eb458cf0f12f646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:31:20 +0900 Subject: [PATCH 08/11] fix(ci): keep custom releases out of prerelease --- scripts/ci/resolve-build-context.sh | 2 +- scripts/ci/resolve-build-context.test.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/resolve-build-context.sh b/scripts/ci/resolve-build-context.sh index e59e5ab..2552aa4 100755 --- a/scripts/ci/resolve-build-context.sh +++ b/scripts/ci/resolve-build-context.sh @@ -427,7 +427,7 @@ elif [ "${build_mode}" = "custom" ] && [ "${should_build}" = "true" ]; then if [ "${publish_release}" = "true" ]; then release_tag="custom-${custom_date}-${short_sha}" release_name="AstrBot Desktop v${base_version}-custom-${short_sha}" - release_prerelease="true" + release_prerelease="false" fi elif [ "${publish_release}" = "true" ] && [ "${should_build}" = "true" ]; then release_tag="v${version}" diff --git a/scripts/ci/resolve-build-context.test.mjs b/scripts/ci/resolve-build-context.test.mjs index aeefbac..4f88f65 100644 --- a/scripts/ci/resolve-build-context.test.mjs +++ b/scripts/ci/resolve-build-context.test.mjs @@ -219,7 +219,7 @@ test('workflow_dispatch custom resolves explicit source ref to a pinned commit S outputs.astrbot_version, /^4\.19\.0-custom\.\d{8}\.44444444$/, ); - assert.equal(outputs.release_prerelease, 'true'); + assert.equal(outputs.release_prerelease, 'false'); assert.equal(outputs.release_make_latest, 'false'); assert.match(outputs.release_tag, /^custom-\d{8}-44444444$/); }); From e81f36c5090e782af017f6c4c63bc3c5300e09af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:33:03 +0900 Subject: [PATCH 09/11] fix(ci): tighten Cargo.lock version matching --- scripts/prepare-resources/version-sync.mjs | 3 +- .../prepare-resources/version-sync.test.mjs | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 96fb1a0..49d29ef 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -51,6 +51,7 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const CARGO_LOCK_PACKAGE_HEADER = /^\s*\[\[package\]\]\s*(?:#.*)?$/; +const CARGO_LOCK_VERSION_LINE = /^\s*version\s*=/; class CargoLockPackageNotFoundError extends Error { constructor(packageName) { @@ -132,7 +133,7 @@ const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { } for (let index = packageNameLineIndex + 1; index < end; index += 1) { - if (!lines[index].trimStart().startsWith('version')) { + if (!CARGO_LOCK_VERSION_LINE.test(lines[index])) { continue; } diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index 3dc8382..d76f2f5 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -207,6 +207,50 @@ version = "9.9.9" } }); +test('syncDesktopVersionFiles only updates the version key, not similarly prefixed keys', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] +name = "${DESKTOP_TAURI_CRATE_NAME}" +versioned_dep = "keep-me" +version = "0.1.0" +`, + 'utf8', + ); + + await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); + + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); + + assert.match(cargoLock, /versioned_dep = "keep-me"/); + assert.match(cargoLock, /version = "2.3.4"/); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); + test('syncDesktopVersionFiles skips Cargo.lock updates when the desktop crate is missing', async () => { const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); const warnings = []; From b5cbe19f283825422b441e818f6739b96d7ffb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:42:36 +0900 Subject: [PATCH 10/11] refactor(ci): simplify version sync status handling --- scripts/ci/resolve-build-context.sh | 17 +-- scripts/ci/resolve-build-context.test.mjs | 22 ++++ scripts/prepare-resources/version-sync.mjs | 124 ++++++++---------- .../prepare-resources/version-sync.test.mjs | 43 ++++++ 4 files changed, 132 insertions(+), 74 deletions(-) diff --git a/scripts/ci/resolve-build-context.sh b/scripts/ci/resolve-build-context.sh index 2552aa4..05d9228 100755 --- a/scripts/ci/resolve-build-context.sh +++ b/scripts/ci/resolve-build-context.sh @@ -149,6 +149,10 @@ resolve_latest_upstream_tag() { printf '%s\n' "${latest_tag}" } +custom_build_mode_supported_for_event() { + [ "$1" = "workflow_dispatch" ] +} + source_git_url="${ASTRBOT_SOURCE_GIT_URL}" source_git_ref="${ASTRBOT_SOURCE_GIT_REF}" nightly_source_git_ref="${ASTRBOT_NIGHTLY_SOURCE_GIT_REF:-master}" @@ -221,6 +225,11 @@ if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then fi fi +if [ "${requested_build_mode}" = "custom" ] && ! custom_build_mode_supported_for_event "${GITHUB_EVENT_NAME}"; then + echo "::error::${GITHUB_EVENT_NAME} runs do not support build_mode=custom." >&2 + exit 1 +fi + # Normalize build mode in one place to keep behavior explicit and predictable. case "${GITHUB_EVENT_NAME}" in workflow_dispatch) @@ -246,10 +255,6 @@ case "${GITHUB_EVENT_NAME}" in ;; schedule) publish_release="true" - if [ "${requested_build_mode}" = "custom" ]; then - echo "::error::schedule runs do not support build_mode=custom." >&2 - exit 1 - fi if [ "${requested_build_mode}" = "auto" ]; then if [ -n "${event_schedule_raw}" ] && [ -n "${nightly_schedule_cron}" ]; then if cron_expressions_match "${event_schedule_raw}" "${nightly_schedule_cron}"; then @@ -285,10 +290,6 @@ case "${GITHUB_EVENT_NAME}" in fi ;; *) - if [ "${requested_build_mode}" = "custom" ]; then - echo "::error::${GITHUB_EVENT_NAME} runs do not support build_mode=custom." >&2 - exit 1 - fi if [ "${requested_build_mode}" = "auto" ]; then build_mode="tag-poll" echo "::notice::${GITHUB_EVENT_NAME} build_mode=auto normalized to tag-poll." diff --git a/scripts/ci/resolve-build-context.test.mjs b/scripts/ci/resolve-build-context.test.mjs index 4f88f65..84ef501 100644 --- a/scripts/ci/resolve-build-context.test.mjs +++ b/scripts/ci/resolve-build-context.test.mjs @@ -233,6 +233,28 @@ test('workflow_dispatch custom requires an explicit source ref', async () => { assert.match(result.stderr, /workflow_dispatch custom mode requires source_git_ref/i); }); +test('schedule runs reject build_mode=custom', async () => { + const { result } = await runResolveBuildContext({ + ...baseEnv, + GITHUB_EVENT_NAME: 'schedule', + WORKFLOW_BUILD_MODE: 'custom', + }); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, /schedule runs do not support build_mode=custom/i); +}); + +test('non-workflow_dispatch runs reject build_mode=custom', async () => { + const { result } = await runResolveBuildContext({ + ...baseEnv, + GITHUB_EVENT_NAME: 'push', + WORKFLOW_BUILD_MODE: 'custom', + }); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, /push runs do not support build_mode=custom/i); +}); + test('fake git rev-parse returns the configured SHA for arbitrary refs', async () => { await withSandbox( { diff --git a/scripts/prepare-resources/version-sync.mjs b/scripts/prepare-resources/version-sync.mjs index 49d29ef..0dc62b8 100644 --- a/scripts/prepare-resources/version-sync.mjs +++ b/scripts/prepare-resources/version-sync.mjs @@ -51,36 +51,9 @@ export const readAstrbotVersionFromPyproject = async ({ sourceDir }) => { const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const CARGO_LOCK_PACKAGE_HEADER = /^\s*\[\[package\]\]\s*(?:#.*)?$/; +const CARGO_LOCK_ANY_HEADER = /^\s*\[\[/; const CARGO_LOCK_VERSION_LINE = /^\s*version\s*=/; - -class CargoLockPackageNotFoundError extends Error { - constructor(packageName) { - super(`Cannot update Cargo.lock: package "${packageName}" not found`); - this.name = 'CargoLockPackageNotFoundError'; - } -} - -const findCargoLockPackageBlocks = (lines) => { - const blocks = []; - let start = null; - - for (let index = 0; index < lines.length; index += 1) { - if (!CARGO_LOCK_PACKAGE_HEADER.test(lines[index])) { - continue; - } - - if (start !== null) { - blocks.push({ start, end: index }); - } - start = index; - } - - if (start !== null) { - blocks.push({ start, end: lines.length }); - } - - return blocks; -}; +const escapeTomlBasicString = (value) => String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"'); const updateVersionLine = (line, version) => { const commentIndex = line.indexOf('#'); @@ -98,9 +71,8 @@ const updateVersionLine = (line, version) => { return null; } - const quote = right.includes("'") ? "'" : '"'; const trailingWhitespace = beforeComment.match(/\s*$/u)?.[0] ?? ''; - const updatedLine = `${left} = ${quote}${version}${quote}`; + const updatedLine = `${left} = "${escapeTomlBasicString(version)}"`; if (!comment) { return `${updatedLine}${trailingWhitespace}`; @@ -111,47 +83,71 @@ const updateVersionLine = (line, version) => { const updateCargoLockPackageVersion = ({ cargoLock, packageName, version }) => { const lines = cargoLock.split(/\r?\n/); - const packageBlocks = findCargoLockPackageBlocks(lines); + const newline = cargoLock.includes('\r\n') ? '\r\n' : '\n'; const packageNameLinePattern = new RegExp( `^\\s*name\\s*=\\s*"${escapeRegExp(packageName)}"\\s*(?:#.*)?$`, ); - for (const { start, end } of packageBlocks) { - let packageNameLineIndex = -1; + let inPackageBlock = false; + let inTargetPackage = false; + let foundTargetPackage = false; - for (let index = start + 1; index < end; index += 1) { - if (!packageNameLinePattern.test(lines[index])) { - continue; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; + + if (CARGO_LOCK_PACKAGE_HEADER.test(line)) { + if (inTargetPackage) { + throw new Error( + `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, + ); } + inPackageBlock = true; + inTargetPackage = false; + continue; + } - packageNameLineIndex = index; - break; + if (inPackageBlock && CARGO_LOCK_ANY_HEADER.test(line)) { + if (inTargetPackage) { + throw new Error( + `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, + ); + } + inPackageBlock = false; + inTargetPackage = false; } - if (packageNameLineIndex === -1) { + if (!inPackageBlock) { continue; } - for (let index = packageNameLineIndex + 1; index < end; index += 1) { - if (!CARGO_LOCK_VERSION_LINE.test(lines[index])) { - continue; - } + if (!inTargetPackage && packageNameLinePattern.test(line)) { + inTargetPackage = true; + foundTargetPackage = true; + continue; + } - const updatedLine = updateVersionLine(lines[index], version); - if (updatedLine === null) { - break; - } + if (!inTargetPackage || !CARGO_LOCK_VERSION_LINE.test(line)) { + continue; + } - lines[index] = updatedLine; - return lines.join(cargoLock.includes('\r\n') ? '\r\n' : '\n'); + const updatedLine = updateVersionLine(line, version); + if (updatedLine === null) { + throw new Error( + `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, + ); } + lines[index] = updatedLine; + return { content: lines.join(newline), updated: true, foundTargetPackage: true }; + } + + if (inTargetPackage) { throw new Error( `Cannot update Cargo.lock: version entry for package "${packageName}" not found or has an unexpected layout`, ); } - throw new CargoLockPackageNotFoundError(packageName); + return { content: cargoLock, updated: false, foundTargetPackage }; }; export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { @@ -184,21 +180,17 @@ export const syncDesktopVersionFiles = async ({ projectRoot, version }) => { if (existsSync(cargoLockPath)) { const cargoLock = await readFile(cargoLockPath, 'utf8'); - let updatedCargoLock = cargoLock; - try { - updatedCargoLock = updateCargoLockPackageVersion({ - cargoLock, - packageName: DESKTOP_TAURI_CRATE_NAME, - version, - }); - } catch (error) { - if (error instanceof CargoLockPackageNotFoundError) { - console.warn(`${cargoLockPath}: ${error.message}. Skipping Cargo.lock version sync.`); - } else { - throw error; - } - } - if (updatedCargoLock !== cargoLock) { + const { content: updatedCargoLock, updated, foundTargetPackage } = updateCargoLockPackageVersion({ + cargoLock, + packageName: DESKTOP_TAURI_CRATE_NAME, + version, + }); + + if (!foundTargetPackage) { + console.warn( + `${cargoLockPath}: package "${DESKTOP_TAURI_CRATE_NAME}" not found. Skipping Cargo.lock version sync.`, + ); + } else if (updated && updatedCargoLock !== cargoLock) { await writeFile(cargoLockPath, updatedCargoLock, 'utf8'); } } diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index d76f2f5..e906222 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -207,6 +207,49 @@ version = "9.9.9" } }); +test('syncDesktopVersionFiles rewrites Cargo.lock version lines using double quotes', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + try { + const srcTauriDir = path.join(tempDir, 'src-tauri'); + await mkdir(srcTauriDir, { recursive: true }); + + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.lock'), + `version = 4 + +[[package]] +name = "${DESKTOP_TAURI_CRATE_NAME}" +version = '0.1.0' # keep this comment +`, + 'utf8', + ); + + await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); + + const cargoLock = await readFile(path.join(srcTauriDir, 'Cargo.lock'), 'utf8'); + + assert.match(cargoLock, /version = "2.3.4"\s+# keep this comment/); + assert.doesNotMatch(cargoLock, /version = '2\.3\.4'/); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +}); + test('syncDesktopVersionFiles only updates the version key, not similarly prefixed keys', async () => { const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); try { From b3411376527d6ba70992aca485dda3b5b4020f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 18 Mar 2026 22:45:54 +0900 Subject: [PATCH 11/11] test(ci): reuse temp desktop project setup --- .../prepare-resources/version-sync.test.mjs | 228 +++++------------- 1 file changed, 63 insertions(+), 165 deletions(-) diff --git a/scripts/prepare-resources/version-sync.test.mjs b/scripts/prepare-resources/version-sync.test.mjs index e906222..9fb7504 100644 --- a/scripts/prepare-resources/version-sync.test.mjs +++ b/scripts/prepare-resources/version-sync.test.mjs @@ -11,6 +11,34 @@ import { syncDesktopVersionFiles, } from './version-sync.mjs'; +const createTempDesktopProject = async ({ cargoLockContents, version = '0.1.0' }) => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + const srcTauriDir = path.join(tempDir, 'src-tauri'); + + await mkdir(srcTauriDir, { recursive: true }); + await writeFile( + path.join(tempDir, 'package.json'), + `${JSON.stringify({ name: 'test', version }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'tauri.conf.json'), + `${JSON.stringify({ version }, null, 2)}\n`, + 'utf8', + ); + await writeFile( + path.join(srcTauriDir, 'Cargo.toml'), + `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "${version}"\n`, + 'utf8', + ); + + if (typeof cargoLockContents === 'string') { + await writeFile(path.join(srcTauriDir, 'Cargo.lock'), cargoLockContents, 'utf8'); + } + + return { tempDir, srcTauriDir }; +}; + 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'); @@ -41,29 +69,11 @@ version = "1.9.1" }); test('syncDesktopVersionFiles updates package.json, tauri.conf.json, Cargo.toml and Cargo.lock', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] name = "${DESKTOP_TAURI_CRATE_NAME}" @@ -73,8 +83,7 @@ version = "0.1.0" name = "dep" version = "9.9.9" `, - 'utf8', - ); + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -97,29 +106,11 @@ version = "9.9.9" }); test('syncDesktopVersionFiles tolerates extra Cargo.lock fields between package name and version', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] name = "${DESKTOP_TAURI_CRATE_NAME}" @@ -134,8 +125,7 @@ name = "dep" version = "9.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" `, - 'utf8', - ); + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -157,29 +147,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" }); test('syncDesktopVersionFiles preserves trailing Cargo.lock comments and spaces on name/version lines', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] # package header name = "${DESKTOP_TAURI_CRATE_NAME}" # desktop package @@ -189,8 +161,7 @@ version = "0.1.0" # keep this comment name = "dep" version = "9.9.9" `, - 'utf8', - ); + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -208,36 +179,17 @@ version = "9.9.9" }); test('syncDesktopVersionFiles rewrites Cargo.lock version lines using double quotes', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] name = "${DESKTOP_TAURI_CRATE_NAME}" version = '0.1.0' # keep this comment `, - 'utf8', - ); + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -251,37 +203,18 @@ version = '0.1.0' # keep this comment }); test('syncDesktopVersionFiles only updates the version key, not similarly prefixed keys', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] name = "${DESKTOP_TAURI_CRATE_NAME}" versioned_dep = "keep-me" version = "0.1.0" `, - 'utf8', - ); + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -295,7 +228,8 @@ version = "0.1.0" }); test('syncDesktopVersionFiles skips Cargo.lock updates when the desktop crate is missing', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; const warnings = []; const originalWarn = console.warn; console.warn = (message) => { @@ -303,32 +237,15 @@ test('syncDesktopVersionFiles skips Cargo.lock updates when the desktop crate is }; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - const originalCargoLock = `version = 4 [[package]] name = "dep" version = "9.9.9" `; - await writeFile(path.join(srcTauriDir, 'Cargo.lock'), originalCargoLock, 'utf8'); + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: originalCargoLock, + })); await syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }); @@ -350,37 +267,18 @@ version = "9.9.9" }); test('syncDesktopVersionFiles throws a specific error when the desktop crate version entry is malformed', async () => { - const tempDir = await mkdtemp(path.join(os.tmpdir(), 'astrbot-sync-')); + let tempDir; + let srcTauriDir; try { - const srcTauriDir = path.join(tempDir, 'src-tauri'); - await mkdir(srcTauriDir, { recursive: true }); - - await writeFile( - path.join(tempDir, 'package.json'), - `${JSON.stringify({ name: 'test', version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'tauri.conf.json'), - `${JSON.stringify({ version: '0.1.0' }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.toml'), - `[package]\nname = "${DESKTOP_TAURI_CRATE_NAME}"\nversion = "0.1.0"\n`, - 'utf8', - ); - await writeFile( - path.join(srcTauriDir, 'Cargo.lock'), - `version = 4 + ({ tempDir, srcTauriDir } = await createTempDesktopProject({ + cargoLockContents: `version = 4 [[package]] name = "${DESKTOP_TAURI_CRATE_NAME}" source = "path+file:///workspace/src-tauri" checksum = "unexpected-layout" `, - 'utf8', - ); + })); await assert.rejects( syncDesktopVersionFiles({ projectRoot: tempDir, version: '2.3.4' }),