Add unit tests for skill capabilities, skill planner, and UV setup
- Implement tests for random ID generation, ensuring preference for crypto.randomUUID. - Create tests for runtime context capabilities, validating the injection of enabled skill capabilities. - Add tests for skill capability parsing, including classification and command example extraction. - Introduce tests for the skill planner, verifying tool call planning based on user requests and attachment requirements. - Establish tests for UV setup, ensuring proper handling of Python installation scenarios and environment checks.
This commit is contained in:
@@ -15,6 +15,83 @@ const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
const ARCH_NAME_MAP = {
|
||||
0: 'ia32',
|
||||
1: 'x64',
|
||||
2: 'armv7l',
|
||||
3: 'arm64',
|
||||
4: 'universal',
|
||||
};
|
||||
|
||||
const REQUIRED_RUNTIME_BINARIES = {
|
||||
win32: ['uv.exe', 'node.exe'],
|
||||
darwin: ['uv'],
|
||||
linux: ['uv'],
|
||||
};
|
||||
|
||||
function normalizePackArch(arch) {
|
||||
if (typeof arch === 'string') {
|
||||
return arch;
|
||||
}
|
||||
|
||||
return ARCH_NAME_MAP[arch] || String(arch);
|
||||
}
|
||||
|
||||
function normalizePlatformName(electronPlatformName) {
|
||||
switch (electronPlatformName) {
|
||||
case 'win':
|
||||
return 'win32';
|
||||
case 'mac':
|
||||
return 'darwin';
|
||||
default:
|
||||
return electronPlatformName;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBundledRuntimeTarget(context) {
|
||||
const platform = normalizePlatformName(context.electronPlatformName);
|
||||
const arch = normalizePackArch(context.arch);
|
||||
return `${platform}-${arch}`;
|
||||
}
|
||||
|
||||
async function copyBundledRuntimeBinaries(context, overrides = {}) {
|
||||
const target = resolveBundledRuntimeTarget(context);
|
||||
const platform = normalizePlatformName(context.electronPlatformName);
|
||||
const sourceRoot = overrides.sourceRoot || path.join(__dirname, '..', 'resources', 'bin', target);
|
||||
const resourcesDir = overrides.resourcesDir || resolveResourcesDir(context);
|
||||
const destRoot = overrides.destRoot || path.join(resourcesDir, 'bin');
|
||||
const requiredFiles = overrides.requiredFiles || REQUIRED_RUNTIME_BINARIES[platform] || [];
|
||||
|
||||
if (!(await fs.pathExists(sourceRoot))) {
|
||||
throw new Error(
|
||||
`[after-pack] Missing bundled runtime directory for ${target}: ${sourceRoot}. ` +
|
||||
`Run the bundled runtime preparation step before packaging.`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.ensureDir(destRoot);
|
||||
|
||||
const entries = await fs.readdir(sourceRoot, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fs.copy(path.join(sourceRoot, entry.name), path.join(destRoot, entry.name), {
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
|
||||
const missing = requiredFiles.filter((fileName) => !fs.existsSync(path.join(destRoot, fileName)));
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`[after-pack] Missing required bundled runtime binaries for ${target}: ${missing.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[after-pack] Copied bundled runtime binaries for ${target} -> ${destRoot}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove development artifacts from a directory (recursive).
|
||||
* Removes: test directories, TypeScript definitions, source maps, docs, etc.
|
||||
@@ -131,6 +208,8 @@ module.exports = async function afterPack(context) {
|
||||
|
||||
console.log(`Running afterPack hook for ${platform}-${arch}`);
|
||||
console.log(`App output directory: ${appOutDir}`);
|
||||
|
||||
await copyBundledRuntimeBinaries(context, { resourcesDir });
|
||||
|
||||
// 1. Handle electron/scripts/ directory
|
||||
const scriptsSrc = path.join(__dirname, '..', 'electron/scripts');
|
||||
@@ -219,3 +298,10 @@ module.exports = async function afterPack(context) {
|
||||
|
||||
console.log('afterPack hook completed successfully');
|
||||
};
|
||||
|
||||
module.exports.__test__ = {
|
||||
normalizePackArch,
|
||||
normalizePlatformName,
|
||||
resolveBundledRuntimeTarget,
|
||||
copyBundledRuntimeBinaries,
|
||||
};
|
||||
|
||||
@@ -53,11 +53,15 @@ async function setupTarget(id) {
|
||||
const tempDir = path.join(ROOT_DIR, 'temp_uv_extract');
|
||||
const archivePath = path.join(ROOT_DIR, target.filename);
|
||||
const downloadUrl = `${BASE_URL}/${target.filename}`;
|
||||
const outputBinary = path.join(targetDir, target.binName);
|
||||
|
||||
echo(chalk.blue`\n📦 Setting up uv for ${id}...`);
|
||||
|
||||
// Cleanup & Prep
|
||||
await fs.remove(targetDir);
|
||||
// Only remove the target binary, not the entire directory,
|
||||
// so uv downloads do not wipe other bundled runtime files like node.exe.
|
||||
if (await fs.pathExists(outputBinary)) {
|
||||
await fs.remove(outputBinary);
|
||||
}
|
||||
await fs.remove(tempDir);
|
||||
await fs.ensureDir(targetDir);
|
||||
await fs.ensureDir(tempDir);
|
||||
@@ -96,7 +100,7 @@ async function setupTarget(id) {
|
||||
// uv archives usually contain a folder named after the target
|
||||
const folderName = target.filename.replace('.tar.gz', '').replace('.zip', '');
|
||||
const sourceBin = path.join(tempDir, folderName, target.binName);
|
||||
const destBin = path.join(targetDir, target.binName);
|
||||
const destBin = outputBinary;
|
||||
|
||||
if (await fs.pathExists(sourceBin)) {
|
||||
await fs.move(sourceBin, destBin, { overwrite: true });
|
||||
@@ -171,4 +175,4 @@ try {
|
||||
echo(chalk.yellow` Packaging will continue without uv binary.`);
|
||||
// Exit with code 0 to allow packaging to continue
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
98
scripts/ensure-bundled-runtime-binaries.mjs
Normal file
98
scripts/ensure-bundled-runtime-binaries.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
|
||||
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
||||
const BIN_ROOT = path.join(ROOT_DIR, 'resources', 'bin');
|
||||
|
||||
const PLATFORM_CONFIG = {
|
||||
win32: {
|
||||
scripts: ['uv:download:win', 'node:download:win'],
|
||||
required: [
|
||||
['win32-x64', 'uv.exe'],
|
||||
['win32-x64', 'node.exe'],
|
||||
['win32-arm64', 'uv.exe'],
|
||||
['win32-arm64', 'node.exe'],
|
||||
],
|
||||
},
|
||||
darwin: {
|
||||
scripts: ['uv:download:mac'],
|
||||
required: [
|
||||
['darwin-x64', 'uv'],
|
||||
['darwin-arm64', 'uv'],
|
||||
],
|
||||
},
|
||||
linux: {
|
||||
scripts: ['uv:download:linux'],
|
||||
required: [
|
||||
['linux-x64', 'uv'],
|
||||
['linux-arm64', 'uv'],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function getMissingBinaries(requiredEntries) {
|
||||
return requiredEntries
|
||||
.map(([target, fileName]) => ({
|
||||
target,
|
||||
fileName,
|
||||
filePath: path.join(BIN_ROOT, target, fileName),
|
||||
}))
|
||||
.filter(({ filePath }) => !existsSync(filePath));
|
||||
}
|
||||
|
||||
function runPnpmScript(scriptName) {
|
||||
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
||||
const result = spawnSync(command, ['run', scriptName], {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to run "${scriptName}" (exit=${result.status ?? 'unknown'})`);
|
||||
}
|
||||
}
|
||||
|
||||
function formatMissing(missingEntries) {
|
||||
return missingEntries
|
||||
.map(({ target, fileName }) => `${target}/${fileName}`)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function main() {
|
||||
const config = PLATFORM_CONFIG[process.platform];
|
||||
if (!config) {
|
||||
console.log(`[bundled-runtime] No bundled runtime requirements for platform ${process.platform}; skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let missing = getMissingBinaries(config.required);
|
||||
if (missing.length === 0) {
|
||||
console.log(`[bundled-runtime] All required bundled runtime binaries are present for ${process.platform}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[bundled-runtime] Missing bundled runtime binaries: ${formatMissing(missing)}`);
|
||||
for (const scriptName of config.scripts) {
|
||||
runPnpmScript(scriptName);
|
||||
}
|
||||
|
||||
missing = getMissingBinaries(config.required);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`[bundled-runtime] Required binaries are still missing after download: ${formatMissing(missing)}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[bundled-runtime] Bundled runtime binaries are ready for ${process.platform}.`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user