fix(desktop): validate unpacked app startup in packaging flow

This commit is contained in:
Sparsh 2026-04-20 21:58:01 +05:30
parent c5238debf6
commit a84380a7a5
6 changed files with 374 additions and 178 deletions

View file

@ -7,12 +7,11 @@ on:
- gitnexus-desktop/**
workflow_dispatch:
permissions:
contents: read
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: false
jobs:
package:

View file

@ -17,15 +17,16 @@ npm run build:linux
- `npm run build:win` creates a Windows NSIS installer `.exe`.
- `npm run build:mac` creates a macOS `.dmg`.
- `npm run build:linux` creates a Linux `.AppImage`.
- `npm run smoke:unpacked` launches the latest unpacked desktop build, verifies startup, and exits.
Build output is written under `gitnexus-desktop/release/<timestamp>/`.
## PR Artifacts
The `Desktop Packaging` GitHub Actions workflow uploads preview desktop artifacts for pull requests that touch `gitnexus-desktop/**` or `.github/workflows/desktop-packaging.yml`.
The `Desktop Packaging` GitHub Actions workflow builds an unpacked desktop app, smoke-tests startup, then uploads preview desktop artifacts for pull requests that touch `.github/workflows/desktop-packaging.yml`, `gitnexus-desktop/**`, `gitnexus/**`, `gitnexus-web/**`, or `gitnexus-shared/**`.
- `gitnexus-desktop-windows` contains the Windows NSIS installer `.exe` and `win-unpacked/`.
- `gitnexus-desktop-macos` contains the macOS `.dmg` and `mac*/` output.
- `gitnexus-desktop-linux` contains the Linux `.AppImage` and `linux-unpacked/`.
GitHub artifact URLs are run-specific and expire, so use the artifact names above from the latest successful PR workflow run.
GitHub artifact URLs are run-specific and expire, so use the artifact names above from the latest successful PR workflow run.

View file

@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { existsSync, readdirSync, statSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@ -10,9 +10,124 @@ const sharedRoot = path.join(workspaceRoot, 'gitnexus-shared');
const gitnexusRoot = path.join(workspaceRoot, 'gitnexus');
const gitnexusWebRoot = path.join(workspaceRoot, 'gitnexus-web');
const gitnexusServerEntry = path.join(gitnexusRoot, 'dist', 'server', 'api.js');
const gitnexusCliEntry = path.join(gitnexusRoot, 'dist', 'cli', 'index.js');
const desktopRendererPort = 5174;
const shouldCleanupDevPort = process.argv.includes('--cleanup-dev-port');
const getEntryMtimeMs = (targetPath) => {
if (!existsSync(targetPath)) {
return 0;
}
return statSync(targetPath).mtimeMs;
};
const getPathMtimeMs = (targetPath) => {
if (!existsSync(targetPath)) {
return 0;
}
const stats = statSync(targetPath);
if (!stats.isDirectory()) {
return stats.mtimeMs;
}
let newestMtimeMs = stats.mtimeMs;
for (const entry of readdirSync(targetPath, { withFileTypes: true })) {
if (entry.name === '.git' || entry.name === 'dist' || entry.name === 'node_modules') {
continue;
}
newestMtimeMs = Math.max(newestMtimeMs, getPathMtimeMs(path.join(targetPath, entry.name)));
}
return newestMtimeMs;
};
const getNewestMtimeMs = (paths) => {
return paths.reduce((latestMtimeMs, targetPath) => {
return Math.max(latestMtimeMs, getPathMtimeMs(targetPath));
}, 0);
};
const getOldestOutputMtimeMs = (paths) => {
let oldestMtimeMs = Number.POSITIVE_INFINITY;
for (const targetPath of paths) {
const mtimeMs = getPathMtimeMs(targetPath);
if (mtimeMs === 0) {
return 0;
}
oldestMtimeMs = Math.min(oldestMtimeMs, mtimeMs);
}
return Number.isFinite(oldestMtimeMs) ? oldestMtimeMs : 0;
};
const gitnexusSharedSourceInputs = [
path.join(sharedRoot, 'src'),
path.join(sharedRoot, 'package.json'),
path.join(sharedRoot, 'package-lock.json'),
path.join(sharedRoot, 'tsconfig.json'),
];
const gitnexusSharedInstallInputs = [
path.join(sharedRoot, 'package.json'),
path.join(sharedRoot, 'package-lock.json'),
path.join(sharedRoot, 'tsconfig.json'),
];
const gitnexusSharedInstallMarkerPaths = [path.join(sharedRoot, 'node_modules', 'typescript')];
const gitnexusInstallInputs = [
path.join(gitnexusRoot, 'package.json'),
path.join(gitnexusRoot, 'package-lock.json'),
path.join(sharedRoot, 'package.json'),
path.join(sharedRoot, 'package-lock.json'),
];
const gitnexusInstallMarkerPaths = [
path.join(gitnexusRoot, 'node_modules', 'tsx'),
path.join(gitnexusRoot, 'node_modules', 'typescript'),
];
const gitnexusRuntimeInputs = [
path.join(gitnexusRoot, 'src'),
path.join(gitnexusRoot, 'scripts'),
path.join(gitnexusRoot, 'package.json'),
path.join(gitnexusRoot, 'package-lock.json'),
path.join(gitnexusRoot, 'tsconfig.json'),
...gitnexusSharedSourceInputs,
];
const gitnexusRuntimeOutputs = [gitnexusServerEntry, gitnexusCliEntry];
const isInstallStale = (inputPaths, installMarkerPaths) => {
if (installMarkerPaths.some((targetPath) => !existsSync(targetPath))) {
return true;
}
const installMarkerMtimeMs = Math.min(...installMarkerPaths.map(getEntryMtimeMs));
return getNewestMtimeMs(inputPaths) > installMarkerMtimeMs;
};
const isGitNexusInstallStale = () => {
return isInstallStale(gitnexusInstallInputs, gitnexusInstallMarkerPaths);
};
const isGitNexusSharedInstallStale = () => {
return isInstallStale(gitnexusSharedInstallInputs, gitnexusSharedInstallMarkerPaths);
};
const isGitNexusBuildStale = () => {
return getNewestMtimeMs(gitnexusRuntimeInputs) > getOldestOutputMtimeMs(gitnexusRuntimeOutputs);
};
const runNpmAttempt = (args, cwd) => {
const result = spawnSync('npm', args, {
cwd,
@ -139,11 +254,16 @@ if (shouldCleanupDevPort) {
ensureDesktopRendererPortAvailable();
}
if (!existsSync(path.join(sharedRoot, 'node_modules', 'typescript'))) {
if (
!existsSync(path.join(sharedRoot, 'node_modules', 'typescript')) ||
isGitNexusSharedInstallStale()
) {
console.info('[gitnexus-desktop] Refreshing gitnexus-shared dependencies.');
runNpm(['ci'], sharedRoot);
}
if (!existsSync(path.join(gitnexusRoot, 'node_modules', 'tsx'))) {
if (!existsSync(path.join(gitnexusRoot, 'node_modules', 'tsx')) || isGitNexusInstallStale()) {
console.info('[gitnexus-desktop] Refreshing GitNexus dependencies.');
runNpm(['ci'], gitnexusRoot);
}
@ -151,6 +271,7 @@ if (!existsSync(path.join(gitnexusWebRoot, 'node_modules', 'vite'))) {
runNpm(['ci'], gitnexusWebRoot);
}
if (!existsSync(gitnexusServerEntry)) {
if (getOldestOutputMtimeMs(gitnexusRuntimeOutputs) === 0 || isGitNexusBuildStale()) {
console.info('[gitnexus-desktop] Rebuilding GitNexus runtime artifacts.');
runNpm(['run', 'build'], gitnexusRoot);
}

View file

@ -1,5 +1,6 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@ -21,12 +22,19 @@ const electronVersion =
desktopPackageJson.devDependencies?.electron?.replace(/^[^\d]*/, '') ?? '41.2.1';
const electronBuilderVersion =
desktopPackageJson.devDependencies?.['electron-builder']?.replace(/^[^\d]*/, '') ?? '26.8.1';
const bundledNodeExecutableName = process.platform === 'win32' ? 'node.exe' : 'node';
const electronBuilderCliPath = path.join(packageRoot, 'node_modules', 'electron-builder', 'cli.js');
const builderUtilRequire = createRequire(
path.join(packageRoot, 'node_modules', 'builder-util', 'out', 'util.js'),
);
const requiredBuilderRuntimeModules = ['app-builder-bin'];
const appBuilderLibVersion =
desktopPackageLock.packages?.['node_modules/app-builder-lib']?.version ??
electronBuilderVersion.replace(/^[^\d]*/, '');
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
const outputDir = path.join(releaseRoot, stamp);
const generatedBuilderConfigPath = path.join(outputDir, 'electron-builder.generated.json');
const latestReleasePointerPath = path.join(releaseRoot, '.latest-unpacked-release');
const electronBuilderConfigPath = path.join(packageRoot, 'electron-builder.yml');
const requiredNsisTemplateFiles = [
path.join(packageRoot, 'node_modules', 'app-builder-lib', 'templates', 'nsis', 'messages.yml'),
path.join(
@ -48,78 +56,23 @@ const artifactFileExtensions = new Set(['.AppImage', '.dmg', '.exe', '.msi', '.z
const builderArgs = process.argv.slice(2);
const requestedTargets = builderArgs.filter((argument) => argument in supportedTargetHosts);
const createBuilderConfig = () => ({
appId: 'io.github.abhigyanpatwari.gitnexus.desktop',
electronVersion,
productName: 'GitNexus Desktop',
directories: {
output: outputDir,
buildResources: path.join(packageRoot, 'build'),
},
files: ['dist/**/*', 'package.json'],
extraResources: [
{
from: path.join(gitnexusWebRoot, 'dist'),
to: 'gitnexus-web',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'dist'),
to: 'gitnexus/dist',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'node_modules'),
to: 'gitnexus/node_modules',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'hooks'),
to: 'gitnexus/hooks',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'skills'),
to: 'gitnexus/skills',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'vendor'),
to: 'gitnexus/vendor',
filter: ['**/*'],
},
{
from: path.join(gitnexusRoot, 'package.json'),
to: 'gitnexus/package.json',
},
],
asar: false,
npmRebuild: false,
win: {
signAndEditExecutable: false,
icon: path.join(packageRoot, 'build', 'icon.png'),
target: ['nsis'],
},
mac: {
category: 'public.app-category.developer-tools',
icon: path.join(packageRoot, 'build', 'icon.png'),
target: ['dmg'],
},
linux: {
category: 'Development',
icon: path.join(packageRoot, 'build', 'icon.png'),
target: ['AppImage'],
},
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true,
},
});
const builderEnvironment = {
GITNEXUS_DESKTOP_GITNEXUS_DIST: path.join(gitnexusRoot, 'dist'),
GITNEXUS_DESKTOP_GITNEXUS_HOOKS: path.join(gitnexusRoot, 'hooks'),
GITNEXUS_DESKTOP_GITNEXUS_NODE_MODULES: path.join(gitnexusRoot, 'node_modules'),
GITNEXUS_DESKTOP_GITNEXUS_PACKAGE_JSON: path.join(gitnexusRoot, 'package.json'),
GITNEXUS_DESKTOP_GITNEXUS_SKILLS: path.join(gitnexusRoot, 'skills'),
GITNEXUS_DESKTOP_GITNEXUS_VENDOR: path.join(gitnexusRoot, 'vendor'),
GITNEXUS_DESKTOP_NODE_EXECUTABLE: process.execPath,
GITNEXUS_DESKTOP_NODE_RESOURCE_PATH: `gitnexus-node/${bundledNodeExecutableName}`,
GITNEXUS_DESKTOP_WEB_DIST: path.join(gitnexusWebRoot, 'dist'),
};
const builderCommand = [
`npx --yes -p electron@${electronVersion} -p electron-builder@${electronBuilderVersion} electron-builder`,
`--config "${generatedBuilderConfigPath}"`,
`node "${electronBuilderCliPath}"`,
`--config "${electronBuilderConfigPath}"`,
`-c.electronVersion=${electronVersion}`,
`-c.directories.output="${outputDir}"`,
'--publish never',
...builderArgs,
].join(' ');
@ -144,6 +97,15 @@ const tryRunCommand = (command, cwd, extraEnv = {}) => {
}
};
const canResolveBuilderRuntimeModule = (moduleName) => {
try {
builderUtilRequire.resolve(moduleName);
return true;
} catch {
return false;
}
};
const overlayDirectoryContents = (sourceDirectory, destinationDirectory) => {
fs.mkdirSync(destinationDirectory, { recursive: true });
@ -202,56 +164,62 @@ const assertSupportedHostForRequestedTargets = () => {
};
const ensureDesktopToolchainHealthy = () => {
if (!requestedTargets.includes('--win')) {
const missingBuilderRuntimeModules = requiredBuilderRuntimeModules.filter(
(moduleName) => !canResolveBuilderRuntimeModule(moduleName),
);
const missingNsisTemplates = requestedTargets.includes('--win')
? requiredNsisTemplateFiles.filter((filePath) => !fs.existsSync(filePath))
: [];
if (missingBuilderRuntimeModules.length === 0 && missingNsisTemplates.length === 0) {
return;
}
const missingNsisTemplates = requiredNsisTemplateFiles.filter(
(filePath) => !fs.existsSync(filePath),
);
if (missingNsisTemplates.length > 0) {
console.warn(
'[build] electron-builder NSIS templates are missing. Restoring app-builder-lib package contents first...',
);
if (missingNsisTemplates.length === 0) {
try {
repairAppBuilderLibPackage();
} catch {
// Fall through to install-based repair paths below.
}
}
const remainingMissingBuilderRuntimeModules = requiredBuilderRuntimeModules.filter(
(moduleName) => !canResolveBuilderRuntimeModule(moduleName),
);
const remainingMissingNsisTemplates = requestedTargets.includes('--win')
? requiredNsisTemplateFiles.filter((filePath) => !fs.existsSync(filePath))
: [];
if (
remainingMissingBuilderRuntimeModules.length === 0 &&
remainingMissingNsisTemplates.length === 0
) {
return;
}
console.warn(
'[build] electron-builder NSIS templates are missing. Restoring app-builder-lib package contents first...',
'[build] electron-builder installation is incomplete. Restoring desktop dependencies with npm ci...',
);
runCommand('npm ci', packageRoot);
try {
repairAppBuilderLibPackage();
} catch {
// Fall through to install-based repair paths below.
}
if (requiredNsisTemplateFiles.every((filePath) => fs.existsSync(filePath))) {
return;
}
console.warn(
'[build] Package overlay repair was not enough. Reinstalling electron-builder next...',
const unresolvedBuilderRuntimeModules = requiredBuilderRuntimeModules.filter(
(moduleName) => !canResolveBuilderRuntimeModule(moduleName),
);
const targetedRepairWorked = tryRunCommand(
`npm install --no-save --package-lock=false electron-builder@${electronBuilderVersion}`,
packageRoot,
);
const targetedRepairResolvedTemplates =
targetedRepairWorked && requiredNsisTemplateFiles.every((filePath) => fs.existsSync(filePath));
if (!targetedRepairResolvedTemplates) {
console.warn('[build] Targeted repair was not enough. Falling back to npm ci...');
runCommand('npm ci', packageRoot);
}
const unresolvedTemplates = requiredNsisTemplateFiles.filter(
(filePath) => !fs.existsSync(filePath),
);
if (unresolvedTemplates.length > 0) {
if (unresolvedBuilderRuntimeModules.length > 0 || unresolvedTemplates.length > 0) {
throw new Error(
`electron-builder is missing required NSIS templates after reinstall:\n${unresolvedTemplates.join('\n')}`,
[
'electron-builder is missing required runtime modules after reinstall:',
...unresolvedBuilderRuntimeModules,
...unresolvedTemplates,
].join('\n'),
);
}
};
@ -282,7 +250,7 @@ const listArtifacts = (directoryPath) => {
};
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(generatedBuilderConfigPath, JSON.stringify(createBuilderConfig(), null, 2));
fs.writeFileSync(latestReleasePointerPath, `${outputDir}\n`);
assertSupportedHostForRequestedTargets();
ensureDesktopToolchainHealthy();
@ -293,7 +261,7 @@ runCommand('npm run bundle', packageRoot);
runCommand('npm run build', gitnexusWebRoot);
runCommand(builderCommand, packageRoot);
runCommand(builderCommand, packageRoot, builderEnvironment);
const artifacts = listArtifacts(outputDir);

View file

@ -0,0 +1,164 @@
import { spawn } from 'node:child_process';
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageRoot = path.resolve(__dirname, '..');
const releaseRoot = path.join(packageRoot, 'release');
const latestReleasePointerPath = path.join(releaseRoot, '.latest-unpacked-release');
const productName = 'GitNexus Desktop';
const smokeTimeoutMs = 120_000;
const releaseDirectoryPattern = /^\d{4}-\d{2}-\d{2}T.*Z$/;
const listTimestampedReleaseDirs = () => {
if (!existsSync(releaseRoot)) {
return [];
}
return readdirSync(releaseRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && releaseDirectoryPattern.test(entry.name))
.map((entry) => path.join(releaseRoot, entry.name))
.sort((leftPath, rightPath) => statSync(rightPath).mtimeMs - statSync(leftPath).mtimeMs);
};
const findFirstMatch = (rootDir, predicate) => {
for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
const entryPath = path.join(rootDir, entry.name);
if (predicate(entry, entryPath)) {
return entryPath;
}
if (entry.isDirectory()) {
const nestedMatch = findFirstMatch(entryPath, predicate);
if (nestedMatch) {
return nestedMatch;
}
}
}
return null;
};
const resolveExecutablePath = (releaseDir) => {
if (process.platform === 'win32') {
return path.join(releaseDir, 'win-unpacked', `${productName}.exe`);
}
if (process.platform === 'darwin') {
return findFirstMatch(releaseDir, (entry, entryPath) => {
return (
entry.isFile() &&
entry.name === productName &&
entryPath.includes(`${productName}.app${path.sep}Contents${path.sep}MacOS${path.sep}`)
);
});
}
const linuxUnpackedDir = path.join(releaseDir, 'linux-unpacked');
if (!existsSync(linuxUnpackedDir)) {
return null;
}
const candidateNames = [productName, 'gitnexus-desktop'];
for (const candidateName of candidateNames) {
const candidatePath = path.join(linuxUnpackedDir, candidateName);
if (existsSync(candidatePath) && statSync(candidatePath).isFile()) {
return candidatePath;
}
}
return findFirstMatch(linuxUnpackedDir, (entry, entryPath) => {
return entry.isFile() && path.extname(entryPath) === '';
});
};
const resolveLatestExecutable = () => {
if (existsSync(latestReleasePointerPath)) {
const releaseDir = readFileSync(latestReleasePointerPath, 'utf8').trim();
const executablePath = releaseDir ? resolveExecutablePath(releaseDir) : null;
return {
executablePath: executablePath && existsSync(executablePath) ? executablePath : null,
releaseDir: releaseDir || null,
};
}
const releaseDirs = listTimestampedReleaseDirs();
for (const releaseDir of releaseDirs) {
const executablePath = resolveExecutablePath(releaseDir);
if (executablePath && existsSync(executablePath)) {
return { executablePath, releaseDir };
}
}
return {
executablePath: null,
releaseDir: releaseDirs[0] ?? null,
};
};
const runSmokeTest = async () => {
const { executablePath, releaseDir: latestReleaseDir } = resolveLatestExecutable();
if (!latestReleaseDir) {
throw new Error(
'No timestamped desktop release directory was found. Run npm run build:dir first.',
);
}
if (!executablePath || !existsSync(executablePath)) {
throw new Error(`Unable to locate the unpacked desktop executable under ${latestReleaseDir}.`);
}
console.info(`[gitnexus-desktop] Smoke testing unpacked app: ${executablePath}`);
await new Promise((resolve, reject) => {
const childProcess = spawn(executablePath, [], {
cwd: path.dirname(executablePath),
env: {
...process.env,
GITNEXUS_DESKTOP_SMOKE_TEST: '1',
},
stdio: 'inherit',
windowsHide: true,
});
const timeoutHandle = setTimeout(() => {
childProcess.kill(process.platform === 'win32' ? undefined : 'SIGTERM');
reject(
new Error(`Timed out waiting for unpacked desktop smoke test after ${smokeTimeoutMs}ms.`),
);
}, smokeTimeoutMs);
childProcess.once('error', (error) => {
clearTimeout(timeoutHandle);
reject(error);
});
childProcess.once('exit', (code, signal) => {
clearTimeout(timeoutHandle);
if (code === 0) {
resolve(undefined);
return;
}
reject(
new Error(
`Unpacked desktop smoke test exited with code ${code ?? 'null'}${signal ? ` (signal: ${signal})` : ''}.`,
),
);
});
});
};
await runSmokeTest();

View file

@ -57,37 +57,6 @@ const MIME_TYPES: Record<string, string> = {
'.woff2': 'font/woff2',
};
const EMBEDDED_APP_SCROLLBAR_CSS = `
:root {
scrollbar-color: rgba(148, 163, 184, 0.42) rgba(7, 10, 18, 0.08);
}
* {
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: rgba(7, 10, 18, 0.08);
}
::-webkit-scrollbar-thumb {
border: 3px solid transparent;
border-radius: 999px;
background: rgba(148, 163, 184, 0.34);
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(148, 163, 184, 0.52);
background-clip: padding-box;
}
`;
let serverReadyPromise: Promise<void> | null = null;
let gitNexusServerProcess: ChildProcess | null = null;
let gitNexusServerOutput = '';
@ -430,20 +399,7 @@ const sendStaticResponse = (assetPath: string | null, response: ServerResponse):
const handlePackagedWebRequest = (request: IncomingMessage, response: ServerResponse): void => {
const requestUrl = request.url ?? '/';
let requestedPath: string;
try {
requestedPath = decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
} catch (error) {
if (error instanceof URIError) {
response.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Bad request');
return;
}
throw error;
}
const requestedPath = decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
sendStaticResponse(normalizeStaticPath(GITNEXUS_WEB_PACKAGED_DIR, requestedPath), response);
};
@ -533,7 +489,7 @@ const getDesktopShellState = (window: BrowserWindow): DesktopShellState => {
isAlwaysOnTop: window.isAlwaysOnTop(),
isMaximized: window.isMaximized() || window.isFullScreen(),
platform: process.platform,
titleBarHeight: getDesktopShellTitlebarHeight(),
titleBarHeight: DESKTOP_SHELL_TITLEBAR_HEIGHT,
};
};
@ -555,10 +511,6 @@ const getWindowFromSender = (sender: Electron.WebContents): BrowserWindow => {
return window;
};
const getDesktopShellTitlebarHeight = (): number => {
return process.platform === 'darwin' ? DESKTOP_SHELL_TITLEBAR_HEIGHT : 0;
};
const updateContentViewBounds = (window: BrowserWindow): void => {
const contentView = contentViews.get(window);
@ -567,13 +519,12 @@ const updateContentViewBounds = (window: BrowserWindow): void => {
}
const [width, height] = window.getContentSize();
const titlebarHeight = getDesktopShellTitlebarHeight();
contentView.setBounds({
x: 0,
y: titlebarHeight,
y: DESKTOP_SHELL_TITLEBAR_HEIGHT,
width: Math.max(width, 1),
height: Math.max(height - titlebarHeight, 1),
height: Math.max(height - DESKTOP_SHELL_TITLEBAR_HEIGHT, 1),
});
};
@ -604,14 +555,6 @@ const createEmbeddedContentView = (contentUrl: string): BrowserView => {
openExternalUrlIfSafe(navigationUrl);
});
contentView.webContents.on('dom-ready', () => {
void contentView.webContents.insertCSS(EMBEDDED_APP_SCROLLBAR_CSS).catch((error) => {
console.warn(
`[gitnexus-desktop] Failed to inject embedded app CSS: ${getErrorMessage(error)}`,
);
});
});
return contentView;
};
@ -624,7 +567,7 @@ const loadShellRenderer = async (window: BrowserWindow): Promise<void> => {
return;
}
const rendererEntry = path.join(__dirname, '../renderer/index.html');
const rendererEntry = path.join(__dirname, '../renderer/src/renderer/index.html');
console.info(`[gitnexus-desktop] Loading shell renderer file: ${rendererEntry}`);
await window.loadFile(rendererEntry);
};
@ -723,9 +666,9 @@ async function createWindow(): Promise<void> {
title: DESKTOP_APP_NAME,
icon: DESKTOP_APP_ICON_PATH,
backgroundColor: DESKTOP_BACKGROUND_COLOR,
frame: false,
show: false,
autoHideMenuBar: true,
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
webPreferences: {
preload: path.join(__dirname, '../preload/preload.js'),
contextIsolation: true,