Skip to content

Commit

Permalink
feat(ui): add custom listr renderer and move listr to ui class
Browse files Browse the repository at this point in the history
closes TryGhost#181
- add custom Listr renderer that works better with other UI methods e.g. log, prompt, run
- refactor commands to use this.ui.listr instead of new Listr()
- fix various command arg bugs
  • Loading branch information
acburdine committed Jun 26, 2017
1 parent cf7bc54 commit bad38d3
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 148 deletions.
4 changes: 0 additions & 4 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ class Command {
let context = {
ui: ui,
service: service,
renderer: verbose ? 'verbose' : 'update',
verbose: verbose,
cliVersion: pkg.version
};

Expand Down Expand Up @@ -160,8 +158,6 @@ class Command {
// Will refactor this out with the service refactors
this.ui = context.ui;
this.service = context.service;
this.renderer = context.renderer;
this.verbose = context.verbose;
this.cliVersion = context.cliVersion;
this.development = context.development;
this.environment = context.environment;
Expand Down
92 changes: 51 additions & 41 deletions lib/commands/doctor/checks/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,72 @@
const os = require('os');
const chalk = require('chalk');
const execa = require('execa');
const Listr = require('listr');

const errors = require('../../../errors');

module.exports = [{
title: 'System Stack',
task: (context) => {
let promise;

if (os.platform() !== 'linux') {
context.linux = false;
return Promise.reject(new errors.SystemError(chalk.yellow('Platform is not Linux')));
}
promise = Promise.reject(new errors.SystemError(chalk.yellow('Platform is not Linux')));
} else {
context.linux = true;

context.linux = true;
promise = execa.shell('lsb_release -a').then((result) => {
if (!result.stdout || !result.stdout.match(/Ubuntu 16/)) {
context.ubuntu = false;
return Promise.reject(new errors.SystemError(chalk.yellow('Linux version is not Ubuntu 16')));
}

return execa.shell('lsb_release -a').then((result) => {
if (!result.stdout || !result.stdout.match(/Ubuntu 16/)) {
context.ubuntu = false;
return Promise.reject(new errors.SystemError(chalk.yellow('Linux version is not Ubuntu 16')));
}
context.ubuntu = true;

return context.ui.listr([{
title: 'Systemd',
task: (ctx) => execa.shell('dpkg -l | grep systemd').then(() => {
ctx.systemd = true;
})
}, {
title: 'Nginx',
task: (ctx) => execa.shell('dpkg -l | grep nginx').then(() => {
ctx.nginx = true;
})
}], context, {concurrent: true, renderer: context.ui.verbose ? 'verbose' : 'silent', exitOnError: false})
});
}

context.ubuntu = true;

return new Listr([{
title: 'Systemd',
task: (ctx) => execa.shell('dpkg -l | grep systemd').then(() => {
ctx.systemd = true;
})
}, {
title: 'Nginx',
task: (ctx) => execa.shell('dpkg -l | grep nginx').then(() => {
ctx.nginx = true;
})
}], {concurrent: true, renderer: context.verbose ? context.renderer : 'silent', exitOnError: false})
.run(context).catch(() => {
let missing = [];

if (!context.systemd) {
missing.push('systemd');
}

if (!context.nginx) {
missing.push('nginx');
}

if (missing.length) {
return Promise.reject(new errors.SystemError(chalk.yellow(`Missing package(s): ${missing.join(', ')}`)));
}
});
}).catch((error) => {
return promise.then(() => { return {continue: true}; }).catch((error) => {
// If the error caught is not a SystemError, something went wrong with execa,
// so throw a ProcessError instead
if (!(error instanceof errors.SystemError)) {
error = new errors.ProcessError(error);
return Promise.reject(new errors.ProcessError(error));
}

// This is a check so that when running as part of `ghost setup`, we can do things more cleanly
// As part of `ghost doctor`, none of the below should run
if (!context.setup) {
return Promise.reject(error);
}

return Promise.reject(error);
});
context.ui.log(
`System Stack checks failed with message: '${error.message}'.${os.EOL}` +
'Some features of Ghost-CLI may not work without additional configuration.',
'yellow'
);

return context.ui.prompt({
type: 'confirm',
name: 'continue',
message: chalk.blue('Continue anyways?'),
default: true
});
}).then(
(answers) => answers.continue || Promise.reject(new errors.SystemError(
`Setup was halted. Ghost is installed but not fully setup.${os.EOL}` +
'Fix any errors shown and re-run `ghost setup`, or run `ghost setup --no-stack`.'
))
);
}
}];
7 changes: 2 additions & 5 deletions lib/commands/doctor/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use strict';
const Listr = require('listr');
const Command = require('../../command');
const errors = require('../../errors');

Expand All @@ -19,9 +18,7 @@ class DoctorCommand extends Command {
return Promise.reject(e);
}

let tasks = new Listr(checks, {concurrent: true, renderer: this.renderer});

return tasks.run(this).then(() => {
return this.ui.listr(checks, {ui: this.ui, system: this.system}, {concurrent: true}).then(() => {
this.ui.success(`All ${category} checks passed`);
}).catch((error) => {
if (error instanceof errors.SystemError) {
Expand All @@ -36,7 +33,7 @@ class DoctorCommand extends Command {
}

DoctorCommand.description = 'Check the system for any potential hiccups when installing/updating Ghost';
DoctorCommand.params = '[name]';
DoctorCommand.params = '[category]';
DoctorCommand.global = true;

module.exports = DoctorCommand;
27 changes: 13 additions & 14 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
const fs = require('fs-extra');
const path = require('path');
const every = require('lodash/every');
const Listr = require('listr');
const Promise = require('bluebird');
const Command = require('../command');
const symlinkSync = require('symlink-or-copy').sync;
Expand Down Expand Up @@ -50,38 +49,38 @@ class InstallCommand extends Command {
this.environment = 'development';
}

return new Listr([{
return this.ui.listr([{
title: 'Checking for latest Ghost version',
task: this.constructor.tasks.version
}, {
title: 'Running system checks',
task: (ctx) => new Listr(installChecks, {concurrent: true, renderer: ctx.renderer})
task: () => this.ui.listr(installChecks, false, {concurrent: true})
}, {
title: 'Setting up install directory',
task: ensureStructure
}, {
title: 'Downloading and installing Ghost',
task: (ctx, task) => {
task.title = `Downloading and installing Ghost v${ctx.version}`;
return yarnInstall(ctx.renderer);
return yarnInstall(ctx.ui);
}
}, {
title: 'Moving files',
task: () => new Listr([{
task: () => this.ui.listr([{
title: 'Summoning Casper',
task: this.constructor.tasks.casper
}, {
title: 'Linking things',
task: this.constructor.tasks.link
}], {concurrent: true})
}], {renderer: this.renderer}).run({
}], false, {concurrent: true})
}], {
version: version,
cliVersion: this.cliVersion,
renderer: this.renderer
cliVersion: this.cliVersion
}).then(() => {
if (argv.noSetup) {
if (!argv.setup) {
return;
}

argv.local = local;

let setup = new SetupCommand(this);
Expand Down Expand Up @@ -128,10 +127,10 @@ InstallCommand.options = {
description: 'Folder to install Ghost in',
type: 'string'
},
noSetup: {
alias: 'N',
description: 'Don\'t automatically run the setup command',
type: 'boolean'
setup: {
description: 'Automatically run the setup command',
type: 'boolean',
default: true
}
};

Expand Down
111 changes: 39 additions & 72 deletions lib/commands/setup.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
'use strict';
const eol = require('os').EOL;
const path = require('path');
const chalk = require('chalk');
const Listr = require('listr');

const Config = require('../utils/config');
const errors = require('../errors');
const setupChecks = require('./doctor/checks/setup');
const StartCommand = require('./start');
const ConfigCommand = require('./config');
Expand All @@ -19,105 +15,76 @@ class SetupCommand extends Command {
}

run(argv) {
let context = {
renderer: this.renderer,
verbose: this.verbose
};

if (argv.local) {
argv.url = argv.url || 'http://localhost:2368/';
argv.pname = argv.pname || 'ghost-local';
argv.process = 'local';
argv.stack = false;

// If the user's already specified a db client, then we won't override it.
if (!argv.db) {
argv.db = argv.db || 'sqlite3';
argv.dbpath = path.join(process.cwd(), 'content/data/ghost-local.db');
}

context.start = true;
argv.start = true;

// In the case that the user runs `ghost setup --local`, we want to make
// sure we're set up in development mode
this.development = true;
process.env.NODE_ENV = this.environment = 'development';
} else {
context.start = argv.start || false;
}

let configCommand = new ConfigCommand(this);
return configCommand.run(argv).then((config) => {
context.config = config;

if (!argv.local && argv.stack) {
return new Listr(setupChecks, {concurrent: true, renderer: this.renderer}).run(context)
.then((context) => {context.continue = true;})
.catch((error) => {
if (!(error instanceof errors.SystemError)) {
return Promise.reject(error);
}

this.ui.log(
`System Stack checks failed with message: '${error.message}'.${eol}` +
'Some features of Ghost-CLI may not work without additional configuration.',
'yellow'
);

return this.ui.prompt({
type: 'confirm',
name: 'continue',
message: chalk.blue('Continue anyways?'),
default: true
}).then((answers) => {
if (!answers.continue) {
return Promise.reject(new Error(
`Setup was halted. Ghost is installed but not fully setup.${eol}` +
'Fix any errors shown and re-run `ghost setup`, or run `ghost setup --no-stack`.'
));
}
});
});
return this.ui.listr([{
title: 'Configuring Ghost',
task: (ctx) => {
let configCommand = new ConfigCommand(this);
return configCommand.run(argv).then((config) => ctx.config = config);
}
}).then(() => {
// De-duplicate process name before setting up the process manager
dedupeProcessName(context.config);

this.service.setConfig(context.config);

return this.ui.run(this.service.callHook('setup', context), 'Finishing setup');
}).then(() => {
if (context.start) {
return;
}, {
title: 'Running setup checks',
skip: () => !argv.stack,
task: () => this.ui.listr(setupChecks, false)
}, {
title: 'Finishing setup',
task: (ctx) => {
// De-duplicate process name before setting up the process manager
dedupeProcessName(ctx.config);
this.service.setConfig(ctx.config);
return this.service.callHook('setup', ctx);
}

return this.ui.prompt({
}], {setup: true}).then((context) => {
let promise = argv.start ? Promise.resolve({start: true}) : this.ui.prompt({
type: 'confirm',
name: 'start',
message: 'Do you want to start Ghost?',
default: true
});
}).then((answer) => {
// Add config to system blog list
let systemConfig = Config.load('system');
let instances = systemConfig.get('instances', {});
instances[context.config.get('pname')] = {
cwd: process.cwd()
};
systemConfig.set('instances', instances).save();

if (context.start || answer.start) {
let startCommand = new StartCommand(this);
return startCommand.run(argv);
}
return promise.then((answer) => {
// Add config to system blog list
let systemConfig = Config.load('system');
let instances = systemConfig.get('instances', {});
instances[context.config.get('pname')] = {
cwd: process.cwd()
};
systemConfig.set('instances', instances).save();

if (answer.start) {
let startCommand = new StartCommand(this);
return startCommand.run(argv);
}
});
});
}
}

SetupCommand.description = 'Setup an installation of Ghost (after it is installed)';
SetupCommand.options = {
noStack: {
description: 'Don\'t check the system stack on setup',
type: 'boolean'
stack: {
description: 'Check the system stack on setup',
type: 'boolean',
default: true
},
local: {
alias: 'l',
Expand All @@ -127,7 +94,7 @@ SetupCommand.options = {
start: {
name: 'start',
description: 'Automatically start Ghost without prompting',
flag: true
type: 'boolean'
}
};

Expand Down
Loading

0 comments on commit bad38d3

Please sign in to comment.