Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions forge/db/controllers/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ module.exports = {
if (state.nodeRedVersion) {
device.set('nodeRedVersion', state.nodeRedVersion)
}
if (state.nodejsVersion) {
device.set('nodejsVersion', state.nodejsVersion)
}
device.set('editorAffinity', state.affinity || null)
if (!state.snapshot || state.snapshot === '0') {
if (device.activeSnapshotId !== null) {
Expand Down
15 changes: 15 additions & 0 deletions forge/db/migrations/20260622-01-add-device-nodejs-ver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { DataTypes } = require('sequelize')

module.exports = {
/**
* upgrade database
* @param {QueryInterface} context Sequelize.QueryInterface
*/
up: async (context, Sequelize) => {
await context.addColumn('Devices', 'nodejsVersion', {
type: DataTypes.STRING,
allowNull: true
})
},
down: async (context, Squelize) => { }
}
12 changes: 9 additions & 3 deletions forge/db/models/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ module.exports = {
get () {
return this.ownerType === 'application'
}
}
},
nodejsVersion: { type: DataTypes.STRING, allowNull: true }
},
associations: function (M) {
this.belongsTo(M.Application)
Expand Down Expand Up @@ -403,8 +404,13 @@ module.exports = {
let nodeRedVersion = '3.0.2' // default to older Node-RED
if (SemVer.satisfies(SemVer.coerce(this.agentVersion), '>=1.11.2')) {
// 1.11.2 includes fix for ESM loading of GOT, so lets use 'latest' as before
// pinning to NR 4.1.x while we fix the device agent
nodeRedVersion = '~4.1.11'
if (this.nodejsVersion) {
if (SemVer.satisfies(SemVer.coerce(this.nodejsVersion), '>=22.9.0')) {
nodeRedVersion = 'latest'
}
} else {
nodeRedVersion = '~4.1.11'
}
}
return nodeRedVersion
},
Expand Down
31 changes: 30 additions & 1 deletion test/unit/forge/routes/api/device_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ describe('Device API', async function () {
result.should.have.property('modules').and.be.an.Object()
result.modules.should.have.property('node-red', '3.0.2')
})
it('agent >= v1.11.2 is instructed to use Node-RED@latest', async function () {
it('agent >= v1.11.2 is instructed to use Node-RED@4.1.11 if NodeJS version unknown', async function () {
const agentVersion = '1.11.2' // min agent version required for NR 3.1 (as this agent handles ESM issue)
const device = await createDevice({ name: 'Ad1a', type: '', team: TestObjects.ATeam.hashid, as: TestObjects.tokens.alice, agentVersion })
// assign the new device to application
Expand All @@ -793,6 +793,35 @@ describe('Device API', async function () {
result.should.have.property('modules').and.be.an.Object()
result.modules.should.have.property('node-red', '~4.1.11')
})
it('agent >= v1.11.2 is instructed to use Node-RED@latest if NodeJS >=22.9.0', async function () {
const agentVersion = '1.11.2' // min agent version required for NR 3.1 (as this agent handles ESM issue)
const device = await createDevice({ name: 'Ad1b', type: '', team: TestObjects.ATeam.hashid, as: TestObjects.tokens.alice, agentVersion })
const dbDevice = await app.db.models.Device.byId(device.id)
dbDevice.nodejsVersion = 'v24.0.0'
await dbDevice.save()
// assign the new device to application
await app.inject({
method: 'PUT',
url: `/api/v1/devices/${device.id}`,
body: {
application: TestObjects.Application1.hashid
},
cookies: { sid: TestObjects.tokens.bob }
})
// get the snapshot for this device
const response = await app.inject({
method: 'GET',
url: `/api/v1/devices/${device.id}/live/snapshot`,
headers: {
authorization: `Bearer ${device.credentials.token}`
}
})
const result = response.json()
result.should.have.property('id')
result.should.have.property('name', 'Starter Snapshot')
result.should.have.property('modules').and.be.an.Object()
result.modules.should.have.property('node-red', 'latest')
})
it('snapshot uploaded without node-red dependency is always delivered to a device with the node-red:version', async function () {
const agentVersion = '1.11.2' // min agent version required for NR 3.1 (as this agent handles ESM issue)
const device = await createDevice({ name: 'Ad1a', type: '', team: TestObjects.ATeam.hashid, as: TestObjects.tokens.alice, agentVersion })
Expand Down
Loading