Skip to content

Commit 1f9383f

Browse files
committed
feat(startup): validate config on startup
closes #26 - add `startup` checks to ghost-cli
1 parent da4e1d9 commit 1f9383f

File tree

4 files changed

+99
-41
lines changed

4 files changed

+99
-41
lines changed

lib/commands/config/advanced.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
// Advanced options for the config command
33
const portfinder = require('portfinder');
4+
const toString = require('lodash/toString');
45
const includes = require('lodash/includes');
56
const validator = require('validator');
67
const url = require('url');
@@ -39,6 +40,8 @@ module.exports = [{
3940
description: 'Port ghost should listen on',
4041
configPath: 'server.port',
4142
validate: value => {
43+
value = toString(value);
44+
4245
if (!validator.isInt(value, {allow_leading_zeros: false})) {
4346
return 'Port must be an integer.';
4447
}

lib/commands/doctor/checks/startup.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
const path = require('path');
3+
const get = require('lodash/get');
4+
const filter = require('lodash/filter');
5+
const Promise = require('bluebird');
6+
7+
const errors = require('../../../errors');
8+
const Config = require('../../../utils/config');
9+
const advancedOptions = require('../../config/advanced');
10+
11+
module.exports = [{
12+
title: 'Validating config',
13+
task: (ctx) => {
14+
let config = Config.exists(path.join(process.cwd(), `config.${ctx.environment}.json`));
15+
16+
if (config === false) {
17+
return Promise.reject(new errors.ConfigError({
18+
environment: ctx.environment,
19+
message: 'Config file is not valid JSON'
20+
}));
21+
}
22+
23+
let configValidations = filter(advancedOptions, cfg => cfg.validate);
24+
25+
return Promise.each(configValidations, (configItem) => {
26+
let key = configItem.configPath || configItem.name
27+
let value = get(config, key);
28+
29+
if (!value) {
30+
return;
31+
}
32+
33+
return Promise.resolve(configItem.validate(value)).then((validated) => {
34+
if (validated !== true) {
35+
return Promise.reject(new errors.ConfigError({
36+
configKey: key,
37+
configValue: value,
38+
message: validated,
39+
environment: ctx.environment
40+
}));
41+
}
42+
});
43+
});
44+
}
45+
}];

lib/commands/start.js

+41-36
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
const fs = require('fs');
33
const path = require('path');
44
const KnexMigrator = require('knex-migrator');
5+
const Listr = require('listr');
56

67
const Config = require('../utils/config');
78
const errors = require('../errors');
9+
const startupChecks = require('./doctor/checks/startup');
810
const checkValidInstall = require('../utils/check-valid-install');
911

1012
function register(config, environment) {
@@ -25,6 +27,7 @@ module.exports.execute = function execute(options) {
2527
checkValidInstall('start');
2628

2729
options = options || {};
30+
let config, cliConfig;
2831

2932
// If we are starting in production mode but a development config exists and a production config doesn't,
3033
// we want to start in development mode anyways.
@@ -35,47 +38,49 @@ module.exports.execute = function execute(options) {
3538
process.env.NODE_ENV = this.environment = 'development';
3639
}
3740

38-
let config = Config.load(this.environment);
39-
let cliConfig = Config.load('.ghost-cli');
41+
return new Listr(startupChecks, {renderer: this.renderer}).run(this).then(() => {
42+
config = Config.load(this.environment);
43+
cliConfig = Config.load('.ghost-cli');
4044

41-
if (cliConfig.has('running')) {
42-
return Promise.reject(new Error('Ghost is already running.'));
43-
}
45+
if (cliConfig.has('running')) {
46+
return Promise.reject(new Error('Ghost is already running.'));
47+
}
4448

45-
this.service.setConfig(config);
49+
this.service.setConfig(config);
4650

47-
process.env.paths__contentPath = path.join(process.cwd(), 'content');
51+
process.env.paths__contentPath = path.join(process.cwd(), 'content');
4852

49-
let knexMigrator = new KnexMigrator({
50-
knexMigratorFilePath: path.join(process.cwd(), 'current')
51-
});
52-
53-
return knexMigrator.isDatabaseOK().catch((error) => {
54-
if (error.code === 'DB_NOT_INITIALISED' ||
55-
error.code === 'MIGRATION_TABLE_IS_MISSING') {
56-
return knexMigrator.init();
57-
} else if (error.code === 'DB_NEEDS_MIGRATION') {
58-
return knexMigrator.migrate();
59-
}
60-
61-
if (error.code === 'ENOTFOUND') {
62-
// Database not found
63-
error = new errors.ConfigError({
64-
configKey: 'database.connection.host',
65-
configValue: config.get('database.connection.host'),
66-
message: 'Invalid database host',
67-
environment: this.environment
68-
});
69-
} else if (error.code === 'ER_ACCESS_DENIED_ERROR') {
70-
error = new errors.ConfigError({
71-
configKey: 'database.connection.user',
72-
configValue: config.get('database.connection.user'),
73-
message: 'Invalid database username or password',
74-
environment: this.environment
75-
});
76-
}
53+
let knexMigrator = new KnexMigrator({
54+
knexMigratorFilePath: path.join(process.cwd(), 'current')
55+
});
7756

78-
return Promise.reject(error);
57+
return knexMigrator.isDatabaseOK().catch((error) => {
58+
if (error.code === 'DB_NOT_INITIALISED' ||
59+
error.code === 'MIGRATION_TABLE_IS_MISSING') {
60+
return knexMigrator.init();
61+
} else if (error.code === 'DB_NEEDS_MIGRATION') {
62+
return knexMigrator.migrate();
63+
}
64+
65+
if (error.code === 'ENOTFOUND') {
66+
// Database not found
67+
error = new errors.ConfigError({
68+
configKey: 'database.connection.host',
69+
configValue: config.get('database.connection.host'),
70+
message: 'Invalid database host',
71+
environment: this.environment
72+
});
73+
} else if (error.code === 'ER_ACCESS_DENIED_ERROR') {
74+
error = new errors.ConfigError({
75+
configKey: 'database.connection.user',
76+
configValue: config.get('database.connection.user'),
77+
message: 'Invalid database username or password',
78+
environment: this.environment
79+
});
80+
}
81+
82+
return Promise.reject(error);
83+
});
7984
}).then(() => {
8085
let start = () => Promise.resolve(this.service.process.start(process.cwd(), this.environment)).then(() => {
8186
register(config, this.environment);

lib/errors.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,16 @@ class ConfigError extends CliError {
111111
}
112112

113113
toString() {
114-
return chalk.red(`Error detected in the ${this.options.environment} configuration.\n\n`) +
115-
`${chalk.gray('Message:')} ${this.options.message}\n` +
116-
`${chalk.gray('Configuration Key:')} ${this.options.configKey}\n` +
117-
`${chalk.gray('Current Value:')} ${this.options.configValue}\n\n` +
118-
chalk.blue(`Run \`${chalk.underline('ghost config ${this.options.configKey} <new value>')}\` to fix it.\n`);
114+
let initial = chalk.red(`Error detected in the ${this.options.environment} configuration.\n\n`) +
115+
`${chalk.gray('Message:')} ${this.options.message}\n`;
116+
117+
if (this.options.configKey) {
118+
initial += `${chalk.gray('Configuration Key:')} ${this.options.configKey}\n` +
119+
`${chalk.gray('Current Value:')} ${this.options.configValue}\n\n` +
120+
chalk.blue(`Run \`${chalk.underline(`ghost config ${this.options.configKey} <new value>`)}\` to fix it.\n`);
121+
}
122+
123+
return initial;
119124
}
120125
}
121126

0 commit comments

Comments
 (0)