mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-21 21:31:12 +02:00
Custom Electron Forge maker that wraps makepkg to produce a .pkg.tar.zst with /opt/<app>, /usr/bin wrapper, .desktop entry, and hicolor icon. Only activates on Linux when makepkg is present, so other platforms are unaffected.
134 lines
4.8 KiB
JavaScript
134 lines
4.8 KiB
JavaScript
// Custom Electron Forge maker that produces Arch Linux .pkg.tar.zst packages
|
|
// via makepkg. Runs only on Linux with makepkg available (i.e. an Arch host).
|
|
//
|
|
// CJS on purpose: forge.config.cjs require()s us.
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { execSync } = require('child_process');
|
|
|
|
const MakerBase = require('@electron-forge/maker-base').default;
|
|
|
|
const ARCH_MAP = { x64: 'x86_64', arm64: 'aarch64', ia32: 'i686', armv7l: 'armv7h' };
|
|
|
|
class MakerPacman extends MakerBase {
|
|
name = 'pacman';
|
|
defaultPlatforms = ['linux'];
|
|
|
|
isSupportedOnCurrentPlatform() {
|
|
if (process.platform !== 'linux') return false;
|
|
try {
|
|
execSync('command -v makepkg', { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async make({ dir, makeDir, targetArch, packageJSON, appName }) {
|
|
const pkgArch = ARCH_MAP[targetArch] || targetArch;
|
|
|
|
const cfg = this.config || {};
|
|
const pkgName = (cfg.name || appName || packageJSON.name).toLowerCase();
|
|
// pacman pkgver disallows '-'; map prerelease tags through.
|
|
const pkgVersion = String(packageJSON.version || '0.0.0').replace(/-/g, '_');
|
|
const pkgDesc = (cfg.description || packageJSON.description || '').replace(/"/g, '\\"');
|
|
const maintainer = cfg.maintainer || 'unknown';
|
|
const homepage = cfg.homepage || packageJSON.homepage || '';
|
|
const license = cfg.license || 'custom';
|
|
const bin = cfg.bin || pkgName;
|
|
const execName = cfg.executableName || appName || pkgName;
|
|
const mimeTypes = cfg.mimeType || [];
|
|
const depends = cfg.depends || [];
|
|
const iconSrc = cfg.icon;
|
|
|
|
const outDir = path.resolve(path.join(makeDir, 'pacman', targetArch));
|
|
await this.ensureDirectory(outDir);
|
|
|
|
// Clean prior contents so makepkg starts fresh each run.
|
|
for (const f of fs.readdirSync(outDir)) {
|
|
fs.rmSync(path.join(outDir, f), { recursive: true, force: true });
|
|
}
|
|
|
|
// Wrapper script — execs the packaged Electron binary, forwards args (incl. rowboat:// URLs).
|
|
fs.writeFileSync(
|
|
path.join(outDir, bin),
|
|
`#!/bin/sh\nexec "/opt/${pkgName}/${execName}" "$@"\n`,
|
|
{ mode: 0o755 },
|
|
);
|
|
|
|
const desktop = [
|
|
'[Desktop Entry]',
|
|
`Name=${appName || pkgName}`,
|
|
`Comment=${pkgDesc}`,
|
|
`Exec=${bin} %U`,
|
|
`Icon=${pkgName}`,
|
|
'Type=Application',
|
|
'Categories=Utility;',
|
|
'Terminal=false',
|
|
mimeTypes.length ? `MimeType=${mimeTypes.join(';')};` : null,
|
|
'',
|
|
].filter(Boolean).join('\n');
|
|
fs.writeFileSync(path.join(outDir, `${pkgName}.desktop`), desktop);
|
|
|
|
const sources = [bin, `${pkgName}.desktop`];
|
|
let iconInstall = '';
|
|
if (iconSrc && fs.existsSync(iconSrc)) {
|
|
fs.copyFileSync(iconSrc, path.join(outDir, 'icon.png'));
|
|
sources.push('icon.png');
|
|
iconInstall = ` install -Dm644 "$srcdir/icon.png" "$pkgdir/usr/share/icons/hicolor/512x512/apps/${pkgName}.png"`;
|
|
}
|
|
|
|
const sumsLine = sources.map(() => "'SKIP'").join(' ');
|
|
const sourceLine = sources.map((s) => `'${s}'`).join(' ');
|
|
const dependsLine = depends.map((d) => `'${d}'`).join(' ');
|
|
// Embed the packager output dir as a bash-safe literal.
|
|
const appDirEscaped = dir.replace(/'/g, `'\\''`);
|
|
|
|
const pkgbuild = `# Maintainer: ${maintainer}
|
|
# Auto-generated by maker-pacman.cjs — do not edit by hand.
|
|
pkgname=${pkgName}
|
|
pkgver=${pkgVersion}
|
|
pkgrel=1
|
|
pkgdesc="${pkgDesc}"
|
|
arch=('${pkgArch}')
|
|
url="${homepage}"
|
|
license=('${license}')
|
|
depends=(${dependsLine})
|
|
options=('!strip' '!debug')
|
|
source=(${sourceLine})
|
|
sha256sums=(${sumsLine})
|
|
|
|
_appdir='${appDirEscaped}'
|
|
|
|
package() {
|
|
install -dm755 "$pkgdir/opt/$pkgname"
|
|
cp -a "$_appdir/." "$pkgdir/opt/$pkgname/"
|
|
|
|
# Electron's sandbox helper needs setuid root for the namespace sandbox.
|
|
if [ -f "$pkgdir/opt/$pkgname/chrome-sandbox" ]; then
|
|
chmod 4755 "$pkgdir/opt/$pkgname/chrome-sandbox"
|
|
fi
|
|
|
|
install -Dm755 "$srcdir/${bin}" "$pkgdir/usr/bin/${bin}"
|
|
install -Dm644 "$srcdir/${pkgName}.desktop" "$pkgdir/usr/share/applications/${pkgName}.desktop"
|
|
${iconInstall}
|
|
}
|
|
`;
|
|
fs.writeFileSync(path.join(outDir, 'PKGBUILD'), pkgbuild);
|
|
|
|
execSync('makepkg -f --noconfirm --nodeps', {
|
|
cwd: outDir,
|
|
stdio: 'inherit',
|
|
env: { ...process.env, PKGEXT: '.pkg.tar.zst', CARCH: pkgArch },
|
|
});
|
|
|
|
return fs
|
|
.readdirSync(outDir)
|
|
.filter((f) => f.endsWith('.pkg.tar.zst'))
|
|
.map((f) => path.join(outDir, f));
|
|
}
|
|
}
|
|
|
|
module.exports = MakerPacman;
|
|
module.exports.default = MakerPacman;
|