From ba56c5883c5cf8e7ca1bfc1197f0ee9e802479bd Mon Sep 17 00:00:00 2001 From: tianma-if Date: Wed, 9 Sep 2026 19:51:51 +0800 Subject: [PATCH] fix(desktop): accept migrated renderer origin in cross-version startup The previous formal native already loads edgeever-app://, so the old-to-new gate must accept that origin as well as the legacy file:// renderer. --- package.json | 2 +- scripts/release.mjs | 1 + .../verify-desktop-cross-version-startup.mjs | 104 ++++++++++++------ ...ify-desktop-cross-version-startup.test.mjs | 22 ++++ 4 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 scripts/verify-desktop-cross-version-startup.test.mjs diff --git a/package.json b/package.json index 0550e604..77d3fecc 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "build:desktop": "EDGE_EVER_DESKTOP_BUILD=1 bun run prepare:desktop:icons && bun run build:web && bun run build:desktop:sidecar && bun run --cwd apps/desktop dist", "benchmark:desktop:sidecar": "bun scripts/benchmark-desktop-sidecar.mjs", "verify:web-performance": "bun scripts/verify-web-performance.mjs", - "test:release-planning": "bun test scripts/plan-native-release.test.mjs scripts/check-native-release-assets.test.mjs scripts/create-mac-update-metadata.test.mjs scripts/windows-update-metadata.test.mjs scripts/linux-update-release.test.mjs scripts/desktop-build-workflow.test.mjs scripts/pe-imports.test.mjs scripts/report-native-build-timings.test.mjs scripts/release.test.mjs scripts/validate-store-delivery.test.mjs scripts/store-delivery.test.mjs scripts/download-play-universal-apk.test.mjs scripts/verify-android-apk-signature.test.mjs scripts/desktop-icns.test.mjs scripts/preview-macos-dock-icon.test.mjs apps/web/src/lib/version-check.test.mjs apps/mobile/src/lib/mobile-release.test.ts", + "test:release-planning": "bun test scripts/plan-native-release.test.mjs scripts/check-native-release-assets.test.mjs scripts/create-mac-update-metadata.test.mjs scripts/windows-update-metadata.test.mjs scripts/linux-update-release.test.mjs scripts/desktop-build-workflow.test.mjs scripts/pe-imports.test.mjs scripts/report-native-build-timings.test.mjs scripts/release.test.mjs scripts/validate-store-delivery.test.mjs scripts/store-delivery.test.mjs scripts/download-play-universal-apk.test.mjs scripts/verify-android-apk-signature.test.mjs scripts/desktop-icns.test.mjs scripts/verify-desktop-cross-version-startup.test.mjs scripts/preview-macos-dock-icon.test.mjs apps/web/src/lib/version-check.test.mjs apps/mobile/src/lib/mobile-release.test.ts", "release": "bun scripts/release.mjs", "publish:stores": "bun scripts/store-delivery.mjs", "test:desktop:sidecar": "bun scripts/test-desktop-sidecar.mjs", diff --git a/scripts/release.mjs b/scripts/release.mjs index 1f32f121..913cbc75 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -52,6 +52,7 @@ export const RELEASE_VALIDATIONS = [ "scripts/store-delivery.test.mjs", "scripts/download-play-universal-apk.test.mjs", "scripts/desktop-icns.test.mjs", + "scripts/verify-desktop-cross-version-startup.test.mjs", "apps/web/src/lib/version-check.test.mjs", "apps/mobile/src/lib/mobile-release.test.ts", ], diff --git a/scripts/verify-desktop-cross-version-startup.mjs b/scripts/verify-desktop-cross-version-startup.mjs index 3c56e2d1..22aa0ab8 100644 --- a/scripts/verify-desktop-cross-version-startup.mjs +++ b/scripts/verify-desktop-cross-version-startup.mjs @@ -4,21 +4,21 @@ import { mkdir, rename, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -const [oldExecutableInput, newExecutableInput] = process.argv.slice(2); -const oldExecutable = resolve(oldExecutableInput || ""); -const newExecutable = resolve(newExecutableInput || ""); -for (const [label, executable] of [["old", oldExecutable], ["new", newExecutable]]) { - if (!executable || !existsSync(executable) || !statSync(executable).isFile()) { - throw new Error(`${label} desktop executable is missing: ${executable || ""}`); - } -} +export const LEGACY_RENDERER_URL_PREFIX = "file://"; +export const CURRENT_RENDERER_URL_PREFIX = "edgeever-app://app/"; + +export const isPreviousReleaseRendererUrl = (url) => { + const value = String(url || ""); + return value.startsWith(LEGACY_RENDERER_URL_PREFIX) + || value.startsWith(CURRENT_RENDERER_URL_PREFIX); +}; + +export const isCurrentReleaseRendererUrl = (url) => + String(url || "").startsWith(CURRENT_RENDERER_URL_PREFIX); -const testDirectory = mkdtempSync(join(tmpdir(), "edgeever-cross-version-")); -const userDataDirectory = join(testDirectory, "profile"); -const logPath = join(userDataDirectory, "logs", "desktop.log"); const wait = (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)); -const readEntries = () => { +const readEntries = (logPath) => { if (!existsSync(logPath)) return []; return readFileSync(logPath, "utf8").split(/\r?\n/).flatMap((line) => { try { return [JSON.parse(line)]; } catch { return []; } @@ -38,7 +38,14 @@ const stopProcess = async (child) => { ]); }; -const launchAndWait = async ({ executable, expectedUrlPrefix, requiredEvents }) => { +const launchAndWait = async ({ + executable, + userDataDirectory, + logPath, + acceptUrl, + expectedUrlDescription, + requiredEvents, +}) => { const output = []; let processError = null; const child = spawn(executable, [`--user-data-dir=${userDataDirectory}`], { @@ -54,10 +61,10 @@ const launchAndWait = async ({ executable, expectedUrlPrefix, requiredEvents }) const deadline = Date.now() + timeoutMs; try { while (Date.now() < deadline) { - const entries = readEntries(); + const entries = readEntries(logPath); const events = new Set(entries.map((entry) => entry.event)); const loaded = entries.find((entry) => - entry.event === "renderer.loaded" && String(entry.url || "").startsWith(expectedUrlPrefix)); + entry.event === "renderer.loaded" && acceptUrl(entry.url)); if (loaded && requiredEvents.every((event) => events.has(event))) { return { events: [...events], rendererUrl: loaded.url }; } @@ -66,7 +73,7 @@ const launchAndWait = async ({ executable, expectedUrlPrefix, requiredEvents }) } throw new Error([ `Desktop transition startup failed for ${executable} (exit=${child.exitCode ?? "running"}).`, - `Expected renderer URL prefix: ${expectedUrlPrefix}`, + `Expected renderer URL: ${expectedUrlDescription}`, processError && `Process error: ${processError.message}`, existsSync(logPath) && `Diagnostic log:\n${readFileSync(logPath, "utf8")}`, output.length > 0 && `Process output:\n${output.join("")}`, @@ -76,23 +83,54 @@ const launchAndWait = async ({ executable, expectedUrlPrefix, requiredEvents }) } }; -try { - await mkdir(userDataDirectory, { recursive: true }); - const oldResult = await launchAndWait({ - executable: oldExecutable, - expectedUrlPrefix: "file://", - requiredEvents: ["sidecar.ready", "renderer.bootstrap-ready"], - }); - await rename(logPath, join(userDataDirectory, "logs", "desktop-old-version.log")); +export const verifyDesktopCrossVersionStartup = async ({ + oldExecutable, + newExecutable, +}) => { + for (const [label, executable] of [["old", oldExecutable], ["new", newExecutable]]) { + if (!executable || !existsSync(executable) || !statSync(executable).isFile()) { + throw new Error(`${label} desktop executable is missing: ${executable || ""}`); + } + } - const newResult = await launchAndWait({ - executable: newExecutable, - expectedUrlPrefix: "edgeever-app://app/", - requiredEvents: ["renderer.origin-ready", "sidecar.ready", "renderer.bootstrap-ready"], + const testDirectory = mkdtempSync(join(tmpdir(), "edgeever-cross-version-")); + const userDataDirectory = join(testDirectory, "profile"); + const logPath = join(userDataDirectory, "logs", "desktop.log"); + try { + await mkdir(userDataDirectory, { recursive: true }); + const oldResult = await launchAndWait({ + executable: oldExecutable, + userDataDirectory, + logPath, + acceptUrl: isPreviousReleaseRendererUrl, + expectedUrlDescription: `${LEGACY_RENDERER_URL_PREFIX} or ${CURRENT_RENDERER_URL_PREFIX}`, + requiredEvents: ["sidecar.ready", "renderer.bootstrap-ready"], + }); + await rename(logPath, join(userDataDirectory, "logs", "desktop-old-version.log")); + + const newResult = await launchAndWait({ + executable: newExecutable, + userDataDirectory, + logPath, + acceptUrl: isCurrentReleaseRendererUrl, + expectedUrlDescription: CURRENT_RENDERER_URL_PREFIX, + requiredEvents: ["renderer.origin-ready", "sidecar.ready", "renderer.bootstrap-ready"], + }); + const markerPath = join(userDataDirectory, "renderer-origin-v1-migrated"); + if (!existsSync(markerPath)) { + throw new Error("The upgraded desktop did not persist its renderer origin migration marker"); + } + return { ok: true, oldResult, newResult }; + } finally { + await rm(testDirectory, { recursive: true, force: true }); + } +}; + +if (import.meta.main) { + const [oldExecutableInput, newExecutableInput] = process.argv.slice(2); + const result = await verifyDesktopCrossVersionStartup({ + oldExecutable: resolve(oldExecutableInput || ""), + newExecutable: resolve(newExecutableInput || ""), }); - const markerPath = join(userDataDirectory, "renderer-origin-v1-migrated"); - if (!existsSync(markerPath)) throw new Error("The upgraded desktop did not persist its renderer origin migration marker"); - console.log(JSON.stringify({ ok: true, oldResult, newResult })); -} finally { - await rm(testDirectory, { recursive: true, force: true }); + console.log(JSON.stringify(result)); } diff --git a/scripts/verify-desktop-cross-version-startup.test.mjs b/scripts/verify-desktop-cross-version-startup.test.mjs new file mode 100644 index 00000000..7418c681 --- /dev/null +++ b/scripts/verify-desktop-cross-version-startup.test.mjs @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test"; +import { + CURRENT_RENDERER_URL_PREFIX, + LEGACY_RENDERER_URL_PREFIX, + isCurrentReleaseRendererUrl, + isPreviousReleaseRendererUrl, +} from "./verify-desktop-cross-version-startup.mjs"; + +describe("desktop cross-version startup origins", () => { + test("accepts both the legacy file origin and the current app origin for the previous native", () => { + expect(isPreviousReleaseRendererUrl(`${LEGACY_RENDERER_URL_PREFIX}/Applications/EdgeEver.app/Contents/Resources/web/index.html`)).toBe(true); + expect(isPreviousReleaseRendererUrl(`${CURRENT_RENDERER_URL_PREFIX}index.html`)).toBe(true); + expect(isPreviousReleaseRendererUrl("https://demo.edgeever.org/")).toBe(false); + expect(isPreviousReleaseRendererUrl("")).toBe(false); + }); + + test("requires the current custom protocol after the upgrade", () => { + expect(isCurrentReleaseRendererUrl(`${CURRENT_RENDERER_URL_PREFIX}index.html`)).toBe(true); + expect(isCurrentReleaseRendererUrl(`${LEGACY_RENDERER_URL_PREFIX}/Applications/EdgeEver.app/Contents/Resources/web/index.html`)).toBe(false); + expect(isCurrentReleaseRendererUrl("edgeever-app://other/index.html")).toBe(false); + }); +});