Skip to content

Commit 156c67a

Browse files
committed
feat(local): handle v4 boot events in local process manager
no issue: - Ghost in v4+ emits two events on successful start: 'started' and 'ready', so we need to handle both - The error message structure in v4 events also changes, so we need to handle things there as well
1 parent 1c63c95 commit 156c67a

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

lib/commands/run.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const spawn = require('child_process').spawn;
2+
const semver = require('semver');
23
const Command = require('../command');
34

45
class RunCommand extends Command {
@@ -50,14 +51,16 @@ class RunCommand extends Command {
5051
}
5152

5253
useDirect(instance) {
54+
const isV4 = semver.major(instance.version) >= 4;
55+
5356
this.child = spawn(process.execPath, ['current/index.js'], {
5457
cwd: instance.dir,
5558
stdio: [0, 1, 'pipe', 'ipc']
5659
});
5760

58-
let started = false;
61+
let started = false, ready = false;
5962
this.child.stderr.on('data', (data) => {
60-
if (!started || process.stderr.isTTY) {
63+
if (!ready || process.stderr.isTTY) {
6164
process.stderr.write(data);
6265
}
6366
});
@@ -71,13 +74,28 @@ class RunCommand extends Command {
7174
if (message.started) {
7275
started = true;
7376

77+
if (!isV4) {
78+
ready = true;
79+
instance.process.success();
80+
}
81+
82+
return;
83+
}
84+
85+
if (isV4 && message.ready) {
86+
ready = true;
7487
instance.process.success();
7588
return;
7689
}
7790

7891
// NOTE: Backwards compatibility of https://github.com/TryGhost/Ghost/pull/9440
7992
setTimeout(() => {
80-
instance.process.error({message: message.error});
93+
let errMsg = isV4 ? message.error.message : message.error;
94+
if (isV4 && started) {
95+
errMsg = `Ghost was able to start, but errored during boot with: ${errMsg}`;
96+
}
97+
98+
instance.process.error({message: errMsg});
8199
}, 1000);
82100
});
83101
}

test/unit/commands/run-spec.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe('Unit: Commands > Run', function () {
159159
const errorStub = sinon.stub();
160160
const exitStub = sinon.stub(process, 'exit');
161161

162-
instance.useDirect({dir: '/var/www/ghost', process: {success: successStub, error: errorStub}}, {delayErrorChaining: false});
162+
instance.useDirect({dir: '/var/www/ghost', version: '3.0.0', process: {success: successStub, error: errorStub}}, {delayErrorChaining: false});
163163

164164
expect(spawnStub.calledOnce).to.be.true;
165165
expect(spawnStub.calledWithExactly(process.execPath, ['current/index.js'], {
@@ -202,6 +202,62 @@ describe('Unit: Commands > Run', function () {
202202
}, 2000);
203203
});
204204

205+
it('useDirect spawns child process and handles events correctly (v4+)', async function () {
206+
this.timeout(10000);
207+
208+
const childStub = new EventEmitter();
209+
childStub.stderr = getReadableStream();
210+
211+
const spawnStub = sinon.stub().returns(childStub);
212+
const RunCommand = proxyquire(modulePath, {
213+
child_process: {spawn: spawnStub}
214+
});
215+
const instance = new RunCommand({}, {});
216+
const successStub = sinon.stub();
217+
const errorStub = sinon.stub();
218+
219+
instance.useDirect({dir: '/var/www/ghost', version: '4.0.0', process: {success: successStub, error: errorStub}}, {delayErrorChaining: false});
220+
221+
expect(spawnStub.calledOnce).to.be.true;
222+
expect(spawnStub.calledWithExactly(process.execPath, ['current/index.js'], {
223+
cwd: '/var/www/ghost',
224+
stdio: [0, 1, 'pipe', 'ipc']
225+
})).to.be.true;
226+
expect(instance.child).to.equal(childStub);
227+
228+
// Check error prior to started
229+
expect(successStub.called).to.be.false;
230+
expect(errorStub.called).to.be.false;
231+
childStub.emit('message', {error: {message: 'test error'}});
232+
await new Promise((resolve) => {
233+
setTimeout(resolve, 2000);
234+
});
235+
expect(successStub.called).to.be.false;
236+
expect(errorStub.calledOnceWithExactly({message: 'test error'}));
237+
238+
errorStub.reset();
239+
240+
// emit started message
241+
childStub.emit('message', {started: true});
242+
expect(successStub.called).to.be.false;
243+
expect(errorStub.called).to.be.false;
244+
245+
// check error after started
246+
childStub.emit('message', {error: {message: 'test error'}});
247+
await new Promise((resolve) => {
248+
setTimeout(resolve, 2000);
249+
});
250+
expect(successStub.called).to.be.false;
251+
expect(errorStub.calledOnceWithExactly({message: 'Ghost was able to start, but errored during boot with: test error'}));
252+
253+
errorStub.reset();
254+
255+
// finally, check ready event
256+
childStub.emit('message', {ready: true});
257+
expect(successStub.calledOnce).to.be.true;
258+
expect(errorStub.called).to.be.false;
259+
});
260+
205261
describe('cleanup handler', function () {
206262
const RunCommand = require(modulePath);
207263

0 commit comments

Comments
 (0)