From f293fb287d6c0f26b8163c0b901595216a41153d Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 17 Feb 2026 09:54:11 +1300 Subject: [PATCH] Add hook script to update and publish appstream metainfo on electron build (#1719) --- electron-builder.yml | 1 + scripts/after-all-artifact-build.mjs | 45 ++++++++++++++++++++++++++++ scripts/update-app-stream.mjs | 43 +++++++++++++++++++------- 3 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 scripts/after-all-artifact-build.mjs diff --git a/electron-builder.yml b/electron-builder.yml index 26f597bef..fdbf2a27d 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -57,6 +57,7 @@ linux: artifactName: ${productName}-${os}-${arch}.${ext} npmRebuild: false +afterAllArtifactBuild: scripts/after-all-artifact-build.mjs publish: provider: github owner: jeffvli diff --git a/scripts/after-all-artifact-build.mjs b/scripts/after-all-artifact-build.mjs new file mode 100644 index 000000000..69e9f3f04 --- /dev/null +++ b/scripts/after-all-artifact-build.mjs @@ -0,0 +1,45 @@ +import { execSync } from 'child_process'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Electron-builder afterAllArtifactBuild hook + * Runs the app stream update script only for Linux builds + * Returns the metainfo file path to be included in published artifacts + */ + +// This is not a typescript file, and is called by electron-builder, so we cannot use typescript features here. +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export default async function afterAllArtifactBuild(buildResult) { + // Check if this build includes Linux as a target + const isLinux = buildResult.platformToTargets + .keys() + .some((platform) => platform.name === 'linux'); + + if (isLinux) { + const updateScriptPath = path.join(__dirname, 'update-app-stream.mjs'); + const projectRoot = path.resolve(__dirname, '..'); + const metainfoFile = path.resolve(projectRoot, 'org.jeffvli.feishin.metainfo.xml'); + + console.log('Running app stream update for Linux build...'); + + try { + execSync(`node ${updateScriptPath} --replace-if-version-missing`, { + cwd: projectRoot, + stdio: 'inherit', + }); + + // Return the metainfo file to be included in published artifacts + return [metainfoFile]; + } catch (error) { + console.error('Failed to update app stream:', error.message); + throw error; + } + } + + // Return empty array if not a Linux build + return []; +} diff --git a/scripts/update-app-stream.mjs b/scripts/update-app-stream.mjs index 13b3491d4..be1a73c3f 100644 --- a/scripts/update-app-stream.mjs +++ b/scripts/update-app-stream.mjs @@ -3,30 +3,51 @@ import fs from 'fs'; import path from 'path'; const args = process.argv.slice(2); -if (args.length > 3) { - console.error('Usage: node update-app-stream.js [package-file] [date] [metainfo-file]'); + +// Parse flags and positional arguments +const flags = args.filter((arg) => arg.startsWith('--')); +const positionalArgs = args.filter((arg) => !arg.startsWith('--')); +const replaceIfVersionMissing = flags.includes('--replace-if-version-missing'); + +if (positionalArgs.length > 3) { + console.error( + 'Usage: node update-app-stream.js [package-file] [date] [metainfo-file] [--replace-if-version-missing]', + ); process.exit(1); } -const packageFile = args[0] || path.resolve(process.cwd(), 'package.json'); +const packageFile = positionalArgs[0] || path.resolve(process.cwd(), 'package.json'); const packageContent = fs.readFileSync(packageFile, 'utf8'); const packageJson = JSON.parse(packageContent); const version = packageJson.version; -const time = Math.floor((Date.parse(args[1]) || Date.now()) / 1000); -const metainfoFile = args[2] || path.resolve(process.cwd(), 'org.jeffvli.feishin.metainfo.xml'); +const time = Math.floor((Date.parse(positionalArgs[1]) || Date.now()) / 1000); +const metainfoFile = + positionalArgs[2] || path.resolve(process.cwd(), 'org.jeffvli.feishin.metainfo.xml'); const parser = new XMLParser({ ignoreAttributes: false }); const metainfoContent = fs.readFileSync(metainfoFile, 'utf8'); const metainfo = parser.parse(metainfoContent); -if (!metainfo.component.releases.release.find((release) => release['@_version'] === version)) { - metainfo.component.releases.release.unshift({ - '@_date': new Date(time * 1000).toISOString().split('T')[0], - '@_type': version.includes('-') ? 'development' : 'stable', - '@_version': version, - }); +const newRelease = { + '@_date': new Date(time * 1000).toISOString().split('T')[0], + '@_type': version.includes('-') ? 'development' : 'stable', + '@_version': version, +}; + +if (replaceIfVersionMissing) { + // Replace all releases with only the current version + metainfo.component.releases.release = [newRelease]; +} else { + // Default behavior: add new release if it doesn't exist + const releaseExists = + metainfo.component.releases.release.findIndex( + (release) => release['@_version'] === version, + ) !== -1; + if (!releaseExists) { + metainfo.component.releases.release.unshift(newRelease); + } } const builder = new XMLBuilder({ format: true, ignoreAttributes: false, indentBy: ' ' });