gscan 4.22.0 → 4.23.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/README.md CHANGED
@@ -18,7 +18,7 @@ In addition, an **error** can be marked as **fatal**. A **fatal error** means, l
18
18
 
19
19
  In Ghost, we call GScan on boot. If any fatal errors are detected, the blog will not boot. In Ghost(Pro) and in Ghost-CLI we call GScan as part of major upgrades. The upgrade will not be allowed to continue if any fatal errors are detected.
20
20
 
21
- Errors are only be marked as **fatal errors** if they would cause errors, and therefore should block a boot or an upgrade.
21
+ Errors are only marked as **fatal errors** if they would cause errors, and therefore should block a boot or an upgrade.
22
22
 
23
23
  ### Tooling
24
24
  When developing new rules or testing gscan following tools are great to have in the toolbelt:
@@ -46,12 +46,13 @@ To run a local zip file through the checks:
46
46
 
47
47
  `gscan /path/to/theme.zip -z`
48
48
 
49
- By default, GScan scans themes for the latest Ghost version compatibility. You can also specify a Ghost version by using the following parameters (for Ghost 1.0, 2.0, 3.0 and 4.0):
49
+ By default, GScan scans themes for the latest Ghost version compatibility. You can also specify a Ghost version by using the following parameters (for Ghost 1.0, 2.0, 3.0, 4.0 and 5.0):
50
50
 
51
51
  `--v1` or `-1`
52
52
  `--v2` or `-2`
53
53
  `--v3` or `-3`
54
- `--v4` or `-4` or `--canary`
54
+ `--v4` or `-4`
55
+ `--v5` or `-5` or `--canary`
55
56
 
56
57
  Use the `--canary` parameter to check for the upcoming Ghost version.
57
58
 
