feat(desktop): enhance packaging workflow and add smoke test for desktop app

This commit is contained in:
Sparsh 2026-04-21 19:24:21 +05:30
parent 558d7c3d82
commit ed83fd3e42
3 changed files with 86 additions and 13 deletions

View file

@ -11,13 +11,15 @@ permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
package:
name: Build ${{ matrix.label }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
permissions:
contents: read
strategy:
fail-fast: false
matrix:
@ -63,6 +65,11 @@ jobs:
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
- name: Smoke test unpacked desktop bundle
if: matrix.os == 'windows-latest'
run: node scripts/smoke-unpacked.mjs
working-directory: gitnexus-desktop
- name: Build desktop package
run: ${{ matrix.command }}
working-directory: gitnexus-desktop

View file

@ -272,14 +272,43 @@ const runNpm = (args, cwd) => {
}
};
const overlayDirectoryContents = (sourceDirectory, destinationDirectory) => {
const overlayDependencyPackages = (sourceDirectory, destinationDirectory, dependencyMap) => {
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,
});
for (const packageName of Object.keys(dependencyMap)) {
const sourcePath = path.join(sourceDirectory, ...packageName.split('/'));
const destinationPath = path.join(destinationDirectory, ...packageName.split('/'));
if (!existsSync(sourcePath)) {
continue;
}
if (existsSync(destinationPath)) {
continue;
}
mkdirSync(path.dirname(destinationPath), { recursive: true });
try {
cpSync(sourcePath, destinationPath, {
dereference: true,
force: true,
recursive: true,
});
} catch (error) {
const errorCode = error?.code;
const isLockedPathError =
(errorCode === 'EPIPE' || errorCode === 'EBUSY' || errorCode === 'EPERM') &&
existsSync(destinationPath);
if (!isLockedPathError) {
throw error;
}
console.warn(
`[gitnexus-desktop] Skipping locked dependency path during repair: ${destinationPath}`,
);
}
}
};
@ -313,9 +342,10 @@ const repairGitNexusPackages = (dependencyMap, label) => {
runNpm(['install', '--no-package-lock'], repairDirectory);
replaceDirectGitNexusPackages(dependencyMap);
overlayDirectoryContents(
overlayDependencyPackages(
path.join(repairDirectory, 'node_modules'),
path.join(gitnexusRoot, 'node_modules'),
dependencyMap,
);
} finally {
rmSync(repairDirectory, { force: true, recursive: true });
@ -331,6 +361,20 @@ const runCommand = (command, args) => {
});
};
const getPosixCommandLine = (pid) => {
if (!pid) {
return '';
}
const lookup = runCommand('ps', ['-p', String(pid), '-o', 'args=']);
if (lookup.status !== 0) {
return '';
}
return lookup.stdout.trim();
};
const findPortOwner = (port) => {
if (process.platform === 'win32') {
const lookup = runCommand('powershell.exe', [
@ -357,7 +401,7 @@ const findPortOwner = (port) => {
return JSON.parse(lookup.stdout.trim());
}
const lookup = runCommand('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpcn']);
const lookup = runCommand('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc']);
if (lookup.status !== 0 || !lookup.stdout.trim()) {
return null;
@ -370,11 +414,11 @@ const findPortOwner = (port) => {
owner.pid = Number.parseInt(line.slice(1), 10);
} else if (line.startsWith('c')) {
owner.name = line.slice(1);
} else if (line.startsWith('n')) {
owner.commandLine = line.slice(1);
}
}
owner.commandLine = getPosixCommandLine(owner.pid);
return owner.pid ? owner : null;
};

View file

@ -37,6 +37,7 @@ 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';
const IS_DESKTOP_SMOKE_TEST = process.env.GITNEXUS_DESKTOP_SMOKE_TEST === '1';
const MIME_TYPES: Record<string, string> = {
'.css': 'text/css; charset=utf-8',
@ -409,9 +410,24 @@ const sendStaticResponse = (assetPath: string | null, response: ServerResponse):
createReadStream(assetPath).pipe(response);
};
const getRequestedPath = (requestUrl: string): string | null => {
try {
return decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
} catch {
return null;
}
};
const handlePackagedWebRequest = (request: IncomingMessage, response: ServerResponse): void => {
const requestUrl = request.url ?? '/';
const requestedPath = decodeURIComponent(new URL(requestUrl, 'http://127.0.0.1').pathname);
const requestedPath = getRequestedPath(requestUrl);
if (!requestedPath) {
response.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Bad request');
return;
}
sendStaticResponse(normalizeStaticPath(GITNEXUS_WEB_PACKAGED_DIR, requestedPath), response);
};
@ -580,7 +596,7 @@ const loadShellRenderer = async (window: BrowserWindow): Promise<void> => {
return;
}
const rendererEntry = path.join(__dirname, '../renderer/src/renderer/index.html');
const rendererEntry = path.join(__dirname, '../renderer/index.html');
console.info(`[gitnexus-desktop] Loading shell renderer file: ${rendererEntry}`);
await window.loadFile(rendererEntry);
};
@ -732,6 +748,12 @@ async function createWindow(): Promise<void> {
if (!window.isDestroyed()) {
window.show();
if (IS_DESKTOP_SMOKE_TEST) {
setTimeout(() => {
app.quit();
}, 1_000);
}
}
}