Skip to content

Commit 379ac6c

Browse files
committed
🐷 Improve config error logs and messages
refs TryGhost#216 This PR changes the `Config.exists()` method in a way that rather than simply returning false, we're catching the error and return a `ConfigError` object that includes the received error object from `fs.readJsonSync()` as a message. There are two places, where `Config.exists()` is being called and was expecting a boolean `false` to determine if the config is valid. But this can mean, that the config either doesn't exist or has a syntax error. It remained a secret to the user. I changed this to check for an `options` property (I'm not very happy with the wording of this, but the CLI uses its own errors and not Ignition and I didn't want to change this within this PR), which than has the `message` property that includes a better error message which we then can log for the user. e. g. ``` A ConfigError occured. Error detected in the production configuration. Message: The config.production.json file is missing or not valid JSON Context: Error: /Users/aileennowak/Code/cli-installs/cli_3/config.production.json: ENOENT: no such file or directory, open '/Users/aileennowak/Code/cli-installs/cli_3/config.production.json' Debug Information: Node Version: v6.11.0 Ghost-CLI Version: 1.0.0-beta.3 Environment: production Command: 'ghost start' ```
1 parent 3c64dd5 commit 379ac6c

File tree

6 files changed

+63
-21
lines changed

6 files changed

+63
-21
lines changed

