Skip to content

Commit

Permalink
feat(pre-checks): add ~/.config folder ownership to pre-run checks
Browse files Browse the repository at this point in the history
closes #675
- convert update check to a listr task set
- add ~/.config directory check to list of prechecks
  • Loading branch information
acburdine committed Sep 17, 2018
1 parent 7cfbfb7 commit e9e640f
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 115 deletions.
6 changes: 3 additions & 3 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ class Command {

let precheck = Promise.resolve();

if (this.checkVersion) {
const updateCheck = require('./utils/update-check');
precheck = updateCheck(ui);
if (this.runPreChecks) {
const preChecks = require('./utils/pre-checks');
precheck = preChecks(ui, system);
}

return precheck.then(() => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ InstallCommand.options = {
default: false
}
};
InstallCommand.checkVersion = true;
InstallCommand.runPreChecks = true;
InstallCommand.ensureDir = true;

module.exports = InstallCommand;
2 changes: 1 addition & 1 deletion lib/commands/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ class MigrateCommand extends Command {
}

MigrateCommand.description = 'Run system migrations on a Ghost instance';
MigrateCommand.checkVersion = true;
MigrateCommand.runPreChecks = true;

module.exports = MigrateCommand;
2 changes: 1 addition & 1 deletion lib/commands/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,6 @@ SetupCommand.options = Object.assign({
type: 'string'
}
}, options);
SetupCommand.checkVersion = true;
SetupCommand.runPreChecks = true;

module.exports = SetupCommand;
2 changes: 1 addition & 1 deletion lib/commands/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,6 @@ UpdateCommand.options = {
default: false
}
};
UpdateCommand.checkVersion = true;
UpdateCommand.runPreChecks = true;

module.exports = UpdateCommand;
48 changes: 48 additions & 0 deletions lib/utils/pre-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const semver = require('semver');
const latestVersion = require('latest-version');
const pkg = require('../../package.json');

/**
* Checks if a version update is available
* @param {UI} ui ui instance
* @param {System} system System instance
*/
module.exports = function preChecks(ui, system) {
const tasks = [{
title: 'Checking for Ghost-CLI updates',
task: () => latestVersion(pkg.name).then((latest) => {
if (semver.lt(pkg.version, latest)) {
const chalk = require('chalk');

ui.log(
'You are running an outdated version of Ghost-CLI.\n' +
'It is recommended that you upgrade before continuing.\n' +
`Run ${chalk.cyan('`npm install -g ghost-cli@latest`')} to upgrade.\n`,
'yellow'
);
}
})
}, {
title: 'Ensuring correct ~/.config folder ownership',
task: () => {
const configstore = path.join(os.homedir(), '.config');

return fs.lstat(configstore).then((stats) => {
if (stats.uid === process.getuid() && stats.gid === process.getgid()) {
return;
}

const {USER} = process.env;

return ui.sudo(`chown -R ${USER}:${USER} ${configstore}`);
});
},
isEnabled: () => system.platform.linux
}];

return ui.listr(tasks, {}, {clearOnSuccess: true});
};
27 changes: 0 additions & 27 deletions lib/utils/update-check.js

This file was deleted.

