chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
#!/usr/bin/env node
/**
* 多平台 uv 集成:构建前按当前平台准备 resources/uv/bin/
*
* 1) 若已存在 resources/uv/<platform-arch>/bin/,则复制到 resources/uv/bin/
* 2) 否则从 GitHub Releases (astral-sh/uv) 下载当前平台包,解压到 <platform-arch>/ 再复制到 bin/
*
* 打包时 electron-builder 的 extraResources 会打包 resources/uv → .app/Contents/Resources/uv
* 运行时 getUvBinPath() 使用 Resources/uv/bin/uv 或 uv.exe
*
* 平台 keydarwin-arm64 | darwin-x64 | win32-x64 | win32-arm64 | linux-x64 | linux-arm64
*/
const path = require('path');
const fs = require('fs');
const https = require('https');
const { execSync } = require('child_process');
const { getProjectRoot } = require('../utils/project-paths');
const UV_VERSION = '0.10.8';
const projectRoot = getProjectRoot();
const uvRoot = path.join(projectRoot, 'resources', 'uv');
const cacheDir = path.join(uvRoot, '.cache');
// Node 与 Electron 一致darwin | win32 | linuxx64 | arm64
function getPlatformKey() {
const p = process.platform;
const a = process.env.TARGET_ARCH || process.arch;
return `${p}-${a}`;
}
// 当前平台对应的 uv 官方 release 资源文件名(不含 .tar.gz / .zip
const UV_ASSET_SUFFIX = {
'darwin-arm64': 'uv-aarch64-apple-darwin',
'darwin-x64': 'uv-x86_64-apple-darwin',
'win32-x64': 'uv-x86_64-pc-windows-msvc',
'win32-arm64': 'uv-aarch64-pc-windows-msvc',
'linux-x64': 'uv-x86_64-unknown-linux-gnu',
'linux-arm64': 'uv-aarch64-unknown-linux-gnu',
};
function copyDirRecursive(src, dest) {
if (!fs.existsSync(src)) return;
fs.mkdirSync(dest, { recursive: true });
for (const name of fs.readdirSync(src)) {
const s = path.join(src, name);
const d = path.join(dest, name);
if (fs.statSync(s).isDirectory()) {
copyDirRecursive(s, d);
} else {
fs.copyFileSync(s, d);
}
}
}
function copyToDestBin(key) {
const srcBin = path.join(uvRoot, key, 'bin');
const destBin = path.join(uvRoot, 'bin');
if (!fs.existsSync(srcBin)) return false;
if (!fs.existsSync(destBin)) fs.mkdirSync(destBin, { recursive: true });
for (const f of fs.readdirSync(srcBin)) {
const src = path.join(srcBin, f);
if (fs.statSync(src).isFile()) {
fs.copyFileSync(src, path.join(destBin, f));
console.log(`[prepare-uv] ${key} -> bin/${f}`);
}
}
return true;
}
/**
* 下载文件到 cache 目录。使用 preferredFilename 避免重定向后 URL 过长导致 ENAMETOOLONG。
* @param {string} url - 下载地址
* @param {string} [preferredFilename] - 保存文件名(建议传入,如 uv-aarch64-apple-darwin.tar.gz
*/
function download(url, preferredFilename) {
return new Promise((resolve, reject) => {
const filename = preferredFilename || path.basename(url.split('?')[0]) || 'download';
const file = path.join(cacheDir, filename);
// 检查缓存是否存在且有效Windows: > 1MB
if (fs.existsSync(file)) {
try {
const stats = fs.statSync(file);
const minSize = process.platform === 'win32' ? 1024 * 1024 : 100 * 1024;
if (stats.size > minSize) {
console.log(`[prepare-uv] 使用缓存: ${filename} (${Math.round(stats.size / 1024 / 1024)} MB)`);
resolve(file);
return;
}
} catch (e) {
// 文件可能被锁定,删除后重新下载
console.log(`[prepare-uv] 缓存文件异常,删除后重新下载`);
try { fs.unlinkSync(file); } catch (_) {}
}
}
const stream = fs.createWriteStream(file);
https.get(url, { headers: { 'User-Agent': 'Qiming-Agent-Build' } }, (res) => {
if (res.statusCode === 302 || res.statusCode === 301) {
stream.close();
fs.unlink(file, () => {});
const loc = res.headers.location;
return download(loc.startsWith('http') ? loc : new URL(loc, url).href, preferredFilename).then(resolve).catch(reject);
}
if (res.statusCode !== 200) {
stream.close();
fs.unlink(file, () => {});
return reject(new Error(`HTTP ${res.statusCode} ${url}`));
}
res.pipe(stream);
stream.on('finish', () => { stream.close(); resolve(file); });
stream.on('error', (e) => { stream.close(); reject(e); });
}).on('error', (e) => { stream.close(); reject(e); });
});
}
function extractArchive(archivePath, outDir) {
fs.mkdirSync(outDir, { recursive: true });
const ext = path.extname(archivePath).toLowerCase();
const isZip = ext === '.zip';
if (isZip) {
if (process.platform === 'win32') {
// Windows: 使用 tar 解压 zipWindows 10+ 内置 tar
// 注意:需要等待文件流完全关闭,避免 "being used by another process" 错误
try {
execSync(`tar -xf "${archivePath}" -C "${outDir}"`, { stdio: 'inherit' });
} catch (e) {
// 如果 tar 失败,尝试使用 PowerShell可能遇到文件锁定问题
console.log('[prepare-uv] tar 解压失败,尝试 PowerShell...');
const psPath = archivePath.replace(/'/g, "''");
const psDest = outDir.replace(/'/g, "''");
execSync(
`powershell -NoProfile -Command "Expand-Archive -LiteralPath '${psPath}' -DestinationPath '${psDest}' -Force"`,
{ stdio: 'inherit' },
);
}
} else {
try {
execSync(`unzip -o -q "${archivePath}" -d "${outDir}"`, { stdio: 'inherit' });
} catch (e) {
execSync(`tar -xf "${archivePath}" -C "${outDir}"`, { stdio: 'inherit' });
}
}
} else {
execSync(`tar -xzf "${archivePath}" -C "${outDir}"`, { stdio: 'inherit' });
}
}
function moveExtractedToKey(extractDir, key) {
const entries = fs.readdirSync(extractDir);
const targetRoot = path.join(uvRoot, key);
if (entries.length === 1) {
const inner = path.join(extractDir, entries[0]);
if (fs.statSync(inner).isDirectory()) {
copyDirRecursive(inner, targetRoot);
} else {
fs.mkdirSync(path.join(targetRoot, 'bin'), { recursive: true });
fs.copyFileSync(inner, path.join(targetRoot, 'bin', entries[0]));
}
} else {
copyDirRecursive(extractDir, targetRoot);
}
// Windows zip 解压后 uv.exe 在根目录,需要移动到 bin/
const binDir = path.join(targetRoot, 'bin');
const uvExe = path.join(targetRoot, 'uv.exe');
const uvBin = path.join(targetRoot, 'uv');
const uvxExe = path.join(targetRoot, 'uvx.exe');
const uvxBin = path.join(targetRoot, 'uvx');
const uvwExe = path.join(targetRoot, 'uvw.exe');
// 确保 bin 目录存在
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
// 将根目录的 uv 相关文件移动到 bin/
if (fs.existsSync(uvExe)) fs.renameSync(uvExe, path.join(binDir, 'uv.exe'));
if (fs.existsSync(uvBin)) fs.renameSync(uvBin, path.join(binDir, 'uv'));
if (fs.existsSync(uvxExe)) fs.renameSync(uvxExe, path.join(binDir, 'uvx.exe'));
if (fs.existsSync(uvxBin)) fs.renameSync(uvxBin, path.join(binDir, 'uvx'));
if (fs.existsSync(uvwExe)) fs.renameSync(uvwExe, path.join(binDir, 'uvw.exe'));
}
async function downloadAndPrepare(key, suffix, version) {
const isZip = process.platform === 'win32';
const ext = isZip ? '.zip' : '.tar.gz';
const assetName = suffix + ext;
const downloadUrl = `https://github.com/astral-sh/uv/releases/download/${version}/${assetName}`;
console.log(`[prepare-uv] 下载 uv ${version} (${assetName}) ...`);
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
const archivePath = await download(downloadUrl, assetName);
const extractDir = path.join(cacheDir, `uv-extract-${key}`);
if (fs.existsSync(extractDir)) {
try { fs.rmSync(extractDir, { recursive: true }); } catch (_) {}
}
extractArchive(archivePath, extractDir);
moveExtractedToKey(extractDir, key);
if (!copyToDestBin(key)) {
console.warn('[prepare-uv] 解压后未找到 bin 目录,请检查包结构');
}
}
async function main() {
const key = getPlatformKey();
const srcDir = path.join(uvRoot, key);
const destBin = path.join(uvRoot, 'bin');
const uvName = process.platform === 'win32' ? 'uv.exe' : 'uv';
const destUv = path.join(destBin, uvName);
const platformKeyFile = path.join(destBin, '.platform-key');
console.log(`[prepare-uv] 平台: ${key}, 源码目录: ${srcDir}, 目标目录: ${destBin}`);
// 检查 .platform-key 是否匹配,不匹配则清理并重新下载
if (fs.existsSync(destUv)) {
if (fs.existsSync(platformKeyFile)) {
const existingKey = fs.readFileSync(platformKeyFile, 'utf-8').trim();
if (existingKey === key) {
console.log(`[prepare-uv] uv 已存在且架构匹配 (${key}), 跳过下载`);
return;
}
console.log(`[prepare-uv] 架构不匹配: 已有 ${existingKey}, 需要 ${key}, 清理并重新下载`);
} else {
console.log(`[prepare-uv] uv 已存在但缺少 .platform-key, 无法确认架构, 清理并重新下载`);
}
fs.rmSync(destBin, { recursive: true, force: true });
}
// 如果源码目录存在但 bin 不完整,尝试复制
if (fs.existsSync(srcDir) && copyToDestBin(key)) {
if (fs.existsSync(destUv)) {
fs.writeFileSync(platformKeyFile, key, 'utf-8');
console.log(`[prepare-uv] 使用已有 uv (${key}), 已复制到 bin/ 并写入 .platform-key`);
return;
}
}
const suffix = UV_ASSET_SUFFIX[key];
if (!suffix) {
console.warn(`[prepare-uv] 未支持的平台: ${key}`);
return;
}
const version = UV_VERSION;
console.log(`[prepare-uv] 使用 uv 版本: ${version}`);
try {
await downloadAndPrepare(key, suffix, version);
// Write .platform-key marker after successful download
if (fs.existsSync(destBin)) {
fs.writeFileSync(platformKeyFile, key, 'utf-8');
console.log(`[prepare-uv] 已写入 .platform-key: ${key}`);
}
} catch (err) {
console.error('[prepare-uv] 下载或解压失败:', err.message);
process.exit(1);
}
}
main();