@evantahler/mcpx 0.18.2 → 0.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +63 -62
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +15 -15
- package/src/client/debug-fetch.ts +53 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +99 -102
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +790 -814
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +52 -58
package/src/update/background.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cyan, dim, yellow } from "ansis";
|
|
2
2
|
import pkg from "../../package.json";
|
|
3
|
-
import {
|
|
4
|
-
import { checkForUpdate, needsCheck, type UpdateCache } from "./checker.ts";
|
|
3
|
+
import { DEFAULTS, ENV } from "../constants.ts";
|
|
5
4
|
import { loadUpdateCache, saveUpdateCache } from "./cache.ts";
|
|
5
|
+
import { checkForUpdate, needsCheck, type UpdateCache } from "./checker.ts";
|
|
6
6
|
|
|
7
7
|
/** Format an update notice for stderr output. */
|
|
8
8
|
function formatNotice(currentVersion: string, latestVersion: string, changelog?: string): string {
|
|
9
|
-
|
|
9
|
+
const lines: string[] = ["", yellow(`Update available: ${currentVersion} → ${latestVersion}`)];
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if (changelog) {
|
|
12
|
+
lines.push("");
|
|
13
|
+
lines.push(dim(changelog));
|
|
14
|
+
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push(cyan(`Run \`mcpx upgrade\` to update`));
|
|
18
|
+
lines.push("");
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
return lines.join("\n");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -25,52 +25,52 @@ function formatNotice(currentVersion: string, latestVersion: string, changelog?:
|
|
|
25
25
|
* if an update is available, or null otherwise. Never throws.
|
|
26
26
|
*/
|
|
27
27
|
export async function maybeCheckForUpdate(): Promise<string | null> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
try {
|
|
29
|
+
// Opt-out via env var
|
|
30
|
+
if (process.env[ENV.NO_UPDATE_CHECK] === "1") return null;
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// Skip if this is the check-update or upgrade command
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const command = args.find((a) => !a.startsWith("-"));
|
|
35
|
+
if (command === "check-update" || command === "upgrade") return null;
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Only show in TTY
|
|
38
|
+
if (!(process.stderr.isTTY ?? false)) return null;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const cache = await loadUpdateCache();
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
if (!needsCheck(cache)) {
|
|
43
|
+
// Cache is fresh — use cached result
|
|
44
|
+
if (cache?.hasUpdate) {
|
|
45
|
+
return formatNotice(pkg.version, cache.latestVersion, cache.changelog);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Cache is stale or missing — check with timeout
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), DEFAULTS.UPDATE_CHECK_TIMEOUT_MS);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
try {
|
|
55
|
+
const info = await checkForUpdate(pkg.version, controller.signal);
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const newCache: UpdateCache = {
|
|
58
|
+
lastCheckAt: new Date().toISOString(),
|
|
59
|
+
latestVersion: info.latestVersion,
|
|
60
|
+
hasUpdate: info.hasUpdate,
|
|
61
|
+
changelog: info.changelog,
|
|
62
|
+
};
|
|
63
|
+
await saveUpdateCache(newCache);
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
if (info.hasUpdate) {
|
|
66
|
+
return formatNotice(pkg.version, info.latestVersion, info.changelog);
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
return null;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
76
|
}
|
package/src/update/cache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "path";
|
|
1
|
+
import { join } from "node:path";
|
|
2
2
|
import { DEFAULT_CONFIG_DIR } from "../constants.ts";
|
|
3
3
|
import type { UpdateCache } from "./checker.ts";
|
|
4
4
|
|
|
@@ -6,32 +6,32 @@ const UPDATE_CACHE_PATH = join(DEFAULT_CONFIG_DIR, "update.json");
|
|
|
6
6
|
|
|
7
7
|
/** Load the cached update check result, if it exists. */
|
|
8
8
|
export async function loadUpdateCache(): Promise<UpdateCache | undefined> {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
try {
|
|
10
|
+
const file = Bun.file(UPDATE_CACHE_PATH);
|
|
11
|
+
if (!(await file.exists())) return undefined;
|
|
12
|
+
return JSON.parse(await file.text()) as UpdateCache;
|
|
13
|
+
} catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** Save update check result to the cache file. */
|
|
19
19
|
export async function saveUpdateCache(cache: UpdateCache): Promise<void> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
try {
|
|
21
|
+
await Bun.write(UPDATE_CACHE_PATH, `${JSON.stringify(cache, null, 2)}\n`);
|
|
22
|
+
} catch {
|
|
23
|
+
// Ignore write failures (e.g. permissions)
|
|
24
|
+
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Remove the cached update check result. */
|
|
28
28
|
export async function clearUpdateCache(): Promise<void> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
try {
|
|
30
|
+
const file = Bun.file(UPDATE_CACHE_PATH);
|
|
31
|
+
if (await file.exists()) {
|
|
32
|
+
await Bun.write(UPDATE_CACHE_PATH, "");
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Ignore
|
|
36
|
+
}
|
|
37
37
|
}
|
package/src/update/checker.ts
CHANGED
|
@@ -2,121 +2,116 @@ import pkg from "../../package.json";
|
|
|
2
2
|
import { DEFAULTS } from "../constants.ts";
|
|
3
3
|
|
|
4
4
|
const NPM_REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
5
|
-
const GITHUB_REPO = pkg.repository.url
|
|
6
|
-
.replace(/^https:\/\/github\.com\//, "")
|
|
7
|
-
.replace(/\.git$/, "");
|
|
5
|
+
const GITHUB_REPO = pkg.repository.url.replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
8
6
|
|
|
9
7
|
export interface UpdateInfo {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
currentVersion: string;
|
|
9
|
+
latestVersion: string;
|
|
10
|
+
hasUpdate: boolean;
|
|
11
|
+
aheadOfLatest: boolean;
|
|
12
|
+
changelog?: string;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
export interface UpdateCache {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
lastCheckAt: string;
|
|
17
|
+
latestVersion: string;
|
|
18
|
+
hasUpdate: boolean;
|
|
19
|
+
changelog?: string;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export type InstallMethod = "npm" | "bun" | "binary" | "local-dev";
|
|
25
23
|
|
|
26
24
|
/** Compare two semver strings. Returns true if latest > current. */
|
|
27
25
|
export function isNewerVersion(current: string, latest: string): boolean {
|
|
28
|
-
|
|
26
|
+
return Bun.semver.order(current, latest) === -1;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
/** Fetch the latest version from the npm registry. */
|
|
32
30
|
export async function fetchLatestVersion(signal?: AbortSignal): Promise<string> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(NPM_REGISTRY_URL, { signal });
|
|
33
|
+
if (!res.ok) return pkg.version;
|
|
34
|
+
const data = (await res.json()) as { version: string };
|
|
35
|
+
return data.version;
|
|
36
|
+
} catch {
|
|
37
|
+
return pkg.version;
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
/** Fetch changelog from GitHub releases between two versions. */
|
|
44
42
|
export async function fetchChangelog(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
fromVersion: string,
|
|
44
|
+
toVersion: string,
|
|
45
|
+
signal?: AbortSignal,
|
|
48
46
|
): Promise<string | undefined> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=20`, {
|
|
49
|
+
signal,
|
|
50
|
+
headers: { Accept: "application/vnd.github.v3+json" },
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok) return undefined;
|
|
53
|
+
|
|
54
|
+
const releases = (await res.json()) as Array<{
|
|
55
|
+
tag_name: string;
|
|
56
|
+
body: string | null;
|
|
57
|
+
}>;
|
|
58
|
+
|
|
59
|
+
const relevant = releases.filter((r) => {
|
|
60
|
+
const v = r.tag_name.replace(/^v/, "");
|
|
61
|
+
return isNewerVersion(fromVersion, v) && !isNewerVersion(toVersion, v);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (relevant.length === 0) return undefined;
|
|
65
|
+
|
|
66
|
+
return relevant
|
|
67
|
+
.map((r) => `## ${r.tag_name}\n${r.body ?? ""}`)
|
|
68
|
+
.join("\n\n")
|
|
69
|
+
.trim();
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
/** Check npm for a newer version and fetch changelog if available. */
|
|
78
|
-
export async function checkForUpdate(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { currentVersion, latestVersion, hasUpdate, aheadOfLatest, changelog };
|
|
76
|
+
export async function checkForUpdate(currentVersion: string, signal?: AbortSignal): Promise<UpdateInfo> {
|
|
77
|
+
const latestVersion = await fetchLatestVersion(signal);
|
|
78
|
+
const hasUpdate = isNewerVersion(currentVersion, latestVersion);
|
|
79
|
+
const aheadOfLatest = isNewerVersion(latestVersion, currentVersion);
|
|
80
|
+
|
|
81
|
+
let changelog: string | undefined;
|
|
82
|
+
if (hasUpdate) {
|
|
83
|
+
changelog = await fetchChangelog(currentVersion, latestVersion, signal);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { currentVersion, latestVersion, hasUpdate, aheadOfLatest, changelog };
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
/** Returns true if the cache is missing or older than 24 hours. */
|
|
95
90
|
export function needsCheck(cache?: UpdateCache): boolean {
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
if (!cache?.lastCheckAt) return true;
|
|
92
|
+
return Date.now() - new Date(cache.lastCheckAt).getTime() > DEFAULTS.UPDATE_CHECK_INTERVAL_MS;
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
/** Detect how mcpx was installed. */
|
|
101
96
|
export function detectInstallMethod(): InstallMethod {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
97
|
+
const script = process.argv[1] ?? "";
|
|
98
|
+
const execPath = process.execPath;
|
|
99
|
+
|
|
100
|
+
// Local dev: running src/cli.ts directly outside node_modules
|
|
101
|
+
if (script.includes("src/cli.ts") && !script.includes("node_modules")) {
|
|
102
|
+
return "local-dev";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Compiled binary: execPath is the binary itself (not bun/node)
|
|
106
|
+
if (!execPath.includes("bun") && !execPath.includes("node")) {
|
|
107
|
+
return "binary";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Bun global install: path contains .bun/install
|
|
111
|
+
if (script.includes(".bun/install") || script.includes(".bun/bin")) {
|
|
112
|
+
return "bun";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// npm global install: fallback for node_modules paths
|
|
116
|
+
return "npm";
|
|
122
117
|
}
|
package/src/validation/schema.ts
CHANGED
|
@@ -7,85 +7,79 @@ const ajv = new Ajv({ allErrors: true, strict: false });
|
|
|
7
7
|
const validatorCache = new Map<string, ReturnType<typeof ajv.compile>>();
|
|
8
8
|
|
|
9
9
|
export interface ValidationError {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
path: string;
|
|
11
|
+
message: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface ValidationResult {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
valid: boolean;
|
|
16
|
+
errors: ValidationError[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Compile (or retrieve from cache), validate, and return result */
|
|
20
20
|
function validateWithSchema(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
cacheKey: string,
|
|
22
|
+
schema: Record<string, unknown>,
|
|
23
|
+
input: Record<string, unknown>,
|
|
24
24
|
): ValidationResult {
|
|
25
|
-
|
|
25
|
+
let validate = validatorCache.get(cacheKey);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
if (!validate) {
|
|
28
|
+
try {
|
|
29
|
+
validate = ajv.compile(schema);
|
|
30
|
+
validatorCache.set(cacheKey, validate);
|
|
31
|
+
} catch {
|
|
32
|
+
return { valid: true, errors: [] };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const valid = validate(input);
|
|
37
|
+
if (valid) {
|
|
38
|
+
return { valid: true, errors: [] };
|
|
39
|
+
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const errors = (validate.errors ?? []).map(formatAjvError);
|
|
42
|
+
return { valid: false, errors };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** Validate tool arguments against the tool's inputSchema */
|
|
46
|
-
export function validateToolInput(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!schema || Object.keys(schema).length === 0) {
|
|
53
|
-
return { valid: true, errors: [] };
|
|
54
|
-
}
|
|
55
|
-
return validateWithSchema(`${serverName}/${tool.name}`, schema, input);
|
|
46
|
+
export function validateToolInput(serverName: string, tool: Tool, input: Record<string, unknown>): ValidationResult {
|
|
47
|
+
const schema = tool.inputSchema;
|
|
48
|
+
if (!schema || Object.keys(schema).length === 0) {
|
|
49
|
+
return { valid: true, errors: [] };
|
|
50
|
+
}
|
|
51
|
+
return validateWithSchema(`${serverName}/${tool.name}`, schema, input);
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
/** Validate user-collected form data against an elicitation requestedSchema */
|
|
59
55
|
export function validateElicitationResponse(
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
schema: Record<string, unknown>,
|
|
57
|
+
input: Record<string, unknown>,
|
|
62
58
|
): ValidationResult {
|
|
63
|
-
|
|
59
|
+
return validateWithSchema(`__elicitation__${JSON.stringify(schema)}`, schema, input);
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
function formatAjvError(err: ErrorObject): ValidationError {
|
|
67
|
-
|
|
68
|
-
? err.instancePath.replace(/^\//, "").replace(/\//g, ".")
|
|
69
|
-
: "(root)";
|
|
63
|
+
const path = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : "(root)";
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
switch (err.keyword) {
|
|
66
|
+
case "required": {
|
|
67
|
+
const field = (err.params as { missingProperty: string }).missingProperty;
|
|
68
|
+
return { path: field, message: `missing required field "${field}"` };
|
|
69
|
+
}
|
|
70
|
+
case "type": {
|
|
71
|
+
const expected = (err.params as { type: string }).type;
|
|
72
|
+
return { path, message: `must be ${expected}` };
|
|
73
|
+
}
|
|
74
|
+
case "enum": {
|
|
75
|
+
const allowed = (err.params as { allowedValues: unknown[] }).allowedValues;
|
|
76
|
+
return { path, message: `must be one of: ${allowed.join(", ")}` };
|
|
77
|
+
}
|
|
78
|
+
case "additionalProperties": {
|
|
79
|
+
const extra = (err.params as { additionalProperty: string }).additionalProperty;
|
|
80
|
+
return { path: extra, message: `unknown property "${extra}"` };
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return { path, message: err.message ?? "validation failed" };
|
|
84
|
+
}
|
|
91
85
|
}
|