Skip to content

Commit

Permalink
feat(local): handle v4 boot events in local process manager
Browse files Browse the repository at this point in the history
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
  • Loading branch information
acburdine committed Mar 1, 2021
1 parent 1c63c95 commit 156c67a
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 4 deletions.
24 changes: 21 additions & 3 deletions lib/commands/run.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const spawn = require('child_process').spawn;
const semver = require('semver');
const Command = require('../command');

class RunCommand extends Command {
Expand Down Expand Up @@ -50,14 +51,16 @@ class RunCommand extends Command {
}

useDirect(instance) {
const isV4 = semver.major(instance.version) >= 4;

this.child = spawn(process.execPath, ['current/index.js'], {
cwd: instance.dir,
stdio: [0, 1, 'pipe', 'ipc']
});

let started = false;
let started = false, ready = false;
this.child.stderr.on('data', (data) => {
if (!started || process.stderr.isTTY) {
if (!ready || process.stderr.isTTY) {
process.stderr.write(data);
}
});
Expand All @@ -71,13 +74,28 @@ class RunCommand extends Command {
if (message.started) {
started = true;

if (!isV4) {
ready = true;
instance.process.success();
}

return;
}

if (isV4 && message.ready) {
ready = true;
instance.process.success();
return;
}

// NOTE: Backwards compatibility of https://github.com/TryGhost/Ghost/pull/9440
setTimeout(() => {
instance.process.error({message: message.error});
let errMsg = isV4 ? message.error.message : message.error;
if (isV4 && started) {
errMsg = `Ghost was able to start, but errored during boot with: ${errMsg}`;
}

instance.process.error({message: errMsg});
}, 1000);
});
}
Expand Down
58 changes: 57 additions & 1 deletion test/unit/commands/run-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('Unit: Commands > Run', function () {
const errorStub = sinon.stub();
const exitStub = sinon.stub(process, 'exit');

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

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

it('useDirect spawns child process and handles events correctly (v4+)', async function () {
this.timeout(10000);

const childStub = new EventEmitter();
childStub.stderr = getReadableStream();

const spawnStub = sinon.stub().returns(childStub);
const RunCommand = proxyquire(modulePath, {
child_process: {spawn: spawnStub}
});
const instance = new RunCommand({}, {});
const successStub = sinon.stub();
const errorStub = sinon.stub();

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

expect(spawnStub.calledOnce).to.be.true;
expect(spawnStub.calledWithExactly(process.execPath, ['current/index.js'], {
cwd: '/var/www/ghost',
stdio: [0, 1, 'pipe', 'ipc']
})).to.be.true;
expect(instance.child).to.equal(childStub);

// Check error prior to started
expect(successStub.called).to.be.false;
expect(errorStub.called).to.be.false;
childStub.emit('message', {error: {message: 'test error'}});
await new Promise((resolve) => {
setTimeout(resolve, 2000);
});
expect(successStub.called).to.be.false;
expect(errorStub.calledOnceWithExactly({message: 'test error'}));

errorStub.reset();

// emit started message
childStub.emit('message', {started: true});
expect(successStub.called).to.be.false;
expect(errorStub.called).to.be.false;

// check error after started
childStub.emit('message', {error: {message: 'test error'}});
await new Promise((resolve) => {
setTimeout(resolve, 2000);
});
expect(successStub.called).to.be.false;
expect(errorStub.calledOnceWithExactly({message: 'Ghost was able to start, but errored during boot with: test error'}));

errorStub.reset();

// finally, check ready event
childStub.emit('message', {ready: true});
expect(successStub.calledOnce).to.be.true;
expect(errorStub.called).to.be.false;
});

describe('cleanup handler', function () {
const RunCommand = require(modulePath);

Expand Down

0 comments on commit 156c67a

Please sign in to comment.