Files
rea/src/cli/argumentErrors.ts
T
Wenyu Chiou 3142f7b0dd fix(adb): align CLI selector schemas with canonical inputs (#7722)
* fix(adb): align CLI selector schemas with canonical inputs

Reuse canonical selector constraints for ADB CLI admission and cover schema, parser, and inert provider boundaries. Independent source review approved tree bc722f8d1b1f8d014be29533e6845c6f897d5918; full contribution checks remain pending.

* test(adb): expect CLI serial validation refusal

Independent review approved staged diff 35418e6994339af838b490be51278fd41b9f8e3e. Focused serial refusal test, lint, and formatting passed with existing dependencies.

* test(adb): assert CLI selector validation stage

Independent review approved staged diff a1164a8dcf1df1f0d60323d7a53a69ee2bfdffeb. Focused dispatch tests, targeted lint and formatting passed using existing dependencies.

* fix(adb): retain safe selector validation constraints

Independent review: review_ci_delta APPROVE for diff 18748a3b4b112dc96b95f7c1099bd479e1d1587b. Focused tests, typecheck and targeted lint passed; the aggregate check remains limited by a matching baseline Knip finding.
2026-10-29 07:44:43 +08:00

75 lines
2.7 KiB
TypeScript

import { Errors } from "incur";
import { z } from "zod";
import { projectInputIssues } from "../domain/inputIssueProjection.js";
import type { CliInstance } from "./types.js";
import { visitRegisteredCommands } from "./visitRegisteredCommands.js";
const ARGUMENT_GUIDANCE =
"REA could not read the command arguments. Check the named argument and its expected type, then try again.";
const adbPackageCommands = new Set([
"pull-adb-package",
"inspect-adb-package",
"uninstall-adb-package",
"start-adb-app",
"stop-adb-app",
]);
const isAdbSelector = (command: string, field: string): boolean =>
(command.includes("-adb-") && field === "serial") ||
(adbPackageCommands.has(command) && field === "package") ||
(command === "start-adb-activity" &&
["action", "component", "data-uri"].includes(field));
/** Preserve parser correction fields without embedding its serialized cause. */
export const presentCliArgumentErrors = (cli: CliInstance): void => {
visitRegisteredCommands(cli, (command, path) => {
command.middleware = [
async (_context, next) => {
try {
await next();
} catch (cause: unknown) {
if (!(cause instanceof Errors.ValidationError)) throw cause;
const failure = new Errors.ValidationError({
message: ARGUMENT_GUIDANCE,
fieldErrors: adbSelectorConstraints(path, cause),
});
// Incur's constructor appends cause.message as display details. Keep
// the original error separately, without copying it into the message.
Object.defineProperty(failure, "cause", { value: cause });
throw failure;
}
},
...(command.middleware ?? []),
];
});
};
/** Retain safe selector constraints dropped by Incur's parser projection. */
const adbSelectorConstraints = (
command: string,
error: Errors.ValidationError,
): Errors.FieldError[] => {
if (!command.includes("-adb-") || !(error.cause instanceof z.ZodError))
return error.fieldErrors;
return error.fieldErrors.map((field, index) => {
const issue =
error.cause instanceof z.ZodError ? error.cause.issues[index] : undefined;
if (
issue === undefined ||
issue.path.length !== 1 ||
issue.path[0] !== field.path ||
issue.code !== field.code ||
!isAdbSelector(command, field.path) ||
(issue.code !== "too_big" && issue.code !== "invalid_format")
)
return field;
const projected = projectInputIssues([issue], undefined)[0];
if (projected?.maximum !== undefined)
return { ...field, maximum: projected.maximum };
if (projected?.expected === "regex") return { ...field, expected: "regex" };
return field;
});
};