* 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.
75 lines
2.7 KiB
TypeScript
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;
|
|
});
|
|
};
|