mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
fix(desktop): ensure-gitnexus-runtime: stop stale backend, repair runtime deps
This commit is contained in:
parent
a84380a7a5
commit
89f038fc67
5 changed files with 478 additions and 89 deletions
21
gitnexus-desktop/package-lock.json
generated
21
gitnexus-desktop/package-lock.json
generated
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue