hermes-agent/apps/desktop/scripts/gen-linux-desktop-entry.mjs

98 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
// gen-linux-desktop-entry.mjs — generate the Linux .desktop entry with the
// same electron-builder machinery that packages the AppImage.
//
// Exec and Icon are emitted as @@EXEC@@ / @@ICON@@ placeholders. Consumers
// substitute real paths at install time (nix wrapper substituteInPlace, the
// runtime installer). The Exec placeholder sits inside double quotes, so a
// substituted absolute path needs no re-quoting — it only has to escape the
// four reserved characters (" $ ` \), which quoteDesktopExecEscape below
// does for runtime callers (nix store paths never contain them).
//
// This runs only at build time (dev deps present), never at runtime.
import { mkdirSync, writeFileSync } from "node:fs"
import { createRequire } from "node:module"
import path from "node:path"
import { fileURLToPath } from "node:url"
import { LinuxTargetHelper } from "../../../node_modules/app-builder-lib/dist/targets/linux/LinuxTargetHelper.js"
const require = createRequire(import.meta.url)
const here = path.dirname(fileURLToPath(import.meta.url))
export const EXEC_PLACEHOLDER = "@@EXEC@@"
export const ICON_PLACEHOLDER = "@@ICON@@"
/**
* Escape a path for substitution into the quoted Exec placeholder, per the
* desktop entry spec's reserved-character rule for quoted Exec tokens.
* @param {string} value
* @returns {string}
*/
export function quoteDesktopExecEscape(value) {
return value.replace(/["$`\\]/g, "\\$&")
}
/**
* Build the stub packager LinuxTargetHelper reads. Only the fields
* computeDesktopEntry() / getDesktopFileName() touch are provided; anything
* else throwing is a signal the helper grew a new dependency.
* @returns {{ helper: LinuxTargetHelper, config: import("app-builder-lib").Configuration }}
*/
function makeHelper() {
/** @type {import("app-builder-lib").Configuration} */
const config = require("../electron-builder.config.cjs")
const packager = {
appInfo: {
productName: config.productName,
description: config.linux?.synopsis ?? "",
},
executableName: config.executableName,
metadata: { desktopName: config.extraMetadata?.desktopName },
platformOptions: config.linux,
config,
fileAssociations: [],
}
// @ts-expect-error — deliberate stub; see the comment above.
return { helper: new LinuxTargetHelper(packager), config }
}
/**
* The variant's .desktop file name (appId-based, e.g.
* "com.nousresearch.hermes-light.desktop").
* @returns {string}
*/
export function desktopFileName() {
const { helper } = makeHelper()
return `${helper.getDesktopFileName()}.desktop`
}
/**
* The variant's .desktop entry text with @@EXEC@@ / @@ICON@@ placeholders.
* @returns {Promise<string>}
*/
export async function generateLinuxDesktopEntry() {
const { helper, config } = makeHelper()
return helper.computeDesktopEntry(
config.linux ?? {},
`"${EXEC_PLACEHOLDER}" %U`,
{ Icon: ICON_PLACEHOLDER },
)
}
// ── CLI: --out-dir DIR ──────────────────────────────────────────────────────
// Writes DIR/<fileName> and prints the written path.
const isCli = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(here, "gen-linux-desktop-entry.mjs")
if (isCli) {
const outDirArg = process.argv.find((a) => a.startsWith("--out-dir="))?.slice("--out-dir=".length)
if (!outDirArg) {
console.error("usage: gen-linux-desktop-entry.mjs --out-dir=DIR")
process.exit(1)
}
const file = path.join(outDirArg, desktopFileName())
mkdirSync(outDirArg, { recursive: true })
writeFileSync(file, await generateLinuxDesktopEntry())
console.log(file)
}