API to get info if updates of palette modules are available and method to update them

Hello,

It would be nice to have API method to poll if there are new versions of the installed modules (palette) and also start the update process using API call.

Have not found any way to do this currently.

As a workaround, is it possible to use update shell command which can be executed from the node-red node?

Something like dependabot but for nodeRED?

Get Currently Installed Nodes.
https://nodered.org/docs/api/admin/methods/get/nodes/

Cross Reference it with (I might get told off here)
https://catalogue.nodered.org/catalogue.json

Install any update with
https://nodered.org/docs/api/admin/methods/post/nodes/

Doing this once a week will achieve what you need.
But I do suggest to not update to a major version of a node automatically

1 Like

Thanks,

Implemented the update checker using node-red http request and function nodes. At least the update checker part works now ok, which is probably enough at the moment.

Simplest way I know is to run npm outdated in the root directory of your node-red runtime --

srickus@ shrpi4: ~/.node-red
$ npm outdated
Package                        Current  Wanted  Latest  Location                                    Depended by
node-red-contrib-tts-ultimate    2.0.5   2.0.6   2.0.6  node_modules/node-red-contrib-tts-ultimate  .node-red

But that probably does not give you as much information as the catalogue.json, and it requires shell access to the server.

1 Like

You can add —json to that npm outdated command to get a parseable response

2 Likes

I run that from an exec node to check multiple Node-RED workspaces for updates.

Note the ; exit 0 appended to the command, because npm returns a non-zero error code when there are outdated packages. At least it did back then, maybe that has been fixed by now.

Here's the flow:

[{"id":"1487dca63022dc12","type":"inject","z":"72d63b27.b68a4c","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"node-red-main\",\"node-red-dashboard\",\"node-red-telegram\",\"node-red-test\"]","payloadType":"json","x":170,"y":780,"wires":[["2005275048f93e02"]]},{"id":"2005275048f93e02","type":"split","z":"72d63b27.b68a4c","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":300,"y":780,"wires":[["ca59066049875e09"]]},{"id":"bd7e955380b2eacd","type":"template","z":"72d63b27.b68a4c","name":"cmd","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"cd /data/workspaces/{{payload}} && npm outdated --json ; exit 0","output":"str","x":510,"y":780,"wires":[["32853aecedabc3a6"]]},{"id":"b9a613f66ca35c29","type":"join","z":"72d63b27.b68a4c","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":970,"y":780,"wires":[["82d20db4c33c79d4"]]},{"id":"32853aecedabc3a6","type":"exec","z":"72d63b27.b68a4c","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":650,"y":780,"wires":[["82c08df8c5472cc4"],[],[]]},{"id":"ca59066049875e09","type":"change","z":"72d63b27.b68a4c","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":405,"y":780,"wires":[["bd7e955380b2eacd"]],"l":false},{"id":"82c08df8c5472cc4","type":"json","z":"72d63b27.b68a4c","name":"","property":"payload","action":"obj","pretty":false,"x":790,"y":780,"wires":[["4382a30a4fd4f6f3"]]},{"id":"4382a30a4fd4f6f3","type":"change","z":"72d63b27.b68a4c","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.outdated","tot":"msg"},{"t":"set","p":"payload.name","pt":"msg","to":"topic","tot":"msg"},{"t":"set","p":"payload.outdated","pt":"msg","to":"[$map($keys(payload.outdated), function($val) { $merge([$lookup(payload.outdated, $val), {'name': $val}]) })]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":875,"y":780,"wires":[["b9a613f66ca35c29"]],"l":false},{"id":"0457e87d4ffefaa2","type":"debug","z":"72d63b27.b68a4c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1290,"y":780,"wires":[]},{"id":"82d20db4c33c79d4","type":"template","z":"72d63b27.b68a4c","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{#payload}}\n{{name}}\n{{#outdated}}\n  {{name}}: {{current}} {{wanted}} {{latest}}\n{{/outdated}}\n{{/payload}}","output":"str","x":1120,"y":780,"wires":[["0457e87d4ffefaa2"]]}]
1 Like

Hello,

Here is my flow which uses catalog and API call.

