


























@@ -31,54 +31,109 @@ export function refreshBundledPluginRuntimeMirrorRoot(params: {
3131if (isBundledRuntimeMirrorRootFresh(params.targetRoot, metadata)) {
3232return false;
3333}
34-const tempDir = fs.mkdtempSync(
35-path.join(
36-params.tempDirParent ?? path.dirname(params.targetRoot),
37-`.plugin-${sanitizeBundledRuntimeMirrorTempId(params.pluginId)}-`,
38-),
39-);
40-const stagedRoot = path.join(tempDir, "plugin");
41-try {
42-copyBundledPluginRuntimeRoot(params.sourceRoot, stagedRoot);
43-writeBundledRuntimeMirrorMetadata(stagedRoot, metadata);
44-fs.rmSync(params.targetRoot, { recursive: true, force: true });
45-fs.renameSync(stagedRoot, params.targetRoot);
46-return true;
47-} finally {
48-fs.rmSync(tempDir, { recursive: true, force: true });
49-}
34+copyBundledPluginRuntimeRoot(params.sourceRoot, params.targetRoot);
35+writeBundledRuntimeMirrorMetadata(params.targetRoot, metadata);
36+return true;
5037}
51385239export function copyBundledPluginRuntimeRoot(sourceRoot: string, targetRoot: string): void {
5340if (path.resolve(sourceRoot) === path.resolve(targetRoot)) {
5441return;
5542}
5643fs.mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
44+const mirroredNames = new Set<string>();
5745for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
5846if (shouldIgnoreBundledRuntimeMirrorEntry(entry.name)) {
5947continue;
6048}
49+if (!entry.isDirectory() && !entry.isSymbolicLink() && !entry.isFile()) {
50+continue;
51+}
52+mirroredNames.add(entry.name);
6153const sourcePath = path.join(sourceRoot, entry.name);
6254const targetPath = path.join(targetRoot, entry.name);
6355if (entry.isDirectory()) {
56+removeBundledRuntimeMirrorPathIfTypeChanged(targetPath, "directory");
6457copyBundledPluginRuntimeRoot(sourcePath, targetPath);
6558continue;
6659}
6760if (entry.isSymbolicLink()) {
68-fs.symlinkSync(fs.readlinkSync(sourcePath), targetPath);
61+removeBundledRuntimeMirrorPathIfTypeChanged(targetPath, "symlink");
62+replaceBundledRuntimeMirrorSymlinkAtomic(fs.readlinkSync(sourcePath), targetPath);
6963continue;
7064}
71-if (!entry.isFile()) {
72-continue;
73-}
74-fs.copyFileSync(sourcePath, targetPath);
65+removeBundledRuntimeMirrorPathIfTypeChanged(targetPath, "file");
66+copyBundledRuntimeMirrorFileAtomic(sourcePath, targetPath);
7567try {
7668const sourceMode = fs.statSync(sourcePath).mode;
7769fs.chmodSync(targetPath, sourceMode | 0o600);
7870} catch {
7971// Readable copied files are enough for plugin loading.
8072}
8173}
74+pruneStaleBundledRuntimeMirrorEntries(targetRoot, mirroredNames);
75+}
76+77+function pruneStaleBundledRuntimeMirrorEntries(targetRoot: string, mirroredNames: Set<string>): void {
78+for (const entry of fs.readdirSync(targetRoot, { withFileTypes: true })) {
79+if (shouldIgnoreBundledRuntimeMirrorEntry(entry.name)) {
80+continue;
81+}
82+if (mirroredNames.has(entry.name)) {
83+continue;
84+}
85+fs.rmSync(path.join(targetRoot, entry.name), { recursive: true, force: true });
86+}
87+}
88+89+function removeBundledRuntimeMirrorPathIfTypeChanged(
90+targetPath: string,
91+expectedType: "directory" | "file" | "symlink",
92+): void {
93+let stat: fs.Stats;
94+try {
95+stat = fs.lstatSync(targetPath);
96+} catch {
97+return;
98+}
99+const matches =
100+expectedType === "directory"
101+ ? stat.isDirectory()
102+ : expectedType === "symlink"
103+ ? stat.isSymbolicLink()
104+ : stat.isFile();
105+if (!matches) {
106+fs.rmSync(targetPath, { recursive: true, force: true });
107+}
108+}
109+110+function replaceBundledRuntimeMirrorSymlinkAtomic(linkTarget: string, targetPath: string): void {
111+fs.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 0o755 });
112+const tempPath = createBundledRuntimeMirrorTempPath(targetPath);
113+try {
114+fs.symlinkSync(linkTarget, tempPath);
115+fs.renameSync(tempPath, targetPath);
116+} finally {
117+fs.rmSync(tempPath, { force: true });
118+}
119+}
120+121+function copyBundledRuntimeMirrorFileAtomic(sourcePath: string, targetPath: string): void {
122+fs.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 0o755 });
123+const tempPath = createBundledRuntimeMirrorTempPath(targetPath);
124+try {
125+fs.copyFileSync(sourcePath, tempPath);
126+fs.renameSync(tempPath, targetPath);
127+} finally {
128+fs.rmSync(tempPath, { force: true });
129+}
130+}
131+132+function createBundledRuntimeMirrorTempPath(targetPath: string): string {
133+return path.join(
134+path.dirname(targetPath),
135+`.openclaw-mirror-${process.pid}-${process.hrtime.bigint()}-${path.basename(targetPath)}.tmp`,
136+);
82137}
8313884139export function precomputeBundledRuntimeMirrorMetadata(params: {
@@ -235,7 +290,3 @@ function resolveBundledRuntimeMirrorSourceRootId(sourceRoot: string): string {
235290function shouldIgnoreBundledRuntimeMirrorEntry(name: string): boolean {
236291return name === "node_modules" || name === BUNDLED_RUNTIME_MIRROR_METADATA_FILE;
237292}
238-239-function sanitizeBundledRuntimeMirrorTempId(pluginId: string): string {
240-return pluginId.replaceAll(/[^a-zA-Z0-9._-]/g, "_");
241-}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。