fluidcad 0.0.33 → 0.0.35
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 +72 -2
- package/bin/commands/init.js +55 -0
- package/bin/commands/login.js +120 -0
- package/bin/commands/mcp.js +33 -0
- package/bin/commands/pack.js +49 -0
- package/bin/commands/publish.js +136 -0
- package/bin/commands/serve.js +77 -0
- package/bin/fluidcad.js +21 -107
- package/bin/lib/api-client.js +40 -0
- package/bin/lib/browser.js +16 -0
- package/bin/lib/config.js +39 -0
- package/bin/lib/model-config.js +38 -0
- package/bin/lib/workspace.js +57 -0
- package/lib/dist/common/scene-object.d.ts +2 -1
- package/lib/dist/common/scene-object.js +3 -2
- package/lib/dist/common/shape-factory.d.ts +2 -1
- package/lib/dist/common/shape-factory.js +4 -0
- package/lib/dist/common/transformable-primitive.d.ts +6 -5
- package/lib/dist/common/transformable-primitive.js +8 -7
- package/lib/dist/common/vertex.js +0 -1
- package/lib/dist/core/2d/aline.d.ts +4 -3
- package/lib/dist/core/2d/aline.js +3 -2
- package/lib/dist/core/2d/arc.d.ts +3 -2
- package/lib/dist/core/2d/arc.js +4 -3
- package/lib/dist/core/2d/bezier.d.ts +8 -6
- package/lib/dist/core/2d/circle.d.ts +4 -3
- package/lib/dist/core/2d/circle.js +3 -2
- package/lib/dist/core/2d/ellipse.d.ts +5 -4
- package/lib/dist/core/2d/ellipse.js +5 -4
- package/lib/dist/core/2d/hline.d.ts +4 -3
- package/lib/dist/core/2d/hline.js +5 -3
- package/lib/dist/core/2d/line.js +1 -0
- package/lib/dist/core/2d/offset.d.ts +3 -2
- package/lib/dist/core/2d/offset.js +6 -5
- package/lib/dist/core/2d/polygon.d.ts +5 -4
- package/lib/dist/core/2d/polygon.js +10 -9
- package/lib/dist/core/2d/rect.d.ts +4 -3
- package/lib/dist/core/2d/rect.js +10 -9
- package/lib/dist/core/2d/slot.d.ts +14 -6
- package/lib/dist/core/2d/slot.js +19 -8
- package/lib/dist/core/2d/tarc.d.ts +20 -2
- package/lib/dist/core/2d/tarc.js +24 -0
- package/lib/dist/core/2d/vline.d.ts +4 -3
- package/lib/dist/core/2d/vline.js +5 -3
- package/lib/dist/core/chamfer.d.ts +5 -4
- package/lib/dist/core/chamfer.js +7 -6
- package/lib/dist/core/color.d.ts +3 -2
- package/lib/dist/core/color.js +2 -1
- package/lib/dist/core/cut.d.ts +4 -3
- package/lib/dist/core/cut.js +5 -4
- package/lib/dist/core/cylinder.d.ts +2 -1
- package/lib/dist/core/cylinder.js +2 -1
- package/lib/dist/core/draft.d.ts +3 -2
- package/lib/dist/core/draft.js +3 -2
- package/lib/dist/core/extrude.d.ts +4 -3
- package/lib/dist/core/extrude.js +5 -4
- package/lib/dist/core/fillet.d.ts +5 -4
- package/lib/dist/core/fillet.js +6 -5
- package/lib/dist/core/index.d.ts +1 -0
- package/lib/dist/core/index.js +1 -0
- package/lib/dist/core/interfaces.d.ts +55 -25
- package/lib/dist/core/param.d.ts +74 -0
- package/lib/dist/core/param.js +147 -0
- package/lib/dist/core/repeat.d.ts +2 -1
- package/lib/dist/core/repeat.js +72 -54
- package/lib/dist/core/revolve.d.ts +2 -1
- package/lib/dist/core/revolve.js +3 -2
- package/lib/dist/core/rib.d.ts +3 -2
- package/lib/dist/core/rib.js +6 -2
- package/lib/dist/core/rotate.d.ts +5 -4
- package/lib/dist/core/rotate.js +4 -3
- package/lib/dist/core/shell.d.ts +3 -2
- package/lib/dist/core/shell.js +3 -2
- package/lib/dist/core/sphere.d.ts +3 -2
- package/lib/dist/core/sphere.js +2 -1
- package/lib/dist/core/translate.d.ts +7 -6
- package/lib/dist/core/translate.js +6 -5
- 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/bezier.js +16 -16
- package/lib/dist/features/2d/circle.js +4 -0
- package/lib/dist/features/2d/ellipse.js +4 -0
- package/lib/dist/features/2d/hline.d.ts +3 -0
- package/lib/dist/features/2d/hline.js +9 -2
- package/lib/dist/features/2d/line.d.ts +3 -0
- package/lib/dist/features/2d/line.js +11 -3
- package/lib/dist/features/2d/sketch.d.ts +4 -0
- package/lib/dist/features/2d/sketch.js +25 -0
- package/lib/dist/features/2d/slot.d.ts +5 -0
- package/lib/dist/features/2d/slot.js +52 -7
- 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 +6 -0
- package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point.js +6 -0
- package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +6 -0
- package/lib/dist/features/2d/tarc.d.ts +2 -0
- package/lib/dist/features/2d/tarc.js +6 -0
- package/lib/dist/features/2d/vline.d.ts +3 -0
- package/lib/dist/features/2d/vline.js +9 -2
- package/lib/dist/features/copy-circular.d.ts +4 -3
- package/lib/dist/features/copy-circular.js +16 -9
- package/lib/dist/features/copy-circular2d.js +16 -9
- package/lib/dist/features/copy-linear.d.ts +4 -3
- package/lib/dist/features/copy-linear.js +18 -12
- package/lib/dist/features/copy-linear2d.js +18 -12
- package/lib/dist/features/extrude-base.d.ts +13 -3
- package/lib/dist/features/extrude-base.js +32 -3
- 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/mirror-feature.d.ts +3 -2
- package/lib/dist/features/mirror-feature.js +1 -1
- package/lib/dist/features/mirror-shape2d.js +2 -2
- package/lib/dist/features/repeat-base.d.ts +13 -0
- package/lib/dist/features/repeat-base.js +21 -0
- package/lib/dist/features/repeat-circular.d.ts +8 -7
- package/lib/dist/features/repeat-circular.js +7 -3
- package/lib/dist/features/repeat-linear.d.ts +9 -7
- package/lib/dist/features/repeat-linear.js +9 -3
- package/lib/dist/features/repeat-matrix.d.ts +3 -1
- package/lib/dist/features/repeat-matrix.js +7 -2
- package/lib/dist/features/shell.d.ts +4 -1
- package/lib/dist/features/shell.js +14 -3
- package/lib/dist/helpers/clone-transform.d.ts +2 -1
- package/lib/dist/index.d.ts +12 -1
- package/lib/dist/index.js +11 -4
- package/lib/dist/io/file-import.d.ts +7 -0
- package/lib/dist/io/file-import.js +30 -10
- package/lib/dist/math/lazy-matrix.d.ts +36 -0
- package/lib/dist/math/lazy-matrix.js +134 -0
- package/lib/dist/oc/boolean-ops.d.ts +2 -2
- 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/mesh.d.ts +9 -4
- package/lib/dist/oc/mesh.js +14 -13
- package/lib/dist/oc/shell-ops.d.ts +2 -1
- package/lib/dist/oc/shell-ops.js +5 -2
- package/lib/dist/param-registry.d.ts +34 -0
- package/lib/dist/param-registry.js +60 -0
- package/lib/dist/rendering/mesh-builder.d.ts +3 -0
- package/lib/dist/rendering/mesh-builder.js +10 -5
- 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 +2 -2
- 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 +3 -0
- package/lib/dist/rendering/render.js +7 -2
- 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/copy-circular.test.js +1 -1
- package/lib/dist/tests/features/copy-linear.test.js +10 -10
- package/lib/dist/tests/features/repeat-user-repro-cache.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-user-repro-cache.test.js +97 -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/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/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 +41 -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 +65 -0
- package/mcp/dist/client.js +255 -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 +502 -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 +72 -0
- package/mcp/dist/tools/engine.js +190 -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 +27 -7
- package/server/dist/api.d.ts +37 -0
- package/server/dist/api.js +44 -0
- package/server/dist/code-editor.d.ts +100 -0
- package/server/dist/code-editor.js +528 -2
- package/server/dist/fluidcad-server.d.ts +118 -1
- package/server/dist/fluidcad-server.js +350 -62
- package/server/dist/global-registry.d.ts +30 -0
- package/server/dist/global-registry.js +126 -0
- package/server/dist/host/blocked-imports.d.ts +8 -0
- package/server/dist/host/blocked-imports.js +30 -0
- package/server/dist/{vite-manager.d.ts → host/local-scene-host.d.ts} +3 -1
- package/server/dist/{vite-manager.js → host/local-scene-host.js} +6 -26
- package/server/dist/host/scene-host.d.ts +19 -0
- package/server/dist/host/scene-host.js +1 -0
- package/server/dist/index.js +175 -123
- 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/model-package/capture-params.d.ts +19 -0
- package/server/dist/model-package/capture-params.js +42 -0
- package/server/dist/model-package/pack.d.ts +23 -0
- package/server/dist/model-package/pack.js +229 -0
- package/server/dist/model-package/types.d.ts +78 -0
- package/server/dist/model-package/types.js +17 -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/hit-test.d.ts +3 -0
- package/server/dist/routes/hit-test.js +17 -0
- package/server/dist/routes/lint.d.ts +10 -0
- package/server/dist/routes/lint.js +28 -0
- package/server/dist/routes/pack.d.ts +10 -0
- package/server/dist/routes/pack.js +47 -0
- package/server/dist/routes/params.d.ts +3 -0
- package/server/dist/routes/params.js +75 -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/routes/sketch-edits.d.ts +3 -0
- package/server/dist/routes/sketch-edits.js +542 -0
- package/server/dist/routes/timeline.d.ts +3 -0
- package/server/dist/routes/timeline.js +49 -0
- package/server/dist/server-core.d.ts +53 -0
- package/server/dist/server-core.js +147 -0
- package/server/dist/ws-protocol.d.ts +156 -3
- package/ui/dist/assets/index-CDJmUpFI.css +2 -0
- package/ui/dist/assets/index-MRqwG9Vh.js +5417 -0
- package/ui/dist/index.html +2 -2
- package/server/dist/routes/actions.d.ts +0 -3
- package/server/dist/routes/actions.js +0 -309
- package/ui/dist/assets/index-CFi9p7wR.js +0 -4946
- package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
|
@@ -1,30 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'vite';
|
|
2
2
|
import { dirname, resolve, isAbsolute } from 'path';
|
|
3
|
-
import { normalizePath } from "
|
|
4
|
-
|
|
5
|
-
'fs',
|
|
6
|
-
'child_process',
|
|
7
|
-
'net',
|
|
8
|
-
'dgram',
|
|
9
|
-
'tls',
|
|
10
|
-
'http',
|
|
11
|
-
'https',
|
|
12
|
-
'http2',
|
|
13
|
-
'os',
|
|
14
|
-
'worker_threads',
|
|
15
|
-
'vm',
|
|
16
|
-
'cluster',
|
|
17
|
-
'dns',
|
|
18
|
-
'module',
|
|
19
|
-
]);
|
|
20
|
-
function getBlockedNodeModule(id) {
|
|
21
|
-
let name = id;
|
|
22
|
-
if (name.startsWith('node:')) {
|
|
23
|
-
name = name.slice(5);
|
|
24
|
-
}
|
|
25
|
-
const baseName = name.split('/')[0];
|
|
26
|
-
return BLOCKED_NODE_MODULES.has(baseName) ? baseName : null;
|
|
27
|
-
}
|
|
3
|
+
import { normalizePath } from "../normalize-path.js";
|
|
4
|
+
import { getBlockedNodeModule } from "./blocked-imports.js";
|
|
28
5
|
const IMPORT_PATTERN = /\b(?:import|export)\s[\s\S]*?from\s+['"]([^'"]+)['"]|\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
29
6
|
function scanForBlockedImports(code) {
|
|
30
7
|
let match;
|
|
@@ -38,7 +15,7 @@ function scanForBlockedImports(code) {
|
|
|
38
15
|
}
|
|
39
16
|
return null;
|
|
40
17
|
}
|
|
41
|
-
export class
|
|
18
|
+
export class LocalSceneHost {
|
|
42
19
|
server;
|
|
43
20
|
rootPath = '';
|
|
44
21
|
buffers = new Map();
|
|
@@ -102,6 +79,9 @@ export class ViteManager {
|
|
|
102
79
|
setBuffer(id, code) {
|
|
103
80
|
this.buffers.set(id, code);
|
|
104
81
|
}
|
|
82
|
+
getBuffer(fileName) {
|
|
83
|
+
return this.buffers.get(`virtual:live-render:${fileName}`) ?? null;
|
|
84
|
+
}
|
|
105
85
|
async loadModule(filePath) {
|
|
106
86
|
const mod = await this.server.ssrLoadModule(filePath);
|
|
107
87
|
for (const value of Object.values(mod)) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-of-truth + execution seam for a workspace's scene code.
|
|
3
|
+
*
|
|
4
|
+
* `FluidCadServer` owns the engine pipeline (param registry, scene cache,
|
|
5
|
+
* render orchestration) and does not care where `.fluid.js` source comes
|
|
6
|
+
* from or how it gets turned into a runnable module. That responsibility
|
|
7
|
+
* lives behind this interface.
|
|
8
|
+
*
|
|
9
|
+
* Implementations:
|
|
10
|
+
* - LocalSceneHost: Vite SSR over the workspace directory (desktop).
|
|
11
|
+
* - HubSceneHost (Phase 2): in-memory consumer of a packed model bundle.
|
|
12
|
+
*/
|
|
13
|
+
export interface SceneHost {
|
|
14
|
+
init(workspacePath: string): Promise<void>;
|
|
15
|
+
loadModule(filePath: string): Promise<Record<string, any>>;
|
|
16
|
+
setBuffer(id: string, code: string): void;
|
|
17
|
+
getBuffer(fileName: string): string | null;
|
|
18
|
+
invalidateModule(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/server/dist/index.js
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
2
|
import http from 'http';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import express from 'express';
|
|
5
|
-
import { WebSocketServer, WebSocket } from 'ws';
|
|
6
5
|
import { FluidCadServer } from "./fluidcad-server.js";
|
|
6
|
+
import { createServerCore } from "./server-core.js";
|
|
7
7
|
import { createPropertiesRouter } from "./routes/properties.js";
|
|
8
|
-
import {
|
|
8
|
+
import { createParamsRouter } from "./routes/params.js";
|
|
9
|
+
import { createHitTestRouter } from "./routes/hit-test.js";
|
|
10
|
+
import { createTimelineRouter } from "./routes/timeline.js";
|
|
11
|
+
import { createSketchEditsRouter } from "./routes/sketch-edits.js";
|
|
9
12
|
import { createExportRouter } from "./routes/export.js";
|
|
10
13
|
import { createScreenshotRouter } from "./routes/screenshot.js";
|
|
11
14
|
import { createPreferencesRouter } from "./routes/preferences.js";
|
|
15
|
+
import { createHealthRouter } from "./routes/health.js";
|
|
16
|
+
import { createSceneRouter } from "./routes/scene.js";
|
|
17
|
+
import { createEditorRouter, DirtyBufferState } from "./routes/editor.js";
|
|
18
|
+
import { createRenderRouter } from "./routes/render.js";
|
|
19
|
+
import { createLintRouter } from "./routes/lint.js";
|
|
20
|
+
import { createPackRouter } from "./routes/pack.js";
|
|
12
21
|
import { normalizePath } from "./normalize-path.js";
|
|
22
|
+
import { writeInstanceFile, deleteInstanceFile } from "./instance-file.js";
|
|
23
|
+
import { addInstance, removeInstance } from "./global-registry.js";
|
|
13
24
|
import { extractSourceLocation } from '../../lib/dist/index.js';
|
|
14
25
|
const PORT = parseInt(process.env.FLUIDCAD_SERVER_PORT || '3100', 10);
|
|
15
26
|
const WORKSPACE_PATH = normalizePath(process.env.FLUIDCAD_WORKSPACE_PATH || '');
|
|
16
27
|
const UI_DIST = path.resolve(import.meta.dirname, '../../ui/dist');
|
|
28
|
+
function readPackageVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const pkgPath = path.resolve(import.meta.dirname, '../../package.json');
|
|
31
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
if (typeof parsed.version === 'string') {
|
|
34
|
+
return parsed.version;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Fall through to unknown.
|
|
39
|
+
}
|
|
40
|
+
return '0.0.0';
|
|
41
|
+
}
|
|
42
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
43
|
+
const STARTED_AT = new Date().toISOString();
|
|
17
44
|
// ---------------------------------------------------------------------------
|
|
18
45
|
// IPC helpers — communication with extension host process
|
|
19
46
|
// ---------------------------------------------------------------------------
|
|
@@ -26,13 +53,35 @@ function sendToExtension(msg) {
|
|
|
26
53
|
// Express app
|
|
27
54
|
// ---------------------------------------------------------------------------
|
|
28
55
|
const fluidCadServer = new FluidCadServer();
|
|
56
|
+
const dirtyBufferState = new DirtyBufferState();
|
|
29
57
|
const app = express();
|
|
30
58
|
app.use(express.json({ limit: '50mb' }));
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// HTTP + WebSocket server (set up early so routes can reference its helpers)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
const httpServer = http.createServer(app);
|
|
63
|
+
const core = createServerCore(httpServer);
|
|
64
|
+
const broadcastToUI = core.broadcastToUI;
|
|
65
|
+
const requestScreenshot = core.requestScreenshot;
|
|
66
|
+
const getLastCameraState = core.getLastCameraState;
|
|
67
|
+
app.use('/api', createHealthRouter({
|
|
68
|
+
version: PACKAGE_VERSION,
|
|
69
|
+
workspacePath: WORKSPACE_PATH,
|
|
70
|
+
startedAt: STARTED_AT,
|
|
71
|
+
}));
|
|
31
72
|
app.use('/api', createPropertiesRouter(fluidCadServer));
|
|
32
|
-
app.use('/api',
|
|
33
|
-
app.use('/api',
|
|
73
|
+
app.use('/api', createParamsRouter(fluidCadServer, sendToExtension, broadcastToUI));
|
|
74
|
+
app.use('/api', createHitTestRouter(fluidCadServer));
|
|
75
|
+
app.use('/api', createTimelineRouter(fluidCadServer, sendToExtension, broadcastToUI));
|
|
76
|
+
app.use('/api', createSketchEditsRouter(fluidCadServer, sendToExtension, WORKSPACE_PATH));
|
|
77
|
+
app.use('/api', createExportRouter(fluidCadServer, WORKSPACE_PATH));
|
|
34
78
|
app.use('/api', createScreenshotRouter(requestScreenshot));
|
|
35
79
|
app.use('/api', createPreferencesRouter());
|
|
80
|
+
app.use('/api', createSceneRouter(fluidCadServer, getLastCameraState));
|
|
81
|
+
app.use('/api', createEditorRouter(dirtyBufferState));
|
|
82
|
+
app.use('/api', createRenderRouter((fileName, code) => runLiveRender(fileName, code)));
|
|
83
|
+
app.use('/api', createLintRouter());
|
|
84
|
+
app.use('/api', createPackRouter(fluidCadServer, WORKSPACE_PATH, PACKAGE_VERSION, getLastCameraState));
|
|
36
85
|
// Static files — serve UI build, with SPA fallback
|
|
37
86
|
app.use(express.static(UI_DIST, {
|
|
38
87
|
setHeaders(res, filePath) {
|
|
@@ -46,106 +95,14 @@ app.get('*splat', (_req, res) => {
|
|
|
46
95
|
res.sendFile(path.join(UI_DIST, 'index.html'));
|
|
47
96
|
});
|
|
48
97
|
// ---------------------------------------------------------------------------
|
|
49
|
-
// HTTP + WebSocket server
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
const httpServer = http.createServer(app);
|
|
52
|
-
const wss = new WebSocketServer({ server: httpServer });
|
|
53
|
-
const uiClients = new Set();
|
|
54
|
-
let lastSceneMessage = null;
|
|
55
|
-
let initCompleteMessage = null;
|
|
56
|
-
function broadcastToUI(msg) {
|
|
57
|
-
const data = JSON.stringify(msg);
|
|
58
|
-
if (msg.type === 'scene-rendered') {
|
|
59
|
-
lastSceneMessage = data;
|
|
60
|
-
}
|
|
61
|
-
if (msg.type === 'init-complete') {
|
|
62
|
-
initCompleteMessage = data;
|
|
63
|
-
}
|
|
64
|
-
for (const client of uiClients) {
|
|
65
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
66
|
-
client.send(data);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Screenshot request/response coordination
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
const SCREENSHOT_TIMEOUT_MS = 10_000;
|
|
74
|
-
const pendingScreenshots = new Map();
|
|
75
|
-
function requestScreenshot(options) {
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
if (uiClients.size === 0) {
|
|
78
|
-
reject(new Error('No UI client connected.'));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const requestId = crypto.randomUUID();
|
|
82
|
-
const timeout = setTimeout(() => {
|
|
83
|
-
pendingScreenshots.delete(requestId);
|
|
84
|
-
reject(new Error('Screenshot request timed out.'));
|
|
85
|
-
}, SCREENSHOT_TIMEOUT_MS);
|
|
86
|
-
pendingScreenshots.set(requestId, {
|
|
87
|
-
resolve(data) {
|
|
88
|
-
clearTimeout(timeout);
|
|
89
|
-
pendingScreenshots.delete(requestId);
|
|
90
|
-
resolve(data);
|
|
91
|
-
},
|
|
92
|
-
reject(err) {
|
|
93
|
-
clearTimeout(timeout);
|
|
94
|
-
pendingScreenshots.delete(requestId);
|
|
95
|
-
reject(err);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
broadcastToUI({ type: 'take-screenshot', requestId, options });
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
function handleUIMessage(raw) {
|
|
102
|
-
let msg;
|
|
103
|
-
try {
|
|
104
|
-
msg = JSON.parse(raw);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (msg.type === 'screenshot-result' && msg.requestId) {
|
|
110
|
-
const pending = pendingScreenshots.get(msg.requestId);
|
|
111
|
-
if (!pending) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (msg.success && msg.data) {
|
|
115
|
-
pending.resolve(Buffer.from(msg.data, 'base64'));
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
pending.reject(new Error(msg.error || 'Screenshot failed.'));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// WebSocket connections
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
wss.on('connection', (ws) => {
|
|
126
|
-
uiClients.add(ws);
|
|
127
|
-
// Replay init-complete and last scene to newly connected UI client
|
|
128
|
-
if (initCompleteMessage) {
|
|
129
|
-
ws.send(initCompleteMessage);
|
|
130
|
-
}
|
|
131
|
-
if (lastSceneMessage) {
|
|
132
|
-
ws.send(lastSceneMessage);
|
|
133
|
-
}
|
|
134
|
-
ws.on('message', (data) => {
|
|
135
|
-
handleUIMessage(String(data));
|
|
136
|
-
});
|
|
137
|
-
ws.on('close', () => {
|
|
138
|
-
uiClients.delete(ws);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
98
|
// IPC message handling — extension host → server
|
|
143
99
|
// ---------------------------------------------------------------------------
|
|
144
100
|
let currentFile = null;
|
|
145
101
|
let renderVersion = 0;
|
|
146
102
|
const lastSceneByFile = new Map();
|
|
147
|
-
function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
|
|
103
|
+
function emitSuccess(version, absPath, result, rollbackStop, breakpointHit, params) {
|
|
148
104
|
lastSceneByFile.set(absPath, { result, rollbackStop });
|
|
105
|
+
fluidCadServer.setCompileError(null);
|
|
149
106
|
sendToExtension({
|
|
150
107
|
type: 'scene-rendered',
|
|
151
108
|
absPath,
|
|
@@ -158,7 +115,9 @@ function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
|
|
|
158
115
|
absPath,
|
|
159
116
|
rollbackStop,
|
|
160
117
|
breakpointHit,
|
|
118
|
+
params,
|
|
161
119
|
});
|
|
120
|
+
broadcastToUI({ type: 'render-version', version, state: 'end', absPath });
|
|
162
121
|
}
|
|
163
122
|
function buildCompileError(filePath, err) {
|
|
164
123
|
const message = err?.message || String(err);
|
|
@@ -178,12 +137,13 @@ function buildCompileError(filePath, err) {
|
|
|
178
137
|
sourceLocation: sourceLocation ?? undefined,
|
|
179
138
|
};
|
|
180
139
|
}
|
|
181
|
-
function emitCompileError(filePath, err) {
|
|
140
|
+
function emitCompileError(version, filePath, err) {
|
|
182
141
|
const compileError = buildCompileError(filePath, err);
|
|
183
142
|
const key = compileError.filePath ?? normalizePath(filePath).replace('virtual:live-render:', '');
|
|
184
143
|
const prev = lastSceneByFile.get(key);
|
|
185
144
|
const result = prev?.result ?? [];
|
|
186
145
|
const rollbackStop = prev?.rollbackStop ?? -1;
|
|
146
|
+
fluidCadServer.setCompileError(compileError);
|
|
187
147
|
sendToExtension({
|
|
188
148
|
type: 'scene-rendered',
|
|
189
149
|
absPath: key,
|
|
@@ -198,12 +158,59 @@ function emitCompileError(filePath, err) {
|
|
|
198
158
|
rollbackStop,
|
|
199
159
|
compileError,
|
|
200
160
|
});
|
|
161
|
+
broadcastToUI({ type: 'render-version', version, state: 'error', absPath: key });
|
|
162
|
+
return compileError;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Render-orchestration chokepoint shared by the IPC `live-update` handler and
|
|
166
|
+
* the HTTP `/api/render` route. Bumps `renderVersion`, broadcasts the
|
|
167
|
+
* lifecycle pings, runs the dedupable `updateLiveCode`, and emits success /
|
|
168
|
+
* compile-error to the UI + extension. Returns a structured outcome so the
|
|
169
|
+
* HTTP caller (MCP) can hand it straight to the agent.
|
|
170
|
+
*/
|
|
171
|
+
async function runLiveRender(fileName, code) {
|
|
172
|
+
const startedAt = Date.now();
|
|
173
|
+
const myVersion = ++renderVersion;
|
|
174
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
175
|
+
if (fileName !== currentFile) {
|
|
176
|
+
broadcastToUI({ type: 'processing-file' });
|
|
177
|
+
currentFile = fileName;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const data = await fluidCadServer.updateLiveCode(fileName, code);
|
|
181
|
+
if (myVersion !== renderVersion) {
|
|
182
|
+
return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
|
|
183
|
+
}
|
|
184
|
+
if (!data) {
|
|
185
|
+
return { state: 'no-scene-manager', version: myVersion, durationMs: Date.now() - startedAt };
|
|
186
|
+
}
|
|
187
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit, data.params);
|
|
188
|
+
return {
|
|
189
|
+
state: 'rendered',
|
|
190
|
+
version: myVersion,
|
|
191
|
+
absPath: data.absPath,
|
|
192
|
+
durationMs: Date.now() - startedAt,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (myVersion !== renderVersion) {
|
|
197
|
+
return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
|
|
198
|
+
}
|
|
199
|
+
const compileError = emitCompileError(myVersion, fileName, err);
|
|
200
|
+
return {
|
|
201
|
+
state: 'compile-error',
|
|
202
|
+
version: myVersion,
|
|
203
|
+
durationMs: Date.now() - startedAt,
|
|
204
|
+
compileError,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
201
207
|
}
|
|
202
208
|
async function handleExtensionMessage(msg) {
|
|
203
209
|
try {
|
|
204
210
|
switch (msg.type) {
|
|
205
211
|
case 'process-file': {
|
|
206
212
|
const myVersion = ++renderVersion;
|
|
213
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
207
214
|
broadcastToUI({ type: 'processing-file' });
|
|
208
215
|
currentFile = msg.filePath;
|
|
209
216
|
try {
|
|
@@ -212,48 +219,30 @@ async function handleExtensionMessage(msg) {
|
|
|
212
219
|
return;
|
|
213
220
|
}
|
|
214
221
|
if (data) {
|
|
215
|
-
emitSuccess(data.absPath, data.result, data.rollbackStop, data.breakpointHit);
|
|
222
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit, data.params);
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
225
|
catch (err) {
|
|
219
226
|
if (myVersion !== renderVersion) {
|
|
220
227
|
return;
|
|
221
228
|
}
|
|
222
|
-
emitCompileError(msg.filePath, err);
|
|
229
|
+
emitCompileError(myVersion, msg.filePath, err);
|
|
223
230
|
}
|
|
224
231
|
break;
|
|
225
232
|
}
|
|
226
233
|
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
|
-
}
|
|
234
|
+
await runLiveRender(msg.fileName, msg.code);
|
|
247
235
|
break;
|
|
248
236
|
}
|
|
249
237
|
case 'rollback': {
|
|
250
238
|
const myVersion = ++renderVersion;
|
|
239
|
+
broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
|
|
251
240
|
const data = await fluidCadServer.rollback(msg.fileName, msg.index);
|
|
252
241
|
if (myVersion !== renderVersion) {
|
|
253
242
|
return;
|
|
254
243
|
}
|
|
255
244
|
if (data) {
|
|
256
|
-
emitSuccess(data.absPath, data.result, data.rollbackStop);
|
|
245
|
+
emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop);
|
|
257
246
|
}
|
|
258
247
|
break;
|
|
259
248
|
}
|
|
@@ -279,6 +268,13 @@ async function handleExtensionMessage(msg) {
|
|
|
279
268
|
broadcastToUI({ type: 'show-shape-properties', shapeId: msg.shapeId });
|
|
280
269
|
break;
|
|
281
270
|
}
|
|
271
|
+
case 'editor-dirty-state': {
|
|
272
|
+
if (Array.isArray(msg.dirtyFiles)) {
|
|
273
|
+
const paths = msg.dirtyFiles.filter((p) => typeof p === 'string');
|
|
274
|
+
dirtyBufferState.setDirtyFiles(paths);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
282
278
|
case 'export-scene': {
|
|
283
279
|
try {
|
|
284
280
|
const result = fluidCadServer.exportShapes(msg.shapeIds, msg.options);
|
|
@@ -321,6 +317,36 @@ process.on('message', (msg) => {
|
|
|
321
317
|
httpServer.listen(PORT, () => {
|
|
322
318
|
const url = `http://localhost:${PORT}`;
|
|
323
319
|
console.log(`FluidCAD server listening on ${url}`);
|
|
320
|
+
// Publish this instance so a standalone MCP process can discover us.
|
|
321
|
+
// Discovery is best-effort: an MCP-less workflow must keep working even if
|
|
322
|
+
// we can't write the file (read-only FS, permissions, …).
|
|
323
|
+
if (WORKSPACE_PATH) {
|
|
324
|
+
try {
|
|
325
|
+
writeInstanceFile({
|
|
326
|
+
schemaVersion: 1,
|
|
327
|
+
port: PORT,
|
|
328
|
+
pid: process.pid,
|
|
329
|
+
workspacePath: WORKSPACE_PATH,
|
|
330
|
+
version: PACKAGE_VERSION,
|
|
331
|
+
startedAt: STARTED_AT,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
console.warn(`Failed to write instance file: ${err?.message ?? err}`);
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
addInstance({
|
|
339
|
+
workspacePath: WORKSPACE_PATH,
|
|
340
|
+
port: PORT,
|
|
341
|
+
pid: process.pid,
|
|
342
|
+
version: PACKAGE_VERSION,
|
|
343
|
+
startedAt: STARTED_AT,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
console.warn(`Failed to update global registry: ${err?.message ?? err}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
324
350
|
// Signal ready immediately so extension can show the webview
|
|
325
351
|
sendToExtension({ type: 'ready', port: PORT, url });
|
|
326
352
|
// Initialize FluidCAD server in the background
|
|
@@ -333,3 +359,29 @@ httpServer.listen(PORT, () => {
|
|
|
333
359
|
broadcastToUI({ type: 'init-complete', success: false, error });
|
|
334
360
|
});
|
|
335
361
|
});
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Shutdown — clean up the instance file and registry entry
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
let cleanedUp = false;
|
|
366
|
+
function cleanupDiscovery() {
|
|
367
|
+
if (cleanedUp || !WORKSPACE_PATH) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
cleanedUp = true;
|
|
371
|
+
deleteInstanceFile(WORKSPACE_PATH, process.pid);
|
|
372
|
+
try {
|
|
373
|
+
removeInstance(WORKSPACE_PATH, process.pid);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Registry cleanup is best-effort; stale entries are pruned by readers.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
process.on('exit', cleanupDiscovery);
|
|
380
|
+
process.on('SIGINT', () => {
|
|
381
|
+
cleanupDiscovery();
|
|
382
|
+
process.exit(0);
|
|
383
|
+
});
|
|
384
|
+
process.on('SIGTERM', () => {
|
|
385
|
+
cleanupDiscovery();
|
|
386
|
+
process.exit(0);
|
|
387
|
+
});
|
|
@@ -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>;
|