drawio-mcp-server 1.7.0 → 2.0.3
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/README.md +96 -425
- package/build/assets/downloader.js +96 -0
- package/build/assets/index.js +2 -0
- package/build/assets/manager.js +31 -0
- package/build/config.js +45 -0
- package/build/config.test.js +54 -0
- package/build/emitter_bus.js +2 -3
- package/build/index.js +305 -337
- package/build/plugin/mcp-plugin.js +2618 -0
- package/build/prefetch-assets.js +11 -0
- package/build/real-environment/add-cell-of-shape.test.js +51 -0
- package/build/real-environment/add-edge.test.js +86 -0
- package/build/real-environment/assertions.js +18 -0
- package/build/real-environment/delete-cell-by-id.test.js +45 -0
- package/build/real-environment/edge-editing.test.js +70 -0
- package/build/real-environment/edit-cell.test.js +64 -0
- package/build/real-environment/harness.js +175 -0
- package/build/real-environment/import-export.test.js +82 -0
- package/build/real-environment/layers-and-selection.test.js +70 -0
- package/build/real-environment/logger.js +25 -0
- package/build/real-environment/screenshot.js +46 -0
- package/build/real-environment/set-cell-parent.test.js +64 -0
- package/build/real-environment/shapes.test.js +153 -0
- package/build/real-environment/test-helpers.js +10 -0
- package/build/real-environment/tools.js +22 -0
- package/build/real-environment/types.js +1 -0
- package/build/tool.js +46 -0
- package/build/tools/add-cell-of-shape.js +42 -0
- package/build/tools/add-edge.js +33 -0
- package/build/tools/add-rectangle.js +41 -0
- package/build/tools/create-layer.js +8 -0
- package/build/tools/delete-cell-by-id.js +10 -0
- package/build/tools/edit-cell.js +28 -0
- package/build/tools/edit-edge.js +30 -0
- package/build/tools/export-diagram.js +96 -0
- package/build/tools/get-active-layer.js +5 -0
- package/build/tools/get-selected-cell.js +5 -0
- package/build/tools/get-shape-by-name.js +10 -0
- package/build/tools/get-shape-categories.js +5 -0
- package/build/tools/get-shapes-in-category.js +10 -0
- package/build/tools/import-diagram.js +22 -0
- package/build/tools/index.js +49 -0
- package/build/tools/list-layers.js +5 -0
- package/build/tools/list-paged-model.js +41 -0
- package/build/tools/move-cell-to-layer.js +11 -0
- package/build/tools/set-active-layer.js +8 -0
- package/build/tools/set-cell-data.js +14 -0
- package/build/tools/set-cell-parent.js +9 -0
- package/build/tools/set-cell-shape.js +13 -0
- package/build/tools/shared.js +7 -0
- package/build/tools/types.js +1 -0
- package/package.json +29 -22
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCellById, resetDiagram, selectCell, } from "./harness.js";
|
|
3
|
+
import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
|
|
4
|
+
import { callToolJson } from "./tools.js";
|
|
5
|
+
import { expectToolSuccess, unwrapToolPayload } from "./test-helpers.js";
|
|
6
|
+
describe("real environment/layers and selection", () => {
|
|
7
|
+
let context;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
context = await createRealEnvironmentContext();
|
|
10
|
+
}, 180000);
|
|
11
|
+
afterAll(async () => {
|
|
12
|
+
await disposeRealEnvironmentContext(context);
|
|
13
|
+
});
|
|
14
|
+
it("covers layer creation, activation, move-to-layer, selected cell, and paged model", async () => {
|
|
15
|
+
await resetDiagram(context);
|
|
16
|
+
context.browserMessages.length = 0;
|
|
17
|
+
const logCountBefore = context.logger.entries.length;
|
|
18
|
+
const { payload: rectangle } = await callToolJson(context, "add-rectangle", {
|
|
19
|
+
x: 120,
|
|
20
|
+
y: 110,
|
|
21
|
+
width: 150,
|
|
22
|
+
height: 90,
|
|
23
|
+
text: "Layered rectangle",
|
|
24
|
+
});
|
|
25
|
+
expectToolSuccess(rectangle);
|
|
26
|
+
const { payload: createdLayer } = await callToolJson(context, "create-layer", {
|
|
27
|
+
name: "Verification Layer",
|
|
28
|
+
});
|
|
29
|
+
expectToolSuccess(createdLayer);
|
|
30
|
+
const { payload: layersPayload } = await callToolJson(context, "list-layers", {});
|
|
31
|
+
const layers = unwrapToolPayload(layersPayload);
|
|
32
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
33
|
+
expect(layers.some((layer) => layer.id === createdLayer.result.id)).toBe(true);
|
|
34
|
+
await callToolJson(context, "set-active-layer", {
|
|
35
|
+
layer_id: createdLayer.result.id,
|
|
36
|
+
});
|
|
37
|
+
const { payload: activeLayerPayload } = await callToolJson(context, "get-active-layer", {});
|
|
38
|
+
const activeLayer = unwrapToolPayload(activeLayerPayload);
|
|
39
|
+
expect(activeLayer?.id).toBe(createdLayer.result.id);
|
|
40
|
+
await callToolJson(context, "move-cell-to-layer", {
|
|
41
|
+
cell_id: rectangle.result.id,
|
|
42
|
+
target_layer_id: createdLayer.result.id,
|
|
43
|
+
});
|
|
44
|
+
await selectCell(context.page, rectangle.result.id);
|
|
45
|
+
const { payload: selectedCellPayload } = await callToolJson(context, "get-selected-cell", {});
|
|
46
|
+
const selectedCell = unwrapToolPayload(selectedCellPayload);
|
|
47
|
+
expect(String(selectedCell?.id ?? "")).toBe(rectangle.result.id);
|
|
48
|
+
const { payload: pagedModelPayload } = await callToolJson(context, "list-paged-model", {
|
|
49
|
+
page: 0,
|
|
50
|
+
page_size: 20,
|
|
51
|
+
filter: {
|
|
52
|
+
ids: [rectangle.result.id],
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const pagedModel = unwrapToolPayload(pagedModelPayload);
|
|
56
|
+
const pagedCells = Array.isArray(pagedModel?.cells)
|
|
57
|
+
? pagedModel.cells
|
|
58
|
+
: Array.isArray(pagedModel)
|
|
59
|
+
? pagedModel
|
|
60
|
+
: [];
|
|
61
|
+
expect(pagedCells.some((cell) => cell.id === rectangle.result.id)).toBe(true);
|
|
62
|
+
await withVerificationScreenshot(context, "layers-and-selection", "before-live-state-verification", async () => {
|
|
63
|
+
const rectangleCell = await getCellById(context.page, rectangle.result.id);
|
|
64
|
+
expect(rectangleCell).not.toBeNull();
|
|
65
|
+
expect(rectangleCell?.parentId).toBe(createdLayer.result.id);
|
|
66
|
+
});
|
|
67
|
+
await expectNoBrowserErrors(context, "layers-and-selection");
|
|
68
|
+
await expectNoServerErrors(context, "layers-and-selection", logCountBefore);
|
|
69
|
+
}, 180000);
|
|
70
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class MemoryLogger {
|
|
2
|
+
entries = [];
|
|
3
|
+
log(level, message, ...data) {
|
|
4
|
+
this.entries.push({
|
|
5
|
+
level,
|
|
6
|
+
message: String(message ?? ""),
|
|
7
|
+
data,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
debug(message, ...data) {
|
|
11
|
+
this.entries.push({
|
|
12
|
+
level: "debug",
|
|
13
|
+
message: String(message ?? ""),
|
|
14
|
+
data,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
errors() {
|
|
18
|
+
return this.entries.filter((entry) => {
|
|
19
|
+
if (entry.level.toLowerCase() === "error") {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return entry.data.some((value) => value instanceof Error);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const ARTIFACTS_ROOT = join(process.cwd(), ".artifacts", "real-environment");
|
|
4
|
+
function normalizeSegment(value) {
|
|
5
|
+
return value.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/-+/g, "-");
|
|
6
|
+
}
|
|
7
|
+
async function captureDiagramXml(page, xmlPath) {
|
|
8
|
+
const xml = await page.evaluate(() => {
|
|
9
|
+
const maybeWindow = window;
|
|
10
|
+
const editor = maybeWindow.ui?.editor;
|
|
11
|
+
if (!editor) {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
if (typeof editor.getGraphXml === "function") {
|
|
15
|
+
const xmlNode = editor.getGraphXml();
|
|
16
|
+
const xmlText = window.mxUtils?.getXml?.(xmlNode);
|
|
17
|
+
return typeof xmlText === "string" ? xmlText : "";
|
|
18
|
+
}
|
|
19
|
+
const graph = editor.graph;
|
|
20
|
+
const encoder = new window.mxCodec();
|
|
21
|
+
const node = encoder.encode(graph.getModel());
|
|
22
|
+
return window.mxUtils?.getXml?.(node) ?? "";
|
|
23
|
+
});
|
|
24
|
+
await writeFile(xmlPath, xml, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
export async function captureVerificationArtifact(artifactRunDir, page, testName, stepName) {
|
|
27
|
+
await mkdir(artifactRunDir, { recursive: true });
|
|
28
|
+
const fileName = `${normalizeSegment(testName)}-${normalizeSegment(stepName)}-${Date.now()}.png`;
|
|
29
|
+
const filePath = join(artifactRunDir, fileName);
|
|
30
|
+
const xmlPath = filePath.replace(/\.png$/, ".xml");
|
|
31
|
+
await page.screenshot({
|
|
32
|
+
path: filePath,
|
|
33
|
+
fullPage: true,
|
|
34
|
+
});
|
|
35
|
+
await captureDiagramXml(page, xmlPath);
|
|
36
|
+
return {
|
|
37
|
+
screenshotPath: filePath,
|
|
38
|
+
xmlPath,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function createArtifactRunDir() {
|
|
42
|
+
await mkdir(ARTIFACTS_ROOT, { recursive: true });
|
|
43
|
+
const runDir = join(ARTIFACTS_ROOT, `run-${Date.now()}`);
|
|
44
|
+
await mkdir(runDir, { recursive: true });
|
|
45
|
+
return runDir;
|
|
46
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCells, resetDiagram, } from "./harness.js";
|
|
3
|
+
import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
|
|
4
|
+
import { callToolJson } from "./tools.js";
|
|
5
|
+
describe("real environment/set-cell-parent", () => {
|
|
6
|
+
let context;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
context = await createRealEnvironmentContext();
|
|
9
|
+
}, 180000);
|
|
10
|
+
afterAll(async () => {
|
|
11
|
+
await disposeRealEnvironmentContext(context);
|
|
12
|
+
});
|
|
13
|
+
it("reparents a child cell through MCP and verifies the live diagram state", async () => {
|
|
14
|
+
await resetDiagram(context);
|
|
15
|
+
context.browserMessages.length = 0;
|
|
16
|
+
const logCountBefore = context.logger.entries.length;
|
|
17
|
+
const { payload: parentPayload } = await callToolJson(context, "add-cell-of-shape", {
|
|
18
|
+
shape_name: "rectangle",
|
|
19
|
+
text: "Parent",
|
|
20
|
+
x: 320,
|
|
21
|
+
y: 160,
|
|
22
|
+
width: 220,
|
|
23
|
+
height: 140,
|
|
24
|
+
});
|
|
25
|
+
const { payload: childPayload } = await callToolJson(context, "add-cell-of-shape", {
|
|
26
|
+
shape_name: "rectangle",
|
|
27
|
+
text: "Child",
|
|
28
|
+
x: 120,
|
|
29
|
+
y: 80,
|
|
30
|
+
width: 100,
|
|
31
|
+
height: 60,
|
|
32
|
+
});
|
|
33
|
+
expect(parentPayload.success).toBe(true);
|
|
34
|
+
expect(childPayload.success).toBe(true);
|
|
35
|
+
const beforeCells = await getCells(context.page);
|
|
36
|
+
const childBefore = beforeCells.find((cell) => cell.id === childPayload.result.id);
|
|
37
|
+
expect(childBefore).toBeDefined();
|
|
38
|
+
expect(childBefore?.parentId).not.toBe(parentPayload.result.id);
|
|
39
|
+
const { payload } = await callToolJson(context, "set-cell-parent", {
|
|
40
|
+
cell_id: childPayload.result.id,
|
|
41
|
+
parent_id: parentPayload.result.id,
|
|
42
|
+
});
|
|
43
|
+
expect(payload.success).toBe(true);
|
|
44
|
+
expect(payload.result.cell_id).toBe(childPayload.result.id);
|
|
45
|
+
expect(payload.result.parent_id).toBe(parentPayload.result.id);
|
|
46
|
+
await context.page.waitForFunction(({ childId, parentId }) => {
|
|
47
|
+
const ui = window.ui;
|
|
48
|
+
const graph = ui?.editor?.graph;
|
|
49
|
+
const child = graph?.getModel?.().getCell?.(childId);
|
|
50
|
+
return child?.parent?.id === parentId;
|
|
51
|
+
}, {
|
|
52
|
+
childId: childPayload.result.id,
|
|
53
|
+
parentId: parentPayload.result.id,
|
|
54
|
+
});
|
|
55
|
+
await withVerificationScreenshot(context, "set-cell-parent", "before-live-state-verification", async () => {
|
|
56
|
+
const afterCells = await getCells(context.page);
|
|
57
|
+
const childAfter = afterCells.find((cell) => cell.id === childPayload.result.id);
|
|
58
|
+
expect(childAfter).toBeDefined();
|
|
59
|
+
expect(childAfter?.parentId).toBe(parentPayload.result.id);
|
|
60
|
+
});
|
|
61
|
+
await expectNoBrowserErrors(context, "set-cell-parent");
|
|
62
|
+
await expectNoServerErrors(context, "set-cell-parent", logCountBefore);
|
|
63
|
+
}, 180000);
|
|
64
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCellById, resetDiagram, } from "./harness.js";
|
|
3
|
+
import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
|
|
4
|
+
import { callToolJson } from "./tools.js";
|
|
5
|
+
import { expectToolSuccess, unwrapToolPayload } from "./test-helpers.js";
|
|
6
|
+
describe("real environment/shapes", () => {
|
|
7
|
+
let context;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
context = await createRealEnvironmentContext();
|
|
10
|
+
}, 180000);
|
|
11
|
+
afterAll(async () => {
|
|
12
|
+
await disposeRealEnvironmentContext(context);
|
|
13
|
+
});
|
|
14
|
+
it("covers shape discovery, rectangle creation, shape assignment, and cell data", async () => {
|
|
15
|
+
await resetDiagram(context);
|
|
16
|
+
context.browserMessages.length = 0;
|
|
17
|
+
const logCountBefore = context.logger.entries.length;
|
|
18
|
+
const { payload: shapeCategoriesPayload } = await callToolJson(context, "get-shape-categories", {});
|
|
19
|
+
const shapeCategories = unwrapToolPayload(shapeCategoriesPayload);
|
|
20
|
+
expect(shapeCategories).toBeDefined();
|
|
21
|
+
const { payload: shapesInCategoryPayload } = await callToolJson(context, "get-shapes-in-category", {
|
|
22
|
+
category_id: "General",
|
|
23
|
+
});
|
|
24
|
+
const shapesInCategory = unwrapToolPayload(shapesInCategoryPayload);
|
|
25
|
+
expect(shapesInCategory).toBeDefined();
|
|
26
|
+
const chosenShapeName = "rectangle";
|
|
27
|
+
const { payload: shapeByNamePayload } = await callToolJson(context, "get-shape-by-name", {
|
|
28
|
+
shape_name: chosenShapeName,
|
|
29
|
+
});
|
|
30
|
+
const shapeByName = unwrapToolPayload(shapeByNamePayload);
|
|
31
|
+
expect(shapeByName).toBeDefined();
|
|
32
|
+
const { payload: rectangle } = await callToolJson(context, "add-rectangle", {
|
|
33
|
+
x: 120,
|
|
34
|
+
y: 110,
|
|
35
|
+
width: 150,
|
|
36
|
+
height: 90,
|
|
37
|
+
text: "Rectangle tool",
|
|
38
|
+
});
|
|
39
|
+
expectToolSuccess(rectangle);
|
|
40
|
+
await callToolJson(context, "set-cell-shape", {
|
|
41
|
+
cell_id: rectangle.result.id,
|
|
42
|
+
shape_name: chosenShapeName,
|
|
43
|
+
});
|
|
44
|
+
await callToolJson(context, "set-cell-data", {
|
|
45
|
+
cell_id: rectangle.result.id,
|
|
46
|
+
key: "status",
|
|
47
|
+
value: "verified",
|
|
48
|
+
});
|
|
49
|
+
await withVerificationScreenshot(context, "shapes", "before-live-state-verification", async () => {
|
|
50
|
+
const rectangleCell = await getCellById(context.page, rectangle.result.id);
|
|
51
|
+
expect(rectangleCell).not.toBeNull();
|
|
52
|
+
expect(rectangleCell?.style.length).toBeGreaterThan(0);
|
|
53
|
+
expect(rectangleCell?.attributes.status).toBe("verified");
|
|
54
|
+
});
|
|
55
|
+
await expectNoBrowserErrors(context, "shapes");
|
|
56
|
+
await expectNoServerErrors(context, "shapes", logCountBefore);
|
|
57
|
+
}, 180000);
|
|
58
|
+
it("creates an AWS Lambda shaped cell with the expected AWS style", async () => {
|
|
59
|
+
await resetDiagram(context);
|
|
60
|
+
context.browserMessages.length = 0;
|
|
61
|
+
const logCountBefore = context.logger.entries.length;
|
|
62
|
+
const shapeName = "mxgraph.aws4.lambda";
|
|
63
|
+
const { payload: shapeByNamePayload } = await callToolJson(context, "get-shape-by-name", {
|
|
64
|
+
shape_name: shapeName,
|
|
65
|
+
});
|
|
66
|
+
const shapeByName = unwrapToolPayload(shapeByNamePayload);
|
|
67
|
+
expect(shapeByName).toBeDefined();
|
|
68
|
+
expect(String(shapeByName?.style ?? "")).toContain("shape=mxgraph.aws4.resourceIcon");
|
|
69
|
+
expect(String(shapeByName?.style ?? "")).toContain("resIcon=mxgraph.aws4.lambda");
|
|
70
|
+
expect(String(shapeByName?.style ?? "")).toContain("fillColor=#ED7100");
|
|
71
|
+
const { payload } = await callToolJson(context, "add-cell-of-shape", {
|
|
72
|
+
shape_name: shapeName,
|
|
73
|
+
text: "Lambda",
|
|
74
|
+
x: 180,
|
|
75
|
+
y: 140,
|
|
76
|
+
width: 120,
|
|
77
|
+
height: 120,
|
|
78
|
+
});
|
|
79
|
+
expectToolSuccess(payload);
|
|
80
|
+
await withVerificationScreenshot(context, "shapes-aws-lambda", "before-live-state-verification", async () => {
|
|
81
|
+
const liveCellState = await context.page.evaluate((cellId) => {
|
|
82
|
+
const maybeWindow = window;
|
|
83
|
+
const graph = maybeWindow.ui?.editor?.graph;
|
|
84
|
+
const cell = graph?.getModel?.().getCell?.(cellId);
|
|
85
|
+
return {
|
|
86
|
+
cellStyle: cell?.style ?? null,
|
|
87
|
+
stateStyle: graph?.view?.getState?.(cell)?.style ?? null,
|
|
88
|
+
};
|
|
89
|
+
}, payload.result.id);
|
|
90
|
+
const exportedXml = await context.page.evaluate(() => {
|
|
91
|
+
const maybeWindow = window;
|
|
92
|
+
const editor = maybeWindow.ui?.editor;
|
|
93
|
+
const xmlNode = editor?.getGraphXml?.();
|
|
94
|
+
return window.mxUtils?.getXml?.(xmlNode) ?? "";
|
|
95
|
+
});
|
|
96
|
+
const lambdaCell = await getCellById(context.page, payload.result.id);
|
|
97
|
+
expect(lambdaCell).not.toBeNull();
|
|
98
|
+
expect(String(liveCellState.cellStyle ?? "")).toContain("mxgraph.aws4.lambda");
|
|
99
|
+
expect(exportedXml).toContain(`id="${payload.result.id}"`);
|
|
100
|
+
expect(exportedXml).toContain("mxgraph.aws4.lambda");
|
|
101
|
+
const exportedLambda = await callToolJson(context, "list-paged-model", {
|
|
102
|
+
page: 0,
|
|
103
|
+
page_size: 20,
|
|
104
|
+
filter: {
|
|
105
|
+
ids: [payload.result.id],
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const lambdaEntry = unwrapToolPayload(exportedLambda.payload);
|
|
109
|
+
const lambdaCells = Array.isArray(lambdaEntry?.cells)
|
|
110
|
+
? lambdaEntry.cells
|
|
111
|
+
: Array.isArray(lambdaEntry)
|
|
112
|
+
? lambdaEntry
|
|
113
|
+
: [];
|
|
114
|
+
const lambdaCellFromTool = lambdaCells.find((cell) => cell.id === payload.result.id);
|
|
115
|
+
expect(lambdaCellFromTool).toBeDefined();
|
|
116
|
+
expect(exportedXml).toContain("mxgraph.aws4.lambda");
|
|
117
|
+
});
|
|
118
|
+
await expectNoBrowserErrors(context, "shapes-aws-lambda");
|
|
119
|
+
await expectNoServerErrors(context, "shapes-aws-lambda", logCountBefore);
|
|
120
|
+
}, 180000);
|
|
121
|
+
it("applies AWS Lambda style through set-cell-shape and preserves it in the live diagram", async () => {
|
|
122
|
+
await resetDiagram(context);
|
|
123
|
+
context.browserMessages.length = 0;
|
|
124
|
+
const logCountBefore = context.logger.entries.length;
|
|
125
|
+
const shapeName = "mxgraph.aws4.lambda";
|
|
126
|
+
const { payload: rectangle } = await callToolJson(context, "add-rectangle", {
|
|
127
|
+
x: 220,
|
|
128
|
+
y: 180,
|
|
129
|
+
width: 120,
|
|
130
|
+
height: 120,
|
|
131
|
+
text: "Lambda by shape",
|
|
132
|
+
});
|
|
133
|
+
expectToolSuccess(rectangle);
|
|
134
|
+
await callToolJson(context, "set-cell-shape", {
|
|
135
|
+
cell_id: rectangle.result.id,
|
|
136
|
+
shape_name: shapeName,
|
|
137
|
+
});
|
|
138
|
+
await withVerificationScreenshot(context, "shapes-aws-lambda-set-cell-shape", "before-live-state-verification", async () => {
|
|
139
|
+
const exportedXml = await context.page.evaluate(() => {
|
|
140
|
+
const maybeWindow = window;
|
|
141
|
+
const editor = maybeWindow.ui?.editor;
|
|
142
|
+
const xmlNode = editor?.getGraphXml?.();
|
|
143
|
+
return window.mxUtils?.getXml?.(xmlNode) ?? "";
|
|
144
|
+
});
|
|
145
|
+
const lambdaCell = await getCellById(context.page, rectangle.result.id);
|
|
146
|
+
expect(lambdaCell).not.toBeNull();
|
|
147
|
+
expect(exportedXml).toContain(`id="${rectangle.result.id}"`);
|
|
148
|
+
expect(exportedXml).toContain("mxgraph.aws4.lambda");
|
|
149
|
+
});
|
|
150
|
+
await expectNoBrowserErrors(context, "shapes-aws-lambda-set-cell-shape");
|
|
151
|
+
await expectNoServerErrors(context, "shapes-aws-lambda-set-cell-shape", logCountBefore);
|
|
152
|
+
}, 180000);
|
|
153
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { expect } from "@jest/globals";
|
|
2
|
+
export function unwrapToolPayload(payload) {
|
|
3
|
+
if (payload && typeof payload === "object" && "success" in payload) {
|
|
4
|
+
return payload.result;
|
|
5
|
+
}
|
|
6
|
+
return payload;
|
|
7
|
+
}
|
|
8
|
+
export function expectToolSuccess(payload) {
|
|
9
|
+
expect(payload?.success).toBe(true);
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect } from "@jest/globals";
|
|
2
|
+
export async function callToolJson(context, name, args) {
|
|
3
|
+
const result = (await context.client.callTool({
|
|
4
|
+
name,
|
|
5
|
+
arguments: args,
|
|
6
|
+
}));
|
|
7
|
+
expect(result.isError).not.toBe(true);
|
|
8
|
+
const content = result.content;
|
|
9
|
+
expect(content[0]?.type).toBe("text");
|
|
10
|
+
return {
|
|
11
|
+
raw: result,
|
|
12
|
+
payload: JSON.parse(String(content[0]?.text ?? "{}")),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function callToolRaw(context, name, args) {
|
|
16
|
+
const result = (await context.client.callTool({
|
|
17
|
+
name,
|
|
18
|
+
arguments: args,
|
|
19
|
+
}));
|
|
20
|
+
expect(result.isError).not.toBe(true);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/tool.js
CHANGED
|
@@ -38,3 +38,49 @@ export function default_tool(name, context) {
|
|
|
38
38
|
});
|
|
39
39
|
return fn;
|
|
40
40
|
}
|
|
41
|
+
export function export_tool_handler(name, context) {
|
|
42
|
+
const fn = build_channel(context, name, (reply) => {
|
|
43
|
+
const { success, result, error } = reply;
|
|
44
|
+
if (!success) {
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `Export failed: ${error || "Unknown error"}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const { format, mimeType, data, width, height, warning } = result;
|
|
55
|
+
const content = [];
|
|
56
|
+
if (warning) {
|
|
57
|
+
content.push({
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Warning: ${warning}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (format === "png") {
|
|
63
|
+
content.push({
|
|
64
|
+
type: "image",
|
|
65
|
+
mimeType: "image/png",
|
|
66
|
+
data: data,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
content.push({
|
|
71
|
+
type: "text",
|
|
72
|
+
text: data,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const dimensions = width && height ? `, ${width}x${height}` : "";
|
|
76
|
+
content.push({
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `Exported ${format} (${mimeType})${dimensions}`,
|
|
79
|
+
});
|
|
80
|
+
const response = {
|
|
81
|
+
content,
|
|
82
|
+
};
|
|
83
|
+
return response;
|
|
84
|
+
});
|
|
85
|
+
return fn;
|
|
86
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_add_cell_of_shape = "add-cell-of-shape";
|
|
4
|
+
export const registerAddCellOfShapeTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_add_cell_of_shape, "This tool allows you to add new vertex cell (object) on the current page of a Draw.io diagram by its shape name. It accepts multiple optional input parameter.", {
|
|
6
|
+
shape_name: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Name of the shape to retrieved from the shape library of the current diagram."),
|
|
9
|
+
x: z
|
|
10
|
+
.number()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("X-axis position of the vertex cell of the shape")
|
|
13
|
+
.default(100),
|
|
14
|
+
y: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Y-axis position of the vertex cell of the shape")
|
|
18
|
+
.default(100),
|
|
19
|
+
width: z
|
|
20
|
+
.number()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Width of the vertex cell of the shape")
|
|
23
|
+
.default(200),
|
|
24
|
+
height: z
|
|
25
|
+
.number()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Height of the vertex cell of the shape")
|
|
28
|
+
.default(100),
|
|
29
|
+
text: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Text content placed inside of the vertex cell of the shape"),
|
|
33
|
+
style: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Semi-colon separated list of Draw.io visual styles, in the form of `key=value`. Example: `whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;`"),
|
|
37
|
+
parent_id: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("ID of the parent cell. If provided, the new cell will be created as a child of this cell. If omitted, the cell is created at the diagram root level."),
|
|
41
|
+
}, default_tool(TOOL_add_cell_of_shape, context));
|
|
42
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_add_edge = "add-edge";
|
|
4
|
+
export const registerAddEdgeTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_add_edge, "This tool creates an edge, sometimes called also a relation, between two vertexes (cells). When source and target are the same shape (self-connector), a loop edge style is automatically applied if no custom style is provided.", {
|
|
6
|
+
source_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Source ID of a cell. It is represented by `id` attribute."),
|
|
9
|
+
target_id: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("Target ID of a cell. It is represented by `id` attribute."),
|
|
12
|
+
text: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Text content placed over the edge cell"),
|
|
16
|
+
style: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Semi-colon separated list of Draw.io visual styles, in the form of `key=value`. Example: `edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;`")
|
|
20
|
+
.default("edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;"),
|
|
21
|
+
points: z
|
|
22
|
+
.array(z.object({
|
|
23
|
+
x: z.number().describe("X coordinate of the waypoint"),
|
|
24
|
+
y: z.number().describe("Y coordinate of the waypoint"),
|
|
25
|
+
}))
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Array of {x, y} waypoints to control edge routing. Useful for custom paths or self-connectors where straight lines are barely visible."),
|
|
28
|
+
parent_id: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("ID of the parent cell. If provided, the new edge will be created as a child of this cell. If omitted, the edge is created at the diagram root level."),
|
|
32
|
+
}, default_tool(TOOL_add_edge, context));
|
|
33
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_add_rectangle = "add-rectangle";
|
|
4
|
+
export const registerAddRectangleTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_add_rectangle, "This tool allows you to add new Rectangle vertex cell (object) on the current page of a Draw.io diagram. It accepts multiple optional input parameter.", {
|
|
6
|
+
x: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("X-axis position of the Rectangle vertex cell")
|
|
10
|
+
.default(100),
|
|
11
|
+
y: z
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Y-axis position of the Rectangle vertex cell")
|
|
15
|
+
.default(100),
|
|
16
|
+
width: z
|
|
17
|
+
.number()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Width of the Rectangle vertex cell")
|
|
20
|
+
.default(200),
|
|
21
|
+
height: z
|
|
22
|
+
.number()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Height of the Rectangle vertex cell")
|
|
25
|
+
.default(100),
|
|
26
|
+
text: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Text content placed inside of the Rectangle vertex cell")
|
|
30
|
+
.default("New Cell"),
|
|
31
|
+
style: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Semi-colon separated list of Draw.io visual styles, in the form of `key=value`. Example: `whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;`")
|
|
35
|
+
.default("whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;"),
|
|
36
|
+
parent_id: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("ID of the parent cell. If provided, the new rectangle will be created as a child of this cell. If omitted, the rectangle is created at the diagram root level."),
|
|
40
|
+
}, default_tool(TOOL_add_rectangle, context));
|
|
41
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_create_layer = "create-layer";
|
|
4
|
+
export const registerCreateLayerTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_create_layer, "Creates a new layer in the diagram.", {
|
|
6
|
+
name: z.string().describe("Name for the new layer"),
|
|
7
|
+
}, default_tool(TOOL_create_layer, context));
|
|
8
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_delete_cell_by_id = "delete-cell-by-id";
|
|
4
|
+
export const registerDeleteCellByIdTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_delete_cell_by_id, "Deletes a cell, whether it is a vertex or edge.", {
|
|
6
|
+
cell_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("The ID of a cell to delete. The cell can be either vertex or edge. The ID is located in `id` attribute."),
|
|
9
|
+
}, default_tool(TOOL_delete_cell_by_id, context));
|
|
10
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_edit_cell = "edit-cell";
|
|
4
|
+
export const registerEditCellTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_edit_cell, "Update properties of an existing vertex/shape cell by its ID. Only provided fields are modified; unspecified properties remain unchanged.", {
|
|
6
|
+
cell_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Identifier (`id` attribute) of the cell to update. Applies to vertex/shape cells."),
|
|
9
|
+
text: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Replace the cell's text/label content."),
|
|
13
|
+
x: z
|
|
14
|
+
.number()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Set a new X-axis position for the cell."),
|
|
17
|
+
y: z
|
|
18
|
+
.number()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Set a new Y-axis position for the cell."),
|
|
21
|
+
width: z.number().optional().describe("Set a new width for the cell."),
|
|
22
|
+
height: z.number().optional().describe("Set a new height for the cell."),
|
|
23
|
+
style: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Replace the cell's style string (semi-colon separated `key=value` pairs)."),
|
|
27
|
+
}, default_tool(TOOL_edit_cell, context));
|
|
28
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_edit_edge = "edit-edge";
|
|
4
|
+
export const registerEditEdgeTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_edit_edge, "Update properties of an existing edge by its ID. Only provided fields are modified; unspecified properties remain unchanged. Supports setting waypoints for edge geometry control.", {
|
|
6
|
+
cell_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Identifier (`id` attribute) of the edge cell to update. The ID must reference an edge."),
|
|
9
|
+
text: z.string().optional().describe("Replace the edge's label text."),
|
|
10
|
+
source_id: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Reassign the edge's source terminal to a different cell ID."),
|
|
14
|
+
target_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Reassign the edge's target terminal to a different cell ID."),
|
|
18
|
+
style: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Replace the edge's style string (semi-colon separated `key=value` pairs)."),
|
|
22
|
+
points: z
|
|
23
|
+
.array(z.object({
|
|
24
|
+
x: z.number().describe("X coordinate of the waypoint"),
|
|
25
|
+
y: z.number().describe("Y coordinate of the waypoint"),
|
|
26
|
+
}))
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Array of {x, y} waypoints to set as edge geometry control points. Replaces existing waypoints. Use an empty array to clear waypoints."),
|
|
29
|
+
}, default_tool(TOOL_edit_edge, context));
|
|
30
|
+
};
|