fix(cli): cancel routed JavaScript analysis in rea analyze (#1156)
`rea analyze PATH` routes a JavaScript directory or ASAR to the same static analysis as `rea analyze-javascript-application`, but it passed no signal and was not wrapped in withCommandCancellation. On SIGINT the process was killed with no output (signal SIGINT, empty stdout). The dedicated command returns a typed `cancelled` result and exits 130. The #1098 review raised this route. Decide the route first. A JavaScript target now runs exactly like the dedicated command: cancellation wraps logging, so the cancelled exit code is not replaced by the logged failure status. The binary route is unchanged and keeps its own SIGINT handling in runDirectAnalysis. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
72201668ed
commit
089bbf58f3
2 files changed
+47
-43
No files matched your search
@@ -14,10 +14,8 @@ import {
|
||||
} from "./options.js";
|
||||
import type { CliInstance } from "./types.js";
|
||||
import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnalysis.js";
|
||||
import type {
|
||||
CliCommandOutput,
|
||||
CliResultOutput,
|
||||
} from "./streamedJsonOutput.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import type { CliResultOutput } from "./streamedJsonOutput.js";
|
||||
|
||||
/** Register provider-neutral binary overview and procedure CLI commands. */
|
||||
export const registerCoreBinaryCommands = (
|
||||
@@ -54,17 +52,38 @@ const registerOverviewCommands = (
|
||||
path: z.string().describe("App, program, or analysis database path"),
|
||||
}),
|
||||
options: overviewOptions,
|
||||
run: ({ args, options, format }) =>
|
||||
logCliCommand(logger, "analyze", () =>
|
||||
runRoutedOverview(
|
||||
run: async ({ args, options, format }) => {
|
||||
const output =
|
||||
resultOutput === undefined
|
||||
? undefined
|
||||
: { output: resultOutput, command: CLI_COMMANDS.analyze, format };
|
||||
// Route JavaScript targets exactly like analyze-javascript-application,
|
||||
// including typed cancellation; cancellation wraps logging so its exit
|
||||
// code is not replaced by the logged failure status.
|
||||
if (await routesToJavaScriptAnalysis(args.path, options))
|
||||
return withCommandCancellation((signal) =>
|
||||
logCliCommand(logger, "analyze", () =>
|
||||
runCliJavaScriptApplicationAnalysis(
|
||||
{ input_path: resolve(args.path) },
|
||||
output,
|
||||
signal,
|
||||
),
|
||||
),
|
||||
);
|
||||
return logCliCommand(logger, "analyze", () =>
|
||||
runDirectAnalysis(
|
||||
args.path,
|
||||
options,
|
||||
logger,
|
||||
resultOutput === undefined
|
||||
? undefined
|
||||
: { output: resultOutput, command: CLI_COMMANDS.analyze, format },
|
||||
"binary_overview",
|
||||
{},
|
||||
directAnalysisOptions(
|
||||
logger,
|
||||
options.snapshot,
|
||||
options.provider,
|
||||
options["target-format"],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
cli.command(CLI_COMMANDS.inspect, {
|
||||
description: "Inspect an app overview with evidence",
|
||||
@@ -140,38 +159,19 @@ const registerDecompileCommand = (cli: CliInstance, logger: Logger): void => {
|
||||
});
|
||||
};
|
||||
|
||||
const runRoutedOverview = async (
|
||||
/** Select static JavaScript analysis only when no binary-analysis option applies. */
|
||||
const routesToJavaScriptAnalysis = async (
|
||||
path: string,
|
||||
options: {
|
||||
readonly snapshot?: string | undefined;
|
||||
readonly "target-format"?: "dos-com" | undefined;
|
||||
readonly provider?: string | undefined;
|
||||
},
|
||||
logger: Logger,
|
||||
output?: CliCommandOutput,
|
||||
) => {
|
||||
if (
|
||||
options.provider === undefined &&
|
||||
options.snapshot === undefined &&
|
||||
options["target-format"] === undefined &&
|
||||
(await isJavaScriptApplicationPath(path))
|
||||
)
|
||||
return runCliJavaScriptApplicationAnalysis(
|
||||
{ input_path: resolve(path) },
|
||||
output,
|
||||
);
|
||||
return runDirectAnalysis(
|
||||
path,
|
||||
"binary_overview",
|
||||
{},
|
||||
directAnalysisOptions(
|
||||
logger,
|
||||
options.snapshot,
|
||||
options.provider,
|
||||
options["target-format"],
|
||||
),
|
||||
);
|
||||
};
|
||||
): Promise<boolean> =>
|
||||
options.provider === undefined &&
|
||||
options.snapshot === undefined &&
|
||||
options["target-format"] === undefined &&
|
||||
(await isJavaScriptApplicationPath(path));
|
||||
|
||||
const isJavaScriptApplicationPath = async (path: string): Promise<boolean> => {
|
||||
const lower = path.toLowerCase();
|
||||
|
||||
@@ -34,14 +34,18 @@ it("returns a nonzero exit for a streamed JSON analysis failure", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"returns a typed cancelled CLI result and exit 130 after SIGINT",
|
||||
async () => {
|
||||
// `analyze` routes a JavaScript directory to the same workflow and must keep
|
||||
// its typed cancellation contract.
|
||||
it
|
||||
.skipIf(process.platform === "win32")
|
||||
.each(["analyze-javascript-application", "analyze"])(
|
||||
"returns a typed cancelled CLI result and exit 130 after SIGINT through %s",
|
||||
async (command) => {
|
||||
const root = await createTestTempDirectory("rea-cli-js-cancellation-");
|
||||
await writeFile(join(root, "main.js"), "export const observed = 1;\n");
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "analyze-javascript-application", root, "--json"],
|
||||
["scripts/rea.mjs", command, root, "--json"],
|
||||
{
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user