10 changes: 5 additions & 5 deletions test/unit/command-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,17 @@ describe('Unit: Command', function () {
const uiStub = sinon.stub().returns({ui: true});
const setEnvironmentStub = sinon.stub();
const systemStub = sinon.stub().returns({setEnvironment: setEnvironmentStub});
const updateCheckStub = sinon.stub().resolves();
const preChecksStub = sinon.stub().resolves();

const Command = proxyquire(modulePath, {
'./ui': uiStub,
'./system': systemStub,
'./utils/update-check': updateCheckStub
'./utils/pre-checks': preChecksStub
});

class TestCommand extends Command {}
TestCommand.global = true;
TestCommand.checkVersion = true;
TestCommand.runPreChecks = true;

const runStub = sinon.stub(TestCommand.prototype, 'run');
const oldEnv = process.env.NODE_ENV;
Expand All @@ -393,8 +393,8 @@ describe('Unit: Command', function () {
expect(setEnvironmentStub.calledWithExactly(true, true)).to.be.true;
expect(systemStub.calledOnce).to.be.true;
expect(systemStub.calledWithExactly({ui: true}, [{extensiona: true}])).to.be.true;
expect(updateCheckStub.calledOnce).to.be.true;
expect(updateCheckStub.calledWithExactly({ui: true})).to.be.true;
expect(preChecksStub.calledOnce).to.be.true;
expect(preChecksStub.calledWithExactly({ui: true}, {setEnvironment: setEnvironmentStub})).to.be.true;
expect(runStub.calledOnce).to.be.true;
expect(runStub.calledWithExactly({verbose: false, prompt: false, development: false, auto: false})).to.be.true;

Expand Down
154 changes: 154 additions & 0 deletions test/unit/utils/pre-checks-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
'use strict';
const {expect} = require('chai');
const proxyquire = require('proxyquire').noCallThru();
const sinon = require('sinon');
const stripAnsi = require('strip-ansi');
const os = require('os');
const fs = require('fs-extra');

function fake(stubs = {}) {
return proxyquire('../../../lib/utils/pre-checks', stubs);
}

function getTasks(stubs = {}, ui = {}, system = {}) {
const preChecks = fake(stubs);
const listr = sinon.stub().resolves();

return preChecks(Object.assign({listr}, ui), system).then(() => {
expect(listr.calledOnce).to.be.true;
return listr.args[0][0];
});
}

describe('Unit: Utils > pre-checks', function () {
describe('update check', function () {
it('rejects error if latestVersion has an error', function (done) {
const pkg = {name: 'ghost', version: '1.0.0'};
const testError = new Error('update check');
const latestVersion = sinon.stub().rejects(testError);

getTasks({
'../../package.json': pkg,
'latest-version': latestVersion
}).then(([task]) => {
expect(task.title).to.equal('Checking for Ghost-CLI updates');

return task.task();
}).catch((err) => {
expect(err.message).to.equal(testError.message);
expect(latestVersion.calledOnce).to.be.true;
expect(latestVersion.calledWithExactly('ghost')).to.be.true;
done();
});
});

it('doesn\'t do anything if there are no updates', function () {
const pkg = {name: 'ghost', version: '1.0.0'};
const latestVersion = sinon.stub().resolves('1.0.0');
const log = sinon.stub();

return getTasks({
'../../package.json': pkg,
'latest-version': latestVersion
}, {log}).then(([task]) => task.task()).then(() => {
expect(log.called).to.be.false;
expect(latestVersion.calledOnce).to.be.true;
expect(latestVersion.calledWithExactly('ghost')).to.be.true;
});
});

it('logs a message if an update is available', function () {
const pkg = {name: 'ghost', version: '1.0.0'};
const latestVersion = sinon.stub().resolves('1.1.0');
const log = sinon.stub();

return getTasks({
'../../package.json': pkg,
'latest-version': latestVersion
}, {log}).then(([task]) => task.task()).then(() => {
expect(log.calledOnce).to.be.true;
const msg = log.args[0][0];

expect(stripAnsi(msg)).to.match(/You are running an outdated version of Ghost-CLI/);

expect(latestVersion.calledOnce).to.be.true;
expect(latestVersion.calledWithExactly('ghost')).to.be.true;
});
});
});

describe('~/.config folder ownership', function () {
afterEach(() => {
sinon.restore();
delete process.env.USER;
});

it('rejects error if fs.lstat errors', function (done) {
const homedir = sinon.stub(os, 'homedir').returns('/home/ghost');
const lstat = sinon.stub(fs, 'lstat').rejects(new Error('test error'));

getTasks({}, {}, {platform: {linux: true}}).then(([,task]) => {
expect(task.title).to.equal('Ensuring correct ~/.config folder ownership');
expect(task.isEnabled()).to.be.true;
return task.task();
}).catch((error) => {
expect(error.message).to.equal('test error');
expect(homedir.calledOnce).to.be.true;
expect(lstat.calledOnce).to.be.true;
expect(lstat.calledWithExactly('/home/ghost/.config')).to.be.true;
done();
});
});

it('doesn\'t do anything if directory ownership if fine', function () {
sinon.stub(os, 'homedir').returns('/home/ghost');
sinon.stub(fs, 'lstat').resolves({uid: 1, gid: 1});
const uid = sinon.stub(process, 'getuid').returns(1);
const gid = sinon.stub(process, 'getgid').returns(1);
const sudo = sinon.stub().resolves();

return getTasks({}, {sudo}, {platform: {linux: false}}).then(([,task]) => {
expect(task.isEnabled()).to.be.false;
return task.task();
}).then(() => {
expect(uid.calledOnce).to.be.true;
expect(gid.calledOnce).to.be.true;
expect(sudo.called).to.be.false;
});
});

it('calls chown if directory owner is not correct', function () {
sinon.stub(os, 'homedir').returns('/home/ghost');
sinon.stub(fs, 'lstat').resolves({uid: 1, gid: 1});
const uid = sinon.stub(process, 'getuid').returns(2);
const gid = sinon.stub(process, 'getgid').returns(2);
const sudo = sinon.stub().resolves();
process.env.USER = 'ghostuser';

return getTasks({}, {sudo}).then(([,task]) => task.task()).then(() => {
expect(uid.calledOnce).to.be.true;
expect(gid.called).to.be.false;
expect(sudo.calledOnce).to.be.true;

expect(sudo.args[0][0]).to.equal('chown -R ghostuser:ghostuser /home/ghost/.config');
});
});

it('calls chown if directory group is not correct', function () {
sinon.stub(os, 'homedir').returns('/home/ghost');
sinon.stub(fs, 'lstat').resolves({uid: 2, gid: 1});
const uid = sinon.stub(process, 'getuid').returns(2);
const gid = sinon.stub(process, 'getgid').returns(2);
const sudo = sinon.stub().resolves();
process.env.USER = 'ghostuser';

return getTasks({}, {sudo}).then(([,task]) => task.task()).then(() => {
expect(uid.calledOnce).to.be.true;
expect(gid.calledOnce).to.be.true;
expect(sudo.calledOnce).to.be.true;

expect(sudo.args[0][0]).to.equal('chown -R ghostuser:ghostuser /home/ghost/.config');
});
});
});
});
76 changes: 0 additions & 76 deletions test/unit/utils/update-check-spec.js

This file was deleted.

0 comments on commit e9e640f

Please sign in to comment.