I usually work with different instances of Node-RED and I like to create subflows to encapsulate the code. However, I always end up with my palette of nodes with something like this:
I've read this thread on the official forum where it's said that the behavior of duplicating subflows while importing them was the way Node-RED worked that time. However, this thread is from September 2019, is this behavior still true? If yes, is there any "non-official" way of solving this problem? My guess would be entering inside a file where I find and replace all "exec node (2)" and "exec node (3)" to "exec node" just so when I restart node-red all those subflows will have become "exec node" and I will be able to erase the other ones because they're not being used anywhere anymore. Is it possible?
After looking to the flow_server.json
file inside the Node-RED folder for a while I've figured out a solution to this problem. Basically, you need to do the following:
flow_server.json
inside your .node-red
folder (if the projects feature is enabled it's inside the folder projects)PS: This solution assumes that the duplicated nodes are identical. I don't really know the consequences of following this procedure with different subflows.
I've also created a subflow that does it in an easier way for me. It relies on activating the fs
library for Node-RED. Backup your flow_server.json
file before experimenting with it...
[{"id":"b8749b41.f740e","type":"subflow","name":"fix duplication (2)","info":"","category":"essencial","in":[{"x":520,"y":460,"wires":[{"id":"948937d8.1051a8"}]}],"out":[],"env":[{"name":"flowFile","type":"str","value":"/home/user/.node-red/flow_server.json"},{"name":"subflowToKeep","type":"str","value":""},{"name":"subflowToSubstitute","type":"str","value":""}],"color":"#A6D2FF","icon":"node-red/swap.svg"},{"id":"948937d8.1051a8","type":"function","z":"b8749b41.f740e","name":"fix duplication","func":"const fs = global.get('fs');\n\nconst flowFile = env.get(\"flowFile\");\nconst subflowToKeep = env.get(\"subflowToKeep\")\nconst subflowToSubstitute = env.get(\"subflowToSubstitute\");\n\nlet flowString;\nlet flowObject;\n\ntry {\n flowString = fs.readFileSync(flowFile, 'utf8');\n flowObject = JSON.parse(flowString);\n\n if (checkIfSubflowExists(subflowToKeep) && checkIfSubflowExists(subflowToSubstitute)) {\n let subflowToKeepString = \"\";\n let subflowToSubstituteString = \"\";\n flowObject.forEach(({ type, name, id }) => {\n if ( type === 'subflow'){\n switch(name) {\n case subflowToKeep:\n subflowToKeepString = `subflow:${id}`\n node.warn(name + \"- \" + subflowToKeepString);\n break;\n case subflowToSubstitute:\n subflowToSubstituteString = `subflow:${id}`\n node.warn(name + \"- \" + subflowToSubstituteString);\n break;\n }\n }\n });\n\n if ( subflowToSubstituteString !== \"\" && subflowToKeepString !== \"\" ){\n var regex = new RegExp(subflowToSubstituteString, \"g\");\n if ( flowString.search(regex) >= 0 ){\n flowString = flowString.replace(regex, subflowToKeepString);\n fs.writeFileSync(flowFile, flowString );\n node.warn(\"nodes updated! restart node-red.\")\n return msg;\n }\n }\n }\n node.warn(\"Nothing to do. Check the input parameters correctly...\");\n return msg;\n} catch(err){\n node.warn(err)\n return msg;\n}\n\n// auxiliary functions\nfunction checkIfSubflowExists(key){\n let hasKey = false;\n flowObject.forEach(({ type, name }) => {\n if ( type === 'subflow'){\n if ( name === key ){\n hasKey = true;\n }\n }\n });\n return hasKey;\n}\n","outputs":0,"noerr":0,"x":860,"y":460,"wires":[]},{"id":"a437e02b.0f67d8","type":"subflow:b8749b41.f740e","z":"26278f81.44628","name":"","env":[],"x":1190,"y":380,"wires":[]}]