[
    {
        "id": "5b5d3ae9aaa21353",
        "type": "inject",
        "z": "a4b9efd5298274fe",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 200,
        "y": 160,
        "wires": [
            [
                "1fcdbe8213a96a73"
            ]
        ]
    },
    {
        "id": "1fcdbe8213a96a73",
        "type": "http request",
        "z": "a4b9efd5298274fe",
        "name": "get current modules",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "http://localhost:1880/nodes",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [
            {
                "keyType": "other",
                "keyValue": "Accept",
                "valueType": "other",
                "valueValue": "application/json"
            }
        ],
        "x": 440,
        "y": 160,
        "wires": [
            [
                "58dba85ba127816b"
            ]
        ]
    },
    {
        "id": "cf8fee0cd89dc449",
        "type": "debug",
        "z": "a4b9efd5298274fe",
        "name": "debug",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1110,
        "y": 160,
        "wires": []
    },
    {
        "id": "e019cb89d3613af9",
        "type": "http request",
        "z": "a4b9efd5298274fe",
        "name": "get catalogue",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://catalogue.nodered.org/catalogue.json",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [
            {
                "keyType": "other",
                "keyValue": "Accept",
                "valueType": "other",
                "valueValue": "application/json"
            }
        ],
        "x": 420,
        "y": 260,
        "wires": [
            [
                "1230974f9fc77120"
            ]
        ]
    },
    {
        "id": "58dba85ba127816b",
        "type": "function",
        "z": "a4b9efd5298274fe",
        "name": "parse current versions",
        "func": "var versions = new Object();\n\nfor (var i = 0; i < msg.payload.length; i++){\n    var o = msg.payload[i];\n    //ignore default modules\n    if (o.module != \"node-red\") {\n        versions[o.module] = o.version;\n    }\n}\n\nmsg.currentVersions = versions;\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 160,
        "wires": [
            [
                "e019cb89d3613af9"
            ]
        ]
    },
    {
        "id": "1230974f9fc77120",
        "type": "function",
        "z": "a4b9efd5298274fe",
        "name": "check if new versions available",
        "func": "var currentVersions = msg.currentVersions;\nvar newVersionsAvailable = new Array();\n\nfor (var i = 0; i < msg.payload.modules.length; i++){\n    var o = msg.payload.modules[i];\n    var currentVersion = currentVersions[o.id];\n\n    if (currentVersion!=null && currentVersion!= o.version) {\n        var mod = new Object();\n        mod[\"module\"] = o.id;\n        mod[\"version\"] = o.version;\n        mod[\"newVersion\"] = currentVersion;\n\n        newVersionsAvailable.push(mod);\n    }\n}\nmsg.payload = newVersionsAvailable;\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 730,
        "y": 260,
        "wires": [
            [
                "0319ffd36e3ffd17"
            ]
        ]
    },
    {
        "id": "0319ffd36e3ffd17",
        "type": "change",
        "z": "a4b9efd5298274fe",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "headers",
                "pt": "msg"
            },
            {
                "t": "delete",
                "p": "responseUrl",
                "pt": "msg"
            },
            {
                "t": "delete",
                "p": "redirectList",
                "pt": "msg"
            },
            {
                "t": "delete",
                "p": "retry",
                "pt": "msg"
            },
            {
                "t": "delete",
                "p": "statusCode",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 980,
        "y": 260,
        "wires": [
            [
                "cf8fee0cd89dc449"
            ]
        ]
    },
    {
        "id": "9b9506e63a0e430e",
        "type": "comment",
        "z": "a4b9efd5298274fe",
        "name": "readme",
        "info": "msg.payload contains array of modules\nwhich have new version available in \nthe catalog.",
        "x": 950,
        "y": 160,
        "wires": []
    }
]

Here is 3 flows that can be combined, it is set for my system but the exec commands could be adjusted for most systems.

The first flow check for npm node-red modules that need updating and outputs a js object.

The second can take that object and search and output packages that need updating. It can search on msg.wanted patch or minor or major and update to msg.latest true or false for wanted or latest. (work in progress on versions, still in test phase)

The final flow will take the output of module name and version and run npm install on each.

