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.
This commit is contained in:
RRFZ authored and GitHub committed 2026-11-06 15:13:43 +08:00
1 parent 3959b68d91
commit 42aaef8bdb
3 files changed
+46 -8

No files matched your search

+2 -2
View File
@@ -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
+2
View File
@@ -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));
@@ -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<Record<string, unknown>>;
/** Positional arguments between the serial and the checked field. */
readonly between?: Readonly<Record<string, string>>;
readonly options?: readonly string[];
}
cliTest.for<AdbSelectorCase>([
{
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<Ajv2020["compile"]>[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({