Skip to content

Commit

Permalink
feat(system): system & interface api
Browse files Browse the repository at this point in the history
closes TryGhost#219, TryGhost#203, TryGhost#170
- addition of System and Interface classes to manage configuration/environment
- fix update command weirdness
- cleanup & fix process name handling
- move error logging to user home directory
  • Loading branch information
acburdine committed Jun 27, 2017
1 parent 86a9eeb commit 3d6d823
Show file tree
Hide file tree
Showing 26 changed files with 507 additions and 450 deletions.
50 changes: 23 additions & 27 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
'use strict';

const fs = require('fs-extra');
const UI = require('./ui');
const path = require('path');
const each = require('lodash/each');
const pkg = require('../package.json');
const Config = require('./utils/config');
const createDebug = require('debug');
const kebabCase = require('lodash/kebabCase');

const UI = require('./ui');
const System = require('./system');
const Config = require('./utils/config');
const ServiceManager = require('./services');

const debug = createDebug('ghost-cli:command');
Expand Down Expand Up @@ -111,23 +112,15 @@ class Command {
verbose: verbose,
allowPrompt: argv.prompt
});
let service = ServiceManager.load(ui);
let context = {
ui: ui,
service: service,
cliVersion: pkg.version
};
let system = new System(ui);
let service = ServiceManager.load(ui, system);

context.development = (process.env.NODE_ENV === 'development') || argv.development;
context.environment = context.development ? 'development' : 'production';

// Set NODE_ENV so it's accessible everywhere
process.env.NODE_ENV = context.environment;
system.setEnvironment(argv.development || process.env.NODE_ENV === 'development', true);

// TODO: This is intended to maintain maximum compatability with the pre-yargs
// version of Ghost-CLI - so this will look ugly until the service manager part
// can be refactored.
let commandInstance = new this(context);
let commandInstance = new this(ui, service, system);
debug(`running command ${ commandName }`);

