mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 05:32:58 +00:00
Restore reliable macOS desktop updates (#4322)
* fix(desktop): restore macOS app updates Electron 41's Squirrel helper can leave the update handoff dormant on current macOS, so Paseo quits cleanly but relaunches the old bundle. Electron 44 wakes ShipIt through its XPC service. Include updater lifecycle events and ShipIt state in app diagnostics so future handoff failures identify the current version, target version, cache paths, and installer output. * fix(desktop): harden updater diagnostics Keep each ShipIt file's evidence available when another read fails, carry the service-selected version into updater lifecycle logs, and make clean workspace installs resolve the Electron runtime used by desktop tests. * refactor(desktop): name updater install options * refactor(desktop): name updater log payloads * refactor(desktop): name clipboard payload * fix(desktop): surface updater lookup failures * test(desktop): await guest focus transition * fix(desktop): guard macOS update compatibility * test(release): validate desktop manifests before publish * test(desktop): await populated daemon pid file
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
import fs from "node:fs";
|
||||
import { dump, load } from "js-yaml";
|
||||
|
||||
// electron-updater compares this with os.release(), which reports the Darwin
|
||||
// kernel version. Darwin 22 is macOS 13 Ventura.
|
||||
export const MACOS_MINIMUM_DARWIN_VERSION = "22.0.0";
|
||||
|
||||
export function mergeMacManifest(arm64Path, x64Path, outputPath) {
|
||||
const arm64 = load(fs.readFileSync(arm64Path, "utf8"));
|
||||
const x64 = load(fs.readFileSync(x64Path, "utf8"));
|
||||
const files = [...(arm64.files ?? []), ...(x64.files ?? [])].filter(
|
||||
(file, index, all) => all.findIndex((entry) => entry.url === file.url) === index,
|
||||
);
|
||||
const output = dump({ ...arm64, files }, { lineWidth: -1, noRefs: true });
|
||||
const output = dump(
|
||||
{ ...arm64, files, minimumSystemVersion: MACOS_MINIMUM_DARWIN_VERSION },
|
||||
{ lineWidth: -1, noRefs: true },
|
||||
);
|
||||
fs.writeFileSync(outputPath, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ test("preserves unknown fields while merging files by url", () => {
|
||||
mergeMacManifest(arm64Path, x64Path, outputPath);
|
||||
const output = readFileSync(outputPath, "utf8");
|
||||
assert.match(output, /stagingPercentage: 25/);
|
||||
assert.match(output, /minimumSystemVersion: 22\.0\.0/);
|
||||
assert.match(output, /url: app-arm64\.zip/);
|
||||
assert.match(output, /url: app-x64\.zip/);
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { load } from "js-yaml";
|
||||
import { MACOS_MINIMUM_DARWIN_VERSION } from "./merge-mac-manifest.mjs";
|
||||
|
||||
export function validateDesktopManifests({ releaseDate, rolloutHours }, paths) {
|
||||
if (!Number.isFinite(rolloutHours) || rolloutHours < 0) {
|
||||
throw new Error(`expected non-negative rolloutHours, got ${rolloutHours}`);
|
||||
}
|
||||
|
||||
for (const manifestPath of paths) {
|
||||
const manifest = load(fs.readFileSync(manifestPath, "utf8")) ?? {};
|
||||
if (manifest.rolloutHours !== rolloutHours) {
|
||||
throw new Error(
|
||||
`${manifestPath}: rolloutHours=${manifest.rolloutHours}, expected ${rolloutHours}`,
|
||||
);
|
||||
}
|
||||
if (manifest.releaseDate !== releaseDate) {
|
||||
throw new Error(
|
||||
`${manifestPath}: releaseDate=${manifest.releaseDate}, expected ${releaseDate}`,
|
||||
);
|
||||
}
|
||||
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
||||
throw new Error(`${manifestPath}: missing or invalid version`);
|
||||
}
|
||||
if (
|
||||
manifestPath.endsWith("-mac.yml") &&
|
||||
manifest.minimumSystemVersion !== MACOS_MINIMUM_DARWIN_VERSION
|
||||
) {
|
||||
throw new Error(
|
||||
`${manifestPath}: minimumSystemVersion=${manifest.minimumSystemVersion}, expected ${MACOS_MINIMUM_DARWIN_VERSION}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const paths = [];
|
||||
let releaseDate;
|
||||
let rolloutHours;
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
if (argv[index] === "--release-date") {
|
||||
releaseDate = argv[++index];
|
||||
} else if (argv[index] === "--rollout-hours") {
|
||||
rolloutHours = Number(argv[++index]);
|
||||
} else {
|
||||
paths.push(argv[index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!releaseDate || rolloutHours === undefined || paths.length === 0) {
|
||||
throw new Error(
|
||||
"Usage: node scripts/validate-desktop-manifests.mjs --release-date <date> --rollout-hours <hours> <manifest...>",
|
||||
);
|
||||
}
|
||||
|
||||
return { releaseDate, rolloutHours, paths };
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
||||
const { releaseDate, rolloutHours, paths } = parseArgs(process.argv.slice(2));
|
||||
validateDesktopManifests({ releaseDate, rolloutHours }, paths);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { test } from "vitest";
|
||||
import { validateDesktopManifests } from "./validate-desktop-manifests.mjs";
|
||||
|
||||
const releaseDate = "2026-09-04T00:00:00.000Z";
|
||||
const scriptPath = fileURLToPath(new URL("./validate-desktop-manifests.mjs", import.meta.url));
|
||||
|
||||
function withManifest(contents, run) {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "paseo-validate-desktop-manifest-"));
|
||||
const manifestPath = path.join(dir, "latest-mac.yml");
|
||||
try {
|
||||
writeFileSync(manifestPath, contents);
|
||||
run(manifestPath);
|
||||
} finally {
|
||||
rmSync(dir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
test("accepts a guarded macOS update manifest", () => {
|
||||
withManifest(
|
||||
`version: 0.7.3\nreleaseDate: '${releaseDate}'\nrolloutHours: 36\nminimumSystemVersion: 22.0.0\n`,
|
||||
(manifestPath) => {
|
||||
validateDesktopManifests({ releaseDate, rolloutHours: 36 }, [manifestPath]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("validates release manifests through the workflow CLI", () => {
|
||||
withManifest(
|
||||
`version: 0.7.3\nreleaseDate: '${releaseDate}'\nrolloutHours: 36\nminimumSystemVersion: 22.0.0\n`,
|
||||
(manifestPath) => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[scriptPath, "--release-date", releaseDate, "--rollout-hours", "36", manifestPath],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects a macOS update manifest without the system floor", () => {
|
||||
withManifest(
|
||||
`version: 0.7.3\nreleaseDate: '${releaseDate}'\nrolloutHours: 36\n`,
|
||||
(manifestPath) => {
|
||||
assert.throws(
|
||||
() => validateDesktopManifests({ releaseDate, rolloutHours: 36 }, [manifestPath]),
|
||||
/minimumSystemVersion=undefined, expected 22\.0\.0/,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects invalid rollout metadata", () => {
|
||||
withManifest(
|
||||
`version: 0.7.3\nreleaseDate: '${releaseDate}'\nrolloutHours: 24\nminimumSystemVersion: 22.0.0\n`,
|
||||
(manifestPath) => {
|
||||
assert.throws(
|
||||
() => validateDesktopManifests({ releaseDate, rolloutHours: 36 }, [manifestPath]),
|
||||
/rolloutHours=24, expected 36/,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user