Refine desktop setup and remove bundled app center apps
This commit is contained in:
@@ -19,8 +19,9 @@
|
||||
* @mariozechner/clipboard).
|
||||
*/
|
||||
|
||||
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync } = require('fs');
|
||||
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync, readFileSync, writeFileSync } = require('fs');
|
||||
const { join, dirname, basename, relative } = require('path');
|
||||
const ts = require('typescript');
|
||||
|
||||
// On Windows, paths in pnpm's virtual store can exceed the default MAX_PATH
|
||||
// limit (260 chars). Node.js 18.17+ respects the system LongPathsEnabled
|
||||
@@ -280,30 +281,6 @@ function removeOptionalNativeClipboard(nodeModulesDir) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
function copyNianxxPlayNodeModules(resourcesDir, platform, arch) {
|
||||
const src = join(__dirname, '..', 'build', 'apps', 'nianxx-play', 'node_modules');
|
||||
const nianxxPlayRoot = join(resourcesDir, 'resources', 'nianxx-play');
|
||||
const dest = join(nianxxPlayRoot, 'node_modules');
|
||||
|
||||
if (!existsSync(nianxxPlayRoot)) return;
|
||||
if (!existsSync(src)) {
|
||||
console.warn('[after-pack] ⚠️ build/apps/nianxx-play/node_modules not found. Run prepare:nianxx-play first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const depCount = readdirSync(src, { withFileTypes: true })
|
||||
.filter(d => d.isDirectory() && d.name !== '.bin')
|
||||
.length;
|
||||
|
||||
console.log(`[after-pack] Copying ${depCount} NianxxPlay dependencies to ${dest} ...`);
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
cpSync(src, dest, { recursive: true, dereference: true });
|
||||
cleanupUnnecessaryFiles(dest);
|
||||
cleanupKoffi(dest, platform, arch);
|
||||
cleanupNativePlatformPackages(dest, platform, arch);
|
||||
console.log('[after-pack] ✅ NianxxPlay node_modules copied.');
|
||||
}
|
||||
|
||||
// ── Broken module patcher ─────────────────────────────────────────────────────
|
||||
// Some bundled packages have transpiled CJS that sets `module.exports = exports.default`
|
||||
// without ever assigning `exports.default`, leaving module.exports === undefined.
|
||||
@@ -488,7 +465,8 @@ function patchPluginIds(pluginDir, expectedId) {
|
||||
if (!existsSync(pkgJsonPath)) return;
|
||||
|
||||
const pkg = JSON.parse(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))];
|
||||
|
||||
for (const entry of entryFiles) {
|
||||
const entryPath = join(pluginDir, entry);
|
||||
@@ -520,6 +498,194 @@ function patchPluginIds(pluginDir, expectedId) {
|
||||
// bundle-openclaw-plugins.mjs so the packaged app is self-contained even when
|
||||
// build/openclaw-plugins/ was not pre-generated.
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function writeJsonFile(filePath, value) {
|
||||
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
function normalizeEntryPath(entry) {
|
||||
if (typeof entry !== 'string') return null;
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed || require('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) && existsSync(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) && existsSync(join(pluginDir, hint))) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function patchRuntimeEntryHints(pluginDir) {
|
||||
const pkgJsonPath = join(pluginDir, 'package.json');
|
||||
if (!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 existsSync(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 = join(pluginDir, 'openclaw.plugin.json');
|
||||
if (!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 readdirSync(currentDir, { withFileTypes: true })) {
|
||||
if (skipDirs.has(entry.name)) continue;
|
||||
const fullPath = 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 = join(pluginDir, 'package.json');
|
||||
if (!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) {
|
||||
console.log(`[after-pack] 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 = join(pluginDir, 'dist');
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
|
||||
for (const sourcePath of tsFiles) {
|
||||
const source = 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 ${relative(pluginDir, sourcePath)}:\n${message}`);
|
||||
}
|
||||
|
||||
const rel = relative(pluginDir, sourcePath).replace(/\.ts$/i, '.js');
|
||||
const outputPath = join(distDir, rel);
|
||||
mkdirSync(dirname(outputPath), { recursive: true });
|
||||
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.`);
|
||||
}
|
||||
|
||||
console.log(`[after-pack] Compiled ${tsFiles.length} TypeScript files -> dist/ (${runtimeEntry})`);
|
||||
}
|
||||
|
||||
function getVirtualStoreNodeModules(realPkgPath) {
|
||||
let dir = realPkgPath;
|
||||
while (dir !== dirname(dir)) {
|
||||
@@ -670,10 +836,6 @@ exports.default = async function afterPack(context) {
|
||||
console.log(`[after-pack] ✅ Removed optional native clipboard packages (${clipboardRemoved}) to avoid macOS Gatekeeper prompts.`);
|
||||
}
|
||||
|
||||
// 1.0 Copy bundled large-app runtime deps that electron-builder skips because
|
||||
// node_modules/ is ignored globally.
|
||||
copyNianxxPlayNodeModules(resourcesDir, platform, arch);
|
||||
|
||||
// Patch broken modules whose CJS transpiled output sets module.exports = undefined,
|
||||
// causing TypeError in Node.js 22+ ESM interop.
|
||||
patchBrokenModules(dest);
|
||||
@@ -685,6 +847,7 @@ exports.default = async function afterPack(context) {
|
||||
// - node_modules/ is excluded by .gitignore so the deps copy must be manual
|
||||
const BUNDLED_PLUGINS = [
|
||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'openclaw-lark' },
|
||||
];
|
||||
|
||||
mkdirSync(pluginsDestRoot, { recursive: true });
|
||||
@@ -700,6 +863,8 @@ exports.default = async function afterPack(context) {
|
||||
cleanupNativePlatformPackages(pluginNM, platform, arch);
|
||||
}
|
||||
// Fix hardcoded plugin ID mismatches in compiled JS
|
||||
patchManifestChannelConfigs(pluginDestDir);
|
||||
compileTypeScriptPluginIfNeeded(pluginDestDir, pluginId);
|
||||
patchPluginIds(pluginDestDir, pluginId);
|
||||
}
|
||||
}
|
||||
@@ -715,6 +880,7 @@ exports.default = async function afterPack(context) {
|
||||
rmSync(pluginDestDir, { recursive: true, force: true });
|
||||
cpSync(sourceDir, pluginDestDir, { recursive: true, dereference: true });
|
||||
cleanupUnnecessaryFiles(pluginDestDir);
|
||||
patchManifestChannelConfigs(pluginDestDir);
|
||||
patchPluginIds(pluginDestDir, entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user