diff --git a/loader/fixture/moved-import-keeps-local-dependencies/Root.flyde b/loader/fixture/moved-import-keeps-local-dependencies/Root.flyde new file mode 100644 index 00000000..151bf925 --- /dev/null +++ b/loader/fixture/moved-import-keeps-local-dependencies/Root.flyde @@ -0,0 +1,31 @@ +--- +imports: + "./moved/Wrapper.flyde": AddOneWrapper +node: + id: Root + inputs: + n: + type: number + mode: required + outputs: + r: + type: number + instances: + - id: addOne + nodeId: AddOneWrapper + pos: + x: 0 + y: 0 + connections: + - from: + insId: __this + pinId: n + to: + insId: addOne + pinId: n + - from: + insId: addOne + pinId: r + to: + insId: __this + pinId: r diff --git a/loader/fixture/moved-import-keeps-local-dependencies/moved/AddOne.flyde.js b/loader/fixture/moved-import-keeps-local-dependencies/moved/AddOne.flyde.js new file mode 100644 index 00000000..7175d67d --- /dev/null +++ b/loader/fixture/moved-import-keeps-local-dependencies/moved/AddOne.flyde.js @@ -0,0 +1,12 @@ +module.exports = { + id: "AddOne", + inputs: { + n: { mode: "required", type: "number" }, + }, + outputs: { + r: "number", + }, + run: (inputs, outputs) => { + outputs.r.next(inputs.n + 1); + }, +}; diff --git a/loader/fixture/moved-import-keeps-local-dependencies/moved/Wrapper.flyde b/loader/fixture/moved-import-keeps-local-dependencies/moved/Wrapper.flyde new file mode 100644 index 00000000..5500e649 --- /dev/null +++ b/loader/fixture/moved-import-keeps-local-dependencies/moved/Wrapper.flyde @@ -0,0 +1,31 @@ +--- +imports: + "./AddOne.flyde.js": AddOne +node: + id: AddOneWrapper + inputs: + n: + type: number + mode: required + outputs: + r: + type: number + instances: + - id: addOne + nodeId: AddOne + pos: + x: 0 + y: 0 + connections: + - from: + insId: __this + pinId: n + to: + insId: addOne + pinId: n + - from: + insId: addOne + pinId: r + to: + insId: __this + pinId: r diff --git a/loader/src/resolver/ReferencedNodeFinder.ts b/loader/src/resolver/ReferencedNodeFinder.ts index 46c2dbc2..b1e4e5b2 100644 --- a/loader/src/resolver/ReferencedNodeFinder.ts +++ b/loader/src/resolver/ReferencedNodeFinder.ts @@ -5,3 +5,7 @@ import { NodeInstance } from "@flyde/core/dist/node"; export type ReferencedNodeFinder = ( instance: NodeInstance, ) => FlydeNode | (CodeNode & { sourceCode: string }); + +export type NestedReferencedNodeFinder = ReferencedNodeFinder & { + forVisualNode?: (node: FlydeNode) => ReferencedNodeFinder; +}; diff --git a/loader/src/resolver/resolveEditorNode.ts b/loader/src/resolver/resolveEditorNode.ts index 328f6a04..9116cece 100644 --- a/loader/src/resolver/resolveEditorNode.ts +++ b/loader/src/resolver/resolveEditorNode.ts @@ -5,7 +5,7 @@ import { isVisualNode, VisualNode, } from "@flyde/core"; -import { ReferencedNodeFinder } from "./ReferencedNodeFinder"; +import { NestedReferencedNodeFinder } from "./ReferencedNodeFinder"; import { resolveEditorInstance } from "./resolveEditorInstance"; function dummyErrorNode(msg: string): EditorNode { @@ -24,8 +24,11 @@ function dummyErrorNode(msg: string): EditorNode { export function resolveEditorNode( visualNode: VisualNode, - findReferencedNode: ReferencedNodeFinder + findReferencedNode: NestedReferencedNodeFinder ): EditorVisualNode { + const finderForNode = (node: VisualNode) => + findReferencedNode.forVisualNode?.(node) ?? findReferencedNode; + return { ...visualNode, instances: visualNode.instances.map((instance) => { @@ -47,7 +50,10 @@ export function resolveEditorNode( } return { ...instance, - node: resolveEditorNode(referencedNode, findReferencedNode), + node: resolveEditorNode( + referencedNode, + finderForNode(referencedNode) + ), }; } } else { diff --git a/loader/src/resolver/resolveVisualNode.ts b/loader/src/resolver/resolveVisualNode.ts index 5d2a37d7..d45133a9 100644 --- a/loader/src/resolver/resolveVisualNode.ts +++ b/loader/src/resolver/resolveVisualNode.ts @@ -8,7 +8,7 @@ import { isInlineVisualNodeInstance, isCodeNode, } from "@flyde/core"; -import { ReferencedNodeFinder } from "./ReferencedNodeFinder"; +import { NestedReferencedNodeFinder } from "./ReferencedNodeFinder"; /* Recursively resolve all dependencies of a flow. For each node instance: @@ -17,9 +17,11 @@ Recursively resolve all dependencies of a flow. For each node instance: */ export function resolveVisualNode( visualNode: VisualNode, - nodeFinder: ReferencedNodeFinder, + nodeFinder: NestedReferencedNodeFinder, secrets: Record ): InternalVisualNode { + const finderForNode = (node: VisualNode) => + nodeFinder.forVisualNode?.(node) ?? nodeFinder; const internalInstances = visualNode.instances.map((instance) => { if (isInlineVisualNodeInstance(instance)) { @@ -43,7 +45,7 @@ export function resolveVisualNode( const node = nodeFinder(instance); if (isVisualNode(node)) { - const resolved = resolveVisualNode(node, nodeFinder, secrets); + const resolved = resolveVisualNode(node, finderForNode(node), secrets); return { ...instance, @@ -61,7 +63,7 @@ export function resolveVisualNode( if (isVisualNode(node)) { - const resolved = resolveVisualNode(node, nodeFinder, secrets); + const resolved = resolveVisualNode(node, finderForNode(node), secrets); return { ...instance, node: resolved, diff --git a/loader/src/resolver/server/findReferencedNodeServer.spec.ts b/loader/src/resolver/server/findReferencedNodeServer.spec.ts index 2913922f..265808ef 100644 --- a/loader/src/resolver/server/findReferencedNodeServer.spec.ts +++ b/loader/src/resolver/server/findReferencedNodeServer.spec.ts @@ -96,5 +96,5 @@ describe("findReferencedNodeServer", () => { }); function getFixturePath(path: string) { - return join(__dirname, "../../fixture", path); + return join(__dirname, "../../../fixture", path); } diff --git a/loader/src/resolver/server/findReferencedNodeServer.ts b/loader/src/resolver/server/findReferencedNodeServer.ts index ecfc88f6..e36b8e44 100644 --- a/loader/src/resolver/server/findReferencedNodeServer.ts +++ b/loader/src/resolver/server/findReferencedNodeServer.ts @@ -11,7 +11,7 @@ import { join } from "path"; import { existsSync, readFileSync } from "fs"; import { resolveImportablePaths } from "./resolveImportablePaths"; import { deserializeFlowByPath } from "../../serdes"; -import { ReferencedNodeFinder } from "./../ReferencedNodeFinder"; +import { NestedReferencedNodeFinder } from "./../ReferencedNodeFinder"; import { resolveCodeNodeDependencies } from "./serverUtils"; const LocalNodes = Object.values(_Nodes).reduce>( @@ -29,8 +29,10 @@ type NodeWithSource = FlydeNode & { sourcePath: string }; export function createServerReferencedNodeFinder( fullFlowPath: string -): ReferencedNodeFinder { - return (instance: NodeInstance): FlydeNode | (CodeNode & { sourceCode: string }) => { +): NestedReferencedNodeFinder { + const finder: NestedReferencedNodeFinder = ( + instance: NodeInstance + ): FlydeNode | (CodeNode & { sourceCode: string }) => { // Handle the case where instance.source is undefined (likely an inline node) if (!instance.source) { return instance as unknown as FlydeNode; @@ -145,7 +147,7 @@ export function createServerReferencedNodeFinder( `Node ID mismatch: expected ${instance.nodeId} but found ${node.id} in ${fullFilePath}` ); } - return node as unknown as FlydeNode; + return { ...node, sourcePath: fullFilePath } as unknown as FlydeNode; } // Update editorComponentBundlePath if it exists @@ -198,6 +200,13 @@ export function createServerReferencedNodeFinder( } } }; + + finder.forVisualNode = (node: FlydeNode) => { + const sourcePath = (node as { sourcePath?: string }).sourcePath; + return sourcePath ? createServerReferencedNodeFinder(sourcePath) : finder; + }; + + return finder; } function getLocalOrPackagePaths(fullFlowPath: string, importPath: string) { diff --git a/loader/src/resolver/server/resolveFlowSever.spec.ts b/loader/src/resolver/server/resolveFlowSever.spec.ts index a6c66c56..ad4ee28b 100644 --- a/loader/src/resolver/server/resolveFlowSever.spec.ts +++ b/loader/src/resolver/server/resolveFlowSever.spec.ts @@ -16,7 +16,7 @@ import { spiedOutput } from "@flyde/core/dist/test-utils"; import _ = require("lodash"); import { resolveVisualNode } from "../resolveVisualNode"; -const getFixturePath = (path: string) => join(__dirname, "../../fixture", path); +const getFixturePath = (path: string) => join(__dirname, "../../../fixture", path); describe("resolver", () => { it("resolves a blank .flyde file without any instances", () => { @@ -194,7 +194,27 @@ describe("resolver", () => { await new Promise(resolve => setTimeout(resolve, 50)); assert.equal(s.lastCall.args[0], 3); - }).timeout(100); + }).timeout(1000); + + it("resolves local imports relative to an imported visual node file", async () => { + const data = resolveFlowByPath( + getFixturePath("moved-import-keeps-local-dependencies/Root.flyde") + ); + + const [s, r] = spiedOutput(); + const n = dynamicNodeInput(); + execute({ + node: data, + inputs: { n }, + outputs: { r }, + }); + + n.subject.next(2); + + await new Promise(resolve => setTimeout(resolve, 50)); + + assert.equal(s.lastCall.args[0], 3); + }).timeout(1000); it("breaks on invalid schemas", () => { const invalidsRoot = getFixturePath("schema-validation/invalid");