Files
zclaw_openfang/desktop/scripts/preseed-tauri-tools.mjs
iven 07079293f4 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>
2026-03-14 23:16:32 +08:00

297 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 完成,未写入任何文件。');
}