From 653a1eec6e1dd504fe38f1faa873a86893c1540b Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 7 Jul 2026 11:27:22 +0100 Subject: [PATCH 1/4] Build a cache of latest NR packages fixes #7735 --- forge/ee/lib/bom/index.js | 5 +++ forge/ee/lib/bom/tasks/cache-catalogues.js | 51 ++++++++++++++++++++++ forge/ee/lib/index.js | 2 + 3 files changed, 58 insertions(+) create mode 100644 forge/ee/lib/bom/index.js create mode 100644 forge/ee/lib/bom/tasks/cache-catalogues.js diff --git a/forge/ee/lib/bom/index.js b/forge/ee/lib/bom/index.js new file mode 100644 index 0000000000..4883d21215 --- /dev/null +++ b/forge/ee/lib/bom/index.js @@ -0,0 +1,5 @@ +module.exports.init = async function (app) { + app.config.features.register('autoNodeUpdate', true, true) + + app.housekeeper.registerTask(require('./tasks/cache-catalogues')) +} diff --git a/forge/ee/lib/bom/tasks/cache-catalogues.js b/forge/ee/lib/bom/tasks/cache-catalogues.js new file mode 100644 index 0000000000..83f9dc892f --- /dev/null +++ b/forge/ee/lib/bom/tasks/cache-catalogues.js @@ -0,0 +1,51 @@ +const { decodeCertifiedNodesToken } = require('../../../../lib/npm') +const { randomInt } = require("../../../../housekeeper/utils") + +const axios = require('axios') + +const NODE_VERSION_CACHE = 'nodes-latestVersion' + +module.exports = { + name: 'cacheCatalogues', + //startup: false, + startup: 1000 * 10, + schedule: `*/5 * * * *`, // '35 21 * * *' // 21:35 every day + run: async function (app) { + app.caches.createCache(NODE_VERSION_CACHE) + const cache = app.caches.getCache(NODE_VERSION_CACHE) + // Starting list of catalogues + const cataloguesList = [ + 'https://catalogue.nodered.org/catalogue.json' + ] + + const platformNPMEnabled = !!app.config.features.enabled('certifiedNodes', false) && + !!app.config.features.enabled('ffNodes', false) && + !!app.settings.get('platform:ff-npm-registry:token') + if (platformNPMEnabled) { + // gets platform wide certified nodes catalogues but not per team override + const { token, catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') + cataloguesList.push(...catalogues) + } + + for (const cat of cataloguesList) { + app.log.debug(`Checking catalogue ${cat} for latest node versions`) + try { + const res = await axios.get(cat, { + headers: { + Accept: 'application/json' + } + }) + if (res.status === 200) { + const modules = res.data.modules + for (const mod of modules) { + const name = mod.id + const version = mod.version + cache.set(name, version) + } + } + } catch (err) { + app.log.debug(`Problem reading catalogue ${cat}, ${err.toString()}`) + } + } + } +} \ No newline at end of file diff --git a/forge/ee/lib/index.js b/forge/ee/lib/index.js index 65abf24ef8..86f2d2a9e3 100644 --- a/forge/ee/lib/index.js +++ b/forge/ee/lib/index.js @@ -58,6 +58,8 @@ module.exports = fp(async function (app, opts) { // temporary until FF Expert Insights can be enabled on Self Hosted EE instance const isInsightsEnabled = isAiEnabled && app.config?.expert?.enabled && app.config?.expert?.insights?.enabled app.config.features.register('expertInsights', isInsightsEnabled ?? false, false) + + require('./bom').init(app) } // Set the Team Library Feature Flag From da408e9458e892870af33ae0024cf0be6d2d3be4 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 8 Jul 2026 09:17:34 +0100 Subject: [PATCH 2/4] fix lint --- forge/ee/lib/bom/tasks/cache-catalogues.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/forge/ee/lib/bom/tasks/cache-catalogues.js b/forge/ee/lib/bom/tasks/cache-catalogues.js index 83f9dc892f..e19c3ffe25 100644 --- a/forge/ee/lib/bom/tasks/cache-catalogues.js +++ b/forge/ee/lib/bom/tasks/cache-catalogues.js @@ -1,15 +1,13 @@ -const { decodeCertifiedNodesToken } = require('../../../../lib/npm') -const { randomInt } = require("../../../../housekeeper/utils") - const axios = require('axios') +const { decodeCertifiedNodesToken } = require('../../../../lib/npm') + const NODE_VERSION_CACHE = 'nodes-latestVersion' module.exports = { name: 'cacheCatalogues', - //startup: false, - startup: 1000 * 10, - schedule: `*/5 * * * *`, // '35 21 * * *' // 21:35 every day + startup: 1000 * 90, // 90 seconds after start up to ensure cache populated + schedule: '47 21 * * *', // Update at 21:47 every day (if not done with a restart) run: async function (app) { app.caches.createCache(NODE_VERSION_CACHE) const cache = app.caches.getCache(NODE_VERSION_CACHE) @@ -23,11 +21,11 @@ module.exports = { !!app.settings.get('platform:ff-npm-registry:token') if (platformNPMEnabled) { // gets platform wide certified nodes catalogues but not per team override - const { token, catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') + const { catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') cataloguesList.push(...catalogues) } - for (const cat of cataloguesList) { + for (const cat of cataloguesList) { app.log.debug(`Checking catalogue ${cat} for latest node versions`) try { const res = await axios.get(cat, { @@ -42,10 +40,10 @@ module.exports = { const version = mod.version cache.set(name, version) } - } + } } catch (err) { app.log.debug(`Problem reading catalogue ${cat}, ${err.toString()}`) } } } -} \ No newline at end of file +} From 4075f6dfe4f27a671fb8196631b3e3981366b982 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 7 Jul 2026 11:27:22 +0100 Subject: [PATCH 3/4] Build a cache of latest NR packages fixes #7735 --- forge/ee/lib/bom/index.js | 5 +++ forge/ee/lib/bom/tasks/cache-catalogues.js | 51 ++++++++++++++++++++++ forge/ee/lib/index.js | 2 + 3 files changed, 58 insertions(+) create mode 100644 forge/ee/lib/bom/index.js create mode 100644 forge/ee/lib/bom/tasks/cache-catalogues.js diff --git a/forge/ee/lib/bom/index.js b/forge/ee/lib/bom/index.js new file mode 100644 index 0000000000..4883d21215 --- /dev/null +++ b/forge/ee/lib/bom/index.js @@ -0,0 +1,5 @@ +module.exports.init = async function (app) { + app.config.features.register('autoNodeUpdate', true, true) + + app.housekeeper.registerTask(require('./tasks/cache-catalogues')) +} diff --git a/forge/ee/lib/bom/tasks/cache-catalogues.js b/forge/ee/lib/bom/tasks/cache-catalogues.js new file mode 100644 index 0000000000..83f9dc892f --- /dev/null +++ b/forge/ee/lib/bom/tasks/cache-catalogues.js @@ -0,0 +1,51 @@ +const { decodeCertifiedNodesToken } = require('../../../../lib/npm') +const { randomInt } = require("../../../../housekeeper/utils") + +const axios = require('axios') + +const NODE_VERSION_CACHE = 'nodes-latestVersion' + +module.exports = { + name: 'cacheCatalogues', + //startup: false, + startup: 1000 * 10, + schedule: `*/5 * * * *`, // '35 21 * * *' // 21:35 every day + run: async function (app) { + app.caches.createCache(NODE_VERSION_CACHE) + const cache = app.caches.getCache(NODE_VERSION_CACHE) + // Starting list of catalogues + const cataloguesList = [ + 'https://catalogue.nodered.org/catalogue.json' + ] + + const platformNPMEnabled = !!app.config.features.enabled('certifiedNodes', false) && + !!app.config.features.enabled('ffNodes', false) && + !!app.settings.get('platform:ff-npm-registry:token') + if (platformNPMEnabled) { + // gets platform wide certified nodes catalogues but not per team override + const { token, catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') + cataloguesList.push(...catalogues) + } + + for (const cat of cataloguesList) { + app.log.debug(`Checking catalogue ${cat} for latest node versions`) + try { + const res = await axios.get(cat, { + headers: { + Accept: 'application/json' + } + }) + if (res.status === 200) { + const modules = res.data.modules + for (const mod of modules) { + const name = mod.id + const version = mod.version + cache.set(name, version) + } + } + } catch (err) { + app.log.debug(`Problem reading catalogue ${cat}, ${err.toString()}`) + } + } + } +} \ No newline at end of file diff --git a/forge/ee/lib/index.js b/forge/ee/lib/index.js index 65abf24ef8..86f2d2a9e3 100644 --- a/forge/ee/lib/index.js +++ b/forge/ee/lib/index.js @@ -58,6 +58,8 @@ module.exports = fp(async function (app, opts) { // temporary until FF Expert Insights can be enabled on Self Hosted EE instance const isInsightsEnabled = isAiEnabled && app.config?.expert?.enabled && app.config?.expert?.insights?.enabled app.config.features.register('expertInsights', isInsightsEnabled ?? false, false) + + require('./bom').init(app) } // Set the Team Library Feature Flag From 6e1b9a712e36b3bd07f7853d877f375f1baf0b8c Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 8 Jul 2026 09:17:34 +0100 Subject: [PATCH 4/4] fix lint --- forge/ee/lib/bom/tasks/cache-catalogues.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/forge/ee/lib/bom/tasks/cache-catalogues.js b/forge/ee/lib/bom/tasks/cache-catalogues.js index 83f9dc892f..e19c3ffe25 100644 --- a/forge/ee/lib/bom/tasks/cache-catalogues.js +++ b/forge/ee/lib/bom/tasks/cache-catalogues.js @@ -1,15 +1,13 @@ -const { decodeCertifiedNodesToken } = require('../../../../lib/npm') -const { randomInt } = require("../../../../housekeeper/utils") - const axios = require('axios') +const { decodeCertifiedNodesToken } = require('../../../../lib/npm') + const NODE_VERSION_CACHE = 'nodes-latestVersion' module.exports = { name: 'cacheCatalogues', - //startup: false, - startup: 1000 * 10, - schedule: `*/5 * * * *`, // '35 21 * * *' // 21:35 every day + startup: 1000 * 90, // 90 seconds after start up to ensure cache populated + schedule: '47 21 * * *', // Update at 21:47 every day (if not done with a restart) run: async function (app) { app.caches.createCache(NODE_VERSION_CACHE) const cache = app.caches.getCache(NODE_VERSION_CACHE) @@ -23,11 +21,11 @@ module.exports = { !!app.settings.get('platform:ff-npm-registry:token') if (platformNPMEnabled) { // gets platform wide certified nodes catalogues but not per team override - const { token, catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') + const { catalogues } = decodeCertifiedNodesToken(app.settings.get('platform:ff-npm-registry:token'), 'placeholder') cataloguesList.push(...catalogues) } - for (const cat of cataloguesList) { + for (const cat of cataloguesList) { app.log.debug(`Checking catalogue ${cat} for latest node versions`) try { const res = await axios.get(cat, { @@ -42,10 +40,10 @@ module.exports = { const version = mod.version cache.set(name, version) } - } + } } catch (err) { app.log.debug(`Problem reading catalogue ${cat}, ${err.toString()}`) } } } -} \ No newline at end of file +}