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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Static import linter for `.fluid.js` sources.
|
|
2
|
+
//
|
|
3
|
+
// LLMs frequently emit a first draft that uses FluidCAD APIs (`sketch`,
|
|
4
|
+
// `extrude`, `face`, …) without an `import { … } from "fluidcad/core"` line —
|
|
5
|
+
// the script then explodes at runtime with `ReferenceError`. The MCP server
|
|
6
|
+
// calls this linter before writing a `.fluid.js` file so the agent gets a
|
|
7
|
+
// precise error pointing at the missing symbols instead of a confusing
|
|
8
|
+
// render failure.
|
|
9
|
+
//
|
|
10
|
+
// Tree-sitter (web-tree-sitter + tree-sitter-wasms) is reused — the parser
|
|
11
|
+
// instance is the same singleton the param editor in `code-editor.ts` uses.
|
|
12
|
+
// Doing it that way means we don't double-load the JavaScript wasm grammar.
|
|
13
|
+
import { getJavaScriptParser } from "./code-editor.js";
|
|
14
|
+
// Authoritative FluidCAD symbol→module map. Mirrors the exports in
|
|
15
|
+
// `lib/core/index.ts`, `lib/core/2d/index.ts`, `lib/filters/index.ts`, and
|
|
16
|
+
// `lib/features/2d/constraints/geometry-qualifier.ts`. If a new public symbol
|
|
17
|
+
// is added there, add it here too — the import lint is the wall the LLM
|
|
18
|
+
// hits, so keep it accurate.
|
|
19
|
+
const CORE_SYMBOLS = new Set([
|
|
20
|
+
'axis', 'local', 'plane', 'sketch', 'fuse', 'subtract', 'common',
|
|
21
|
+
'cut', 'revolve', 'extrude', 'sphere', 'cylinder', 'select', 'shell',
|
|
22
|
+
'chamfer', 'fillet', 'translate', 'rotate', 'mirror', 'copy', 'repeat',
|
|
23
|
+
'load', 'loft', 'sweep', 'rib', 'color', 'draft', 'remove', 'split',
|
|
24
|
+
'trim', 'part', 'breakpoint',
|
|
25
|
+
'line', 'circle', 'ellipse', 'rect', 'hMove', 'vMove', 'rMove',
|
|
26
|
+
'hLine', 'vLine', 'tLine', 'tCircle', 'tArc', 'arc', 'move', 'pMove',
|
|
27
|
+
'aLine', 'slot', 'connect', 'polygon', 'offset', 'project', 'intersect',
|
|
28
|
+
'bezier', 'center', 'back',
|
|
29
|
+
]);
|
|
30
|
+
const FILTER_SYMBOLS = new Set(['face', 'edge']);
|
|
31
|
+
const CONSTRAINT_SYMBOLS = new Set([
|
|
32
|
+
'outside', 'enclosed', 'enclosing', 'unqualified',
|
|
33
|
+
]);
|
|
34
|
+
const MODULE_FOR_SYMBOL = new Map();
|
|
35
|
+
for (const s of CORE_SYMBOLS) {
|
|
36
|
+
MODULE_FOR_SYMBOL.set(s, 'fluidcad/core');
|
|
37
|
+
}
|
|
38
|
+
for (const s of FILTER_SYMBOLS) {
|
|
39
|
+
MODULE_FOR_SYMBOL.set(s, 'fluidcad/filters');
|
|
40
|
+
}
|
|
41
|
+
for (const s of CONSTRAINT_SYMBOLS) {
|
|
42
|
+
MODULE_FOR_SYMBOL.set(s, 'fluidcad/constraints');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Walk every named child recursively, invoking `visit` once per node. The
|
|
46
|
+
* walker stops descending into a subtree when `visit` returns `false`.
|
|
47
|
+
*/
|
|
48
|
+
function walk(node, visit) {
|
|
49
|
+
const cont = visit(node);
|
|
50
|
+
if (cont === false) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const child of node.namedChildren) {
|
|
54
|
+
walk(child, visit);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Determine whether `node` (an `identifier`) is at a position the JS spec
|
|
59
|
+
* treats as a *reference* (would throw `ReferenceError` if unbound) vs a
|
|
60
|
+
* *binding* (a declared name) vs a *property name* (never a reference at
|
|
61
|
+
* all).
|
|
62
|
+
*
|
|
63
|
+
* Tree-sitter's JavaScript grammar has distinct node types for some of
|
|
64
|
+
* these (`property_identifier`, `shorthand_property_identifier_pattern`,
|
|
65
|
+
* etc.), but plain `identifier` nodes still cover a lot of ground and we
|
|
66
|
+
* have to look at parent context to classify them.
|
|
67
|
+
*/
|
|
68
|
+
function isReferenceUse(node) {
|
|
69
|
+
if (node.type !== 'identifier') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const parent = node.parent;
|
|
73
|
+
if (!parent) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
switch (parent.type) {
|
|
77
|
+
case 'import_specifier':
|
|
78
|
+
case 'namespace_import':
|
|
79
|
+
case 'import_clause':
|
|
80
|
+
case 'import_statement':
|
|
81
|
+
// Anything inside an `import` statement is bookkeeping, not a use.
|
|
82
|
+
return false;
|
|
83
|
+
case 'variable_declarator': {
|
|
84
|
+
// `const X = ...` → the `name` field is a binding.
|
|
85
|
+
const nameField = parent.childForFieldName('name');
|
|
86
|
+
if (nameField && nameField.startIndex === node.startIndex) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
case 'function_declaration':
|
|
92
|
+
case 'function_expression':
|
|
93
|
+
case 'generator_function_declaration':
|
|
94
|
+
case 'class_declaration':
|
|
95
|
+
case 'class_expression':
|
|
96
|
+
case 'method_definition': {
|
|
97
|
+
const nameField = parent.childForFieldName('name');
|
|
98
|
+
if (nameField && nameField.startIndex === node.startIndex) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
case 'formal_parameters':
|
|
104
|
+
case 'required_parameter':
|
|
105
|
+
case 'optional_parameter':
|
|
106
|
+
case 'rest_pattern':
|
|
107
|
+
// Parameter names are bindings.
|
|
108
|
+
return false;
|
|
109
|
+
case 'arrow_function': {
|
|
110
|
+
// `(x) => …` or `x => …` — parameter is a binding when it's the
|
|
111
|
+
// function's `parameter` field.
|
|
112
|
+
const param = parent.childForFieldName('parameter');
|
|
113
|
+
if (param && param.startIndex === node.startIndex) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
case 'member_expression': {
|
|
119
|
+
// `obj.foo` — the `property` field is a name lookup, not a reference.
|
|
120
|
+
const prop = parent.childForFieldName('property');
|
|
121
|
+
if (prop && prop.startIndex === node.startIndex) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
case 'pair': {
|
|
127
|
+
// `{ key: value }` — `key` (if an identifier) is a property name.
|
|
128
|
+
const key = parent.childForFieldName('key');
|
|
129
|
+
if (key && key.startIndex === node.startIndex) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
case 'property_signature':
|
|
135
|
+
case 'public_field_definition':
|
|
136
|
+
// Class/object field declarations — the name slot is a binding.
|
|
137
|
+
return false;
|
|
138
|
+
case 'labeled_statement':
|
|
139
|
+
// `label: stmt` — labels are not references.
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Walk an `import_statement` and collect every name it locally binds.
|
|
146
|
+
* Handles:
|
|
147
|
+
* - `import x from "…"` (default)
|
|
148
|
+
* - `import { a, b as c } from "…"` (named, with renaming)
|
|
149
|
+
* - `import x, { a } from "…"` (default + named)
|
|
150
|
+
* - `import * as ns from "…"` (namespace)
|
|
151
|
+
* - `import "…"` (side-effect, binds nothing)
|
|
152
|
+
*/
|
|
153
|
+
function collectImportedNames(importNode, into) {
|
|
154
|
+
walk(importNode, (n) => {
|
|
155
|
+
if (n.type === 'import_specifier') {
|
|
156
|
+
// `{ a as b }` → `alias` field (b) is what binds; otherwise `name` (a).
|
|
157
|
+
const alias = n.childForFieldName('alias');
|
|
158
|
+
const name = n.childForFieldName('name');
|
|
159
|
+
const local = alias ?? name;
|
|
160
|
+
if (local && local.type === 'identifier') {
|
|
161
|
+
into.add(local.text);
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (n.type === 'namespace_import') {
|
|
166
|
+
const id = n.namedChildren.find((c) => c.type === 'identifier');
|
|
167
|
+
if (id) {
|
|
168
|
+
into.add(id.text);
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (n.type === 'import_clause') {
|
|
173
|
+
// The default import (if any) is a direct `identifier` child.
|
|
174
|
+
for (const child of n.namedChildren) {
|
|
175
|
+
if (child.type === 'identifier') {
|
|
176
|
+
into.add(child.text);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Collect names that are bound at the top level of the program: `const`,
|
|
184
|
+
* `let`, `var`, `function`, `class`. Used so user code like
|
|
185
|
+
* `const sketch = …` shadows the FluidCAD `sketch` without tripping the
|
|
186
|
+
* lint. We deliberately keep this top-level-only to keep the implementation
|
|
187
|
+
* simple — local shadowing inside a function is rare in `.fluid.js` files.
|
|
188
|
+
*/
|
|
189
|
+
function collectTopLevelDeclaredNames(root, into) {
|
|
190
|
+
for (const stmt of root.namedChildren) {
|
|
191
|
+
if (stmt.type === 'variable_declaration' || stmt.type === 'lexical_declaration') {
|
|
192
|
+
for (const decl of stmt.namedChildren) {
|
|
193
|
+
if (decl.type !== 'variable_declarator') {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const name = decl.childForFieldName('name');
|
|
197
|
+
if (name && name.type === 'identifier') {
|
|
198
|
+
into.add(name.text);
|
|
199
|
+
}
|
|
200
|
+
// Destructuring (`const { a, b } = …`) — pick up shorthand pattern names.
|
|
201
|
+
if (name) {
|
|
202
|
+
walk(name, (n) => {
|
|
203
|
+
if (n.type === 'shorthand_property_identifier_pattern' ||
|
|
204
|
+
n.type === 'identifier') {
|
|
205
|
+
if (n.parent && n.parent.type !== 'pair_pattern') {
|
|
206
|
+
into.add(n.text);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (stmt.type === 'function_declaration' || stmt.type === 'class_declaration') {
|
|
214
|
+
const name = stmt.childForFieldName('name');
|
|
215
|
+
if (name && name.type === 'identifier') {
|
|
216
|
+
into.add(name.text);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export async function lintFluidJs(code) {
|
|
222
|
+
const parser = await getJavaScriptParser();
|
|
223
|
+
const tree = parser.parse(code);
|
|
224
|
+
const root = tree.rootNode;
|
|
225
|
+
const bound = new Set();
|
|
226
|
+
for (const stmt of root.namedChildren) {
|
|
227
|
+
if (stmt.type === 'import_statement') {
|
|
228
|
+
collectImportedNames(stmt, bound);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
collectTopLevelDeclaredNames(root, bound);
|
|
232
|
+
const missingByName = new Map();
|
|
233
|
+
walk(root, (n) => {
|
|
234
|
+
if (n.type === 'import_statement') {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (n.type !== 'identifier') {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!MODULE_FOR_SYMBOL.has(n.text)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (bound.has(n.text)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!isReferenceUse(n)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (missingByName.has(n.text)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
missingByName.set(n.text, {
|
|
253
|
+
symbol: n.text,
|
|
254
|
+
module: MODULE_FOR_SYMBOL.get(n.text),
|
|
255
|
+
line: n.startPosition.row,
|
|
256
|
+
column: n.startPosition.column,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
const sorted = Array.from(missingByName.values()).sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
260
|
+
const byModule = new Map();
|
|
261
|
+
for (const m of sorted) {
|
|
262
|
+
const list = byModule.get(m.module) ?? [];
|
|
263
|
+
list.push(m.symbol);
|
|
264
|
+
byModule.set(m.module, list);
|
|
265
|
+
}
|
|
266
|
+
const suggestion = Array.from(byModule.entries())
|
|
267
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
268
|
+
.map(([mod, syms]) => `import { ${syms.join(', ')} } from "${mod}";`)
|
|
269
|
+
.join('\n');
|
|
270
|
+
return { missing: sorted, suggestion };
|
|
271
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParamDefinition } from '../../../lib/dist/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Render a model once, headlessly, and return its full parameter schema.
|
|
4
|
+
*
|
|
5
|
+
* Param *definitions* (type, default, current value, constraints) only exist
|
|
6
|
+
* after the engine runs the model — the packer/bundler never executes user
|
|
7
|
+
* code, so a static manifest can carry override values at most. `fluidcad
|
|
8
|
+
* publish` calls this to capture the real schema and embeds it in the manifest
|
|
9
|
+
* (`paramDefinitions`), so the hub can build param forms without a live worker.
|
|
10
|
+
*
|
|
11
|
+
* The render doubles as a build gate: a compile/runtime error in the model
|
|
12
|
+
* propagates out of here, failing the publish before any draft is created.
|
|
13
|
+
*
|
|
14
|
+
* Side-effect-free at import time — it constructs its own `FluidCadServer`
|
|
15
|
+
* (which boots OC wasm + a Vite SSR pipeline) and tears the Vite server down
|
|
16
|
+
* before returning so a one-shot CLI process can exit. Import this (or use
|
|
17
|
+
* `fluidcad/server/api`), NOT `fluidcad/server`, which boots the desktop binary.
|
|
18
|
+
*/
|
|
19
|
+
export declare function captureParamDefinitions(entryPath: string, workspacePath: string): Promise<ParamDefinition[]>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LocalSceneHost } from "../host/local-scene-host.js";
|
|
2
|
+
import { FluidCadServer } from "../fluidcad-server.js";
|
|
3
|
+
/**
|
|
4
|
+
* Render a model once, headlessly, and return its full parameter schema.
|
|
5
|
+
*
|
|
6
|
+
* Param *definitions* (type, default, current value, constraints) only exist
|
|
7
|
+
* after the engine runs the model — the packer/bundler never executes user
|
|
8
|
+
* code, so a static manifest can carry override values at most. `fluidcad
|
|
9
|
+
* publish` calls this to capture the real schema and embeds it in the manifest
|
|
10
|
+
* (`paramDefinitions`), so the hub can build param forms without a live worker.
|
|
11
|
+
*
|
|
12
|
+
* The render doubles as a build gate: a compile/runtime error in the model
|
|
13
|
+
* propagates out of here, failing the publish before any draft is created.
|
|
14
|
+
*
|
|
15
|
+
* Side-effect-free at import time — it constructs its own `FluidCadServer`
|
|
16
|
+
* (which boots OC wasm + a Vite SSR pipeline) and tears the Vite server down
|
|
17
|
+
* before returning so a one-shot CLI process can exit. Import this (or use
|
|
18
|
+
* `fluidcad/server/api`), NOT `fluidcad/server`, which boots the desktop binary.
|
|
19
|
+
*/
|
|
20
|
+
export async function captureParamDefinitions(entryPath, workspacePath) {
|
|
21
|
+
const host = new LocalSceneHost();
|
|
22
|
+
const server = new FluidCadServer(host);
|
|
23
|
+
try {
|
|
24
|
+
await server.init(workspacePath);
|
|
25
|
+
const rendered = await server.processFile(entryPath);
|
|
26
|
+
if (!rendered) {
|
|
27
|
+
throw new Error('The engine did not initialize — is there an init.js at the workspace root? ' +
|
|
28
|
+
'Run `fluidcad init` to scaffold one.');
|
|
29
|
+
}
|
|
30
|
+
return rendered.params ?? [];
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
// LocalSceneHost.init() starts a Vite dev server that keeps the event loop
|
|
34
|
+
// alive; close it so the CLI can exit after a single render.
|
|
35
|
+
try {
|
|
36
|
+
await host.server?.close();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* best-effort teardown */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ParamDefinition } from '../../../lib/dist/index.js';
|
|
2
|
+
import { type ModelPackageCamera, type ModelPackageManifest, type ParamValue } from './types.ts';
|
|
3
|
+
export interface PackInputs {
|
|
4
|
+
entryPath: string;
|
|
5
|
+
workspacePath: string;
|
|
6
|
+
fluidcadVersion: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
paramOverrides?: Record<string, ParamValue>;
|
|
10
|
+
/**
|
|
11
|
+
* Full param schema to embed in the manifest. `fluidcad publish` renders the
|
|
12
|
+
* model once to capture this (see `capture-params.ts`); `fluidcad pack` omits
|
|
13
|
+
* it. Kept as an input (rather than rendering inside `packModel`) so packing
|
|
14
|
+
* stays a pure, engine-free file producer.
|
|
15
|
+
*/
|
|
16
|
+
paramDefinitions?: ParamDefinition[];
|
|
17
|
+
camera?: ModelPackageCamera;
|
|
18
|
+
}
|
|
19
|
+
export interface PackResult {
|
|
20
|
+
manifest: ModelPackageManifest;
|
|
21
|
+
zip: Buffer;
|
|
22
|
+
}
|
|
23
|
+
export declare function packModel(inputs: PackInputs): Promise<PackResult>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { build } from 'esbuild';
|
|
2
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { basename, extname, join, relative } from 'path';
|
|
5
|
+
import JSZip from 'jszip';
|
|
6
|
+
import ignoreFactory from 'ignore';
|
|
7
|
+
import { normalizePath } from "../normalize-path.js";
|
|
8
|
+
import { getBlockedNodeModule } from "../host/blocked-imports.js";
|
|
9
|
+
import { ASSETS_PREFIX, BUNDLE_FILENAME, FILES_PREFIX, MANIFEST_FILENAME, } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Reject Node.js builtins that are off-limits in `.fluid.js` code. Same
|
|
12
|
+
* defence the LocalSceneHost applies at SSR transform time; here it runs
|
|
13
|
+
* at pack time so the produced bundle is verified before it ships.
|
|
14
|
+
*/
|
|
15
|
+
function blockNodeBuiltinsPlugin() {
|
|
16
|
+
return {
|
|
17
|
+
name: 'block-node-builtins',
|
|
18
|
+
setup(b) {
|
|
19
|
+
b.onResolve({ filter: /.*/ }, (args) => {
|
|
20
|
+
const blocked = getBlockedNodeModule(args.path);
|
|
21
|
+
if (!blocked)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
errors: [
|
|
25
|
+
{
|
|
26
|
+
text: `Module "${args.path}" is not allowed in FluidCAD scripts. ` +
|
|
27
|
+
`Access to Node.js "${blocked}" module is restricted for security.`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Bundle the model into a single ES module via a virtual wrapper. When
|
|
37
|
+
* `init.js` exists it runs FIRST (its side effects set up the engine) and
|
|
38
|
+
* its `default` export is forwarded as the bundle's `default` export so
|
|
39
|
+
* the hub-side loader has a handle on the SceneManager. When there's no
|
|
40
|
+
* init.js, the entry is bundled directly.
|
|
41
|
+
*
|
|
42
|
+
* The bundle is self-contained — every transitively-imported workspace file
|
|
43
|
+
* is inlined (npm deps too). The original file text the hub displays comes
|
|
44
|
+
* from the `files/` tree, not from this bundle.
|
|
45
|
+
*/
|
|
46
|
+
async function bundleModel(entryAbs, initAbs, workspaceAbs) {
|
|
47
|
+
const entryRel = './' + normalizePath(relative(workspaceAbs, entryAbs));
|
|
48
|
+
const initRel = initAbs ? './' + normalizePath(relative(workspaceAbs, initAbs)) : null;
|
|
49
|
+
const wrapperSource = initRel
|
|
50
|
+
? `import sceneManager from ${JSON.stringify(initRel)};\n` +
|
|
51
|
+
`import ${JSON.stringify(entryRel)};\n` +
|
|
52
|
+
`export default sceneManager;\n`
|
|
53
|
+
: `export * from ${JSON.stringify(entryRel)};\n`;
|
|
54
|
+
const result = await build({
|
|
55
|
+
stdin: {
|
|
56
|
+
contents: wrapperSource,
|
|
57
|
+
resolveDir: workspaceAbs,
|
|
58
|
+
sourcefile: '__fluidpkg_entry__.js',
|
|
59
|
+
loader: 'js',
|
|
60
|
+
},
|
|
61
|
+
format: 'esm',
|
|
62
|
+
bundle: true,
|
|
63
|
+
write: false,
|
|
64
|
+
platform: 'node',
|
|
65
|
+
external: ['fluidcad', 'fluidcad/*'],
|
|
66
|
+
plugins: [blockNodeBuiltinsPlugin()],
|
|
67
|
+
logLevel: 'silent',
|
|
68
|
+
});
|
|
69
|
+
if (result.errors.length) {
|
|
70
|
+
throw new Error(result.errors.map((e) => e.text).join('\n'));
|
|
71
|
+
}
|
|
72
|
+
if (!result.outputFiles || result.outputFiles.length === 0) {
|
|
73
|
+
throw new Error(`esbuild produced no output for ${entryAbs}`);
|
|
74
|
+
}
|
|
75
|
+
return result.outputFiles[0].text;
|
|
76
|
+
}
|
|
77
|
+
async function collectImportAssetPaths(workspacePath) {
|
|
78
|
+
// STEP imports are stored as cached `.brep` (+ `.colors.json` sidecar) under
|
|
79
|
+
// `imports/` — the engine reads those at render time, not the original
|
|
80
|
+
// `.step` files. Walk the whole workspace so any `.brep`/`.colors.json` is
|
|
81
|
+
// captured; also include any `.step`/`.stp` originals the user kept around
|
|
82
|
+
// (for display in the hub's file viewer; the engine ignores them).
|
|
83
|
+
const out = [];
|
|
84
|
+
async function walk(dir) {
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = await readdir(dir);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'dist')
|
|
94
|
+
continue;
|
|
95
|
+
const full = join(dir, entry);
|
|
96
|
+
let st;
|
|
97
|
+
try {
|
|
98
|
+
st = await stat(full);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (st.isDirectory()) {
|
|
104
|
+
await walk(full);
|
|
105
|
+
}
|
|
106
|
+
else if (st.isFile()) {
|
|
107
|
+
const lower = entry.toLowerCase();
|
|
108
|
+
const ext = extname(lower);
|
|
109
|
+
const isColors = lower.endsWith('.colors.json');
|
|
110
|
+
if (ext === '.step' || ext === '.stp' || ext === '.brep' || isColors) {
|
|
111
|
+
out.push(normalizePath(relative(workspacePath, full)));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await walk(workspacePath);
|
|
117
|
+
return out.sort();
|
|
118
|
+
}
|
|
119
|
+
// Enforced on top of any `.gitignore`: dependency trees and prior pack outputs
|
|
120
|
+
// (the latter would otherwise recurse into the next pack). `node_modules` is
|
|
121
|
+
// also pruned during the walk for speed. Hidden dot-entries are excluded by the
|
|
122
|
+
// walk directly (see below), so VCS metadata (`.git`) and secrets (`.env`) need
|
|
123
|
+
// no pattern here.
|
|
124
|
+
const ALWAYS_EXCLUDE = ['node_modules', '*.fluidpkg'];
|
|
125
|
+
// `ignore` ships a CJS `module.exports = factory`, but its bundled types use
|
|
126
|
+
// `export default`, which loses the call signature under `module: nodenext`.
|
|
127
|
+
// Pin the factory's real signature; the runtime value is the callable factory.
|
|
128
|
+
const ignore = ignoreFactory;
|
|
129
|
+
/**
|
|
130
|
+
* Pack v2 file selection: every non-ignored file in the workspace, so the hub
|
|
131
|
+
* ships the whole project (README, package.json, configs, sources) — not just
|
|
132
|
+
* the entry's transitive imports.
|
|
133
|
+
*
|
|
134
|
+
* A root `.gitignore` is honored via the mature `ignore` package (same matcher
|
|
135
|
+
* eslint/prettier use). Hidden dot-entries (names starting with `.`) are ALWAYS
|
|
136
|
+
* excluded — `.git`, `.env`, and tool/editor state like `.claude`/`.vscode` are
|
|
137
|
+
* never model content and may hold secrets — regardless of whether they're
|
|
138
|
+
* gitignored. `node_modules` is pruned too; `ALWAYS_EXCLUDE` (prior `.fluidpkg`
|
|
139
|
+
* outputs) is enforced on top of any `.gitignore`. We walk and filter per-file
|
|
140
|
+
* rather than pruning ignored directories so negation rules (`!keep/this`) work.
|
|
141
|
+
*/
|
|
142
|
+
async function collectWorkspaceFiles(workspaceAbs) {
|
|
143
|
+
const gitignorePath = join(workspaceAbs, '.gitignore');
|
|
144
|
+
const hasGitignore = existsSync(gitignorePath);
|
|
145
|
+
const ig = ignore().add(ALWAYS_EXCLUDE);
|
|
146
|
+
if (hasGitignore) {
|
|
147
|
+
ig.add(await readFile(gitignorePath, 'utf8'));
|
|
148
|
+
}
|
|
149
|
+
const out = [];
|
|
150
|
+
async function walk(dir) {
|
|
151
|
+
let entries;
|
|
152
|
+
try {
|
|
153
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const name = entry.name;
|
|
160
|
+
// Skip dependency trees and ALL hidden dot-entries (VCS metadata, secrets,
|
|
161
|
+
// editor/tool state) — never packaged, gitignore or not.
|
|
162
|
+
if (name === 'node_modules' || name.startsWith('.'))
|
|
163
|
+
continue;
|
|
164
|
+
const full = join(dir, name);
|
|
165
|
+
const rel = normalizePath(relative(workspaceAbs, full));
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
await walk(full);
|
|
168
|
+
}
|
|
169
|
+
else if (entry.isFile()) {
|
|
170
|
+
if (ig.ignores(rel))
|
|
171
|
+
continue;
|
|
172
|
+
out.push(rel);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await walk(workspaceAbs);
|
|
177
|
+
return out.sort();
|
|
178
|
+
}
|
|
179
|
+
export async function packModel(inputs) {
|
|
180
|
+
const entryAbs = normalizePath(inputs.entryPath);
|
|
181
|
+
const workspaceAbs = normalizePath(inputs.workspacePath);
|
|
182
|
+
const initPath = join(workspaceAbs, 'init.js');
|
|
183
|
+
const initAbs = existsSync(initPath) ? normalizePath(initPath) : null;
|
|
184
|
+
const bundle = await bundleModel(entryAbs, initAbs, workspaceAbs);
|
|
185
|
+
const assetPaths = await collectImportAssetPaths(workspaceAbs);
|
|
186
|
+
// The full human tree, minus anything already shipped under assets/ (so large
|
|
187
|
+
// brep/STEP bytes aren't duplicated). assets + files together = the package.
|
|
188
|
+
const assetSet = new Set(assetPaths);
|
|
189
|
+
const filePaths = (await collectWorkspaceFiles(workspaceAbs)).filter((p) => !assetSet.has(p));
|
|
190
|
+
const entryRelative = normalizePath(relative(workspaceAbs, entryAbs));
|
|
191
|
+
const defaultName = basename(entryAbs).replace(/\.fluid\.js$/i, '');
|
|
192
|
+
const manifest = {
|
|
193
|
+
schemaVersion: 2,
|
|
194
|
+
name: inputs.name ?? defaultName,
|
|
195
|
+
fluidcadVersion: inputs.fluidcadVersion,
|
|
196
|
+
createdAt: new Date().toISOString(),
|
|
197
|
+
entry: entryRelative,
|
|
198
|
+
hasInit: !!initAbs,
|
|
199
|
+
assets: assetPaths,
|
|
200
|
+
files: filePaths,
|
|
201
|
+
};
|
|
202
|
+
if (inputs.description)
|
|
203
|
+
manifest.description = inputs.description;
|
|
204
|
+
if (inputs.paramOverrides && Object.keys(inputs.paramOverrides).length > 0) {
|
|
205
|
+
manifest.params = inputs.paramOverrides;
|
|
206
|
+
}
|
|
207
|
+
if (inputs.paramDefinitions && inputs.paramDefinitions.length > 0) {
|
|
208
|
+
manifest.paramDefinitions = inputs.paramDefinitions;
|
|
209
|
+
}
|
|
210
|
+
if (inputs.camera)
|
|
211
|
+
manifest.camera = inputs.camera;
|
|
212
|
+
const zip = new JSZip();
|
|
213
|
+
zip.file(MANIFEST_FILENAME, JSON.stringify(manifest, null, 2));
|
|
214
|
+
zip.file(BUNDLE_FILENAME, bundle);
|
|
215
|
+
for (const relPath of assetPaths) {
|
|
216
|
+
const bytes = await readFile(join(workspaceAbs, relPath));
|
|
217
|
+
zip.file(ASSETS_PREFIX + relPath, bytes);
|
|
218
|
+
}
|
|
219
|
+
for (const relPath of filePaths) {
|
|
220
|
+
const bytes = await readFile(join(workspaceAbs, relPath));
|
|
221
|
+
zip.file(FILES_PREFIX + relPath, bytes);
|
|
222
|
+
}
|
|
223
|
+
const buffer = await zip.generateAsync({
|
|
224
|
+
type: 'nodebuffer',
|
|
225
|
+
compression: 'DEFLATE',
|
|
226
|
+
compressionOptions: { level: 6 },
|
|
227
|
+
});
|
|
228
|
+
return { manifest, zip: buffer };
|
|
229
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ParamDefinition } from '../../../lib/dist/index.js';
|
|
2
|
+
export type ParamValue = string | number | boolean | (string | number)[];
|
|
3
|
+
export interface ModelPackageCamera {
|
|
4
|
+
position: [number, number, number];
|
|
5
|
+
target: [number, number, number];
|
|
6
|
+
up: [number, number, number];
|
|
7
|
+
projection: 'orthographic' | 'perspective';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Contents of `manifest.json` inside a `.fluidpkg` archive. The bundle, the
|
|
11
|
+
* optional init.js, and any STEP assets live alongside this manifest as
|
|
12
|
+
* separate entries in the zip — we never base64-embed binaries in JSON.
|
|
13
|
+
*
|
|
14
|
+
* The HubSceneHost reads this manifest first to decide what else to load from
|
|
15
|
+
* the archive (presence of `init.js`, which asset paths to map, the file tree).
|
|
16
|
+
*
|
|
17
|
+
* `schemaVersion: 2` retired the `src/` source tree: the single self-contained
|
|
18
|
+
* `bundle.js` is what the engine executes, and the `files` tree (below) is the
|
|
19
|
+
* full human-readable project the hub displays — so `sources`/`src/` (the old
|
|
20
|
+
* transitive-import subset) is gone.
|
|
21
|
+
*/
|
|
22
|
+
export interface ModelPackageManifest {
|
|
23
|
+
schemaVersion: 2;
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
fluidcadVersion: string;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
entry: string;
|
|
29
|
+
/**
|
|
30
|
+
* True when the workspace had an `init.js`. Its code is bundled at the top of
|
|
31
|
+
* `bundle.js` so the engine pipeline is set up before the entry runs, and the
|
|
32
|
+
* bundle's `default` export is init's default (the SceneManager). The original
|
|
33
|
+
* `init.js` text is still in the `files` tree for display.
|
|
34
|
+
*/
|
|
35
|
+
hasInit: boolean;
|
|
36
|
+
assets: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Workspace-relative paths of every non-ignored file in the workspace
|
|
39
|
+
* (Pack v2), shipped verbatim under `files/<path>`. The full human tree —
|
|
40
|
+
* README, package.json, configs, the `.fluid.js` sources — that the hub's
|
|
41
|
+
* file viewer lists and serves, separate from the self-contained `bundle.js`
|
|
42
|
+
* the engine executes. Paths already shipped under `assets/` (engine
|
|
43
|
+
* brep/STEP) are NOT repeated here; `files` + `assets` is the whole package.
|
|
44
|
+
*
|
|
45
|
+
* Selection respects a root `.gitignore` (via the `ignore` package) and
|
|
46
|
+
* always excludes `node_modules`, prior `*.fluidpkg` outputs, and every
|
|
47
|
+
* hidden dot-entry (`.git`, `.env`, `.claude`, `.vscode`, … — never model
|
|
48
|
+
* content, may hold secrets), whether or not they're gitignored.
|
|
49
|
+
*/
|
|
50
|
+
files: string[];
|
|
51
|
+
params?: Record<string, ParamValue>;
|
|
52
|
+
/**
|
|
53
|
+
* Full parameter schema captured by rendering the model once at pack time
|
|
54
|
+
* (type/default/current value/constraints per `param()` call). Unlike
|
|
55
|
+
* `params` (override VALUES only), this is the complete definition set the
|
|
56
|
+
* hub stores and renders forms from. Populated by `fluidcad publish` (which
|
|
57
|
+
* boots the engine to render); plain `fluidcad pack` leaves it undefined.
|
|
58
|
+
*/
|
|
59
|
+
paramDefinitions?: ParamDefinition[];
|
|
60
|
+
camera?: ModelPackageCamera;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Standard layout inside a `.fluidpkg` zip:
|
|
64
|
+
* manifest.json — ModelPackageManifest as JSON
|
|
65
|
+
* bundle.js — esbuild ES module output: init.js code first (if
|
|
66
|
+
* hasInit), then the entry; bundle's default export
|
|
67
|
+
* is init's default (SceneManager) when present
|
|
68
|
+
* assets/<path> — raw bytes of imported STEP files, paths preserved
|
|
69
|
+
* relative to the workspace root
|
|
70
|
+
* files/<path> — every non-ignored workspace file (Pack v2), verbatim;
|
|
71
|
+
* the full human tree the hub viewer lists and serves.
|
|
72
|
+
* Excludes anything already under assets/ to avoid
|
|
73
|
+
* duplicate bytes.
|
|
74
|
+
*/
|
|
75
|
+
export declare const MANIFEST_FILENAME = "manifest.json";
|
|
76
|
+
export declare const BUNDLE_FILENAME = "bundle.js";
|
|
77
|
+
export declare const ASSETS_PREFIX = "assets/";
|
|
78
|
+
export declare const FILES_PREFIX = "files/";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard layout inside a `.fluidpkg` zip:
|
|
3
|
+
* manifest.json — ModelPackageManifest as JSON
|
|
4
|
+
* bundle.js — esbuild ES module output: init.js code first (if
|
|
5
|
+
* hasInit), then the entry; bundle's default export
|
|
6
|
+
* is init's default (SceneManager) when present
|
|
7
|
+
* assets/<path> — raw bytes of imported STEP files, paths preserved
|
|
8
|
+
* relative to the workspace root
|
|
9
|
+
* files/<path> — every non-ignored workspace file (Pack v2), verbatim;
|
|
10
|
+
* the full human tree the hub viewer lists and serves.
|
|
11
|
+
* Excludes anything already under assets/ to avoid
|
|
12
|
+
* duplicate bytes.
|
|
13
|
+
*/
|
|
14
|
+
export const MANIFEST_FILENAME = 'manifest.json';
|
|
15
|
+
export const BUNDLE_FILENAME = 'bundle.js';
|
|
16
|
+
export const ASSETS_PREFIX = 'assets/';
|
|
17
|
+
export const FILES_PREFIX = 'files/';
|