mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 07:16:13 +00:00
* fix(cli): align ADB directory and package query schemas * fix(cli): preserve ADB selector correction details
109 lines
3.9 KiB
TypeScript
109 lines
3.9 KiB
TypeScript
import { Errors } from "incur";
|
|
import { z } from "zod";
|
|
|
|
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
|
import { encodeMarkdownTableCell } from "./cliOutputFormatter.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 === "list-adb-directory" && field === "device-path") ||
|
|
(command === "resolve-adb-packages" && field === "query") ||
|
|
(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 ?? []),
|
|
];
|
|
});
|
|
};
|
|
|
|
/** Encode only at the final Markdown presentation boundary, after redaction. */
|
|
export const encodeCliArgumentErrors = (cli: CliInstance): void => {
|
|
visitRegisteredCommands(cli, (command) => {
|
|
command.middleware = [
|
|
async (context, next) => {
|
|
try {
|
|
await next();
|
|
} catch (error: unknown) {
|
|
if (
|
|
context.format === "md" &&
|
|
error instanceof Errors.ValidationError
|
|
) {
|
|
error.fieldErrors = error.fieldErrors.map((field) => ({
|
|
...field,
|
|
...(field.code === undefined
|
|
? {}
|
|
: { code: encodeMarkdownTableCell(field.code) }),
|
|
path: encodeMarkdownTableCell(field.path),
|
|
expected: encodeMarkdownTableCell(field.expected),
|
|
received: encodeMarkdownTableCell(field.received),
|
|
message: encodeMarkdownTableCell(field.message),
|
|
}));
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
...(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;
|
|
});
|
|
};
|