mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* fix(electron): retry wayland idle helper startup * build(electron): enforce wayland helper in CI * fix(electron): avoid blocking wayland idle promotion * test(e2e): make reminder option selector exact * test(e2e): resolve reminder option selector conflict * fix(electron): gate wayland helper redetection * fix(electron): avoid stale Wayland helper packaging --------- Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
120 lines
4.4 KiB
JavaScript
120 lines
4.4 KiB
JavaScript
// Post-pack hook for Linux builds.
|
|
//
|
|
// Renames the main Electron binary to `superproductivity-bin` and installs
|
|
// a shell wrapper at the original name. The wrapper forces
|
|
// --class=superproductivity for stable desktop-file matching and forces
|
|
// --ozone-platform=x11 when running in our Snap sandbox on a Wayland session.
|
|
// Non-Snap launches (AppImage, .deb, .rpm) and X11 sessions only receive the
|
|
// stable class flag.
|
|
//
|
|
// Context: field reports on issue #7270 (v18.2.4/v18.2.5) show that
|
|
// app.commandLine.appendSwitch('ozone-platform','x11') from inside the
|
|
// main process is not equivalent to passing the flag on argv — Chromium's
|
|
// Ozone init in the browser process dlopens libEGL/libgbm on the core22
|
|
// Mesa path before the switch is honored, which segfaults under Mesa ABI
|
|
// drift. Injecting the flag via argv before Electron starts bypasses this.
|
|
//
|
|
// See docs/research/snap-wayland-gpu-fix-research.md §18.
|
|
|
|
const { promises: fs } = require('fs');
|
|
const { join } = require('path');
|
|
|
|
const BIN_NAME = 'superproductivity'; // must match linux.executableName
|
|
const RENAMED = 'superproductivity-bin';
|
|
const WRAPPER_SRC = join(__dirname, '..', 'build', 'linux', 'snap-wrapper.sh');
|
|
const WAYLAND_IDLE_HELPER_SRC = join(
|
|
__dirname,
|
|
'..',
|
|
'electron',
|
|
'bin',
|
|
'wayland-idle-helper',
|
|
);
|
|
const WAYLAND_IDLE_HELPER_DEST = 'wayland-idle-helper';
|
|
|
|
const isTruthyEnv = (value) => value === '1' || value?.toLowerCase() === 'true';
|
|
|
|
async function afterPack(context) {
|
|
if (context.electronPlatformName !== 'linux') return;
|
|
|
|
const { appOutDir } = context;
|
|
const binPath = join(appOutDir, BIN_NAME);
|
|
const renamedPath = join(appOutDir, RENAMED);
|
|
const helperDestPath = join(appOutDir, WAYLAND_IDLE_HELPER_DEST);
|
|
|
|
const installWaylandIdleHelper = async () => {
|
|
if (isTruthyEnv(process.env.SP_SKIP_WAYLAND_IDLE_HELPER_BUILD)) {
|
|
console.warn(
|
|
'[afterPack] Skipping Wayland idle helper copy because SP_SKIP_WAYLAND_IDLE_HELPER_BUILD is set',
|
|
);
|
|
return;
|
|
}
|
|
|
|
const helperStat = await fs.stat(WAYLAND_IDLE_HELPER_SRC).catch(() => null);
|
|
if (!helperStat) {
|
|
throw new Error(
|
|
`[afterPack] ${WAYLAND_IDLE_HELPER_SRC} not found. Linux packages must include the Wayland idle helper; install Rust/Cargo before packaging or set SP_SKIP_WAYLAND_IDLE_HELPER_BUILD=1 to intentionally omit ext-idle-notify support.`,
|
|
);
|
|
}
|
|
|
|
await fs.copyFile(WAYLAND_IDLE_HELPER_SRC, helperDestPath);
|
|
await fs.chmod(helperDestPath, 0o755);
|
|
};
|
|
|
|
// Read wrapper content BEFORE touching appOutDir. If the source file is
|
|
// missing or unreadable we fail fast with the Electron binary still in
|
|
// place — no broken intermediate state.
|
|
const wrapperContent = await fs.readFile(WRAPPER_SRC, 'utf8');
|
|
|
|
const [binStat, renamedStat] = await Promise.all([
|
|
fs.stat(binPath).catch(() => null),
|
|
fs.stat(renamedPath).catch(() => null),
|
|
]);
|
|
|
|
if (!binStat && !renamedStat) {
|
|
console.warn(`[afterPack] ${binPath} not found; skipping wrapper install`);
|
|
return;
|
|
}
|
|
|
|
// Idempotency: "already installed" requires both files + a shell shebang at
|
|
// binPath. A shebang-less binPath co-existing with renamedPath means a
|
|
// prior run crashed mid-way; fall through to re-write the wrapper.
|
|
if (binStat && renamedStat) {
|
|
const head = await fs.readFile(binPath, 'utf8').catch(() => '');
|
|
if (head.startsWith('#!')) {
|
|
await installWaylandIdleHelper();
|
|
console.log(`[afterPack] wrapper already installed`);
|
|
return;
|
|
}
|
|
throw new Error(
|
|
`[afterPack] unexpected state at ${appOutDir}: both ${BIN_NAME} and ` +
|
|
`${RENAMED} exist but ${BIN_NAME} is not a shell script. Refusing ` +
|
|
`to overwrite; investigate manually.`,
|
|
);
|
|
}
|
|
|
|
// Fresh install path: binStat exists, renamedStat doesn't → rename first.
|
|
// Partial-recovery path: only renamedStat exists → skip rename, just
|
|
// re-write the wrapper on top of the missing slot.
|
|
if (!renamedStat) {
|
|
await fs.rename(binPath, renamedPath);
|
|
}
|
|
|
|
try {
|
|
await fs.writeFile(binPath, wrapperContent, { mode: 0o755 });
|
|
} catch (err) {
|
|
// Best-effort rollback so the build doesn't ship a pkg with no launcher.
|
|
if (!renamedStat) {
|
|
await fs.rename(renamedPath, binPath).catch(() => {});
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
await fs.chmod(renamedPath, 0o755);
|
|
await installWaylandIdleHelper();
|
|
|
|
console.log(
|
|
`[afterPack] Installed argv wrapper: ${BIN_NAME} -> ${RENAMED} + shell wrapper`,
|
|
);
|
|
}
|
|
|
|
module.exports = afterPack;
|