gscan 5.3.3 → 5.3.5
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/bin/cli.js +180 -104
- package/package.json +16 -13
- package/app/ghost-version.js +0 -39
- package/app/index.js +0 -154
- package/app/middlewares/log-request.js +0 -30
- package/app/middlewares/sentry.js +0 -11
- package/app/middlewares/upload-validation.js +0 -43
- package/app/public/android-chrome-192x192.png +0 -0
- package/app/public/android-chrome-256x256.png +0 -0
- package/app/public/apple-touch-icon.png +0 -0
- package/app/public/favicon-16x16.png +0 -0
- package/app/public/favicon-32x32.png +0 -0
- package/app/public/favicon.ico +0 -0
- package/app/public/gscan.css +0 -2960
- package/app/public/js/gscan.js +0 -38
- package/app/public/logo-black-01.png +0 -0
- package/app/public/logo-gscan-black.png +0 -0
- package/app/public/logo-white-01.png +0 -0
- package/app/public/mstile-150x150.png +0 -0
- package/app/public/safari-pinned-tab.svg +0 -28
- package/app/tpl/error-404.hbs +0 -5
- package/app/tpl/error.hbs +0 -12
- package/app/tpl/index.hbs +0 -58
- package/app/tpl/layouts/default.hbs +0 -194
- package/app/tpl/partials/icon-arrow-down.hbs +0 -12
- package/app/tpl/partials/icon-arrow-up.hbs +0 -12
- package/app/tpl/partials/rule-fail.hbs +0 -25
- package/app/tpl/partials/rule-pass.hbs +0 -3
- package/app/tpl/partials/toggle.hbs +0 -4
- package/app/tpl/result.hbs +0 -80
package/bin/cli.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Remove all Node warnings before doing anything else
|
|
4
|
-
process.removeAllListeners('warning');
|
|
5
|
-
|
|
6
3
|
const prettyCLI = require('@tryghost/pretty-cli');
|
|
7
4
|
const ui = require('@tryghost/pretty-cli').ui;
|
|
8
5
|
const _ = require('lodash');
|
|
@@ -10,6 +7,51 @@ const {default: chalk} = require('chalk');
|
|
|
10
7
|
const gscan = require('../lib');
|
|
11
8
|
const ghostVersions = require('../lib/utils').versions;
|
|
12
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} CliArgv
|
|
12
|
+
* @property {string} themePath
|
|
13
|
+
* @property {boolean} [zip]
|
|
14
|
+
* @property {boolean} [v1]
|
|
15
|
+
* @property {boolean} [v2]
|
|
16
|
+
* @property {boolean} [v3]
|
|
17
|
+
* @property {boolean} [v4]
|
|
18
|
+
* @property {boolean} [v5]
|
|
19
|
+
* @property {boolean} [v6]
|
|
20
|
+
* @property {boolean} [canary]
|
|
21
|
+
* @property {boolean} [fatal]
|
|
22
|
+
* @property {boolean} [verbose]
|
|
23
|
+
* @property {string[]} [labs]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} CheckOptions
|
|
28
|
+
* @property {'cli'} format
|
|
29
|
+
* @property {string} [checkVersion]
|
|
30
|
+
* @property {boolean} [verbose]
|
|
31
|
+
* @property {boolean} [onlyFatalErrors]
|
|
32
|
+
* @property {Record<string, true>} [labs]
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} ResultFailure
|
|
37
|
+
* @property {string} ref
|
|
38
|
+
* @property {string} [message]
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} FormattedResult
|
|
43
|
+
* @property {'error' | 'warning' | 'recommendation' | 'feature'} level
|
|
44
|
+
* @property {string} rule
|
|
45
|
+
* @property {string} details
|
|
46
|
+
* @property {ResultFailure[]} [failures]
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {object} FormattedTheme
|
|
51
|
+
* @property {string} checkedVersion
|
|
52
|
+
* @property {{error: FormattedResult[], warning: FormattedResult[], recommendation: FormattedResult[]}} results
|
|
53
|
+
*/
|
|
54
|
+
|
|
13
55
|
const levels = {
|
|
14
56
|
error: chalk.red,
|
|
15
57
|
warning: chalk.yellow,
|
|
@@ -17,115 +59,68 @@ const levels = {
|
|
|
17
59
|
feature: chalk.green
|
|
18
60
|
};
|
|
19
61
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
62
|
+
/**
|
|
63
|
+
* @param {CliArgv} argv
|
|
64
|
+
* @returns {CheckOptions}
|
|
65
|
+
*/
|
|
66
|
+
function resolveOptions(argv) {
|
|
67
|
+
/** @type {CheckOptions} */
|
|
68
|
+
const options = {format: 'cli'};
|
|
23
69
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'Utilities:',
|
|
31
|
-
'Commands:',
|
|
32
|
-
'Arguments:',
|
|
33
|
-
'Required Options:',
|
|
34
|
-
'Options:',
|
|
35
|
-
'Global Options:'
|
|
36
|
-
])
|
|
37
|
-
.positional('<themePath>', {
|
|
38
|
-
paramsDesc: 'Theme folder or .zip file path',
|
|
39
|
-
mustExist: true
|
|
40
|
-
})
|
|
41
|
-
.boolean('-z, --zip', {
|
|
42
|
-
desc: 'Theme path points to a zip file'
|
|
43
|
-
})
|
|
44
|
-
.boolean('-1, --v1', {
|
|
45
|
-
desc: 'Check theme for Ghost 1.0 compatibility'
|
|
46
|
-
})
|
|
47
|
-
.boolean('-2, --v2', {
|
|
48
|
-
desc: 'Check theme for Ghost 2.0 compatibility'
|
|
49
|
-
})
|
|
50
|
-
.boolean('-3, --v3', {
|
|
51
|
-
desc: 'Check theme for Ghost 3.0 compatibility'
|
|
52
|
-
})
|
|
53
|
-
.boolean('-4, --v4', {
|
|
54
|
-
desc: 'Check theme for Ghost 4.0 compatibility'
|
|
55
|
-
})
|
|
56
|
-
.boolean('-5, --v5', {
|
|
57
|
-
desc: 'Check theme for Ghost 5.0 compatibility'
|
|
58
|
-
})
|
|
59
|
-
.boolean('-6, --v6', {
|
|
60
|
-
desc: 'Check theme for Ghost 6.0 compatibility'
|
|
61
|
-
})
|
|
62
|
-
.boolean('-c, --canary', {
|
|
63
|
-
desc: 'Check theme for Ghost 6.0 compatibility (alias for --v6)'
|
|
64
|
-
})
|
|
65
|
-
.boolean('-f, --fatal', {
|
|
66
|
-
desc: 'Only show fatal errors that prevent upgrading Ghost'
|
|
67
|
-
})
|
|
68
|
-
.boolean('--verbose', {
|
|
69
|
-
desc: 'Output check details'
|
|
70
|
-
})
|
|
71
|
-
.array('--labs', {
|
|
72
|
-
desc: 'a list of labs flags'
|
|
73
|
-
})
|
|
74
|
-
.parseAndExit()
|
|
75
|
-
.then((argv) => {
|
|
76
|
-
if (argv.v1) {
|
|
77
|
-
cliOptions.checkVersion = 'v1';
|
|
78
|
-
} else if (argv.v2) {
|
|
79
|
-
cliOptions.checkVersion = 'v2';
|
|
80
|
-
} else if (argv.v3) {
|
|
81
|
-
cliOptions.checkVersion = 'v3';
|
|
82
|
-
} else if (argv.v4) {
|
|
83
|
-
cliOptions.checkVersion = 'v4';
|
|
84
|
-
} else if (argv.v5) {
|
|
85
|
-
cliOptions.checkVersion = 'v5';
|
|
86
|
-
} else if (argv.v6) {
|
|
87
|
-
cliOptions.checkVersion = 'v6';
|
|
88
|
-
} else if (argv.canary) {
|
|
89
|
-
cliOptions.checkVersion = ghostVersions.canary;
|
|
90
|
-
} else {
|
|
91
|
-
cliOptions.checkVersion = ghostVersions.default;
|
|
92
|
-
}
|
|
70
|
+
if (argv.canary) {
|
|
71
|
+
options.checkVersion = ghostVersions.canary;
|
|
72
|
+
} else {
|
|
73
|
+
const versionKey = Object.keys(ghostVersions).find(k => k.startsWith('v') && argv[k]);
|
|
74
|
+
options.checkVersion = versionKey || ghostVersions.default;
|
|
75
|
+
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
options.verbose = argv.verbose;
|
|
78
|
+
options.onlyFatalErrors = argv.fatal;
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
if (argv.labs) {
|
|
81
|
+
options.labs = {};
|
|
99
82
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
83
|
+
argv.labs.forEach((flag) => {
|
|
84
|
+
options.labs[flag] = true;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
104
87
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} else {
|
|
108
|
-
ui.log(chalk.bold('\nChecking theme compatibility...'));
|
|
109
|
-
}
|
|
88
|
+
return options;
|
|
89
|
+
}
|
|
110
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @param {CliArgv} argv
|
|
93
|
+
* @param {CheckOptions} options
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
async function runCheck(argv, options) {
|
|
97
|
+
if (options.onlyFatalErrors) {
|
|
98
|
+
ui.log(chalk.bold('\nChecking theme compatibility (fatal issues only)...'));
|
|
99
|
+
} else {
|
|
100
|
+
ui.log(chalk.bold('\nChecking theme compatibility...'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const theme = argv.zip
|
|
105
|
+
? await gscan.checkZip(argv.themePath, options)
|
|
106
|
+
: await gscan.check(argv.themePath, options);
|
|
107
|
+
outputResults(theme, options);
|
|
108
|
+
} catch (err) {
|
|
111
109
|
if (argv.zip) {
|
|
112
|
-
|
|
113
|
-
.then(theme => outputResults(theme, cliOptions))
|
|
114
|
-
.catch((error) => {
|
|
115
|
-
ui.log(error);
|
|
116
|
-
});
|
|
110
|
+
ui.log(err);
|
|
117
111
|
} else {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
if (err.code === 'ENOTDIR') {
|
|
123
|
-
ui.log('Did you mean to add the -z flag to read a zip file?');
|
|
124
|
-
}
|
|
125
|
-
});
|
|
112
|
+
ui.log(err.message);
|
|
113
|
+
if (err.code === 'ENOTDIR') {
|
|
114
|
+
ui.log('Did you mean to add the -z flag to read a zip file?');
|
|
115
|
+
}
|
|
126
116
|
}
|
|
127
|
-
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
128
119
|
|
|
120
|
+
/**
|
|
121
|
+
* @param {FormattedResult} result
|
|
122
|
+
* @param {CheckOptions} options
|
|
123
|
+
*/
|
|
129
124
|
function outputResult(result, options) {
|
|
130
125
|
ui.log(levels[result.level](`- ${_.capitalize(result.level)}:`), result.rule);
|
|
131
126
|
|
|
@@ -153,10 +148,20 @@ function outputResult(result, options) {
|
|
|
153
148
|
ui.log(''); // extra line-break
|
|
154
149
|
}
|
|
155
150
|
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} word
|
|
153
|
+
* @param {number} count
|
|
154
|
+
* @returns {string}
|
|
155
|
+
*/
|
|
156
156
|
function formatCount(word, count) {
|
|
157
157
|
return `${count} ${count === 1 ? word : `${word}s`}`;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @param {FormattedTheme} theme
|
|
162
|
+
* @param {CheckOptions} options
|
|
163
|
+
* @returns {string}
|
|
164
|
+
*/
|
|
160
165
|
function getSummary(theme, options) {
|
|
161
166
|
let summaryText = '';
|
|
162
167
|
const errorCount = theme.results.error.length;
|
|
@@ -195,15 +200,20 @@ function getSummary(theme, options) {
|
|
|
195
200
|
return summaryText;
|
|
196
201
|
}
|
|
197
202
|
|
|
203
|
+
/**
|
|
204
|
+
* @param {*} theme - Raw theme object; mutated into FormattedTheme by gscan.format()
|
|
205
|
+
* @param {CheckOptions} options
|
|
206
|
+
*/
|
|
198
207
|
function outputResults(theme, options) {
|
|
199
208
|
try {
|
|
209
|
+
/** @type {FormattedTheme} */
|
|
200
210
|
theme = gscan.format(theme, options);
|
|
201
211
|
} catch (err) {
|
|
202
212
|
ui.log.error('Error formating result, some results may be missing.');
|
|
203
213
|
ui.log.error(err);
|
|
204
214
|
}
|
|
205
215
|
|
|
206
|
-
|
|
216
|
+
const errorCount = theme.results.error.length;
|
|
207
217
|
|
|
208
218
|
ui.log('\n' + getSummary(theme, options));
|
|
209
219
|
|
|
@@ -229,7 +239,7 @@ function outputResults(theme, options) {
|
|
|
229
239
|
_.each(theme.results.recommendation, rule => outputResult(rule, options));
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
ui.log(`\nGet more help at ${chalk.cyan.underline('https://ghost.org/
|
|
242
|
+
ui.log(`\nGet more help at ${chalk.cyan.underline('https://docs.ghost.org/themes/')}`);
|
|
233
243
|
ui.log(`You can also check theme compatibility at ${chalk.cyan.underline('https://gscan.ghost.org/')}`);
|
|
234
244
|
|
|
235
245
|
// The CLI feature is mainly used to run gscan programatically in tests within themes.
|
|
@@ -244,3 +254,69 @@ function outputResults(theme, options) {
|
|
|
244
254
|
process.exit(0);
|
|
245
255
|
}
|
|
246
256
|
}
|
|
257
|
+
|
|
258
|
+
function main() {
|
|
259
|
+
prettyCLI
|
|
260
|
+
.configure({
|
|
261
|
+
name: 'gscan'
|
|
262
|
+
})
|
|
263
|
+
.groupOrder([
|
|
264
|
+
'Sources:',
|
|
265
|
+
'Utilities:',
|
|
266
|
+
'Commands:',
|
|
267
|
+
'Arguments:',
|
|
268
|
+
'Required Options:',
|
|
269
|
+
'Options:',
|
|
270
|
+
'Global Options:'
|
|
271
|
+
])
|
|
272
|
+
.positional('<themePath>', {
|
|
273
|
+
paramsDesc: 'Theme folder or .zip file path',
|
|
274
|
+
mustExist: true
|
|
275
|
+
})
|
|
276
|
+
.boolean('-z, --zip', {
|
|
277
|
+
desc: 'Theme path points to a zip file'
|
|
278
|
+
})
|
|
279
|
+
.boolean('-1, --v1', {
|
|
280
|
+
desc: 'Check theme for Ghost 1.0 compatibility'
|
|
281
|
+
})
|
|
282
|
+
.boolean('-2, --v2', {
|
|
283
|
+
desc: 'Check theme for Ghost 2.0 compatibility'
|
|
284
|
+
})
|
|
285
|
+
.boolean('-3, --v3', {
|
|
286
|
+
desc: 'Check theme for Ghost 3.0 compatibility'
|
|
287
|
+
})
|
|
288
|
+
.boolean('-4, --v4', {
|
|
289
|
+
desc: 'Check theme for Ghost 4.0 compatibility'
|
|
290
|
+
})
|
|
291
|
+
.boolean('-5, --v5', {
|
|
292
|
+
desc: 'Check theme for Ghost 5.0 compatibility'
|
|
293
|
+
})
|
|
294
|
+
.boolean('-6, --v6', {
|
|
295
|
+
desc: 'Check theme for Ghost 6.0 compatibility'
|
|
296
|
+
})
|
|
297
|
+
.boolean('-c, --canary', {
|
|
298
|
+
desc: 'Check theme for Ghost 6.0 compatibility (alias for --v6)'
|
|
299
|
+
})
|
|
300
|
+
.boolean('-f, --fatal', {
|
|
301
|
+
desc: 'Only show fatal errors that prevent upgrading Ghost'
|
|
302
|
+
})
|
|
303
|
+
.boolean('--verbose', {
|
|
304
|
+
desc: 'Output check details'
|
|
305
|
+
})
|
|
306
|
+
.array('--labs', {
|
|
307
|
+
desc: 'a list of labs flags'
|
|
308
|
+
})
|
|
309
|
+
.parseAndExit()
|
|
310
|
+
.then((argv) => {
|
|
311
|
+
const options = resolveOptions(argv);
|
|
312
|
+
runCheck(argv, options);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = {formatCount, getSummary, outputResult, outputResults, resolveOptions, runCheck};
|
|
317
|
+
|
|
318
|
+
if (require.main === module) {
|
|
319
|
+
// Remove all Node warnings only when run as a CLI, not when imported as a module
|
|
320
|
+
process.removeAllListeners('warning');
|
|
321
|
+
main();
|
|
322
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscan",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.5",
|
|
4
4
|
"description": "Scans Ghost themes looking for errors, deprecations, features and compatibility",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ghost",
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"gscan": "./bin/cli.js"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@sentry/node": "10.
|
|
47
|
-
"@tryghost/config": "2.0.
|
|
48
|
-
"@tryghost/debug": "2.0.
|
|
49
|
-
"@tryghost/errors": "3.0.
|
|
50
|
-
"@tryghost/logging": "4.0.
|
|
46
|
+
"@sentry/node": "10.43.0",
|
|
47
|
+
"@tryghost/config": "2.0.3",
|
|
48
|
+
"@tryghost/debug": "2.0.3",
|
|
49
|
+
"@tryghost/errors": "3.0.3",
|
|
50
|
+
"@tryghost/logging": "4.0.3",
|
|
51
51
|
"@tryghost/nql": "0.12.10",
|
|
52
|
-
"@tryghost/pretty-cli": "3.0.
|
|
53
|
-
"@tryghost/server": "2.0.
|
|
54
|
-
"@tryghost/zip": "3.0.
|
|
52
|
+
"@tryghost/pretty-cli": "3.0.3",
|
|
53
|
+
"@tryghost/server": "2.0.3",
|
|
54
|
+
"@tryghost/zip": "3.0.3",
|
|
55
55
|
"chalk": "5.6.2",
|
|
56
56
|
"express": "5.2.1",
|
|
57
57
|
"express-handlebars": "7.1.3",
|
|
@@ -67,17 +67,20 @@
|
|
|
67
67
|
"@eslint/eslintrc": "3.3.5",
|
|
68
68
|
"@eslint/js": "10.0.1",
|
|
69
69
|
"@tryghost/pro-ship": "1.0.7",
|
|
70
|
-
"@vitest/coverage-v8": "
|
|
70
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
71
71
|
"eslint": "10.0.3",
|
|
72
72
|
"eslint-plugin-ghost": "3.5.0",
|
|
73
73
|
"nodemon": "3.1.14",
|
|
74
74
|
"should": "13.2.3",
|
|
75
75
|
"sinon": "21.0.2",
|
|
76
|
-
"vitest": "
|
|
76
|
+
"vitest": "4.1.0"
|
|
77
|
+
},
|
|
78
|
+
"resolutions": {
|
|
79
|
+
"node-loggly-bulk": "4.0.2",
|
|
80
|
+
"node-loggly-bulk/axios": "1.13.6"
|
|
77
81
|
},
|
|
78
82
|
"files": [
|
|
79
83
|
"lib",
|
|
80
|
-
"bin"
|
|
81
|
-
"app"
|
|
84
|
+
"bin"
|
|
82
85
|
]
|
|
83
86
|
}
|
package/app/ghost-version.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const debug = require('@tryghost/debug')('ghost-version');
|
|
2
|
-
const exec = require('child_process').exec;
|
|
3
|
-
const config = require('@tryghost/config');
|
|
4
|
-
|
|
5
|
-
let ttl;
|
|
6
|
-
let ghostVersion;
|
|
7
|
-
|
|
8
|
-
const fetchGhostVersion = function fetchGhostVersion() {
|
|
9
|
-
debug('Ghost version not set or ttl expired');
|
|
10
|
-
exec('npm show ghost version', function (err, stdout, stderr) {
|
|
11
|
-
if (err) {
|
|
12
|
-
debug('fetchGhostVersion err', err);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (stderr) {
|
|
16
|
-
debug('fetchGhostVersion stderr', stderr);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (stdout) {
|
|
20
|
-
debug('fetchGhostVersion stdout', stdout);
|
|
21
|
-
ghostVersion = stdout;
|
|
22
|
-
ttl = new Date(Date.now() + config.get('ghostVersionTTL')).valueOf();
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const middleware = function middleware(req, res, next) {
|
|
28
|
-
if (!ghostVersion || ttl && ttl < Date.now()) {
|
|
29
|
-
fetchGhostVersion();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
debug('res.locals.ghostVersion: ' + ghostVersion);
|
|
33
|
-
res.locals.ghostVersion = ghostVersion;
|
|
34
|
-
next();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
fetchGhostVersion();
|
|
38
|
-
|
|
39
|
-
module.exports = middleware;
|
package/app/index.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Init Sentry middleware
|
|
2
|
-
require('./middlewares/sentry');
|
|
3
|
-
const sentry = require('@sentry/node');
|
|
4
|
-
|
|
5
|
-
// Require rest of the modules
|
|
6
|
-
const express = require('express');
|
|
7
|
-
const debug = require('@tryghost/debug')('app');
|
|
8
|
-
const {create} = require('express-handlebars');
|
|
9
|
-
const multer = require('multer');
|
|
10
|
-
const server = require('@tryghost/server');
|
|
11
|
-
const config = require('@tryghost/config');
|
|
12
|
-
const errors = require('@tryghost/errors');
|
|
13
|
-
const gscan = require('../lib');
|
|
14
|
-
const fs = require('fs-extra');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const logRequest = require('./middlewares/log-request');
|
|
17
|
-
const uploadValidation = require('./middlewares/upload-validation');
|
|
18
|
-
const ghostVer = require('./ghost-version');
|
|
19
|
-
const ghostVersions = require('../lib/utils').versions;
|
|
20
|
-
const upload = multer({dest: __dirname + '/uploads/'});
|
|
21
|
-
const app = express();
|
|
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
|
-
});
|
|
63
|
-
|
|
64
|
-
// Configure express
|
|
65
|
-
app.set('x-powered-by', false);
|
|
66
|
-
app.set('query parser', false);
|
|
67
|
-
|
|
68
|
-
app.engine('hbs', scanHbs.engine);
|
|
69
|
-
|
|
70
|
-
app.set('view engine', 'hbs');
|
|
71
|
-
app.set('views', viewsDir);
|
|
72
|
-
|
|
73
|
-
app.use(logRequest);
|
|
74
|
-
app.use(express.static(__dirname + '/public'));
|
|
75
|
-
app.use(ghostVer);
|
|
76
|
-
|
|
77
|
-
app.get('/', function (req, res) {
|
|
78
|
-
res.render('index', {ghostVersions});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
app.post('/',
|
|
82
|
-
upload.single('theme'),
|
|
83
|
-
uploadValidation,
|
|
84
|
-
function (req, res, next) {
|
|
85
|
-
const zip = {
|
|
86
|
-
path: req.file.path,
|
|
87
|
-
name: req.file.originalname
|
|
88
|
-
};
|
|
89
|
-
const options = {
|
|
90
|
-
checkVersion: req.body.version || ghostVersions.default
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
debug('Uploaded: ' + zip.name + ' to ' + zip.path);
|
|
94
|
-
debug('Version to check: ' + options.checkVersion);
|
|
95
|
-
|
|
96
|
-
let checkError;
|
|
97
|
-
|
|
98
|
-
gscan.checkZip(zip, options)
|
|
99
|
-
.then(function processResult(theme) {
|
|
100
|
-
debug('Checked: ' + zip.name);
|
|
101
|
-
res.theme = theme;
|
|
102
|
-
}).catch(function (error) {
|
|
103
|
-
checkError = error;
|
|
104
|
-
}).finally(function () {
|
|
105
|
-
debug('attempting to remove: ' + req.file.path);
|
|
106
|
-
fs.remove(req.file.path)
|
|
107
|
-
.catch(function (removeError) {
|
|
108
|
-
debug('failed to remove uploaded file', removeError);
|
|
109
|
-
})
|
|
110
|
-
.then(function () {
|
|
111
|
-
if (checkError) {
|
|
112
|
-
debug('Calling next with error');
|
|
113
|
-
return next(checkError);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
debug('Calling next');
|
|
117
|
-
return next();
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
},
|
|
121
|
-
function doRender(req, res) {
|
|
122
|
-
const options = {
|
|
123
|
-
checkVersion: req.body.version || ghostVersions.default
|
|
124
|
-
};
|
|
125
|
-
debug('Formatting result');
|
|
126
|
-
const result = gscan.format(res.theme, options);
|
|
127
|
-
debug('Rendering result');
|
|
128
|
-
scanHbs.handlebars.logger.level = 0;
|
|
129
|
-
res.render('result', result);
|
|
130
|
-
}
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
app.use(function (req, res, next) {
|
|
134
|
-
next(new errors.NotFoundError({message: 'Page not found'}));
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// eslint-disable-next-line no-unused-vars
|
|
138
|
-
app.use(function (err, req, res, next) {
|
|
139
|
-
let template = 'error';
|
|
140
|
-
req.err = err;
|
|
141
|
-
|
|
142
|
-
let statusCode = err.statusCode || 500;
|
|
143
|
-
res.status(statusCode);
|
|
144
|
-
|
|
145
|
-
if (res.statusCode === 404) {
|
|
146
|
-
template = 'error-404';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
res.render(template, {message: err.message, stack: err.stack, details: err.errorDetails, context: err.context});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
sentry.setupExpressErrorHandler(app);
|
|
153
|
-
|
|
154
|
-
server.start(app, config.get('port'));
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const randomUUID = require('crypto').randomUUID;
|
|
2
|
-
const logging = require('@tryghost/logging');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @TODO:
|
|
6
|
-
* - move middleware to ignition?
|
|
7
|
-
*/
|
|
8
|
-
module.exports = function logRequest(req, res, next) {
|
|
9
|
-
const startTime = Date.now();
|
|
10
|
-
const requestId = randomUUID();
|
|
11
|
-
|
|
12
|
-
function logResponse() {
|
|
13
|
-
res.responseTime = (Date.now() - startTime) + 'ms';
|
|
14
|
-
req.requestId = requestId;
|
|
15
|
-
req.userId = req.user ? (req.user.id ? req.user.id : req.user) : null;
|
|
16
|
-
|
|
17
|
-
if (req.err) {
|
|
18
|
-
logging.error({req: req, res: res, err: req.err});
|
|
19
|
-
} else {
|
|
20
|
-
logging.info({req: req, res: res});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
res.removeListener('finish', logResponse);
|
|
24
|
-
res.removeListener('close', logResponse);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
res.on('finish', logResponse);
|
|
28
|
-
res.on('close', logResponse);
|
|
29
|
-
next();
|
|
30
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const sentryDSN = process.env.SENTRY_DSN;
|
|
2
|
-
|
|
3
|
-
if (sentryDSN) {
|
|
4
|
-
const Sentry = require('@sentry/node');
|
|
5
|
-
const version = require('../../package.json').version;
|
|
6
|
-
Sentry.init({
|
|
7
|
-
dsn: sentryDSN,
|
|
8
|
-
release: 'gscan@' + version,
|
|
9
|
-
environment: process.env.NODE_ENV
|
|
10
|
-
});
|
|
11
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// NOTE: this middleware was extracted from Ghost core validation for theme uploads
|
|
2
|
-
// might be useful to unify this logic in the future if it's extracted to separate module
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const errors = require('@tryghost/errors');
|
|
5
|
-
|
|
6
|
-
const checkFileExists = function checkFileExists(fileData) {
|
|
7
|
-
return !!(fileData.mimetype && fileData.path);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const checkFileIsValid = function checkFileIsValid(fileData, types, extensions) {
|
|
11
|
-
const type = fileData.mimetype;
|
|
12
|
-
|
|
13
|
-
if (types.includes(type) && extensions.includes(fileData.ext)) {
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return false;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
module.exports = function uploadValidation(req, res, next) {
|
|
21
|
-
const extensions = ['.zip'];
|
|
22
|
-
const contentTypes = ['application/zip', 'application/x-zip-compressed', 'application/octet-stream'];
|
|
23
|
-
|
|
24
|
-
req.file = req.file || {};
|
|
25
|
-
req.file.name = req.file.originalname;
|
|
26
|
-
req.file.type = req.file.mimetype;
|
|
27
|
-
|
|
28
|
-
if (!checkFileExists(req.file)) {
|
|
29
|
-
return next(new errors.ValidationError({
|
|
30
|
-
message: `"Please select a zip file.`
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
req.file.ext = path.extname(req.file.name).toLowerCase();
|
|
35
|
-
|
|
36
|
-
if (!checkFileIsValid(req.file, contentTypes, extensions)) {
|
|
37
|
-
return next(new errors.UnsupportedMediaTypeError({
|
|
38
|
-
message: 'Please select a valid zip file.'
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
next();
|
|
43
|
-
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|