From d94ef22d70f02e56f043ff030fc6a025a19e56d9 Mon Sep 17 00:00:00 2001 From: Wade <160517347+Sriram-PR@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:55:44 +1100 Subject: [PATCH] fix(cli): route app bundles with trailing separators to native analysis (#3511) --- src/cli/coreBinaryCommands.ts | 3 +- .../cli/analyzeAppBundlePathCli.test.ts | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/boundary/cli/analyzeAppBundlePathCli.test.ts diff --git a/src/cli/coreBinaryCommands.ts b/src/cli/coreBinaryCommands.ts index ff8a865368..3f9c7f0c0b 100644 --- a/src/cli/coreBinaryCommands.ts +++ b/src/cli/coreBinaryCommands.ts @@ -235,7 +235,8 @@ const routesToJavaScriptAnalysis = async ( const isJavaScriptApplicationPath = async (path: string): Promise => { const lower = path.toLowerCase(); if (lower.endsWith(".asar")) return true; - if (lower.endsWith(".app")) return false; + // `A.app/` and `A.app/.` name the same bundle as `A.app`. + if (lower.replace(/(?:\/\.?)+$/u, "").endsWith(".app")) return false; try { return (await stat(path)).isDirectory(); } catch (cause: unknown) { diff --git a/tests/boundary/cli/analyzeAppBundlePathCli.test.ts b/tests/boundary/cli/analyzeAppBundlePathCli.test.ts new file mode 100644 index 0000000000..eb4d020f0c --- /dev/null +++ b/tests/boundary/cli/analyzeAppBundlePathCli.test.ts @@ -0,0 +1,52 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { describe, expect } from "vitest"; + +import { thinMach } from "../../../src/domain/binaryTarget.fixture.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; + +// An unknown standing provider stops native analysis before any engine runs. +const environment = { + REA_LOG_LEVEL: "silent", + REA_ANALYSIS_PROVIDER: "missing-provider", +} as const; + +const fixtureApp = async () => { + const directory = await createTestTempDirectory("rea-analyze-app-path-"); + const contents = join(directory, "Sample.app", "Contents"); + await mkdir(join(contents, "MacOS"), { recursive: true }); + await writeFile( + join(contents, "MacOS/Sample"), + thinMach(0xfeedfacf, 0x100000c), + ); + await writeFile( + join(contents, "Info.plist"), + "CFBundleExecutableSample", + ); + return directory; +}; + +describe("analyze app bundle path spellings", () => { + cliTest.for(["Sample.app", "Sample.app/", "Sample.app/.", "Sample.app//"])( + "routes %s to native provider selection", + async (path, { cli }) => { + const cwd = await fixtureApp(); + const result = await cli.run({ + arguments: ["analyze", path, "--json"], + cwd, + environment, + }); + expect(result.exitCode).toBe(1); + expect(result.json).toMatchObject({ + code: "capability_unavailable", + details: { + operation: "open_binary", + selection_reason: "unknown_provider", + requested_provider_id: "missing-provider", + }, + }); + }, + ); +});