diff --git a/lib/launcher.js b/lib/launcher.js index c6c465b..2c0e5ec 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -6,6 +6,7 @@ const LogBuffer = require('./logBuffer') const { getSettingsFile } = require('./runtimeSettings') const SampleBuffer = require('./resources/sampleBuffer') const resourceSample = require('./resources/sample') +const hasProperty = (object, property) => !!(object && Object.prototype.hasOwnProperty.call(object, property)) /** The point at which we check to see if we are in a boot loop */ const MAX_RESTART_COUNT = 5 @@ -86,6 +87,8 @@ class Launcher { this.sampleBuffer = new SampleBuffer(this.options.sampleBufferMax || 2520) this.cpuAuditLogged = 0 this.memoryAuditLogged = 0 + + this.disableAutoSafeMode = false } /** @type {Number} */ @@ -101,7 +104,7 @@ class Launcher { const settingsURL = `${this.options.forgeURL}/api/v1/projects/${this.options.project}/settings` let newSettings try { - newSettings = await got(settingsURL, { + newSettings = await got.get(settingsURL, { headers: { authorization: `Bearer ${this.options.token}` } @@ -149,7 +152,12 @@ class Launcher { nodesDir.push(require.main.path) this.settings.nodesDir = nodesDir - + if (hasProperty(this.settings, 'disableAutoSafeMode') && typeof this.settings.disableAutoSafeMode === 'boolean') { + this.disableAutoSafeMode = this.settings.disableAutoSafeMode + } + if (this.disableAutoSafeMode === true) { + this.logBuffer.add({ level: 'system', msg: 'Auto Safe Mode is disabled.' }) + } const settingsFileContent = getSettingsFile(this.settings) const settingsPath = path.join(this.settings.rootDir, this.settings.userDir, 'settings.js') this.targetState = this.settings.state || States.RUNNING @@ -464,8 +472,8 @@ class Launcher { this.logBuffer.add({ level: 'system', msg: `Node-RED unexpectedly stopped after: ${Math.round(duration / 1000)}s` }) - // if start count == MAX_RESTART_COUNT, then check for boot loop - if (this.startTimes.length === MAX_RESTART_COUNT) { + // if auto-safe-mode is not disabled && start count == MAX_RESTART_COUNT, then check for boot loop + if (this.disableAutoSafeMode !== true && this.startTimes.length === MAX_RESTART_COUNT) { // calculate the average runtime const avg = this.runDurations.reduce((a, b) => a + b, 0) / this.runDurations.length diff --git a/test/unit/lib/launcher_spec.js b/test/unit/lib/launcher_spec.js index 5d53455..a5d165d 100644 --- a/test/unit/lib/launcher_spec.js +++ b/test/unit/lib/launcher_spec.js @@ -1,5 +1,7 @@ const should = require('should') // eslint-disable-line const sinon = require('sinon') +const got = require('got') +const fs = require('fs') const launcher = require('../../../lib/launcher.js') describe('Launcher', function () { @@ -7,6 +9,9 @@ describe('Launcher', function () { const l = new launcher.Launcher({}) should.exist(l) }) + this.afterEach(function () { + sinon.restore() + }) describe('health check', function () { it('has a default value', async function () { const l = new launcher.Launcher({}) @@ -14,19 +19,44 @@ describe('Launcher', function () { }) it('can be set by user', async function () { const l = new launcher.Launcher({}) - sinon.stub(l, 'loadSettings').callsFake(() => { - l.settings = { - healthCheckInterval: 1234 + l.should.have.property('healthCheckInterval', 7499) // initial/default value is 7499 + sinon.stub(fs, 'writeFileSync').callsFake(() => {}) + sinon.stub(l, 'updatePackage').callsFake(() => {}) + sinon.stub(got, 'get').callsFake(() => { + return { + json () { + return { + forgeURL: 'http://localhost:1880', + rootDir: '/path/to/node-red', + userDir: '/path/to/.node-red', + settings: { + palette: {} + }, + healthCheckInterval: 1234 + } + } } }) await l.loadSettings() - l.should.have.property('healthCheckInterval', 1234) + l.should.have.property('healthCheckInterval', 1234) // when loaded from API, it should now be updated }) it('cannot be less than 1 second', async function () { const l = new launcher.Launcher({}) - sinon.stub(l, 'loadSettings').callsFake(() => { - l.settings = { - healthCheckInterval: 999 + sinon.stub(fs, 'writeFileSync').callsFake(() => {}) + sinon.stub(l, 'updatePackage').callsFake(() => {}) + sinon.stub(got, 'get').callsFake(() => { + return { + json () { + return { + forgeURL: 'http://localhost:1880', + rootDir: '/path/to/node-red', + userDir: '/path/to/.node-red', + settings: { + palette: {} + }, + healthCheckInterval: 999 + } + } } }) await l.loadSettings() @@ -34,4 +64,34 @@ describe('Launcher', function () { l.should.have.property('healthCheckInterval', 7499) }) }) + + describe('disable auto safe mode', function () { + it('is disabled by default', async function () { + const l = new launcher.Launcher({}) + l.should.have.property('disableAutoSafeMode', false) + }) + it('can be set by user', async function () { + const l = new launcher.Launcher({}) + sinon.stub(fs, 'writeFileSync').callsFake(() => {}) + sinon.stub(l, 'updatePackage').callsFake(() => {}) + sinon.stub(got, 'get').callsFake(() => { + return { + json () { + return { + forgeURL: 'http://localhost:1880', + rootDir: '/path/to/node-red', + userDir: '/path/to/.node-red', + settings: { + palette: {} + }, + disableAutoSafeMode: true + } + } + } + }) + + await l.loadSettings() + l.should.have.property('disableAutoSafeMode', true) + }) + }) })