fix: harden Baileys postinstall and dep root dedupe

This commit is contained in:
Frank Yang
2026-04-14 16:31:24 +08:00
parent e9b635df3d
commit 7a48e4e5e1
4 changed files with 128 additions and 40 deletions

View File

@@ -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);

View File

@@ -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,

View File

@@ -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-",

View File

@@ -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: {