argsbarg 6.1.6 → 6.1.8
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/CHANGELOG.md +15 -1
- package/docs/README.md +1 -1
- package/docs/cli-program.md +3 -3
- package/docs/config-schema.md +12 -9
- package/docs/decisions.md +1 -1
- package/docs/json-schema-subset.md +38 -12
- package/docs/output-schema.md +1 -1
- package/package.json +3 -2
- package/src/builtins/builtins.test.ts +4 -4
- package/src/builtins/completion-bash.ts +1 -1
- package/src/builtins/completion-fish.ts +1 -1
- package/src/builtins/completion-group.ts +3 -3
- package/src/builtins/completion-zsh.ts +1 -1
- package/src/builtins/config.test.ts +3 -3
- package/src/builtins/config.ts +9 -9
- package/src/builtins/configure-copy.ts +2 -2
- package/src/builtins/configure.ts +3 -3
- package/src/builtins/dispatch.ts +10 -10
- package/src/builtins/export.ts +4 -4
- package/src/builtins/http.ts +5 -5
- package/src/builtins/mcp.ts +3 -3
- package/src/builtins/presentation.ts +5 -5
- package/src/builtins/registry.ts +3 -3
- package/src/builtins/scopes.ts +2 -2
- package/src/builtins/version.ts +1 -1
- package/src/cli-tool/full-example-capabilities.test.ts +2 -2
- package/src/cli-tool/main.ts +1 -1
- package/src/cli-tool/program.ts +1 -1
- package/src/cli-tool/prompt.ts +1 -1
- package/src/config/bindings.test.ts +1 -1
- package/src/config/bindings.ts +1 -1
- package/src/config/bootstrap.test.ts +1 -1
- package/src/config/bootstrap.ts +3 -3
- package/src/config/context.test.ts +1 -1
- package/src/config/context.ts +1 -1
- package/src/config/entry.ts +1 -1
- package/src/config/file.test.ts +1 -1
- package/src/config/file.ts +3 -3
- package/src/config/manifest.ts +1 -1
- package/src/config/resolve.test.ts +1 -1
- package/src/config/resolve.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/config/validate.test.ts +45 -1
- package/src/config/validate.ts +138 -203
- package/src/configure/artifacts/binary-placement.test.ts +1 -1
- package/src/configure/artifacts/binary-placement.ts +1 -1
- package/src/configure/artifacts/gh-release-update.ts +1 -1
- package/src/configure/artifacts/install-validate.test.ts +3 -3
- package/src/configure/artifacts/mcp-config.ts +1 -1
- package/src/configure/artifacts/mcp-opencode.test.ts +1 -1
- package/src/configure/artifacts/mcp-opencode.ts +1 -1
- package/src/configure/artifacts/paths.ts +5 -5
- package/src/configure/artifacts/plan.ts +1 -1
- package/src/configure/artifacts/status.test.ts +1 -1
- package/src/configure/artifacts/status.ts +2 -2
- package/src/configure/artifacts/target-base.ts +1 -1
- package/src/configure/artifacts/target-detect.ts +1 -1
- package/src/configure/artifacts/target-effective.ts +3 -3
- package/src/configure/artifacts/target-mcp-cli.ts +1 -1
- package/src/configure/artifacts/target-mcp-json.ts +1 -1
- package/src/configure/artifacts/target-plan-build.ts +2 -2
- package/src/configure/artifacts/target-scope.ts +3 -3
- package/src/configure/artifacts/target-skill.ts +1 -1
- package/src/configure/artifacts/target-types.ts +2 -2
- package/src/configure/artifacts/targets/app.ts +5 -5
- package/src/configure/artifacts/targets/chatgpt-mcp.ts +2 -2
- package/src/configure/artifacts/targets/claude-code-mcp.ts +2 -2
- package/src/configure/artifacts/targets/claude-desktop-mcp.ts +2 -2
- package/src/configure/artifacts/targets/claude-skill.ts +2 -2
- package/src/configure/artifacts/targets/codex-mcp.ts +2 -2
- package/src/configure/artifacts/targets/codex-skill.ts +2 -2
- package/src/configure/artifacts/targets/configure.ts +5 -5
- package/src/configure/artifacts/targets/cursor-mcp.ts +2 -2
- package/src/configure/artifacts/targets/cursor-skill.ts +2 -2
- package/src/configure/artifacts/targets/index.ts +1 -1
- package/src/configure/artifacts/targets/openclaw-mcp.ts +2 -2
- package/src/configure/artifacts/targets/openclaw-skill.ts +3 -3
- package/src/configure/artifacts/targets/opencode-mcp.ts +5 -5
- package/src/configure/artifacts/targets/opencode-skill.ts +3 -3
- package/src/configure/artifacts/targets.test.ts +1 -1
- package/src/configure/artifacts/uninstall.ts +1 -1
- package/src/configure/configure.test.ts +9 -9
- package/src/configure/index.ts +5 -5
- package/src/configure/prompt.ts +1 -1
- package/src/core/context.ts +3 -3
- package/src/core/json-leaf.test.ts +1 -1
- package/src/core/leaf-inputs.test.ts +1 -1
- package/src/core/leaf-inputs.ts +2 -2
- package/src/core/parse.test.ts +9 -9
- package/src/core/parse.ts +2 -2
- package/src/core/schema.ts +3 -3
- package/src/core/types.ts +1 -1
- package/src/core/validate.ts +6 -6
- package/src/docs/builtin.ts +3 -3
- package/src/docs/cli-guide.test.ts +3 -3
- package/src/docs/cli-guide.ts +5 -5
- package/src/docs/docs.test.ts +8 -8
- package/src/docs/http-guide.ts +7 -7
- package/src/docs/mcp-guide.ts +7 -7
- package/src/docs/mcp-resources.test.ts +1 -1
- package/src/docs/mcp-resources.ts +1 -1
- package/src/docs/resolve.ts +4 -4
- package/src/docs/save.ts +2 -2
- package/src/exports/cli.ts +9 -9
- package/src/exports/headless.ts +2 -2
- package/src/exports/http.ts +2 -2
- package/src/exports/mcp.ts +2 -2
- package/src/headless/routing.ts +2 -2
- package/src/headless/tool-call.ts +11 -11
- package/src/hooks/run.ts +6 -6
- package/src/http/openapi.ts +3 -3
- package/src/http/paths.ts +1 -1
- package/src/http/readiness.ts +3 -3
- package/src/http/result.ts +1 -1
- package/src/http/routes.ts +4 -4
- package/src/http/server.ts +6 -6
- package/src/log/emitter.ts +1 -1
- package/src/mcp/bundle.ts +2 -2
- package/src/mcp/claude.test.ts +1 -1
- package/src/mcp/claude.ts +4 -4
- package/src/mcp/hidden-mcpb.test.ts +5 -5
- package/src/mcp/result.ts +2 -2
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +7 -7
- package/src/runtime/capabilities.ts +2 -2
- package/src/runtime/cli-errors.ts +3 -3
- package/src/runtime/cli.ts +22 -22
- package/src/runtime/exposure.ts +2 -2
- package/src/runtime/invoke.test.ts +6 -6
- package/src/server/context.ts +3 -3
- package/src/server/overrides.ts +3 -3
- package/src/skill/generate.ts +5 -5
- package/src/skill/hint.ts +1 -1
- package/src/skill/install.ts +2 -2
- package/src/skill/naming.ts +1 -1
- package/src/test/fixtures.ts +2 -2
- package/src/test/integration/config.test.ts +3 -3
- package/src/test/integration/http.test.ts +10 -10
- package/src/test/integration/mcp.test.ts +6 -6
package/src/config/file.test.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
-
import type { CliProgram } from "
|
|
9
|
+
import type { CliProgram } from "../core/types.ts";
|
|
10
10
|
import { CONFIG_BINDINGS_KEY } from "./bindings.ts";
|
|
11
11
|
import { bootstrapAppConfig } from "./bootstrap.ts";
|
|
12
12
|
import {
|
package/src/config/file.ts
CHANGED
|
@@ -4,9 +4,9 @@ JSON app config file path helpers and strict read/write.
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
-
import type { CliProgram } from "
|
|
8
|
-
import { sanitizeToolSegment } from "
|
|
9
|
-
import { appConfigLibHome, displayHomePath } from "
|
|
7
|
+
import type { CliProgram } from "../core/types.ts";
|
|
8
|
+
import { sanitizeToolSegment } from "../mcp/tools.ts";
|
|
9
|
+
import { appConfigLibHome, displayHomePath } from "../paths/host.ts";
|
|
10
10
|
import { isFrameworkConfigKey, validateBindingsShape } from "./bindings.ts";
|
|
11
11
|
import { effectiveJsonSchema } from "./schema.ts";
|
|
12
12
|
import { validateConfigDocument, validateConfigDocumentPartial } from "./validate.ts";
|
package/src/config/manifest.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
MCP bundle / plugin manifest builders for program.appConfig env-mapped entries.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { CliAppConfigEntry, CliProgram } from "
|
|
5
|
+
import type { CliAppConfigEntry, CliProgram } from "../core/types.ts";
|
|
6
6
|
import {
|
|
7
7
|
configEntryRequired,
|
|
8
8
|
configEntrySensitive,
|
|
@@ -3,7 +3,7 @@ Tests for config/resolve module behavior.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import type { CliProgram } from "
|
|
6
|
+
import type { CliProgram } from "../core/types.ts";
|
|
7
7
|
import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "./resolve.ts";
|
|
8
8
|
|
|
9
9
|
const program: CliProgram = {
|
package/src/config/resolve.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Resolve program.appConfig values: defaults, file, env override, export to process.env.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { CliAppConfigEntry, CliAppConfigResolveContext, CliProgram } from "
|
|
5
|
+
import type { CliAppConfigEntry, CliAppConfigResolveContext, CliProgram } from "../core/types.ts";
|
|
6
6
|
import { configEntryRequired, jsonSchemaRequiredKeys } from "./entry.ts";
|
|
7
7
|
import type { AppConfigFileData } from "./file.ts";
|
|
8
8
|
import { displayAppConfigPath } from "./file.ts";
|
package/src/config/schema.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Effective JSON Schema for program.appConfig (block schema or all-string synthesis).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { CliAppConfigEntry, CliProgram } from "
|
|
5
|
+
import type { CliAppConfigEntry, CliProgram } from "../core/types.ts";
|
|
6
6
|
import { configEntryRequired, jsonSchemaRequiredKeys } from "./entry.ts";
|
|
7
7
|
|
|
8
8
|
/** Synthesize a draft-07 object schema with string properties from metadata entries. */
|
|
@@ -3,7 +3,7 @@ Tests for config/validate module behavior.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import { parseConfigSetValue, validateConfigDocument } from "./validate.ts";
|
|
6
|
+
import { parseConfigSetValue, resolveSchemaDraft, validateConfigDocument } from "./validate.ts";
|
|
7
7
|
|
|
8
8
|
const rootSchema = {
|
|
9
9
|
type: "object",
|
|
@@ -98,4 +98,48 @@ describe("config/validate", () => {
|
|
|
98
98
|
expect(() => parseConfigSetValue("a,b", schema, rootSchema, false)).toThrow(/--json/);
|
|
99
99
|
expect(parseConfigSetValue('[{"ttl":1}]', schema, rootSchema, false)).toEqual([{ ttl: 1 }]);
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
test("resolveSchemaDraft maps $schema URIs", () => {
|
|
103
|
+
expect(resolveSchemaDraft({ $schema: "http://json-schema.org/draft-07/schema#" })).toBe("7");
|
|
104
|
+
expect(resolveSchemaDraft({ $schema: "https://json-schema.org/draft/2020-12/schema" })).toBe("2020-12");
|
|
105
|
+
expect(resolveSchemaDraft({ $schema: "https://json-schema.org/draft/2019-09/schema" })).toBe("2019-09");
|
|
106
|
+
expect(resolveSchemaDraft({})).toBe("7");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("validates draft 2020-12 schemas when $schema is set", () => {
|
|
110
|
+
const schema = {
|
|
111
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
112
|
+
type: "object",
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
required: ["name"],
|
|
115
|
+
properties: {
|
|
116
|
+
name: { type: "string", minLength: 1 },
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
expect(validateConfigDocument({ name: "ok" }, schema).valid).toBe(true);
|
|
120
|
+
expect(validateConfigDocument({ name: "" }, schema).valid).toBe(false);
|
|
121
|
+
expect(validateConfigDocument({}, schema).valid).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("validates draft 2020-12 $defs refs", () => {
|
|
125
|
+
const schema = {
|
|
126
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
127
|
+
type: "object",
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
required: ["item"],
|
|
130
|
+
properties: {
|
|
131
|
+
item: { $ref: "#/$defs/Item" },
|
|
132
|
+
},
|
|
133
|
+
$defs: {
|
|
134
|
+
Item: {
|
|
135
|
+
type: "object",
|
|
136
|
+
additionalProperties: false,
|
|
137
|
+
required: ["id"],
|
|
138
|
+
properties: { id: { type: "string" } },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
expect(validateConfigDocument({ item: { id: "a" } }, schema).valid).toBe(true);
|
|
143
|
+
expect(validateConfigDocument({ item: { id: 1 } }, schema).valid).toBe(false);
|
|
144
|
+
});
|
|
101
145
|
});
|
package/src/config/validate.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
JSON Schema validation for program.appConfig and leaf inputSchema (@cfworker/json-schema).
|
|
3
|
+
CLI value coercion for configure set remains here (comma-separated arrays, booleans, etc.).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { format as jsonSchemaFormats, type Schema, type SchemaDraft, Validator } from "@cfworker/json-schema";
|
|
7
|
+
import { parseCommaList, parseDate, parseDateTime, validateCommaList } from "../core/formats.ts";
|
|
7
8
|
import { isFrameworkConfigKey } from "./bindings.ts";
|
|
8
9
|
|
|
9
10
|
type JsonSchema = Record<string, unknown>;
|
|
@@ -19,248 +20,183 @@ export interface ValidateResult {
|
|
|
19
20
|
errors: string[];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Validate present keys only — skips root `required` checks (partial writes / bootstrap). */
|
|
30
|
-
export function validateConfigDocumentPartial(data: unknown, rootSchema: JsonSchema): ValidateResult {
|
|
31
|
-
const errors: string[] = [];
|
|
32
|
-
validateValue(data, rootSchema, rootSchema, "$", errors, true);
|
|
33
|
-
return { valid: errors.length === 0, errors };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function validateValue(
|
|
37
|
-
value: unknown,
|
|
38
|
-
schema: JsonSchema,
|
|
39
|
-
root: JsonSchema,
|
|
40
|
-
path: string,
|
|
41
|
-
errors: string[],
|
|
42
|
-
partial: boolean,
|
|
43
|
-
): void {
|
|
44
|
-
const resolved = resolveSchema(schema, root);
|
|
45
|
-
if (!resolved) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if ("const" in resolved) {
|
|
50
|
-
if (value !== resolved.const) {
|
|
51
|
-
errors.push(`${path}: must be ${JSON.stringify(resolved.const)}`);
|
|
23
|
+
if (!jsonSchemaFormats["comma-list"]) {
|
|
24
|
+
jsonSchemaFormats["comma-list"] = (value: string) => {
|
|
25
|
+
try {
|
|
26
|
+
validateCommaList(value);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
52
30
|
}
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
};
|
|
32
|
+
}
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return;
|
|
34
|
+
function dataForSchemaValidation(data: unknown): unknown {
|
|
35
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
36
|
+
return data;
|
|
61
37
|
}
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (const branch of anyOf) {
|
|
67
|
-
if (typeof branch !== "object" || branch === null || Array.isArray(branch)) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const branchErrs: string[] = [];
|
|
71
|
-
validateValue(value, branch as JsonSchema, root, path, branchErrs, partial);
|
|
72
|
-
if (branchErrs.length === 0) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
branchErrors.push(branchErrs);
|
|
38
|
+
const out: Record<string, unknown> = {};
|
|
39
|
+
for (const [key, value] of Object.entries(data)) {
|
|
40
|
+
if (isFrameworkConfigKey(key)) {
|
|
41
|
+
continue;
|
|
76
42
|
}
|
|
77
|
-
|
|
78
|
-
if (branchErrors.length === 1 && branchErrors[0]?.[0]) {
|
|
79
|
-
errors.push(branchErrors[0][0]);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const types = normalizeTypes(resolved.type);
|
|
85
|
-
if (types.length > 0 && !matchesAnyType(value, types)) {
|
|
86
|
-
errors.push(`${path}: must be ${types.join(" or ")}`);
|
|
87
|
-
return;
|
|
43
|
+
out[key] = value;
|
|
88
44
|
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
89
47
|
|
|
90
|
-
|
|
91
|
-
|
|
48
|
+
function schemaWithoutRequired(schema: unknown): JsonSchema {
|
|
49
|
+
if (typeof schema !== "object" || schema === null) {
|
|
50
|
+
return schema as JsonSchema;
|
|
92
51
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
52
|
+
if (Array.isArray(schema)) {
|
|
53
|
+
return schema.map(schemaWithoutRequired) as unknown as JsonSchema;
|
|
95
54
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
55
|
+
const out: Record<string, unknown> = {};
|
|
56
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
57
|
+
if (key === "required") {
|
|
58
|
+
continue;
|
|
99
59
|
}
|
|
60
|
+
out[key] = schemaWithoutRequired(value);
|
|
100
61
|
}
|
|
101
|
-
|
|
102
|
-
errors.push(`${path}: must be null`);
|
|
103
|
-
}
|
|
104
|
-
if (types.includes("object") || (types.length === 0 && isPlainObject(value))) {
|
|
105
|
-
validateObject(value, resolved, root, path, errors, partial);
|
|
106
|
-
}
|
|
107
|
-
if (types.includes("array") || (types.length === 0 && Array.isArray(value))) {
|
|
108
|
-
validateArray(value, resolved, root, path, errors, partial);
|
|
109
|
-
}
|
|
62
|
+
return out as JsonSchema;
|
|
110
63
|
}
|
|
111
64
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const name = decodeURIComponent(ref.slice("#/definitions/".length));
|
|
116
|
-
const definitions = root.definitions;
|
|
117
|
-
if (typeof definitions !== "object" || definitions === null || Array.isArray(definitions)) {
|
|
118
|
-
return schema;
|
|
119
|
-
}
|
|
120
|
-
const target = (definitions as Record<string, unknown>)[name];
|
|
121
|
-
if (typeof target === "object" && target !== null && !Array.isArray(target)) {
|
|
122
|
-
return target as JsonSchema;
|
|
123
|
-
}
|
|
65
|
+
function formatInstancePath(instanceLocation: string): string {
|
|
66
|
+
if (instanceLocation.length === 0 || instanceLocation === "#") {
|
|
67
|
+
return "$";
|
|
124
68
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
function normalizeTypes(type: unknown): string[] {
|
|
129
|
-
if (typeof type === "string") {
|
|
130
|
-
return [type];
|
|
69
|
+
if (instanceLocation.startsWith("#/")) {
|
|
70
|
+
return instanceLocation.slice(2).replace(/\//g, ".");
|
|
131
71
|
}
|
|
132
|
-
|
|
133
|
-
return type.filter((t): t is string => typeof t === "string");
|
|
134
|
-
}
|
|
135
|
-
return [];
|
|
72
|
+
return instanceLocation;
|
|
136
73
|
}
|
|
137
74
|
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (t === "integer" && typeof value === "number" && Number.isInteger(value)) return true;
|
|
144
|
-
if (t === "number" && typeof value === "number") return true;
|
|
145
|
-
if (t === "boolean" && typeof value === "boolean") return true;
|
|
146
|
-
if (t === "string" && typeof value === "string") return true;
|
|
147
|
-
}
|
|
148
|
-
return types.length === 0;
|
|
75
|
+
function formatValidationErrors(errors: { instanceLocation: string; error: string }[]): string[] {
|
|
76
|
+
return errors.map(({ instanceLocation, error }) => {
|
|
77
|
+
const path = formatInstancePath(instanceLocation);
|
|
78
|
+
return `${path}: ${error}`;
|
|
79
|
+
});
|
|
149
80
|
}
|
|
150
81
|
|
|
151
|
-
function
|
|
152
|
-
return
|
|
82
|
+
function decodeJsonPointerSegment(segment: string): string {
|
|
83
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
153
84
|
}
|
|
154
85
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
86
|
+
/** Map a schema `$schema` URI to the @cfworker/json-schema draft (defaults to Draft-07). */
|
|
87
|
+
export function resolveSchemaDraft(schema: JsonSchema): SchemaDraft {
|
|
88
|
+
const $schema = schema.$schema;
|
|
89
|
+
if (typeof $schema !== "string") {
|
|
90
|
+
return "7";
|
|
159
91
|
}
|
|
160
|
-
|
|
161
|
-
|
|
92
|
+
const normalized = $schema.toLowerCase();
|
|
93
|
+
if (normalized.includes("2020-12")) {
|
|
94
|
+
return "2020-12";
|
|
162
95
|
}
|
|
163
|
-
if (
|
|
164
|
-
|
|
96
|
+
if (normalized.includes("2019-09")) {
|
|
97
|
+
return "2019-09";
|
|
165
98
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
99
|
+
if (normalized.includes("draft-04") || normalized.includes("draft/4")) {
|
|
100
|
+
return "4";
|
|
101
|
+
}
|
|
102
|
+
if (normalized.includes("draft-07") || normalized.includes("draft/7")) {
|
|
103
|
+
return "7";
|
|
104
|
+
}
|
|
105
|
+
return "7";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveJsonPointer(root: JsonSchema, ref: string): unknown {
|
|
109
|
+
if (!ref.startsWith("#/")) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const segments = ref
|
|
113
|
+
.slice(2)
|
|
114
|
+
.split("/")
|
|
115
|
+
.filter((segment) => segment.length > 0)
|
|
116
|
+
.map(decodeJsonPointerSegment);
|
|
117
|
+
let current: unknown = root;
|
|
118
|
+
for (const segment of segments) {
|
|
119
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) {
|
|
120
|
+
return undefined;
|
|
173
121
|
}
|
|
122
|
+
current = (current as Record<string, unknown>)[segment];
|
|
174
123
|
}
|
|
124
|
+
return current;
|
|
175
125
|
}
|
|
176
126
|
|
|
177
|
-
function
|
|
178
|
-
if (
|
|
179
|
-
errors.push(`${path}: must be number`);
|
|
127
|
+
function attachRootCompanionSchemas(validator: Validator, root: JsonSchema, active: JsonSchema): void {
|
|
128
|
+
if (active === root) {
|
|
180
129
|
return;
|
|
181
130
|
}
|
|
182
|
-
|
|
183
|
-
|
|
131
|
+
const companion: Schema = {};
|
|
132
|
+
if (typeof root.definitions === "object" && root.definitions !== null && !Array.isArray(root.definitions)) {
|
|
133
|
+
companion.definitions = root.definitions;
|
|
184
134
|
}
|
|
185
|
-
if (typeof
|
|
186
|
-
|
|
135
|
+
if (typeof root.$defs === "object" && root.$defs !== null && !Array.isArray(root.$defs)) {
|
|
136
|
+
companion.$defs = root.$defs;
|
|
187
137
|
}
|
|
188
|
-
if (
|
|
189
|
-
|
|
138
|
+
if (Object.keys(companion).length > 0) {
|
|
139
|
+
validator.addSchema(companion);
|
|
190
140
|
}
|
|
191
141
|
}
|
|
192
142
|
|
|
193
|
-
function
|
|
194
|
-
|
|
143
|
+
function validatorForSchema(schema: JsonSchema, root: JsonSchema, partial: boolean): Validator {
|
|
144
|
+
const active = partial ? schemaWithoutRequired(schema) : schema;
|
|
145
|
+
const validator = new Validator(active as Schema, resolveSchemaDraft(root), false);
|
|
146
|
+
attachRootCompanionSchemas(validator, root, active);
|
|
147
|
+
return validator;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function validateInstance(
|
|
151
|
+
data: unknown,
|
|
195
152
|
schema: JsonSchema,
|
|
196
153
|
root: JsonSchema,
|
|
197
|
-
path: string,
|
|
198
|
-
errors: string[],
|
|
199
154
|
partial: boolean,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
155
|
+
stripFrameworkKeys: boolean,
|
|
156
|
+
): ValidateResult {
|
|
157
|
+
const validator = validatorForSchema(schema, root, partial);
|
|
158
|
+
const payload = stripFrameworkKeys ? dataForSchemaValidation(data) : data;
|
|
159
|
+
const result = validator.validate(payload);
|
|
160
|
+
if (result.valid) {
|
|
161
|
+
return { valid: true, errors: [] };
|
|
162
|
+
}
|
|
163
|
+
return { valid: false, errors: formatValidationErrors(result.errors) };
|
|
164
|
+
}
|
|
210
165
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
validateValue(value[key], propSchema, root, `${path}.${key}`, errors, partial);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
166
|
+
function validateAgainstSchema(data: unknown, rootSchema: JsonSchema, partial: boolean): ValidateResult {
|
|
167
|
+
return validateInstance(data, rootSchema, rootSchema, partial, true);
|
|
168
|
+
}
|
|
218
169
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (typeof key === "string" && !(key in value)) {
|
|
224
|
-
errors.push(`${path}: missing required property '${key}'`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
170
|
+
/** Validate `data` against a JSON Schema root. Returns human-readable error messages. */
|
|
171
|
+
export function validateConfigDocument(data: unknown, rootSchema: JsonSchema): ValidateResult {
|
|
172
|
+
return validateAgainstSchema(data, rootSchema, false);
|
|
173
|
+
}
|
|
229
174
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (!(key in propMap)) {
|
|
234
|
-
errors.push(`${path}: unknown property '${key}'`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
175
|
+
/** Validate present keys only — skips `required` checks (partial writes / bootstrap). */
|
|
176
|
+
export function validateConfigDocumentPartial(data: unknown, rootSchema: JsonSchema): ValidateResult {
|
|
177
|
+
return validateAgainstSchema(data, rootSchema, true);
|
|
238
178
|
}
|
|
239
179
|
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
path: string,
|
|
245
|
-
errors: string[],
|
|
246
|
-
partial: boolean,
|
|
247
|
-
): void {
|
|
248
|
-
if (!Array.isArray(value)) {
|
|
249
|
-
errors.push(`${path}: must be array`);
|
|
250
|
-
return;
|
|
180
|
+
function resolveSchema(schema: JsonSchema, root: JsonSchema): JsonSchema | undefined {
|
|
181
|
+
const ref = schema.$ref;
|
|
182
|
+
if (typeof ref !== "string" || !ref.startsWith("#/")) {
|
|
183
|
+
return schema;
|
|
251
184
|
}
|
|
252
|
-
|
|
253
|
-
|
|
185
|
+
const target = resolveJsonPointer(root, ref);
|
|
186
|
+
if (typeof target === "object" && target !== null && !Array.isArray(target)) {
|
|
187
|
+
return target as JsonSchema;
|
|
254
188
|
}
|
|
255
|
-
|
|
256
|
-
|
|
189
|
+
return schema;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalizeTypes(type: unknown): string[] {
|
|
193
|
+
if (typeof type === "string") {
|
|
194
|
+
return [type];
|
|
257
195
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (let i = 0; i < value.length; i++) {
|
|
261
|
-
validateValue(value[i], items as JsonSchema, root, `${path}[${i}]`, errors, partial);
|
|
262
|
-
}
|
|
196
|
+
if (Array.isArray(type)) {
|
|
197
|
+
return type.filter((t): t is string => typeof t === "string");
|
|
263
198
|
}
|
|
199
|
+
return [];
|
|
264
200
|
}
|
|
265
201
|
|
|
266
202
|
export function validateParsedConfigValue(
|
|
@@ -271,10 +207,9 @@ export function validateParsedConfigValue(
|
|
|
271
207
|
if (!propertySchema) {
|
|
272
208
|
return parsed;
|
|
273
209
|
}
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
throw new Error(errors[0] ?? "Invalid config value");
|
|
210
|
+
const result = validateInstance(parsed, propertySchema, rootSchema, false, false);
|
|
211
|
+
if (!result.valid) {
|
|
212
|
+
throw new Error(result.errors[0] ?? "Invalid config value");
|
|
278
213
|
}
|
|
279
214
|
return parsed;
|
|
280
215
|
}
|
|
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { chmodSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import type { CliProgram } from "
|
|
9
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
10
10
|
import { isAppInstalled, isExternallyManagedBinary, resolvePathCommand } from "./binary-placement.ts";
|
|
11
11
|
|
|
12
12
|
const program: CliProgram = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { accessSync, constants, realpathSync } from "node:fs";
|
|
2
2
|
import { delimiter, join } from "node:path";
|
|
3
|
-
import type { CliProgram } from "
|
|
3
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
4
4
|
|
|
5
5
|
/** Resolves a command name on PATH to its real path, if present. */
|
|
6
6
|
export function resolvePathCommand(key: string): string | undefined {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import type { CliUpdateGetLatest } from "
|
|
4
|
+
import type { CliUpdateGetLatest } from "../../core/types.ts";
|
|
5
5
|
|
|
6
6
|
/** Config for {@link ghReleaseUpdateGetLatest}. */
|
|
7
7
|
export interface GhReleaseUpdateConfig {
|
|
@@ -3,9 +3,9 @@ Tests for install/install-validate module behavior.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import type { CliProgram } from "
|
|
7
|
-
import { CliSchemaValidationError } from "
|
|
8
|
-
import { cliValidateProgram } from "
|
|
6
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
7
|
+
import { CliSchemaValidationError } from "../../core/types.ts";
|
|
8
|
+
import { cliValidateProgram } from "../../core/validate.ts";
|
|
9
9
|
|
|
10
10
|
const base: CliProgram = {
|
|
11
11
|
key: "app",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import type { CliProgram } from "
|
|
3
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
4
4
|
|
|
5
5
|
export interface McpServerEntry {
|
|
6
6
|
command: string;
|
|
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import type { CliProgram } from "
|
|
9
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
10
10
|
import {
|
|
11
11
|
detectOpenCodeMcpConfigPath,
|
|
12
12
|
expectedOpenCodeMcpEntry,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import type { CliProgram } from "
|
|
3
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
4
4
|
import { expectedMcpEntry } from "./mcp-config.ts";
|
|
5
5
|
|
|
6
6
|
export const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import type { CliProgram } from "
|
|
4
|
-
import { mcpServerId, sanitizeToolSegment } from "
|
|
5
|
-
import { displayHomePath, userHome, xdgConfigHome } from "
|
|
6
|
-
import { skillDirNameForTarget } from "
|
|
3
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
4
|
+
import { mcpServerId, sanitizeToolSegment } from "../../mcp/tools.ts";
|
|
5
|
+
import { displayHomePath, userHome, xdgConfigHome } from "../../paths/host.ts";
|
|
6
|
+
import { skillDirNameForTarget } from "../../skill/install.ts";
|
|
7
7
|
import { resolveOpenclawConfigPath } from "./mcp-openclaw.ts";
|
|
8
8
|
import { resolveOpenCodeConfigPathForInstall } from "./mcp-opencode.ts";
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ export interface InstallPaths {
|
|
|
24
24
|
skillDirName: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export { userHome } from "
|
|
27
|
+
export { userHome } from "../../paths/host.ts";
|
|
28
28
|
|
|
29
29
|
/** Format an absolute path for user-facing install output. */
|
|
30
30
|
export function displayInstallPath(path: string): string {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CliProgram } from "
|
|
1
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
2
2
|
import type { InstallPaths } from "./paths.ts";
|
|
3
3
|
import { buildInstallPlanFromTargets } from "./target-plan-build.ts";
|
|
4
4
|
import type { InstallAction, InstallOpts } from "./target-types.ts";
|
|
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import type { CliProgram } from "
|
|
9
|
+
import type { CliProgram } from "../../core/types.ts";
|
|
10
10
|
import { resolveInstallPaths } from "./paths.ts";
|
|
11
11
|
import { printInstallStatus } from "./status.ts";
|
|
12
12
|
import { resolveInstallTargetPreview } from "./target-scope.ts";
|