mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(packaging): distinguish tar line endings from filename bytes
Parse one listing separator so CRLF output matches packaged bridge paths while literal CR bytes in LF listings remain part of filenames. Retain generated-bytecode rejection. Refs #4154 Co-authored-by: abdooo2235 <abdalrhmantheiv15@gmail.com> Co-authored-by: Ysopwnable <161784452+N0zoM1z0@users.noreply.github.com>
This commit is contained in:
@@ -17,7 +17,10 @@ export async function verifyPackagePack({ root, workspace }) {
|
||||
})
|
||||
).stdout.trim(),
|
||||
);
|
||||
const packedFiles = (await exec("tar", ["-tf", tarball])).stdout.split("\n");
|
||||
const listing = (await exec("tar", ["-tf", tarball])).stdout;
|
||||
// A bare LF identifies LF output; preceding CRs then belong to filenames.
|
||||
const lineEnding = /(^|[^\r])\n/u.test(listing) ? "\n" : "\r\n";
|
||||
const packedFiles = listing.split(lineEnding);
|
||||
const packedManifest = JSON.parse(
|
||||
(await exec("tar", ["-xOf", tarball, "package/package.json"])).stdout,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const packerUrl = pathToFileURL(
|
||||
resolve("scripts/verify-package-pack.mjs"),
|
||||
).href;
|
||||
const catalogUrl = pathToFileURL(resolve("dist/catalogIdentity.js")).href;
|
||||
const bridge = "package/bridge/hopper_bridge.py";
|
||||
const members = [
|
||||
bridge,
|
||||
"package/bridge/cutter/rea_bridge.py",
|
||||
"package/bridge/ghidra/ReaGhidraBridge.java",
|
||||
"package/bridge/ghidra/ReaGhidraBundleActivator.java",
|
||||
"package/bridge/ghidra/ReaGhidraExtensions.java",
|
||||
"package/bridge/ghidra/ReaGhidraPrepareCom.java",
|
||||
"package/bridge/ghidra/ReaGhidraPrepareRaw.java",
|
||||
"package/bridge/ghidra/ReaGhidraApplySeeds.java",
|
||||
"package/bridge/ghidra/ReaGhidraAnalysisOptions.java",
|
||||
"package/bridge/ghidra/ReaGhidraNoReturnFix.java",
|
||||
"package/bridge/ghidra/ReaGhidraMipsSoftFloat.java",
|
||||
];
|
||||
|
||||
async function verifyListing(listing: string) {
|
||||
const workspace = await createTestTempDirectory("rea-tar-listing-");
|
||||
const { stdout } = await execFileAsync(process.execPath, [
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
`import childProcess from "node:child_process";
|
||||
import { syncBuiltinESMExports } from "node:module";
|
||||
import { promisify } from "node:util";
|
||||
import { CATALOG_IDENTITY } from ${JSON.stringify(catalogUrl)};
|
||||
let bridgeParsed = false;
|
||||
childProcess.execFile = () => { throw new Error("Expected promisified execution"); };
|
||||
childProcess.execFile[promisify.custom] = async (command, args) => {
|
||||
let stdout;
|
||||
if ((command === "npm" && args[0] === "pack") || (command === process.execPath && args[1] === "pack")) stdout = "fixture.tgz\\n";
|
||||
else if (command === "tar" && args[0] === "-tf") stdout = ${JSON.stringify(listing)};
|
||||
else if (command === "tar" && args[0] === "-xOf" && args[2] === "package/package.json")
|
||||
stdout = JSON.stringify({ bin: { rea: "scripts/rea.mjs", "rea-agents": "scripts/rea.mjs" }, mcpName: "io.github.morluto/rea", dependencies: { "@lydell/node-pty": "1.1.0" } });
|
||||
else if (command === "tar" && args[0] === "-xOf" && args[2] === "package/skills/reverse-engineer-anything/SKILL.md")
|
||||
stdout = " tool_count: " + CATALOG_IDENTITY.counts.mcp_tools + "\\n";
|
||||
else if (command === "tar" && args[0] === "-xf") stdout = "";
|
||||
else if (command === "python3") { bridgeParsed = true; stdout = ""; }
|
||||
else throw new Error("Unexpected command: " + command + " " + JSON.stringify(args));
|
||||
return { stdout, stderr: "" };
|
||||
};
|
||||
syncBuiltinESMExports();
|
||||
const { verifyPackagePack } = await import(${JSON.stringify(packerUrl)});
|
||||
try {
|
||||
const result = await verifyPackagePack({ root: process.cwd(), workspace: ${JSON.stringify(workspace)} });
|
||||
process.stdout.write(JSON.stringify({ ...result, bridgeParsed }));
|
||||
} catch (cause) {
|
||||
process.stdout.write(JSON.stringify({ error: cause.message, bridgeParsed }));
|
||||
}`,
|
||||
]);
|
||||
return { result: JSON.parse(stdout), workspace };
|
||||
}
|
||||
|
||||
it("preserves a literal trailing CR in an LF-listed member", async () => {
|
||||
const listing =
|
||||
members.map((path) => (path === bridge ? `${path}\r` : path)).join("\n") +
|
||||
"\n";
|
||||
const { result } = await verifyListing(listing);
|
||||
expect(result).toEqual({
|
||||
error: `package omitted ${bridge}`,
|
||||
bridgeParsed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts exact bridge entries from a CRLF tar listing and still rejects generated bytecode", async () => {
|
||||
const ending = "\r\n";
|
||||
const { result, workspace } = await verifyListing(
|
||||
members.join(ending) + ending,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
tarball: join(workspace, "pack/fixture.tgz"),
|
||||
bridgeParsed: true,
|
||||
});
|
||||
|
||||
const rejected = await verifyListing(
|
||||
[...members, "package/bridge/stale.pyc"].join(ending) + ending,
|
||||
);
|
||||
expect(rejected.result).toEqual({
|
||||
error: "package contained generated Python bytecode",
|
||||
bridgeParsed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts exact bridge entries from an LF tar listing", async () => {
|
||||
const { result } = await verifyListing(members.join("\n") + "\n");
|
||||
expect(result.bridgeParsed).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user