mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
refactor: share package cleanup helpers
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import JSON5 from "json5";
|
||||
import { NON_PACKAGED_BUNDLED_PLUGIN_DIRS } from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import { shouldBuildBundledCluster } from "./lib/optional-bundled-clusters.mjs";
|
||||
import {
|
||||
mergeGeneratedChannelConfigs,
|
||||
readGeneratedBundledChannelConfigs,
|
||||
} from "./lib/plugin-npm-package-manifest.mjs";
|
||||
import {
|
||||
removeFileIfExists,
|
||||
removePathIfExists,
|
||||
@@ -11,8 +14,6 @@ import {
|
||||
} from "./runtime-postbuild-shared.mjs";
|
||||
|
||||
const GENERATED_BUNDLED_SKILLS_DIR = "bundled-skills";
|
||||
const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH =
|
||||
"src/config/bundled-channel-config-metadata.generated.ts";
|
||||
const TRANSIENT_COPY_ERROR_CODES = new Set(["EEXIST", "ENOENT", "ENOTEMPTY", "EBUSY"]);
|
||||
const COPY_RETRY_DELAYS_MS = [10, 25, 50];
|
||||
|
||||
@@ -220,86 +221,6 @@ function copyDeclaredPluginSkillPaths(params) {
|
||||
return copiedSkills;
|
||||
}
|
||||
|
||||
function readGeneratedBundledChannelConfigs(repoRoot) {
|
||||
const metadataPath = path.join(repoRoot, GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH);
|
||||
if (!fs.existsSync(metadataPath)) {
|
||||
return new Map();
|
||||
}
|
||||
const source = fs.readFileSync(metadataPath, "utf8");
|
||||
const match = source.match(
|
||||
/export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = ([\s\S]*?) as const;/u,
|
||||
);
|
||||
if (!match?.[1]) {
|
||||
return new Map();
|
||||
}
|
||||
let entries;
|
||||
try {
|
||||
entries = JSON5.parse(match[1]);
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
if (!Array.isArray(entries)) {
|
||||
return new Map();
|
||||
}
|
||||
const byPlugin = new Map();
|
||||
for (const entry of entries) {
|
||||
if (
|
||||
!entry ||
|
||||
typeof entry !== "object" ||
|
||||
typeof entry.pluginId !== "string" ||
|
||||
typeof entry.channelId !== "string" ||
|
||||
!entry.schema ||
|
||||
typeof entry.schema !== "object"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const pluginConfigs = byPlugin.get(entry.pluginId) ?? {};
|
||||
pluginConfigs[entry.channelId] = {
|
||||
schema: entry.schema,
|
||||
...(typeof entry.label === "string" && entry.label ? { label: entry.label } : {}),
|
||||
...(typeof entry.description === "string" && entry.description
|
||||
? { description: entry.description }
|
||||
: {}),
|
||||
...(entry.uiHints && typeof entry.uiHints === "object" ? { uiHints: entry.uiHints } : {}),
|
||||
};
|
||||
byPlugin.set(entry.pluginId, pluginConfigs);
|
||||
}
|
||||
return byPlugin;
|
||||
}
|
||||
|
||||
function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) {
|
||||
if (!generatedChannelConfigs || Object.keys(generatedChannelConfigs).length === 0) {
|
||||
return manifest;
|
||||
}
|
||||
const existingChannelConfigs =
|
||||
manifest.channelConfigs && typeof manifest.channelConfigs === "object"
|
||||
? manifest.channelConfigs
|
||||
: {};
|
||||
const channelConfigs = { ...existingChannelConfigs };
|
||||
for (const [channelId, generated] of Object.entries(generatedChannelConfigs)) {
|
||||
const existing =
|
||||
existingChannelConfigs[channelId] && typeof existingChannelConfigs[channelId] === "object"
|
||||
? existingChannelConfigs[channelId]
|
||||
: {};
|
||||
channelConfigs[channelId] = {
|
||||
...generated,
|
||||
...existing,
|
||||
schema: generated.schema,
|
||||
...(generated.uiHints || existing.uiHints
|
||||
? { uiHints: { ...generated.uiHints, ...existing.uiHints } }
|
||||
: {}),
|
||||
...(existing.label || generated.label ? { label: existing.label ?? generated.label } : {}),
|
||||
...(existing.description || generated.description
|
||||
? { description: existing.description ?? generated.description }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...manifest,
|
||||
channelConfigs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* cwd?: string;
|
||||
|
||||
@@ -96,7 +96,7 @@ export function resolveAugmentedPluginNpmPackageJson(params) {
|
||||
};
|
||||
}
|
||||
|
||||
function readGeneratedBundledChannelConfigs(repoRoot) {
|
||||
export function readGeneratedBundledChannelConfigs(repoRoot) {
|
||||
const metadataPath = path.join(repoRoot, GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH);
|
||||
if (!fs.existsSync(metadataPath)) {
|
||||
return new Map();
|
||||
@@ -145,7 +145,7 @@ function readGeneratedBundledChannelConfigs(repoRoot) {
|
||||
return byPlugin;
|
||||
}
|
||||
|
||||
function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) {
|
||||
export function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) {
|
||||
if (!generatedChannelConfigs || Object.keys(generatedChannelConfigs).length === 0) {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@@ -19,16 +19,9 @@ import {
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
isAbsolute,
|
||||
join,
|
||||
posix,
|
||||
relative,
|
||||
resolve as pathResolve,
|
||||
} from "node:path";
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve as pathResolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { expandPackageDistImportClosure } from "./lib/package-dist-imports.mjs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
|
||||
@@ -400,145 +393,6 @@ export function pruneLegacyPluginRuntimeDepsState(params = {}) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
const JS_DIST_FILE_RE = /^dist\/.*\.(?:cjs|js|mjs)$/u;
|
||||
|
||||
function stripSpecifierSuffix(value) {
|
||||
return value.replace(/[?#].*$/u, "");
|
||||
}
|
||||
|
||||
function resolveDistImportPath(importerPath, specifier) {
|
||||
if (!specifier.startsWith(".")) {
|
||||
return null;
|
||||
}
|
||||
const stripped = stripSpecifierSuffix(specifier);
|
||||
if (!stripped) {
|
||||
return null;
|
||||
}
|
||||
return posix.normalize(posix.join(posix.dirname(importerPath), stripped));
|
||||
}
|
||||
|
||||
function findStatementStart(source, index) {
|
||||
return (
|
||||
Math.max(
|
||||
source.lastIndexOf(";", index),
|
||||
source.lastIndexOf("{", index),
|
||||
source.lastIndexOf("}", index),
|
||||
source.lastIndexOf("\n", index),
|
||||
source.lastIndexOf("\r", index),
|
||||
) + 1
|
||||
);
|
||||
}
|
||||
|
||||
function isImportSpecifierContext(source, index) {
|
||||
const dynamicPrefix = source.slice(Math.max(0, index - 32), index);
|
||||
if (/\bimport\s*\(\s*$/u.test(dynamicPrefix)) {
|
||||
return true;
|
||||
}
|
||||
const statementPrefix = source.slice(findStatementStart(source, index), index).trimStart();
|
||||
return (
|
||||
/^(?:import|export)\b[\s\S]*\bfrom\s*$/u.test(statementPrefix) ||
|
||||
/^import\s*$/u.test(statementPrefix)
|
||||
);
|
||||
}
|
||||
|
||||
function collectImportSpecifiers(source) {
|
||||
const specifiers = [];
|
||||
let inBlockComment = false;
|
||||
let inLineComment = false;
|
||||
for (let index = 0; index < source.length; index += 1) {
|
||||
if (inBlockComment) {
|
||||
if (source[index] === "*" && source[index + 1] === "/") {
|
||||
inBlockComment = false;
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inLineComment) {
|
||||
if (source[index] === "\n" || source[index] === "\r") {
|
||||
inLineComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (source[index] === "/" && source[index + 1] === "*") {
|
||||
inBlockComment = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (source[index] === "/" && source[index + 1] === "/") {
|
||||
inLineComment = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const quote = source[index];
|
||||
if (quote !== '"' && quote !== "'") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cursor = index + 1;
|
||||
let value = "";
|
||||
while (cursor < source.length) {
|
||||
const char = source[cursor];
|
||||
if (char === "\\") {
|
||||
value += source.slice(cursor, cursor + 2);
|
||||
cursor += 2;
|
||||
continue;
|
||||
}
|
||||
if (char === quote) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
cursor += 1;
|
||||
}
|
||||
if (cursor >= source.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (value.startsWith(".") && isImportSpecifierContext(source, index)) {
|
||||
specifiers.push(value);
|
||||
}
|
||||
index = cursor;
|
||||
}
|
||||
return specifiers;
|
||||
}
|
||||
|
||||
function expandInstalledDistImportClosure(params) {
|
||||
const files = [...new Set(params.files)];
|
||||
const fileSet = new Set(files);
|
||||
const expectedSet = new Set(params.seedFiles);
|
||||
let changed = true;
|
||||
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const importerPath of [...expectedSet]
|
||||
.filter((file) => fileSet.has(file))
|
||||
.toSorted((left, right) => left.localeCompare(right))) {
|
||||
if (!JS_DIST_FILE_RE.test(importerPath) || importerPath.includes("/node_modules/")) {
|
||||
continue;
|
||||
}
|
||||
let source;
|
||||
try {
|
||||
source = params.readText(importerPath);
|
||||
} catch (error) {
|
||||
if (error?.code === "ENOENT") {
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
for (const specifier of collectImportSpecifiers(source)) {
|
||||
const importedPath = resolveDistImportPath(importerPath, specifier);
|
||||
if (!importedPath || !fileSet.has(importedPath) || expectedSet.has(importedPath)) {
|
||||
continue;
|
||||
}
|
||||
expectedSet.add(importedPath);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...expectedSet].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function pruneInstalledPackageDist(params = {}) {
|
||||
const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
|
||||
const removeFile = params.unlinkSync ?? unlinkSync;
|
||||
@@ -569,11 +423,18 @@ export function pruneInstalledPackageDist(params = {}) {
|
||||
const installedFiles = listInstalledDistFiles(params);
|
||||
const readFile = params.readFileSync ?? readFileSync;
|
||||
expectedFiles = new Set(
|
||||
expandInstalledDistImportClosure({
|
||||
expandPackageDistImportClosure({
|
||||
files: installedFiles,
|
||||
seedFiles: [...expectedFiles],
|
||||
readText(relativePath) {
|
||||
return readFile(join(packageRoot, relativePath), "utf8");
|
||||
try {
|
||||
return readFile(join(packageRoot, relativePath), "utf8");
|
||||
} catch (error) {
|
||||
if (error?.code === "ENOENT") {
|
||||
return "";
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user