Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
653a1ee
Build a cache of latest NR packages
hardillb Jul 7, 2026
da408e9
fix lint
hardillb Jul 8, 2026
4075f6d
Build a cache of latest NR packages
hardillb Jul 7, 2026
6e1b9a7
fix lint
hardillb Jul 8, 2026
a30fe05
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 8, 2026
5bd6b5b
Merge branch 'cache-catalogue-versions' of https://github.com/FlowFus…
hardillb Jul 8, 2026
4c0171a
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 17, 2026
f6381d8
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 17, 2026
e24e3c0
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 27, 2026
02679f0
Build a cache of latest NR packages
hardillb Jul 7, 2026
5cd8d1f
fix lint
hardillb Jul 8, 2026
42c2e64
Build a cache of latest NR packages
hardillb Jul 7, 2026
362eb3a
fix lint
hardillb Jul 8, 2026
fc15dea
Merge branch 'cache-catalogue-versions' of https://github.com/FlowFus…
hardillb Jul 27, 2026
28fd7fb
Installed node settings hook (#7880)
hardillb Jul 27, 2026
126ced1
Merge branch 'cache-catalogue-versions' of https://github.com/FlowFus…
hardillb Jul 27, 2026
06ac860
fix lint
hardillb Jul 27, 2026
0b92a18
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 27, 2026
d0a8fa3
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 27, 2026
097a3ad
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 28, 2026
8fa134a
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 28, 2026
17398ed
spell destroy correctly
hardillb Jul 28, 2026
b132cb3
Merge branch 'main' into cache-catalogue-versions
hardillb Jul 29, 2026
e6a0dc1
Fix db migrations
hardillb Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions forge/db/migrations/20260729-01-add-node-red-node-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { DataTypes } = require('sequelize')

module.exports = {
/**
* upgrade database
* @param {QueryInterface} context Sequelize.QueryInterface
*/
up: async (context, Sequelize) => {
await context.createTable('NodeREDNodeVersions', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
ownerId: {
type: DataTypes.STRING,
allowNull: false
},
ownerType: {
type: DataTypes.STRING,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
currentVersion: {
type: DataTypes.STRING,
allowNull: false
},
latestVersion: {
type: DataTypes.STRING,
allowNull: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false
}
})
},
down: async (context, Sequelize) => {}
}
66 changes: 66 additions & 0 deletions forge/db/models/NodeREDNodeVersions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Which nodes are installed in which Instances
* @namespace forge.db.models.NodeREDNodeVersion
* // type helpers for design time help and error checking
* @typedef {import('sequelize').Model} Model
* @typedef {import('sequelize').ModelAttributes} ModelAttributes
* @typedef {import('sequelize').SchemaOptions} SchemaOptions
* @typedef {import('sequelize').ModelIndexesOptions} ModelIndexesOptions
* @typedef {import('sequelize').InitOptions} InitOptions
* @typedef {import('sequelize').ModelScopeOptions} ModelScopeOptions
* @typedef {{name: string, schema: ModelAttributes, model: Model, indexes?: ModelIndexesOptions[], scopes?: ModelScopeOptions, options?: InitOptions}} FFModel
*/

const { DataTypes } = require('sequelize')

/** @type {FFModel} */
module.exports = {
name: 'NodeREDNodeVersions',
schema: {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
ownerId: {
type: DataTypes.STRING,
allowNull: false
},
ownerType: {
type: DataTypes.STRING,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
currentVersion: {
type: DataTypes.STRING,
allowNull: false
},
latestVersion: {
type: DataTypes.STRING,
allowNull: true
}
},
associations: function (M) {
this.belongsTo(M.Project, { foreignKey: 'ownerId', constraints: false })
this.belongsTo(M.Device, { foreignKey: 'ownerId', constraints: false })
},
finders: function (M) {
return {
static: {
updateAllLatest: async (name, version) => {
await this.update({
latestVersion: version
}, {
where: {
name
}
})
}
},
instance: {}
}
}
}
7 changes: 7 additions & 0 deletions forge/db/models/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ module.exports = {
app.log.error(`Error removing MCPRegistrations for deleted instance ${project.id}: ${err.message}`)
}
}
// Remove version info when Project removed
await app.db.models.NodeREDNodeVersions.destroy({
where: {
ownerType: 'instance',
ownerId: project.id
}
})
}
}
},
Expand Down
30 changes: 30 additions & 0 deletions forge/db/models/StorageSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
const { DataTypes } = require('sequelize')

const NODE_VERSION_CACHE = 'nodes-latestVersion'

module.exports = {
name: 'StorageSettings',
schema: {
Expand All @@ -23,5 +25,33 @@ module.exports = {
}
}
}
},
hooks: function (M, app) {
return {
afterUpdate: async (storageSettings, options) => {
try {
const cache = app.caches.getCache(NODE_VERSION_CACHE)
await storageSettings.reload({
attributes: ['id', 'settings', 'ProjectId']
})
const settingsObj = JSON.parse(storageSettings.settings)
const nodes = settingsObj.nodes
for (const n of Object.keys(nodes)) {
if (n !== 'node-red') {
const latest = await cache.get(n)
await app.db.models.NodeREDNodeVersions.upsert({
ownerId: storageSettings.ProjectId,
ownerType: 'instance',
name: n,
currentVersion: nodes[n].version,
latestVersion: latest
})
}
}
} catch (err) {
// swallowing error as this is an async hook triggered by an update to the underlying table
}
}
}
}
}
3 changes: 2 additions & 1 deletion forge/db/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ const modelTypes = [
'BrokerCredentials',
'MQTTTopicSchema',
'TeamBrokerAgent',
'AsyncLoginSession'
'AsyncLoginSession',
'NodeREDNodeVersions'
]

// A local map of the known models.
Expand Down
5 changes: 5 additions & 0 deletions forge/ee/lib/bom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports.init = async function (app) {
app.config.features.register('autoNodeUpdate', true, true)

app.housekeeper.registerTask(require('./tasks/cache-catalogues'))
}
53 changes: 53 additions & 0 deletions forge/ee/lib/bom/tasks/cache-catalogues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const axios = require('axios')

const { decodeCertifiedNodesToken } = require('../../../../lib/npm')

const NODE_VERSION_CACHE = 'nodes-latestVersion'

module.exports = {
name: 'cacheCatalogues',
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)
// 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 { 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
const current = await cache.get(name)
if (current && current !== version) {
await app.models.NodeREDNodeVersions.updateAllLatest(name, version)
}
cache.set(name, version)
}
}
} catch (err) {
app.log.debug(`Problem reading catalogue ${cat}, ${err.toString()}`)
}
}
}
}
2 changes: 2 additions & 0 deletions forge/ee/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ module.exports = fp(async function (app, opts) {
(Object.prototype.hasOwnProperty.call(app.config?.expert ?? {}, 'insights') ? !!app.config?.expert?.insights?.enabled : true)

app.config.features.register('expertInsights', isInsightsEnabled ?? false, true)

require('./bom').init(app)
}

// Set the Team Library Feature Flag
Expand Down
Loading