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:
DEV_DSW
2026-04-24 17:02:59 +08:00
parent e11a2296cc
commit 4c61e93c3e
42 changed files with 12560 additions and 224 deletions

View File

@@ -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,
};

View File

@@ -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);
}
}

View 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);
}