feat(hands): restructure Hands UI with Chinese localization
Major changes: - Add HandList.tsx component for left sidebar - Add HandTaskPanel.tsx for middle content area - Restructure Sidebar tabs: 分身/HANDS/Workflow - Remove Hands tab from RightPanel - Localize all UI text to Chinese - Archive legacy OpenClaw documentation - Add Hands integration lessons document - Update feature checklist with new components UI improvements: - Left sidebar now shows Hands list with status icons - Middle area shows selected Hand's tasks and results - Consistent styling with Tailwind CSS - Chinese status labels and buttons Documentation: - Create docs/archive/openclaw-legacy/ for old docs - Add docs/knowledge-base/hands-integration-lessons.md - Update docs/knowledge-base/feature-checklist.md - Update docs/knowledge-base/README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
150
desktop/scripts/download-openfang.ts
Normal file
150
desktop/scripts/download-openfang.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* OpenFang Binary Downloader
|
||||
* Automatically downloads the correct OpenFang binary for the current platform
|
||||
* Run during Tauri build process
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync, mkdirSync, writeFileSync, renameSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { platform, arch } from 'os';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const RESOURCES_DIR = join(__dirname, '../src-tauri/resources/openfang-runtime');
|
||||
|
||||
// OpenFang release info
|
||||
const OPENFANG_REPO = 'RightNow-AI/openfang';
|
||||
const OPENFANG_VERSION = process.env.OPENFANG_VERSION || 'latest';
|
||||
|
||||
interface PlatformConfig {
|
||||
binaryName: string;
|
||||
downloadName: string;
|
||||
}
|
||||
|
||||
function getPlatformConfig(): PlatformConfig {
|
||||
const currentPlatform = platform();
|
||||
const currentArch = arch();
|
||||
|
||||
switch (currentPlatform) {
|
||||
case 'win32':
|
||||
return {
|
||||
binaryName: 'openfang.exe',
|
||||
downloadName: currentArch === 'x64'
|
||||
? 'openfang-x86_64-pc-windows-msvc.exe'
|
||||
: 'openfang-aarch64-pc-windows-msvc.exe',
|
||||
};
|
||||
case 'darwin':
|
||||
return {
|
||||
binaryName: currentArch === 'arm64'
|
||||
? 'openfang-aarch64-apple-darwin'
|
||||
: 'openfang-x86_64-apple-darwin',
|
||||
downloadName: currentArch === 'arm64'
|
||||
? 'openfang-aarch64-apple-darwin'
|
||||
: 'openfang-x86_64-apple-darwin',
|
||||
};
|
||||
case 'linux':
|
||||
return {
|
||||
binaryName: currentArch === 'arm64'
|
||||
? 'openfang-aarch64-unknown-linux-gnu'
|
||||
: 'openfang-x86_64-unknown-linux-gnu',
|
||||
downloadName: currentArch === 'arm64'
|
||||
? 'openfang-aarch64-unknown-linux-gnu'
|
||||
: 'openfang-x86_64-unknown-linux-gnu',
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${currentPlatform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadBinary(): void {
|
||||
const config = getPlatformConfig();
|
||||
const baseUrl = `https://github.com/${OPENFANG_REPO}/releases`;
|
||||
const downloadUrl = OPENFANG_VERSION === 'latest'
|
||||
? `${baseUrl}/latest/download/${config.downloadName}`
|
||||
: `${baseUrl}/download/${OPENFANG_VERSION}/${config.downloadName}`;
|
||||
|
||||
const outputPath = join(RESOURCES_DIR, config.binaryName);
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('OpenFang Binary Downloader');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Platform: ${platform()} (${arch()})`);
|
||||
console.log(`Binary: ${config.binaryName}`);
|
||||
console.log(`Version: ${OPENFANG_VERSION}`);
|
||||
console.log(`URL: ${downloadUrl}`);
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// Ensure directory exists
|
||||
if (!existsSync(RESOURCES_DIR)) {
|
||||
mkdirSync(RESOURCES_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Check if already downloaded
|
||||
if (existsSync(outputPath)) {
|
||||
console.log('✓ Binary already exists, skipping download.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Download using curl (cross-platform via Node.js)
|
||||
console.log('Downloading...');
|
||||
|
||||
try {
|
||||
// Use curl for download (available on all platforms with Git/WSL)
|
||||
const tempPath = `${outputPath}.tmp`;
|
||||
|
||||
if (platform() === 'win32') {
|
||||
// Windows: use PowerShell
|
||||
execSync(
|
||||
`powershell -Command "Invoke-WebRequest -Uri '${downloadUrl}' -OutFile '${tempPath}'"`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
} else {
|
||||
// Unix: use curl
|
||||
execSync(`curl -fsSL -o "${tempPath}" "${downloadUrl}"`, { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// Rename temp file to final name
|
||||
renameSync(tempPath, outputPath);
|
||||
|
||||
// Make executable on Unix
|
||||
if (platform() !== 'win32') {
|
||||
execSync(`chmod +x "${outputPath}"`);
|
||||
}
|
||||
|
||||
console.log('✓ Download complete!');
|
||||
} catch (error) {
|
||||
console.error('✗ Download failed:', error);
|
||||
console.log('\nPlease download manually from:');
|
||||
console.log(` ${baseUrl}/${OPENFANG_VERSION === 'latest' ? 'latest' : 'tag/' + OPENFANG_VERSION}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function updateManifest(): void {
|
||||
const manifestPath = join(RESOURCES_DIR, 'runtime-manifest.json');
|
||||
|
||||
const manifest = {
|
||||
source: {
|
||||
binPath: platform() === 'win32' ? 'openfang.exe' : `openfang-${arch()}-${platform()}`,
|
||||
},
|
||||
stagedAt: new Date().toISOString(),
|
||||
version: OPENFANG_VERSION === 'latest' ? new Date().toISOString().split('T')[0].replace(/-/g, '.') : OPENFANG_VERSION,
|
||||
runtimeType: 'openfang',
|
||||
description: 'OpenFang Agent OS - Single binary runtime (~32MB)',
|
||||
endpoints: {
|
||||
websocket: 'ws://127.0.0.1:4200/ws',
|
||||
rest: 'http://127.0.0.1:4200/api',
|
||||
},
|
||||
};
|
||||
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
||||
console.log('✓ Manifest updated');
|
||||
}
|
||||
|
||||
// Run
|
||||
downloadBinary();
|
||||
updateManifest();
|
||||
|
||||
console.log('\n✓ OpenFang runtime ready for build!');
|
||||
167
desktop/scripts/prepare-openclaw-runtime.mjs
Normal file
167
desktop/scripts/prepare-openclaw-runtime.mjs
Normal file
@@ -0,0 +1,167 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const desktopRoot = path.resolve(__dirname, '..');
|
||||
const outputDir = path.join(desktopRoot, 'src-tauri', 'resources', 'openclaw-runtime');
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
|
||||
function log(message) {
|
||||
console.log(`[prepare-openclaw-runtime] ${message}`);
|
||||
}
|
||||
|
||||
function readFirstExistingPath(commandNames) {
|
||||
for (const commandName of commandNames) {
|
||||
try {
|
||||
const stdout = execFileSync('where.exe', [commandName], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
const firstMatch = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
if (firstMatch) {
|
||||
return firstMatch;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureFileExists(filePath, label) {
|
||||
if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||
throw new Error(`${label} 不存在:${filePath || '(empty)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDirExists(dirPath, label) {
|
||||
if (!dirPath || !fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
||||
throw new Error(`${label} 不存在:${dirPath || '(empty)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveOpenClawBin() {
|
||||
const override = process.env.OPENCLAW_BIN;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
const resolved = readFirstExistingPath(['openclaw.cmd', 'openclaw']);
|
||||
if (!resolved) {
|
||||
throw new Error('未找到 openclaw 入口。请先安装 OpenClaw,或设置 OPENCLAW_BIN。');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function resolvePackageDir(openclawBinPath) {
|
||||
const override = process.env.OPENCLAW_PACKAGE_DIR;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
return path.join(path.dirname(openclawBinPath), 'node_modules', 'openclaw');
|
||||
}
|
||||
|
||||
function resolveNodeExe(openclawBinPath) {
|
||||
const override = process.env.OPENCLAW_NODE_EXE;
|
||||
if (override) {
|
||||
return path.resolve(override);
|
||||
}
|
||||
|
||||
const bundledNode = path.join(path.dirname(openclawBinPath), 'node.exe');
|
||||
if (fs.existsSync(bundledNode)) {
|
||||
return bundledNode;
|
||||
}
|
||||
|
||||
const resolved = readFirstExistingPath(['node.exe', 'node']);
|
||||
if (!resolved) {
|
||||
throw new Error('未找到 node.exe。请先安装 Node.js,或设置 OPENCLAW_NODE_EXE。');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function cleanOutputDirectory(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(dirPath)) {
|
||||
fs.rmSync(path.join(dirPath, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function writeCmdLauncher(dirPath) {
|
||||
const launcher = [
|
||||
'@ECHO off',
|
||||
'SETLOCAL',
|
||||
'SET "_prog=%~dp0\\node.exe"',
|
||||
'"%_prog%" "%~dp0\\node_modules\\openclaw\\openclaw.mjs" %*',
|
||||
'',
|
||||
].join('\r\n');
|
||||
|
||||
fs.writeFileSync(path.join(dirPath, 'openclaw.cmd'), launcher, 'utf8');
|
||||
}
|
||||
|
||||
function stageRuntime() {
|
||||
const openclawBinPath = resolveOpenClawBin();
|
||||
const packageDir = resolvePackageDir(openclawBinPath);
|
||||
const nodeExePath = resolveNodeExe(openclawBinPath);
|
||||
const packageJsonPath = path.join(packageDir, 'package.json');
|
||||
const entryPath = path.join(packageDir, 'openclaw.mjs');
|
||||
|
||||
ensureFileExists(openclawBinPath, 'OpenClaw 入口');
|
||||
ensureDirExists(packageDir, 'OpenClaw 包目录');
|
||||
ensureFileExists(packageJsonPath, 'OpenClaw package.json');
|
||||
ensureFileExists(entryPath, 'OpenClaw 入口脚本');
|
||||
ensureFileExists(nodeExePath, 'Node.js 可执行文件');
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
const destinationPackageDir = path.join(outputDir, 'node_modules', 'openclaw');
|
||||
const manifest = {
|
||||
source: {
|
||||
openclawBinPath,
|
||||
packageDir,
|
||||
nodeExePath,
|
||||
},
|
||||
stagedAt: new Date().toISOString(),
|
||||
version: packageJson.version ?? null,
|
||||
};
|
||||
|
||||
log(`OpenClaw version: ${packageJson.version || 'unknown'}`);
|
||||
log(`Source bin: ${openclawBinPath}`);
|
||||
log(`Source package: ${packageDir}`);
|
||||
log(`Source node.exe: ${nodeExePath}`);
|
||||
log(`Target dir: ${outputDir}`);
|
||||
|
||||
if (dryRun) {
|
||||
log('Dry run 完成,未写入任何文件。');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
cleanOutputDirectory(outputDir);
|
||||
fs.mkdirSync(path.join(outputDir, 'node_modules'), { recursive: true });
|
||||
fs.copyFileSync(nodeExePath, path.join(outputDir, 'node.exe'));
|
||||
fs.cpSync(packageDir, destinationPackageDir, { recursive: true, force: true });
|
||||
writeCmdLauncher(outputDir);
|
||||
fs.writeFileSync(path.join(outputDir, 'runtime-manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
||||
|
||||
log('OpenClaw runtime 已写入 src-tauri/resources/openclaw-runtime');
|
||||
}
|
||||
|
||||
try {
|
||||
stageRuntime();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`[prepare-openclaw-runtime] ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
296
desktop/scripts/preseed-tauri-tools.mjs
Normal file
296
desktop/scripts/preseed-tauri-tools.mjs
Normal file
@@ -0,0 +1,296 @@
|
||||
import { mkdtempSync, rmSync, existsSync, cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const desktopRoot = path.resolve(__dirname, '..');
|
||||
const localToolsRoot = path.join(desktopRoot, 'local-tools');
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const dryRun = args.has('--dry-run');
|
||||
const showHelp = args.has('--help') || args.has('-h');
|
||||
const projectCacheRoot = path.join(desktopRoot, 'src-tauri', 'target', '.tauri');
|
||||
const userCacheRoot = process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'tauri') : null;
|
||||
const cacheRoots = [projectCacheRoot, userCacheRoot].filter(Boolean);
|
||||
const nsisUtilsDllName = 'nsis_tauri_utils.dll';
|
||||
|
||||
function log(message) {
|
||||
console.log(`[preseed-tauri-tools] ${message}`);
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
console.error(`[preseed-tauri-tools] ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function ensureDir(dirPath) {
|
||||
mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
function findNsisRoot(dirPath) {
|
||||
return findDirectoryContaining(dirPath, (current, entries) => {
|
||||
const names = new Set(entries.map((entry) => entry.name));
|
||||
return names.has('makensis.exe') || names.has('Bin');
|
||||
});
|
||||
}
|
||||
|
||||
function findWixRoot(dirPath) {
|
||||
return findDirectoryContaining(dirPath, (current, entries) => {
|
||||
const names = new Set(entries.map((entry) => entry.name));
|
||||
return names.has('candle.exe') || names.has('light.exe');
|
||||
});
|
||||
}
|
||||
|
||||
function directoryHasToolSignature(toolName, dirPath) {
|
||||
if (!existsSync(dirPath) || !statSync(dirPath).isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = toolName === 'NSIS' ? findNsisRoot(dirPath) : findWixRoot(dirPath);
|
||||
|
||||
return Boolean(match);
|
||||
}
|
||||
|
||||
function directoryHasReadyNsisLayout(dirPath) {
|
||||
const root = findNsisRoot(dirPath);
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return existsSync(path.join(root, 'Plugins', 'x86-unicode', nsisUtilsDllName))
|
||||
|| existsSync(path.join(root, 'Plugins', 'x86-unicode', 'additional', nsisUtilsDllName));
|
||||
}
|
||||
|
||||
function copyDirectoryContents(sourceDir, destinationDir) {
|
||||
ensureDir(destinationDir);
|
||||
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
||||
const sourcePath = path.join(sourceDir, entry.name);
|
||||
const destinationPath = path.join(destinationDir, entry.name);
|
||||
cpSync(sourcePath, destinationPath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function expandZip(zipPath, destinationDir) {
|
||||
const command = [
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
`Expand-Archive -LiteralPath '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`,
|
||||
];
|
||||
const result = spawnSync('powershell', command, {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
if (typeof result.status === 'number' && result.status !== 0) {
|
||||
process.exit(result.status);
|
||||
}
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
}
|
||||
|
||||
function findDirectoryContaining(rootDir, predicate) {
|
||||
const queue = [rootDir];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
const entries = readdirSync(current, { withFileTypes: true });
|
||||
if (predicate(current, entries)) {
|
||||
return current;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
queue.push(path.join(current, entry.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function firstExistingFile(candidates) {
|
||||
for (const candidate of candidates.filter(Boolean).map((value) => path.resolve(value))) {
|
||||
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveNsisSupportDll() {
|
||||
return firstExistingFile([
|
||||
process.env.ZCLAW_TAURI_NSIS_TAURI_UTILS_DLL,
|
||||
path.join(localToolsRoot, nsisUtilsDllName),
|
||||
path.join(localToolsRoot, 'nsis_tauri_utils-v0.5.3', nsisUtilsDllName),
|
||||
path.join(localToolsRoot, 'nsis_tauri_utils-v0.5.2', nsisUtilsDllName),
|
||||
]);
|
||||
}
|
||||
|
||||
function resolveSource(toolName) {
|
||||
if (toolName === 'NSIS') {
|
||||
const dirCandidates = [
|
||||
process.env.ZCLAW_TAURI_NSIS_DIR,
|
||||
path.join(localToolsRoot, 'NSIS'),
|
||||
].filter(Boolean).map((value) => path.resolve(value));
|
||||
for (const candidate of dirCandidates) {
|
||||
if (directoryHasReadyNsisLayout(candidate)) {
|
||||
return { kind: 'dir', path: candidate };
|
||||
}
|
||||
}
|
||||
|
||||
const supportDll = resolveNsisSupportDll();
|
||||
|
||||
for (const candidate of dirCandidates) {
|
||||
if (directoryHasToolSignature('NSIS', candidate)) {
|
||||
return { kind: 'nsis-base-dir', path: candidate, supportDll };
|
||||
}
|
||||
}
|
||||
|
||||
const zipCandidates = [
|
||||
process.env.ZCLAW_TAURI_NSIS_ZIP,
|
||||
path.join(localToolsRoot, 'nsis.zip'),
|
||||
path.join(localToolsRoot, 'nsis-3.11.zip'),
|
||||
path.join(localToolsRoot, 'nsis-3.08.zip'),
|
||||
].filter(Boolean).map((value) => path.resolve(value));
|
||||
for (const candidate of zipCandidates) {
|
||||
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
||||
return { kind: 'nsis-base-zip', path: candidate, supportDll };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const envDirKey = toolName === 'NSIS' ? 'ZCLAW_TAURI_NSIS_DIR' : 'ZCLAW_TAURI_WIX_DIR';
|
||||
const envZipKey = toolName === 'NSIS' ? 'ZCLAW_TAURI_NSIS_ZIP' : 'ZCLAW_TAURI_WIX_ZIP';
|
||||
const localZipCandidates = toolName === 'NSIS'
|
||||
? [path.join(localToolsRoot, 'nsis.zip'), path.join(localToolsRoot, 'nsis-3.11.zip')]
|
||||
: [
|
||||
path.join(localToolsRoot, 'wix.zip'),
|
||||
path.join(localToolsRoot, 'wix314-binaries.zip'),
|
||||
path.join(localToolsRoot, 'wix311-binaries.zip'),
|
||||
];
|
||||
|
||||
const localDirCandidates = toolName === 'NSIS'
|
||||
? [path.join(localToolsRoot, toolName)]
|
||||
: [path.join(localToolsRoot, 'WixTools314'), path.join(localToolsRoot, 'WixTools')];
|
||||
const dirCandidates = [process.env[envDirKey], ...localDirCandidates].filter(Boolean).map((value) => path.resolve(value));
|
||||
for (const candidate of dirCandidates) {
|
||||
if (directoryHasToolSignature(toolName, candidate)) {
|
||||
return { kind: 'dir', path: candidate };
|
||||
}
|
||||
}
|
||||
|
||||
const zipCandidates = [process.env[envZipKey], ...localZipCandidates].filter(Boolean).map((value) => path.resolve(value));
|
||||
for (const candidate of zipCandidates) {
|
||||
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
||||
return { kind: 'zip', path: candidate };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeToolSource(toolName, source) {
|
||||
if (toolName === 'NSIS' && source.kind !== 'dir') {
|
||||
const tempRoot = mkdtempSync(path.join(os.tmpdir(), 'zclaw-tauri-tool-'));
|
||||
const assembledRoot = path.join(tempRoot, 'NSIS');
|
||||
ensureDir(assembledRoot);
|
||||
|
||||
if (source.kind === 'nsis-base-dir') {
|
||||
const baseRoot = findNsisRoot(source.path);
|
||||
if (!baseRoot) {
|
||||
fail(`NSIS 目录未找到 makensis:${source.path}`);
|
||||
}
|
||||
copyDirectoryContents(baseRoot, assembledRoot);
|
||||
} else if (source.kind === 'nsis-base-zip') {
|
||||
const extractedRoot = path.join(tempRoot, 'extract');
|
||||
ensureDir(extractedRoot);
|
||||
expandZip(source.path, extractedRoot);
|
||||
const baseRoot = findNsisRoot(extractedRoot);
|
||||
if (!baseRoot) {
|
||||
fail(`NSIS zip 解压后未找到 makensis:${source.path}`);
|
||||
}
|
||||
copyDirectoryContents(baseRoot, assembledRoot);
|
||||
}
|
||||
|
||||
if (!source.supportDll) {
|
||||
fail(`检测到 NSIS 基础包,但缺少 ${nsisUtilsDllName}。请放到 desktop/local-tools/${nsisUtilsDllName} 或设置 ZCLAW_TAURI_NSIS_TAURI_UTILS_DLL。`);
|
||||
}
|
||||
|
||||
const pluginsDir = path.join(assembledRoot, 'Plugins', 'x86-unicode');
|
||||
const additionalPluginsDir = path.join(pluginsDir, 'additional');
|
||||
ensureDir(pluginsDir);
|
||||
ensureDir(additionalPluginsDir);
|
||||
cpSync(source.supportDll, path.join(pluginsDir, nsisUtilsDllName), { force: true });
|
||||
cpSync(source.supportDll, path.join(additionalPluginsDir, nsisUtilsDllName), { force: true });
|
||||
|
||||
return { tempRoot, path: assembledRoot };
|
||||
}
|
||||
|
||||
if (source.kind === 'dir') {
|
||||
return source.path;
|
||||
}
|
||||
|
||||
const tempRoot = mkdtempSync(path.join(os.tmpdir(), 'zclaw-tauri-tool-'));
|
||||
const extractedRoot = path.join(tempRoot, 'extract');
|
||||
ensureDir(extractedRoot);
|
||||
expandZip(source.path, extractedRoot);
|
||||
const normalized = toolName === 'NSIS'
|
||||
? findNsisRoot(extractedRoot)
|
||||
: findWixRoot(extractedRoot);
|
||||
|
||||
if (!normalized) {
|
||||
fail(`${toolName} zip 解压后未找到有效工具目录:${source.path}`);
|
||||
}
|
||||
|
||||
return { tempRoot, path: normalized };
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
console.log('Usage: node scripts/preseed-tauri-tools.mjs [--dry-run]');
|
||||
console.log('Sources:');
|
||||
console.log(' ZCLAW_TAURI_NSIS_DIR / desktop/local-tools/NSIS');
|
||||
console.log(' ZCLAW_TAURI_NSIS_ZIP / desktop/local-tools/nsis.zip or nsis-3.11.zip');
|
||||
console.log(` ZCLAW_TAURI_NSIS_TAURI_UTILS_DLL / desktop/local-tools/${nsisUtilsDllName}`);
|
||||
console.log(' ZCLAW_TAURI_WIX_DIR / desktop/local-tools/WixTools314 or WixTools');
|
||||
console.log(' ZCLAW_TAURI_WIX_ZIP / desktop/local-tools/wix.zip or wix314-binaries.zip');
|
||||
}
|
||||
|
||||
if (showHelp) {
|
||||
printUsage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (const toolName of ['NSIS', 'WixTools']) {
|
||||
const source = resolveSource(toolName);
|
||||
if (!source) {
|
||||
log(`${toolName} 未提供本地预置源,跳过。`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let normalized = null;
|
||||
try {
|
||||
normalized = normalizeToolSource(toolName, source);
|
||||
const sourcePath = typeof normalized === 'string' ? normalized : normalized.path;
|
||||
for (const cacheRoot of cacheRoots) {
|
||||
const destinationNames = toolName === 'WixTools' ? ['WixTools314', 'WixTools'] : [toolName];
|
||||
for (const destinationName of destinationNames) {
|
||||
const destination = path.join(cacheRoot, destinationName);
|
||||
log(`${toolName}: ${source.path} -> ${destination}`);
|
||||
if (!dryRun) {
|
||||
ensureDir(cacheRoot);
|
||||
rmSync(destination, { recursive: true, force: true });
|
||||
copyDirectoryContents(sourcePath, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (normalized && typeof normalized !== 'string' && normalized.tempRoot) {
|
||||
rmSync(normalized.tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
log('Dry run 完成,未写入任何文件。');
|
||||
}
|
||||
40
desktop/scripts/tauri-build-bundled.mjs
Normal file
40
desktop/scripts/tauri-build-bundled.mjs
Normal file
@@ -0,0 +1,40 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const desktopRoot = path.resolve(__dirname, '..');
|
||||
const forwardArgs = process.argv.slice(2);
|
||||
|
||||
function run(command, args, extraEnv = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: desktopRoot,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
env: {
|
||||
...process.env,
|
||||
...extraEnv,
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof result.status === 'number' && result.status !== 0) {
|
||||
process.exit(result.status);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
}
|
||||
|
||||
const env = {};
|
||||
if (!process.env.TAURI_BUNDLER_TOOLS_GITHUB_MIRROR && process.env.ZCLAW_TAURI_TOOLS_GITHUB_MIRROR) {
|
||||
env.TAURI_BUNDLER_TOOLS_GITHUB_MIRROR = process.env.ZCLAW_TAURI_TOOLS_GITHUB_MIRROR;
|
||||
}
|
||||
if (!process.env.TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE && process.env.ZCLAW_TAURI_TOOLS_GITHUB_MIRROR_TEMPLATE) {
|
||||
env.TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE = process.env.ZCLAW_TAURI_TOOLS_GITHUB_MIRROR_TEMPLATE;
|
||||
}
|
||||
|
||||
run('node', ['scripts/prepare-openfang-runtime.mjs']);
|
||||
run('node', ['scripts/preseed-tauri-tools.mjs']);
|
||||
run('pnpm', ['exec', 'tauri', 'build', ...forwardArgs], env);
|
||||
Reference in New Issue
Block a user