From 42aaef8bdb458105b7d7260c60fa6f5612dde51b Mon Sep 17 00:00:00 2001 From: RRFZ <247457381+Plastic-time@users.noreply.github.com> Date: Fri, 6 Nov 2026 02:13:43 -0500 Subject: [PATCH] fix(cli): require absolute ADB device paths for file pull and push (#9048) pull-adb-file and push-adb-file declared device-path as any non-empty string. The CLI schema advertised a relative path as valid, and the service then refused it under the MCP field name. Reuse the shared device_path fields, and keep the regex constraint in CLI field errors as list-adb-directory does. --- src/cli/adbCommands.ts | 4 +- src/cli/argumentErrors.ts | 2 + .../adbPackageInventoryCoverage.test.ts | 48 ++++++++++++++++--- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/cli/adbCommands.ts b/src/cli/adbCommands.ts index d86da63dd1..f908935da0 100644 --- a/src/cli/adbCommands.ts +++ b/src/cli/adbCommands.ts @@ -145,7 +145,7 @@ export const registerAdbCommands = ( description: "Pull one device file to a local copy with a SHA-256 digest", args: z.object({ serial, - "device-path": z.string().min(1).describe("Absolute device path to pull"), + "device-path": adbInputSchemas.pull_adb_file.shape.device_path, }), options: z.object({ "output-directory": z @@ -166,7 +166,7 @@ export const registerAdbCommands = ( args: z.object({ serial, "local-path": z.string().min(1).describe("Existing local file"), - "device-path": z.string().min(1).describe("Absolute device destination"), + "device-path": adbInputSchemas.push_adb_file.shape.device_path, }), options: z.object({ overwrite: z diff --git a/src/cli/argumentErrors.ts b/src/cli/argumentErrors.ts index 0becf11a73..f213b861a8 100644 --- a/src/cli/argumentErrors.ts +++ b/src/cli/argumentErrors.ts @@ -22,6 +22,8 @@ const isAdbSelector = (command: string, field: string): boolean => (adbPackageCommands.has(command) && field === "package") || (command === "list-adb-directory" && field === "device-path") || (command === "resolve-adb-packages" && field === "query") || + (["pull-adb-file", "push-adb-file"].includes(command) && + field === "device-path") || (command === "start-adb-activity" && ["action", "component", "data-uri"].includes(field)); diff --git a/tests/acceptance/applications/adbPackageInventoryCoverage.test.ts b/tests/acceptance/applications/adbPackageInventoryCoverage.test.ts index 06651fedb7..78179e780f 100644 --- a/tests/acceptance/applications/adbPackageInventoryCoverage.test.ts +++ b/tests/acceptance/applications/adbPackageInventoryCoverage.test.ts @@ -12,7 +12,18 @@ import { cliTest, runFileCli } from "../../support/cli/cliFixture.js"; const PACKAGE_LINE = "package:/data/app/example/base.apk=com.example.app"; -cliTest.for([ +interface AdbSelectorCase { + readonly command: string; + readonly field: string; + readonly valid: string; + readonly invalid: string; + readonly constraint: Readonly>; + /** Positional arguments between the serial and the checked field. */ + readonly between?: Readonly>; + readonly options?: readonly string[]; +} + +cliTest.for([ { command: "list-adb-directory", field: "device-path", @@ -20,6 +31,22 @@ cliTest.for([ invalid: "sdcard", constraint: { expected: "regex" }, }, + { + command: "pull-adb-file", + field: "device-path", + valid: "/sdcard/notes.txt", + invalid: "sdcard/notes.txt", + constraint: { expected: "regex" }, + options: ["--output-directory", "pulled"], + }, + { + command: "push-adb-file", + field: "device-path", + valid: "/data/local/tmp/payload.bin", + invalid: "data/local/tmp/payload.bin", + constraint: { expected: "regex" }, + between: { "local-path": "payload.bin" }, + }, { command: "resolve-adb-packages", field: "query", @@ -29,18 +56,27 @@ cliTest.for([ }, ])( "publishes the enforced ADB selector constraint for $command", - async ({ command, field, valid, invalid, constraint }, { cli }) => { + async ( + { command, field, valid, invalid, constraint, between = {}, options = [] }, + { cli }, + ) => { const schema = await cli.run({ arguments: [command, "--schema", "--json"], }); expect(schema.exitCode, schema.stderr).toBe(0); const output = schema.json as { args: Parameters[0] }; const validate = new Ajv2020({ strict: false }).compile(output.args); - const serial = "emulator-5554"; - expect(validate({ serial, [field]: valid })).toBe(true); - expect(validate({ serial, [field]: invalid })).toBe(false); + const leading = { serial: "emulator-5554", ...between }; + expect(validate({ ...leading, [field]: valid })).toBe(true); + expect(validate({ ...leading, [field]: invalid })).toBe(false); const result = await cli.run({ - arguments: [command, serial, invalid, "--json"], + arguments: [ + command, + ...Object.values(leading), + invalid, + ...options, + "--json", + ], }); expect(result.exitCode).toBe(1); expect(result.json).toMatchObject({