gscan 5.2.4 → 5.3.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/LICENSE +1 -1
- package/README.md +1 -1
- package/app/index.js +58 -19
- package/app/middlewares/log-request.js +2 -2
- package/bin/cli.js +7 -4
- package/lib/ast-linter/rules/internal/scope.js +1 -5
- package/lib/checker.js +24 -12
- package/lib/checks/090-template-syntax.js +5 -3
- package/lib/read-zip.js +17 -15
- package/lib/specs/v1.js +25 -30
- package/lib/specs/v2.js +12 -13
- package/lib/specs/v3.js +5 -5
- package/lib/specs/v4.js +29 -32
- package/lib/specs/v5.js +3 -3
- package/lib/specs/v6.js +1 -1
- package/lib/utils/one-line-trim.js +10 -0
- package/package.json +28 -40
- package/app/public/.eslintrc.js +0 -7
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -114,4 +114,4 @@ When developing new rules or testing gscan following tools are great to have in
|
|
|
114
114
|
|
|
115
115
|
# Copyright & License
|
|
116
116
|
|
|
117
|
-
Copyright (c) 2013-
|
|
117
|
+
Copyright (c) 2013-2026 Ghost Foundation - Released under the [MIT license](LICENSE). Ghost and the Ghost Logo are trademarks of Ghost Foundation Ltd. Please see our [trademark policy](https://ghost.org/trademark/) for info on acceptable usage.
|
package/app/index.js
CHANGED
|
@@ -5,35 +5,70 @@ const sentry = require('@sentry/node');
|
|
|
5
5
|
// Require rest of the modules
|
|
6
6
|
const express = require('express');
|
|
7
7
|
const debug = require('@tryghost/debug')('app');
|
|
8
|
-
const
|
|
8
|
+
const {create} = require('express-handlebars');
|
|
9
9
|
const multer = require('multer');
|
|
10
10
|
const server = require('@tryghost/server');
|
|
11
11
|
const config = require('@tryghost/config');
|
|
12
12
|
const errors = require('@tryghost/errors');
|
|
13
13
|
const gscan = require('../lib');
|
|
14
14
|
const fs = require('fs-extra');
|
|
15
|
+
const path = require('path');
|
|
15
16
|
const logRequest = require('./middlewares/log-request');
|
|
16
17
|
const uploadValidation = require('./middlewares/upload-validation');
|
|
17
18
|
const ghostVer = require('./ghost-version');
|
|
18
|
-
const pkgJson = require('../package.json');
|
|
19
19
|
const ghostVersions = require('../lib/utils').versions;
|
|
20
20
|
const upload = multer({dest: __dirname + '/uploads/'});
|
|
21
21
|
const app = express();
|
|
22
|
-
const
|
|
22
|
+
const viewsDir = path.join(__dirname, 'tpl');
|
|
23
|
+
const scanHbs = create({
|
|
24
|
+
extname: '.hbs',
|
|
25
|
+
partialsDir: path.join(viewsDir, 'partials'),
|
|
26
|
+
layoutsDir: path.join(viewsDir, 'layouts'),
|
|
27
|
+
defaultLayout: 'default',
|
|
28
|
+
helpers: {
|
|
29
|
+
block(name, options) {
|
|
30
|
+
const root = options.data && options.data.root;
|
|
31
|
+
|
|
32
|
+
if (!root) {
|
|
33
|
+
return typeof options.fn === 'function' ? options.fn(this) : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const blockCache = root.blockCache || {};
|
|
37
|
+
let val = blockCache[name];
|
|
38
|
+
|
|
39
|
+
if (val === undefined && typeof options.fn === 'function') {
|
|
40
|
+
val = options.fn(this);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(val)) {
|
|
44
|
+
val = val.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return val;
|
|
48
|
+
},
|
|
49
|
+
contentFor(name, options) {
|
|
50
|
+
const root = options.data && options.data.root;
|
|
51
|
+
|
|
52
|
+
if (!root) {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const blockCache = root.blockCache || (root.blockCache = {});
|
|
57
|
+
const block = blockCache[name] || (blockCache[name] = []);
|
|
58
|
+
block.push(options.fn(this));
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
23
63
|
|
|
24
64
|
// Configure express
|
|
25
65
|
app.set('x-powered-by', false);
|
|
26
66
|
app.set('query parser', false);
|
|
27
67
|
|
|
28
|
-
app.engine('hbs', scanHbs.
|
|
29
|
-
partialsDir: __dirname + '/tpl/partials',
|
|
30
|
-
layoutsDir: __dirname + '/tpl/layouts',
|
|
31
|
-
defaultLayout: __dirname + '/tpl/layouts/default',
|
|
32
|
-
templateOptions: {data: {version: pkgJson.version}}
|
|
33
|
-
}));
|
|
68
|
+
app.engine('hbs', scanHbs.engine);
|
|
34
69
|
|
|
35
70
|
app.set('view engine', 'hbs');
|
|
36
|
-
app.set('views',
|
|
71
|
+
app.set('views', viewsDir);
|
|
37
72
|
|
|
38
73
|
app.use(logRequest);
|
|
39
74
|
app.use(express.static(__dirname + '/public'));
|
|
@@ -58,25 +93,29 @@ app.post('/',
|
|
|
58
93
|
debug('Uploaded: ' + zip.name + ' to ' + zip.path);
|
|
59
94
|
debug('Version to check: ' + options.checkVersion);
|
|
60
95
|
|
|
96
|
+
let checkError;
|
|
97
|
+
|
|
61
98
|
gscan.checkZip(zip, options)
|
|
62
99
|
.then(function processResult(theme) {
|
|
63
100
|
debug('Checked: ' + zip.name);
|
|
64
101
|
res.theme = theme;
|
|
65
|
-
|
|
102
|
+
}).catch(function (error) {
|
|
103
|
+
checkError = error;
|
|
104
|
+
}).finally(function () {
|
|
66
105
|
debug('attempting to remove: ' + req.file.path);
|
|
67
106
|
fs.remove(req.file.path)
|
|
68
|
-
.
|
|
69
|
-
debug('
|
|
70
|
-
return next();
|
|
107
|
+
.catch(function (removeError) {
|
|
108
|
+
debug('failed to remove uploaded file', removeError);
|
|
71
109
|
})
|
|
72
|
-
.
|
|
73
|
-
|
|
110
|
+
.then(function () {
|
|
111
|
+
if (checkError) {
|
|
112
|
+
debug('Calling next with error');
|
|
113
|
+
return next(checkError);
|
|
114
|
+
}
|
|
115
|
+
|
|
74
116
|
debug('Calling next');
|
|
75
117
|
return next();
|
|
76
118
|
});
|
|
77
|
-
}).catch(function (error) {
|
|
78
|
-
debug('Calling next with error');
|
|
79
|
-
return next(error);
|
|
80
119
|
});
|
|
81
120
|
},
|
|
82
121
|
function doRender(req, res) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var randomUUID = require('crypto').randomUUID,
|
|
2
2
|
logging = require('@tryghost/logging');
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -7,7 +7,7 @@ var uuid = require('uuid'),
|
|
|
7
7
|
*/
|
|
8
8
|
module.exports = function logRequest(req, res, next) {
|
|
9
9
|
var startTime = Date.now(),
|
|
10
|
-
requestId =
|
|
10
|
+
requestId = randomUUID();
|
|
11
11
|
|
|
12
12
|
function logResponse() {
|
|
13
13
|
res.responseTime = (Date.now() - startTime) + 'ms';
|
package/bin/cli.js
CHANGED
|
@@ -146,18 +146,21 @@ function outputResult(result, options) {
|
|
|
146
146
|
ui.log(message);
|
|
147
147
|
});
|
|
148
148
|
} else {
|
|
149
|
-
ui.log(`${chalk.bold('Affected Files:')} ${_.map(result.failures, 'ref')}`);
|
|
149
|
+
ui.log(`${chalk.bold('Affected Files:')} ${_.uniq(_.map(result.failures, 'ref')).join(', ')}`);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
ui.log(''); // extra line-break
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function formatCount(word, count) {
|
|
157
|
+
return `${count} ${count === 1 ? word : `${word}s`}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
156
160
|
function getSummary(theme, options) {
|
|
157
161
|
let summaryText = '';
|
|
158
162
|
const errorCount = theme.results.error.length;
|
|
159
163
|
const warnCount = theme.results.warning.length;
|
|
160
|
-
const pluralize = require('pluralize');
|
|
161
164
|
const checkSymbol = '\u2713';
|
|
162
165
|
|
|
163
166
|
if (errorCount === 0 && warnCount === 0) {
|
|
@@ -170,7 +173,7 @@ function getSummary(theme, options) {
|
|
|
170
173
|
summaryText = `Your theme has`;
|
|
171
174
|
|
|
172
175
|
if (!_.isEmpty(theme.results.error)) {
|
|
173
|
-
summaryText += chalk.red.bold(` ${
|
|
176
|
+
summaryText += chalk.red.bold(` ${formatCount('error', theme.results.error.length)}`);
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
if (!_.isEmpty(theme.results.error) && !_.isEmpty(theme.results.warning)) {
|
|
@@ -178,7 +181,7 @@ function getSummary(theme, options) {
|
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
if (!_.isEmpty(theme.results.warning)) {
|
|
181
|
-
summaryText += chalk.yellow.bold(` ${
|
|
184
|
+
summaryText += chalk.yellow.bold(` ${formatCount('warning', theme.results.warning.length)}`);
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
summaryText += '!';
|
|
@@ -215,11 +215,7 @@ class Scope {
|
|
|
215
215
|
let matchedFrame = null;
|
|
216
216
|
|
|
217
217
|
if (this.frames && this.frames.length) {
|
|
218
|
-
matchedFrame = this.frames.find(
|
|
219
|
-
if (frame.nodeName === context) {
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
});
|
|
218
|
+
matchedFrame = this.frames.find(frame => frame.nodeName === context);
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
return matchedFrame && matchedFrame.node;
|
package/lib/checker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const
|
|
2
|
+
const nodeFs = require('fs');
|
|
3
|
+
const nodePath = require('path');
|
|
3
4
|
const debug = require('@tryghost/debug')('checker');
|
|
4
5
|
|
|
5
6
|
const errors = require('@tryghost/errors');
|
|
@@ -10,6 +11,18 @@ const versions = require('./utils').versions;
|
|
|
10
11
|
const labsEnabledHelpers = {
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
function loadChecks() {
|
|
15
|
+
const checksDir = nodePath.join(__dirname, 'checks');
|
|
16
|
+
|
|
17
|
+
return nodeFs.readdirSync(checksDir)
|
|
18
|
+
.filter(fileName => fileName.endsWith('.js'))
|
|
19
|
+
.reduce((checks, fileName) => {
|
|
20
|
+
const checkName = nodePath.basename(fileName, '.js');
|
|
21
|
+
checks[checkName] = require(nodePath.join(checksDir, fileName));
|
|
22
|
+
return checks;
|
|
23
|
+
}, {});
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
/**
|
|
14
27
|
* Check theme
|
|
15
28
|
*
|
|
@@ -25,7 +38,7 @@ const labsEnabledHelpers = {
|
|
|
25
38
|
*/
|
|
26
39
|
const check = async function checkAll(themePath, options = {}) {
|
|
27
40
|
// Require checks late to avoid loading all until used
|
|
28
|
-
const checks = options.skipChecks === true ?
|
|
41
|
+
const checks = options.skipChecks === true ? {} : loadChecks();
|
|
29
42
|
const passedVersion = _.get(options, 'checkVersion', versions.default);
|
|
30
43
|
let version = passedVersion;
|
|
31
44
|
|
|
@@ -90,18 +103,12 @@ const checkZip = async function checkZip(path, options) {
|
|
|
90
103
|
zip = _.clone(path);
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
let extractedZipPath;
|
|
107
|
+
|
|
93
108
|
try {
|
|
94
109
|
const readZip = require('./read-zip');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (options.keepExtractedDir) {
|
|
99
|
-
return theme;
|
|
100
|
-
} else {
|
|
101
|
-
const fs = require('fs-extra');
|
|
102
|
-
await fs.remove(zip.origPath);
|
|
103
|
-
return theme;
|
|
104
|
-
}
|
|
110
|
+
({path: extractedZipPath} = await readZip(zip));
|
|
111
|
+
return await check(extractedZipPath, Object.assign({themeName: zip.name}, options));
|
|
105
112
|
} catch (error) {
|
|
106
113
|
if (!errors.utils.isGhostError(error)) {
|
|
107
114
|
throw new errors.ValidationError({
|
|
@@ -114,6 +121,11 @@ const checkZip = async function checkZip(path, options) {
|
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
throw error;
|
|
124
|
+
} finally {
|
|
125
|
+
if (!options.keepExtractedDir && zip.origPath) {
|
|
126
|
+
const fs = require('fs-extra');
|
|
127
|
+
await fs.remove(zip.origPath);
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
130
|
};
|
|
119
131
|
|
|
@@ -34,9 +34,11 @@ function processFileFunction(files, failures, rules, partialVerificationCache) {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
if (astResults.length) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
astResults.forEach((result) => {
|
|
38
|
+
failures.push({
|
|
39
|
+
ref: themeFile.file,
|
|
40
|
+
message: `${result.message} (L${result.line})`
|
|
41
|
+
});
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
44
|
|
package/lib/read-zip.js
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('zip');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const
|
|
4
|
+
const {randomUUID} = require('crypto');
|
|
5
|
+
const {glob} = require('glob');
|
|
5
6
|
const {extract} = require('@tryghost/zip');
|
|
6
7
|
const errors = require('@tryghost/errors');
|
|
7
|
-
const uuid = require('uuid');
|
|
8
8
|
const _ = require('lodash');
|
|
9
9
|
|
|
10
|
-
const resolveBaseDir = (zipPath) => {
|
|
11
|
-
|
|
12
|
-
glob('**/index.hbs', {cwd: zipPath, nosort: true}, function (err, matches) {
|
|
13
|
-
var matchedPath;
|
|
10
|
+
const resolveBaseDir = async (zipPath) => {
|
|
11
|
+
let matches = [];
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
try {
|
|
14
|
+
matches = await glob('**/index.hbs', {cwd: zipPath, nosort: true});
|
|
15
|
+
} catch (err) {
|
|
16
|
+
debug('Glob match error while resolving zip base dir', err);
|
|
17
|
+
}
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (!_.isEmpty(matches)) {
|
|
20
|
+
debug('Found matches', matches);
|
|
21
|
+
const matchedPath = matches[0].replace(/index\.hbs$/, '');
|
|
22
|
+
zipPath = path.join(zipPath, matchedPath).replace(/\/$/, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return zipPath;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const readZip = (zip) => {
|
|
27
|
-
const tempUuid =
|
|
29
|
+
const tempUuid = randomUUID();
|
|
28
30
|
const tempPath = os.tmpdir() + '/' + tempUuid;
|
|
29
31
|
|
|
30
32
|
debug('Reading Zip', zip.path, 'into', tempPath);
|
package/lib/specs/v1.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This file contains details of the theme API spec, in a format that can be used by GScan
|
|
5
5
|
*/
|
|
6
|
-
const oneLineTrim = require('
|
|
6
|
+
const oneLineTrim = require('../utils/one-line-trim');
|
|
7
7
|
const docsBaseUrl = `https://ghost.org/docs/themes/`;
|
|
8
|
-
let knownHelpers, templates, rules, ruleNext;
|
|
8
|
+
let knownHelpers, templates, rules, ruleNext;
|
|
9
9
|
|
|
10
10
|
knownHelpers = [
|
|
11
11
|
// Ghost
|
|
@@ -99,8 +99,7 @@ rules = {
|
|
|
99
99
|
details: oneLineTrim`The <code>{{image}}</code> helper was replaced with the <code>{{img_url}}</code> helper.<br>
|
|
100
100
|
Depending on the context of the <code>{{img_url}}</code> helper you would need to use e. g. <br><br><code>{{#post}}<br> {{img_url feature_image}}<br>{{/post}}</code><br><br>to render the feature image of the blog post.<br>
|
|
101
101
|
<br><b>If you are using <code>{{if image}}</code></b>, then you have to replace it with e.g. <code>{{if feature_image}}.</code>
|
|
102
|
-
<br><br>Find more information about the <code>{{img_url}}</code> helper <a href="${docsBaseUrl}helpers/img_url/" target=_blank>here</a> and
|
|
103
|
-
read more about Ghost's usage of contexts <a href="${docsBaseUrl}contexts/" target=_blank>here</a>.`,
|
|
102
|
+
<br><br>Find more information about the <code>{{img_url}}</code> helper <a href="${docsBaseUrl}helpers/img_url/" target=_blank>here</a> and read more about Ghost's usage of contexts <a href="${docsBaseUrl}contexts/" target=_blank>here</a>.`,
|
|
104
103
|
regex: /{{\s*?image\b[\w\s='"]*?}}/g,
|
|
105
104
|
helper: '{{image}}'
|
|
106
105
|
},
|
|
@@ -323,7 +322,7 @@ rules = {
|
|
|
323
322
|
rule: 'Replace <code>{{@blog.posts_per_page}}</code> with <code>{{@config.posts_per_page}}</code>',
|
|
324
323
|
details: oneLineTrim`The global <code>{{@blog.posts_per_page}}</code> property was replaced with <code>{{@config.posts_per_page}}</code>.<br>
|
|
325
324
|
Read <a href="${docsBaseUrl}helpers/config/" target=_blank>here</a> about the attribute and
|
|
326
|
-
check <a href="${docsBaseUrl}structure/#
|
|
325
|
+
check <a href="${docsBaseUrl}structure/#package-json" target=_blank>here</a> where you can customise the posts per page setting, as this is now adjustable in your theme.`,
|
|
327
326
|
regex: /{{\s*?@blog\.posts_per_page\s*?}}/g,
|
|
328
327
|
helper: '{{@blog.posts_per_page}}'
|
|
329
328
|
},
|
|
@@ -365,22 +364,18 @@ rules = {
|
|
|
365
364
|
level: 'error',
|
|
366
365
|
rule: 'Replace <code>{{id}}</code> with <code>{{comment_id}}</code> in Disqus embeds.',
|
|
367
366
|
fatal: true,
|
|
368
|
-
details: oneLineTrim`The output of <code>{{id}}</code> has changed in v1.0.0 from an incremental ID to an ObjectID
|
|
369
|
-
This results in Disqus comments not loading in Ghost v1.0.0 posts which were imported from earlier versions
|
|
370
|
-
To resolve this, we've added a <code>{{comment_id}}</code> helper that will output the old ID
|
|
371
|
-
|
|
372
|
-
The Disqus embed must be updated from <code>this.page.identifier = 'ghost-{{id}}';</code> to
|
|
373
|
-
<code>this.page.identifier = 'ghost-{{comment_id}}';</code> to ensure Disqus continues to work.`,
|
|
367
|
+
details: oneLineTrim`The output of <code>{{id}}</code> has changed in v1.0.0 from an incremental ID to an ObjectID.<br>
|
|
368
|
+
This results in Disqus comments not loading in Ghost v1.0.0 posts which were imported from earlier versions.<br>
|
|
369
|
+
To resolve this, we've added a <code>{{comment_id}}</code> helper that will output the old ID for posts that have been imported from earlier versions, and ID for new posts.<br>
|
|
370
|
+
The Disqus embed must be updated from <code>this.page.identifier = 'ghost-{{id}}';</code> to <code>this.page.identifier = 'ghost-{{comment_id}}';</code> to ensure Disqus continues to work.`,
|
|
374
371
|
regex: /(page\.|disqus_)identifier\s?=\s?['"].*?({{\s*?id\s*?}}).*?['"];?/g
|
|
375
372
|
},
|
|
376
373
|
'GS002-ID-HELPER': {
|
|
377
374
|
level: 'recommendation',
|
|
378
375
|
rule: 'The output of <code>{{id}}</code> changed in Ghost v1.0.0, you may need to use <code>{{comment_id}}</code> instead.',
|
|
379
|
-
details: oneLineTrim`The output of <code>{{id}}</code> has changed in v1.0.0 from an incremental ID to an ObjectID
|
|
380
|
-
In v1.0.0 we added a <code>{{comment_id}}</code> helper that will output the old ID
|
|
381
|
-
|
|
382
|
-
If you need the old ID to be output on imported posts, then you will need to use
|
|
383
|
-
<code>{{comment_id}}</code> rather than <code>{{id}}</code>.`,
|
|
376
|
+
details: oneLineTrim`The output of <code>{{id}}</code> has changed in v1.0.0 from an incremental ID to an ObjectID.<br>
|
|
377
|
+
In v1.0.0 we added a <code>{{comment_id}}</code> helper that will output the old ID for posts that have been imported from earlier versions, and ID for new posts.<br>
|
|
378
|
+
If you need the old ID to be output on imported posts, then you will need to use <code>{{comment_id}}</code> rather than <code>{{id}}</code>.`,
|
|
384
379
|
regex: /({{\s*?id\s*?}})/g
|
|
385
380
|
},
|
|
386
381
|
'GS005-TPL-ERR': {
|
|
@@ -394,65 +389,65 @@ rules = {
|
|
|
394
389
|
level: 'error',
|
|
395
390
|
rule: '<code>package.json</code> file should be present',
|
|
396
391
|
details: oneLineTrim`You should provide a <code>package.json</code> file for your theme.<br>
|
|
397
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
392
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> to see which properties are required and which are recommended.`
|
|
398
393
|
},
|
|
399
394
|
'GS010-PJ-PARSE': {
|
|
400
395
|
level: 'error',
|
|
401
396
|
rule: '<code>package.json</code> file can be parsed',
|
|
402
397
|
details: oneLineTrim`Your <code>package.json</code> file couldn't be parsed. This is mostly caused by a missing or unnecessary <code>','</code> or the wrong usage of <code>'""'</code>.<br>
|
|
403
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
404
|
-
A good reference for your <code>package.json</code> file is always the latest version of
|
|
398
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.<br>
|
|
399
|
+
A good reference for your <code>package.json</code> file is always the latest version of <a href="https://github.com/TryGhost/Casper/blob/master/package.json" target=_blank>Casper</a>.`
|
|
405
400
|
},
|
|
406
401
|
'GS010-PJ-NAME-LC': {
|
|
407
402
|
level: 'error',
|
|
408
403
|
rule: '<code>package.json</code> property <code>"name"</code> must be lowercase',
|
|
409
404
|
details: oneLineTrim`The property <code>"name"</code> in your <code>package.json</code> file must be lowercase.<br>
|
|
410
405
|
Good examples are: <code>"my-theme"</code> or <code>"theme"</code> rather than <code>"My Theme"</code> or <code>"Theme"</code>.<br>
|
|
411
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
406
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
412
407
|
},
|
|
413
408
|
'GS010-PJ-NAME-HY': {
|
|
414
409
|
level: 'error',
|
|
415
410
|
rule: '<code>package.json</code> property <code>"name"</code> must be hyphenated',
|
|
416
411
|
details: oneLineTrim`The property <code>"name"</code> in your <code>package.json</code> file must be hyphenated.<br>
|
|
417
412
|
Please use <code>"my-theme"</code> rather than <code>"My Theme"</code> or <code>"my theme"</code>.<br>
|
|
418
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
413
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
419
414
|
},
|
|
420
415
|
'GS010-PJ-NAME-REQ': {
|
|
421
416
|
level: 'error',
|
|
422
417
|
rule: '<code>package.json</code> property <code>"name"</code> is required',
|
|
423
418
|
details: oneLineTrim`Please add the property <code>"name"</code> to your <code>package.json</code>. E.g. <code>{"name": "my-theme"}</code>.<br>
|
|
424
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
419
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> to see which properties are required and which are recommended.`
|
|
425
420
|
},
|
|
426
421
|
'GS010-PJ-VERSION-SEM': {
|
|
427
422
|
level: 'error',
|
|
428
423
|
rule: '<code>package.json</code> property <code>"version"</code> must be semver compliant',
|
|
429
424
|
details: oneLineTrim`The property <code>"version"</code> in your <code>package.json</code> file must be semver compliant. E.g. <code>{"version": "1.0.0"}</code>.<br>
|
|
430
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
425
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
431
426
|
},
|
|
432
427
|
'GS010-PJ-VERSION-REQ': {
|
|
433
428
|
level: 'error',
|
|
434
429
|
rule: '<code>package.json</code> property <code>"version"</code> is required',
|
|
435
430
|
details: oneLineTrim`Please add the property <code>"version"</code> to your <code>package.json</code>. E.g. <code>{"version": "1.0.0"}</code>.<br>
|
|
436
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
431
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> to see which properties are required and which are recommended.`
|
|
437
432
|
},
|
|
438
433
|
'GS010-PJ-AUT-EM-VAL': {
|
|
439
434
|
level: 'error',
|
|
440
435
|
rule: '<code>package.json</code> property <code>"author.email"</code> must be valid',
|
|
441
436
|
details: oneLineTrim`The property <code>"author.email"</code> in your <code>package.json</code> file must a valid email. E.g. <code>{"author": {"email": "hello@example.com"}}</code>.<br>
|
|
442
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
437
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
443
438
|
},
|
|
444
439
|
'GS010-PJ-CONF-PPP': {
|
|
445
440
|
level: 'recommendation',
|
|
446
441
|
rule: '<code>package.json</code> property <code>"config.posts_per_page"</code> is recommended. Otherwise, it falls back to 5',
|
|
447
442
|
details: oneLineTrim`Please add <code>"posts_per_page"</code> to your <code>package.json</code>. E.g. <code>{"config": { "posts_per_page": 5}}</code>.<br>
|
|
448
443
|
If no <code>"posts_per_page"</code> property is provided, Ghost will use its default setting of 5 posts per page.<br>
|
|
449
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
444
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
450
445
|
},
|
|
451
446
|
'GS010-PJ-CONF-PPP-INT': {
|
|
452
447
|
level: 'error',
|
|
453
448
|
rule: '<code>package.json</code> property <code>"config.posts_per_page"</code> must be a number above 0',
|
|
454
449
|
details: oneLineTrim`The property <code>"config.posts_per_page"</code> in your <code>package.json</code> file must be a number greater than zero. E.g. <code>{"config": { "posts_per_page": 5}}</code>.<br>
|
|
455
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
450
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
456
451
|
},
|
|
457
452
|
'GS010-PJ-AUT-EM-REQ': {
|
|
458
453
|
level: 'error',
|
|
@@ -461,7 +456,7 @@ rules = {
|
|
|
461
456
|
The email is required so that themes which are distributed (either free or paid) have a method of contacting the author so users can get support and more importantly so that>
|
|
462
457
|
Ghost can reach out about breaking changes and security updates.<br>
|
|
463
458
|
The <code>package.json</code> file is <strong>NOT</strong> accessible when uploaded to a blog so if the theme is only uploaded to a single blog, no one will see this email address.<br>
|
|
464
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
459
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> to see which properties are required and which are recommended.`
|
|
465
460
|
},
|
|
466
461
|
'GS020-INDEX-REQ': {
|
|
467
462
|
level: 'error',
|
|
@@ -476,14 +471,14 @@ rules = {
|
|
|
476
471
|
rule: 'A template file called <code>post.hbs</code> must be present',
|
|
477
472
|
fatal: true,
|
|
478
473
|
details: oneLineTrim`Your theme must have a template file called <code>index.hbs</code>.<br>
|
|
479
|
-
Read <a href="${docsBaseUrl}structure/#templates" target=_blank>here</a> more about the required template structure and <code>post.hbs</code> in <a href="${docsBaseUrl}structure/#
|
|
474
|
+
Read <a href="${docsBaseUrl}structure/#templates" target=_blank>here</a> more about the required template structure and <code>post.hbs</code> in <a href="${docsBaseUrl}structure/#post-hbs" target=_blank>particular</a>.`,
|
|
480
475
|
path: 'post.hbs'
|
|
481
476
|
},
|
|
482
477
|
'GS020-DEF-REC': {
|
|
483
478
|
level: 'recommendation',
|
|
484
479
|
rule: 'Provide a default layout template called default.hbs',
|
|
485
480
|
details: oneLineTrim`It is recommended that your theme has a template file called <code>default.hbs</code>.<br>
|
|
486
|
-
Read <a href="${docsBaseUrl}structure/#templates" target=_blank>here</a> more about the recommended template structure and <code>default.hbs</code> in <a href="${docsBaseUrl}structure/#
|
|
481
|
+
Read <a href="${docsBaseUrl}structure/#templates" target=_blank>here</a> more about the recommended template structure and <code>default.hbs</code> in <a href="${docsBaseUrl}structure/#default-hbs" target=_blank>particular</a>.`,
|
|
487
482
|
path: 'default.hbs'
|
|
488
483
|
},
|
|
489
484
|
'GS030-ASSET-REQ': {
|
package/lib/specs/v2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const oneLineTrim = require('
|
|
2
|
+
const oneLineTrim = require('../utils/one-line-trim');
|
|
3
3
|
const previousSpec = require('./v1');
|
|
4
4
|
const ghostVersions = require('../utils').versions;
|
|
5
5
|
const docsBaseUrl = `https://ghost.org/docs/themes/`;
|
|
@@ -68,8 +68,7 @@ let rules = {
|
|
|
68
68
|
'GS001-DEPR-AUTHBL': {
|
|
69
69
|
level: 'error',
|
|
70
70
|
rule: 'Replace <code>{{#author}}</code> with <code>{{#primary_author}}</code> or <code>{{#foreach authors}}...{{/foreach}}</code>',
|
|
71
|
-
details: oneLineTrim`The usage of <code>{{#author}}</code> block helper outside of <code>author.hbs</code> is deprecated and
|
|
72
|
-
should be replaced with <code>{{#primary_author}}</code> or <code>{{#foreach authors}}...{{/foreach}}</code>.<br>
|
|
71
|
+
details: oneLineTrim`The usage of <code>{{#author}}</code> block helper outside of <code>author.hbs</code> is deprecated and should be replaced with <code>{{#primary_author}}</code> or <code>{{#foreach authors}}...{{/foreach}}</code>.<br>
|
|
73
72
|
Find more information about the <code>{{authors}}</code> helper <a href="${docsBaseUrl}helpers/authors/" target=_blank>here</a>.`,
|
|
74
73
|
regex: /{{\s*?#author\s*?}}/g,
|
|
75
74
|
notValidIn: 'author.hbs',
|
|
@@ -395,7 +394,7 @@ let rules = {
|
|
|
395
394
|
level: 'error',
|
|
396
395
|
rule: 'The <code>{{@blog}}</code> helper should be replaced with <code>{{@site}}</code>',
|
|
397
396
|
details: oneLineTrim`With the introduction of the Content API <code>{{@blog}}</code> became deprecated in favour of <code>{{@site}}</code>.<br>
|
|
398
|
-
The <code>{{@blog}}</code> helper will be removed in next version of Ghost and should not be used
|
|
397
|
+
The <code>{{@blog}}</code> helper will be removed in next version of Ghost and should not be used.<br>
|
|
399
398
|
Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
|
|
400
399
|
regex: /{{\s*?@blog\.[a-zA-Z0-9_]+\s*?}}/g,
|
|
401
400
|
helper: '{{@blog}}'
|
|
@@ -404,8 +403,8 @@ let rules = {
|
|
|
404
403
|
level: 'error',
|
|
405
404
|
rule: '<code>{{@blog.permalinks}}</code> was removed',
|
|
406
405
|
details: oneLineTrim`With the introduction of Dynamic Routing, you can define multiple permalinks.<br>
|
|
407
|
-
The <code>{{@blog.permalinks}}</code> property will therefore no longer be used and should be removed from the theme
|
|
408
|
-
Find more information about Ghost data helpers <a href="${docsBaseUrl}/
|
|
406
|
+
The <code>{{@blog.permalinks}}</code> property will therefore no longer be used and should be removed from the theme.<br>
|
|
407
|
+
Find more information about Ghost data helpers <a href="${docsBaseUrl}helpers/data/" target=_blank>here</a>.`,
|
|
409
408
|
regex: /{{\s*?@blog\.permalinks\s*?}}/g,
|
|
410
409
|
helper: '{{@blog.permalinks}}'
|
|
411
410
|
},
|
|
@@ -413,7 +412,7 @@ let rules = {
|
|
|
413
412
|
level: 'error',
|
|
414
413
|
rule: '<code>{{@site.permalinks}}</code> was removed',
|
|
415
414
|
details: oneLineTrim`With the introduction of Dynamic Routing, you can define multiple permalinks.<br>
|
|
416
|
-
The <code>{{@site.permalinks}}</code> property will therefore no longer be used and should be removed from the theme
|
|
415
|
+
The <code>{{@site.permalinks}}</code> property will therefore no longer be used and should be removed from the theme.<br>
|
|
417
416
|
Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
|
|
418
417
|
regex: /{{\s*?@site\.permalinks\s*?}}/g,
|
|
419
418
|
helper: '{{@site.permalinks}}'
|
|
@@ -422,7 +421,7 @@ let rules = {
|
|
|
422
421
|
level: 'error',
|
|
423
422
|
rule: 'Replace <code>{{@site.ghost_head}}</code> with <code>{{ghost_head}}</code>',
|
|
424
423
|
details: oneLineTrim`The usage of <code>{{@site.ghost_head}}</code> is deprecated and should be replaced with <code>{{ghost_head}}</code>.<br>
|
|
425
|
-
The <code>{{@site.ghost_head}}</code> property will therefore no longer be used and should be removed from the theme
|
|
424
|
+
The <code>{{@site.ghost_head}}</code> property will therefore no longer be used and should be removed from the theme.<br>
|
|
426
425
|
Find more information about the <code>{{ghost_head}}</code> property <a href="${docsBaseUrl}helpers/ghost_head_foot/" target=_blank>here</a>.`,
|
|
427
426
|
regex: /{{\s*?@site\.ghost_head\s*?}}/g,
|
|
428
427
|
helper: '{{@site.ghost_head}}'
|
|
@@ -431,7 +430,7 @@ let rules = {
|
|
|
431
430
|
level: 'error',
|
|
432
431
|
rule: 'Replace <code>{{@site.ghost_foot}}</code> with <code>{{ghost_foot}}</code>',
|
|
433
432
|
details: oneLineTrim`The usage of <code>{{@site.ghost_foot}}</code> is deprecated and should be replaced with <code>{{ghost_foot}}</code>.<br>
|
|
434
|
-
The <code>{{@site.ghost_foot}}</code> property will therefore no longer be used and should be removed from the theme
|
|
433
|
+
The <code>{{@site.ghost_foot}}</code> property will therefore no longer be used and should be removed from the theme.<br>
|
|
435
434
|
Find more information about the <code>{{ghost_foot}}</code> property <a href="${docsBaseUrl}helpers/ghost_head_foot/" target=_blank>here</a>.`,
|
|
436
435
|
regex: /{{\s*?@site\.ghost_foot\s*?}}/g,
|
|
437
436
|
helper: '{{@site.ghost_foot}}'
|
|
@@ -440,7 +439,7 @@ let rules = {
|
|
|
440
439
|
level: 'error',
|
|
441
440
|
rule: 'The <code>{{lang}}</code> helper should be replaced with <code>{{@site.lang}}</code>',
|
|
442
441
|
details: oneLineTrim`The <code>{{lang}}</code> helper is a duplicate of <code>{{@site.lang}}</code>. Using <code>{{@site.lang}}</code> is preferred.<br>
|
|
443
|
-
The <code>{{lang}}</code> helper will be removed in next version of Ghost and should not be used
|
|
442
|
+
The <code>{{lang}}</code> helper will be removed in next version of Ghost and should not be used.<br>
|
|
444
443
|
Find more information about the <code>@site.lang</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
|
|
445
444
|
regex: /{{\s*?lang\s*?}}/g,
|
|
446
445
|
helper: '{{lang}}'
|
|
@@ -448,8 +447,8 @@ let rules = {
|
|
|
448
447
|
'GS001-DEPR-CSS-KGMD': {
|
|
449
448
|
level: 'warning',
|
|
450
449
|
rule: `<code>.kg-card-markdown</code> doesn't exist in current version of Ghost, ensure your theme works without it`,
|
|
451
|
-
details: oneLineTrim`The <code>.kg-card-markdown</code> CSS class is deprecated and will no longer be used in Ghost
|
|
452
|
-
It's recommended to add your own wrapper around the <code>{{content}}</code> helper and target that instead if needed
|
|
450
|
+
details: oneLineTrim`The <code>.kg-card-markdown</code> CSS class is deprecated and will no longer be used in Ghost.<br>
|
|
451
|
+
It's recommended to add your own wrapper around the <code>{{content}}</code> helper and target that instead if needed.<br>
|
|
453
452
|
Find out more about required theme changes for the Koenig editor <a href="${docsBaseUrl}content/" target=_blank>here</a>.`,
|
|
454
453
|
regex: /\.kg-card-markdown/g,
|
|
455
454
|
className: '.kg-card-markdown',
|
|
@@ -843,7 +842,7 @@ let rules = {
|
|
|
843
842
|
level: 'warning',
|
|
844
843
|
rule: '<code>package.json</code> property <code>keywords</code> should contain <code>ghost-theme</code>',
|
|
845
844
|
details: oneLineTrim`The property <code>keywords</code> in your <code>package.json</code> file must contain <code>ghost-theme</code>. E.g. <code>{"keywords": ["ghost-theme"]}</code>.<br>
|
|
846
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
845
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
847
846
|
}
|
|
848
847
|
};
|
|
849
848
|
|
package/lib/specs/v3.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const oneLineTrim = require('
|
|
2
|
+
const oneLineTrim = require('../utils/one-line-trim');
|
|
3
3
|
const previousSpec = require('./v2');
|
|
4
4
|
const ghostVersions = require('../utils').versions;
|
|
5
5
|
const docsBaseUrl = `https://ghost.org/docs/themes/`;
|
|
@@ -22,21 +22,21 @@ let rules = {
|
|
|
22
22
|
rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is recommended. Otherwise, it falls back to "v3"',
|
|
23
23
|
details: oneLineTrim`Please add <code>"ghost-api"</code> to your <code>package.json</code>. E.g. <code>{"engines": {"ghost-api": "v3"}}</code>.<br>
|
|
24
24
|
If no <code>"ghost-api"</code> property is provided, Ghost will use its default setting of "v3" Ghost API.<br>
|
|
25
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
25
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
26
26
|
},
|
|
27
27
|
'GS010-PJ-GHOST-API-V01': {
|
|
28
28
|
level: 'error',
|
|
29
29
|
rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is incompatible with current version of Ghost API and will fall back to "v3"',
|
|
30
30
|
details: oneLineTrim`Please change <code>"ghost-api"</code> in your <code>package.json</code> to higher version. E.g. <code>{"engines": {"ghost-api": "v3"}}</code>.<br>
|
|
31
31
|
If <code>"ghost-api"</code> property is left at "v0.1", Ghost will use its default setting of "v3".<br>
|
|
32
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
32
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
33
33
|
},
|
|
34
34
|
'GS001-DEPR-ESC': {
|
|
35
35
|
level: 'error',
|
|
36
36
|
rule: 'Replace <code>{{error.code}}</code> with <code>{{error.statusCode}}</code>',
|
|
37
37
|
details: oneLineTrim`The usage of <code>{{error.code}}</code> is deprecated and should be replaced with <code>{{error.statusCode}}</code>.<br>
|
|
38
|
-
When in <code>error</code> context, e.
|
|
39
|
-
Find more information about the <code>error.hbs</code> template <a href="${docsBaseUrl}structure/#
|
|
38
|
+
When in <code>error</code> context, e.g. in the <code>error.hbs</code> template, replace <code>{{code}}</code> with <code>{{statusCode}}</code>.<br>
|
|
39
|
+
Find more information about the <code>error.hbs</code> template <a href="${docsBaseUrl}structure/#error-hbs" target=_blank>here</a>.`,
|
|
40
40
|
regex: /{{\s*?(?:error\.)?(code)\s*?}}/g,
|
|
41
41
|
helper: '{{error.code}}'
|
|
42
42
|
},
|
package/lib/specs/v4.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const oneLineTrim = require('
|
|
2
|
+
const oneLineTrim = require('../utils/one-line-trim');
|
|
3
3
|
const previousSpec = require('./v3');
|
|
4
4
|
const ghostVersions = require('../utils').versions;
|
|
5
5
|
const docsBaseUrl = `https://ghost.org/docs/themes/`;
|
|
@@ -35,81 +35,81 @@ let rules = {
|
|
|
35
35
|
rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is deprecated.',
|
|
36
36
|
details: oneLineTrim`Remove <code>"ghost-api"</code> from your <code>package.json</code>.<br>
|
|
37
37
|
The <code>ghost-api</code> support will be removed in next major version of Ghost and should not be used.
|
|
38
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
38
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
39
39
|
},
|
|
40
40
|
'GS010-PJ-GHOST-API-V01': {
|
|
41
41
|
level: 'error',
|
|
42
42
|
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"',
|
|
43
43
|
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>
|
|
44
44
|
If <code>"ghost-api"</code> property is left at "v0.1", Ghost will use its default setting of "v4".<br>
|
|
45
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
45
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
46
46
|
},
|
|
47
47
|
'GS010-PJ-GHOST-API-V2': {
|
|
48
48
|
level: 'warning',
|
|
49
49
|
rule: '<code>package.json</code> property <code>"engines.ghost-api"</code> is using a deprecated version of Ghost API',
|
|
50
50
|
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>
|
|
51
51
|
If <code>"ghost-api"</code> property is left at "v2", it will stop working with next major version upgrade and default to v5.<br>
|
|
52
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
52
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
53
53
|
},
|
|
54
54
|
'GS010-PJ-CUST-THEME-TOTAL-SETTINGS': {
|
|
55
55
|
level: 'error',
|
|
56
56
|
rule: '<code>package.json</code> property <code>"config.custom"</code> contains too many settings',
|
|
57
57
|
details: oneLineTrim`Remove key from <code>"config.custom"</code> in your <code>package.json</code> to have less than or exactly 20 settings.<br>
|
|
58
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
58
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
59
59
|
},
|
|
60
60
|
'GS010-PJ-CUST-THEME-SETTINGS-CASE': {
|
|
61
61
|
level: 'error',
|
|
62
62
|
rule: '<code>package.json</code> property <code>"config.custom"</code> contains a property that isn\'t snake-cased',
|
|
63
63
|
details: oneLineTrim`Rewrite all property in <code>"config.custom"</code> in your <code>package.json</code> in snake case.<br>
|
|
64
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
64
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
65
65
|
},
|
|
66
66
|
'GS010-PJ-CUST-THEME-SETTINGS-TYPE': {
|
|
67
67
|
level: 'error',
|
|
68
68
|
rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"type"</code>.',
|
|
69
69
|
details: oneLineTrim`Only use the following types: <code>"select"</code>, <code>"boolean"</code>, <code>"color"</code>, <code>"image"</code>, <code>"text"</code>.<br>
|
|
70
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
70
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
71
71
|
},
|
|
72
72
|
'GS010-PJ-CUST-THEME-SETTINGS-GROUP': {
|
|
73
73
|
level: 'recommendation',
|
|
74
74
|
rule: '<code>package.json</code> objects defined in <code>"config.custom"</code> should have a known <code>"group"</code>.',
|
|
75
75
|
details: oneLineTrim`Only use the following groups: <code>"post"</code>, <code>"homepage"</code>.<br>
|
|
76
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
76
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
77
77
|
},
|
|
78
78
|
'GS010-PJ-CUST-THEME-SETTINGS-SELECT-OPTIONS': {
|
|
79
79
|
level: 'error',
|
|
80
80
|
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>.',
|
|
81
81
|
details: oneLineTrim`Make sure there is at least 2 <code>"options"</code> in each <code>"select"</code> custom theme property.<br>
|
|
82
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
82
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
83
83
|
},
|
|
84
84
|
'GS010-PJ-CUST-THEME-SETTINGS-SELECT-DEFAULT': {
|
|
85
85
|
level: 'error',
|
|
86
86
|
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>.',
|
|
87
87
|
details: oneLineTrim`Make sure the <code>"default"</code> property matches a value in <code>"options"</code> of the same <code>"select"</code>.<br>
|
|
88
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
88
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
89
89
|
},
|
|
90
90
|
'GS010-PJ-CUST-THEME-SETTINGS-BOOLEAN-DEFAULT': {
|
|
91
91
|
level: 'error',
|
|
92
92
|
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>.',
|
|
93
93
|
details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>true</code> or <code>false</code>.<br>
|
|
94
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
94
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
95
95
|
},
|
|
96
96
|
'GS010-PJ-CUST-THEME-SETTINGS-COLOR-DEFAULT': {
|
|
97
97
|
level: 'error',
|
|
98
98
|
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>.',
|
|
99
99
|
details: oneLineTrim`Make sure the <code>"default"</code> property is a valid 6-hexadecimal-digit color code like <code>#15171a</code>.<br>
|
|
100
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
100
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
101
101
|
},
|
|
102
102
|
'GS010-PJ-CUST-THEME-SETTINGS-IMAGE-DEFAULT': {
|
|
103
103
|
level: 'error',
|
|
104
104
|
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.',
|
|
105
105
|
details: oneLineTrim`Make sure the <code>"default"</code> property is either <code>null</code>, an empty string <code>''</code> or isn't present.<br>
|
|
106
|
-
Check the <a href="${docsBaseUrl}structure/#
|
|
106
|
+
Check the <a href="${docsBaseUrl}structure/#package-json" target=_blank><code>package.json</code> documentation</a> for further information.`
|
|
107
107
|
},
|
|
108
108
|
'GS001-DEPR-LABS-MEMBERS': {
|
|
109
109
|
level: 'warning',
|
|
110
110
|
rule: 'The <code>{{@labs.members}}</code> helper should not be used.',
|
|
111
111
|
details: oneLineTrim`Remove <code>{{@labs.members}}</code> from the theme.<br>
|
|
112
|
-
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
|
|
112
|
+
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>.<br>
|
|
113
113
|
Find more information about the <code>@labs</code> property <a href="${docsBaseUrl}helpers/labs/" target=_blank>here</a>.`,
|
|
114
114
|
regex: /@labs\.members/g,
|
|
115
115
|
helper: '{{@labs.members}}'
|
|
@@ -126,7 +126,7 @@ let rules = {
|
|
|
126
126
|
'GS080-CARD-LAST4': {
|
|
127
127
|
level: 'warning',
|
|
128
128
|
rule: 'The <code>default_payment_card_last4</code> field now coalesces to <code>****</code> in Ghost 4.x instead of null.',
|
|
129
|
-
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>
|
|
129
|
+
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><br>
|
|
130
130
|
Find more information about the <code>default_payment_card_last4</code> attribute <a href="${docsBaseUrl}members/#subscription-attributes" target=_blank>here</a>.`,
|
|
131
131
|
regex: /default_payment_card_last4/g,
|
|
132
132
|
helper: '{{default_payment_card_last4}}',
|
|
@@ -144,7 +144,7 @@ let rules = {
|
|
|
144
144
|
'GS001-DEPR-CURR-SYM': {
|
|
145
145
|
level: 'warning',
|
|
146
146
|
rule: 'Replace <code>{{[#].currency_symbol}}</code> with <code>{{price currency=currency}}</code>.',
|
|
147
|
-
details: oneLineTrim`The hardcoded <code>currency_symbol</code> attribute was removed in favour of passing the currency to updated <code>{{price}}</code> helper
|
|
147
|
+
details: oneLineTrim`The hardcoded <code>currency_symbol</code> attribute was removed in favour of passing the currency to updated <code>{{price}}</code> helper.<br>
|
|
148
148
|
Find more information about the updated <code>{{price}}</code> helper <a href="${docsBaseUrl}members/#the-price-helper" target=_blank>here</a>.`,
|
|
149
149
|
helper: '{{[#].currency_symbol}}',
|
|
150
150
|
regex: /currency_symbol/g
|
|
@@ -153,7 +153,7 @@ let rules = {
|
|
|
153
153
|
level: 'warning',
|
|
154
154
|
rule: 'Replace <code>{{@site.lang}}</code> with <code>{{@site.locale}}</code>',
|
|
155
155
|
details: oneLineTrim`Replace <code>{{@site.lang}}</code> helper with <code>{{@site.locale}}</code>.<br>
|
|
156
|
-
The <code>{{@site.lang}}</code> helper will be removed in next version of Ghost and should not be used
|
|
156
|
+
The <code>{{@site.lang}}</code> helper will be removed in next version of Ghost and should not be used.<br>
|
|
157
157
|
Find more information about the <code>@site</code> property <a href="${docsBaseUrl}helpers/site/" target=_blank>here</a>.`,
|
|
158
158
|
regex: /@site\.lang/g,
|
|
159
159
|
helper: '{{@site.lang}}'
|
|
@@ -214,17 +214,17 @@ let rules = {
|
|
|
214
214
|
},
|
|
215
215
|
|
|
216
216
|
'GS050-CSS-KGCO': cssCardRule('callout', 'kg-callout-card'),
|
|
217
|
-
'GS050-CSS-KGCOE': cssCardRule('callout', 'kg-callout-
|
|
218
|
-
'GS050-CSS-KGCOT': cssCardRule('callout', 'kg-callout-
|
|
219
|
-
'GS050-CSS-KGCOBGGY': cssCardRule('callout', 'kg-callout-card-
|
|
220
|
-
'GS050-CSS-KGCOBGW': cssCardRule('callout', 'kg-callout-card-
|
|
221
|
-
'GS050-CSS-KGCOBGB': cssCardRule('callout', 'kg-callout-card-
|
|
222
|
-
'GS050-CSS-KGCOBGGN': cssCardRule('callout', 'kg-callout-card-
|
|
223
|
-
'GS050-CSS-KGCOBGY': cssCardRule('callout', 'kg-callout-card-
|
|
224
|
-
'GS050-CSS-KGCOBGR': cssCardRule('callout', 'kg-callout-card-
|
|
225
|
-
'GS050-CSS-KGCOBGPK': cssCardRule('callout', 'kg-callout-card-
|
|
226
|
-
'GS050-CSS-KGCOBGPE': cssCardRule('callout', 'kg-callout-card-
|
|
227
|
-
'GS050-CSS-KGCOBGA': cssCardRule('callout', 'kg-callout-card-
|
|
217
|
+
'GS050-CSS-KGCOE': cssCardRule('callout', 'kg-callout-emoji'),
|
|
218
|
+
'GS050-CSS-KGCOT': cssCardRule('callout', 'kg-callout-text'),
|
|
219
|
+
'GS050-CSS-KGCOBGGY': cssCardRule('callout', 'kg-callout-card-grey'),
|
|
220
|
+
'GS050-CSS-KGCOBGW': cssCardRule('callout', 'kg-callout-card-white'),
|
|
221
|
+
'GS050-CSS-KGCOBGB': cssCardRule('callout', 'kg-callout-card-blue'),
|
|
222
|
+
'GS050-CSS-KGCOBGGN': cssCardRule('callout', 'kg-callout-card-green'),
|
|
223
|
+
'GS050-CSS-KGCOBGY': cssCardRule('callout', 'kg-callout-card-yellow'),
|
|
224
|
+
'GS050-CSS-KGCOBGR': cssCardRule('callout', 'kg-callout-card-red'),
|
|
225
|
+
'GS050-CSS-KGCOBGPK': cssCardRule('callout', 'kg-callout-card-pink'),
|
|
226
|
+
'GS050-CSS-KGCOBGPE': cssCardRule('callout', 'kg-callout-card-purple'),
|
|
227
|
+
'GS050-CSS-KGCOBGA': cssCardRule('callout', 'kg-callout-card-accent'),
|
|
228
228
|
|
|
229
229
|
'GS050-CSS-KG-NFT': cssCardRule('nft', 'kg-nft-card'),
|
|
230
230
|
'GS050-CSS-KG-NFTCO': cssCardRule('nft', 'kg-nft-card-container'),
|
|
@@ -232,7 +232,7 @@ let rules = {
|
|
|
232
232
|
'GS050-CSS-KG-NFTIMG': cssCardRule('nft', 'kg-nft-image'),
|
|
233
233
|
'GS050-CSS-KG-NFTHD': cssCardRule('nft', 'kg-nft-header'),
|
|
234
234
|
'GS050-CSS-KG-NFTTIT': cssCardRule('nft', 'kg-nft-title'),
|
|
235
|
-
'GS050-CSS-KG-NFTLG': cssCardRule('nft', 'kg-nft-logo'),
|
|
235
|
+
'GS050-CSS-KG-NFTLG': cssCardRule('nft', 'kg-nft-opensea-logo'),
|
|
236
236
|
'GS050-CSS-KG-NFTCTR': cssCardRule('nft', 'kg-nft-creator'),
|
|
237
237
|
'GS050-CSS-KG-NFTDSC': cssCardRule('nft', 'kg-nft-description'),
|
|
238
238
|
|
|
@@ -264,10 +264,7 @@ let rules = {
|
|
|
264
264
|
'GS050-CSS-KGVIDCNT': cssCardRule('video', 'kg-video-container'),
|
|
265
265
|
'GS050-CSS-KGVIDOVL': cssCardRule('video', 'kg-video-overlay'),
|
|
266
266
|
'GS050-CSS-KGVIDLGPLICO': cssCardRule('video', 'kg-video-large-play-icon'),
|
|
267
|
-
'GS050-CSS-KGVIDTHUMB': cssCardRule('video', 'kg-video-thumbnail'),
|
|
268
|
-
'GS050-CSS-KGVIDTHUMBPL': cssCardRule('video', 'kg-video-thumbnail.placeholder'),
|
|
269
267
|
'GS050-CSS-KGVIDPLCNT': cssCardRule('video', 'kg-video-player-container'),
|
|
270
|
-
'GS050-CSS-KGVIDTI': cssCardRule('video', 'kg-video-title'),
|
|
271
268
|
'GS050-CSS-KGVIDPL': cssCardRule('video', 'kg-video-player'),
|
|
272
269
|
'GS050-CSS-KGVIDCURRTM': cssCardRule('video', 'kg-video-current-time'),
|
|
273
270
|
'GS050-CSS-KGVIDTM': cssCardRule('video', 'kg-video-time'),
|
package/lib/specs/v5.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const oneLineTrim = require('
|
|
2
|
+
const oneLineTrim = require('../utils/one-line-trim');
|
|
3
3
|
const previousSpec = require('./v4');
|
|
4
4
|
const ghostVersions = require('../utils').versions;
|
|
5
5
|
const docsBaseUrl = `https://ghost.org/docs/themes/`;
|
|
@@ -23,7 +23,7 @@ let rules = {
|
|
|
23
23
|
level: 'warning',
|
|
24
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
|
-
Find more information about the <code>package.json</code> file <a href="${docsBaseUrl}structure/#
|
|
26
|
+
Find more information about the <code>package.json</code> file <a href="${docsBaseUrl}structure/#package-json" target=_blank>here</a>.`
|
|
27
27
|
},
|
|
28
28
|
'GS010-PJ-GHOST-CARD-ASSETS-NOT-PRESENT': {
|
|
29
29
|
level: 'warning',
|
|
@@ -650,7 +650,7 @@ let rules = {
|
|
|
650
650
|
rule: 'Remove uses of <code>{{@blog.permalinks}}</code>',
|
|
651
651
|
details: oneLineTrim`With the introduction of Dynamic Routing, you can define multiple permalinks.<br>
|
|
652
652
|
The <code>{{@blog.permalinks}}</code> property will therefore no longer be used and should be removed from the theme.
|
|
653
|
-
Find more information about Ghost data helpers <a href="${docsBaseUrl}/
|
|
653
|
+
Find more information about Ghost data helpers <a href="${docsBaseUrl}helpers/data/" target=_blank>here</a>.`,
|
|
654
654
|
regex: /{{\s*?@blog\.permalinks\s*?}}/g,
|
|
655
655
|
helper: '{{@blog.permalinks}}'
|
|
656
656
|
},
|
package/lib/specs/v6.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function oneLineTrim(strings, ...values) {
|
|
2
|
+
const combined = strings.reduce((result, str, index) => {
|
|
3
|
+
const value = index < values.length ? values[index] : '';
|
|
4
|
+
return result + str + value;
|
|
5
|
+
}, '');
|
|
6
|
+
|
|
7
|
+
return combined.replace(/(?:\n\s*)/g, '').trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = oneLineTrim;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscan",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Scans Ghost themes looking for errors, deprecations, features and compatibility",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ghost",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"registry": "https://registry.npmjs.org/"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": "^
|
|
20
|
+
"node": "^20.11.1 || ^22.13.1 || ^24.0.0"
|
|
21
21
|
},
|
|
22
22
|
"bugs": {
|
|
23
23
|
"url": "https://github.com/TryGhost/gscan/issues"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"start": "node app/index.js",
|
|
34
34
|
"dev": "NODE_ENV=development DEBUG=gscan:* nodemon",
|
|
35
35
|
"lint": "eslint . --ext .js --cache",
|
|
36
|
-
"test": "NODE_ENV=testing c8 mocha test/*.test.js",
|
|
36
|
+
"test": "NODE_ENV=testing c8 --check-coverage --branches 95 --functions 95 --lines 95 --statements 95 mocha test/*.test.js",
|
|
37
37
|
"posttest": "yarn lint",
|
|
38
38
|
"preship": "yarn test",
|
|
39
39
|
"ship": "pro-ship",
|
|
@@ -43,38 +43,35 @@
|
|
|
43
43
|
"gscan": "./bin/cli.js"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@sentry/node": "
|
|
47
|
-
"@tryghost/config": "
|
|
48
|
-
"@tryghost/debug": "
|
|
49
|
-
"@tryghost/errors": "
|
|
50
|
-
"@tryghost/logging": "
|
|
51
|
-
"@tryghost/nql": "
|
|
52
|
-
"@tryghost/pretty-cli": "
|
|
53
|
-
"@tryghost/server": "
|
|
54
|
-
"@tryghost/zip": "
|
|
55
|
-
"chalk": "
|
|
56
|
-
"
|
|
57
|
-
"express": "
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"pluralize": "^8.0.0",
|
|
64
|
-
"require-dir": "^1.2.0",
|
|
65
|
-
"semver": "^7.5.4",
|
|
66
|
-
"uuid": "^9.0.1",
|
|
46
|
+
"@sentry/node": "10.40.0",
|
|
47
|
+
"@tryghost/config": "2.0.0",
|
|
48
|
+
"@tryghost/debug": "2.0.0",
|
|
49
|
+
"@tryghost/errors": "3.0.0",
|
|
50
|
+
"@tryghost/logging": "4.0.0",
|
|
51
|
+
"@tryghost/nql": "0.12.10",
|
|
52
|
+
"@tryghost/pretty-cli": "3.0.0",
|
|
53
|
+
"@tryghost/server": "2.0.0",
|
|
54
|
+
"@tryghost/zip": "3.0.0",
|
|
55
|
+
"chalk": "5.6.2",
|
|
56
|
+
"express": "5.2.1",
|
|
57
|
+
"express-handlebars": "7.1.3",
|
|
58
|
+
"fs-extra": "11.3.3",
|
|
59
|
+
"glob": "13.0.6",
|
|
60
|
+
"lodash": "4.17.23",
|
|
61
|
+
"multer": "2.1.0",
|
|
62
|
+
"semver": "7.7.4",
|
|
67
63
|
"validator": "^13.0.0"
|
|
68
64
|
},
|
|
69
65
|
"devDependencies": {
|
|
66
|
+
"@eslint/compat": "2.0.2",
|
|
67
|
+
"@eslint/js": "10.0.1",
|
|
70
68
|
"@tryghost/pro-ship": "1.0.7",
|
|
71
|
-
"c8": "
|
|
72
|
-
"eslint": "
|
|
73
|
-
"eslint-plugin-ghost": "
|
|
69
|
+
"c8": "11.0.0",
|
|
70
|
+
"eslint": "10.0.2",
|
|
71
|
+
"eslint-plugin-ghost": "3.4.4",
|
|
74
72
|
"mocha": "11.7.5",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"rewire": "6.0.0",
|
|
73
|
+
"nodemon": "3.1.14",
|
|
74
|
+
"rewire": "9.0.1",
|
|
78
75
|
"should": "13.2.3",
|
|
79
76
|
"sinon": "21.0.1"
|
|
80
77
|
},
|
|
@@ -82,14 +79,5 @@
|
|
|
82
79
|
"lib",
|
|
83
80
|
"bin",
|
|
84
81
|
"app"
|
|
85
|
-
]
|
|
86
|
-
"renovate": {
|
|
87
|
-
"extends": [
|
|
88
|
-
"@tryghost:quietJS",
|
|
89
|
-
"@tryghost:automergeDevDependencies"
|
|
90
|
-
],
|
|
91
|
-
"ignoreDeps": [
|
|
92
|
-
"validator"
|
|
93
|
-
]
|
|
94
|
-
}
|
|
82
|
+
]
|
|
95
83
|
}
|