mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:40:44 +00:00
fix: harden Baileys postinstall and dep root dedupe
This commit is contained in:
@@ -293,11 +293,9 @@ export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
|
||||
(BAILEYS_MEDIA_HOTFIX_FINISH_PROMISES_RE.test(patchedText) &&
|
||||
(BAILEYS_MEDIA_HOTFIX_PROMISE_ALL_RE.test(patchedText) ||
|
||||
BAILEYS_MEDIA_HOTFIX_SEQUENTIAL_AWAITS_RE.test(patchedText)));
|
||||
const encryptedStreamPatchable = patchedText.includes(BAILEYS_MEDIA_HOTFIX_NEEDLE);
|
||||
|
||||
if (!encryptedStreamAlreadyPatched) {
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_HOTFIX_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
if (!encryptedStreamAlreadyPatched && encryptedStreamPatchable) {
|
||||
if (!BAILEYS_MEDIA_ONCE_IMPORT_RE.test(patchedText)) {
|
||||
return { applied: false, reason: "missing_once_import", targetPath };
|
||||
}
|
||||
@@ -311,17 +309,14 @@ export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
|
||||
applied = true;
|
||||
}
|
||||
|
||||
if (
|
||||
!patchedText.includes(
|
||||
"...(typeof fetchAgent?.dispatch === 'function' ? { dispatcher: fetchAgent } : {}),",
|
||||
)
|
||||
) {
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_DISPATCHER_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
const dispatcherAlreadyPatched = patchedText.includes(
|
||||
"...(typeof fetchAgent?.dispatch === 'function' ? { dispatcher: fetchAgent } : {}),",
|
||||
);
|
||||
const dispatcherPatchable =
|
||||
patchedText.includes(BAILEYS_MEDIA_DISPATCHER_NEEDLE) &&
|
||||
patchedText.includes(BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE);
|
||||
|
||||
if (!dispatcherAlreadyPatched && dispatcherPatchable) {
|
||||
patchedText = patchedText
|
||||
.replace(BAILEYS_MEDIA_DISPATCHER_NEEDLE, BAILEYS_MEDIA_DISPATCHER_REPLACEMENT)
|
||||
.replace(
|
||||
@@ -332,6 +327,12 @@ export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
|
||||
}
|
||||
|
||||
if (!applied) {
|
||||
if (
|
||||
(!encryptedStreamAlreadyPatched && !encryptedStreamPatchable) ||
|
||||
(!dispatcherAlreadyPatched && !dispatcherPatchable)
|
||||
) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
return { applied: false, reason: "already_patched" };
|
||||
}
|
||||
const tempPath = createTempPath(targetPath);
|
||||
|
||||
@@ -171,8 +171,9 @@ function collectInstalledRuntimeDependencyRoots(rootNodeModulesDir, dependencySp
|
||||
if (depRoot === null) {
|
||||
return null;
|
||||
}
|
||||
const canonicalDepRoot = fs.realpathSync(depRoot);
|
||||
|
||||
const seenKey = `${current.depName}\0${depRoot}`;
|
||||
const seenKey = `${current.depName}\0${canonicalDepRoot}`;
|
||||
if (seen.has(seenKey)) {
|
||||
continue;
|
||||
}
|
||||
@@ -184,8 +185,9 @@ function collectInstalledRuntimeDependencyRoots(rootNodeModulesDir, dependencySp
|
||||
directRoots.push(record);
|
||||
}
|
||||
|
||||
const packageJson = packageCache.get(depRoot) ?? readJson(path.join(depRoot, "package.json"));
|
||||
packageCache.set(depRoot, packageJson);
|
||||
const packageJson =
|
||||
packageCache.get(canonicalDepRoot) ?? readJson(path.join(depRoot, "package.json"));
|
||||
packageCache.set(canonicalDepRoot, packageJson);
|
||||
for (const [childName, childSpec] of Object.entries(packageJson.dependencies ?? {})) {
|
||||
queue.push({
|
||||
depName: childName,
|
||||
|
||||
@@ -68,23 +68,14 @@ function createBaileysMessagesMediaSource(params?: {
|
||||
encryptedStreamPatched?: boolean;
|
||||
encryptedStreamPatchedSequentially?: boolean;
|
||||
encryptedStreamPatchedSequentiallyWithComments?: boolean;
|
||||
encryptedStreamUnrecognized?: boolean;
|
||||
}) {
|
||||
const encryptedLines = params?.encryptedStreamPatchedSequentiallyWithComments
|
||||
const encryptedLines = params?.encryptedStreamUnrecognized
|
||||
? [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" const encFinishPromise = once(encFileWriteStream, 'finish');",
|
||||
" const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" // Wait for write streams to fully flush to disk before returning encFilePath.",
|
||||
" // Without this await, the caller may open a read stream on the file before",
|
||||
" // the OS has created it, causing a race-condition ENOENT crash.",
|
||||
" await encFinishPromise;",
|
||||
" await originalFinishPromise;",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
" logger?.debug('encrypted data changed upstream');",
|
||||
]
|
||||
: params?.encryptedStreamPatchedSequentially
|
||||
: params?.encryptedStreamPatchedSequentiallyWithComments
|
||||
? [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" const encFinishPromise = once(encFileWriteStream, 'finish');",
|
||||
@@ -92,11 +83,14 @@ function createBaileysMessagesMediaSource(params?: {
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" // Wait for write streams to fully flush to disk before returning encFilePath.",
|
||||
" // Without this await, the caller may open a read stream on the file before",
|
||||
" // the OS has created it, causing a race-condition ENOENT crash.",
|
||||
" await encFinishPromise;",
|
||||
" await originalFinishPromise;",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
]
|
||||
: params?.encryptedStreamPatched
|
||||
: params?.encryptedStreamPatchedSequentially
|
||||
? [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" const encFinishPromise = once(encFileWriteStream, 'finish');",
|
||||
@@ -104,16 +98,28 @@ function createBaileysMessagesMediaSource(params?: {
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
" await encFinishPromise;",
|
||||
" await originalFinishPromise;",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
]
|
||||
: [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
];
|
||||
: params?.encryptedStreamPatched
|
||||
? [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" const encFinishPromise = once(encFileWriteStream, 'finish');",
|
||||
" const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
]
|
||||
: [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
];
|
||||
const dispatcherLines = params?.dispatcherPatched
|
||||
? [
|
||||
" const response = await fetch(url, {",
|
||||
@@ -355,6 +361,39 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("patches the Baileys dispatcher guard even when the encryptedStream block changed", async () => {
|
||||
const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-hotfix-dispatcher-only-");
|
||||
const targetPath = path.join(
|
||||
repoRoot,
|
||||
"node_modules",
|
||||
"@whiskeysockets",
|
||||
"baileys",
|
||||
"lib",
|
||||
"Utils",
|
||||
"messages-media.js",
|
||||
);
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
createBaileysMessagesMediaSource({ encryptedStreamUnrecognized: true }),
|
||||
);
|
||||
|
||||
const { applyBaileysEncryptedStreamFinishHotfix } = await loadPostinstallBundledPluginsModule();
|
||||
const result = applyBaileysEncryptedStreamFinishHotfix({ packageRoot: repoRoot });
|
||||
|
||||
expect(result).toEqual({
|
||||
applied: true,
|
||||
reason: "patched",
|
||||
targetPath,
|
||||
});
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"logger?.debug('encrypted data changed upstream');",
|
||||
);
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"...(typeof fetchAgent?.dispatch === 'function' ? { dispatcher: fetchAgent } : {}),",
|
||||
);
|
||||
});
|
||||
|
||||
it("patches the Baileys dispatcher guard when sequential awaits include comments", async () => {
|
||||
const repoRoot = makeRepoRoot(
|
||||
"openclaw-stage-bundled-runtime-hotfix-dispatcher-sequential-comments-",
|
||||
|
||||
@@ -331,6 +331,52 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
expect(fs.readFileSync(stampPath, "utf8")).toBe(firstStamp);
|
||||
});
|
||||
|
||||
it("dedupes cyclic dependency aliases by canonical root", () => {
|
||||
const { pluginDir, repoRoot } = createBundledPluginFixture({
|
||||
packageJson: {
|
||||
name: "@openclaw/fixture-plugin",
|
||||
version: "1.0.0",
|
||||
dependencies: { a: "1.0.0" },
|
||||
openclaw: { bundle: { stageRuntimeDependencies: true } },
|
||||
},
|
||||
});
|
||||
const rootNodeModulesDir = path.join(repoRoot, "node_modules");
|
||||
const storeDir = path.join(repoRoot, ".store");
|
||||
const aStoreDir = path.join(storeDir, "a");
|
||||
const bStoreDir = path.join(storeDir, "b");
|
||||
fs.mkdirSync(path.join(aStoreDir, "node_modules"), { recursive: true });
|
||||
fs.mkdirSync(path.join(bStoreDir, "node_modules"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(aStoreDir, "package.json"),
|
||||
'{ "name": "a", "version": "1.0.0", "dependencies": { "b": "1.0.0" } }\n',
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(aStoreDir, "index.js"), "module.exports = 'a';\n", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(bStoreDir, "package.json"),
|
||||
'{ "name": "b", "version": "1.0.0", "dependencies": { "a": "1.0.0" } }\n',
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(bStoreDir, "index.js"), "module.exports = 'b';\n", "utf8");
|
||||
fs.mkdirSync(rootNodeModulesDir, { recursive: true });
|
||||
fs.symlinkSync(aStoreDir, path.join(rootNodeModulesDir, "a"));
|
||||
fs.symlinkSync(bStoreDir, path.join(rootNodeModulesDir, "b"));
|
||||
fs.symlinkSync(bStoreDir, path.join(aStoreDir, "node_modules", "b"));
|
||||
fs.symlinkSync(aStoreDir, path.join(bStoreDir, "node_modules", "a"));
|
||||
|
||||
stageBundledPluginRuntimeDeps({ cwd: repoRoot });
|
||||
|
||||
expect(fs.readFileSync(path.join(pluginDir, "node_modules", "a", "index.js"), "utf8")).toBe(
|
||||
"module.exports = 'a';\n",
|
||||
);
|
||||
expect(
|
||||
fs.readFileSync(
|
||||
path.join(pluginDir, "node_modules", "a", "node_modules", "b", "index.js"),
|
||||
"utf8",
|
||||
),
|
||||
).toBe("module.exports = 'b';\n");
|
||||
});
|
||||
|
||||
it("falls back to install when the root transitive closure is incomplete", () => {
|
||||
const { pluginDir, repoRoot } = createBundledPluginFixture({
|
||||
packageJson: {
|
||||
|
||||
Reference in New Issue
Block a user