


















@@ -0,0 +1,219 @@
1+import { createHash } from "node:crypto";
2+import fs from "node:fs";
3+import path from "node:path";
4+5+const BUNDLED_RUNTIME_MIRROR_METADATA_FILE = ".openclaw-runtime-mirror.json";
6+const BUNDLED_RUNTIME_MIRROR_METADATA_VERSION = 1;
7+8+type BundledRuntimeMirrorMetadata = {
9+version: number;
10+pluginId: string;
11+sourceRoot: string;
12+sourceFingerprint: string;
13+};
14+15+export function refreshBundledPluginRuntimeMirrorRoot(params: {
16+pluginId: string;
17+sourceRoot: string;
18+targetRoot: string;
19+tempDirParent?: string;
20+}): boolean {
21+if (path.resolve(params.sourceRoot) === path.resolve(params.targetRoot)) {
22+return false;
23+}
24+const metadata = createBundledRuntimeMirrorMetadata(params);
25+if (isBundledRuntimeMirrorRootFresh(params.targetRoot, metadata)) {
26+return false;
27+}
28+const tempDir = fs.mkdtempSync(
29+path.join(
30+params.tempDirParent ?? path.dirname(params.targetRoot),
31+`.plugin-${sanitizeBundledRuntimeMirrorTempId(params.pluginId)}-`,
32+),
33+);
34+const stagedRoot = path.join(tempDir, "plugin");
35+try {
36+copyBundledPluginRuntimeRoot(params.sourceRoot, stagedRoot);
37+writeBundledRuntimeMirrorMetadata(stagedRoot, metadata);
38+fs.rmSync(params.targetRoot, { recursive: true, force: true });
39+fs.renameSync(stagedRoot, params.targetRoot);
40+return true;
41+} finally {
42+fs.rmSync(tempDir, { recursive: true, force: true });
43+}
44+}
45+46+export function copyBundledPluginRuntimeRoot(sourceRoot: string, targetRoot: string): void {
47+if (path.resolve(sourceRoot) === path.resolve(targetRoot)) {
48+return;
49+}
50+fs.mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
51+for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
52+if (shouldIgnoreBundledRuntimeMirrorEntry(entry.name)) {
53+continue;
54+}
55+const sourcePath = path.join(sourceRoot, entry.name);
56+const targetPath = path.join(targetRoot, entry.name);
57+if (entry.isDirectory()) {
58+copyBundledPluginRuntimeRoot(sourcePath, targetPath);
59+continue;
60+}
61+if (entry.isSymbolicLink()) {
62+fs.symlinkSync(fs.readlinkSync(sourcePath), targetPath);
63+continue;
64+}
65+if (!entry.isFile()) {
66+continue;
67+}
68+fs.copyFileSync(sourcePath, targetPath);
69+try {
70+const sourceMode = fs.statSync(sourcePath).mode;
71+fs.chmodSync(targetPath, sourceMode | 0o600);
72+} catch {
73+// Readable copied files are enough for plugin loading.
74+}
75+}
76+}
77+78+function createBundledRuntimeMirrorMetadata(params: {
79+pluginId: string;
80+sourceRoot: string;
81+}): BundledRuntimeMirrorMetadata {
82+return {
83+version: BUNDLED_RUNTIME_MIRROR_METADATA_VERSION,
84+pluginId: params.pluginId,
85+sourceRoot: resolveBundledRuntimeMirrorSourceRootId(params.sourceRoot),
86+sourceFingerprint: fingerprintBundledRuntimeMirrorSourceRoot(params.sourceRoot),
87+};
88+}
89+90+function isBundledRuntimeMirrorRootFresh(
91+targetRoot: string,
92+expected: BundledRuntimeMirrorMetadata,
93+): boolean {
94+try {
95+if (!fs.lstatSync(targetRoot).isDirectory()) {
96+return false;
97+}
98+} catch {
99+return false;
100+}
101+const actual = readBundledRuntimeMirrorMetadata(targetRoot);
102+return (
103+actual?.version === expected.version &&
104+actual.pluginId === expected.pluginId &&
105+actual.sourceRoot === expected.sourceRoot &&
106+actual.sourceFingerprint === expected.sourceFingerprint
107+);
108+}
109+110+function readBundledRuntimeMirrorMetadata(targetRoot: string): BundledRuntimeMirrorMetadata | null {
111+try {
112+const parsed = JSON.parse(
113+fs.readFileSync(path.join(targetRoot, BUNDLED_RUNTIME_MIRROR_METADATA_FILE), "utf8"),
114+) as Partial<BundledRuntimeMirrorMetadata>;
115+if (
116+parsed.version !== BUNDLED_RUNTIME_MIRROR_METADATA_VERSION ||
117+typeof parsed.pluginId !== "string" ||
118+typeof parsed.sourceRoot !== "string" ||
119+typeof parsed.sourceFingerprint !== "string"
120+) {
121+return null;
122+}
123+return parsed as BundledRuntimeMirrorMetadata;
124+} catch {
125+return null;
126+}
127+}
128+129+function writeBundledRuntimeMirrorMetadata(
130+targetRoot: string,
131+metadata: BundledRuntimeMirrorMetadata,
132+): void {
133+fs.writeFileSync(
134+path.join(targetRoot, BUNDLED_RUNTIME_MIRROR_METADATA_FILE),
135+`${JSON.stringify(metadata, null, 2)}\n`,
136+"utf8",
137+);
138+}
139+140+function fingerprintBundledRuntimeMirrorSourceRoot(sourceRoot: string): string {
141+const hash = createHash("sha256");
142+hashBundledRuntimeMirrorDirectory(hash, sourceRoot, sourceRoot);
143+return hash.digest("hex");
144+}
145+146+function hashBundledRuntimeMirrorDirectory(
147+hash: ReturnType<typeof createHash>,
148+sourceRoot: string,
149+directory: string,
150+): void {
151+const entries = fs
152+.readdirSync(directory, { withFileTypes: true })
153+.filter((entry) => !shouldIgnoreBundledRuntimeMirrorEntry(entry.name))
154+.toSorted((left, right) => left.name.localeCompare(right.name));
155+156+for (const entry of entries) {
157+const sourcePath = path.join(directory, entry.name);
158+const relativePath = path.relative(sourceRoot, sourcePath).replaceAll(path.sep, "/");
159+const stat = fs.lstatSync(sourcePath, { bigint: true });
160+if (entry.isDirectory()) {
161+updateBundledRuntimeMirrorHash(hash, [
162+"dir",
163+relativePath,
164+formatBundledRuntimeMirrorMode(stat.mode),
165+]);
166+hashBundledRuntimeMirrorDirectory(hash, sourceRoot, sourcePath);
167+continue;
168+}
169+if (entry.isSymbolicLink()) {
170+updateBundledRuntimeMirrorHash(hash, [
171+"symlink",
172+relativePath,
173+formatBundledRuntimeMirrorMode(stat.mode),
174+stat.ctimeNs.toString(),
175+fs.readlinkSync(sourcePath),
176+]);
177+continue;
178+}
179+if (!entry.isFile()) {
180+continue;
181+}
182+updateBundledRuntimeMirrorHash(hash, [
183+"file",
184+relativePath,
185+formatBundledRuntimeMirrorMode(stat.mode),
186+stat.size.toString(),
187+stat.mtimeNs.toString(),
188+stat.ctimeNs.toString(),
189+]);
190+}
191+}
192+193+function updateBundledRuntimeMirrorHash(
194+hash: ReturnType<typeof createHash>,
195+fields: readonly string[],
196+): void {
197+hash.update(JSON.stringify(fields));
198+hash.update("\n");
199+}
200+201+function formatBundledRuntimeMirrorMode(mode: bigint): string {
202+return (mode & 0o7777n).toString(8);
203+}
204+205+function resolveBundledRuntimeMirrorSourceRootId(sourceRoot: string): string {
206+try {
207+return fs.realpathSync.native(sourceRoot);
208+} catch {
209+return path.resolve(sourceRoot);
210+}
211+}
212+213+function shouldIgnoreBundledRuntimeMirrorEntry(name: string): boolean {
214+return name === "node_modules" || name === BUNDLED_RUNTIME_MIRROR_METADATA_FILE;
215+}
216+217+function sanitizeBundledRuntimeMirrorTempId(pluginId: string): string {
218+return pluginId.replaceAll(/[^a-zA-Z0-9._-]/g, "_");
219+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。