if (commandInstance.cleanup) {
Expand All @@ -141,10 +134,7 @@ class Command {
return Promise.resolve(commandInstance.run(argv)).catch((error) => {
debug(`command ${ commandName } failed!`);

ui.error(error, {
context: context,
cliVersion: pkg.version
});
ui.error(error, system);

process.exit(1);
});
Expand All @@ -153,20 +143,26 @@ class Command {
/**
* Constructs the command instance
*/
constructor(context) {
// TODO: THIS IS REALLY DIRTY
// Will refactor this out with the service refactors
this.ui = context.ui;
this.service = context.service;
this.cliVersion = context.cliVersion;
this.development = context.development;
this.environment = context.environment;
constructor(ui, service, system) {
this.ui = ui;
this.service = service;
this.system = system;
}

/**
* @param {Object} argv Parsed arguments object
*/
run() {
throw new Error('Command must implement run function');
}

runCommand(CommandClass, argv) {
if (!(CommandClass.prototype instanceof Command)) {
throw new Error('Provided command class does not extend the Command class');
}

let cmdInstance = new CommandClass(this.ui, this.service, this.system);
return cmdInstance.run(argv || {});
}
}

Expand Down
10 changes: 1 addition & 9 deletions lib/commands/config/advanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ module.exports = {
'Invalid URL. Your URL should include a protocol, E.g. http://my-ghost-blog.com',
type: 'string'
},
pname: {
description: 'Name of the Ghost instance',
validate: value => !!value.match(/^[A-Za-z0-9\-_]+$/) ||
'Invalid process name. Process name can contain alphanumeric characters ' +
'and the special characters \'-\' and \'_\'',
defaultValue: config => url.parse(config.get('url')).hostname.replace(/\./g, '-'),
type: 'string'
},
port: {
description: 'Port ghost should listen on',
configPath: 'server.port',
Expand All @@ -63,7 +55,7 @@ module.exports = {
},
process: {
description: 'Type of process manager to run Ghost with',
defaultValue: config => config.environment === 'production' ? 'systemd' : 'local',
defaultValue: (c, env) => env === 'production' ? 'systemd' : 'local',
type: 'string'
},
db: {
Expand Down
35 changes: 22 additions & 13 deletions lib/commands/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ const Promise = require('bluebird');
const Command = require('../../command');
const advancedOptions = require('./advanced');
const errors = require('../../errors');
const Config = require('../../utils/config');

class ConfigCommand extends Command {
constructor(context) {
super(context);
constructor(ui, service, system) {
super(ui, service, system);

this.config = Config.load(this.environment);
this.instance = this.system.getInstance();
this.instance.loadConfig(true);
this.config = this.instance.config;
}

handleAdvancedOptions(argv) {
Expand All @@ -28,8 +29,12 @@ class ConfigCommand extends Command {
return Promise.resolve();
}

return Promise.resolve(isFunction(option.defaultValue) ? option.defaultValue(this.config) : option.defaultValue).then((defaultValue) => {
this.config.set(configKey, defaultValue);
let defaultValue = isFunction(option.defaultValue) ?
option.defaultValue(this.config, this.system.environment) :
option.defaultValue;

return Promise.resolve(defaultValue).then((result) => {
this.config.set(configKey, result);
});
}

Expand Down Expand Up @@ -59,6 +64,9 @@ class ConfigCommand extends Command {
}

this.config.save();

let pname = argv.pname || url.parse(this.config.get('url')).hostname.replace(/\./g, '-');
this.instance.name = pname; // Set instance name
});
}

Expand Down Expand Up @@ -93,7 +101,7 @@ class ConfigCommand extends Command {
type: 'input',
name: 'dbname',
message: 'Enter your Ghost database name:',
default: () => `ghost_${this.environment}`
default: () => `ghost_${this.system.environment}`
}];

if (key && !value) {
Expand All @@ -115,9 +123,7 @@ class ConfigCommand extends Command {
// every prompt is provided in options
// then skip prompts
if ((argv.db && argv.url) || every(prompts.map((prompt) => prompt.name), (name) => argv[name])) {
return this.handleAdvancedOptions(argv).then(() => {
return this.config;
});
return this.handleAdvancedOptions(argv);
}

return this.ui.prompt(prompts).then((values) => {
Expand All @@ -126,14 +132,17 @@ class ConfigCommand extends Command {
values.db = values.db || 'mysql';

return this.handleAdvancedOptions(Object.assign(argv, values));
}).then(() => {
return this.config;
});
}
}

ConfigCommand.description = 'Configure a Ghost instance';
ConfigCommand.params = '[key] [value]';
ConfigCommand.options = advancedOptions;
ConfigCommand.options = Object.assign({
pname: {
description: 'Ghost process name',
type: 'string'
}
}, advancedOptions);

module.exports = ConfigCommand;
35 changes: 14 additions & 21 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const symlinkSync = require('symlink-or-copy').sync;

// Utils
const resolveVersion = require('../utils/resolve-version');
const Config = require('../utils/config');
const errors = require('../errors');

// Tasks/Commands
Expand Down Expand Up @@ -45,13 +44,12 @@ class InstallCommand extends Command {
if (version === 'local') {
local = true;
version = null;
this.development = true;
this.environment = 'development';
this.system.setEnvironment(true, true);
}

return this.ui.listr([{
title: 'Checking for latest Ghost version',
task: this.constructor.tasks.version
task: this.version
}, {
title: 'Running system checks',
task: () => this.ui.listr(installChecks, false, {concurrent: true})
Expand All @@ -68,10 +66,10 @@ class InstallCommand extends Command {
title: 'Moving files',
task: () => this.ui.listr([{
title: 'Summoning Casper',
task: this.constructor.tasks.casper
task: this.casper
}, {
title: 'Linking things',
task: this.constructor.tasks.link
task: this.link.bind(this)
}], false, {concurrent: true})
}], {
version: version,
Expand All @@ -83,40 +81,35 @@ class InstallCommand extends Command {

argv.local = local;

let setup = new SetupCommand(this);
return setup.run(argv);
return this.runCommand(SetupCommand, argv);
});
}
}

InstallCommand.tasks = {
version: (ctx) => {
version(ctx) {
return resolveVersion(ctx.version).then((version) => {
ctx.version = version;
ctx.installPath = path.join(process.cwd(), 'versions', version);
});
},
casper: (ctx) => {
const move = Promise.promisify(fs.move);
}

// TODO: this should be re-thought
return move(
casper(ctx) {
return fs.move(
path.join(ctx.installPath, 'content', 'themes', 'casper'),
path.join(process.cwd(), 'content', 'themes', 'casper')
);
},
link: (ctx) => {
let cliConfig = Config.load('.ghost-cli');
}

link(ctx) {
symlinkSync(ctx.installPath, path.join(process.cwd(), 'current'));

// Make sure we save the current cli version to the config
// also - this ensures the config exists so the config command
// doesn't throw errors
cliConfig.set('cli-version', ctx.cliVersion)
this.system.getInstance().cliConfig
.set('cli-version', this.system.cliVersion)
.set('active-version', ctx.version).save();
}
};
}

InstallCommand.global = true;
InstallCommand.description = 'Install a brand new instance of Ghost';
Expand Down
15 changes: 7 additions & 8 deletions lib/commands/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,34 @@ const PrettyStream = require('ghost-ignition/lib/logging/PrettyStream');

const errors = require('../errors');
const Command = require('../command');
const Config = require('../utils/config');

class LogCommand extends Command {
run(argv) {
let instance = Config.load('system').get(`instances.${argv.name}`, null);
let instance = this.system.getInstance(argv.name);

if (!instance) {
return Promise.reject(new errors.SystemError(`Ghost instance '${argv.name}' does not exist`));
}

// Change into the cwd of the running ghost instance so we can do things
// relative to that
process.chdir(instance.cwd);
process.chdir(instance.dir);

let instanceConfig = Config.load(instance.mode);
instance.loadRunningConfig(true);

// Check if logging file transport is set in config
if (!includes(instanceConfig.get('logging.transports', []), 'file')) {
if (!includes(instance.config.get('logging.transports', []), 'file')) {
// TODO: fallback to process manager log retrieval?
return Promise.reject(new errors.ConfigError({
configKey: 'logging.transports',
configValue: instanceConfig.get('logging.transports').join(', '),
configValue: instance.config.get('logging.transports').join(', '),
message: 'You have excluded file logging in your ghost config.' +
'Please add it to your transport config to use this command.',
environment: instance.mode
environment: this.system.environment
}));
}

let logFileName = path.join(process.cwd(), 'content/logs', `${instanceConfig.get('url').replace(/[^\w]/gi, '_')}_${instance.mode}.log`);
let logFileName = path.join(process.cwd(), 'content/logs', `${instance.config.get('url').replace(/[^\w]/gi, '_')}_${this.system.environment}.log`);
let slice = sliceFile(logFileName);
let prettyStream = new PrettyStream();

Expand Down
34 changes: 7 additions & 27 deletions lib/commands/ls.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,21 @@
'use strict';
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const each = require('lodash/each');
const Command = require('../command');
const Config = require('../utils/config');

class LsCommand extends Command {
run() {
let systemConfig = Config.load('system');
let rows = [];
let instances = systemConfig.get('instances', {});
let save = false;
let instances = this.system.getAllInstances();
let rows = instances.map((instance) => {
let summary = instance.summary();

each(instances, (instance, name) => {
if (!fs.existsSync(path.join(instance.cwd, '.ghost-cli'))) {
// install has been removed, so we want to remove it from the list
delete instances[name];
save = true;
return;
if (!summary.running) {
return [summary.name, summary.dir, summary.version, chalk.red('stopped'), chalk.red('n/a'), chalk.red('n/a'), chalk.red('n/a')];
}

if (!instance.mode) {
rows.push([name, instance.cwd, chalk.red('stopped'), chalk.red('n/a'), chalk.red('n/a'), chalk.red('n/a')]);
return;
}

let instanceConfig = Config.load(path.join(instance.cwd, `config.${instance.mode}.json`));
rows.push([name, instance.cwd, `${chalk.green('running')} (${instance.mode})`, instanceConfig.get('url'), instanceConfig.get('server.port'), instanceConfig.get('process')]);
return [summary.name, summary.dir, summary.version, `${chalk.green('running')} (${summary.mode})`, summary.url, summary.port, summary.process];
});

if (save) {
// There have been instances removed, re-save the instance list
systemConfig.save();
}

this.ui.table(['Name', 'Location', 'Status', 'Url', 'Port', 'Process Manager'], rows, {
this.ui.table(['Name', 'Location', 'Version', 'Status', 'Url', 'Port', 'Process Manager'], rows, {
style: {head: ['cyan']}
});
}
Expand Down
Loading

0 comments on commit 3d6d823

Please sign in to comment.