[{"id":"32853aecedabc3a6","type":"exec","z":"d1395164b4eec73e","command":"cd .node-red && npm outdated --json","addpay":"","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":530,"y":3600,"wires":[["82c08df8c5472cc4"],[],[]]},{"id":"c29555bd4ee9e837","type":"inject","z":"d1395164b4eec73e","name":"","props":[{"p":"payload"},{"p":"wanted","v":"patch","vt":"str"},{"p":"latest","v":"false","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"@flowforge/node-red-dashboard\":{\"current\":\"0.0.9\",\"wanted\":\"0.0.9\",\"latest\":\"0.7.0\",\"dependent\":\".node-red\",\"location\":\"/data/data/com.termux/files/home/.node-red/node_modules/@flowforge/node-red-dashboard\"},\"moment\":{\"current\":\"2.29.4\",\"wanted\":\"2.30.1\",\"latest\":\"2.30.1\",\"dependent\":\".node-red\",\"location\":\"/data/data/com.termux/files/home/.node-red/node_modules/moment\"},\"moment-timezone\":{\"current\":\"0.5.43\",\"wanted\":\"0.5.44\",\"latest\":\"0.5.44\",\"dependent\":\".node-red\",\"location\":\"/data/data/com.termux/files/home/.node-red/node_modules/moment-timezone\"},\"node-red-contrib-telegrambot\":{\"current\":\"15.1.7\",\"wanted\":\"15.1.8\",\"latest\":\"15.1.8\",\"dependent\":\".node-red\",\"location\":\"/data/data/com.termux/files/home/.node-red/node_modules/node-red-contrib-telegrambot\"},\"sqlite3\":{\"current\":\"5.1.6\",\"wanted\":\"5.1.7\",\"latest\":\"5.1.7\",\"dependent\":\".node-red\",\"location\":\"/data/data/com.termux/files/home/.node-red/node_modules/sqlite3\"}}","payloadType":"json","x":330,"y":3720,"wires":[["5462f69b0ba250e6"]]},{"id":"443d58b47b51c375","type":"inject","z":"d1395164b4eec73e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"name\":\"dddsdsdmoment-timezone\",\"version\":\"0.5.44\"},{\"name\":\"node-red-contrib-telegrambot\",\"version\":\"15.1.8\"},{\"name\":\"sqlite3\",\"version\":\"5.1.7\"}]","payloadType":"json","x":250,"y":3780,"wires":[["104f9e2dea7d014b"]]},{"id":"104f9e2dea7d014b","type":"split","z":"d1395164b4eec73e","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":370,"y":3780,"wires":[["0d05802c058056ea"]]},{"id":"0d05802c058056ea","type":"delay","z":"d1395164b4eec73e","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"120","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":550,"y":3780,"wires":[["0d55d90f55aa7b1f"]]},{"id":"43617bb8073f8f31","type":"function","z":"d1395164b4eec73e","name":"function 151","func":"return {flush: 1};","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":3900,"wires":[["0d05802c058056ea"]]},{"id":"0d55d90f55aa7b1f","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload.name","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"\"cd .node-red && npm install \" & $$.payload.name & \"@\" & $$.payload.version","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":3780,"wires":[["173fc04921fa5fac"]]},{"id":"173fc04921fa5fac","type":"exec","z":"d1395164b4eec73e","command":"","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":890,"y":3780,"wires":[["43617bb8073f8f31","153a240649129510"],[],["43617bb8073f8f31","89c70cd5bdf54f72","773a822f16e75e2c"]]},{"id":"153a240649129510","type":"switch","z":"d1395164b4eec73e","name":"","property":"rc.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1030,"y":3760,"wires":[["2637eea540660a22"]]},{"id":"89c70cd5bdf54f72","type":"debug","z":"d1395164b4eec73e","name":"debug 2468","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1090,"y":3680,"wires":[]},{"id":"773a822f16e75e2c","type":"switch","z":"d1395164b4eec73e","name":"","property":"rc.code","propertyType":"msg","rules":[{"t":"neq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1030,"y":3800,"wires":[["2637eea540660a22"]]},{"id":"2637eea540660a22","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.result","tot":"msg"},{"t":"set","p":"payload.name","pt":"msg","to":"topic","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1200,"y":3780,"wires":[["c96719fc516cad0f"]]},{"id":"c96719fc516cad0f","type":"join","z":"d1395164b4eec73e","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1350,"y":3780,"wires":[["3fe75f47bb18439c"]]},{"id":"3fe75f47bb18439c","type":"debug","z":"d1395164b4eec73e","name":"debug 2467","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1090,"y":3860,"wires":[]},{"id":"6c2b0329d1326a63","type":"inject","z":"d1395164b4eec73e","name":"","props":[{"p":"wanted","v":"patch","vt":"str"},{"p":"latest","v":"false","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":290,"y":3600,"wires":[["32853aecedabc3a6"]]},{"id":"5462f69b0ba250e6","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"[$$.payload.*.(\t    $lookfor := $lookup({\t        \"patch\": 2,\t        \"minor\":1,\t        \"major\": 0\t    }, $$.wanted);\t    $current := $split($.current, \".\");\t    $wanted := $split($.wanted, \".\");\t    $latest := $split($.latest, \".\");\t   ($wanted[$lookfor] > $current[$lookfor]) ? (\t        {\t            \"name\": $split($.location, \"node_modules/\")[1],\t            \"version\": $lookup($, ($$.latest ? \"latest\"  : \"wanted\"))\t        }\t    ):(\t        /*$wanted[$lookfor] = $current[$lookfor]  ##false*/\t    )\t)]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":3720,"wires":[["f03c7d1eb0edb07f"]]},{"id":"82c08df8c5472cc4","type":"json","z":"d1395164b4eec73e","name":"","property":"payload","action":"obj","pretty":false,"x":750,"y":3600,"wires":[["0457e87d4ffefaa2"]]},{"id":"0457e87d4ffefaa2","type":"debug","z":"d1395164b4eec73e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":3640,"wires":[]},{"id":"f03c7d1eb0edb07f","type":"debug","z":"d1395164b4eec73e","name":"debug 2466","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":3720,"wires":[]}]
1 Like