fix(update): verify and apply the actual latest version
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { resolveRuntimeAppVersion } from "../src/main/app-version";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("runtime app version", () => {
|
||||
it("uses the version from the installed app package instead of a stale bundled fallback", () => {
|
||||
const resourcesPath = fs.mkdtempSync(path.join(os.tmpdir(), "mdd-version-"));
|
||||
tempDirs.push(resourcesPath);
|
||||
fs.mkdirSync(path.join(resourcesPath, "app.asar"));
|
||||
fs.writeFileSync(path.join(resourcesPath, "app.asar", "package.json"), JSON.stringify({ version: "2.0.51" }), "utf8");
|
||||
|
||||
expect(resolveRuntimeAppVersion("2.0.50", resourcesPath)).toBe("2.0.51");
|
||||
});
|
||||
|
||||
it("uses the bundled fallback when no installed package metadata is available", () => {
|
||||
const resourcesPath = fs.mkdtempSync(path.join(os.tmpdir(), "mdd-version-"));
|
||||
tempDirs.push(resourcesPath);
|
||||
|
||||
expect(resolveRuntimeAppVersion("2.0.51", resourcesPath)).toBe("2.0.51");
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,8 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createPackage } from "@electron/asar";
|
||||
|
||||
type ReleaseVerification = {
|
||||
publish: {
|
||||
@@ -38,7 +39,21 @@ const { verifyPublicRelease, verifyReleaseArchives } = await import(verifierUrl)
|
||||
}
|
||||
) => ArchiveVerification;
|
||||
};
|
||||
const fixtureRoots: string[] = [];
|
||||
const fixtureRoots: string[] = [];
|
||||
async function createFixtureAsar(version: string): Promise<Buffer> {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "public-release-asar-"));
|
||||
const sourceDir = path.join(rootDir, "source");
|
||||
const outputPath = path.join(rootDir, "app.asar");
|
||||
fs.mkdirSync(path.join(sourceDir, "build", "main", "main"), { recursive: true });
|
||||
fs.writeFileSync(path.join(sourceDir, "package.json"), JSON.stringify({ name: "multi-debrid-downloader", version }), "utf8");
|
||||
fs.writeFileSync(path.join(sourceDir, "build", "main", "main", "main.js"), `var package_default = { name: "multi-debrid-downloader", version: "${version}" };`, "utf8");
|
||||
await createPackage(sourceDir, outputPath);
|
||||
const payload = fs.readFileSync(outputPath);
|
||||
fs.rmSync(rootDir, { recursive: true, force: true });
|
||||
return payload;
|
||||
}
|
||||
const validAppAsar = await createFixtureAsar("1.7.233");
|
||||
const staleAppAsar = await createFixtureAsar("1.7.232");
|
||||
const redistributionFiles = [
|
||||
"LICENSE",
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
@@ -172,6 +187,7 @@ function createReleaseFixture(): string {
|
||||
writeRedistributionFiles(rootDir, true);
|
||||
writeFile(rootDir, "assets/app_icon.ico", "application-icon");
|
||||
writeFile(rootDir, "win-unpacked/resources/assets/app_icon.ico", "application-icon");
|
||||
writeFile(rootDir, "win-unpacked/resources/app.asar", validAppAsar);
|
||||
writeFile(rootDir, "resources/installer.nsh", "!macro customCheckAppRunning\n${isUpdated}\nFIND_PROCESS\ntaskkill /f /im\n!macroend\n");
|
||||
|
||||
return rootDir;
|
||||
@@ -357,6 +373,13 @@ describe("public release metadata", () => {
|
||||
expect(() => verifyPublicRelease(rootDir)).toThrow(/NSIS|update|installer/i);
|
||||
});
|
||||
|
||||
it("rejects a packaged main bundle built before the release version changed", () => {
|
||||
const rootDir = createReleaseFixture();
|
||||
fs.writeFileSync(path.join(rootDir, "win-unpacked", "resources", "app.asar"), staleAppAsar);
|
||||
|
||||
expect(() => verifyPublicRelease(rootDir)).toThrow(/main bundle|version/i);
|
||||
});
|
||||
|
||||
it("rejects a packaged application without its window and tray icon", () => {
|
||||
const rootDir = createReleaseFixture();
|
||||
fs.rmSync(path.join(rootDir, "win-unpacked", "resources", "assets", "app_icon.ico"));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runLatestUpdateCheck, shouldApplyUpdateCheckResult } from "../src/renderer/App";
|
||||
import { runLatestUpdateCheck, shouldApplyUpdateCheckResult, shouldOpenUpdatePrompt } from "../src/renderer/App";
|
||||
import type { UpdateCheckResult } from "../src/shared/types";
|
||||
import { AppHeader } from "../src/renderer/shell/AppHeader";
|
||||
import { getUpdateDialogFocusTarget, UpdateExperience } from "../src/renderer/shell/UpdateExperience";
|
||||
@@ -13,7 +13,13 @@ const callbacks = {
|
||||
onLater: () => {}
|
||||
};
|
||||
|
||||
describe("update experience", () => {
|
||||
describe("update experience", () => {
|
||||
it("keeps a dismissed update version closed until the app restarts or a newer version appears", () => {
|
||||
expect(shouldOpenUpdatePrompt("startup", "v2.0.51", "v2.0.51")).toBe(false);
|
||||
expect(shouldOpenUpdatePrompt("startup", "v2.0.52", "v2.0.51")).toBe(true);
|
||||
expect(shouldOpenUpdatePrompt("manual", "v2.0.51", "v2.0.51")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the available update and prompt as one accessible experience", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<UpdateExperience
|
||||
@@ -146,15 +152,15 @@ describe("update experience", () => {
|
||||
expect(css).toMatch(/\.md-update-dialog\s*\{[^}]*box-shadow:\s*0 12px 40px rgb\(0 0 0 \/ 45%\)/s);
|
||||
});
|
||||
|
||||
it("uses a light-blue update affordance and a bounded scrollable changelog", () => {
|
||||
it("uses a green update affordance and a bounded scrollable changelog", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
|
||||
const theme = readFileSync(new URL("../src/renderer/theme.css", import.meta.url), "utf8");
|
||||
|
||||
expect(theme).toMatch(/--ui-update:\s*#BAD0FC;/);
|
||||
expect(theme).toMatch(/--ui-update-hover:\s*#8AA5DC;/);
|
||||
expect(theme).toMatch(/--ui-update-text:\s*#181A1F;/);
|
||||
expect(css).toMatch(/\.md-update-trigger\s*\{[^}]*background:\s*var\(--ui-update\);[^}]*color:\s*var\(--ui-update-text\);/s);
|
||||
expect(css).toMatch(/\.md-update-trigger:hover\s*\{[^}]*background:\s*var\(--ui-update-hover\);/s);
|
||||
expect(css).toMatch(/\.md-update-trigger\s*\{[^}]*background:\s*var\(--ui-success\);[^}]*color:\s*var\(--ui-update-text\);/s);
|
||||
expect(css).toMatch(/\.md-update-trigger:hover\s*\{[^}]*background:\s*color-mix\(in srgb, var\(--ui-success\) 82%, #000\);/s);
|
||||
expect(css).toMatch(/\.md-update-release-notes pre\s*\{[^}]*max-height:\s*min\(360px, 45vh\);[^}]*overflow-y:\s*auto;/s);
|
||||
});
|
||||
|
||||
|
||||
+11
-2
@@ -44,8 +44,17 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
it("normalizes update repo input", () => {
|
||||
describe("update", () => {
|
||||
it("always refreshes release metadata before installing instead of using the previous check result", () => {
|
||||
const controller = fs.readFileSync(new URL("../src/main/app-controller.ts", import.meta.url), "utf8");
|
||||
const install = controller.slice(controller.indexOf("public async installUpdate"), controller.indexOf("public addLinks"));
|
||||
|
||||
expect(install).toContain("installLatestUpdate(this.settings.updateRepo, undefined, onProgress)");
|
||||
expect(install).not.toContain("cacheAgeMs");
|
||||
expect(install).not.toContain("this.lastUpdateCheck &&");
|
||||
});
|
||||
|
||||
it("normalizes update repo input", () => {
|
||||
expect(normalizeUpdateRepo("")).toBe("Sucukdeluxe/Multi-Debrid-Downloader");
|
||||
expect(normalizeUpdateRepo("owner/repo")).toBe("owner/repo");
|
||||
expect(normalizeUpdateRepo("https://github.com/owner/repo")).toBe("owner/repo");
|
||||
|
||||
Reference in New Issue
Block a user