Skip to content

Commit

Permalink
feat: added support for named start (#768)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Lockyer <hi@daniellockyer.com>
  • Loading branch information
vikaspotluri123 and daniellockyer committed Oct 14, 2022
1 parent 0512edd commit 701b125
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 54 deletions.
2 changes: 2 additions & 0 deletions lib/commands/doctor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class DoctorCommand extends Command {
findValidInstall('doctor');
instance = this.system.getInstance();
instance.checkEnvironment();
} else {
instance = this.system.getInstance();
}

const context = {
Expand Down
13 changes: 11 additions & 2 deletions lib/commands/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ class StartCommand extends Command {
}

async run(argv) {
const instance = this.system.getInstance();
const getInstance = require('../utils/get-instance');

const runOptions = {quiet: argv.quiet};
const instance = getInstance({
name: argv.name,
system: this.system,
command: 'start',
recurse: !argv.dir
});

const isRunning = await instance.isRunning();
if (isRunning) {
Expand All @@ -41,7 +48,7 @@ class StartCommand extends Command {
].join('\n'), 'yellow');
}

await this.runCommand(DoctorCommand, {categories: ['start'], ...argv, quiet: true});
await this.runCommand(DoctorCommand, {categories: ['start'], ...argv, quiet: true, skipInstanceCheck: true});
await this.ui.run(() => instance.start(argv.enable), `Starting Ghost: ${instance.name}`, runOptions);

if (!argv.quiet) {
Expand All @@ -56,12 +63,14 @@ class StartCommand extends Command {
}

StartCommand.description = 'Start an instance of Ghost';
StartCommand.params = '[name]';
StartCommand.options = {
enable: {
description: '[--no-enable] Enable/don\'t enable instance restart on server reboot (if the process manager supports it)',
type: 'boolean',
default: true
}
};
StartCommand.global = true;

module.exports = StartCommand;
27 changes: 10 additions & 17 deletions lib/commands/stop.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,21 @@ class StopCommand extends Command {
}

async run(argv) {
const {SystemError} = require('../errors');
const findValidInstall = require('../utils/find-valid-install');

const getInstance = require('../utils/get-instance');
const runOptions = {quiet: argv.quiet};

if (argv.all) {
return this.stopAll();
}

if (!argv.name) {
findValidInstall('stop', true);
}

const instance = this.system.getInstance(argv.name);

if (argv.name && !instance) {
throw new SystemError(`Ghost instance '${argv.name}' does not exist`);
}

const instance = getInstance({
name: argv.name,
system: this.system,
command: 'stop',
recurse: !argv.dir
});
const isRunning = await instance.isRunning();

if (!isRunning) {
this.ui.log('Ghost is already stopped! For more information, run', 'ghost ls', 'green', 'cmd', true);
return;
Expand All @@ -49,12 +44,10 @@ class StopCommand extends Command {
}

async stopAll() {
const Promise = require('bluebird');

const instances = this.system.getAllInstances(true);
await Promise.each(instances, async ({name}) => {
for (const {name} of instances) {
await this.ui.run(() => this.run({quiet: true, name}), `Stopping Ghost: ${name}`);
});
}
}
}

Expand Down
31 changes: 31 additions & 0 deletions lib/utils/get-instance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const {SystemError} = require('../errors');
const findValidInstallation = require('./find-valid-install');

/**
* @param {object} options
* @param {string} options.name
* @param {import('../system.js')} options.system
* @param {string} options.command
* @param {boolean} options.recurse
*/
function getInstance({
name: instanceName,
system,
command: commandName,
recurse
}) {
if (instanceName) {
const instance = system.getInstance(instanceName);
if (!instance) {
throw new SystemError(`Ghost instance '${instanceName}' does not exist`);
}

process.chdir(instance.dir);
return instance;
}

findValidInstallation(commandName, recurse);
return system.getInstance();
}

module.exports = getInstance;
8 changes: 2 additions & 6 deletions test/unit/commands/doctor/command-spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
const expect = require('chai').expect;
const {expect} = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru().noPreserveCache();

Expand Down Expand Up @@ -121,7 +121,6 @@ describe('Unit: Commands > Doctor', function () {

return instance.run({skipInstanceCheck: true, local: true, argv: true}).then(() => {
expect(findValidStub.called).to.be.false;
expect(system.getInstance.called).to.be.false;
expect(system.hook.calledOnce).to.be.true;
expect(system.hook.calledWithExactly('doctor')).to.be.true;
expect(instanceStub.checkEnvironment.called).to.be.false;
Expand All @@ -136,7 +135,6 @@ describe('Unit: Commands > Doctor', function () {
const context = ui.listr.args[0][1];
expect(context.argv).to.deep.equal({skipInstanceCheck: true, local: true, argv: true});
expect(context.system).to.equal(system);
expect(context.instance).to.not.exist;
expect(context.ui).to.equal(ui);
expect(context.local).to.be.true;
expect(context.isDoctorCommand).to.be.false;
Expand Down Expand Up @@ -169,7 +167,6 @@ describe('Unit: Commands > Doctor', function () {
_: ['doctor']
}).then(() => {
expect(findValidStub.called).to.be.false;
expect(system.getInstance.called).to.be.false;
expect(system.hook.calledOnce).to.be.true;
expect(system.hook.calledWithExactly('doctor')).to.be.true;
expect(instanceStub.checkEnvironment.called).to.be.false;
Expand All @@ -184,7 +181,6 @@ describe('Unit: Commands > Doctor', function () {
_: ['doctor']
});
expect(context.system).to.equal(system);
expect(context.instance).to.not.exist;
expect(context.ui).to.equal(ui);
expect(context.local).to.be.true;
expect(context.isDoctorCommand).to.be.true;
Expand All @@ -210,7 +206,7 @@ describe('Unit: Commands > Doctor', function () {
const DoctorCommand = proxyquire(modulePath, {
'./checks': testChecks
});
instance = new DoctorCommand({listr: listrStub}, {hook: hookStub});
instance = new DoctorCommand({listr: listrStub}, {hook: hookStub, getInstance: sinon.stub()});
});

afterEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/commands/setup-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ describe('Unit: Commands > Setup', function () {
config: {options: {setup: {test: true}}}
}, {}];

const yargs = {option: sinon.stub(), epilogue: () => true};
const yargs = {option: sinon.stub(), epilogue: () => true, usage: () => true};
yargs.option.returns(yargs);
SetupCommand.configureOptions.call({options: {}}, 'Test', yargs, extensions, true);
expect(yargs.option.called).to.be.true;
Expand Down
46 changes: 27 additions & 19 deletions test/unit/commands/start-spec.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
const expect = require('chai').expect;
const {expect} = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru();
const createConfigStub = require('../../utils/config-stub');

const Instance = require('../../../lib/instance');
const System = require('../../../lib/system');
const UI = require('../../../lib/ui');
const DoctorCommand = require('../../../lib/commands/doctor');

const modulePath = '../../../lib/commands/start';
const StartCommand = require(modulePath);

function getStubs(dir, environment = undefined) {
const ui = new UI({});
Expand All @@ -22,23 +20,30 @@ function getStubs(dir, environment = undefined) {
instance._config.environment = environment;
system.environment = environment;

const getInstance = sinon.stub(system, 'getInstance').returns(instance);

return {
ui, system, instance, getInstance
ui, system, instance
};
}

describe('Unit: Commands > Start', function () {
describe('run', function () {
const oldArgv = process.argv;
let StartCommand;
let returnedInstance;

beforeEach(function () {
StartCommand = proxyquire(modulePath, {
'../utils/get-instance': sinon.stub().callsFake(() => returnedInstance)
});
});

afterEach(() => {
process.argv = oldArgv;
});

it('notifies and exits for already running instance', async function () {
const {ui, system, instance, getInstance} = getStubs('/var/www/ghost');
const {ui, system, instance} = getStubs('/var/www/ghost');
returnedInstance = instance;
const isRunning = sinon.stub(instance, 'isRunning').resolves(true);
const checkEnvironment = sinon.stub(instance, 'checkEnvironment');
const log = sinon.stub(ui, 'log');
Expand All @@ -49,7 +54,6 @@ describe('Unit: Commands > Start', function () {
const runCommand = sinon.stub(cmd, 'runCommand').resolves();

await cmd.run({});
expect(getInstance.calledOnce).to.be.true;
expect(isRunning.calledOnce).to.be.true;
expect(log.calledOnce).to.be.true;

Expand All @@ -61,6 +65,7 @@ describe('Unit: Commands > Start', function () {

it('warns of http use in production', async function () {
const {ui, system, instance} = getStubs('/var/www/ghost', 'production');
returnedInstance = instance;
const logStub = sinon.stub(ui, 'log');
const isRunning = sinon.stub(instance, 'isRunning').resolves(false);
const checkEnvironment = sinon.stub(instance, 'checkEnvironment');
Expand All @@ -85,6 +90,7 @@ describe('Unit: Commands > Start', function () {

it('no warning with ssl in production', async function () {
const {ui, system, instance} = getStubs('/var/www/ghost', 'production');
returnedInstance = instance;
const logStub = sinon.stub(ui, 'log');
const isRunning = sinon.stub(instance, 'isRunning').resolves(false);
const checkEnvironment = sinon.stub(instance, 'checkEnvironment');
Expand All @@ -105,7 +111,8 @@ describe('Unit: Commands > Start', function () {
});

it('runs startup checks and starts correctly', async function () {
const {ui, system, instance, getInstance} = getStubs('/var/www/ghost');
const {ui, system, instance} = getStubs('/var/www/ghost');
returnedInstance = instance;
const isRunning = sinon.stub(instance, 'isRunning').resolves(false);
const checkEnvironment = sinon.stub(instance, 'checkEnvironment');
const log = sinon.stub(ui, 'log');
Expand All @@ -118,23 +125,24 @@ describe('Unit: Commands > Start', function () {
instance.config.get.returns('http://localhost:2368');

await cmd.run({checkMem: false});
expect(getInstance.calledOnce).to.be.true;
expect(isRunning.calledOnce).to.be.true;
expect(checkEnvironment.calledOnce).to.be.true;
expect(runCommand.calledOnce).to.be.true;
expect(runCommand.calledWithExactly(DoctorCommand, {
expect(runCommand.args[0][1]).to.deep.equal({
categories: ['start'],
quiet: true,
checkMem: false
})).to.be.true;
checkMem: false,
skipInstanceCheck: true
});
expect(run.calledOnce).to.be.true;
expect(start.calledOnce).to.be.true;
expect(log.calledTwice).to.be.true;
expect(instance.config.get.calledTwice).to.be.true;
});

it('doesn\'t log if quiet is set to true', async function () {
const {ui, system, instance, getInstance} = getStubs('/var/www/ghost');
const {ui, system, instance} = getStubs('/var/www/ghost');
returnedInstance = instance;
const isRunning = sinon.stub(instance, 'isRunning').resolves(false);
const checkEnvironment = sinon.stub(instance, 'checkEnvironment');
const log = sinon.stub(ui, 'log');
Expand All @@ -145,15 +153,15 @@ describe('Unit: Commands > Start', function () {
const runCommand = sinon.stub(cmd, 'runCommand').resolves();

await cmd.run({checkMem: false, quiet: true});
expect(getInstance.calledOnce).to.be.true;
expect(isRunning.calledOnce).to.be.true;
expect(checkEnvironment.calledOnce).to.be.true;
expect(runCommand.calledOnce).to.be.true;
expect(runCommand.calledWithExactly(DoctorCommand, {
expect(runCommand.args[0][1]).to.deep.equal({
categories: ['start'],
quiet: true,
checkMem: false
})).to.be.true;
checkMem: false,
skipInstanceCheck: true
});
expect(run.calledOnce).to.be.true;
expect(start.calledOnce).to.be.true;
expect(log.called).to.be.false;
Expand All @@ -170,7 +178,7 @@ describe('Unit: Commands > Start', function () {
config: {options: {start: {test: true}}}
}, {}];

const yargs = {option: sinon.stub(), epilogue: () => true};
const yargs = {option: sinon.stub(), epilogue: () => true, usage: () => true};
yargs.option.returns(yargs);
StartCommand.configureOptions.call({options: {}}, 'Test', yargs, extensions, true);
expect(yargs.option.called).to.be.true;
Expand Down
19 changes: 10 additions & 9 deletions test/unit/commands/stop-spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use strict';
const expect = require('chai').expect;
const sinon = require('sinon');
const proxyquire = require('proxyquire');
Expand All @@ -19,17 +18,17 @@ describe('Unit: Commands > Stop', function () {
});

it('checks for valid install if name not specified', async function () {
const findStub = sinon.stub().throws(new Error('findValidInstall'));
const getInstance = sinon.stub().throws(new Error('getInstance'));
const Command = proxyquire(modulePath, {
'../utils/find-valid-install': findStub
'../utils/get-instance': getInstance
});
const stop = new Command();

try {
await stop.run({});
} catch (error) {
expect(error.message).to.equal('findValidInstall');
expect(findStub.calledOnce).to.be.true;
expect(error.message).to.equal('getInstance');
expect(getInstance.calledOnce).to.be.true;
return;
}

Expand All @@ -54,12 +53,13 @@ describe('Unit: Commands > Stop', function () {
const log = sinon.stub();
const isRunning = sinon.stub().resolves(false);
const getInstance = sinon.stub().returns({isRunning});
const stop = new StopCommand({log}, {getInstance});
const Command = proxyquire(modulePath, {'../utils/get-instance': getInstance});
const stop = new Command({log});

await stop.run({name: 'testing'});

expect(getInstance.calledOnce).to.be.true;
expect(getInstance.calledWithExactly('testing')).to.be.true;
expect(getInstance.args[0][0].name).to.equal('testing');
expect(isRunning.calledOnce).to.be.true;
expect(log.calledOnce).to.be.true;
});
Expand All @@ -72,12 +72,13 @@ describe('Unit: Commands > Stop', function () {
const stop = sinon.stub().resolves();

const getInstance = sinon.stub().returns({isRunning, stop});
const Command = proxyquire(modulePath, {'../utils/get-instance': getInstance});

const cmd = new StopCommand({log, run}, {getInstance});
const cmd = new Command({log, run});
await cmd.run({name: 'testing'});

expect(getInstance.calledOnce).to.be.true;
expect(getInstance.calledWithExactly('testing')).to.be.true;
expect(getInstance.args[0][0].name).to.equal('testing');
expect(isRunning.calledOnce).to.be.true;
expect(run.calledOnce).to.be.true;
expect(stop.calledOnce).to.be.true;
Expand Down

0 comments on commit 701b125

Please sign in to comment.