Files
openchamber/packages/electron/scripts/verify-update-manifest.mjs
T
502c96630e feat(desktop): Linux AppImage polish — window controls, updater UX, docs (#2144)
* feat(electron): add Linux AppImage releases

* ci: cache Linux OpenCode CLI artifacts

* fix(ci): await Linux release inventory check

* fix(electron): add frameless window controls on Linux desktop

Linux AppImages were created without native WM decorations and without
in-app controls, leaving users unable to close the window with a mouse.

Treat Linux like Windows: frameless BrowserWindow plus the existing
WindowsWindowControls header buttons and app-menu entry. macOS keeps
hidden title bar with traffic lights unchanged.

Shared usesFramelessElectronChrome() helper drives main window, mini
chat, header insets, and titlebar controls.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* feat(desktop): add configurable window controls position by OS

Add desktopWindowControlsPosition setting (auto/left/right) with OS-aware
defaults: Linux left, Windows right. Wire frameless chrome controls in
Header, TitlebarLeftControls, and MiniChatLayout, plus a Sessions settings
control for Windows and Linux desktop shells.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(desktop): address Linux AppImage release review findings

Propagate updater capability errors to the UI, treat missing
latest-linux.yml feeds as no-update, stop installed-apps IPC spam on
Linux, document FUSE/AppImage limits, add CHANGELOG entry, migrate
remaining btriapitsyn URLs, and run Electron Linux unit tests on PRs.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: jibanez-staticduo <staticduo@gmail.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-13 08:59:31 +03:00

73 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
export const parseUpdateManifest = (content) => {
const version = content.match(/^version:\s*(\S+)\s*$/m)?.[1] || '';
const lines = content.split(/\r?\n/);
const files = [];
let entry = null;
for (const line of lines) {
const start = line.match(/^\s{2}-\s+(url|sha512|size|blockMapSize):\s*(\S+)\s*$/);
const field = start || line.match(/^\s{4}(url|sha512|size|blockMapSize):\s*(\S+)\s*$/);
if (start) {
if (entry) files.push(entry);
entry = {};
}
if (!field || !entry) continue;
const [, key, value] = field;
entry[key] = key === 'size' || key === 'blockMapSize' ? Number(value) : value;
}
if (entry) files.push(entry);
return {
version,
files: files.filter((file) => file.url && file.sha512 && Number.isSafeInteger(file.size)),
};
};
export const verifyUpdateManifest = ({ manifestPath, artifactPath, expectedVersion }) => {
const manifest = parseUpdateManifest(fs.readFileSync(manifestPath, 'utf8'));
const expectedName = path.basename(artifactPath);
if (manifest.version !== expectedVersion) {
throw new Error(`Update manifest version mismatch: expected ${expectedVersion}, got ${manifest.version || '(missing)'}`);
}
if (manifest.files.length !== 1) {
throw new Error(`Linux update manifest must contain exactly one artifact, got ${manifest.files.length}`);
}
const [entry] = manifest.files;
if (decodeURIComponent(path.basename(entry.url)) !== expectedName) {
throw new Error(`Update manifest artifact mismatch: expected ${expectedName}, got ${entry.url}`);
}
const bytes = fs.readFileSync(artifactPath);
if (entry.size !== bytes.length) {
throw new Error(`Update manifest size mismatch: expected ${bytes.length}, got ${entry.size}`);
}
const checksum = crypto.createHash('sha512').update(bytes).digest('base64');
if (entry.sha512 !== checksum) throw new Error('Update manifest sha512 mismatch');
return { name: expectedName, size: bytes.length, version: manifest.version };
};
const main = () => {
const [manifestPath, artifactPath, expectedVersion] = process.argv.slice(2);
if (!manifestPath || !artifactPath || !expectedVersion) {
throw new Error('Usage: verify-update-manifest.mjs <manifest> <artifact> <version>');
}
const result = verifyUpdateManifest({
manifestPath: path.resolve(manifestPath),
artifactPath: path.resolve(artifactPath),
expectedVersion,
});
console.log(`[electron] verified ${path.basename(manifestPath)} for ${result.name} (${result.size} bytes)`);
};
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
}
}