lib/commands/doctor/checks/startup.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ module.exports = [{
1313
task: (ctx) => {
1414
let config = Config.exists(path.join(process.cwd(), `config.${ctx.environment}.json`));
1515

16-
if (config === false) {
16+
/**
17+
* CASE: `Config.exists()` returns an `options.message` property with a detailed error message
18+
* in case an error (e. g. file doesn't exist or has syntaxerrors) occurred.
19+
* If the `options` property doesn't exist, the config file was found and valid.
20+
*/
21+
if (config.options && config.options.message) {
1722
return Promise.reject(new errors.ConfigError({
1823
environment: ctx.environment,
19-
message: 'Config file is not valid JSON'
24+
message: `The config.${ctx.environment}.json file is missing or not valid JSON`,
25+
context: config.options.message
2026
}));
2127
}
2228

lib/errors.js

+4
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ class ConfigError extends CliError {
138138
chalk.blue(`Run \`${chalk.underline(`ghost config ${this.options.configKey} <new value>`)}\` to fix it.\n`);
139139
}
140140

141+
if (this.options.context) {
142+
initial += `${chalk.gray('Context:')} ${this.options.context}\n`
143+
}
144+
141145
return initial;
142146
}
143147
}

lib/instance.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,22 @@ class Instance {
131131
* @public
132132
*/
133133
checkEnvironment() {
134-
if (
135-
this.system.production &&
136-
Config.exists(path.join(this.dir, 'config.development.json')) &&
137-
!Config.exists(path.join(this.dir, 'config.production.json'))
138-
) {
139-
this.ui.log('Found a development config but not a production config, running in development mode instead', 'yellow');
134+
let prodConfig = Config.exists(path.join(this.dir, 'config.production.json'));
135+
let devConfig = Config.exists(path.join(this.dir, 'config.development.json'));
136+
137+
/**
138+
* CASE: Attempt to start in production mode, but no production config is found.
139+
* `Config.exists()` returns an `options.message` property with a detailed error message
140+
* in case an error (e. g. file doesn't exist or has syntaxerrors) occurred.
141+
* If the `options` property doesn't exist, the config file was found and valid.
142+
*/
143+
if (this.system.production && prodConfig.options && !devConfig.options) {
144+
this.ui.log('The production config is either invalid or missing, running in development mode instead', 'yellow');
145+
146+
if (prodConfig.options.message) {
147+
this.ui.log('Message: ' + prodConfig.options.message, 'grey');
148+
}
149+
140150
this.system.setEnvironment(true, true);
141151
}
142152
}

lib/utils/config.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const _get = require('lodash/get');
55
const _set = require('lodash/set');
66
const _has = require('lodash/has');
77
const fs = require('fs-extra');
8+
const errors = require('../errors');
89

910
/**
1011
* Config class. Basic wrapper around a json file, but handles
@@ -19,12 +20,16 @@ class Config {
1920
* @param {string} filename Filename to load
2021
*/
2122
constructor(filename) {
23+
let config;
24+
2225
if (!filename) {
2326
throw new Error('Config file not specified.');
2427
}
2528

2629
this.file = filename;
27-
this.values = Config.exists(this.file) || {};
30+
31+
config = Config.exists(this.file);
32+
this.values = (config && !config.options) ? config : {};
2833
}
2934

3035
/**
@@ -100,13 +105,17 @@ class Config {
100105
* @static
101106
* @method exists
102107
* @public
108+
* @return {object} that either contains the property `options.message` in
109+
* case of an error, or the parsed config values
103110
*/
104111
static exists(filename) {
105112
try {
106113
var result = fs.readJsonSync(filename);
107114
return result;
108115
} catch (e) {
109-
return false;
116+
return new errors.ConfigError({
117+
message: e
118+
});
110119
}
111120
}
112121
};

test/unit/instance-spec.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,14 @@ describe('Unit: Instance', function () {
253253
it('doesn\'t do anything if config.development.json doesn\'t exist', function () {
254254
let logStub = sandbox.stub();
255255
let environmentStub = sandbox.stub();
256-
let existsStub = sandbox.stub(Config, 'exists').withArgs('/path/config.development.json').returns(false);
256+
let existsStub = sandbox.stub(Config, 'exists');
257+
existsStub.withArgs('/path/config.development.json').returns(
258+
{
259+
options: {
260+
message: 'file does not exist'
261+
}
262+
});
263+
existsStub.withArgs('/path/config.production.json').returns(true);
257264
let testInstance = new Instance({ log: logStub }, {
258265
setEnvironment: environmentStub,
259266
production: true,
@@ -263,25 +270,30 @@ describe('Unit: Instance', function () {
263270
testInstance.checkEnvironment();
264271
expect(logStub.called).to.be.false;
265272
expect(environmentStub.called).to.be.false;
266-
expect(existsStub.calledOnce).to.be.true;
273+
expect(existsStub.calledTwice).to.be.true;
267274
});
268275

269276
it('logs and sets environment if not production, config.dev exists, and config.production doesn\'t exist', function () {
270277
let logStub = sandbox.stub();
271278
let environmentStub = sandbox.stub();
272279
let existsStub = sandbox.stub(Config, 'exists');
273280
existsStub.withArgs('/path/config.development.json').returns(true);
274-
existsStub.withArgs('/path/config.production.json').returns(false);
281+
existsStub.withArgs('/path/config.production.json').returns(
282+
{
283+
options: {
284+
message: 'file does not exist'
285+
}
286+
});
275287
let testInstance = new Instance({ log: logStub }, {
276288
setEnvironment: environmentStub,
277289
production: true,
278290
environment: 'production'
279291
}, '/path');
280292

281293
testInstance.checkEnvironment();
282-
283-
expect(logStub.calledOnce).to.be.true;
284-
expect(logStub.args[0][0]).to.match(/Found a development config/);
294+
expect(logStub.calledTwice).to.be.true;
295+
expect(logStub.args[0][0]).to.match(/The production config is either invalid or missing, running in development mode instead/);
296+
expect(logStub.args[1][0]).to.match(/file does not exist/);
285297
expect(environmentStub.calledOnce).to.be.true;
286298
expect(environmentStub.args[0]).to.deep.equal([true, true]);
287299
expect(existsStub.calledTwice).to.be.true;

test/unit/utils/config-spec.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22
const expect = require('chai').expect;
33
const fs = require('fs-extra');
4-
const path = require('path');
54

65
const Config = require('../../../lib/utils/config');
76

@@ -108,16 +107,18 @@ describe('Unit: Config', function () {
108107
});
109108

110109
describe('exists()', function () {
111-
it('returns false if file does not exist', function () {
110+
it('returns error if file does not exist', function () {
112111
let result = Config.exists('does-not-exist.txt');
113112

114-
expect(result).to.be.false;
113+
expect(result).to.have.property('options');
114+
expect(result.options).to.have.property('message');
115115
});
116116

117-
it('returns false if file contains invalid JSON', function () {
117+
it('returns error if file contains invalid JSON', function () {
118118
fs.writeFileSync('config-test.json', 'invalid json');
119119
let result = Config.exists('config-test.json');
120-
expect(result).to.be.false;
120+
expect(result).to.have.property('options');
121+
expect(result.options).to.have.property('message');
121122
fs.removeSync('config-test.json');
122123
});
123124

0 commit comments

Comments
 (0)