Quickly install a node package from a local folder & quickly uninstall
These flows let you install a node package from a local folder instead of the npmjs cloud. You can use this for local development testing.
There is also a flow to quickly uninstall as well.
Just change the path in the first flow and the module name in the 2nd.
[{"id":"e510af06f5e20a0f","type":"group","z":"f1a0c0de01","style":{"stroke":"#555555","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#f0f0f0"},"nodes":["d3a0c0de02","d3a0c0de03","d3a0c0de04","d3a0c0de05","d3a0c0de06","d3a0c0de07"],"x":14,"y":159,"w":1072,"h":122},{"id":"d3a0c0de02","type":"comment","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"Remove a module from the palette via DELETE /nodes/:module","info":"Set msg.moduleName in the inject node to the npm package name (scoped names such as @scope/name work too).\n\nBase URL comes from the NR_ADMIN_URL environment variable (include httpAdminRoot if you use one), falling back to http://localhost:1885.\nIf adminAuth is enabled, set NR_ADMIN_TOKEN to a bearer token from POST /auth/token.\n\nBefore removing: delete every node from this module (including config nodes) from all flows and deploy, otherwise the uninstall is refused.\nOnly modules installed in the Node-RED user directory can be removed.","x":270,"y":200,"wires":[]},{"id":"d3a0c0de03","type":"inject","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"Remove module","props":[{"p":"moduleName","v":"node-red-contrib-uibuilder","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":240,"wires":[["d3a0c0de04"]]},{"id":"d3a0c0de04","type":"function","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"Build DELETE request","func":"const name = msg.moduleName;\nif (!name || !/^(@[^/@\\s]+\\/)?[^/@\\s]+$/.test(name)) {\n node.error('msg.moduleName must be a valid npm package name', msg);\n return null;\n}\n\nconst base = (env.get('NR_ADMIN_URL') || 'http://localhost:1885').replace(/\\/+$/, '');\n\n// Scoped names keep their slash: /nodes/@scope/name\nmsg.method = 'DELETE';\nmsg.url = `${base}/nodes/${name}`;\ndelete msg.payload;\n\nmsg.headers = {};\nconst token = env.get('NR_ADMIN_TOKEN');\nif (token) {\n msg.headers.Authorization = 'Bearer ' + token;\n}\n\nnode.status({ fill: 'blue', shape: 'ring', text: 'removing ' + name });\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":240,"wires":[["d3a0c0de05"]]},{"id":"d3a0c0de05","type":"http request","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"DELETE /nodes/:module","method":"use","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":590,"y":240,"wires":[["d3a0c0de06"]]},{"id":"d3a0c0de06","type":"function","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"Format result","func":"const name = msg.moduleName;\n\nif (msg.statusCode === 204 || msg.statusCode === 200) {\n node.status({ fill: 'green', shape: 'dot', text: 'removed ' + name });\n msg.payload = { removed: name };\n return msg;\n}\n\nlet body = msg.payload;\ntry { body = JSON.parse(body); } catch (e) { /* keep raw text */ }\n\nconst hints = {\n 400: 'Uninstall failed - the module is probably still used by a flow (including config nodes), or it is not installed in the Node-RED user directory',\n 401: 'Not authorised - set NR_ADMIN_TOKEN to a valid bearer token',\n 404: 'Not found - check the module name, NR_ADMIN_URL and httpAdminRoot'\n};\n\nnode.status({ fill: 'red', shape: 'ring', text: 'failed (' + msg.statusCode + ')' });\nmsg.payload = {\n error: true,\n module: name,\n status: msg.statusCode,\n hint: hints[msg.statusCode],\n detail: body\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":240,"wires":[["d3a0c0de07"]]},{"id":"d3a0c0de07","type":"debug","z":"f1a0c0de01","g":"e510af06f5e20a0f","name":"Remove result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":960,"y":240,"wires":[]},{"id":"d4f7d7b9cf3ad11d","type":"group","z":"f1a0c0de01","style":{"stroke":"#555555","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#f0f0f0"},"nodes":["f1a0c0de02","f1a0c0de03","f1a0c0de04","f1a0c0de08","f1a0c0de09","f1a0c0de0a"],"x":14,"y":19,"w":1052,"h":122},{"id":"f1a0c0de02","type":"comment","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"Install a module from the server's disk via POST /nodes","info":"Top lane: module FOLDER on the server (JSON body, {module: '/abs/path'}).\nBottom lane: .tgz package on the server (multipart/form-data, field 'tarball').\n\nEdit the paths in the inject nodes and the URL in the http request node (include httpAdminRoot if you use one).\nIf adminAuth is enabled, set an NR_ADMIN_TOKEN environment variable (bearer token from POST /auth/token) before starting Node-RED.","x":240,"y":60,"wires":[]},{"id":"f1a0c0de03","type":"inject","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"Install from folder","props":[{"p":"modulePath","v":"/src/node-red-contrib-uibuilder","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":100,"wires":[["f1a0c0de04"]]},{"id":"f1a0c0de04","type":"function","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"Build JSON request","func":"const modulePath = msg.modulePath;\nif (!modulePath) {\n node.error('msg.modulePath is required', msg);\n return null;\n}\n\nmsg.payload = { module: modulePath };\nmsg.headers = {};\n\nconst base = (env.get('NR_ADMIN_URL') || 'http://localhost:1885').replace(/\\/+$/, '');\nmsg.method = 'POST';\nmsg.url = `${base}/nodes`;\n\nconst token = env.get('NR_ADMIN_TOKEN');\nif (token) {\n msg.headers.Authorization = 'Bearer ' + token;\n}\n\nnode.status({ fill: 'blue', shape: 'ring', text: 'installing...' });\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":100,"wires":[["f1a0c0de08"]]},{"id":"f1a0c0de08","type":"http request","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"POST /nodes","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":560,"y":100,"wires":[["f1a0c0de09"]]},{"id":"f1a0c0de09","type":"function","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"Format result","func":"const body = msg.payload;\n\nif (msg.statusCode === 200 && body && body.name) {\n node.status({ fill: 'green', shape: 'dot', text: body.name + '@' + body.version });\n msg.payload = {\n installed: body.name,\n version: body.version,\n nodes: (body.nodes || []).map(n => `${n.id} [${(n.types || []).join(', ')}]`)\n };\n return msg;\n}\n\nconst hints = {\n 401: 'Not authorised - set NR_ADMIN_TOKEN to a valid bearer token',\n 404: 'Not found - check the URL / httpAdminRoot, and that palette editing is enabled'\n};\n\nnode.status({ fill: 'red', shape: 'ring', text: 'failed (' + msg.statusCode + ')' });\nmsg.payload = {\n error: true,\n status: msg.statusCode,\n hint: hints[msg.statusCode],\n detail: body\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":100,"wires":[["f1a0c0de0a"]]},{"id":"f1a0c0de0a","type":"debug","z":"f1a0c0de01","g":"d4f7d7b9cf3ad11d","name":"Install result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":100,"wires":[]}]