jui-tools-mcp-server 2.5.0
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/.github/workflows/test.yml +35 -0
- package/README.md +148 -0
- package/data/attribute_definitions.json +4248 -0
- package/data/attribute_semantics.json +137 -0
- package/data/binding_semantics.json +115 -0
- package/data/component_metadata.json +769 -0
- package/data/coverage.json +114 -0
- package/data/screen_identity.json +350 -0
- package/dist/cli_runner.d.ts +10 -0
- package/dist/cli_runner.js +33 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +67 -0
- package/dist/data/component_metadata.d.ts +30 -0
- package/dist/data/component_metadata.js +498 -0
- package/dist/data/derived.d.ts +198 -0
- package/dist/data/derived.js +293 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +130 -0
- package/dist/spec_loader.d.ts +110 -0
- package/dist/spec_loader.js +445 -0
- package/dist/tools/api/list_api_models.d.ts +10 -0
- package/dist/tools/api/list_api_models.js +37 -0
- package/dist/tools/api/list_api_specs.d.ts +19 -0
- package/dist/tools/api/list_api_specs.js +42 -0
- package/dist/tools/api/preview_api_model_sync.d.ts +11 -0
- package/dist/tools/api/preview_api_model_sync.js +34 -0
- package/dist/tools/context/get_project_config.d.ts +3 -0
- package/dist/tools/context/get_project_config.js +20 -0
- package/dist/tools/context/list_component_specs.d.ts +3 -0
- package/dist/tools/context/list_component_specs.js +35 -0
- package/dist/tools/context/list_layouts.d.ts +3 -0
- package/dist/tools/context/list_layouts.js +118 -0
- package/dist/tools/context/list_screen_specs.d.ts +3 -0
- package/dist/tools/context/list_screen_specs.js +37 -0
- package/dist/tools/context/read_layout_file.d.ts +3 -0
- package/dist/tools/context/read_layout_file.js +25 -0
- package/dist/tools/context/read_spec_file.d.ts +3 -0
- package/dist/tools/context/read_spec_file.js +27 -0
- package/dist/tools/doc/doc_generate_component.d.ts +3 -0
- package/dist/tools/doc/doc_generate_component.js +38 -0
- package/dist/tools/doc/doc_generate_html.d.ts +3 -0
- package/dist/tools/doc/doc_generate_html.js +46 -0
- package/dist/tools/doc/doc_generate_spec.d.ts +3 -0
- package/dist/tools/doc/doc_generate_spec.js +38 -0
- package/dist/tools/doc/doc_init_component.d.ts +3 -0
- package/dist/tools/doc/doc_init_component.js +30 -0
- package/dist/tools/doc/doc_init_spec.d.ts +3 -0
- package/dist/tools/doc/doc_init_spec.js +33 -0
- package/dist/tools/doc/doc_rules_init.d.ts +3 -0
- package/dist/tools/doc/doc_rules_init.js +25 -0
- package/dist/tools/doc/doc_rules_show.d.ts +3 -0
- package/dist/tools/doc/doc_rules_show.js +21 -0
- package/dist/tools/doc/doc_validate_component.d.ts +3 -0
- package/dist/tools/doc/doc_validate_component.js +39 -0
- package/dist/tools/doc/doc_validate_spec.d.ts +3 -0
- package/dist/tools/doc/doc_validate_spec.js +40 -0
- package/dist/tools/jui/jui_build.d.ts +3 -0
- package/dist/tools/jui/jui_build.js +31 -0
- package/dist/tools/jui/jui_generate_converter.d.ts +3 -0
- package/dist/tools/jui/jui_generate_converter.js +43 -0
- package/dist/tools/jui/jui_generate_project.d.ts +3 -0
- package/dist/tools/jui/jui_generate_project.js +43 -0
- package/dist/tools/jui/jui_generate_screen.d.ts +3 -0
- package/dist/tools/jui/jui_generate_screen.js +22 -0
- package/dist/tools/jui/jui_init.d.ts +3 -0
- package/dist/tools/jui/jui_init.js +42 -0
- package/dist/tools/jui/jui_migrate_layouts.d.ts +3 -0
- package/dist/tools/jui/jui_migrate_layouts.js +25 -0
- package/dist/tools/jui/jui_sync_tool.d.ts +3 -0
- package/dist/tools/jui/jui_sync_tool.js +33 -0
- package/dist/tools/jui/jui_verify.d.ts +3 -0
- package/dist/tools/jui/jui_verify.js +43 -0
- package/dist/tools/spec/get_binding_rules.d.ts +3 -0
- package/dist/tools/spec/get_binding_rules.js +8 -0
- package/dist/tools/spec/get_data_source.d.ts +3 -0
- package/dist/tools/spec/get_data_source.js +41 -0
- package/dist/tools/spec/get_modifier_order.d.ts +3 -0
- package/dist/tools/spec/get_modifier_order.js +14 -0
- package/dist/tools/spec/get_platform_mapping.d.ts +3 -0
- package/dist/tools/spec/get_platform_mapping.js +20 -0
- package/dist/tools/spec/get_screen_identity.d.ts +3 -0
- package/dist/tools/spec/get_screen_identity.js +22 -0
- package/dist/tools/spec/lookup_attribute.d.ts +3 -0
- package/dist/tools/spec/lookup_attribute.js +14 -0
- package/dist/tools/spec/lookup_component.d.ts +3 -0
- package/dist/tools/spec/lookup_component.js +15 -0
- package/dist/tools/spec/search_components.d.ts +3 -0
- package/dist/tools/spec/search_components.js +14 -0
- package/dist/tools/test/test_artifacts_pull.d.ts +3 -0
- package/dist/tools/test/test_artifacts_pull.js +48 -0
- package/dist/tools/test/test_artifacts_status.d.ts +3 -0
- package/dist/tools/test/test_artifacts_status.js +28 -0
- package/dist/tools/test/test_generate_description.d.ts +3 -0
- package/dist/tools/test/test_generate_description.js +27 -0
- package/dist/tools/test/test_generate_flow.d.ts +3 -0
- package/dist/tools/test/test_generate_flow.js +29 -0
- package/dist/tools/test/test_generate_screen.d.ts +3 -0
- package/dist/tools/test/test_generate_screen.js +29 -0
- package/dist/tools/test/test_mock_generate.d.ts +3 -0
- package/dist/tools/test/test_mock_generate.js +64 -0
- package/dist/tools/test/test_report.d.ts +3 -0
- package/dist/tools/test/test_report.js +26 -0
- package/dist/tools/test/test_validate.d.ts +3 -0
- package/dist/tools/test/test_validate.js +43 -0
- package/docs/design.md +227 -0
- package/install.sh +90 -0
- package/package.json +24 -0
- package/scripts/fetch-definitions.js +101 -0
- package/scripts/fetch-manifest.js +60 -0
- package/src/cli_runner.ts +55 -0
- package/src/config.ts +88 -0
- package/src/data/derived.ts +305 -0
- package/src/index.ts +157 -0
- package/src/spec_loader.ts +591 -0
- package/src/tools/api/list_api_models.ts +44 -0
- package/src/tools/api/list_api_specs.ts +53 -0
- package/src/tools/api/preview_api_model_sync.ts +41 -0
- package/src/tools/context/get_project_config.ts +27 -0
- package/src/tools/context/list_component_specs.ts +44 -0
- package/src/tools/context/list_layouts.ts +131 -0
- package/src/tools/context/list_screen_specs.ts +46 -0
- package/src/tools/context/read_layout_file.ts +35 -0
- package/src/tools/context/read_spec_file.ts +38 -0
- package/src/tools/doc/doc_generate_component.ts +43 -0
- package/src/tools/doc/doc_generate_html.ts +40 -0
- package/src/tools/doc/doc_generate_spec.ts +43 -0
- package/src/tools/doc/doc_init_component.ts +32 -0
- package/src/tools/doc/doc_init_spec.ts +37 -0
- package/src/tools/doc/doc_rules_init.ts +29 -0
- package/src/tools/doc/doc_rules_show.ts +27 -0
- package/src/tools/doc/doc_validate_component.ts +47 -0
- package/src/tools/doc/doc_validate_spec.ts +48 -0
- package/src/tools/jui/jui_build.ts +31 -0
- package/src/tools/jui/jui_generate_converter.ts +41 -0
- package/src/tools/jui/jui_generate_project.ts +37 -0
- package/src/tools/jui/jui_generate_screen.ts +28 -0
- package/src/tools/jui/jui_init.ts +38 -0
- package/src/tools/jui/jui_migrate_layouts.ts +29 -0
- package/src/tools/jui/jui_sync_tool.ts +33 -0
- package/src/tools/jui/jui_verify.ts +43 -0
- package/src/tools/spec/get_binding_rules.ts +16 -0
- package/src/tools/spec/get_data_source.ts +53 -0
- package/src/tools/spec/get_modifier_order.ts +22 -0
- package/src/tools/spec/get_platform_mapping.ts +28 -0
- package/src/tools/spec/get_screen_identity.ts +31 -0
- package/src/tools/spec/lookup_attribute.ts +22 -0
- package/src/tools/spec/lookup_component.ts +23 -0
- package/src/tools/spec/search_components.ts +22 -0
- package/src/tools/test/test_artifacts_pull.ts +47 -0
- package/src/tools/test/test_artifacts_status.ts +34 -0
- package/src/tools/test/test_generate_description.ts +33 -0
- package/src/tools/test/test_generate_flow.ts +33 -0
- package/src/tools/test/test_generate_screen.ts +33 -0
- package/src/tools/test/test_mock_generate.ts +58 -0
- package/src/tools/test/test_report.ts +32 -0
- package/src/tools/test/test_validate.ts +43 -0
- package/tests/__snapshots__/derived.test.ts.snap +279 -0
- package/tests/bundled_snapshot.test.ts +155 -0
- package/tests/cli_runner.test.ts +120 -0
- package/tests/config.test.ts +212 -0
- package/tests/derived.test.ts +114 -0
- package/tests/helpers.ts +272 -0
- package/tests/setup.ts +27 -0
- package/tests/snapshot_completeness.test.ts +152 -0
- package/tests/spec_loader.test.ts +488 -0
- package/tests/tools_cli.test.ts +687 -0
- package/tests/tools_context.test.ts +274 -0
- package/tests/tools_spec.test.ts +225 -0
- package/tests/tools_test.test.ts +284 -0
- package/tsconfig.json +15 -0
- package/uninstall.sh +46 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_artifacts_pull",
|
|
9
|
+
"Pull test artifacts (screenshots/recordings) from the latest run (iOS xcresult / Android device / web Playwright output) into the project's configured artifacts directory, returning generated file paths",
|
|
10
|
+
{
|
|
11
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
12
|
+
platform: z.enum(["ios", "android", "web", "all"]).optional().describe("Restrict pulling to one platform (default: all configured)"),
|
|
13
|
+
xcresult: z.string().optional().describe("Explicit .xcresult bundle path (iOS)"),
|
|
14
|
+
serial: z.string().optional().describe("adb device serial (Android)"),
|
|
15
|
+
clean: z.boolean().optional().describe("Remove artifacts from the device after pulling (Android)"),
|
|
16
|
+
},
|
|
17
|
+
async (params) => {
|
|
18
|
+
try {
|
|
19
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
20
|
+
const args = ["artifacts", "pull", "--json"];
|
|
21
|
+
if (params.platform) { args.push("--platform", params.platform); }
|
|
22
|
+
if (params.xcresult) { args.push("--xcresult", params.xcresult); }
|
|
23
|
+
if (params.serial) { args.push("--serial", params.serial); }
|
|
24
|
+
if (params.clean) { args.push("--clean"); }
|
|
25
|
+
|
|
26
|
+
// xcresult export can exceed the default 60s timeout.
|
|
27
|
+
const result = await runCli("jsonui-test", args, {
|
|
28
|
+
cwd: projectDir,
|
|
29
|
+
timeout: 180_000,
|
|
30
|
+
});
|
|
31
|
+
if (result.exitCode === 0) {
|
|
32
|
+
try {
|
|
33
|
+
JSON.parse(result.stdout);
|
|
34
|
+
// The CLI already emits a JSON document ({outputDir, files, skipped})
|
|
35
|
+
// — pass it through verbatim so callers don't peel a wrapper envelope.
|
|
36
|
+
return { content: [{ type: "text", text: result.stdout.trim() }] };
|
|
37
|
+
} catch {
|
|
38
|
+
// Not JSON — fall through to the standard envelope.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
42
|
+
} catch (e: any) {
|
|
43
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_artifacts_status",
|
|
9
|
+
"Show the resolved test-artifacts configuration (output dir, iOS xcresult discovery, Android appId) and list already-pulled artifact files",
|
|
10
|
+
{
|
|
11
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
12
|
+
},
|
|
13
|
+
async (params) => {
|
|
14
|
+
try {
|
|
15
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
16
|
+
const result = await runCli("jsonui-test", ["artifacts", "status", "--json"], {
|
|
17
|
+
cwd: projectDir,
|
|
18
|
+
});
|
|
19
|
+
if (result.exitCode === 0) {
|
|
20
|
+
try {
|
|
21
|
+
JSON.parse(result.stdout);
|
|
22
|
+
// The CLI already emits a JSON document — pass it through verbatim.
|
|
23
|
+
return { content: [{ type: "text", text: result.stdout.trim() }] };
|
|
24
|
+
} catch {
|
|
25
|
+
// Not JSON — fall through to the standard envelope.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
29
|
+
} catch (e: any) {
|
|
30
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_generate_description",
|
|
9
|
+
"Generate a description JSON file for a screen or flow test case via the jsonui-test CLI",
|
|
10
|
+
{
|
|
11
|
+
test_type: z.enum(["screen", "flow"]).describe("Whether the case belongs to a screen or flow test"),
|
|
12
|
+
name: z.string().describe("Screen/flow name (e.g., login, checkout)"),
|
|
13
|
+
case_name: z.string().describe("Test case name (e.g., initial_display, happy_path)"),
|
|
14
|
+
path: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Output file path (default: tests/<type>s/<name>/descriptions/<case_name>.json)"),
|
|
18
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
19
|
+
},
|
|
20
|
+
async (params) => {
|
|
21
|
+
try {
|
|
22
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
23
|
+
const args = ["generate", "description", params.test_type, params.name, params.case_name];
|
|
24
|
+
if (params.path) { args.push("--path", params.path); }
|
|
25
|
+
|
|
26
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
27
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_generate_flow",
|
|
9
|
+
"Generate a flow test file template via the jsonui-test CLI",
|
|
10
|
+
{
|
|
11
|
+
name: z.string().describe("Flow name (e.g., login, checkout)"),
|
|
12
|
+
path: z.string().optional().describe("Output test file path (default: tests/flows/<name>/<name>.test.json)"),
|
|
13
|
+
platform: z
|
|
14
|
+
.enum(["ios", "ios-swiftui", "ios-uikit", "android", "web", "all"])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Target platform"),
|
|
17
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
18
|
+
},
|
|
19
|
+
async (params) => {
|
|
20
|
+
try {
|
|
21
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
22
|
+
const args = ["generate", "test", "flow", params.name];
|
|
23
|
+
if (params.path) { args.push("--path", params.path); }
|
|
24
|
+
if (params.platform) { args.push("--platform", params.platform); }
|
|
25
|
+
|
|
26
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
27
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_generate_screen",
|
|
9
|
+
"Generate a screen test file template via the jsonui-test CLI",
|
|
10
|
+
{
|
|
11
|
+
name: z.string().describe("Screen name (e.g., login, home)"),
|
|
12
|
+
path: z.string().optional().describe("Output test file path (default: tests/screens/<name>/<name>.test.json)"),
|
|
13
|
+
platform: z
|
|
14
|
+
.enum(["ios", "ios-swiftui", "ios-uikit", "android", "web", "all"])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Target platform"),
|
|
17
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
18
|
+
},
|
|
19
|
+
async (params) => {
|
|
20
|
+
try {
|
|
21
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
22
|
+
const args = ["generate", "test", "screen", params.name];
|
|
23
|
+
if (params.path) { args.push("--path", params.path); }
|
|
24
|
+
if (params.platform) { args.push("--platform", params.platform); }
|
|
25
|
+
|
|
26
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
27
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_mock_generate",
|
|
9
|
+
"Manage API mock files against an OpenAPI/Swagger spec via the jsonui-test CLI. Writes ONLY into <mockDir>/generated/, which it wipes and rewrites on each run — anything outside that directory is hand-written and never touched. Mocks are matched to operations by their source method+path, not by filename, so a project's own naming keeps working. Only the endpoints the project declares it consumes are scaffolded and checked: api.schemas.include_paths / exclude_paths (the same keys the DTO codegen filters on), or mock.includePaths / mock.excludePaths when the mock scope differs. A shared swagger's other realms are therefore NOT reported as mocks this project is missing — if a check reports many [MISSING] entries, read the paths before writing any, because endpoints from a realm the app cannot reach mean the scope is undeclared and the fix is one config key. A mock file serving an out-of-scope route is reported as [SCOPE] (an unused file, safe to delete) and does not fail; [ORPHAN] still means the swagger has no such endpoint at all, and still fails. check=true reports drift (response bodies are compared against the schema: types, required, enum) without writing. Findings under generated/ are warnings, findings outside it are errors, and a scenario that merely omits OPTIONAL fields is a note that does not fail — omitting an optional field is a valid instance, and failing on it buries the real violations (set strict=true to demand full coverage). Do NOT fill optional fields in to silence a note: a mechanical merge puts null into non-nullable slots and manufactures violations. update_default=true REPAIRS each existing mock's default scenario: it adds the required fields the contract has and the body lacks, refreshes the source route, and changes nothing else — no existing value is overwritten, nothing is removed, other scenarios are untouched. The default scenario is where a project keeps the data its tests assert on (scaffolding creates default and nothing else), so never replace a body wholesale to satisfy the check. Violations a merge cannot decide — wrong type, undeclared field — are reported for a person. dry_run=true previews. The 'mock serve' subcommand is intentionally NOT exposed over MCP (it is a long-running local HTTP server that executes run-targets).",
|
|
10
|
+
{
|
|
11
|
+
swagger: z
|
|
12
|
+
.array(z.string())
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Paths to OpenAPI files (repeatable). Defaults to the project mock config if omitted."),
|
|
15
|
+
out: z.string().optional().describe("Output mock dir (default: mock.mockDir or tests/mocks)"),
|
|
16
|
+
config_file: z.string().optional().describe("Config file (default: jui.config.json)"),
|
|
17
|
+
check: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(false)
|
|
21
|
+
.describe("When true, report drift vs swagger and do NOT write (adds --check). Default false = regenerate <mockDir>/generated/."),
|
|
22
|
+
strict: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.optional()
|
|
25
|
+
.default(false)
|
|
26
|
+
.describe("With check=true, treat a missing OPTIONAL field as drift too (adds --strict). Off by default."),
|
|
27
|
+
update_default: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(false)
|
|
31
|
+
.describe("Repair each existing mock's default scenario by ADDING missing required fields (adds --update-default). Never overwrites a value or removes a field."),
|
|
32
|
+
dry_run: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe("With update_default, report what would be added without writing (adds --dry-run)."),
|
|
37
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
38
|
+
},
|
|
39
|
+
async (params) => {
|
|
40
|
+
try {
|
|
41
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
42
|
+
const args = ["mock", "generate"];
|
|
43
|
+
for (const s of params.swagger ?? []) { args.push("--swagger", s); }
|
|
44
|
+
if (params.out) { args.push("--out", params.out); }
|
|
45
|
+
if (params.config_file) { args.push("--config", params.config_file); }
|
|
46
|
+
if (params.check) { args.push("--check"); }
|
|
47
|
+
if (params.strict) { args.push("--strict"); }
|
|
48
|
+
if (params.update_default) { args.push("--update-default"); }
|
|
49
|
+
if (params.dry_run) { args.push("--dry-run"); }
|
|
50
|
+
|
|
51
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
52
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
53
|
+
} catch (e: any) {
|
|
54
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_report",
|
|
9
|
+
"Convert one or more results JSON files (results.schema.json format) into a JUnit XML or HTML report via the jsonui-test CLI",
|
|
10
|
+
{
|
|
11
|
+
files: z
|
|
12
|
+
.array(z.string())
|
|
13
|
+
.min(1)
|
|
14
|
+
.describe("Results JSON files to convert (multiple inputs merge into one report)"),
|
|
15
|
+
format: z.enum(["junit", "html"]).describe("Report format"),
|
|
16
|
+
output: z.string().optional().describe("Output file path (default: report.xml / report.html)"),
|
|
17
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
18
|
+
},
|
|
19
|
+
async (params) => {
|
|
20
|
+
try {
|
|
21
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
22
|
+
const args = ["report", ...params.files, "--format", params.format];
|
|
23
|
+
if (params.output) { args.push("--output", params.output); }
|
|
24
|
+
|
|
25
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
26
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { runCli, formatResult } from "../../cli_runner.js";
|
|
5
|
+
|
|
6
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
7
|
+
server.tool(
|
|
8
|
+
"test_validate",
|
|
9
|
+
"Validate JsonUI test files (screen/flow tests + descriptions) against the schema via the jsonui-test CLI. When the project config declares mock.swagger + mock.mockDir this ALSO regenerates <mockDir>/generated/ if it is missing or older than the swagger, and fails on mock contract drift — so a non-zero result here is not necessarily about the test files. A mock that merely omits optional fields does not fail this gate (set mock.checkOptionalFields=true in the project config to demand full coverage). Only the endpoints the project declares it consumes are checked (api.schemas.include_paths / exclude_paths, the same keys the DTO codegen filters on), so a shared swagger's other realms are not reported as missing mocks. Pass no_mock_check to validate the test files alone. Like the CLI, this installs (flatten-copies) the tests into the configured test.install destinations as a side effect — pass no_install to validate without consuming the files.",
|
|
10
|
+
{
|
|
11
|
+
files: z
|
|
12
|
+
.array(z.string())
|
|
13
|
+
.min(1)
|
|
14
|
+
.describe("Files or directories to validate (e.g., ['tests/screens'] or ['login.test.json'])"),
|
|
15
|
+
verbose: z.boolean().optional().describe("Show all files, including valid ones"),
|
|
16
|
+
quiet: z.boolean().optional().describe("Hide warnings, show only errors"),
|
|
17
|
+
no_mock_check: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Skip the mock-vs-swagger contract check that otherwise runs on the same gate"),
|
|
21
|
+
no_install: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Validate only; skip the flatten-install side effect even if test.install is configured (maps to CLI --no-install; default false, same behavior as the CLI)"),
|
|
25
|
+
project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
|
|
26
|
+
},
|
|
27
|
+
async (params) => {
|
|
28
|
+
try {
|
|
29
|
+
const projectDir = config.resolveProjectDir(params.project_dir);
|
|
30
|
+
const args = ["validate", ...params.files];
|
|
31
|
+
if (params.verbose) { args.push("--verbose"); }
|
|
32
|
+
if (params.quiet) { args.push("--quiet"); }
|
|
33
|
+
if (params.no_mock_check) { args.push("--no-mock-check"); }
|
|
34
|
+
if (params.no_install) { args.push("--no-install"); }
|
|
35
|
+
|
|
36
|
+
const result = await runCli("jsonui-test", args, { cwd: projectDir });
|
|
37
|
+
return { content: [{ type: "text", text: formatResult(result) }] };
|
|
38
|
+
} catch (e: any) {
|
|
39
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`derived constants (pinned) > BINDING_RULES snapshot 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"criticalRules": [
|
|
6
|
+
"Two-way binding expressions must be a single flat identifier — no dots, brackets, ??, or ! (binding-two-way-complex)",
|
|
7
|
+
"Frame values (width, height, etc.) MUST be read-only (data.). $data. is NOT allowed",
|
|
8
|
+
"Two-way binding is only for stateful input components",
|
|
9
|
+
"React auto-generates onPropertyNameChange for two-way bindings",
|
|
10
|
+
"Kotlin Dynamic uses updateData(mapOf(key to value)) for data updates",
|
|
11
|
+
"onclick (lowercase) = selector format (string), onClick (camelCase) = binding format (@{handler})",
|
|
12
|
+
],
|
|
13
|
+
"description": "JsonUI binding syntax rules",
|
|
14
|
+
"directions": {
|
|
15
|
+
"read-only": {
|
|
16
|
+
"applicableAttributes": [
|
|
17
|
+
"Label.text",
|
|
18
|
+
"*.width / *.height (frame values)",
|
|
19
|
+
"*.enabled",
|
|
20
|
+
"*.hidden / *.visibility",
|
|
21
|
+
"*.background / *.fontColor",
|
|
22
|
+
"*.fontSize",
|
|
23
|
+
"*.opacity / *.alpha",
|
|
24
|
+
],
|
|
25
|
+
"description": "Display data values only (no write-back)",
|
|
26
|
+
"kotlin": "data[propertyName]",
|
|
27
|
+
"react": "{data.propertyName}",
|
|
28
|
+
"swift": "data.propertyName",
|
|
29
|
+
},
|
|
30
|
+
"two-way": {
|
|
31
|
+
"applicableAttributes": [
|
|
32
|
+
"TextField.text",
|
|
33
|
+
"TextView.text",
|
|
34
|
+
"Switch.isOn",
|
|
35
|
+
"Toggle.isOn",
|
|
36
|
+
"CheckBox.checked",
|
|
37
|
+
"Slider.value",
|
|
38
|
+
"Segment.selectedIndex",
|
|
39
|
+
"Radio.selectedIndex",
|
|
40
|
+
"SelectBox.selectedIndex",
|
|
41
|
+
"TabView.selectedIndex",
|
|
42
|
+
],
|
|
43
|
+
"description": "Component state changes reflect back to data model",
|
|
44
|
+
"kotlin": "mutableStateOf + updateData(mapOf(key to value))",
|
|
45
|
+
"react": "value={data.prop} + auto-generated onChange handler",
|
|
46
|
+
"swift": "$data.propertyName",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"format": {
|
|
50
|
+
"bindingExpression": "@{propertyName}",
|
|
51
|
+
"defaultValue": "@{propertyName ?? default} (one ?? max; string default in single or double quotes, or true/false/number; null = unresolved)",
|
|
52
|
+
"negation": "@{!propertyName} (boolean value attributes only — validator error elsewhere)",
|
|
53
|
+
"nestedPath": "@{user.name} / @{items[0].title} (dot path + bracket index — canonical on all platforms; see semantics)",
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
exports[`derived constants (pinned) > MODIFIER_ORDER snapshot 1`] = `
|
|
59
|
+
{
|
|
60
|
+
"description": "Platform-specific modifier application order. Order affects rendering.",
|
|
61
|
+
"kotlin": {
|
|
62
|
+
"criticalRules": [
|
|
63
|
+
"Modifier.then() chains left-to-right; order matters",
|
|
64
|
+
"margins before size (outside to inside)",
|
|
65
|
+
"cornerRadius must be applied as clip before background",
|
|
66
|
+
],
|
|
67
|
+
"order": [
|
|
68
|
+
"testTag",
|
|
69
|
+
"margins",
|
|
70
|
+
"weight (caller applies)",
|
|
71
|
+
"size (width/height, matchParent/wrapContent, min/max/aspectRatio)",
|
|
72
|
+
"alpha/opacity",
|
|
73
|
+
"shadow/elevation",
|
|
74
|
+
"background (cornerRadius -> clip -> border -> bgColor)",
|
|
75
|
+
"clickable",
|
|
76
|
+
"padding",
|
|
77
|
+
"alignment (RowScope/ColumnScope/BoxScope)",
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
"react": {
|
|
81
|
+
"classOrder": [
|
|
82
|
+
"layout (flex, flex-col/row)",
|
|
83
|
+
"sizing (w-*, h-*)",
|
|
84
|
+
"spacing (p-*, m-*)",
|
|
85
|
+
"typography (text-*, font-*)",
|
|
86
|
+
"visual (bg-*, rounded-*, border-*, shadow-*)",
|
|
87
|
+
"opacity",
|
|
88
|
+
"overflow",
|
|
89
|
+
"cursor/interaction",
|
|
90
|
+
],
|
|
91
|
+
"dynamicStyles": "Values not mappable to Tailwind use inline style objects",
|
|
92
|
+
"note": "React uses Tailwind CSS classes; ordering is less strict",
|
|
93
|
+
},
|
|
94
|
+
"swift": {
|
|
95
|
+
"criticalRules": [
|
|
96
|
+
"background MUST come after padding (background includes padding area)",
|
|
97
|
+
"border MUST come after cornerRadius (for rounded border)",
|
|
98
|
+
"frame MUST come before margins (size before external spacing)",
|
|
99
|
+
"Image: .resizable() must come first, then .aspectRatio",
|
|
100
|
+
"Label: .leading alignment added to frame for textAlign to work",
|
|
101
|
+
],
|
|
102
|
+
"order": [
|
|
103
|
+
"centerAlignment",
|
|
104
|
+
"edgeAlignment",
|
|
105
|
+
"padding",
|
|
106
|
+
"frameConstraints (min/max)",
|
|
107
|
+
"frameSize (width/height)",
|
|
108
|
+
"insets/insetHorizontal",
|
|
109
|
+
"background",
|
|
110
|
+
"cornerRadius",
|
|
111
|
+
"border",
|
|
112
|
+
"margins",
|
|
113
|
+
"alpha/opacity",
|
|
114
|
+
"shadow",
|
|
115
|
+
"clipping",
|
|
116
|
+
"offset",
|
|
117
|
+
"visibility (hidden/opacity ternary)",
|
|
118
|
+
"safeAreaInsets",
|
|
119
|
+
"disabled",
|
|
120
|
+
"tag (for TabView)",
|
|
121
|
+
"tintColor",
|
|
122
|
+
"onClick/onTapGesture",
|
|
123
|
+
"lifecycle (onAppear/onDisappear)",
|
|
124
|
+
"confirmationDialog",
|
|
125
|
+
"accessibilityIdentifier",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
exports[`derived constants (pinned) > PLATFORM_MAPPING snapshot 1`] = `
|
|
132
|
+
{
|
|
133
|
+
"contentMode": {
|
|
134
|
+
"aspectFill": {
|
|
135
|
+
"kotlin": "ContentScale.Crop",
|
|
136
|
+
"react": "object-cover",
|
|
137
|
+
"swift": ".fill",
|
|
138
|
+
},
|
|
139
|
+
"aspectFit": {
|
|
140
|
+
"kotlin": "ContentScale.Fit",
|
|
141
|
+
"react": "object-contain",
|
|
142
|
+
"swift": ".fit",
|
|
143
|
+
},
|
|
144
|
+
"scaleToFill": {
|
|
145
|
+
"kotlin": "ContentScale.FillBounds",
|
|
146
|
+
"react": "object-fill",
|
|
147
|
+
"swift": ".fill (no aspectRatio)",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
"description": "Cross-platform attribute value conversion mapping",
|
|
151
|
+
"fontWeight": {
|
|
152
|
+
"bold": {
|
|
153
|
+
"kotlin": "FontWeight.Bold",
|
|
154
|
+
"react": "font-bold",
|
|
155
|
+
"swift": ".bold",
|
|
156
|
+
},
|
|
157
|
+
"light": {
|
|
158
|
+
"kotlin": "FontWeight.Light",
|
|
159
|
+
"react": "font-light",
|
|
160
|
+
"swift": ".light",
|
|
161
|
+
},
|
|
162
|
+
"medium": {
|
|
163
|
+
"kotlin": "FontWeight.Medium",
|
|
164
|
+
"react": "font-medium",
|
|
165
|
+
"swift": ".medium",
|
|
166
|
+
},
|
|
167
|
+
"semibold": {
|
|
168
|
+
"kotlin": "FontWeight.SemiBold",
|
|
169
|
+
"react": "font-semibold",
|
|
170
|
+
"swift": ".semibold",
|
|
171
|
+
},
|
|
172
|
+
"thin": {
|
|
173
|
+
"kotlin": "FontWeight.Thin",
|
|
174
|
+
"react": "font-thin",
|
|
175
|
+
"swift": ".thin",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
"gravity": {
|
|
179
|
+
"center": {
|
|
180
|
+
"kotlin": "Alignment.Center",
|
|
181
|
+
"react": "items-center justify-center",
|
|
182
|
+
"swift": ".center",
|
|
183
|
+
},
|
|
184
|
+
"centerHorizontal": {
|
|
185
|
+
"kotlin_vertical": "Alignment.CenterHorizontally",
|
|
186
|
+
"react": "items-center (flex-col)",
|
|
187
|
+
"swift_vertical": ".center HStack alignment",
|
|
188
|
+
},
|
|
189
|
+
"centerVertical": {
|
|
190
|
+
"kotlin_horizontal": "Alignment.CenterVertically",
|
|
191
|
+
"react": "items-center (flex-row)",
|
|
192
|
+
"swift_horizontal": ".center VStack alignment",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
"orientation": {
|
|
196
|
+
"horizontal": {
|
|
197
|
+
"kotlin": "Row",
|
|
198
|
+
"react": "flex flex-row",
|
|
199
|
+
"swift": "HStack",
|
|
200
|
+
},
|
|
201
|
+
"none": {
|
|
202
|
+
"kotlin": "Box",
|
|
203
|
+
"react": "relative",
|
|
204
|
+
"swift": "ZStack",
|
|
205
|
+
},
|
|
206
|
+
"vertical": {
|
|
207
|
+
"kotlin": "Column",
|
|
208
|
+
"react": "flex flex-col",
|
|
209
|
+
"swift": "VStack",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
"textAlign": {
|
|
213
|
+
"center": {
|
|
214
|
+
"kotlin": "TextAlign.Center",
|
|
215
|
+
"react": "text-center",
|
|
216
|
+
"swift": ".center",
|
|
217
|
+
},
|
|
218
|
+
"left": {
|
|
219
|
+
"kotlin": "TextAlign.Start",
|
|
220
|
+
"react": "text-left",
|
|
221
|
+
"swift": ".leading",
|
|
222
|
+
},
|
|
223
|
+
"right": {
|
|
224
|
+
"kotlin": "TextAlign.End",
|
|
225
|
+
"react": "text-right",
|
|
226
|
+
"swift": ".trailing",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
"types": {
|
|
230
|
+
"Array": {
|
|
231
|
+
"kotlin": "List<T>",
|
|
232
|
+
"react": "T[]",
|
|
233
|
+
"swift": "[T]",
|
|
234
|
+
},
|
|
235
|
+
"Bool": {
|
|
236
|
+
"kotlin": "Boolean",
|
|
237
|
+
"react": "boolean",
|
|
238
|
+
"swift": "Bool",
|
|
239
|
+
},
|
|
240
|
+
"Dictionary": {
|
|
241
|
+
"kotlin": "Map<String, Any>",
|
|
242
|
+
"react": "Record<string, any>",
|
|
243
|
+
"swift": "[String: Any]",
|
|
244
|
+
},
|
|
245
|
+
"Double": {
|
|
246
|
+
"kotlin": "Double",
|
|
247
|
+
"react": "number",
|
|
248
|
+
"swift": "Double",
|
|
249
|
+
},
|
|
250
|
+
"Float": {
|
|
251
|
+
"kotlin": "Float",
|
|
252
|
+
"react": "number",
|
|
253
|
+
"swift": "CGFloat",
|
|
254
|
+
},
|
|
255
|
+
"Int": {
|
|
256
|
+
"kotlin": "Int",
|
|
257
|
+
"react": "number",
|
|
258
|
+
"swift": "Int",
|
|
259
|
+
},
|
|
260
|
+
"String": {
|
|
261
|
+
"kotlin": "String",
|
|
262
|
+
"react": "string",
|
|
263
|
+
"swift": "String",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
"values": {
|
|
267
|
+
"matchParent": {
|
|
268
|
+
"kotlin": "fillMaxWidth() / fillMaxHeight()",
|
|
269
|
+
"react": "w-full / h-full",
|
|
270
|
+
"swift": ".infinity (frame maxWidth/maxHeight)",
|
|
271
|
+
},
|
|
272
|
+
"wrapContent": {
|
|
273
|
+
"kotlin": "wrapContentWidth() / wrapContentHeight()",
|
|
274
|
+
"react": "w-fit / h-fit",
|
|
275
|
+
"swift": "Default (no frame specified)",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
`;
|