@@ -72,9 +73,9 @@ gscan.checkZip({
72
73
  path: 'path-to-zip',
73
74
  // if you need to check the theme for a different
74
75
  // major Ghost version, you can pass it. Currently
75
- // v1, v2, v3 and canary are supported. Default is
76
- // the latest Ghost version 3.0:
77
- // checkVersion: 'v3',
76
+ // v1, v2, v3, v4 and canary (v5) are supported. Default is
77
+ // the latest Ghost version 4.0:
78
+ // checkVersion: 'v4',
78
79
  name: 'my-theme'
79
80
  }).then(function (result) {
80
81
  console.log(result);
package/app/tpl/index.hbs CHANGED
@@ -23,6 +23,7 @@
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
25
  <option value="canary" selected>{{ghostVersions.canary.major}}</option>
26
+ <option value="v4">{{ghostVersions.v4.major}}</option>
26
27
  <option value="v3">{{ghostVersions.v3.major}}</option>
27
28
  <option value="v2">{{ghostVersions.v2.major}}</option>
28
29
  <option value="v1">{{ghostVersions.v1.major}}</option>
package/bin/cli.js CHANGED
@@ -53,6 +53,9 @@ prettyCLI
53
53
  .boolean('-4, --v4', {
54
54
  desc: 'Check theme for Ghost 4.0 compatibility'
55
55
  })
56
+ .boolean('-5, --v5', {
57
+ desc: 'Check theme for Ghost 4.0 compatibility'
58
+ })
56
59
  .boolean('-c, --canary', {
57
60
  desc: 'Check theme for upcoming Ghost version compatibility'
58
61
  })
@@ -75,6 +78,8 @@ prettyCLI
75
78
  cliOptions.checkVersion = 'v3';
76
79
  } else if (argv.v4) {
77
80
  cliOptions.checkVersion = 'v4';
81
+ } else if (argv.v5) {
82
+ cliOptions.checkVersion = 'v5';
78
83
  } else if (argv.canary) {
79
84
  cliOptions.checkVersion = 'canary';
80
85
  } else {
@@ -2,6 +2,7 @@ module.exports = {
2
2
  'GS090-NO-IMG-URL-IN-CONDITIONALS': require('./lint-no-img-url-in-conditionals'),
3
3
  'GS090-NO-UNKNOWN-CUSTOM-THEME-SETTINGS': require('./lint-no-unknown-custom-theme-settings'),
4
4
  'GS090-NO-UNKNOWN-CUSTOM-THEME-SELECT-VALUE-IN-MATCH': require('./lint-no-unknown-custom-theme-select-value-in-match'),
5
+ 'GS090-NO-AUTHOR-HELPER-IN-POST-CONTEXT': require('./lint-no-author-helper-in-post-page-context'),
5
6
  'no-multi-param-conditionals': require('./lint-no-multi-param-conditionals'),
6
7
  'no-nested-async-helpers': require('./lint-no-nested-async-helpers'),
7
8
  'no-prev-next-post-outside-post-context': require('./lint-no-prev-next-post-outside-post-context'),
@@ -190,6 +190,20 @@ class Scope {
190
190
  return this.currentFrame && this.currentFrame.context === context || false;
191
191
  }
192
192
 
193
+ hasParentContext(context) {
194
+ let found = false;
195
+
196
+ if (this.frames && this.frames.length) {
197
+ this.frames.forEach((frame) => {
198
+ if (frame.nodeName === context) {
199
+ found = true;
200
+ }
201
+ });
202
+ }
203
+
204
+ return found;
205
+ }
206
+
193
207
  isLocal(node) {
194
208
  // @foo MustacheStatements are referencing globals rather than locals
195
209
  if (node.type === 'MustacheStatement' && node.path.data) {
@@ -0,0 +1,25 @@
1
+ const Rule = require('./base');
2
+ const {getNodeName, logNode} = require('../helpers');
3
+
4
+ module.exports = class NoAuthorHelperInPostContext extends Rule {
5
+ _checkForHelerInPostContext(node) {
6
+ const nodeName = getNodeName(node);
7
+ const isAuthorHelper = (nodeName === 'author');
8
+ const isPostContext = this.scope.hasParentContext('post');
9
+
10
+ if (isAuthorHelper && isPostContext) {
11
+ this.log({
12
+ message: `${logNode(node)} should not be used in ${this.scope.currentFrame.context} 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
+ visitor() {
21
+ return {
22
+ MustacheStatement: this._checkForHelerInPostContext.bind(this)
23
+ };
24
+ }
25
+ };
package/lib/checker.js CHANGED
@@ -27,7 +27,7 @@ const check = function checkAll(themePath, options = {}) {
27
27
  const passedVersion = _.get(options, 'checkVersion', versions.default);
28
28
  let version = passedVersion;
29
29
 
30
- if (passedVersion === 'v4') {
30
+ if (passedVersion === 'v5') {
31
31
  version = 'canary';
32
32
  }
33
33
 
@@ -33,14 +33,15 @@ const v2PackageJSONValidationRules = _.extend({},
33
33
 
34
34
  const v3PackageJSONConditionalRules = {};
35
35
  const v3PackageJSONValidationRules = _.extend({},
36
- {isPresentEngineGhostAPI: 'GS010-PJ-GHOST-API'},
36
+ {isNotPresentEngineGhostAPI: 'GS010-PJ-GHOST-API'},
37
37
  {isv01EngineGhostAPI: 'GS010-PJ-GHOST-API-V01'},
38
38
  v3PackageJSONConditionalRules
39
39
  );
40
40
 
41
- const canaryPackageJSONConditionalRules = {};
42
- const canaryPackageJSONValidationRules = _.extend({},
41
+ const v4PackageJSONConditionalRules = {};
42
+ const v4PackageJSONValidationRules = _.extend({},
43
43
  {isv2EngineGhostAPI: 'GS010-PJ-GHOST-API-V2'},
44
+ {isPresentEngineGhostAPI: 'GS010-PJ-GHOST-API-PRESENT'},
44
45
  {hasTooManyCustomThemeSettings: 'GS010-PJ-CUST-THEME-TOTAL-SETTINGS'},
45
46
  {customThemeSettingsMustBeSnakecased: 'GS010-PJ-CUST-THEME-SETTINGS-CASE'},
46
47
  {unkownCustomThemeSettingsType: 'GS010-PJ-CUST-THEME-SETTINGS-TYPE'},
@@ -50,9 +51,12 @@ const canaryPackageJSONValidationRules = _.extend({},
50
51
  {invalidCustomThemeSetingBooleanDefault: 'GS010-PJ-CUST-THEME-SETTINGS-BOOLEAN-DEFAULT'},
51
52
  {invalidCustomThemeSetingColorDefault: 'GS010-PJ-CUST-THEME-SETTINGS-COLOR-DEFAULT'},
52
53
  {invalidCustomThemeSetingImageDefault: 'GS010-PJ-CUST-THEME-SETTINGS-IMAGE-DEFAULT'},
53
- canaryPackageJSONConditionalRules
54
+ v4PackageJSONConditionalRules
54
55
  );
55
56
 
57
+ const canaryPackageJSONConditionalRules = {};
58
+ const canaryPackageJSONValidationRules = _.extend({});
59
+
56
60
  _private.validatePackageJSONFields = function validatePackageJSONFields(packageJSON, theme, packageJSONValidationRules) {
57
61
  let failed = [];
58
62
  const passedRulesToOmit = [];
@@ -109,6 +113,10 @@ _private.validatePackageJSONFields = function validatePackageJSONFields(packageJ
109
113
  }
110
114
 
111
115
  if (!packageJSON.engines || !packageJSON.engines['ghost-api']) {
116
+ markFailed('isNotPresentEngineGhostAPI');
117
+ }
118
+
119
+ if (packageJSON.engines && packageJSON.engines['ghost-api']) {
112
120
  markFailed('isPresentEngineGhostAPI');
113
121
  }
114
122
 
@@ -221,15 +229,66 @@ module.exports = function checkPackageJSON(theme, options) {
221
229
  packageJSONValidationRules = v1PackageJSONValidationRules;
222
230
  packageJSONConditionalRules = v1PackageJSONConditionalRules;
223
231
  } else if (checkVersion === 'v2') {
224
- packageJSONValidationRules = _.merge({}, v1PackageJSONValidationRules, v2PackageJSONValidationRules);
225
- packageJSONConditionalRules = _.merge({}, v1PackageJSONConditionalRules, v2PackageJSONConditionalRules);
232
+ packageJSONValidationRules = _.merge(
233
+ {},
234
+ v1PackageJSONValidationRules,
235
+ v2PackageJSONValidationRules
236
+ );
237
+ packageJSONConditionalRules = _.merge(
238
+ {},
239
+ v1PackageJSONConditionalRules,
240
+ v2PackageJSONConditionalRules
241
+ );
226
242
  } else if (checkVersion === 'v3') {
227
- packageJSONValidationRules = _.merge({}, v1PackageJSONValidationRules, v2PackageJSONValidationRules, v3PackageJSONValidationRules);
228
- packageJSONConditionalRules = _.merge({}, v1PackageJSONConditionalRules, v2PackageJSONConditionalRules, v3PackageJSONConditionalRules);
243
+ packageJSONValidationRules = _.merge(
244
+ {},
245
+ v1PackageJSONValidationRules,
246
+ v2PackageJSONValidationRules,
247
+ v3PackageJSONValidationRules
248
+ );
249
+ packageJSONConditionalRules = _.merge(
250
+ {},
251
+ v1PackageJSONConditionalRules,
252
+ v2PackageJSONConditionalRules,
253
+ v3PackageJSONConditionalRules
254
+ );
255
+ } else if (checkVersion === 'v5') {
256
+ packageJSONValidationRules = _.merge(
257
+ {},
258
+ v1PackageJSONValidationRules,
259
+ v2PackageJSONValidationRules,
260
+ v3PackageJSONValidationRules,
261
+ v4PackageJSONValidationRules,
262
+ canaryPackageJSONValidationRules
263
+ );
264
+ delete packageJSONValidationRules.isNotPresentEngineGhostAPI;
265
+
266
+ packageJSONConditionalRules = _.merge(
267
+ {},
268
+ v1PackageJSONConditionalRules,
269
+ v2PackageJSONConditionalRules,
270
+ v3PackageJSONConditionalRules,
271
+ v4PackageJSONConditionalRules,
272
+ canaryPackageJSONConditionalRules
273
+ );
229
274
  } else {
230
- // default check for current version 'v4'/'canary' rules
231
- packageJSONValidationRules = _.merge({}, v1PackageJSONValidationRules, v2PackageJSONValidationRules, v3PackageJSONValidationRules, canaryPackageJSONValidationRules);
232
- packageJSONConditionalRules = _.merge({}, v1PackageJSONConditionalRules, v2PackageJSONConditionalRules, v3PackageJSONConditionalRules, canaryPackageJSONConditionalRules);
275
+ // default check for current version 'v4' rules
276
+ packageJSONValidationRules = _.merge(
277
+ {},
278
+ v1PackageJSONValidationRules,
279
+ v2PackageJSONValidationRules,
280
+ v3PackageJSONValidationRules,
281
+ v4PackageJSONValidationRules
282
+ );
283
+ delete packageJSONValidationRules.isNotPresentEngineGhostAPI;
284
+
285
+ packageJSONConditionalRules = _.merge(
286
+ {},
287
+ v1PackageJSONConditionalRules,
288
+ v2PackageJSONConditionalRules,
289
+ v3PackageJSONConditionalRules,
290
+ v4PackageJSONConditionalRules
291
+ );
233
292
  }
234
293
 
235
294
  let [packageJSON] = _.filter(theme.files, {file: packageJSONFileName});
@@ -49,7 +49,6 @@ function getCustomThemeSettings(theme) {
49
49
  }
50
50
 
51
51
  const checkTemplatesCompile = function checkTemplatesCompile(theme, options) {
52
- const failures = [];
53
52
  const checkVersion = _.get(options, 'checkVersion', versions.default);
54
53
  const ruleSet = spec.get([checkVersion]);
55
54
  const customThemeSettings = getCustomThemeSettings(theme);
@@ -63,6 +62,7 @@ const checkTemplatesCompile = function checkTemplatesCompile(theme, options) {
63
62
  });
64
63
 
65
64
  _.each(rulesToCheck, function (check, ruleCode) {
65
+ const failures = [];
66
66
  const processFile = processFileFunction(
67
67
  theme.files,
68
68
  failures,
@@ -1,291 +1,46 @@
1
1
  const _ = require('lodash');
2
2
  const oneLineTrim = require('common-tags/lib/oneLineTrim');
3
- const previousSpec = require('./v3');
3
+ const previousSpec = require('./v4');
4
4
  const ghostVersions = require('../utils').versions;
5
5
  const docsBaseUrl = `https://ghost.org/docs/api/handlebars-themes/`;
6
- const prevDocsBaseUrl = `https://themes.ghost.org/v${ghostVersions.v3.docs}/docs/`;
6
+ const prevDocsBaseUrl = `https://themes.ghost.org/v${ghostVersions.canary.docs}/docs/`;
7
7
  const prevDocsBaseUrlRegEx = new RegExp(prevDocsBaseUrl, 'g');
8
8
 
9
9
  const previousKnownHelpers = previousSpec.knownHelpers;
10
10
  const previousTemplates = previousSpec.templates;
11
11
  const previousRules = previousSpec.rules;
12
12
 
13
- function cssCardRule(cardName, className) {
14
- return {
15
- level: 'warning',
16
- rule: `The <code>.${className}</code> CSS class is required to appear styled in your theme`,
17
- details: oneLineTrim`The <code>.${className}</code> CSS class is required otherwise the ${cardName} card will appear unstyled.
18
- Find out more about required theme changes for the Koenig editor <a href="${docsBaseUrl}editor/" target=_blank>here</a>.`,
19
- regex: new RegExp(`\\.${className}`, 'g'),
20
- className: `.${className}`,
21
- css: true,
22
- cardAsset: cardName
23
- };
24
- }
25
-
26
13
  // assign new or overwrite existing knownHelpers, templates, or rules here:
27
- let knownHelpers = ['match'];
14
+ let knownHelpers = [];
28
15
  let templates = [];
29
16
  let rules = {
30
17
  // New rules
31
- 'GS010-PJ-GHOST-API': {
32
- level: 'warning',
33
- rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is recommended. Otherwise, it falls back to "v4"',
34
- details: oneLineTrim`Add <code>"ghost-api"</code> to your <code>package.json</code>. E.g. <code>{"engines": {"ghost-api": "v4"}}</code>.<br>
35
- If no <code>"ghost-api"</code> property is provided, Ghost will use its default setting of "v4" Ghost API.<br>
36
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
37
- },
38
- 'GS010-PJ-GHOST-API-V01': {
39
- level: 'error',
40
- rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is incompatible with current version of Ghost API and will fall back to "v4"',
41
- details: oneLineTrim`Change <code>"ghost-api"</code> in your <code>package.json</code> to higher version. E.g. <code>{"engines": {"ghost-api": "v4"}}</code>.<br>
42
- If <code>"ghost-api"</code> property is left at "v0.1", Ghost will use its default setting of "v4".<br>
43
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
44
- },
45
- 'GS010-PJ-GHOST-API-V2': {
46
- level: 'warning',
47
- rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is using a deprecated version of Ghost API',
48
- details: oneLineTrim`Change <code>"ghost-api"</code> in your <code>package.json</code> to higher version. E.g. <code>{"engines": {"ghost-api": "v4"}}</code>.<br>
49
- If <code>"ghost-api"</code> property is left at "v2", it will stop working with next major version upgrade and default to v5.<br>
50
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
51
- },
52
- 'GS010-PJ-CUST-THEME-TOTAL-SETTINGS': {
53
- level: 'error',
54
- rule: '<code>package.json</code> property <code>"config.custom"</code> contains too many settings',
55
- details: oneLineTrim`Remove key from <code>"config.custom"</code> in your <code>package.json</code> to have less than or exactly 15 settings.<br>
56
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
57
- },
58
- 'GS010-PJ-CUST-THEME-SETTINGS-CASE': {
59
- level: 'error',
60
- rule: '<code>package.json</code> property <code>"config.custom"</code> contains a property that isn\'t snake-cased',
61
- details: oneLineTrim`Rewrite all property in <code>"config.custom"</code> in your <code>package.json</code> in snake case.<br>
62
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
63
- },
64
- 'GS010-PJ-CUST-THEME-SETTINGS-TYPE': {
65
- level: 'error',
66
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"type"</code>.',
67
- details: oneLineTrim`Only use the following types: <code>"select"</code>, <code>"boolean"</code>, <code>"color"</code>, <code>"image"</code>, <code>"text"</code>.<br>
68
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
69
- },
70
- 'GS010-PJ-CUST-THEME-SETTINGS-GROUP': {
71
- level: 'recommendation',
72
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"group"</code>.',
73
- details: oneLineTrim`Only use the following groups: <code>"post"</code>, <code>"homepage"</code>.<br>
74
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
75
- },
76
- 'GS010-PJ-CUST-THEME-SETTINGS-SELECT-OPTIONS': {
77
- level: 'error',
78
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"select"</code> need to have at least 2 <code>"options"</code>.',
79
- details: oneLineTrim`Make sure there is at least 2 <code>"options"</code> in each <code>"select"</code> custom theme property.<br>
80
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
81
- },
82
- 'GS010-PJ-CUST-THEME-SETTINGS-SELECT-DEFAULT': {
83
- level: 'error',
84
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"select"</code> need to have a valid <code>"default"</code>.',
85
- details: oneLineTrim`Make sure the <code>"default"</code> property matches a value in <code>"options"</code> of the same <code>"select"</code>.<br>
86
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
87
- },
88
- 'GS010-PJ-CUST-THEME-SETTINGS-BOOLEAN-DEFAULT': {
89
- level: 'error',
90
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"boolean"</code> need to have a valid <code>"default"</code>.',
91
- details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>true</code> or <code>false</code>.<br>
92
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
93
- },
94
- 'GS010-PJ-CUST-THEME-SETTINGS-COLOR-DEFAULT': {
18
+ 'GS010-PJ-GHOST-API-PRESENT': {
95
19
  level: 'error',
96
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"color"</code> need to have a valid <code>"default"</code>.',
97
- details: oneLineTrim`Make sure the <code>"default"</code> property is a valid 6-hexadecimal-digit color code like <code>#15171a</code>.<br>
20
+ rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is not supported.',
21
+ details: oneLineTrim`Remove <code>"ghost-api"</code> from your <code>package.json</code>.<br>
22
+ The <code>ghost-api</code> is not supported starting Ghost v5 and should not be used.
98
23
  Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
99
24
  },
100
- 'GS010-PJ-CUST-THEME-SETTINGS-IMAGE-DEFAULT': {
25
+ 'GS001-DEPR-BLOG': {
101
26
  level: 'error',
102
- rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"image"</code> can\'t have a <code>"default"</code> value.',
103
- details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>null</code>, an empty string <code>''</code> or isn't present.<br>
104
- Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
105
- },
106
- 'GS001-DEPR-LABS-MEMBERS': {
107
- level: 'warning',
108
- rule: 'The <code>{{@labs.members}}</code> helper should not be used.',
109
- details: oneLineTrim`Remove <code>{{@labs.members}}</code> from the theme.<br>
110
- The <code>{{@labs.members}}</code> helper will always return <code>true</code> in Ghost v4 and will be removed from Ghost v5, at which point it will return <code>null</code> and evaluate to <code>false</code>.
111
- Find more information about the <code>@labs</code> property <a href="${docsBaseUrl}helpers/labs/" target=_blank>here</a>.`,
112
- regex: /@labs\.members/g,
113
- helper: '{{@labs.members}}'
114
- },
115
- 'GS080-FEACH-POSTS': {
116
- level: 'warning',
117
- rule: 'The default visibility for posts in <code>{{#foreach}}</code> block helper has changed in v4.',
118
- details: oneLineTrim`The default visibility for posts in <code>{{#foreach}}</code> block helper has changed from <code>public</code> to <code>all</code> in Ghost v4.
119
- Find more information about the <code>{{foreach}}</code> helper <a href="${docsBaseUrl}helpers/foreach/" target=_blank>here</a>.`,
120
- regex: /{{\s*?#foreach\s*?\w*?\s*?}}/g,
121
- helper: '{{#foreach}}',
122
- validInAPI: ['v3']
123
- },
124
- 'GS080-CARD-LAST4': {
125
- level: 'warning',
126
- rule: 'The <code>default_payment_card_last4</code> field now coalesces to <code>****</code> in Ghost 4.x instead of null.',
127
- details: oneLineTrim`The <code>default_payment_card_last4</code> field no longer outputs a falsy(null) value in case of missing card details starting from Ghost 4.x and instead coalesces to <code>****</code>
128
- Find more information about the <code>default_payment_card_last4</code> attribute <a href="${docsBaseUrl}members/#subscription-attributes" target=_blank>here</a>.`,
129
- regex: /default_payment_card_last4/g,
130
- helper: '{{default_payment_card_last4}}',
131
- validInAPI: ['v3']
132
- },
133
- 'GS080-FEACH-PV': {
134
- level: 'recommendation',
135
- rule: 'The use of <code>visibility="all"</code> is no longer required for posts in <code>{{#foreach}}</code> helper.',
136
- details: oneLineTrim`The default visibility in <code>{{#foreach}}</code> helper for posts has changed in v4 from "public" to "all" and is no longer required when looping over posts.
137
- Check out the documentation for <code>{{#foreach}}</code> <a href="${docsBaseUrl}helpers/foreach/" target=_blank>here</a>.`,
138
- regex: /{{\s*?#foreach\b[\w\s='"]*?visibility=("|')all("|')[\w\s='"]*?}}/g,
139
- helper: '{{#foreach}}',
140
- validInAPI: ['v3']
141
- },
142
- 'GS001-DEPR-CURR-SYM': {
143
- level: 'warning',
144
- rule: 'Replace <code>{{[#].currency_symbol}}</code> with <code>{{price currency=currency}}</code>.',
145
- details: oneLineTrim`The hardcoded <code>currency_symbol</code> attribute was removed in favour of passing the currency to updated <code>{{price}}</code> helper.
146
- Find more information about the updated <code>{{price}}</code> helper <a href="${docsBaseUrl}members/#the-price-helper" target=_blank>here</a>.`,
147
- helper: '{{[#].currency_symbol}}',
148
- regex: /currency_symbol/g
149
- },
150
- 'GS001-DEPR-SITE-LANG': {
151
- level: 'warning',
152
- rule: 'The <code>{{@site.lang}}</code> helper should be replaced with <code>{{@site.locale}}</code>',
153
- details: oneLineTrim`Replace <code>{{@site.lang}}</code> helper with <code>{{@site.locale}}</code>.<br>
154
- The <code>{{@site.lang}}</code> helper will be removed in next version of Ghost and should not be used.
27
+ fatal: true,
28
+ rule: 'The <code>{{@blog}}</code> helper should be replaced with <code>{{@site}}</code>',
29
+ details: oneLineTrim`With the introduction of the Content API <code>{{@blog}}</code> became deprecated in favour of <code>{{@site}}</code>.<br>
30
+ The <code>{{@blog}}</code> helper was removed in Ghost v5 and should not be used.
155
31
  Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
156
- regex: /@site\.lang/g,
157
- helper: '{{@site.lang}}'
158
- },
159
- 'GS070-VALID-TRANSLATIONS': {
160
- level: 'error',
161
- rule: 'Theme translations must be parsable',
162
- fatal: true, // overwritten from v3 to be fatal
163
- details: oneLineTrim`Theme translations (located in <code>locales/*.json</code>) need to be readable by the node JSON parser, or they will not be applied.`
164
- },
165
- 'GS090-NO-IMG-URL-IN-CONDITIONALS': {
166
- level: 'warning',
167
- rule: 'The {{img_url}} helper should not be used as a parameter to {{#if}} or {{#unless}}',
168
- fatal: false,
169
- details: oneLineTrim`The {{img_url}} helper should not be used as a parameter to {{#if}} or {{#unless}}`
32
+ regex: /{{\s*?@blog\.[a-zA-Z0-9_]+\s*?}}/g,
33
+ helper: '{{@blog}}'
170
34
  },
171
- 'GS090-NO-UNKNOWN-CUSTOM-THEME-SETTINGS': {
35
+ 'GS090-NO-AUTHOR-HELPER-IN-POST-CONTEXT': {
172
36
  level: 'error',
173
- rule: 'An unkown custom theme setting has been used.',
174
- fatal: false,
175
- details: oneLineTrim`The custom theme setting should all be defined in the package.json <code>config.custom</code> object.`
176
- },
177
- 'GS090-NO-UNKNOWN-CUSTOM-THEME-SELECT-VALUE-IN-MATCH': {
178
- level: 'error',
179
- rule: 'A custom theme setting of type <code>select</code> has been compared to a value that isn\'t defined.',
180
- fatal: false,
181
- details: oneLineTrim`Custom theme settings of type <code>select</code> can only be compared to their defined <code>options</code> when used in a <code>match</code> block.`
182
- },
183
- 'GS100-NO-UNUSED-CUSTOM-THEME-SETTING': {
184
- level: 'error',
185
- rule: 'A custom theme setting defined in <code>package.json</code> hasn\'t been used in any theme file.',
186
- details: oneLineTrim`Custom theme settings defined in <code>package.json</code> must be used at least once in the theme templates.`
187
- },
188
-
189
- 'GS050-CSS-KGCO': cssCardRule('callout', 'kg-callout-card'),
190
- 'GS050-CSS-KGCOE': cssCardRule('callout', 'kg-callout-card-emoji'),
191
- 'GS050-CSS-KGCOT': cssCardRule('callout', 'kg-callout-card-text'),
192
- 'GS050-CSS-KGCOBGGY': cssCardRule('callout', 'kg-callout-card-background-grey'),
193
- 'GS050-CSS-KGCOBGW': cssCardRule('callout', 'kg-callout-card-background-white'),
194
- 'GS050-CSS-KGCOBGB': cssCardRule('callout', 'kg-callout-card-background-blue'),
195
- 'GS050-CSS-KGCOBGGN': cssCardRule('callout', 'kg-callout-card-background-green'),
196
- 'GS050-CSS-KGCOBGY': cssCardRule('callout', 'kg-callout-card-background-yellow'),
197
- 'GS050-CSS-KGCOBGR': cssCardRule('callout', 'kg-callout-card-background-red'),
198
- 'GS050-CSS-KGCOBGPK': cssCardRule('callout', 'kg-callout-card-background-pink'),
199
- 'GS050-CSS-KGCOBGPE': cssCardRule('callout', 'kg-callout-card-background-purple'),
200
- 'GS050-CSS-KGCOBGA': cssCardRule('callout', 'kg-callout-card-background-accent'),
201
-
202
- 'GS050-CSS-KG-NFT': cssCardRule('nft', 'kg-nft-card'),
203
- 'GS050-CSS-KG-NFTCO': cssCardRule('nft', 'kg-nft-card-container'),
204
- 'GS050-CSS-KG-NFTMD': cssCardRule('nft', 'kg-nft-metadata'),
205
- 'GS050-CSS-KG-NFTIMG': cssCardRule('nft', 'kg-nft-image'),
206
- 'GS050-CSS-KG-NFTHD': cssCardRule('nft', 'kg-nft-header'),
207
- 'GS050-CSS-KG-NFTTIT': cssCardRule('nft', 'kg-nft-title'),
208
- 'GS050-CSS-KG-NFTLG': cssCardRule('nft', 'kg-nft-logo'),
209
- 'GS050-CSS-KG-NFTCTR': cssCardRule('nft', 'kg-nft-creator'),
210
- 'GS050-CSS-KG-NFTDSC': cssCardRule('nft', 'kg-nft-description'),
211
-
212
- 'GS050-CSS-KGTGL': cssCardRule('toggle', 'kg-toggle-card'),
213
- 'GS050-CSS-KGTGLH': cssCardRule('toggle', 'kg-toggle-heading'),
214
- 'GS050-CSS-KGTGLHT': cssCardRule('toggle', 'kg-toggle-heading-text'),
215
- 'GS050-CSS-KGTGLIC': cssCardRule('toggle', 'kg-toggle-card-icon'),
216
- 'GS050-CSS-KGTGLC': cssCardRule('toggle', 'kg-toggle-content'),
217
-
218
- 'GS050-CSS-KGAUD': cssCardRule('audio', 'kg-audio-card'),
219
- 'GS050-CSS-KGAUDTHUMB': cssCardRule('audio', 'kg-audio-thumbnail'),
220
- 'GS050-CSS-KGAUDTHUMBPL': cssCardRule('audio', 'kg-audio-thumbnail.placeholder'),
221
- 'GS050-CSS-KGAUDPLCNT': cssCardRule('audio', 'kg-audio-player-container'),
222
- 'GS050-CSS-KGAUDTI': cssCardRule('audio', 'kg-audio-title'),
223
- 'GS050-CSS-KGAUDPL': cssCardRule('audio', 'kg-audio-player'),
224
- 'GS050-CSS-KGAUDCURRTM': cssCardRule('audio', 'kg-audio-current-time'),
225
- 'GS050-CSS-KGAUDTM': cssCardRule('audio', 'kg-audio-time'),
226
- 'GS050-CSS-KGAUDDUR': cssCardRule('audio', 'kg-audio-duration'),
227
- 'GS050-CSS-KGAUDPLICO': cssCardRule('audio', 'kg-audio-play-icon'),
228
- 'GS050-CSS-KGAUDPAUICO': cssCardRule('audio', 'kg-audio-pause-icon'),
229
- 'GS050-CSS-KGAUDSKSL': cssCardRule('audio', 'kg-audio-seek-slider'),
230
- 'GS050-CSS-KGAUDPLRT': cssCardRule('audio', 'kg-audio-playback-rate'),
231
- 'GS050-CSS-KGAUDMTICO': cssCardRule('audio', 'kg-audio-mute-icon'),
232
- 'GS050-CSS-KGAUDUNMTICO': cssCardRule('audio', 'kg-audio-unmute-icon'),
233
- 'GS050-CSS-KGAUDVOLSL': cssCardRule('audio', 'kg-audio-volume-slider'),
234
-
235
- 'GS050-CSS-KGVID': cssCardRule('video', 'kg-video-card'),
236
- 'GS050-CSS-KGVIDHD': cssCardRule('video', 'kg-video-hide'),
237
- 'GS050-CSS-KGVIDCNT': cssCardRule('video', 'kg-video-container'),
238
- 'GS050-CSS-KGVIDOVL': cssCardRule('video', 'kg-video-overlay'),
239
- 'GS050-CSS-KGVIDLGPLICO': cssCardRule('video', 'kg-video-large-play-icon'),
240
- 'GS050-CSS-KGVIDTHUMB': cssCardRule('video', 'kg-video-thumbnail'),
241
- 'GS050-CSS-KGVIDTHUMBPL': cssCardRule('video', 'kg-video-thumbnail.placeholder'),
242
- 'GS050-CSS-KGVIDPLCNT': cssCardRule('video', 'kg-video-player-container'),
243
- 'GS050-CSS-KGVIDTI': cssCardRule('video', 'kg-video-title'),
244
- 'GS050-CSS-KGVIDPL': cssCardRule('video', 'kg-video-player'),
245
- 'GS050-CSS-KGVIDCURRTM': cssCardRule('video', 'kg-video-current-time'),
246
- 'GS050-CSS-KGVIDTM': cssCardRule('video', 'kg-video-time'),
247
- 'GS050-CSS-KGVIDDUR': cssCardRule('video', 'kg-video-duration'),
248
- 'GS050-CSS-KGVIDPLICO': cssCardRule('video', 'kg-video-play-icon'),
249
- 'GS050-CSS-KGVIDPAUICO': cssCardRule('video', 'kg-video-pause-icon'),
250
- 'GS050-CSS-KGVIDSKSL': cssCardRule('video', 'kg-video-seek-slider'),
251
- 'GS050-CSS-KGVIDPLRT': cssCardRule('video', 'kg-video-playback-rate'),
252
- 'GS050-CSS-KGVIDMTICO': cssCardRule('video', 'kg-video-mute-icon'),
253
- 'GS050-CSS-KGVIDUNMTICO': cssCardRule('video', 'kg-video-unmute-icon'),
254
- 'GS050-CSS-KGVIDVOLSL': cssCardRule('video', 'kg-video-volume-slider'),
255
-
256
- 'GS050-CSS-KGBTN': cssCardRule('button', 'kg-button-card'),
257
- 'GS050-CSS-KGBTNL': cssCardRule('button', 'kg-button-card.kg-align-left'),
258
- 'GS050-CSS-KGBTNC': cssCardRule('button', 'kg-button-card.kg-align-center'),
259
- 'GS050-CSS-KGBTNBTN': cssCardRule('button', 'kg-btn'),
260
- 'GS050-CSS-KGBTNBTNA': cssCardRule('button', 'kg-btn-accent'),
261
-
262
- 'GS050-CSS-KGPR': cssCardRule('product', 'kg-product-card'),
263
- 'GS050-CSS-KGPRBTNA': cssCardRule('product', 'kg-product-card-btn-accent'),
264
- 'GS050-CSS-KGPRBTN': cssCardRule('product', 'kg-product-card-button'),
265
- 'GS050-CSS-KGPRCO': cssCardRule('product', 'kg-product-card-container'),
266
- 'GS050-CSS-KGPRDE': cssCardRule('product', 'kg-product-card-description'),
267
- 'GS050-CSS-KGPRIM': cssCardRule('product', 'kg-product-card-image'),
268
- 'GS050-CSS-KGPRRA': cssCardRule('product', 'kg-product-card-rating'),
269
- 'GS050-CSS-KGPRRAA': cssCardRule('product', 'kg-product-card-rating-active'),
270
- 'GS050-CSS-KGPRRAS': cssCardRule('product', 'kg-product-card-rating-star'),
271
- 'GS050-CSS-KGPRTI': cssCardRule('product', 'kg-product-card-title'),
272
- 'GS050-CSS-KGPRTICO': cssCardRule('product', 'kg-product-card-title-container'),
273
-
274
- 'GS050-CSS-KGBA': cssCardRule('before-after', 'kg-before-after-card'),
275
- 'GS050-CSS-KGBAIA': cssCardRule('before-after', 'kg-before-after-card-image-before'),
276
- 'GS050-CSS-KGBAIB': cssCardRule('before-after', 'kg-before-after-card-image-after'),
277
-
278
- 'GS050-CSS-KGFL': cssCardRule('file', 'kg-file-card'),
279
- 'GS050-CSS-KGFLCON': cssCardRule('file', 'kg-file-card-container'),
280
- 'GS050-CSS-KGFLCNT': cssCardRule('file', 'kg-file-card-contents'),
281
- 'GS050-CSS-KGFLTTL': cssCardRule('file', 'kg-file-card-title'),
282
- 'GS050-CSS-KGFLCAP': cssCardRule('file', 'kg-file-card-caption'),
283
- 'GS050-CSS-KGFLNM': cssCardRule('file', 'kg-file-card-filename'),
284
- 'GS050-CSS-KGFLSZ': cssCardRule('file', 'kg-file-card-filesize'),
285
- 'GS050-CSS-KGFLMD': cssCardRule('file', 'kg-file-card-medium'),
286
- 'GS050-CSS-KGFLSM': cssCardRule('file', 'kg-file-card-small'),
287
-
288
- 'GS050-CSS-KGBQALT': cssCardRule('blockquote', 'kg-blockquote-alt')
37
+ fatal: true,
38
+ rule: 'The <code>{{author}}</code> helper should be replaces with <code>{{authors}}</code>',
39
+ details: oneLineTrim`The <code>{{author}}</code> helper has been deprecated since Ghost 1.22.0 in favor of <code>{{<authors>}}</code><br>
40
+ The <code>{{author}}</code> helper was removed in Ghost v5 and should not be used.
41
+ Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
42
+ helper: '{{author}}'
43
+ }
289
44
  };
290
45
 
291
46
  knownHelpers = _.union(previousKnownHelpers, knownHelpers);
@@ -4,7 +4,7 @@ module.exports = {
4
4
  get: function get(key) {
5
5
  let [version] = key;
6
6
 
7
- if (version === 'v4') {
7
+ if (version === 'v5') {
8
8
  version = 'canary';
9
9
  }
10
10
 
@@ -0,0 +1,306 @@
1
+ const _ = require('lodash');
2
+ const oneLineTrim = require('common-tags/lib/oneLineTrim');
3
+ const previousSpec = require('./v3');
4
+ const ghostVersions = require('../utils').versions;
5
+ const docsBaseUrl = `https://ghost.org/docs/api/handlebars-themes/`;
6
+ const prevDocsBaseUrl = `https://themes.ghost.org/v${ghostVersions.v3.docs}/docs/`;
7
+ const prevDocsBaseUrlRegEx = new RegExp(prevDocsBaseUrl, 'g');
8
+
9
+ const previousKnownHelpers = previousSpec.knownHelpers;
10
+ const previousTemplates = previousSpec.templates;
11
+ const previousRules = previousSpec.rules;
12
+
13
+ function cssCardRule(cardName, className) {
14
+ return {
15
+ level: 'warning',
16
+ rule: `The <code>.${className}</code> CSS class is required to appear styled in your theme`,
17
+ details: oneLineTrim`The <code>.${className}</code> CSS class is required otherwise the ${cardName} card will appear unstyled.
18
+ Find out more about required theme changes for the Koenig editor <a href="${docsBaseUrl}editor/" target=_blank>here</a>.`,
19
+ regex: new RegExp(`\\.${className}`, 'g'),
20
+ className: `.${className}`,
21
+ css: true,
22
+ cardAsset: cardName
23
+ };
24
+ }
25
+
26
+ // assign new or overwrite existing knownHelpers, templates, or rules here:
27
+ let knownHelpers = ['match'];
28
+ let templates = [];
29
+ let rules = {
30
+ // New rules
31
+ 'GS010-PJ-GHOST-API-PRESENT': {
32
+ level: 'warning',
33
+ rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is deprecated.',
34
+ details: oneLineTrim`Remove <code>"ghost-api"</code> from your <code>package.json</code>.<br>
35
+ The <code>ghost-api</code> support will be removed in next major version of Ghost and should not be used.
36
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
37
+ },
38
+ 'GS010-PJ-GHOST-API-V01': {
39
+ level: 'error',
40
+ rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is incompatible with current version of Ghost API and will fall back to "v4"',
41
+ details: oneLineTrim`Change <code>"ghost-api"</code> in your <code>package.json</code> to higher version. E.g. <code>{"engines": {"ghost-api": "v4"}}</code>.<br>
42
+ If <code>"ghost-api"</code> property is left at "v0.1", Ghost will use its default setting of "v4".<br>
43
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
44
+ },
45
+ 'GS010-PJ-GHOST-API-V2': {
46
+ level: 'warning',
47
+ rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is using a deprecated version of Ghost API',
48
+ details: oneLineTrim`Change <code>"ghost-api"</code> in your <code>package.json</code> to higher version. E.g. <code>{"engines": {"ghost-api": "v4"}}</code>.<br>
49
+ If <code>"ghost-api"</code> property is left at "v2", it will stop working with next major version upgrade and default to v5.<br>
50
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
51
+ },
52
+ 'GS010-PJ-CUST-THEME-TOTAL-SETTINGS': {
53
+ level: 'error',
54
+ rule: '<code>package.json</code> property <code>"config.custom"</code> contains too many settings',
55
+ details: oneLineTrim`Remove key from <code>"config.custom"</code> in your <code>package.json</code> to have less than or exactly 15 settings.<br>
56
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
57
+ },
58
+ 'GS010-PJ-CUST-THEME-SETTINGS-CASE': {
59
+ level: 'error',
60
+ rule: '<code>package.json</code> property <code>"config.custom"</code> contains a property that isn\'t snake-cased',
61
+ details: oneLineTrim`Rewrite all property in <code>"config.custom"</code> in your <code>package.json</code> in snake case.<br>
62
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
63
+ },
64
+ 'GS010-PJ-CUST-THEME-SETTINGS-TYPE': {
65
+ level: 'error',
66
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"type"</code>.',
67
+ details: oneLineTrim`Only use the following types: <code>"select"</code>, <code>"boolean"</code>, <code>"color"</code>, <code>"image"</code>, <code>"text"</code>.<br>
68
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
69
+ },
70
+ 'GS010-PJ-CUST-THEME-SETTINGS-GROUP': {
71
+ level: 'recommendation',
72
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"group"</code>.',
73
+ details: oneLineTrim`Only use the following groups: <code>"post"</code>, <code>"homepage"</code>.<br>
74
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
75
+ },
76
+ 'GS010-PJ-CUST-THEME-SETTINGS-SELECT-OPTIONS': {
77
+ level: 'error',
78
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"select"</code> need to have at least 2 <code>"options"</code>.',
79
+ details: oneLineTrim`Make sure there is at least 2 <code>"options"</code> in each <code>"select"</code> custom theme property.<br>
80
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
81
+ },
82
+ 'GS010-PJ-CUST-THEME-SETTINGS-SELECT-DEFAULT': {
83
+ level: 'error',
84
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"select"</code> need to have a valid <code>"default"</code>.',
85
+ details: oneLineTrim`Make sure the <code>"default"</code> property matches a value in <code>"options"</code> of the same <code>"select"</code>.<br>
86
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
87
+ },
88
+ 'GS010-PJ-CUST-THEME-SETTINGS-BOOLEAN-DEFAULT': {
89
+ level: 'error',
90
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"boolean"</code> need to have a valid <code>"default"</code>.',
91
+ details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>true</code> or <code>false</code>.<br>
92
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
93
+ },
94
+ 'GS010-PJ-CUST-THEME-SETTINGS-COLOR-DEFAULT': {
95
+ level: 'error',
96
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"color"</code> need to have a valid <code>"default"</code>.',
97
+ details: oneLineTrim`Make sure the <code>"default"</code> property is a valid 6-hexadecimal-digit color code like <code>#15171a</code>.<br>
98
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
99
+ },
100
+ 'GS010-PJ-CUST-THEME-SETTINGS-IMAGE-DEFAULT': {
101
+ level: 'error',
102
+ rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> of type <code>"image"</code> can\'t have a <code>"default"</code> value.',
103
+ details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>null</code>, an empty string <code>''</code> or isn't present.<br>
104
+ Check the <a href="${docsBaseUrl}packagejson/" target=_blank><code>package.json</code> documentation</a> for further information.`
105
+ },
106
+ 'GS001-DEPR-LABS-MEMBERS': {
107
+ level: 'warning',
108
+ rule: 'The <code>{{@labs.members}}</code> helper should not be used.',
109
+ details: oneLineTrim`Remove <code>{{@labs.members}}</code> from the theme.<br>
110
+ The <code>{{@labs.members}}</code> helper will always return <code>true</code> in Ghost v4 and will be removed from Ghost v5, at which point it will return <code>null</code> and evaluate to <code>false</code>.
111
+ Find more information about the <code>@labs</code> property <a href="${docsBaseUrl}helpers/labs/" target=_blank>here</a>.`,
112
+ regex: /@labs\.members/g,
113
+ helper: '{{@labs.members}}'
114
+ },
115
+ 'GS080-FEACH-POSTS': {
116
+ level: 'warning',
117
+ rule: 'The default visibility for posts in <code>{{#foreach}}</code> block helper has changed in v4.',
118
+ details: oneLineTrim`The default visibility for posts in <code>{{#foreach}}</code> block helper has changed from <code>public</code> to <code>all</code> in Ghost v4.
119
+ Find more information about the <code>{{foreach}}</code> helper <a href="${docsBaseUrl}helpers/foreach/" target=_blank>here</a>.`,
120
+ regex: /{{\s*?#foreach\s*?\w*?\s*?}}/g,
121
+ helper: '{{#foreach}}',
122
+ validInAPI: ['v3']
123
+ },
124
+ 'GS080-CARD-LAST4': {
125
+ level: 'warning',
126
+ rule: 'The <code>default_payment_card_last4</code> field now coalesces to <code>****</code> in Ghost 4.x instead of null.',
127
+ details: oneLineTrim`The <code>default_payment_card_last4</code> field no longer outputs a falsy(null) value in case of missing card details starting from Ghost 4.x and instead coalesces to <code>****</code>
128
+ Find more information about the <code>default_payment_card_last4</code> attribute <a href="${docsBaseUrl}members/#subscription-attributes" target=_blank>here</a>.`,
129
+ regex: /default_payment_card_last4/g,
130
+ helper: '{{default_payment_card_last4}}',
131
+ validInAPI: ['v3']
132
+ },
133
+ 'GS080-FEACH-PV': {
134
+ level: 'recommendation',
135
+ rule: 'The use of <code>visibility="all"</code> is no longer required for posts in <code>{{#foreach}}</code> helper.',
136
+ details: oneLineTrim`The default visibility in <code>{{#foreach}}</code> helper for posts has changed in v4 from "public" to "all" and is no longer required when looping over posts.
137
+ Check out the documentation for <code>{{#foreach}}</code> <a href="${docsBaseUrl}helpers/foreach/" target=_blank>here</a>.`,
138
+ regex: /{{\s*?#foreach\b[\w\s='"]*?visibility=("|')all("|')[\w\s='"]*?}}/g,
139
+ helper: '{{#foreach}}',
140
+ validInAPI: ['v3']
141
+ },
142
+ 'GS001-DEPR-CURR-SYM': {
143
+ level: 'warning',
144
+ rule: 'Replace <code>{{[#].currency_symbol}}</code> with <code>{{price currency=currency}}</code>.',
145
+ details: oneLineTrim`The hardcoded <code>currency_symbol</code> attribute was removed in favour of passing the currency to updated <code>{{price}}</code> helper.
146
+ Find more information about the updated <code>{{price}}</code> helper <a href="${docsBaseUrl}members/#the-price-helper" target=_blank>here</a>.`,
147
+ helper: '{{[#].currency_symbol}}',
148
+ regex: /currency_symbol/g
149
+ },
150
+ 'GS001-DEPR-SITE-LANG': {
151
+ level: 'warning',
152
+ rule: 'The <code>{{@site.lang}}</code> helper should be replaced with <code>{{@site.locale}}</code>',
153
+ details: oneLineTrim`Replace <code>{{@site.lang}}</code> helper with <code>{{@site.locale}}</code>.<br>
154
+ The <code>{{@site.lang}}</code> helper will be removed in next version of Ghost and should not be used.
155
+ Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
156
+ regex: /@site\.lang/g,
157
+ helper: '{{@site.lang}}'
158
+ },
159
+ 'GS070-VALID-TRANSLATIONS': {
160
+ level: 'error',
161
+ rule: 'Theme translations must be parsable',
162
+ fatal: true, // overwritten from v3 to be fatal
163
+ details: oneLineTrim`Theme translations (located in <code>locales/*.json</code>) need to be readable by the node JSON parser, or they will not be applied.`
164
+ },
165
+ 'GS090-NO-IMG-URL-IN-CONDITIONALS': {
166
+ level: 'warning',
167
+ rule: 'The {{img_url}} helper should not be used as a parameter to {{#if}} or {{#unless}}',
168
+ fatal: false,
169
+ details: oneLineTrim`The {{img_url}} helper should not be used as a parameter to {{#if}} or {{#unless}}`
170
+ },
171
+ 'GS090-NO-UNKNOWN-CUSTOM-THEME-SETTINGS': {
172
+ level: 'error',
173
+ rule: 'An unkown custom theme setting has been used.',
174
+ fatal: false,
175
+ details: oneLineTrim`The custom theme setting should all be defined in the package.json <code>config.custom</code> object.`
176
+ },
177
+ 'GS090-NO-UNKNOWN-CUSTOM-THEME-SELECT-VALUE-IN-MATCH': {
178
+ level: 'error',
179
+ rule: 'A custom theme setting of type <code>select</code> has been compared to a value that isn\'t defined.',
180
+ fatal: false,
181
+ details: oneLineTrim`Custom theme settings of type <code>select</code> can only be compared to their defined <code>options</code> when used in a <code>match</code> block.`
182
+ },
183
+ 'GS100-NO-UNUSED-CUSTOM-THEME-SETTING': {
184
+ level: 'error',
185
+ rule: 'A custom theme setting defined in <code>package.json</code> hasn\'t been used in any theme file.',
186
+ details: oneLineTrim`Custom theme settings defined in <code>package.json</code> must be used at least once in the theme templates.`
187
+ },
188
+
189
+ 'GS050-CSS-KGCO': cssCardRule('callout', 'kg-callout-card'),
190
+ 'GS050-CSS-KGCOE': cssCardRule('callout', 'kg-callout-card-emoji'),
191
+ 'GS050-CSS-KGCOT': cssCardRule('callout', 'kg-callout-card-text'),
192
+ 'GS050-CSS-KGCOBGGY': cssCardRule('callout', 'kg-callout-card-background-grey'),
193
+ 'GS050-CSS-KGCOBGW': cssCardRule('callout', 'kg-callout-card-background-white'),
194
+ 'GS050-CSS-KGCOBGB': cssCardRule('callout', 'kg-callout-card-background-blue'),
195
+ 'GS050-CSS-KGCOBGGN': cssCardRule('callout', 'kg-callout-card-background-green'),
196
+ 'GS050-CSS-KGCOBGY': cssCardRule('callout', 'kg-callout-card-background-yellow'),
197
+ 'GS050-CSS-KGCOBGR': cssCardRule('callout', 'kg-callout-card-background-red'),
198
+ 'GS050-CSS-KGCOBGPK': cssCardRule('callout', 'kg-callout-card-background-pink'),
199
+ 'GS050-CSS-KGCOBGPE': cssCardRule('callout', 'kg-callout-card-background-purple'),
200
+ 'GS050-CSS-KGCOBGA': cssCardRule('callout', 'kg-callout-card-background-accent'),
201
+
202
+ 'GS050-CSS-KG-NFT': cssCardRule('nft', 'kg-nft-card'),
203
+ 'GS050-CSS-KG-NFTCO': cssCardRule('nft', 'kg-nft-card-container'),
204
+ 'GS050-CSS-KG-NFTMD': cssCardRule('nft', 'kg-nft-metadata'),
205
+ 'GS050-CSS-KG-NFTIMG': cssCardRule('nft', 'kg-nft-image'),
206
+ 'GS050-CSS-KG-NFTHD': cssCardRule('nft', 'kg-nft-header'),
207
+ 'GS050-CSS-KG-NFTTIT': cssCardRule('nft', 'kg-nft-title'),
208
+ 'GS050-CSS-KG-NFTLG': cssCardRule('nft', 'kg-nft-logo'),
209
+ 'GS050-CSS-KG-NFTCTR': cssCardRule('nft', 'kg-nft-creator'),
210
+ 'GS050-CSS-KG-NFTDSC': cssCardRule('nft', 'kg-nft-description'),
211
+
212
+ 'GS050-CSS-KGTGL': cssCardRule('toggle', 'kg-toggle-card'),
213
+ 'GS050-CSS-KGTGLH': cssCardRule('toggle', 'kg-toggle-heading'),
214
+ 'GS050-CSS-KGTGLHT': cssCardRule('toggle', 'kg-toggle-heading-text'),
215
+ 'GS050-CSS-KGTGLIC': cssCardRule('toggle', 'kg-toggle-card-icon'),
216
+ 'GS050-CSS-KGTGLC': cssCardRule('toggle', 'kg-toggle-content'),
217
+
218
+ 'GS050-CSS-KGAUD': cssCardRule('audio', 'kg-audio-card'),
219
+ 'GS050-CSS-KGAUDTHUMB': cssCardRule('audio', 'kg-audio-thumbnail'),
220
+ 'GS050-CSS-KGAUDTHUMBPL': cssCardRule('audio', 'kg-audio-thumbnail.placeholder'),
221
+ 'GS050-CSS-KGAUDPLCNT': cssCardRule('audio', 'kg-audio-player-container'),
222
+ 'GS050-CSS-KGAUDTI': cssCardRule('audio', 'kg-audio-title'),
223
+ 'GS050-CSS-KGAUDPL': cssCardRule('audio', 'kg-audio-player'),
224
+ 'GS050-CSS-KGAUDCURRTM': cssCardRule('audio', 'kg-audio-current-time'),
225
+ 'GS050-CSS-KGAUDTM': cssCardRule('audio', 'kg-audio-time'),
226
+ 'GS050-CSS-KGAUDDUR': cssCardRule('audio', 'kg-audio-duration'),
227
+ 'GS050-CSS-KGAUDPLICO': cssCardRule('audio', 'kg-audio-play-icon'),
228
+ 'GS050-CSS-KGAUDPAUICO': cssCardRule('audio', 'kg-audio-pause-icon'),
229
+ 'GS050-CSS-KGAUDSKSL': cssCardRule('audio', 'kg-audio-seek-slider'),
230
+ 'GS050-CSS-KGAUDPLRT': cssCardRule('audio', 'kg-audio-playback-rate'),
231
+ 'GS050-CSS-KGAUDMTICO': cssCardRule('audio', 'kg-audio-mute-icon'),
232
+ 'GS050-CSS-KGAUDUNMTICO': cssCardRule('audio', 'kg-audio-unmute-icon'),
233
+ 'GS050-CSS-KGAUDVOLSL': cssCardRule('audio', 'kg-audio-volume-slider'),
234
+
235
+ 'GS050-CSS-KGVID': cssCardRule('video', 'kg-video-card'),
236
+ 'GS050-CSS-KGVIDHD': cssCardRule('video', 'kg-video-hide'),
237
+ 'GS050-CSS-KGVIDCNT': cssCardRule('video', 'kg-video-container'),
238
+ 'GS050-CSS-KGVIDOVL': cssCardRule('video', 'kg-video-overlay'),
239
+ 'GS050-CSS-KGVIDLGPLICO': cssCardRule('video', 'kg-video-large-play-icon'),
240
+ 'GS050-CSS-KGVIDTHUMB': cssCardRule('video', 'kg-video-thumbnail'),
241
+ 'GS050-CSS-KGVIDTHUMBPL': cssCardRule('video', 'kg-video-thumbnail.placeholder'),
242
+ 'GS050-CSS-KGVIDPLCNT': cssCardRule('video', 'kg-video-player-container'),
243
+ 'GS050-CSS-KGVIDTI': cssCardRule('video', 'kg-video-title'),
244
+ 'GS050-CSS-KGVIDPL': cssCardRule('video', 'kg-video-player'),
245
+ 'GS050-CSS-KGVIDCURRTM': cssCardRule('video', 'kg-video-current-time'),
246
+ 'GS050-CSS-KGVIDTM': cssCardRule('video', 'kg-video-time'),
247
+ 'GS050-CSS-KGVIDDUR': cssCardRule('video', 'kg-video-duration'),
248
+ 'GS050-CSS-KGVIDPLICO': cssCardRule('video', 'kg-video-play-icon'),
249
+ 'GS050-CSS-KGVIDPAUICO': cssCardRule('video', 'kg-video-pause-icon'),
250
+ 'GS050-CSS-KGVIDSKSL': cssCardRule('video', 'kg-video-seek-slider'),
251
+ 'GS050-CSS-KGVIDPLRT': cssCardRule('video', 'kg-video-playback-rate'),
252
+ 'GS050-CSS-KGVIDMTICO': cssCardRule('video', 'kg-video-mute-icon'),
253
+ 'GS050-CSS-KGVIDUNMTICO': cssCardRule('video', 'kg-video-unmute-icon'),
254
+ 'GS050-CSS-KGVIDVOLSL': cssCardRule('video', 'kg-video-volume-slider'),
255
+
256
+ 'GS050-CSS-KGBTN': cssCardRule('button', 'kg-button-card'),
257
+ 'GS050-CSS-KGBTNL': cssCardRule('button', 'kg-button-card.kg-align-left'),
258
+ 'GS050-CSS-KGBTNC': cssCardRule('button', 'kg-button-card.kg-align-center'),
259
+ 'GS050-CSS-KGBTNBTN': cssCardRule('button', 'kg-btn'),
260
+ 'GS050-CSS-KGBTNBTNA': cssCardRule('button', 'kg-btn-accent'),
261
+
262
+ 'GS050-CSS-KGPR': cssCardRule('product', 'kg-product-card'),
263
+ 'GS050-CSS-KGPRBTNA': cssCardRule('product', 'kg-product-card-btn-accent'),
264
+ 'GS050-CSS-KGPRBTN': cssCardRule('product', 'kg-product-card-button'),
265
+ 'GS050-CSS-KGPRCO': cssCardRule('product', 'kg-product-card-container'),
266
+ 'GS050-CSS-KGPRDE': cssCardRule('product', 'kg-product-card-description'),
267
+ 'GS050-CSS-KGPRIM': cssCardRule('product', 'kg-product-card-image'),
268
+ 'GS050-CSS-KGPRRA': cssCardRule('product', 'kg-product-card-rating'),
269
+ 'GS050-CSS-KGPRRAA': cssCardRule('product', 'kg-product-card-rating-active'),
270
+ 'GS050-CSS-KGPRRAS': cssCardRule('product', 'kg-product-card-rating-star'),
271
+ 'GS050-CSS-KGPRTI': cssCardRule('product', 'kg-product-card-title'),
272
+ 'GS050-CSS-KGPRTICO': cssCardRule('product', 'kg-product-card-title-container'),
273
+
274
+ 'GS050-CSS-KGBA': cssCardRule('before-after', 'kg-before-after-card'),
275
+ 'GS050-CSS-KGBAIA': cssCardRule('before-after', 'kg-before-after-card-image-before'),
276
+ 'GS050-CSS-KGBAIB': cssCardRule('before-after', 'kg-before-after-card-image-after'),
277
+
278
+ 'GS050-CSS-KGFL': cssCardRule('file', 'kg-file-card'),
279
+ 'GS050-CSS-KGFLCON': cssCardRule('file', 'kg-file-card-container'),
280
+ 'GS050-CSS-KGFLCNT': cssCardRule('file', 'kg-file-card-contents'),
281
+ 'GS050-CSS-KGFLTTL': cssCardRule('file', 'kg-file-card-title'),
282
+ 'GS050-CSS-KGFLCAP': cssCardRule('file', 'kg-file-card-caption'),
283
+ 'GS050-CSS-KGFLNM': cssCardRule('file', 'kg-file-card-filename'),
284
+ 'GS050-CSS-KGFLSZ': cssCardRule('file', 'kg-file-card-filesize'),
285
+ 'GS050-CSS-KGFLMD': cssCardRule('file', 'kg-file-card-medium'),
286
+ 'GS050-CSS-KGFLSM': cssCardRule('file', 'kg-file-card-small'),
287
+
288
+ 'GS050-CSS-KGBQALT': cssCardRule('blockquote', 'kg-blockquote-alt')
289
+ };
290
+
291
+ knownHelpers = _.union(previousKnownHelpers, knownHelpers);
292
+ templates = _.union(previousTemplates, templates);
293
+
294
+ // Merge the previous rules into the new rules, but overwrite any specified property,
295
+ // as well as adding any new rule to the spec.
296
+ // Furthermore, replace the usage of the old doc URLs that we're linking to, with the
297
+ // new version.
298
+ rules = _.each(_.merge({}, previousRules, rules), function replaceDocsUrl(value) {
299
+ value.details = value.details.replace(prevDocsBaseUrlRegEx, docsBaseUrl);
300
+ });
301
+
302
+ module.exports = {
303
+ knownHelpers: knownHelpers,
304
+ templates: templates,
305
+ rules: rules
306
+ };
@@ -2,7 +2,7 @@
2
2
  * Makes sure the paths are forward-slash-separated `/`
3
3
  * This is needed because the partial are only referenced with forward-slashes
4
4
  * in Handlebars templates.
5
- * @param {string} path filesytem path
5
+ * @param {string} path filesystem path
6
6
  * @returns {string} the linux-normalized path
7
7
  */
8
8
  function normalizePath(path) {
@@ -11,9 +11,13 @@
11
11
  "major": "3.x",
12
12
  "docs": "3.0.0"
13
13
  },
14
- "canary": {
14
+ "v4": {
15
15
  "major": "4.x",
16
16
  "docs": "4.0.0"
17
17
  },
18
- "default": "canary"
18
+ "canary": {
19
+ "major": "5.x",
20
+ "docs": "5.0.0"
21
+ },
22
+ "default": "v4"
19
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gscan",
3
- "version": "4.22.0",
3
+ "version": "4.23.0",
4
4
  "description": "Scans Ghost themes looking for errors, deprecations, features and compatibility",
5
5
  "keywords": [
6
6
  "ghost",
@@ -29,31 +29,31 @@
29
29
  "scripts": {
30
30
  "start": "node app/index.js",
31
31
  "dev": "NODE_ENV=development DEBUG=gscan:* nodemon",
32
- "lint": "eslint . --ext .js --cache",
32
+ "lint": "eslint ./app --ext .js --cache",
33
33
  "test": "NODE_ENV=testing mocha -- $(find test -name '*.test.js')",
34
34
  "posttest": "yarn lint",
35
35
  "coverage": "NODE_ENV=testing istanbul cover --dir test/coverage _mocha -- $(find test -name '*.test.js')",
36
36
  "preship": "yarn test",
37
- "ship": "STATUS=$(git status --porcelain); echo $STATUS; if [ -z \"$STATUS\" ]; then yarn publish && git push ${GHOST_UPSTREAM:-upstream} main --follow-tags; fi"
37
+ "ship": "yarn publish && git push ${GHOST_UPSTREAM:-upstream} main --follow-tags"
38
38
  },
39
39
  "bin": {
40
40
  "gscan": "./bin/cli.js"
41
41
  },
42
42
  "dependencies": {
43
- "@sentry/node": "6.16.1",
43
+ "@sentry/node": "6.18.2",
44
44
  "@tryghost/config": "0.2.2",
45
- "@tryghost/debug": "0.1.10",
45
+ "@tryghost/debug": "0.1.11",
46
46
  "@tryghost/ignition-errors": "0.1.8",
47
- "@tryghost/logging": "2.0.1",
48
- "@tryghost/pretty-cli": "1.2.22",
47
+ "@tryghost/logging": "2.0.4",
48
+ "@tryghost/pretty-cli": "1.2.24",
49
49
  "@tryghost/server": "0.1.4",
50
- "@tryghost/zip": "1.1.18",
50
+ "@tryghost/zip": "1.1.20",
51
51
  "bluebird": "3.7.2",
52
52
  "chalk": "4.1.2",
53
53
  "common-tags": "1.8.2",
54
- "express": "4.17.2",
54
+ "express": "4.17.3",
55
55
  "express-hbs": "2.4.0",
56
- "fs-extra": "9.1.0",
56
+ "fs-extra": "10.0.1",
57
57
  "glob": "7.2.0",
58
58
  "lodash": "4.17.21",
59
59
  "multer": "1.4.4",
@@ -71,7 +71,7 @@
71
71
  "nodemon": "2.0.7",
72
72
  "rewire": "6.0.0",
73
73
  "should": "13.2.3",
74
- "sinon": "12.0.0"
74
+ "sinon": "13.0.0"
75
75
  },
76
76
  "files": [
77
77
  "lib",