Skip to content

Commit 9ea3249

Browse files
aboseclaude
andcommitted
feat: vary electron AppImage desktopName/productName per stage
Adds patchElectronStageBranding helper invoked from all 4 electron build entry points after config-effective.json is written. Writes the kebab-case stage identifier into both src-electron/electron-builder.yml productName (becomes the .desktop StartupWMClass) and src-electron/package.json desktopName (Electron 41.5+ Wayland app_id) so they match per stage: dev -> phoenix-code-experimental-build stage -> phoenix-code-pre-release prod -> phoenix-code Without per-stage identifiers the AppImages collapse into one taskbar entry / icon slot when installed side-by-side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 816373b commit 9ea3249

6 files changed

Lines changed: 43 additions & 6 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-build/ci-setupDistFolders.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { EOL } from "os";
1010
import os from "os";
1111
import fs from 'fs';
12-
import {patchTauriConfigWithMetricsHTML} from "./utils.js";
12+
import {patchTauriConfigWithMetricsHTML, patchElectronStageBranding} from "./utils.js";
1313

1414
const __filename = fileURLToPath(import.meta.url);
1515
const __dirname = dirname(__filename);
@@ -182,6 +182,8 @@ function createElectronReleaseAssets() {
182182
console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL);
183183
console.log('version:', effectiveConfig.version);
184184
fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2));
185+
186+
patchElectronStageBranding(electronDir, effectiveConfig.productName);
185187
}
186188

187189
ciCreateTauriDistReleaseConfig();

src-build/createDistRelease.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { dirname, join } from 'path';
44
import fs from 'fs';
55
import * as os from 'os';
66
import chalk from 'chalk';
7-
import { getPlatformDetails, patchTauriConfigWithMetricsHTML } from './utils.js';
7+
import { getPlatformDetails, patchTauriConfigWithMetricsHTML, patchElectronStageBranding } from './utils.js';
88

99
const __filename = fileURLToPath(import.meta.url);
1010
const __dirname = dirname(__filename);
@@ -164,6 +164,8 @@ function createElectronConfig() {
164164
console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL);
165165
console.log('version:', effectiveConfig.version);
166166
fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2));
167+
168+
patchElectronStageBranding(electronDir, effectiveConfig.productName);
167169
}
168170

169171
function buildElectron() {

src-build/createDistTestRelease.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { dirname, join } from 'path';
44
import fs from 'fs';
55
import * as os from 'os';
66
import chalk from 'chalk';
7-
import { getPlatformDetails } from './utils.js';
7+
import { getPlatformDetails, patchElectronStageBranding } from './utils.js';
88

99
const __filename = fileURLToPath(import.meta.url);
1010
const __dirname = dirname(__filename);
@@ -160,6 +160,8 @@ function createElectronConfig() {
160160
console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL);
161161
console.log('version:', effectiveConfig.version);
162162
fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2));
163+
164+
patchElectronStageBranding(electronDir, effectiveConfig.productName);
163165
}
164166

165167
function buildElectron() {

src-build/createSrcRelease.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { dirname, join } from 'path';
44
import fs from 'fs';
55
import * as os from 'os';
66
import chalk from 'chalk';
7-
import { getPlatformDetails } from './utils.js';
7+
import { getPlatformDetails, patchElectronStageBranding } from './utils.js';
88

99
const __filename = fileURLToPath(import.meta.url);
1010
const __dirname = dirname(__filename);
@@ -136,6 +136,8 @@ function createElectronConfig() {
136136
console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL);
137137
console.log('version:', effectiveConfig.version);
138138
fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2));
139+
140+
patchElectronStageBranding(electronDir, effectiveConfig.productName);
139141
}
140142

141143
function buildElectron() {

src-build/utils.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,35 @@ const METRIC_URL_FOR_STAGE = {
103103
"production": "https://phcode.dev/desktop-metrics.html"
104104
};
105105

106+
/**
107+
* Patches the electron build inputs so each stage has a unique app identity:
108+
* - src-electron/package.json `desktopName` (Electron 41.5+ uses this as the Wayland app_id)
109+
* - src-electron/electron-builder.yml `productName` (becomes the .desktop StartupWMClass + binary name)
110+
* Both are set to the kebab-case form of `productName` so they match. Without matching values,
111+
* the running window can't be paired to its .desktop entry and falls back to the generic Electron icon.
112+
*
113+
* @param {string} electronDir - absolute path to src-electron/
114+
* @param {string} productName - stage-specific display name from config-effective.json
115+
* (e.g. "Phoenix Code", "Phoenix Code Pre-release", "Phoenix Code Experimental Build")
116+
*/
117+
export function patchElectronStageBranding(electronDir, productName) {
118+
const appId = productName.toLowerCase().trim().replace(/\s+/g, '-');
119+
console.log(`Patching electron stage branding -> ${appId}`);
120+
121+
const pkgPath = path.join(electronDir, 'package.json');
122+
const pkg = JSON.parse(fs.readFileSync(pkgPath));
123+
pkg.desktopName = appId;
124+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
125+
126+
const builderPath = path.join(electronDir, 'electron-builder.yml');
127+
const builderYml = fs.readFileSync(builderPath, 'utf8');
128+
const patched = builderYml.replace(/^productName:.*$/m, `productName: ${appId}`);
129+
if (patched === builderYml) {
130+
throw new Error(`Could not find productName line in ${builderPath}`);
131+
}
132+
fs.writeFileSync(builderPath, patched);
133+
}
134+
106135
export function patchTauriConfigWithMetricsHTML(tauriConf, useClonedPhoenix) {
107136
const platform = getPlatformDetails().platform;
108137
let phoenixConfigPath = (platform === "win") ? `${__dirname}\\..\\..\\phoenix\\dist\\config.json`

0 commit comments

Comments
 (0)