Refine desktop setup and remove bundled app center apps

This commit is contained in:
inman
2026-06-04 09:58:58 +08:00
parent 6153579b90
commit 84128dbe23
73 changed files with 3888 additions and 2024 deletions

View File

@@ -6,9 +6,10 @@
* Build a self-contained mirror of OpenClaw third-party plugins for packaging.
* Current plugins:
* - @tencent-weixin/openclaw-weixin -> build/openclaw-plugins/openclaw-weixin
* - @larksuite/openclaw-lark -> build/openclaw-plugins/openclaw-lark
*
* The output plugin directory contains:
* - plugin source files (index.ts, openclaw.plugin.json, package.json, ...)
* - plugin runtime files (dist/index.js or index.js, openclaw.plugin.json, package.json, ...)
* - plugin runtime node_modules/ (flattened direct + transitive deps)
*/
@@ -16,6 +17,7 @@ import 'zx/globals';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import ts from 'typescript';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
@@ -36,8 +38,197 @@ function normWin(p) {
const PLUGINS = [
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
{ npmName: '@larksuite/openclaw-lark', pluginId: 'openclaw-lark' },
];
function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
function writeJsonFile(filePath, value) {
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
function normalizeEntryPath(entry) {
if (typeof entry !== 'string') return null;
const trimmed = entry.trim();
if (!trimmed || path.isAbsolute(trimmed)) return null;
return trimmed.replace(/^\.\//, '');
}
function toPackageEntry(entry) {
return entry.startsWith('.') ? entry : `./${entry}`;
}
function isJavaScriptEntry(entry) {
return /\.(?:cjs|mjs|js)$/i.test(entry);
}
function entryExists(pluginDir, entry) {
const normalized = normalizeEntryPath(entry);
return Boolean(normalized) && fs.existsSync(path.join(pluginDir, normalized));
}
function collectRuntimeEntryHints(pkg) {
const hints = [];
const extensions = pkg.openclaw?.extensions;
if (Array.isArray(extensions)) hints.push(...extensions);
if (typeof pkg.main === 'string') hints.push(pkg.main);
if (typeof pkg.module === 'string') hints.push(pkg.module);
hints.push('./dist/index.js', './index.js');
return [...new Set(hints.map(normalizeEntryPath).filter(Boolean))];
}
function findExistingRuntimeEntry(pluginDir, pkg) {
for (const hint of collectRuntimeEntryHints(pkg)) {
if (isJavaScriptEntry(hint) && fs.existsSync(path.join(pluginDir, hint))) {
return hint;
}
}
return null;
}
function patchRuntimeEntryHints(pluginDir) {
const pkgJsonPath = path.join(pluginDir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) return null;
const pkg = readJsonFile(pkgJsonPath);
let modified = false;
const extensions = pkg.openclaw?.extensions;
if (Array.isArray(extensions)) {
const patchedExtensions = extensions.map((entry) => {
const normalized = normalizeEntryPath(entry);
if (!normalized?.endsWith('.ts')) return entry;
const jsEntry = `dist/${normalized.replace(/\.ts$/i, '.js')}`;
return fs.existsSync(path.join(pluginDir, jsEntry)) ? toPackageEntry(jsEntry) : entry;
});
if (JSON.stringify(patchedExtensions) !== JSON.stringify(extensions)) {
pkg.openclaw.extensions = patchedExtensions;
modified = true;
}
}
const existingRuntimeEntry = findExistingRuntimeEntry(pluginDir, pkg);
if (existingRuntimeEntry) {
if (typeof pkg.main !== 'string' || !entryExists(pluginDir, pkg.main)) {
pkg.main = toPackageEntry(existingRuntimeEntry);
modified = true;
}
if (typeof pkg.module === 'string' && !entryExists(pluginDir, pkg.module)) {
pkg.module = toPackageEntry(existingRuntimeEntry);
modified = true;
}
}
if (modified) {
writeJsonFile(pkgJsonPath, pkg);
}
return existingRuntimeEntry;
}
function patchManifestChannelConfigs(pluginDir) {
const manifestPath = path.join(pluginDir, 'openclaw.plugin.json');
if (!fs.existsSync(manifestPath)) return;
const manifest = readJsonFile(manifestPath);
if (manifest.channelConfigs || !Array.isArray(manifest.channels)) return;
const schema = { type: 'object' };
manifest.channelConfigs = Object.fromEntries(
manifest.channels
.filter((channelId) => typeof channelId === 'string' && channelId.trim().length > 0)
.map((channelId) => [channelId, { schema }]),
);
writeJsonFile(manifestPath, manifest);
}
function collectTypeScriptFiles(pluginDir) {
const result = [];
const skipDirs = new Set(['node_modules', 'dist', '.git']);
function walk(currentDir) {
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
if (skipDirs.has(entry.name)) continue;
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
walk(fullPath);
continue;
}
if (!entry.isFile()) continue;
if (!entry.name.endsWith('.ts')) continue;
if (entry.name.endsWith('.d.ts') || entry.name.endsWith('.test.ts')) continue;
result.push(fullPath);
}
}
walk(pluginDir);
return result;
}
function compileTypeScriptPluginIfNeeded(pluginDir, pluginId) {
const pkgJsonPath = path.join(pluginDir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) return;
const pkg = readJsonFile(pkgJsonPath);
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
const hasTypeScriptEntry = extensionEntries.some((entry) => normalizeEntryPath(entry)?.endsWith('.ts'));
if (!hasTypeScriptEntry) {
const runtimeEntry = patchRuntimeEntryHints(pluginDir);
if (runtimeEntry) {
echo` 🔗 Runtime entry: ${runtimeEntry}`;
}
return;
}
const tsFiles = collectTypeScriptFiles(pluginDir);
if (tsFiles.length === 0) {
throw new Error(`Plugin ${pluginId} declares TypeScript entries but no .ts source files were found.`);
}
const distDir = path.join(pluginDir, 'dist');
fs.rmSync(distDir, { recursive: true, force: true });
for (const sourcePath of tsFiles) {
const source = fs.readFileSync(sourcePath, 'utf8');
const output = ts.transpileModule(source, {
compilerOptions: {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ES2022,
esModuleInterop: true,
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
sourceMap: false,
inlineSources: false,
},
fileName: sourcePath,
reportDiagnostics: true,
});
const diagnostics = output.diagnostics ?? [];
const blocking = diagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error);
if (blocking.length > 0) {
const message = blocking
.map((diag) => ts.flattenDiagnosticMessageText(diag.messageText, '\n'))
.join('\n');
throw new Error(`Failed to transpile ${path.relative(pluginDir, sourcePath)}:\n${message}`);
}
const rel = path.relative(pluginDir, sourcePath).replace(/\.ts$/i, '.js');
const outputPath = path.join(distDir, rel);
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, output.outputText, 'utf8');
}
patchRuntimeEntryHints(pluginDir);
const runtimeEntry = findExistingRuntimeEntry(pluginDir, readJsonFile(pkgJsonPath));
if (!runtimeEntry) {
throw new Error(`Plugin ${pluginId} did not produce a loadable JavaScript runtime entry.`);
}
echo` 🛠️ Compiled ${tsFiles.length} TypeScript files -> dist/ (${runtimeEntry})`;
}
function getVirtualStoreNodeModules(realPkgPath) {
let dir = realPkgPath;
while (dir !== path.dirname(dir)) {
@@ -171,6 +362,8 @@ function bundleOnePlugin({ npmName, pluginId }) {
// 4) Patch plugin ID mismatch: some npm packages hardcode a different ID in
// their JS output than what openclaw.plugin.json declares. The Gateway
// validates that these match, so we fix it post-copy.
patchManifestChannelConfigs(outputDir);
compileTypeScriptPluginIfNeeded(outputDir, pluginId);
patchPluginId(outputDir, pluginId);
echo`${pluginId}: copied ${copiedCount} deps (skipped dupes: ${skippedDupes})`;
@@ -196,7 +389,8 @@ function patchPluginId(pluginDir, expectedId) {
if (!fs.existsSync(pkgJsonPath)) return;
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
const entryFiles = [pkg.main, pkg.module].filter(Boolean);
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
const entryFiles = [...new Set([pkg.main, pkg.module, ...extensionEntries].filter(Boolean))];
// Known ID mismatches to patch. Keys are the wrong ID found in compiled JS,
// values are the correct ID (must match openclaw.plugin.json).