pmx-canvas 0.1.11 โ 0.1.13
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 +130 -0
- package/dist/canvas/index.js +34 -34
- package/dist/types/client/nodes/trace-model.d.ts +9 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +88 -0
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/web-artifacts.d.ts +1 -0
- package/package.json +1 -1
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +9 -8
- package/src/client/nodes/TraceNode.tsx +2 -6
- package/src/client/nodes/trace-model.ts +19 -0
- package/src/client/state/canvas-store.ts +5 -2
- package/src/client/state/sse-bridge.ts +2 -1
- package/src/mcp/canvas-access.ts +676 -0
- package/src/mcp/server.ts +184 -97
- package/src/server/canvas-operations.ts +1 -0
- package/src/server/canvas-schema.ts +11 -0
- package/src/server/diagram-presets.ts +6 -28
- package/src/server/index.ts +2 -2
- package/src/server/server.ts +5 -1
- package/src/server/web-artifacts/scripts/init-artifact.sh +9 -8
- package/src/server/web-artifacts.ts +14 -1
|
@@ -135,8 +135,6 @@ function normalizeExcalidrawBoundText(elements: Array<Record<string, unknown>>):
|
|
|
135
135
|
|
|
136
136
|
let changed = false;
|
|
137
137
|
const boundElementIdsByContainer = new Map<string, Set<string>>();
|
|
138
|
-
const labelByContainer = new Map<string, Record<string, unknown>>();
|
|
139
|
-
const textIdsConvertedToLabels = new Set<string>();
|
|
140
138
|
|
|
141
139
|
for (const element of elements) {
|
|
142
140
|
if (element.type !== 'text' || typeof element.id !== 'string' || typeof element.containerId !== 'string') continue;
|
|
@@ -145,48 +143,28 @@ function normalizeExcalidrawBoundText(elements: Array<Record<string, unknown>>):
|
|
|
145
143
|
const ids = boundElementIdsByContainer.get(element.containerId) ?? new Set<string>();
|
|
146
144
|
ids.add(element.id);
|
|
147
145
|
boundElementIdsByContainer.set(element.containerId, ids);
|
|
148
|
-
const text = typeof element.text === 'string' ? element.text.trim() : '';
|
|
149
|
-
if (!isRecord(container.label) && text.length > 0) {
|
|
150
|
-
labelByContainer.set(element.containerId, {
|
|
151
|
-
text,
|
|
152
|
-
...(typeof element.fontSize === 'number' && Number.isFinite(element.fontSize) ? { fontSize: element.fontSize } : {}),
|
|
153
|
-
});
|
|
154
|
-
textIdsConvertedToLabels.add(element.id);
|
|
155
|
-
}
|
|
156
146
|
}
|
|
157
147
|
|
|
158
|
-
const normalized = elements.
|
|
159
|
-
if (typeof element.id === 'string' && textIdsConvertedToLabels.has(element.id)) {
|
|
160
|
-
changed = true;
|
|
161
|
-
return [];
|
|
162
|
-
}
|
|
148
|
+
const normalized = elements.map((element) => {
|
|
163
149
|
if (typeof element.id !== 'string') return element;
|
|
164
150
|
const boundTextIds = boundElementIdsByContainer.get(element.id);
|
|
165
|
-
|
|
166
|
-
if ((!boundTextIds || boundTextIds.size === 0) && !label) return element;
|
|
151
|
+
if (!boundTextIds || boundTextIds.size === 0) return element;
|
|
167
152
|
|
|
168
153
|
const existing = Array.isArray(element.boundElements)
|
|
169
154
|
? element.boundElements.filter(isRecord)
|
|
170
155
|
: [];
|
|
171
|
-
const remainingExisting = existing.filter((boundElement) => {
|
|
172
|
-
return !(boundElement.type === 'text' && typeof boundElement.id === 'string' && textIdsConvertedToLabels.has(boundElement.id));
|
|
173
|
-
});
|
|
174
156
|
const existingTextIds = new Set(
|
|
175
|
-
|
|
157
|
+
existing
|
|
176
158
|
.filter((boundElement) => boundElement.type === 'text' && typeof boundElement.id === 'string')
|
|
177
159
|
.map((boundElement) => boundElement.id as string),
|
|
178
160
|
);
|
|
179
|
-
const missing = [...(
|
|
180
|
-
|
|
181
|
-
if (missing.length === 0 && !label && remainingExisting.length === existing.length) return element;
|
|
161
|
+
const missing = [...boundTextIds].filter((id) => !existingTextIds.has(id));
|
|
162
|
+
if (missing.length === 0) return element;
|
|
182
163
|
|
|
183
164
|
changed = true;
|
|
184
165
|
return {
|
|
185
166
|
...element,
|
|
186
|
-
...(
|
|
187
|
-
...(remainingExisting.length > 0 || missing.length > 0
|
|
188
|
-
? { boundElements: [...remainingExisting, ...missing.map((id) => ({ type: 'text', id }))] }
|
|
189
|
-
: {}),
|
|
167
|
+
boundElements: [...existing, ...missing.map((id) => ({ type: 'text', id }))],
|
|
190
168
|
};
|
|
191
169
|
});
|
|
192
170
|
|
package/src/server/index.ts
CHANGED
|
@@ -97,7 +97,7 @@ export class PmxCanvas extends EventEmitter {
|
|
|
97
97
|
open?: boolean;
|
|
98
98
|
automationWebView?: boolean | CanvasAutomationWebViewOptions;
|
|
99
99
|
}): Promise<void> {
|
|
100
|
-
const base = startCanvasServer({ port: this._port });
|
|
100
|
+
const base = startCanvasServer({ port: this._port, allowPortFallback: false });
|
|
101
101
|
if (!base) {
|
|
102
102
|
throw new Error(`Failed to start canvas server on port ${this._port}`);
|
|
103
103
|
}
|
|
@@ -605,7 +605,7 @@ export class PmxCanvas extends EventEmitter {
|
|
|
605
605
|
async startAutomationWebView(
|
|
606
606
|
options: CanvasAutomationWebViewOptions = {},
|
|
607
607
|
): Promise<CanvasAutomationWebViewStatus> {
|
|
608
|
-
const base = this._server ?? startCanvasServer({ port: this._port });
|
|
608
|
+
const base = this._server ?? startCanvasServer({ port: this._port, allowPortFallback: false });
|
|
609
609
|
if (!base) {
|
|
610
610
|
throw new Error(`Failed to start canvas server on port ${this._port}`);
|
|
611
611
|
}
|
package/src/server/server.ts
CHANGED
|
@@ -1639,6 +1639,7 @@ async function handleCanvasBuildWebArtifact(req: Request): Promise<Response> {
|
|
|
1639
1639
|
bytes: result.fileSize,
|
|
1640
1640
|
projectPath: result.projectPath,
|
|
1641
1641
|
openedInCanvas: result.openedInCanvas,
|
|
1642
|
+
completedAt: result.completedAt,
|
|
1642
1643
|
// `id` is the canvas node id alias used by every other add-style
|
|
1643
1644
|
// response. It is only present when a canvas node was actually
|
|
1644
1645
|
// created (i.e. openInCanvas was not explicitly disabled). When
|
|
@@ -3727,6 +3728,7 @@ export interface CanvasServerOptions {
|
|
|
3727
3728
|
port?: number;
|
|
3728
3729
|
workspaceRoot?: string;
|
|
3729
3730
|
autoOpenBrowser?: boolean;
|
|
3731
|
+
allowPortFallback?: boolean;
|
|
3730
3732
|
}
|
|
3731
3733
|
|
|
3732
3734
|
export function startCanvasServer(options: CanvasServerOptions = {}): string | null {
|
|
@@ -3764,7 +3766,9 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
3764
3766
|
rotatePrimaryWorkbenchSessionIfNeeded();
|
|
3765
3767
|
|
|
3766
3768
|
const preferredPort = options.port ?? Number(process.env.PMX_WEB_CANVAS_PORT ?? DEFAULT_PORT);
|
|
3767
|
-
const portCandidates =
|
|
3769
|
+
const portCandidates = options.allowPortFallback === false
|
|
3770
|
+
? [preferredPort > 0 ? Math.floor(preferredPort) : DEFAULT_PORT]
|
|
3771
|
+
: buildPortCandidates(preferredPort);
|
|
3768
3772
|
|
|
3769
3773
|
for (const portCandidate of portCandidates) {
|
|
3770
3774
|
try {
|
|
@@ -116,12 +116,13 @@ else
|
|
|
116
116
|
echo "โ
Using Vite $VITE_VERSION (Node 18 compatible)"
|
|
117
117
|
fi
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
|
|
124
|
-
fi
|
|
119
|
+
function sed_in_place() {
|
|
120
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
121
|
+
sed -i '' "$@"
|
|
122
|
+
else
|
|
123
|
+
sed -i "$@"
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
125
126
|
|
|
126
127
|
declare -a PNPM_CMD
|
|
127
128
|
configure_pnpm
|
|
@@ -159,8 +160,8 @@ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
|
159
160
|
"
|
|
160
161
|
|
|
161
162
|
echo "๐งน Cleaning up Vite template..."
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
sed_in_place '/<link rel="icon".*/d' index.html
|
|
164
|
+
sed_in_place 's/<title>.*<\/title>/<title>'"$PROJECT_NAME"'<\/title>/' index.html
|
|
164
165
|
|
|
165
166
|
echo "๐ฆ Installing base dependencies..."
|
|
166
167
|
run_pnpm_quiet install
|
|
@@ -2,9 +2,11 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import {
|
|
3
3
|
copyFileSync,
|
|
4
4
|
existsSync,
|
|
5
|
+
readdirSync,
|
|
5
6
|
mkdirSync,
|
|
6
7
|
readFileSync,
|
|
7
8
|
statSync,
|
|
9
|
+
unlinkSync,
|
|
8
10
|
writeFileSync,
|
|
9
11
|
} from 'node:fs';
|
|
10
12
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -71,6 +73,7 @@ export interface WebArtifactCanvasBuildResult extends WebArtifactBuildOutput {
|
|
|
71
73
|
openedInCanvas: boolean;
|
|
72
74
|
nodeId?: string;
|
|
73
75
|
url?: string;
|
|
76
|
+
completedAt: string;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
function currentWorkspaceRoot(): string {
|
|
@@ -300,6 +303,14 @@ function writeProjectFiles(
|
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
function removeLiteralSedBackupFiles(projectPath: string): void {
|
|
307
|
+
for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
|
|
308
|
+
if (entry.isFile() && entry.name.endsWith("''")) {
|
|
309
|
+
unlinkSync(join(projectPath, entry.name));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
303
314
|
function ensurePackageManagerBoundary(dirPath: string): void {
|
|
304
315
|
const packageJsonPath = join(dirPath, 'package.json');
|
|
305
316
|
mkdirSync(dirPath, { recursive: true });
|
|
@@ -401,6 +412,7 @@ export async function executeWebArtifactBuild(
|
|
|
401
412
|
});
|
|
402
413
|
stdout = [stdout, initResult.stdout].filter(Boolean).join('\n');
|
|
403
414
|
stderr = [stderr, initResult.stderr].filter(Boolean).join('\n');
|
|
415
|
+
removeLiteralSedBackupFiles(projectPath);
|
|
404
416
|
}
|
|
405
417
|
|
|
406
418
|
writeProjectFiles(projectPath, input);
|
|
@@ -508,7 +520,7 @@ export async function buildWebArtifactOnCanvas(input: WebArtifactBuildInput & {
|
|
|
508
520
|
}): Promise<WebArtifactCanvasBuildResult> {
|
|
509
521
|
const build = await executeWebArtifactBuild(input);
|
|
510
522
|
if (input.openInCanvas === false) {
|
|
511
|
-
return { ...build, openedInCanvas: false };
|
|
523
|
+
return { ...build, openedInCanvas: false, completedAt: new Date().toISOString() };
|
|
512
524
|
}
|
|
513
525
|
const opened = openWebArtifactInCanvas({
|
|
514
526
|
title: input.title,
|
|
@@ -519,5 +531,6 @@ export async function buildWebArtifactOnCanvas(input: WebArtifactBuildInput & {
|
|
|
519
531
|
openedInCanvas: true,
|
|
520
532
|
nodeId: opened.nodeId,
|
|
521
533
|
url: opened.url,
|
|
534
|
+
completedAt: new Date().toISOString(),
|
|
522
535
|
};
|
|
523
536
|
}
|