fix(desktop): ensure-gitnexus-runtime: stop stale backend, repair runtime deps

This commit is contained in:
Sparsh 2026-04-21 00:59:53 +05:30
parent a84380a7a5
commit 89f038fc67
5 changed files with 478 additions and 89 deletions

View file

@ -4934,23 +4934,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/react": {
"version": "19.2.5",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.5",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.5"
}
},
"node_modules/react-refresh": {
"version": "0.18.0",
"dev": true,
@ -5155,10 +5138,6 @@
"node": ">=11.0.0"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
"dev": true,

View file

@ -1,5 +1,17 @@
import { spawnSync } from 'node:child_process';
import { existsSync, readdirSync, statSync } from 'node:fs';
import {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@ -9,11 +21,56 @@ const workspaceRoot = path.resolve(__dirname, '..', '..');
const sharedRoot = path.join(workspaceRoot, 'gitnexus-shared');
const gitnexusRoot = path.join(workspaceRoot, 'gitnexus');
const gitnexusWebRoot = path.join(workspaceRoot, 'gitnexus-web');
const gitnexusLockfile = JSON.parse(
readFileSync(path.join(gitnexusRoot, 'package-lock.json'), 'utf8'),
);
const gitnexusPackageJson = JSON.parse(
readFileSync(path.join(gitnexusRoot, 'package.json'), 'utf8'),
);
const gitnexusModuleResolver = createRequire(path.join(gitnexusRoot, 'package.json'));
const gitnexusServerEntry = path.join(gitnexusRoot, 'dist', 'server', 'api.js');
const gitnexusCliEntry = path.join(gitnexusRoot, 'dist', 'cli', 'index.js');
const gitnexusServerPort = 4747;
const gitnexusWebDevPort = 5173;
const desktopRendererPort = 5174;
const shouldCleanupDevPort = process.argv.includes('--cleanup-dev-port');
const getNodeModulePackagePath = (packageRoot, packageName) => {
return path.join(packageRoot, 'node_modules', ...packageName.split('/'));
};
const getNodeModulePackageJsonPath = (packageRoot, packageName) => {
return path.join(packageRoot, 'node_modules', ...packageName.split('/'), 'package.json');
};
const getLockedPackageInstallSpecifier = (packageName) => {
const lockedPackage = gitnexusLockfile.packages?.[`node_modules/${packageName}`];
const lockedVersion = lockedPackage?.version;
if (!lockedVersion || String(lockedVersion).startsWith('file:')) {
return null;
}
return lockedVersion;
};
const getDependencySubset = (dependencyMap, packageNames, overrides = {}) => {
return Object.fromEntries(
packageNames.map((packageName) => {
const versionSpecifier =
overrides[packageName] ??
getLockedPackageInstallSpecifier(packageName) ??
dependencyMap?.[packageName];
if (!versionSpecifier) {
throw new Error(`Missing package specifier for ${packageName}.`);
}
return [packageName, versionSpecifier];
}),
);
};
const getEntryMtimeMs = (targetPath) => {
if (!existsSync(targetPath)) {
return 0;
@ -90,11 +147,58 @@ const gitnexusInstallInputs = [
path.join(sharedRoot, 'package-lock.json'),
];
const gitnexusInstallMarkerPaths = [
path.join(gitnexusRoot, 'node_modules', 'tsx'),
path.join(gitnexusRoot, 'node_modules', 'typescript'),
const gitnexusDesktopRuntimeDependencyNames = [
'@ladybugdb/core',
'@modelcontextprotocol/sdk',
'cli-progress',
'commander',
'cors',
'express',
'glob',
'graphology',
'graphology-indices',
'graphology-utils',
'ignore',
'js-yaml',
'lru-cache',
'mnemonist',
'pandemonium',
'uuid',
];
const gitnexusDesktopBuildDependencyNames = ['@types/node', 'gitnexus-shared', 'typescript'];
const gitnexusDesktopRuntimeDependencies = getDependencySubset(
gitnexusPackageJson.dependencies ?? {},
gitnexusDesktopRuntimeDependencyNames,
);
const gitnexusDesktopBuildDependencies = getDependencySubset(
gitnexusPackageJson.devDependencies ?? {},
gitnexusDesktopBuildDependencyNames,
{ 'gitnexus-shared': `file:${sharedRoot}` },
);
const gitnexusDesktopRuntimeInstallMarkerPaths = [
...gitnexusDesktopRuntimeDependencyNames.map((packageName) =>
getNodeModulePackageJsonPath(gitnexusRoot, packageName),
),
path.join(gitnexusRoot, 'node_modules', 'commander', 'index.js'),
path.join(gitnexusRoot, 'node_modules', '@ladybugdb', 'core', 'lbugjs.node'),
];
const gitnexusDesktopRuntimeResolutionChecks = [
'commander',
'@modelcontextprotocol/sdk/server/index.js',
'@modelcontextprotocol/sdk/server/streamableHttp.js',
'ignore',
'js-yaml',
];
const gitnexusDesktopBuildInstallMarkerPaths = gitnexusDesktopBuildDependencyNames.map(
(packageName) => getNodeModulePackageJsonPath(gitnexusRoot, packageName),
);
const gitnexusRuntimeInputs = [
path.join(gitnexusRoot, 'src'),
path.join(gitnexusRoot, 'scripts'),
@ -116,14 +220,32 @@ const isInstallStale = (inputPaths, installMarkerPaths) => {
return getNewestMtimeMs(inputPaths) > installMarkerMtimeMs;
};
const isGitNexusInstallStale = () => {
return isInstallStale(gitnexusInstallInputs, gitnexusInstallMarkerPaths);
};
const isGitNexusSharedInstallStale = () => {
return isInstallStale(gitnexusSharedInstallInputs, gitnexusSharedInstallMarkerPaths);
};
const canResolveGitNexusSpecifier = (specifier) => {
try {
gitnexusModuleResolver.resolve(specifier);
return true;
} catch {
return false;
}
};
const isGitNexusRuntimeInstallStale = () => {
return (
isInstallStale(gitnexusInstallInputs, gitnexusDesktopRuntimeInstallMarkerPaths) ||
gitnexusDesktopRuntimeResolutionChecks.some(
(specifier) => !canResolveGitNexusSpecifier(specifier),
)
);
};
const isGitNexusBuildInstallStale = () => {
return isInstallStale(gitnexusInstallInputs, gitnexusDesktopBuildInstallMarkerPaths);
};
const isGitNexusBuildStale = () => {
return getNewestMtimeMs(gitnexusRuntimeInputs) > getOldestOutputMtimeMs(gitnexusRuntimeOutputs);
};
@ -150,6 +272,56 @@ const runNpm = (args, cwd) => {
}
};
const overlayDirectoryContents = (sourceDirectory, destinationDirectory) => {
mkdirSync(destinationDirectory, { recursive: true });
for (const entry of readdirSync(sourceDirectory, { withFileTypes: true })) {
cpSync(path.join(sourceDirectory, entry.name), path.join(destinationDirectory, entry.name), {
force: true,
recursive: true,
});
}
};
const replaceDirectGitNexusPackages = (dependencyMap) => {
for (const packageName of Object.keys(dependencyMap)) {
rmSync(getNodeModulePackagePath(gitnexusRoot, packageName), {
force: true,
recursive: true,
});
}
};
const repairGitNexusPackages = (dependencyMap, label) => {
const repairDirectory = mkdtempSync(path.join(os.tmpdir(), 'gitnexus-desktop-gitnexus-repair-'));
try {
writeFileSync(
path.join(repairDirectory, 'package.json'),
JSON.stringify(
{
name: 'gitnexus-desktop-gitnexus-repair',
private: true,
dependencies: dependencyMap,
},
null,
2,
),
);
console.info(`[gitnexus-desktop] ${label}.`);
runNpm(['install', '--no-package-lock'], repairDirectory);
replaceDirectGitNexusPackages(dependencyMap);
overlayDirectoryContents(
path.join(repairDirectory, 'node_modules'),
path.join(gitnexusRoot, 'node_modules'),
);
} finally {
rmSync(repairDirectory, { force: true, recursive: true });
}
};
const runCommand = (command, args) => {
return spawnSync(command, args, {
encoding: 'utf8',
@ -250,8 +422,56 @@ const ensureDesktopRendererPortAvailable = () => {
killProcessTree(owner.pid);
};
const ensureGitNexusWebDevPortAvailable = () => {
const owner = findPortOwner(gitnexusWebDevPort);
if (!owner) {
return;
}
const signature = `${owner.name ?? ''} ${owner.commandLine ?? ''}`.toLowerCase();
const isGitNexusWebViteProcess = signature.includes('vite') && signature.includes('gitnexus-web');
if (!isGitNexusWebViteProcess) {
console.error(
`Port ${gitnexusWebDevPort} is already in use by ${owner.name ?? 'another process'} (PID ${owner.pid}). Stop that process and try again.`,
);
process.exit(1);
}
console.info(
`Stopping stale GitNexus web dev server on port ${gitnexusWebDevPort} (PID ${owner.pid}).`,
);
killProcessTree(owner.pid);
};
const ensureGitNexusServerPortAvailable = () => {
const owner = findPortOwner(gitnexusServerPort);
if (!owner) {
return;
}
const signature = `${owner.name ?? ''} ${owner.commandLine ?? ''}`.toLowerCase();
const isGitNexusServerProcess =
(signature.includes('dist\\cli\\index.js') || signature.includes('dist/cli/index.js')) &&
signature.includes('serve');
if (!isGitNexusServerProcess) {
console.error(
`Port ${gitnexusServerPort} is already in use by ${owner.name ?? 'another process'} (PID ${owner.pid}). Stop that process and try again.`,
);
process.exit(1);
}
console.info(`Stopping stale GitNexus backend on port ${gitnexusServerPort} (PID ${owner.pid}).`);
killProcessTree(owner.pid);
};
if (shouldCleanupDevPort) {
ensureGitNexusServerPortAvailable();
ensureDesktopRendererPortAvailable();
ensureGitNexusWebDevPortAvailable();
}
if (
@ -262,9 +482,8 @@ if (
runNpm(['ci'], sharedRoot);
}
if (!existsSync(path.join(gitnexusRoot, 'node_modules', 'tsx')) || isGitNexusInstallStale()) {
console.info('[gitnexus-desktop] Refreshing GitNexus dependencies.');
runNpm(['ci'], gitnexusRoot);
if (isGitNexusRuntimeInstallStale()) {
repairGitNexusPackages(gitnexusDesktopRuntimeDependencies, 'Repairing GitNexus runtime packages');
}
if (!existsSync(path.join(gitnexusWebRoot, 'node_modules', 'vite'))) {
@ -272,6 +491,16 @@ if (!existsSync(path.join(gitnexusWebRoot, 'node_modules', 'vite'))) {
}
if (getOldestOutputMtimeMs(gitnexusRuntimeOutputs) === 0 || isGitNexusBuildStale()) {
if (isGitNexusBuildInstallStale()) {
repairGitNexusPackages(
{
...gitnexusDesktopRuntimeDependencies,
...gitnexusDesktopBuildDependencies,
},
'Repairing GitNexus build tooling',
);
}
console.info('[gitnexus-desktop] Rebuilding GitNexus runtime artifacts.');
runNpm(['run', 'build'], gitnexusRoot);
}

View file

@ -12,6 +12,12 @@ const workspaceRoot = path.resolve(packageRoot, '..');
const gitnexusRoot = path.join(workspaceRoot, 'gitnexus');
const gitnexusWebRoot = path.join(workspaceRoot, 'gitnexus-web');
const releaseRoot = path.join(packageRoot, 'release');
const gitnexusPackageJson = JSON.parse(
fs.readFileSync(path.join(gitnexusRoot, 'package.json'), 'utf8'),
);
const gitnexusPackageLock = JSON.parse(
fs.readFileSync(path.join(gitnexusRoot, 'package-lock.json'), 'utf8'),
);
const desktopPackageJson = JSON.parse(
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
);
@ -52,20 +58,90 @@ const supportedTargetHosts = {
'--win': 'win32',
};
const artifactFileExtensions = new Set(['.AppImage', '.dmg', '.exe', '.msi', '.zip']);
const gitnexusRuntimeDependencyNames = Object.keys(gitnexusPackageJson.dependencies ?? {});
const packagedResourceEntries = [
{
from: path.join(gitnexusWebRoot, 'dist'),
to: 'gitnexus-web',
},
{
from: path.join(gitnexusRoot, 'dist'),
to: path.join('gitnexus', 'dist'),
},
{
from: path.join(gitnexusRoot, 'hooks'),
to: path.join('gitnexus', 'hooks'),
},
{
from: path.join(gitnexusRoot, 'skills'),
to: path.join('gitnexus', 'skills'),
},
{
from: path.join(gitnexusRoot, 'vendor'),
to: path.join('gitnexus', 'vendor'),
},
{
from: path.join(gitnexusRoot, 'package.json'),
to: path.join('gitnexus', 'package.json'),
},
];
const toBuilderRelativePath = (targetPath) => {
return path.relative(packageRoot, targetPath) || '.';
};
const getNodeModulePath = (packageName) => {
return path.join(gitnexusRoot, 'node_modules', ...packageName.split('/'));
};
const getRuntimeDependencyClosure = () => {
const visited = new Set();
const queue = [...gitnexusRuntimeDependencyNames];
while (queue.length > 0) {
const packageName = queue.shift();
if (!packageName || visited.has(packageName)) {
continue;
}
visited.add(packageName);
const lockEntry = gitnexusPackageLock.packages?.[`node_modules/${packageName}`];
const dependencyNames = Object.keys({
...(lockEntry?.dependencies ?? {}),
...(lockEntry?.optionalDependencies ?? {}),
});
for (const dependencyName of dependencyNames) {
if (!visited.has(dependencyName)) {
queue.push(dependencyName);
}
}
}
return [...visited].filter((packageName) => fs.existsSync(getNodeModulePath(packageName))).sort();
};
const packagedRuntimeNodeModules = getRuntimeDependencyClosure();
const builderArgs = process.argv.slice(2);
const requestedTargets = builderArgs.filter((argument) => argument in supportedTargetHosts);
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_GITNEXUS_DIST: toBuilderRelativePath(path.join(gitnexusRoot, 'dist')),
GITNEXUS_DESKTOP_GITNEXUS_HOOKS: toBuilderRelativePath(path.join(gitnexusRoot, 'hooks')),
GITNEXUS_DESKTOP_GITNEXUS_NODE_MODULES: toBuilderRelativePath(
path.join(gitnexusRoot, 'node_modules'),
),
GITNEXUS_DESKTOP_GITNEXUS_PACKAGE_JSON: toBuilderRelativePath(
path.join(gitnexusRoot, 'package.json'),
),
GITNEXUS_DESKTOP_GITNEXUS_SKILLS: toBuilderRelativePath(path.join(gitnexusRoot, 'skills')),
GITNEXUS_DESKTOP_GITNEXUS_VENDOR: toBuilderRelativePath(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'),
GITNEXUS_DESKTOP_WEB_DIST: toBuilderRelativePath(path.join(gitnexusWebRoot, 'dist')),
};
const builderCommand = [
@ -97,6 +173,33 @@ const tryRunCommand = (command, cwd, extraEnv = {}) => {
}
};
const mirrorDirectory = (sourceDirectory, destinationDirectory) => {
if (process.platform === 'win32') {
fs.mkdirSync(destinationDirectory, { recursive: true });
try {
execSync(
`robocopy "${sourceDirectory}" "${destinationDirectory}" /MIR /NFL /NDL /NJH /NJS /NP`,
{
cwd: packageRoot,
stdio: 'inherit',
},
);
} catch (error) {
const exitCode = error.status ?? 16;
if (exitCode > 7) {
throw error;
}
}
return;
}
fs.rmSync(destinationDirectory, { force: true, recursive: true });
fs.cpSync(sourceDirectory, destinationDirectory, { force: true, recursive: true });
};
const canResolveBuilderRuntimeModule = (moduleName) => {
try {
builderUtilRequire.resolve(moduleName);
@ -117,6 +220,63 @@ const overlayDirectoryContents = (sourceDirectory, destinationDirectory) => {
}
};
const resolvePackagedResourceRoots = () => {
const candidateRoots = [
path.join(outputDir, 'win-unpacked', 'resources'),
path.join(outputDir, 'linux-unpacked', 'resources'),
path.join(
outputDir,
`${desktopPackageJson.productName ?? 'GitNexus Desktop'}.app`,
'Contents',
'Resources',
),
];
return candidateRoots.filter((candidateRoot) => fs.existsSync(candidateRoot));
};
const syncPackagedRuntimeResources = () => {
const resourceRoots = resolvePackagedResourceRoots();
if (resourceRoots.length === 0) {
return;
}
for (const resourceRoot of resourceRoots) {
const packagedNodeModulesRoot = path.join(resourceRoot, 'gitnexus', 'node_modules');
fs.rmSync(packagedNodeModulesRoot, { force: true, recursive: true });
fs.mkdirSync(packagedNodeModulesRoot, { recursive: true });
const sourceNodeModulesLock = path.join(gitnexusRoot, 'node_modules', '.package-lock.json');
const destinationNodeModulesLock = path.join(packagedNodeModulesRoot, '.package-lock.json');
if (fs.existsSync(sourceNodeModulesLock)) {
fs.cpSync(sourceNodeModulesLock, destinationNodeModulesLock, { force: true });
}
for (const packageName of packagedRuntimeNodeModules) {
const sourcePackagePath = getNodeModulePath(packageName);
const destinationPackagePath = path.join(packagedNodeModulesRoot, ...packageName.split('/'));
mirrorDirectory(sourcePackagePath, destinationPackagePath);
}
for (const entry of packagedResourceEntries) {
const destinationPath = path.join(resourceRoot, entry.to);
if (fs.statSync(entry.from).isDirectory()) {
mirrorDirectory(entry.from, destinationPath);
continue;
}
fs.rmSync(destinationPath, { force: true, recursive: true });
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
fs.cpSync(entry.from, destinationPath, { force: true });
}
}
};
const repairAppBuilderLibPackage = () => {
const repairDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), 'gitnexus-desktop-app-builder-lib-'),
@ -262,6 +422,7 @@ runCommand('npm run bundle', packageRoot);
runCommand('npm run build', gitnexusWebRoot);
runCommand(builderCommand, packageRoot, builderEnvironment);
syncPackagedRuntimeResources();
const artifacts = listArtifacts(outputDir);

View file

@ -33,7 +33,8 @@ const GITNEXUS_WEB_DEV_ROOT_FALLBACK = path.resolve(__dirname, '../../../gitnexu
const GITNEXUS_PACKAGED_RUNTIME_DIR = path.join(process.resourcesPath, 'gitnexus');
const GITNEXUS_WEB_PACKAGED_DIR = path.join(process.resourcesPath, 'gitnexus-web');
const GITNEXUS_WEB_EXPECTED_MARKERS = ['<title>GitNexus</title>', '<div id="root"></div>'];
const GITNEXUS_WEB_READY_TIMEOUT_MS = 30_000;
const GITNEXUS_SERVER_READY_TIMEOUT_MS = 30_000;
const GITNEXUS_WEB_READY_TIMEOUT_MS = 60_000;
const GITNEXUS_WEB_READY_POLL_MS = 500;
const PACKAGED_WEB_SERVER_HOST = '127.0.0.1';
@ -67,6 +68,10 @@ let packagedWebServerUrl: string | null = null;
const openWindows = new Set<BrowserWindow>();
const contentViews = new WeakMap<BrowserWindow, BrowserView>();
const getDesktopShellTitlebarHeight = (): number => {
return process.platform === 'darwin' ? DESKTOP_SHELL_TITLEBAR_HEIGHT : 0;
};
type DesktopShellState = {
appName: string;
isAlwaysOnTop: boolean;
@ -222,7 +227,15 @@ const getGitNexusWebDevRoot = (): string => {
return existsSync(GITNEXUS_WEB_DEV_ROOT) ? GITNEXUS_WEB_DEV_ROOT : GITNEXUS_WEB_DEV_ROOT_FALLBACK;
};
const getGitNexusWebViteCliEntry = (): string => {
return path.join(getGitNexusWebDevRoot(), 'node_modules', 'vite', 'bin', 'vite.js');
};
const getNodeCommand = (): string => {
if (app.isPackaged) {
return process.execPath;
}
return (
process.env.npm_node_execpath ||
process.env.NODE ||
@ -230,15 +243,26 @@ const getNodeCommand = (): string => {
);
};
const getNodeProcessEnvironment = (overrides: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv => {
const environment = {
...process.env,
...overrides,
};
if (app.isPackaged) {
environment.ELECTRON_RUN_AS_NODE = '1';
}
return environment;
};
const spawnGitNexusServer = (): ChildProcess => {
const childProcess = spawn(
getNodeCommand(),
[getGitNexusCliEntry(), 'serve', '--host', GITNEXUS_HOST],
{
cwd: getGitNexusRuntimeDir(),
env: {
...process.env,
},
env: getNodeProcessEnvironment(),
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
},
@ -262,7 +286,7 @@ const spawnGitNexusServer = (): ChildProcess => {
};
const waitForGitNexusServerReady = async (): Promise<void> => {
const deadline = Date.now() + GITNEXUS_WEB_READY_TIMEOUT_MS;
const deadline = Date.now() + GITNEXUS_SERVER_READY_TIMEOUT_MS;
while (Date.now() < deadline) {
if (await isAnyHttpUrlReady(GITNEXUS_SERVER_HEALTH_URLS)) {
@ -290,35 +314,23 @@ const waitForGitNexusServerReady = async (): Promise<void> => {
};
const spawnWebDevServer = (): ChildProcess => {
const command = process.platform === 'win32' ? 'cmd.exe' : 'npm';
const args =
process.platform === 'win32'
? [
'/d',
'/s',
'/c',
`npm run dev -- --host ${GITNEXUS_WEB_DEV_HOST} --port ${GITNEXUS_WEB_DEV_PORT} --strictPort`,
]
: [
'run',
'dev',
'--',
'--host',
GITNEXUS_WEB_DEV_HOST,
'--port',
String(GITNEXUS_WEB_DEV_PORT),
'--strictPort',
];
const childProcess = spawn(command, args, {
cwd: getGitNexusWebDevRoot(),
env: {
...process.env,
BROWSER: 'none',
const childProcess = spawn(
getNodeCommand(),
[
getGitNexusWebViteCliEntry(),
'--host',
GITNEXUS_WEB_DEV_HOST,
'--port',
String(GITNEXUS_WEB_DEV_PORT),
'--strictPort',
],
{
cwd: getGitNexusWebDevRoot(),
env: getNodeProcessEnvironment({ BROWSER: 'none' }),
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
},
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
});
);
childProcess.stdout?.on('data', (chunk) => {
appendProcessOutput(chunk);
@ -489,7 +501,7 @@ const getDesktopShellState = (window: BrowserWindow): DesktopShellState => {
isAlwaysOnTop: window.isAlwaysOnTop(),
isMaximized: window.isMaximized() || window.isFullScreen(),
platform: process.platform,
titleBarHeight: DESKTOP_SHELL_TITLEBAR_HEIGHT,
titleBarHeight: getDesktopShellTitlebarHeight(),
};
};
@ -519,12 +531,13 @@ const updateContentViewBounds = (window: BrowserWindow): void => {
}
const [width, height] = window.getContentSize();
const titlebarHeight = getDesktopShellTitlebarHeight();
contentView.setBounds({
x: 0,
y: DESKTOP_SHELL_TITLEBAR_HEIGHT,
y: titlebarHeight,
width: Math.max(width, 1),
height: Math.max(height - DESKTOP_SHELL_TITLEBAR_HEIGHT, 1),
height: Math.max(height - titlebarHeight, 1),
});
};
@ -666,9 +679,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,

View file

@ -134,6 +134,8 @@ html,
body,
#root {
height: 100%;
background-color: var(--color-void);
color-scheme: dark;
}
body {
@ -147,27 +149,32 @@ body {
/* ═══════════════════════════════════════════════════════════════
CUSTOM SCROLLBAR
═══════════════════════════════════════════════════════════════ */
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: #2a2a3a #0a0a10;
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.scrollbar-thin::-webkit-scrollbar {
width: 8px;
height: 8px;
*::-webkit-scrollbar-track {
background: #07070d;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: var(--color-deep);
*::-webkit-scrollbar-thumb {
min-height: 44px;
background: #3a3d4f;
border: 2px solid #07070d;
border-radius: 999px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: var(--color-border-default);
border-radius: 4px;
*::-webkit-scrollbar-thumb:hover {
background: #50546b;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: var(--color-text-muted);
*::-webkit-scrollbar-thumb:active {
background: #656b88;
}
*::-webkit-scrollbar-corner {
background: #07070d;
}
/* ═══════════════════════════════════════════════════════════════