fluidcad 0.0.32 → 0.0.34
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 +3 -2
- package/bin/commands/init.js +55 -0
- package/bin/commands/mcp.js +33 -0
- package/bin/commands/serve.js +77 -0
- package/bin/fluidcad.js +15 -107
- package/lib/dist/common/scene-object.d.ts +4 -1
- package/lib/dist/common/scene-object.js +9 -2
- package/lib/dist/common/solid.d.ts +4 -1
- package/lib/dist/common/solid.js +13 -0
- package/lib/dist/core/2d/tarc.d.ts +20 -2
- package/lib/dist/core/2d/tarc.js +24 -0
- package/lib/dist/core/index.d.ts +2 -1
- package/lib/dist/core/index.js +1 -0
- package/lib/dist/core/interfaces.d.ts +107 -2
- package/lib/dist/core/load.d.ts +2 -2
- package/lib/dist/core/repeat.js +62 -46
- package/lib/dist/core/rib.d.ts +18 -0
- package/lib/dist/core/rib.js +37 -0
- package/lib/dist/features/2d/arc.d.ts +8 -2
- package/lib/dist/features/2d/arc.js +94 -17
- package/lib/dist/features/2d/back.js +3 -2
- package/lib/dist/features/2d/sketch.d.ts +4 -0
- package/lib/dist/features/2d/sketch.js +21 -0
- package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
- package/lib/dist/features/2d/tarc-constrained.js +8 -0
- package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
- package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
- package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
- package/lib/dist/features/2d/tarc-to-object.js +66 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
- package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point.js +3 -0
- package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
- package/lib/dist/features/2d/tarc.d.ts +2 -0
- package/lib/dist/features/2d/tarc.js +3 -0
- package/lib/dist/features/extrude-base.d.ts +9 -0
- package/lib/dist/features/extrude-base.js +22 -0
- package/lib/dist/features/extrude-to-face.js +1 -5
- package/lib/dist/features/extrude-two-distances.js +1 -2
- package/lib/dist/features/extrude.js +1 -2
- package/lib/dist/features/load.d.ts +6 -0
- package/lib/dist/features/load.js +53 -1
- package/lib/dist/features/mirror-feature.d.ts +3 -2
- package/lib/dist/features/mirror-feature.js +1 -1
- package/lib/dist/features/repeat-circular.d.ts +3 -3
- package/lib/dist/features/repeat-circular.js +8 -1
- package/lib/dist/features/repeat-linear.d.ts +4 -2
- package/lib/dist/features/repeat-linear.js +10 -1
- package/lib/dist/features/repeat-matrix.d.ts +3 -1
- package/lib/dist/features/repeat-matrix.js +7 -2
- package/lib/dist/features/rib.d.ts +31 -0
- package/lib/dist/features/rib.js +321 -0
- package/lib/dist/features/select.d.ts +1 -0
- package/lib/dist/features/select.js +81 -10
- package/lib/dist/features/shell.d.ts +4 -1
- package/lib/dist/features/shell.js +14 -3
- package/lib/dist/filters/edge/belongs-to-face.d.ts +12 -9
- package/lib/dist/filters/edge/belongs-to-face.js +64 -15
- package/lib/dist/filters/filter-builder-base.d.ts +25 -0
- package/lib/dist/filters/filter-builder-base.js +47 -0
- package/lib/dist/filters/filter.js +39 -14
- package/lib/dist/filters/from-object.d.ts +4 -0
- package/lib/dist/filters/from-object.js +10 -0
- package/lib/dist/helpers/clone-transform.d.ts +2 -1
- package/lib/dist/helpers/scene-helpers.d.ts +1 -1
- package/lib/dist/helpers/scene-helpers.js +146 -12
- package/lib/dist/index.d.ts +7 -1
- package/lib/dist/index.js +3 -3
- package/lib/dist/io/file-import.d.ts +5 -1
- package/lib/dist/io/file-import.js +29 -18
- package/lib/dist/math/lazy-matrix.d.ts +31 -0
- package/lib/dist/math/lazy-matrix.js +66 -0
- package/lib/dist/oc/color-transfer.d.ts +19 -8
- package/lib/dist/oc/color-transfer.js +70 -12
- package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
- package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
- package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
- package/lib/dist/oc/extrude-ops.d.ts +2 -1
- package/lib/dist/oc/extrude-ops.js +51 -2
- package/lib/dist/oc/mesh.d.ts +9 -4
- package/lib/dist/oc/mesh.js +14 -13
- package/lib/dist/oc/rib-ops.d.ts +35 -0
- package/lib/dist/oc/rib-ops.js +619 -0
- package/lib/dist/oc/shell-ops.d.ts +2 -1
- package/lib/dist/oc/shell-ops.js +5 -2
- package/lib/dist/oc/topology-index.d.ts +6 -0
- package/lib/dist/oc/topology-index.js +36 -0
- package/lib/dist/rendering/mesh-builder.d.ts +3 -0
- package/lib/dist/rendering/mesh-builder.js +8 -4
- package/lib/dist/rendering/render-edge.d.ts +2 -1
- package/lib/dist/rendering/render-edge.js +2 -2
- package/lib/dist/rendering/render-face.d.ts +2 -1
- package/lib/dist/rendering/render-face.js +2 -2
- package/lib/dist/rendering/render-solid.d.ts +2 -1
- package/lib/dist/rendering/render-solid.js +3 -5
- package/lib/dist/rendering/render-wire.d.ts +2 -1
- package/lib/dist/rendering/render-wire.js +2 -2
- package/lib/dist/rendering/render.d.ts +4 -0
- package/lib/dist/rendering/render.js +50 -2
- package/lib/dist/rendering/scene-compare.js +3 -0
- package/lib/dist/rendering/scene.d.ts +1 -0
- package/lib/dist/rendering/scene.js +4 -0
- package/lib/dist/scene-manager.d.ts +4 -2
- package/lib/dist/scene-manager.js +12 -4
- package/lib/dist/tests/features/2d/arc.test.js +64 -0
- package/lib/dist/tests/features/2d/back.test.js +17 -1
- package/lib/dist/tests/features/2d/tarc.test.js +157 -0
- package/lib/dist/tests/features/color-lineage.test.js +18 -0
- package/lib/dist/tests/features/filter-positional.test.d.ts +1 -0
- package/lib/dist/tests/features/filter-positional.test.js +129 -0
- package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
- package/lib/dist/tests/features/rib.test.d.ts +1 -0
- package/lib/dist/tests/features/rib.test.js +598 -0
- package/lib/dist/tests/features/shell.test.js +36 -0
- package/lib/dist/tests/global-setup.js +2 -1
- package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
- package/lib/dist/tests/helpers/extract-blocks.js +56 -0
- package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
- package/lib/dist/tests/llm-docs-examples.test.js +62 -0
- package/lib/dist/tests/scene-compare.test.d.ts +1 -0
- package/lib/dist/tests/scene-compare.test.js +77 -0
- package/lib/dist/tests/setup.js +2 -1
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/.coverage-allowlist.txt +9 -0
- package/llm-docs/api/arc.md +48 -0
- package/llm-docs/api/axis.md +42 -0
- package/llm-docs/api/bezier.md +42 -0
- package/llm-docs/api/booleans.md +44 -0
- package/llm-docs/api/chamfer.md +40 -0
- package/llm-docs/api/circle.md +36 -0
- package/llm-docs/api/color.md +34 -0
- package/llm-docs/api/connect.md +41 -0
- package/llm-docs/api/constraint-qualifiers.md +48 -0
- package/llm-docs/api/copy.md +63 -0
- package/llm-docs/api/cursor-lines.md +50 -0
- package/llm-docs/api/cursor-move.md +61 -0
- package/llm-docs/api/cut.md +55 -0
- package/llm-docs/api/draft.md +36 -0
- package/llm-docs/api/edge-filter.md +57 -0
- package/llm-docs/api/ellipse.md +34 -0
- package/llm-docs/api/extrude.md +74 -0
- package/llm-docs/api/face-filter.md +61 -0
- package/llm-docs/api/fillet.md +51 -0
- package/llm-docs/api/index.json +139 -0
- package/llm-docs/api/line.md +42 -0
- package/llm-docs/api/load.md +37 -0
- package/llm-docs/api/local.md +38 -0
- package/llm-docs/api/loft.md +37 -0
- package/llm-docs/api/mirror.md +44 -0
- package/llm-docs/api/offset.md +36 -0
- package/llm-docs/api/part.md +40 -0
- package/llm-docs/api/plane.md +44 -0
- package/llm-docs/api/polygon.md +37 -0
- package/llm-docs/api/primitive-solids.md +39 -0
- package/llm-docs/api/project-intersect.md +48 -0
- package/llm-docs/api/rect.md +48 -0
- package/llm-docs/api/remove.md +32 -0
- package/llm-docs/api/repeat.md +79 -0
- package/llm-docs/api/revolve.md +38 -0
- package/llm-docs/api/rib.md +40 -0
- package/llm-docs/api/rotate.md +37 -0
- package/llm-docs/api/select.md +41 -0
- package/llm-docs/api/shell.md +41 -0
- package/llm-docs/api/sketch.md +76 -0
- package/llm-docs/api/slot.md +36 -0
- package/llm-docs/api/split-trim.md +42 -0
- package/llm-docs/api/sweep.md +43 -0
- package/llm-docs/api/tarc.md +45 -0
- package/llm-docs/api/tcircle.md +38 -0
- package/llm-docs/api/tline.md +42 -0
- package/llm-docs/api/translate.md +40 -0
- package/llm-docs/api/types/aline.md +35 -0
- package/llm-docs/api/types/arc-angles.md +29 -0
- package/llm-docs/api/types/arc-points.md +48 -0
- package/llm-docs/api/types/axis-like.md +38 -0
- package/llm-docs/api/types/axis.md +21 -0
- package/llm-docs/api/types/boolean-operation.md +50 -0
- package/llm-docs/api/types/circular-repeat-options.md +31 -0
- package/llm-docs/api/types/common.md +32 -0
- package/llm-docs/api/types/cut.md +125 -0
- package/llm-docs/api/types/draft.md +21 -0
- package/llm-docs/api/types/extrudable-geometry.md +23 -0
- package/llm-docs/api/types/extrude.md +194 -0
- package/llm-docs/api/types/geometry.md +51 -0
- package/llm-docs/api/types/hline.md +35 -0
- package/llm-docs/api/types/linear-repeat-options.md +31 -0
- package/llm-docs/api/types/loft.md +154 -0
- package/llm-docs/api/types/mirror.md +35 -0
- package/llm-docs/api/types/offset.md +31 -0
- package/llm-docs/api/types/plane-like.md +35 -0
- package/llm-docs/api/types/plane-transform-options.md +29 -0
- package/llm-docs/api/types/plane.md +21 -0
- package/llm-docs/api/types/point-like.md +22 -0
- package/llm-docs/api/types/point2dlike.md +26 -0
- package/llm-docs/api/types/polygon.md +46 -0
- package/llm-docs/api/types/rect.md +128 -0
- package/llm-docs/api/types/revolve.md +102 -0
- package/llm-docs/api/types/rib.md +133 -0
- package/llm-docs/api/types/scene-object.md +33 -0
- package/llm-docs/api/types/select.md +21 -0
- package/llm-docs/api/types/shell.md +54 -0
- package/llm-docs/api/types/slot.md +43 -0
- package/llm-docs/api/types/sweep.md +189 -0
- package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
- package/llm-docs/api/types/transformable.md +93 -0
- package/llm-docs/api/types/trim.md +27 -0
- package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
- package/llm-docs/api/types/vertex.md +17 -0
- package/llm-docs/api/types/vline.md +35 -0
- package/llm-docs/concepts/coordinate-system.md +45 -0
- package/llm-docs/concepts/history-and-rollback.md +40 -0
- package/llm-docs/concepts/last-selection.md +49 -0
- package/llm-docs/concepts/scene-graph.md +37 -0
- package/llm-docs/index.json +1750 -0
- package/mcp/dist/client.d.ts +64 -0
- package/mcp/dist/client.js +248 -0
- package/mcp/dist/discovery.d.ts +11 -0
- package/mcp/dist/discovery.js +78 -0
- package/mcp/dist/docs-index.d.ts +81 -0
- package/mcp/dist/docs-index.js +261 -0
- package/mcp/dist/resources.d.ts +4 -0
- package/mcp/dist/resources.js +115 -0
- package/mcp/dist/server.d.ts +12 -0
- package/mcp/dist/server.js +489 -0
- package/mcp/dist/tools/coordination.d.ts +9 -0
- package/mcp/dist/tools/coordination.js +46 -0
- package/mcp/dist/tools/docs.d.ts +66 -0
- package/mcp/dist/tools/docs.js +122 -0
- package/mcp/dist/tools/engine.d.ts +56 -0
- package/mcp/dist/tools/engine.js +145 -0
- package/mcp/dist/tools/inspection.d.ts +75 -0
- package/mcp/dist/tools/inspection.js +121 -0
- package/mcp/dist/tools/screenshot.d.ts +63 -0
- package/mcp/dist/tools/screenshot.js +263 -0
- package/mcp/dist/tools/source.d.ts +84 -0
- package/mcp/dist/tools/source.js +434 -0
- package/mcp/dist/tools/workspaces.d.ts +13 -0
- package/mcp/dist/tools/workspaces.js +33 -0
- package/mcp/dist/types.d.ts +18 -0
- package/mcp/dist/types.js +11 -0
- package/package.json +19 -5
- package/server/dist/code-editor.d.ts +36 -0
- package/server/dist/code-editor.js +8 -0
- package/server/dist/fluidcad-server.d.ts +50 -0
- package/server/dist/fluidcad-server.js +153 -1
- package/server/dist/global-registry.d.ts +30 -0
- package/server/dist/global-registry.js +126 -0
- package/server/dist/index.js +171 -26
- package/server/dist/instance-file.d.ts +31 -0
- package/server/dist/instance-file.js +73 -0
- package/server/dist/lint-fluid-js.d.ts +15 -0
- package/server/dist/lint-fluid-js.js +271 -0
- package/server/dist/routes/editor.d.ts +24 -0
- package/server/dist/routes/editor.js +44 -0
- package/server/dist/routes/export.d.ts +1 -1
- package/server/dist/routes/export.js +45 -8
- package/server/dist/routes/health.d.ts +7 -0
- package/server/dist/routes/health.js +14 -0
- package/server/dist/routes/lint.d.ts +10 -0
- package/server/dist/routes/lint.js +28 -0
- package/server/dist/routes/render.d.ts +33 -0
- package/server/dist/routes/render.js +34 -0
- package/server/dist/routes/scene.d.ts +5 -0
- package/server/dist/routes/scene.js +48 -0
- package/server/dist/routes/screenshot.js +68 -1
- package/server/dist/ws-protocol.d.ts +56 -2
- package/ui/dist/assets/{index-DMw0OYCF.js → index-BdqrMDRu.js} +30 -30
- package/ui/dist/index.html +1 -1
package/server/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import http from 'http';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import express from 'express';
|
|
@@ -9,11 +10,34 @@ import { createActionsRouter } from "./routes/actions.js";
|
|
|
9
10
|
import { createExportRouter } from "./routes/export.js";
|
|
10
11
|
import { createScreenshotRouter } from "./routes/screenshot.js";
|
|
11
12
|
import { createPreferencesRouter } from "./routes/preferences.js";
|
|
13
|
+
import { createHealthRouter } from "./routes/health.js";
|
|
14
|
+
import { createSceneRouter } from "./routes/scene.js";
|
|
15
|
+
import { createEditorRouter, DirtyBufferState } from "./routes/editor.js";
|
|
16
|
+
import { createRenderRouter } from "./routes/render.js";
|
|
17
|
+
import { createLintRouter } from "./routes/lint.js";
|
|
12
18
|
import { normalizePath } from "./normalize-path.js";
|
|
19
|
+
import { writeInstanceFile, deleteInstanceFile } from "./instance-file.js";
|
|
20
|
+
import { addInstance, removeInstance } from "./global-registry.js";
|
|
13
21
|
import { extractSourceLocation } from '../../lib/dist/index.js';
|
|
14
22
|
const PORT = parseInt(process.env.FLUIDCAD_SERVER_PORT || '3100', 10);
|
|
15
23
|
const WORKSPACE_PATH = normalizePath(process.env.FLUIDCAD_WORKSPACE_PATH || '');
|
|
16
24
|
const UI_DIST = path.resolve(import.meta.dirname, '../../ui/dist');
|
|
25
|
+
function readPackageVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const pkgPath = path.resolve(import.meta.dirname, '../../package.json');
|
|
28
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (typeof parsed.version === 'string') {
|
|
31
|
+
return parsed.version;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Fall through to unknown.
|
|
36
|
+
}
|
|
37
|
+
return '0.0.0';
|
|
38
|
+
}
|
|
39
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
40
|
+
const STARTED_AT = new Date().toISOString();
|
|
17
41
|
// ---------------------------------------------------------------------------
|
|
18
42
|
// IPC helpers — communication with extension host process
|
|
19
43
|
// ---------------------------------------------------------------------------
|
|
@@ -26,13 +50,23 @@ function sendToExtension(msg) {
|
|
|
26
50
|
// Express app
|
|
27
51
|
// ---------------------------------------------------------------------------
|
|
28
52
|
const fluidCadServer = new FluidCadServer();
|
|
53
|
+
const dirtyBufferState = new DirtyBufferState();
|
|
29
54
|
const app = express();
|
|
30
55
|
app.use(express.json({ limit: '50mb' }));
|
|
56
|
+
app.use('/api', createHealthRouter({
|
|
57
|
+
version: PACKAGE_VERSION,
|
|
58
|
+
workspacePath: WORKSPACE_PATH,
|
|
59
|
+
startedAt: STARTED_AT,
|
|
60
|
+
}));
|
|
31
61
|
app.use('/api', createPropertiesRouter(fluidCadServer));
|
|
32
62
|
app.use('/api', createActionsRouter(fluidCadServer, sendToExtension, broadcastToUI, WORKSPACE_PATH));
|
|
33
|
-
app.use('/api', createExportRouter(fluidCadServer));
|
|
63
|
+
app.use('/api', createExportRouter(fluidCadServer, WORKSPACE_PATH));
|
|
34
64
|
app.use('/api', createScreenshotRouter(requestScreenshot));
|
|
35
65
|
app.use('/api', createPreferencesRouter());
|
|
66
|
+
app.use('/api', createSceneRouter(fluidCadServer, () => lastCameraState));
|
|
67
|
+
app.use('/api', createEditorRouter(dirtyBufferState));
|
|
68
|
+
app.use('/api', createRenderRouter((fileName, code) => runLiveRender(fileName, code)));
|
|
69
|
+
app.use('/api', createLintRouter());
|
|
36
70
|
// Static files — serve UI build, with SPA fallback
|
|
37
71
|
app.use(express.static(UI_DIST, {
|
|
38
72
|
setHeaders(res, filePath) {
|
|
@@ -53,6 +87,7 @@ const wss = new WebSocketServer({ server: httpServer });
|
|
|
53
87
|
const uiClients = new Set();
|
|
54
88
|
let lastSceneMessage = null;
|
|
55
89
|
let initCompleteMessage = null;
|
|
90
|
+
let lastCameraState = null;
|
|
56
91
|
function broadcastToUI(msg) {
|
|
57
92
|
const data = JSON.stringify(msg);
|
|
58
93
|
if (msg.type === 'scene-rendered') {
|
|
@@ -117,6 +152,21 @@ function handleUIMessage(raw) {
|
|
|
117
152
|
else {
|
|
118
153
|
pending.reject(new Error(msg.error || 'Screenshot failed.'));
|
|
119
154
|
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (msg.type === 'camera-state') {
|
|
158
|
+
// Trust UI structure — we own both ends. Just shape-check arrays.
|
|
159
|
+
if (Array.isArray(msg.position) && msg.position.length === 3 &&
|
|
160
|
+
Array.isArray(msg.target) && msg.target.length === 3 &&
|
|
161
|
+
Array.isArray(msg.up) && msg.up.length === 3) {
|
|
162
|
+
lastCameraState = {
|
|
163
|
+
type: 'camera-state',
|
|
164
|
+
position: msg.position,
|
|
165
|
+
target: msg.target,
|
|
166
|
+
up: msg.up,
|
|
167
|
+
projection: msg.projection === 'perspective' ? 'perspective' : 'orthographic',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
172
|
// ---------------------------------------------------------------------------
|
|
@@ -144,8 +194,9 @@ wss.on('connection', (ws) => {
|
|
|
144
194
|
let currentFile = null;
|
|
145
195
|
let renderVersion = 0;
|
|
146
196
|
const lastSceneByFile = new Map();
|
|
147
|
-
function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
|
|
197
|
+
function emitSuccess(version, absPath, result, rollbackStop, breakpointHit) {
|
|
148
198
|
lastSceneByFile.set(absPath, { result, rollbackStop });
|
|
199
|
+
fluidCadServer.setCompileError(null);
|
|
149
200
|
sendToExtension({
|
|
150
201
|
type: 'scene-rendered',
|
|
151
202
|
absPath,
|
|
@@ -159,6 +210,7 @@ function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
|
|
|
159
210
|
rollbackStop,
|
|
160
211
|
breakpointHit,
|
|
161
212
|
});
|
|
213
|
+
broadcastToUI({ type: 'render-version', version, state: 'end', absPath });
|
|
162
214
|
}
|
|
163
215
|
function buildCompileError(filePath, err) {
|
|
164
216
|
const message = err?.message || String(err);
|
|
@@ -178,12 +230,13 @@ function buildCompileError(filePath, err) {
|
|
|
178
230
|
sourceLocation: sourceLocation ?? undefined,
|
|
179
231
|
};
|
|
180
232
|
}
|
|
181
|
-
function emitCompileError(filePath, err) {
|
|
233
|
+
function emitCompileError(version, filePath, err) {
|
|
182
234
|
const compileError = buildCompileError(filePath, err);
|
|
183
235
|
const key = compileError.filePath ?? normalizePath(filePath).replace('virtual:live-render:', '');
|
|
184
236
|
const prev = lastSceneByFile.get(key);
|
|
185
237
|
const result = prev?.result ?? [];
|
|
186
238
|
const rollbackStop = prev?.rollbackStop ?? -1;
|
|
239
|
+
fluidCadServer.setCompileError(compileError);
|
|
187
240
|
sendToExtension({
|
|
188
241
|
type: 'scene-rendered',
|
|
189
242
|
absPath: key,
|
|
@@ -198,12 +251,59 @@ function emitCompileError(filePath, err) {
|
|
|
198
251
|
rollbackStop,
|
|
199
252
|
compileError,
|
|
200
253
|
});
|
|
254
|
+
broadcastToUI({ type: 'render-version', version, state: 'error', absPath: key });
|
|
255
|
+
return compileError;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Render-orchestration chokepoint shared by the IPC `live-update` handler and
|
|
259
|
+
* the HTTP `/api/render` route. Bumps `renderVersion`, broadcasts the
|
|
260
|
+
* lifecycle pings, runs the dedupable `updateLiveCode`, and emits success /
|
|
261
|
+
* compile-error to the UI + extension. Returns a structured outcome so the
|
|
262
|
+
* HTTP caller (MCP) can hand it straight to the agent.
|
|
263
|
+
*/
|
|
264
|
+
async function runLiveRender(fileName, code) {
|
|
265
|
+
const startedAt = Date.now();
|
|
266
|
+
const myVersion = ++renderVersion;
|
|
267
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
268
|
+
if (fileName !== currentFile) {
|
|
269
|
+
broadcastToUI({ type: 'processing-file' });
|
|
270
|
+
currentFile = fileName;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const data = await fluidCadServer.updateLiveCode(fileName, code);
|
|
274
|
+
if (myVersion !== renderVersion) {
|
|
275
|
+
return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
|
|
276
|
+
}
|
|
277
|
+
if (!data) {
|
|
278
|
+
return { state: 'no-scene-manager', version: myVersion, durationMs: Date.now() - startedAt };
|
|
279
|
+
}
|
|
280
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit);
|
|
281
|
+
return {
|
|
282
|
+
state: 'rendered',
|
|
283
|
+
version: myVersion,
|
|
284
|
+
absPath: data.absPath,
|
|
285
|
+
durationMs: Date.now() - startedAt,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
if (myVersion !== renderVersion) {
|
|
290
|
+
return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
|
|
291
|
+
}
|
|
292
|
+
const compileError = emitCompileError(myVersion, fileName, err);
|
|
293
|
+
return {
|
|
294
|
+
state: 'compile-error',
|
|
295
|
+
version: myVersion,
|
|
296
|
+
durationMs: Date.now() - startedAt,
|
|
297
|
+
compileError,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
201
300
|
}
|
|
202
301
|
async function handleExtensionMessage(msg) {
|
|
203
302
|
try {
|
|
204
303
|
switch (msg.type) {
|
|
205
304
|
case 'process-file': {
|
|
206
305
|
const myVersion = ++renderVersion;
|
|
306
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
207
307
|
broadcastToUI({ type: 'processing-file' });
|
|
208
308
|
currentFile = msg.filePath;
|
|
209
309
|
try {
|
|
@@ -212,48 +312,30 @@ async function handleExtensionMessage(msg) {
|
|
|
212
312
|
return;
|
|
213
313
|
}
|
|
214
314
|
if (data) {
|
|
215
|
-
emitSuccess(data.absPath, data.result, data.rollbackStop, data.breakpointHit);
|
|
315
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit);
|
|
216
316
|
}
|
|
217
317
|
}
|
|
218
318
|
catch (err) {
|
|
219
319
|
if (myVersion !== renderVersion) {
|
|
220
320
|
return;
|
|
221
321
|
}
|
|
222
|
-
emitCompileError(msg.filePath, err);
|
|
322
|
+
emitCompileError(myVersion, msg.filePath, err);
|
|
223
323
|
}
|
|
224
324
|
break;
|
|
225
325
|
}
|
|
226
326
|
case 'live-update': {
|
|
227
|
-
|
|
228
|
-
if (msg.fileName !== currentFile) {
|
|
229
|
-
broadcastToUI({ type: 'processing-file' });
|
|
230
|
-
currentFile = msg.fileName;
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
const data = await fluidCadServer.updateLiveCode(msg.fileName, msg.code);
|
|
234
|
-
if (myVersion !== renderVersion) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (data) {
|
|
238
|
-
emitSuccess(data.absPath, data.result, data.rollbackStop, data.breakpointHit);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
if (myVersion !== renderVersion) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
emitCompileError(msg.fileName, err);
|
|
246
|
-
}
|
|
327
|
+
await runLiveRender(msg.fileName, msg.code);
|
|
247
328
|
break;
|
|
248
329
|
}
|
|
249
330
|
case 'rollback': {
|
|
250
331
|
const myVersion = ++renderVersion;
|
|
332
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
251
333
|
const data = await fluidCadServer.rollback(msg.fileName, msg.index);
|
|
252
334
|
if (myVersion !== renderVersion) {
|
|
253
335
|
return;
|
|
254
336
|
}
|
|
255
337
|
if (data) {
|
|
256
|
-
emitSuccess(data.absPath, data.result, data.rollbackStop);
|
|
338
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop);
|
|
257
339
|
}
|
|
258
340
|
break;
|
|
259
341
|
}
|
|
@@ -279,6 +361,13 @@ async function handleExtensionMessage(msg) {
|
|
|
279
361
|
broadcastToUI({ type: 'show-shape-properties', shapeId: msg.shapeId });
|
|
280
362
|
break;
|
|
281
363
|
}
|
|
364
|
+
case 'editor-dirty-state': {
|
|
365
|
+
if (Array.isArray(msg.dirtyFiles)) {
|
|
366
|
+
const paths = msg.dirtyFiles.filter((p) => typeof p === 'string');
|
|
367
|
+
dirtyBufferState.setDirtyFiles(paths);
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
282
371
|
case 'export-scene': {
|
|
283
372
|
try {
|
|
284
373
|
const result = fluidCadServer.exportShapes(msg.shapeIds, msg.options);
|
|
@@ -321,6 +410,36 @@ process.on('message', (msg) => {
|
|
|
321
410
|
httpServer.listen(PORT, () => {
|
|
322
411
|
const url = `http://localhost:${PORT}`;
|
|
323
412
|
console.log(`FluidCAD server listening on ${url}`);
|
|
413
|
+
// Publish this instance so a standalone MCP process can discover us.
|
|
414
|
+
// Discovery is best-effort: an MCP-less workflow must keep working even if
|
|
415
|
+
// we can't write the file (read-only FS, permissions, …).
|
|
416
|
+
if (WORKSPACE_PATH) {
|
|
417
|
+
try {
|
|
418
|
+
writeInstanceFile({
|
|
419
|
+
schemaVersion: 1,
|
|
420
|
+
port: PORT,
|
|
421
|
+
pid: process.pid,
|
|
422
|
+
workspacePath: WORKSPACE_PATH,
|
|
423
|
+
version: PACKAGE_VERSION,
|
|
424
|
+
startedAt: STARTED_AT,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
console.warn(`Failed to write instance file: ${err?.message ?? err}`);
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
addInstance({
|
|
432
|
+
workspacePath: WORKSPACE_PATH,
|
|
433
|
+
port: PORT,
|
|
434
|
+
pid: process.pid,
|
|
435
|
+
version: PACKAGE_VERSION,
|
|
436
|
+
startedAt: STARTED_AT,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
console.warn(`Failed to update global registry: ${err?.message ?? err}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
324
443
|
// Signal ready immediately so extension can show the webview
|
|
325
444
|
sendToExtension({ type: 'ready', port: PORT, url });
|
|
326
445
|
// Initialize FluidCAD server in the background
|
|
@@ -333,3 +452,29 @@ httpServer.listen(PORT, () => {
|
|
|
333
452
|
broadcastToUI({ type: 'init-complete', success: false, error });
|
|
334
453
|
});
|
|
335
454
|
});
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
// Shutdown — clean up the instance file and registry entry
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
let cleanedUp = false;
|
|
459
|
+
function cleanupDiscovery() {
|
|
460
|
+
if (cleanedUp || !WORKSPACE_PATH) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
cleanedUp = true;
|
|
464
|
+
deleteInstanceFile(WORKSPACE_PATH, process.pid);
|
|
465
|
+
try {
|
|
466
|
+
removeInstance(WORKSPACE_PATH, process.pid);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Registry cleanup is best-effort; stale entries are pruned by readers.
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
process.on('exit', cleanupDiscovery);
|
|
473
|
+
process.on('SIGINT', () => {
|
|
474
|
+
cleanupDiscovery();
|
|
475
|
+
process.exit(0);
|
|
476
|
+
});
|
|
477
|
+
process.on('SIGTERM', () => {
|
|
478
|
+
cleanupDiscovery();
|
|
479
|
+
process.exit(0);
|
|
480
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const INSTANCE_DIR_NAME = ".fluidcad";
|
|
2
|
+
export declare const INSTANCE_FILE_NAME = "instance.json";
|
|
3
|
+
export type InstanceFile = {
|
|
4
|
+
/** Schema version; bump when changing the shape. */
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
/** Port the HTTP+WS server is listening on. */
|
|
7
|
+
port: number;
|
|
8
|
+
/** PID of the server process — used by MCP for liveness probes. */
|
|
9
|
+
pid: number;
|
|
10
|
+
/** Absolute path to the workspace this instance serves. */
|
|
11
|
+
workspacePath: string;
|
|
12
|
+
/** FluidCAD package version. */
|
|
13
|
+
version: string;
|
|
14
|
+
/** ISO 8601 timestamp captured when the server began listening. */
|
|
15
|
+
startedAt: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function instanceFilePath(workspacePath: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Atomically write the instance file. Uses a PID-suffixed tmp file plus
|
|
20
|
+
* `renameSync` so a concurrent reader never observes a half-written file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function writeInstanceFile(entry: InstanceFile): void;
|
|
23
|
+
/**
|
|
24
|
+
* Best-effort delete; never throws. Called from SIGINT/SIGTERM and `exit`
|
|
25
|
+
* handlers, where we cannot afford to interrupt shutdown over an I/O error.
|
|
26
|
+
*
|
|
27
|
+
* `expectedPid` guards against deleting another process's file on race —
|
|
28
|
+
* e.g., a second window started for the same workspace after a crash.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deleteInstanceFile(workspacePath: string, expectedPid: number): void;
|
|
31
|
+
export declare function readInstanceFile(workspacePath: string): InstanceFile | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Per-workspace instance discovery file at <workspace>/.fluidcad/instance.json.
|
|
2
|
+
//
|
|
3
|
+
// Written when the HTTP server is listening, deleted on graceful shutdown. A
|
|
4
|
+
// standalone MCP process reads this file to learn the workspace's random port
|
|
5
|
+
// without scanning. The on-disk format is intentionally small and stable.
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
export const INSTANCE_DIR_NAME = '.fluidcad';
|
|
9
|
+
export const INSTANCE_FILE_NAME = 'instance.json';
|
|
10
|
+
function instanceDir(workspacePath) {
|
|
11
|
+
return path.join(workspacePath, INSTANCE_DIR_NAME);
|
|
12
|
+
}
|
|
13
|
+
export function instanceFilePath(workspacePath) {
|
|
14
|
+
return path.join(instanceDir(workspacePath), INSTANCE_FILE_NAME);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Atomically write the instance file. Uses a PID-suffixed tmp file plus
|
|
18
|
+
* `renameSync` so a concurrent reader never observes a half-written file.
|
|
19
|
+
*/
|
|
20
|
+
export function writeInstanceFile(entry) {
|
|
21
|
+
const dir = instanceDir(entry.workspacePath);
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
const destination = path.join(dir, INSTANCE_FILE_NAME);
|
|
24
|
+
const tmp = path.join(dir, `${INSTANCE_FILE_NAME}.${entry.pid}.tmp`);
|
|
25
|
+
const payload = JSON.stringify(entry, null, 2) + '\n';
|
|
26
|
+
fs.writeFileSync(tmp, payload, { encoding: 'utf8', mode: 0o644 });
|
|
27
|
+
fs.renameSync(tmp, destination);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Best-effort delete; never throws. Called from SIGINT/SIGTERM and `exit`
|
|
31
|
+
* handlers, where we cannot afford to interrupt shutdown over an I/O error.
|
|
32
|
+
*
|
|
33
|
+
* `expectedPid` guards against deleting another process's file on race —
|
|
34
|
+
* e.g., a second window started for the same workspace after a crash.
|
|
35
|
+
*/
|
|
36
|
+
export function deleteInstanceFile(workspacePath, expectedPid) {
|
|
37
|
+
const file = instanceFilePath(workspacePath);
|
|
38
|
+
try {
|
|
39
|
+
const existing = readInstanceFile(workspacePath);
|
|
40
|
+
if (existing && existing.pid !== expectedPid) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
fs.unlinkSync(file);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// File missing, permissions, partial write — nothing useful to do during shutdown.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function readInstanceFile(workspacePath) {
|
|
50
|
+
try {
|
|
51
|
+
const raw = fs.readFileSync(instanceFilePath(workspacePath), 'utf8');
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
if (!isInstanceFile(parsed)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isInstanceFile(value) {
|
|
63
|
+
if (typeof value !== 'object' || value === null) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const v = value;
|
|
67
|
+
return (v.schemaVersion === 1 &&
|
|
68
|
+
typeof v.port === 'number' &&
|
|
69
|
+
typeof v.pid === 'number' &&
|
|
70
|
+
typeof v.workspacePath === 'string' &&
|
|
71
|
+
typeof v.version === 'string' &&
|
|
72
|
+
typeof v.startedAt === 'string');
|
|
73
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type MissingImport = {
|
|
2
|
+
symbol: string;
|
|
3
|
+
module: string;
|
|
4
|
+
/** Zero-based line where the unbound use first appears. */
|
|
5
|
+
line: number;
|
|
6
|
+
/** Zero-based UTF-16 column where the unbound use first appears. */
|
|
7
|
+
column: number;
|
|
8
|
+
};
|
|
9
|
+
export type LintFluidJsResult = {
|
|
10
|
+
/** Free-variable usages of known symbols that lack an import. */
|
|
11
|
+
missing: MissingImport[];
|
|
12
|
+
/** A copy-pasteable block of suggested import statements, grouped by module. */
|
|
13
|
+
suggestion: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function lintFluidJs(code: string): Promise<LintFluidJsResult>;
|