qwen-code/packages/desktop/scripts/smoke-packaged-app.mjs
2026-04-25 03:38:17 +08:00

173 lines
4.3 KiB
JavaScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { existsSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import process from 'node:process';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const outputRoot = resolve(packageRoot, '../../dist/desktop');
const resourcesDir = findResourcesDir(outputRoot);
const appBundlePath = findAppBundlePath(outputRoot);
const requiredResources = [
join(resourcesDir, 'qwen-cli', 'cli.js'),
join(resourcesDir, 'app.asar'),
];
for (const resourcePath of requiredResources) {
if (!existsSync(resourcePath)) {
throw new Error(`Missing packaged desktop resource: ${resourcePath}`);
}
}
console.log(`Packaged app bundle: ${appBundlePath}`);
console.log(`Packaged resources: ${resourcesDir}`);
if (process.argv.includes('--launch')) {
await smokeLaunch(appBundlePath);
}
console.log('Desktop package smoke check passed.');
function findResourcesDir(root) {
if (!existsSync(root)) {
throw new Error(`Desktop package output is missing: ${root}`);
}
if (process.platform === 'darwin') {
const appBundle = findAppBundlePath(root);
return join(appBundle, 'Contents', 'Resources');
}
const candidates = collectDirectories(root).filter((dir) =>
existsSync(join(dir, 'resources')),
);
const resources = candidates
.map((dir) => join(dir, 'resources'))
.find((dir) => existsSync(join(dir, 'app.asar')));
if (!resources) {
throw new Error(`Could not find packaged resources under ${root}`);
}
return resources;
}
function findAppBundlePath(root) {
if (process.platform !== 'darwin') {
const executableDir = collectDirectories(root).find((dir) =>
existsSync(join(dir, 'resources', 'app.asar')),
);
if (!executableDir) {
throw new Error(`Could not find packaged app directory under ${root}`);
}
return executableDir;
}
const appBundle = collectDirectories(root).find((dir) =>
dir.endsWith('.app'),
);
if (!appBundle) {
throw new Error(`Could not find .app bundle under ${root}`);
}
return appBundle;
}
async function smokeLaunch(appBundlePath) {
const executablePath = getExecutablePath(appBundlePath);
if (!existsSync(executablePath)) {
throw new Error(`Packaged executable is missing: ${executablePath}`);
}
const child = spawn(executablePath, [], {
env: {
...process.env,
QWEN_DESKTOP_SMOKE: '1',
},
stdio: 'ignore',
});
const exit = await waitForEarlyExit(child, 3000);
if (exit) {
throw new Error(
`Packaged app exited during smoke launch: code=${exit.code}, signal=${exit.signal}`,
);
}
child.kill('SIGTERM');
await waitForExit(child, 5000);
console.log(`Packaged launch smoke passed: ${executablePath}`);
}
function getExecutablePath(appBundlePath) {
if (process.platform === 'darwin') {
return join(appBundlePath, 'Contents', 'MacOS', 'Qwen Code');
}
if (process.platform === 'win32') {
return join(appBundlePath, 'Qwen Code.exe');
}
return join(appBundlePath, 'qwen-code');
}
function waitForEarlyExit(child, ms) {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
cleanup();
resolve(null);
}, ms);
const handleExit = (code, signal) => {
cleanup();
resolve({ code, signal });
};
const cleanup = () => {
clearTimeout(timeout);
child.off('exit', handleExit);
};
child.once('exit', handleExit);
});
}
function waitForExit(child, ms) {
if (child.exitCode !== null || child.signalCode !== null) {
return Promise.resolve();
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
child.kill('SIGKILL');
}, ms);
child.once('exit', () => {
clearTimeout(timeout);
resolve();
});
});
}
function collectDirectories(root) {
const result = [];
const pending = [root];
while (pending.length > 0) {
const current = pending.pop();
if (!current) {
continue;
}
result.push(current);
for (const entry of readdirSync(current)) {
const fullPath = join(current, entry);
if (statSync(fullPath).isDirectory()) {
pending.push(fullPath);
}
}
}
return result;
}