gscan 5.2.5 → 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/app/index.js +58 -19
- package/app/middlewares/log-request.js +2 -2
- package/bin/cli.js +6 -3
- package/lib/ast-linter/rules/internal/scope.js +1 -5
- package/lib/checker.js +24 -12
- package/lib/read-zip.js +17 -15
- package/lib/specs/v1.js +12 -16
- package/lib/specs/v2.js +10 -11
- package/lib/specs/v3.js +2 -2
- package/lib/specs/v4.js +17 -20
- package/lib/specs/v5.js +1 -1
- 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/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
|
@@ -153,11 +153,14 @@ function outputResult(result, options) {
|
|
|
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
|
|
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
|
},
|
|
@@ -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': {
|
|
@@ -400,7 +395,8 @@ rules = {
|
|
|
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/#package-json" target=_blank><code>package.json</code> documentation</a> for further information
|
|
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>.`
|
|
404
400
|
},
|
|
405
401
|
'GS010-PJ-NAME-LC': {
|
|
406
402
|
level: 'error',
|
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,7 +403,7 @@ 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
|
|
406
|
+
The <code>{{@blog.permalinks}}</code> property will therefore no longer be used and should be removed from the theme.<br>
|
|
408
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}}'
|
|
@@ -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',
|
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/`;
|
|
@@ -35,7 +35,7 @@ let rules = {
|
|
|
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.
|
|
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
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}}'
|
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/`;
|
|
@@ -109,7 +109,7 @@ let rules = {
|
|
|
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/`;
|
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
|
}
|