gscan 4.29.2 → 4.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/tpl/index.hbs +2 -2
- package/lib/ast-linter/rules/index.js +5 -1
- package/lib/ast-linter/rules/internal/scope.js +14 -0
- package/lib/ast-linter/rules/lint-no-price-data-currency-context.js +26 -0
- package/lib/ast-linter/rules/lint-no-price-data-currency-global.js +26 -0
- package/lib/ast-linter/rules/lint-no-price-data-monthly-yearly.js +20 -0
- package/lib/ast-linter/rules/lint-no-tier-benefit-as-object.js +30 -0
- package/lib/ast-linter/rules/{lint-no-price-data-helper.js → lint-no-tier-price-as-object.js} +6 -3
- package/lib/checks/010-package-json.js +9 -1
- package/lib/format.js +1 -24
- package/lib/specs/canary.js +46 -8
- package/lib/utils/score-calculator.js +40 -0
- package/lib/utils/versions.json +1 -1
- package/package.json +2 -2
package/app/tpl/index.hbs
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
<span class="gh-input-icon select-arrow">{{> icon-arrow-up}}</span>
|
|
23
23
|
<span class="gh-input-icon select-arrow active">{{> icon-arrow-down}}</span>
|
|
24
24
|
<select class="gh-input gh-select" name="version" id="version">
|
|
25
|
-
<option value="v5">{{ghostVersions.v5.major}}</option>
|
|
26
|
-
<option value="v4"
|
|
25
|
+
<option value="v5" selected>{{ghostVersions.v5.major}}</option>
|
|
26
|
+
<option value="v4">{{ghostVersions.v4.major}}</option>
|
|
27
27
|
<option value="v3">{{ghostVersions.v3.major}}</option>
|
|
28
28
|
<option value="v2">{{ghostVersions.v2.major}}</option>
|
|
29
29
|
<option value="v1">{{ghostVersions.v1.major}}</option>
|
|
@@ -7,7 +7,11 @@ module.exports = {
|
|
|
7
7
|
'GS090-NO-PRODUCT-DATA-HELPER': require('./lint-no-product-data-helper'),
|
|
8
8
|
'GS090-NO-PRODUCTS-DATA-HELPER': require('./lint-no-products-data-helper'),
|
|
9
9
|
'GS090-NO-MEMBER-PRODUCTS-DATA-HELPER': require('./lint-no-member-products-data-helper'),
|
|
10
|
-
'GS090-NO-PRICE-
|
|
10
|
+
'GS090-NO-TIER-PRICE-AS-OBJECT': require('./lint-no-tier-price-as-object'),
|
|
11
|
+
'GS090-NO-TIER-BENEFIT-AS-OBJECT': require('./lint-no-tier-benefit-as-object'),
|
|
12
|
+
'GS090-NO-PRICE-DATA-CURRENCY-GLOBAL': require('./lint-no-price-data-currency-global'),
|
|
13
|
+
'GS090-NO-PRICE-DATA-CURRENCY-CONTEXT': require('./lint-no-price-data-currency-context'),
|
|
14
|
+
'GS090-NO-PRICE-DATA-MONTHLY-YEARLY': require('./lint-no-price-data-monthly-yearly'),
|
|
11
15
|
'no-multi-param-conditionals': require('./lint-no-multi-param-conditionals'),
|
|
12
16
|
'no-nested-async-helpers': require('./lint-no-nested-async-helpers'),
|
|
13
17
|
'no-prev-next-post-outside-post-context': require('./lint-no-prev-next-post-outside-post-context'),
|
|
@@ -204,6 +204,20 @@ class Scope {
|
|
|
204
204
|
return found;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
getParentContextNode(context) {
|
|
208
|
+
let matchedFrame = null;
|
|
209
|
+
|
|
210
|
+
if (this.frames && this.frames.length) {
|
|
211
|
+
matchedFrame = this.frames.find((frame) => {
|
|
212
|
+
if (frame.nodeName === context) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return matchedFrame && matchedFrame.node;
|
|
219
|
+
}
|
|
220
|
+
|
|
207
221
|
isLocal(node) {
|
|
208
222
|
// @foo MustacheStatements are referencing globals rather than locals
|
|
209
223
|
if (node.type === 'MustacheStatement' && node.path.data) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
module.exports = class NoPriceDataCurrencyContext extends Rule {
|
|
5
|
+
_findPriceData(node) {
|
|
6
|
+
if (node.data && node.parts && node.parts.length === 2 && node.parts[0] === 'price' && node.parts[1] === 'currency') {
|
|
7
|
+
const foreachNode = this.scope.getParentContextNode('foreach');
|
|
8
|
+
const foreachObject = _.get(foreachNode, 'params[0].original');
|
|
9
|
+
|
|
10
|
+
if (['@member.subscriptions', 'tiers'].includes(foreachObject)) {
|
|
11
|
+
this.log(({
|
|
12
|
+
message: `{{@price.currency}} should be replaced with {{currency}}`,
|
|
13
|
+
line: node.loc && node.loc.start.line,
|
|
14
|
+
column: node.loc && node.loc.start.column,
|
|
15
|
+
source: this.sourceForNode(node)
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
visitor() {
|
|
22
|
+
return {
|
|
23
|
+
PathExpression: this._findPriceData.bind(this)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
module.exports = class NoPriceDataCurrencyGlobal extends Rule {
|
|
5
|
+
_findPriceData(node) {
|
|
6
|
+
if (node.data && node.parts && node.parts.length === 2 && node.parts[0] === 'price' && node.parts[1] === 'currency') {
|
|
7
|
+
const foreachNode = this.scope.getParentContextNode('foreach');
|
|
8
|
+
const foreachObject = _.get(foreachNode, 'params[0].original');
|
|
9
|
+
|
|
10
|
+
if (!['@member.subscriptions', 'tiers'].includes(foreachObject)) {
|
|
11
|
+
this.log(({
|
|
12
|
+
message: `{{@price.currency}} should be replaced with {{currency}} and a context`,
|
|
13
|
+
line: node.loc && node.loc.start.line,
|
|
14
|
+
column: node.loc && node.loc.start.column,
|
|
15
|
+
source: this.sourceForNode(node)
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
visitor() {
|
|
22
|
+
return {
|
|
23
|
+
PathExpression: this._findPriceData.bind(this)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
|
|
3
|
+
module.exports = class NoPriceDataMonthlyYearly extends Rule {
|
|
4
|
+
_findPriceData(node) {
|
|
5
|
+
if (node.data && node.parts && node.parts.length === 2 && node.parts[0] === 'price' && ['monthly', 'yearly'].includes(node.parts[1])) {
|
|
6
|
+
this.log(({
|
|
7
|
+
message: `{{@price.monthly}} and {{@price.yearly}} should be replaced with {{#get "tiers"}}`,
|
|
8
|
+
line: node.loc && node.loc.start.line,
|
|
9
|
+
column: node.loc && node.loc.start.column,
|
|
10
|
+
source: this.sourceForNode(node)
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
visitor() {
|
|
16
|
+
return {
|
|
17
|
+
PathExpression: this._findPriceData.bind(this)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const Rule = require('./base');
|
|
2
|
+
const {getNodeName, logNode} = require('../helpers');
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
module.exports = class NoTierBenefitAsObject extends Rule {
|
|
6
|
+
_checkForHelperName(node) {
|
|
7
|
+
const nodeName = getNodeName(node);
|
|
8
|
+
const isMatchingHelper = (nodeName.includes('name'));
|
|
9
|
+
const foreachNode = this.scope.getParentContextNode('foreach');
|
|
10
|
+
const getNode = this.scope.getParentContextNode('get');
|
|
11
|
+
|
|
12
|
+
const hasForeachBenefits = _.get(foreachNode, 'params[0].original') === 'benefits';
|
|
13
|
+
const hasGetTiers = _.get(getNode, 'params[0].original') === 'tiers';
|
|
14
|
+
|
|
15
|
+
if (hasForeachBenefits && hasGetTiers && isMatchingHelper) {
|
|
16
|
+
this.log({
|
|
17
|
+
message: `${logNode(node)} should not be used`,
|
|
18
|
+
line: node.loc && node.loc.start.line,
|
|
19
|
+
column: node.loc && node.loc.start.column,
|
|
20
|
+
source: this.sourceForNode(node)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
visitor() {
|
|
26
|
+
return {
|
|
27
|
+
MustacheStatement: this._checkForHelperName.bind(this)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
package/lib/ast-linter/rules/{lint-no-price-data-helper.js → lint-no-tier-price-as-object.js}
RENAMED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
1
2
|
const Rule = require('./base');
|
|
2
3
|
const {getNodeName, logNode} = require('../helpers');
|
|
3
4
|
|
|
4
|
-
module.exports = class
|
|
5
|
+
module.exports = class NoMonthlyYearlyPriceObjects extends Rule {
|
|
5
6
|
_checkForHelperName(node) {
|
|
6
7
|
const nodeName = getNodeName(node);
|
|
7
|
-
const isMatchingHelper = (nodeName.
|
|
8
|
+
const isMatchingHelper = (nodeName.includes('monthly_price.')) || (nodeName.includes('yearly_price.'));
|
|
9
|
+
const getNode = this.scope.getParentContextNode('get');
|
|
10
|
+
const hasGetTiers = _.get(getNode, 'params[0].original') === 'tiers';
|
|
8
11
|
|
|
9
|
-
if (isMatchingHelper) {
|
|
12
|
+
if (isMatchingHelper && hasGetTiers) {
|
|
10
13
|
this.log({
|
|
11
14
|
message: `${logNode(node)} should not be used`,
|
|
12
15
|
line: node.loc && node.loc.start.line,
|
|
@@ -55,7 +55,10 @@ const v4PackageJSONValidationRules = _.extend({},
|
|
|
55
55
|
);
|
|
56
56
|
|
|
57
57
|
const canaryPackageJSONConditionalRules = {};
|
|
58
|
-
const canaryPackageJSONValidationRules = _.extend({}
|
|
58
|
+
const canaryPackageJSONValidationRules = _.extend({},
|
|
59
|
+
{isNotPresentCardAssets: 'GS010-PJ-GHOST-CARD-ASSETS-NOT-PRESENT'},
|
|
60
|
+
canaryPackageJSONConditionalRules
|
|
61
|
+
);
|
|
59
62
|
|
|
60
63
|
_private.validatePackageJSONFields = function validatePackageJSONFields(packageJSON, theme, packageJSONValidationRules) {
|
|
61
64
|
let failed = [];
|
|
@@ -138,6 +141,11 @@ _private.validatePackageJSONFields = function validatePackageJSONFields(packageJ
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// @TODO: validate that the card_assets config is valid
|
|
145
|
+
if (!packageJSON.config || (_.isEmpty(packageJSON.config.card_assets) && !_.isBoolean(packageJSON.config.card_assets))) {
|
|
146
|
+
markFailed('isNotPresentCardAssets');
|
|
147
|
+
}
|
|
148
|
+
|
|
141
149
|
if (packageJSON.config && packageJSON.config.custom) {
|
|
142
150
|
const customSettingsKeys = Object.keys(packageJSON.config.custom);
|
|
143
151
|
|
package/lib/format.js
CHANGED
|
@@ -1,30 +1,7 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const spec = require('./specs');
|
|
3
|
-
const levelWeights = {
|
|
4
|
-
error: 10,
|
|
5
|
-
warning: 3,
|
|
6
|
-
recommendation: 1
|
|
7
|
-
};
|
|
8
3
|
const versions = require('./utils').versions;
|
|
9
|
-
|
|
10
|
-
const calcScore = function calcScore(results, stats) {
|
|
11
|
-
var maxScore, actualScore, balancedScore;
|
|
12
|
-
|
|
13
|
-
maxScore = _.reduce(levelWeights, function (max, weight, level) {
|
|
14
|
-
return max + (weight * stats[level]);
|
|
15
|
-
}, 0);
|
|
16
|
-
|
|
17
|
-
actualScore = _.reduce(levelWeights, function (max, weight, level) {
|
|
18
|
-
return max - (weight * _.size(results[level]));
|
|
19
|
-
}, maxScore);
|
|
20
|
-
|
|
21
|
-
balancedScore = _.ceil((100 / maxScore) * actualScore);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
value: balancedScore,
|
|
25
|
-
level: _.size(results.error) > 0 ? 'error' : balancedScore < 60 ? 'warning' : 'passing'
|
|
26
|
-
};
|
|
27
|
-
};
|
|
4
|
+
const calcScore = require('./utils/score-calculator');
|
|
28
5
|
|
|
29
6
|
const formatCLI = (theme) => {
|
|
30
7
|
_.each(theme.results, (results) => {
|
package/lib/specs/canary.js
CHANGED
|
@@ -21,10 +21,16 @@ let rules = {
|
|
|
21
21
|
// New rules
|
|
22
22
|
'GS010-PJ-GHOST-API-PRESENT': {
|
|
23
23
|
level: 'warning',
|
|
24
|
-
rule: 'Remove <code>"engines.ghost-api"</code> from <code>package.json</code
|
|
24
|
+
rule: 'Remove <code>"engines.ghost-api"</code> from <code>package.json</code>',
|
|
25
25
|
details: oneLineTrim`The <code>"ghost-api"</code> version is no longer used and can be removed.<br>
|
|
26
26
|
Find more information about the <code>package.json</code> file <a href="${docsBaseUrl}structure/#packagejson" target=_blank>here</a>.`
|
|
27
27
|
},
|
|
28
|
+
'GS010-PJ-GHOST-CARD-ASSETS-NOT-PRESENT': {
|
|
29
|
+
level: 'warning',
|
|
30
|
+
rule: '<code>"card_assets"</code> will default to <code>true</code> in <code>package.json</code>',
|
|
31
|
+
details: oneLineTrim`The <code>"card_assets"</code> property is enabled by default if not explicitly set.<br>
|
|
32
|
+
Find more information about the <code>card_assets</code> property <a href="${docsBaseUrl}content/#editor-cards" target=_blank>here</a>.`
|
|
33
|
+
},
|
|
28
34
|
'GS090-NO-AUTHOR-HELPER-IN-POST-CONTEXT': {
|
|
29
35
|
level: 'error',
|
|
30
36
|
fatal: true,
|
|
@@ -69,14 +75,46 @@ let rules = {
|
|
|
69
75
|
Find more information about the <code>{{@member.subscriptions}}</code> property <a href="${docsBaseUrl}members/#member-subscriptions" target=_blank>here</a>.`,
|
|
70
76
|
helper: '{{@member.products}}'
|
|
71
77
|
},
|
|
72
|
-
'GS090-NO-PRICE-DATA-
|
|
78
|
+
'GS090-NO-PRICE-DATA-CURRENCY-GLOBAL': {
|
|
79
|
+
level: 'error',
|
|
80
|
+
fatal: true,
|
|
81
|
+
rule: 'Replace <code>@price.currency</code> with <code>{{#get "tiers"}}</code> and <code>{{currency}}</code> or <code>{{#foreach @member.subscriptions}}</code> and <code>{{plan.currency}}</code>',
|
|
82
|
+
details: oneLineTrim`There is no longer a global <code>@price</code> object. You need to use either <code>{{#get "tiers"}}</code> to fetch all tiers and use the <code>{{currency}}</code> property of a tier<br>
|
|
83
|
+
or use <code>{{#foreach @member.subscriptions}}</code> to fetch an individual member's subscriptions, and use the <code>{{plan.currency}}</code> property from the subscription.<br>
|
|
84
|
+
Find more information about the <code>{{price}}</code> helper <a href="${docsBaseUrl}members/price/" target=_blank>here</a>.`
|
|
85
|
+
},
|
|
86
|
+
'GS090-NO-PRICE-DATA-CURRENCY-CONTEXT': {
|
|
87
|
+
level: 'error',
|
|
88
|
+
fatal: true,
|
|
89
|
+
rule: 'Replace <code>{{@price.currency}}</code> with <code>{{currency}}</code> or <code>{{plan.currency}}</code>',
|
|
90
|
+
details: oneLineTrim`There is no longer a global <code>@price</code> object. Instead the <code>{{currency}}</code> property can be used inside <code>{{#get "tiers"}}</code><br>
|
|
91
|
+
or <code>{{plan.currency}}</code> can be used inside <code>{{#foreach @member.subscriptions}}</code><br>
|
|
92
|
+
Find more information about the <code>{{price}}</code> helper <a href="${docsBaseUrl}members/price/" target=_blank>here</a>.`
|
|
93
|
+
},
|
|
94
|
+
'GS090-NO-PRICE-DATA-MONTHLY-YEARLY': {
|
|
95
|
+
level: 'error',
|
|
96
|
+
fatal: true,
|
|
97
|
+
rule: 'Replace <code>{{@price.monthly}}</code> and <code>{{@price.yearly}}</code> with <code>{{#get "tiers"}}</code> and <code>{{monthly_price}}</code> or <code>{{yearly_price}}</code>',
|
|
98
|
+
details: oneLineTrim`There is no longer a global <code>@price</code> object. You need to use <code>{{#get "tiers"}}</code> to fetch all the tiers and get access to the <code>{{monthly_price}}</code> or <code>{{yearly_price}}</code> for each tier<br>
|
|
99
|
+
Find more information about the <code>{{price}}</code> helper <a href="${docsBaseUrl}helpers/price/" target=_blank>here</a>.`
|
|
100
|
+
},
|
|
101
|
+
'GS090-NO-TIER-PRICE-AS-OBJECT': {
|
|
102
|
+
level: 'error',
|
|
103
|
+
fatal: true,
|
|
104
|
+
rule: 'Remove usage of <code>{{monthly_price.*}}</code> and <code>{{yearly_price.*}}.</code>',
|
|
105
|
+
details: oneLineTrim`The usage of <code>{{monthly_price.*}}</code> and <code>{{yearly_price.*}} is no longer supported.</code><br>
|
|
106
|
+
${tierDesc}<br>
|
|
107
|
+
Find more information about the <code>{{#get "tiers"}}</code> <a href="${docsBaseUrl}helpers/tiers/" target=_blank>here</a>.`,
|
|
108
|
+
helper: '{{#get "tiers"}}'
|
|
109
|
+
},
|
|
110
|
+
'GS090-NO-TIER-BENEFIT-AS-OBJECT': {
|
|
73
111
|
level: 'error',
|
|
74
112
|
fatal: true,
|
|
75
|
-
rule: '
|
|
76
|
-
details: oneLineTrim`The <code>{{
|
|
113
|
+
rule: 'Remove usage of <code>{{name}}</code> for tier benefits.</code>',
|
|
114
|
+
details: oneLineTrim`The usage of <code>{{name}}</code> for tier benefits is no longer supported.</code><br>
|
|
77
115
|
${tierDesc}<br>
|
|
78
|
-
Find more information about the <code>{{
|
|
79
|
-
helper: '{{
|
|
116
|
+
Find more information about the <code>{{#get "tiers"}}</code> <a href="${docsBaseUrl}helpers/tiers/" target=_blank>here</a>.`,
|
|
117
|
+
helper: '{{#get "tiers"}}'
|
|
80
118
|
},
|
|
81
119
|
|
|
82
120
|
// Updated v1 & v2 rules
|
|
@@ -685,8 +723,8 @@ let rules = {
|
|
|
685
723
|
level: 'error',
|
|
686
724
|
fatal: true,
|
|
687
725
|
rule: 'Replace <code>{{[#].currency_symbol}}</code> with <code>{{price currency=currency}}</code>.',
|
|
688
|
-
details: oneLineTrim`The
|
|
689
|
-
Find more information about the updated <code>{{price}}</code> helper <a href="${docsBaseUrl}
|
|
726
|
+
details: oneLineTrim`The <code>currency_symbol</code> attribute is no longer supported in favour of passing the currency to updated <code>{{price}}</code> helper.<br>
|
|
727
|
+
Find more information about the updated <code>{{price}}</code> helper <a href="${docsBaseUrl}helpers/price" target=_blank>here</a>.`,
|
|
690
728
|
helper: '{{[#].currency_symbol}}',
|
|
691
729
|
regex: /currency_symbol/g
|
|
692
730
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
|
|
3
|
+
const levelWeights = {
|
|
4
|
+
error: 10,
|
|
5
|
+
warning: 3,
|
|
6
|
+
recommendation: 1
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} results
|
|
12
|
+
* @param {Object} results.error
|
|
13
|
+
* @param {Object} results.warning
|
|
14
|
+
* @param {Object} results.recommendation
|
|
15
|
+
* @param {Object} stats
|
|
16
|
+
* @param {Object} stats.error
|
|
17
|
+
* @param {Object} stats.warning
|
|
18
|
+
* @param {Object} stats.recommendation
|
|
19
|
+
* @returns {Object}
|
|
20
|
+
*/
|
|
21
|
+
const calcScore = function calcScore(results, stats) {
|
|
22
|
+
var maxScore, actualScore, balancedScore;
|
|
23
|
+
|
|
24
|
+
maxScore = _.reduce(levelWeights, function (max, weight, level) {
|
|
25
|
+
return max + (weight * stats[level]);
|
|
26
|
+
}, 0);
|
|
27
|
+
|
|
28
|
+
actualScore = _.reduce(levelWeights, function (max, weight, level) {
|
|
29
|
+
return max - (weight * _.size(results[level]));
|
|
30
|
+
}, maxScore);
|
|
31
|
+
|
|
32
|
+
balancedScore = _.floor((100 / maxScore) * actualScore);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
value: balancedScore,
|
|
36
|
+
level: _.size(results.error) > 0 ? 'error' : balancedScore < 60 ? 'warning' : 'passing'
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
module.exports = calcScore;
|
package/lib/utils/versions.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscan",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.30.0",
|
|
4
4
|
"description": "Scans Ghost themes looking for errors, deprecations, features and compatibility",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ghost",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@tryghost/debug": "0.1.11",
|
|
46
46
|
"@tryghost/errors": "1.2.12",
|
|
47
47
|
"@tryghost/logging": "2.1.8",
|
|
48
|
-
"@tryghost/pretty-cli": "1.2.
|
|
48
|
+
"@tryghost/pretty-cli": "1.2.28",
|
|
49
49
|
"@tryghost/server": "0.1.4",
|
|
50
50
|
"@tryghost/zip": "1.1.25",
|
|
51
51
|
"bluebird": "3.7.2",
|