ghost 4.23.0 → 4.24.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.
Files changed (48) hide show
  1. package/core/app.js +12 -1
  2. package/core/boot.js +31 -17
  3. package/core/bridge.js +10 -10
  4. package/core/built/assets/{ghost-dark-42cf6e0c730578940ec069bda45aea41.css → ghost-dark-e7b57ab951512c5719aee89b16b9a448.css} +1 -1
  5. package/core/built/assets/{ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css → ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css} +1 -1
  6. package/core/built/assets/{ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js → ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js} +84 -84
  7. package/core/built/assets/{vendor.min-c9002845b6c30ac978abdadde9f33d7c.js → vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js} +148 -75
  8. package/core/frontend/services/card-assets/index.js +0 -12
  9. package/core/frontend/services/card-assets/service.js +22 -21
  10. package/core/frontend/src/cards/css/bookmark.css +7 -0
  11. package/core/frontend/src/cards/css/callout.css +1 -2
  12. package/core/frontend/src/cards/css/gallery.css +13 -3
  13. package/core/frontend/src/cards/css/toggle.css +20 -9
  14. package/core/frontend/web/middleware/serve-public-file.js +25 -8
  15. package/core/frontend/web/site.js +0 -3
  16. package/core/server/services/email-analytics/jobs/index.js +1 -1
  17. package/core/server/services/mega/template.js +5 -1
  18. package/core/server/services/redirects/api.js +18 -23
  19. package/core/server/services/redirects/index.js +18 -10
  20. package/core/server/services/redirects/utils.js +14 -0
  21. package/core/server/services/redirects/validation.js +10 -0
  22. package/core/server/services/route-settings/index.js +40 -17
  23. package/core/server/services/route-settings/route-settings.js +127 -114
  24. package/core/server/services/route-settings/settings-loader.js +14 -32
  25. package/core/server/services/themes/activation-bridge.js +3 -3
  26. package/core/server/services/url/LocalFileCache.js +75 -0
  27. package/core/server/services/url/UrlService.js +15 -47
  28. package/core/server/services/url/index.js +17 -4
  29. package/core/server/web/admin/app.js +2 -5
  30. package/core/server/web/admin/controller.js +35 -12
  31. package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
  32. package/core/server/web/admin/views/default-prod.html +4 -4
  33. package/core/server/web/admin/views/default.html +4 -4
  34. package/core/server/web/api/canary/admin/app.js +0 -3
  35. package/core/server/web/api/canary/content/app.js +0 -3
  36. package/core/server/web/api/v2/admin/app.js +0 -3
  37. package/core/server/web/api/v2/content/app.js +0 -3
  38. package/core/server/web/api/v3/admin/app.js +0 -3
  39. package/core/server/web/api/v3/content/app.js +0 -3
  40. package/core/server/web/members/app.js +0 -3
  41. package/core/server/web/oauth/app.js +0 -4
  42. package/core/server/web/parent/app.js +17 -8
  43. package/core/server/web/shared/middleware/error-handler.js +70 -53
  44. package/core/server/web/shared/middleware/index.js +0 -4
  45. package/core/shared/labs.js +7 -2
  46. package/package.json +19 -18
  47. package/yarn.lock +267 -107
  48. package/core/server/web/shared/middleware/maintenance.js +0 -25
@@ -1,10 +1,20 @@
1
1
  const debug = require('@tryghost/debug')('web:admin:controller');
2
+ const errors = require('@tryghost/errors');
3
+ const tpl = require('@tryghost/tpl');
2
4
  const path = require('path');
3
5
  const fs = require('fs');
4
6
  const crypto = require('crypto');
5
7
  const config = require('../../../shared/config');
6
8
  const updateCheck = require('../../update-check');
7
9
 
10
+ const messages = {
11
+ templateError: {
12
+ message: 'Unable to find admin template file {templatePath}',
13
+ context: 'These template files are generated as part of the build process',
14
+ help: 'Please see {link}'
15
+ }
16
+ };
17
+
8
18
  /**
9
19
  * @description Admin controller to handle /ghost/ requests.
10
20
  *
@@ -23,18 +33,31 @@ module.exports = function adminController(req, res) {
23
33
  const templatePath = path.resolve(config.get('paths').adminViews, defaultTemplate);
24
34
  const headers = {};
25
35
 
26
- // Generate our own ETag header
27
- // `sendFile` by default uses filesize+lastmod date to generate an etag.
28
- // That doesn't work for admin templates because the filesize doesn't change between versions
29
- // and `npm pack` sets a fixed lastmod date for every file meaning the default etag never changes
30
- const fileBuffer = fs.readFileSync(templatePath);
31
- const hashSum = crypto.createHash('md5');
32
- hashSum.update(fileBuffer);
33
- headers.ETag = hashSum.digest('hex');
36
+ try {
37
+ // Generate our own ETag header
38
+ // `sendFile` by default uses filesize+lastmod date to generate an etag.
39
+ // That doesn't work for admin templates because the filesize doesn't change between versions
40
+ // and `npm pack` sets a fixed lastmod date for every file meaning the default etag never changes
41
+ const fileBuffer = fs.readFileSync(templatePath);
42
+ const hashSum = crypto.createHash('md5');
43
+ hashSum.update(fileBuffer);
44
+ headers.ETag = hashSum.digest('hex');
34
45
 
35
- if (config.get('adminFrameProtection')) {
36
- headers['X-Frame-Options'] = 'sameorigin';
37
- }
46
+ if (config.get('adminFrameProtection')) {
47
+ headers['X-Frame-Options'] = 'sameorigin';
48
+ }
38
49
 
39
- res.sendFile(templatePath, {headers});
50
+ res.sendFile(templatePath, {headers});
51
+ } catch (error) {
52
+ if (error.code === 'ENOENT') {
53
+ throw new errors.IncorrectUsageError({
54
+ message: tpl(messages.templateError.message, {templatePath}),
55
+ context: tpl(messages.templateError.context),
56
+ help: tpl(messages.templateError.help, {link: 'https://ghost.org/docs/install/source/'}),
57
+ error: error
58
+ });
59
+ } else {
60
+ throw error;
61
+ }
62
+ }
40
63
  };
@@ -0,0 +1,15 @@
1
+ const urlUtils = require('../../../../shared/url-utils');
2
+
3
+ function redirectAdminUrls(req, res, next) {
4
+ const subdir = urlUtils.getSubdir();
5
+ const ghostPathRegex = new RegExp(`^${subdir}/ghost/(.+)`);
6
+ const ghostPathMatch = req.originalUrl.match(ghostPathRegex);
7
+
8
+ if (ghostPathMatch) {
9
+ return res.redirect(urlUtils.urlJoin(urlUtils.urlFor('admin'), '#', ghostPathMatch[1]));
10
+ }
11
+
12
+ next();
13
+ }
14
+
15
+ module.exports = redirectAdminUrls;
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.23%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.24%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -41,7 +41,7 @@
41
41
 
42
42
 
43
43
  <link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
44
- <link rel="stylesheet" href="assets/ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css" title="light">
44
+ <link rel="stylesheet" href="assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css" title="light">
45
45
 
46
46
 
47
47
 
@@ -59,8 +59,8 @@
59
59
  <div id="ember-basic-dropdown-wormhole"></div>
60
60
 
61
61
 
62
- <script src="assets/vendor.min-c9002845b6c30ac978abdadde9f33d7c.js"></script>
63
- <script src="assets/ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js"></script>
62
+ <script src="assets/vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js"></script>
63
+ <script src="assets/ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js"></script>
64
64
 
65
65
  </body>
66
66
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.23%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.24%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -41,7 +41,7 @@
41
41
 
42
42
 
43
43
  <link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
44
- <link rel="stylesheet" href="assets/ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css" title="light">
44
+ <link rel="stylesheet" href="assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css" title="light">
45
45
 
46
46
 
47
47
 
@@ -59,8 +59,8 @@
59
59
  <div id="ember-basic-dropdown-wormhole"></div>
60
60
 
61
61
 
62
- <script src="assets/vendor.min-c9002845b6c30ac978abdadde9f33d7c.js"></script>
63
- <script src="assets/ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js"></script>
62
+ <script src="assets/vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js"></script>
63
+ <script src="assets/ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js"></script>
64
64
 
65
65
  </body>
66
66
  </html>
@@ -19,9 +19,6 @@ module.exports = function setupApiApp() {
19
19
  // Query parsing
20
20
  apiApp.use(boolParser());
21
21
 
22
- // send 503 json response in case of maintenance
23
- apiApp.use(shared.middleware.maintenance);
24
-
25
22
  // Check version matches for API requests, depends on res.locals.safeVersion being set
26
23
  // Therefore must come after themeHandler.ghostLocals, for now
27
24
  apiApp.use(apiMw.versionMatch);
@@ -17,9 +17,6 @@ module.exports = function setupApiApp() {
17
17
  // Query parsing
18
18
  apiApp.use(boolParser());
19
19
 
20
- // send 503 json response in case of maintenance
21
- apiApp.use(shared.middleware.maintenance);
22
-
23
20
  // API shouldn't be cached
24
21
  apiApp.use(shared.middleware.cacheControl('private'));
25
22
 
@@ -19,9 +19,6 @@ module.exports = function setupApiApp() {
19
19
  // Query parsing
20
20
  apiApp.use(boolParser());
21
21
 
22
- // send 503 json response in case of maintenance
23
- apiApp.use(shared.middleware.maintenance);
24
-
25
22
  // Check version matches for API requests, depends on res.locals.safeVersion being set
26
23
  // Therefore must come after themeHandler.ghostLocals, for now
27
24
  apiApp.use(apiMw.versionMatch);
@@ -17,9 +17,6 @@ module.exports = function setupApiApp() {
17
17
  // Query parsing
18
18
  apiApp.use(boolParser());
19
19
 
20
- // send 503 json response in case of maintenance
21
- apiApp.use(shared.middleware.maintenance);
22
-
23
20
  // API shouldn't be cached
24
21
  apiApp.use(shared.middleware.cacheControl('private'));
25
22
 
@@ -19,9 +19,6 @@ module.exports = function setupApiApp() {
19
19
  // Query parsing
20
20
  apiApp.use(boolParser());
21
21
 
22
- // send 503 json response in case of maintenance
23
- apiApp.use(shared.middleware.maintenance);
24
-
25
22
  // Check version matches for API requests, depends on res.locals.safeVersion being set
26
23
  // Therefore must come after themeHandler.ghostLocals, for now
27
24
  apiApp.use(apiMw.versionMatch);
@@ -17,9 +17,6 @@ module.exports = function setupApiApp() {
17
17
  // Query parsing
18
18
  apiApp.use(boolParser());
19
19
 
20
- // send 503 json response in case of maintenance
21
- apiApp.use(shared.middleware.maintenance);
22
-
23
20
  // API shouldn't be cached
24
21
  apiApp.use(shared.middleware.cacheControl('private'));
25
22
 
@@ -13,9 +13,6 @@ module.exports = function setupMembersApp() {
13
13
  debug('Members App setup start');
14
14
  const membersApp = express('members');
15
15
 
16
- // send 503 json response in case of maintenance
17
- membersApp.use(shared.middleware.maintenance);
18
-
19
16
  // Members API shouldn't be cached
20
17
  membersApp.use(shared.middleware.cacheControl('private'));
21
18
 
@@ -2,7 +2,6 @@ const debug = require('@tryghost/debug')('web:oauth:app');
2
2
  const {URL} = require('url');
3
3
  const express = require('../../../shared/express');
4
4
  const urlUtils = require('../../../shared/url-utils');
5
- const shared = require('../shared');
6
5
  const settingsCache = require('../../../shared/settings-cache');
7
6
  const models = require('../../models');
8
7
  const auth = require('../../services/auth');
@@ -24,9 +23,6 @@ module.exports = function setupOAuthApp() {
24
23
  }
25
24
  oauthApp.use(labsMiddleware);
26
25
 
27
- // send 503 json response in case of maintenance
28
- oauthApp.use(shared.middleware.maintenance);
29
-
30
26
  /**
31
27
  * Configure the passport.authenticate middleware
32
28
  * We need to configure it on each request because clientId and secret
@@ -5,7 +5,13 @@ const compress = require('compression');
5
5
  const mw = require('./middleware');
6
6
  const vhost = require('@tryghost/vhost-middleware');
7
7
 
8
- module.exports = function setupParentApp(options = {}) {
8
+ /**
9
+ * @param {Object} options
10
+ * @param {Boolean} [options.start]
11
+ * @param {Boolean} [options.backend]
12
+ * @param {Boolean} [options.frontend]
13
+ */
14
+ module.exports = function setupParentApp({start, frontend = true, backend = true}) {
9
15
  debug('ParentApp setup start');
10
16
  const parentApp = express('parent');
11
17
 
@@ -26,14 +32,17 @@ module.exports = function setupParentApp(options = {}) {
26
32
 
27
33
  // Mount the express apps on the parentApp
28
34
 
29
- // ADMIN + API
30
- const backendApp = require('./backend')();
31
- parentApp.use(vhost(config.getBackendMountPath(), backendApp));
32
-
33
- // SITE + MEMBERS
34
- const frontendApp = require('./frontend')(options);
35
- parentApp.use(vhost(config.getFrontendMountPath(), frontendApp));
35
+ if (backend) {
36
+ debug('Mounting bakcend: ADMIN + API');
37
+ const backendApp = require('./backend')();
38
+ parentApp.use(vhost(config.getBackendMountPath(), backendApp));
39
+ }
36
40
 
41
+ if (frontend) {
42
+ debug('Mounting frontend: SITE + MEMBERS');
43
+ const frontendApp = require('./frontend')({start});
44
+ parentApp.use(vhost(config.getFrontendMountPath(), frontendApp));
45
+ }
37
46
  debug('ParentApp setup end');
38
47
 
39
48
  return parentApp;
@@ -60,6 +60,32 @@ _private.createHbsEngine = () => {
60
60
  return engine.express4();
61
61
  };
62
62
 
63
+ _private.updateStack = (err) => {
64
+ let stackbits = err.stack.split(/\n/g);
65
+
66
+ // We build this up backwards, so we always insert at position 1
67
+
68
+ if (process.env.NODE_ENV === 'production' || err.statusCode === 404) {
69
+ // In production mode, remove the stack trace
70
+ stackbits.splice(1, stackbits.length - 1);
71
+ } else {
72
+ // In dev mode, clearly mark the strack trace
73
+ stackbits.splice(1, 0, `Stack Trace:`);
74
+ }
75
+
76
+ // Add in our custom cotext and help methods
77
+
78
+ if (err.help) {
79
+ stackbits.splice(1, 0, `${err.help}`);
80
+ }
81
+
82
+ if (err.context) {
83
+ stackbits.splice(1, 0, `${err.context}`);
84
+ }
85
+
86
+ return stackbits.join('\n');
87
+ };
88
+
63
89
  /**
64
90
  * Get an error ready to be shown the the user
65
91
  *
@@ -79,14 +105,6 @@ _private.prepareError = (err, req, res, next) => {
79
105
  err = new errors.NotFoundError({
80
106
  err: err
81
107
  });
82
- } else if (err.stack.match(/node_modules\/handlebars\//)) {
83
- // Temporary handling of theme errors from handlebars
84
- // @TODO remove this when #10496 is solved properly
85
- err = new errors.IncorrectUsageError({
86
- err: err,
87
- message: err.message,
88
- statusCode: err.statusCode
89
- });
90
108
  } else {
91
109
  err = new errors.GhostError({
92
110
  err: err,
@@ -102,6 +120,8 @@ _private.prepareError = (err, req, res, next) => {
102
120
  // alternative for res.status();
103
121
  res.statusCode = err.statusCode;
104
122
 
123
+ err.stack = _private.updateStack(err);
124
+
105
125
  // never cache errors
106
126
  res.set({
107
127
  'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
@@ -123,6 +143,23 @@ _private.JSONErrorRenderer = (err, req, res, next) => { // eslint-disable-line n
123
143
  });
124
144
  };
125
145
 
146
+ _private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
147
+ const userError = _private.prepareUserMessage(err, req);
148
+
149
+ res.json({
150
+ errors: [{
151
+ message: userError.message || null,
152
+ context: userError.context || null,
153
+ type: err.errorType || null,
154
+ details: err.errorDetails || null,
155
+ property: err.property || null,
156
+ help: err.help || null,
157
+ code: err.code || null,
158
+ id: err.id || null
159
+ }]
160
+ });
161
+ };
162
+
126
163
  _private.prepareUserMessage = (err, res) => {
127
164
  const userError = {
128
165
  message: err.message,
@@ -169,23 +206,6 @@ _private.prepareUserMessage = (err, res) => {
169
206
  return userError;
170
207
  };
171
208
 
172
- _private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
173
- const userError = _private.prepareUserMessage(err, req);
174
-
175
- res.json({
176
- errors: [{
177
- message: userError.message || null,
178
- context: userError.context || null,
179
- type: err.errorType || null,
180
- details: err.errorDetails || null,
181
- property: err.property || null,
182
- help: err.help || null,
183
- code: err.code || null,
184
- id: err.id || null
185
- }]
186
- });
187
- };
188
-
189
209
  _private.ErrorFallbackMessage = err => `<h1>${tpl(messages.oopsErrorTemplateHasError)}</h1>
190
210
  <p>${tpl(messages.encounteredError)}</p>
191
211
  <pre>${escapeExpression(err.message || err)}</pre>
@@ -241,39 +261,36 @@ _private.ThemeErrorRenderer = (err, req, res, next) => {
241
261
  });
242
262
  };
243
263
 
244
- _private.HTMLErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
245
- const data = {
246
- message: err.message,
247
- statusCode: err.statusCode,
248
- errorDetails: err.errorDetails || []
249
- };
250
-
251
- // e.g. if you serve the admin /ghost and Ghost returns a 503 because it generates the urls at the moment.
252
- // This ensures that no matter what res.render will work here
253
- // @TODO: put to prepare error function?
254
- if (_.isEmpty(req.app.engines)) {
255
- res._template = 'error';
256
- req.app.engine('hbs', _private.createHbsEngine());
257
- req.app.set('view engine', 'hbs');
258
- req.app.set('views', config.get('paths').defaultViews);
259
- }
260
-
261
- res.render('error', data, (_err, html) => {
262
- if (!_err) {
263
- return res.send(html);
264
- }
264
+ /**
265
+ * Borrowed heavily from finalHandler
266
+ */
265
267
 
266
- // re-attach new error e.g. error template has syntax error or misusage
267
- req.err = _err;
268
+ const DOUBLE_SPACE_REGEXP = /\x20{2}/g;
269
+ const NEWLINE_REGEXP = /\n/g;
270
+
271
+ function createHtmlDocument(status, message) {
272
+ let body = escapeExpression(message)
273
+ .replace(NEWLINE_REGEXP, '<br>')
274
+ .replace(DOUBLE_SPACE_REGEXP, ' &nbsp;');
275
+
276
+ return `<!DOCTYPE html>\n
277
+ <html lang="en">\n
278
+ <head>\n
279
+ <meta charset="utf-8">\n
280
+ <title>${status} Error</title>\n
281
+ </head>\n
282
+ <body>\n
283
+ <pre>${status} ${body}</pre>\n
284
+ </body>\n
285
+ </html>\n`;
286
+ }
268
287
 
269
- // And then try to explain things to the user...
270
- // Cheat and output the error using handlebars escapeExpression
271
- return res.status(500).send(_private.ErrorFallbackMessage(_err));
272
- });
288
+ _private.HTMLErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
289
+ return res.send(createHtmlDocument(res.statusCode, err.stack));
273
290
  };
274
291
 
275
292
  _private.BasicErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
276
- return res.send(res.statusCode + ' ' + err.message);
293
+ return res.send(res.statusCode + ' ' + err.stack);
277
294
  };
278
295
 
279
296
  errorHandler.resourceNotFound = (req, res, next) => {
@@ -15,10 +15,6 @@ module.exports = {
15
15
  return require('./error-handler');
16
16
  },
17
17
 
18
- get maintenance() {
19
- return require('./maintenance');
20
- },
21
-
22
18
  get prettyUrls() {
23
19
  return require('./pretty-urls');
24
20
  },
@@ -37,7 +37,12 @@ const ALPHA_FEATURES = [
37
37
  'calloutCard',
38
38
  'nftCard',
39
39
  'accordionCard',
40
- 'gifsCard'
40
+ 'gifsCard',
41
+ 'fileCard',
42
+ 'audioCard',
43
+ 'videoCard',
44
+ 'productCard',
45
+ 'quoteStyles'
41
46
  ];
42
47
 
43
48
  module.exports.GA_KEYS = [...GA_FEATURES];
@@ -47,7 +52,7 @@ module.exports.getAll = () => {
47
52
  const labs = _.cloneDeep(settingsCache.get('labs')) || {};
48
53
 
49
54
  ALPHA_FEATURES.forEach((alphaKey) => {
50
- if (labs[alphaKey] && !(config.get('enableDeveloperExperiments') || process.env.NODE_ENV.match(/^testing/))) {
55
+ if (labs[alphaKey] && !(config.get('enableDeveloperExperiments') || process.env.NODE_ENV.startsWith('test'))) {
51
56
  delete labs[alphaKey];
52
57
  }
53
58
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "4.23.0",
3
+ "version": "4.24.0",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -70,10 +70,10 @@
70
70
  "@tryghost/express-dynamic-redirects": "0.2.1",
71
71
  "@tryghost/helpers": "1.1.54",
72
72
  "@tryghost/image-transform": "1.0.18",
73
- "@tryghost/job-manager": "0.8.12",
73
+ "@tryghost/job-manager": "0.8.13",
74
74
  "@tryghost/kg-card-factory": "3.1.0",
75
75
  "@tryghost/kg-default-atoms": "3.1.0",
76
- "@tryghost/kg-default-cards": "5.8.3",
76
+ "@tryghost/kg-default-cards": "5.8.5",
77
77
  "@tryghost/kg-markdown-html-renderer": "5.1.0",
78
78
  "@tryghost/kg-mobiledoc-html-renderer": "5.2.0",
79
79
  "@tryghost/limit-service": "1.0.0",
@@ -94,6 +94,7 @@
94
94
  "@tryghost/root-utils": "0.3.7",
95
95
  "@tryghost/security": "0.2.13",
96
96
  "@tryghost/session-service": "0.1.28",
97
+ "@tryghost/settings-path-manager": "0.1.2",
97
98
  "@tryghost/social-urls": "0.1.27",
98
99
  "@tryghost/string": "0.1.21",
99
100
  "@tryghost/tpl": "0.1.8",
@@ -104,7 +105,7 @@
104
105
  "@tryghost/vhost-middleware": "1.0.19",
105
106
  "@tryghost/zip": "1.1.18",
106
107
  "amperize": "0.6.1",
107
- "analytics-node": "5.1.2",
108
+ "analytics-node": "6.0.0",
108
109
  "bluebird": "3.7.2",
109
110
  "body-parser": "1.19.0",
110
111
  "bookshelf": "1.2.0",
@@ -129,7 +130,7 @@
129
130
  "ghost-storage-base": "1.0.0",
130
131
  "glob": "7.2.0",
131
132
  "got": "9.6.0",
132
- "gscan": "4.11.0",
133
+ "gscan": "4.13.2",
133
134
  "html-to-text": "5.1.1",
134
135
  "image-size": "1.0.0",
135
136
  "intl": "1.2.5",
@@ -144,15 +145,15 @@
144
145
  "lodash": "4.17.21",
145
146
  "luxon": "2.1.1",
146
147
  "mailgun-js": "0.22.0",
147
- "metascraper": "5.25.1",
148
- "metascraper-author": "5.25.1",
149
- "metascraper-description": "5.25.1",
150
- "metascraper-image": "5.25.1",
151
- "metascraper-logo": "5.25.1",
152
- "metascraper-logo-favicon": "5.25.1",
153
- "metascraper-publisher": "5.25.1",
154
- "metascraper-title": "5.25.1",
155
- "metascraper-url": "5.25.1",
148
+ "metascraper": "5.25.2",
149
+ "metascraper-author": "5.25.2",
150
+ "metascraper-description": "5.25.2",
151
+ "metascraper-image": "5.25.2",
152
+ "metascraper-logo": "5.25.2",
153
+ "metascraper-logo-favicon": "5.25.2",
154
+ "metascraper-publisher": "5.25.2",
155
+ "metascraper-title": "5.25.2",
156
+ "metascraper-url": "5.25.2",
156
157
  "moment": "2.24.0",
157
158
  "moment-timezone": "0.5.23",
158
159
  "multer": "1.4.3",
@@ -165,7 +166,7 @@
165
166
  "path-match": "1.2.4",
166
167
  "probe-image-size": "5.0.0",
167
168
  "rss": "1.2.2",
168
- "sanitize-html": "2.5.3",
169
+ "sanitize-html": "2.6.0",
169
170
  "semver": "7.3.5",
170
171
  "stoppable": "1.1.0",
171
172
  "tough-cookie": "4.0.0",
@@ -173,7 +174,7 @@
173
174
  "xml": "1.0.1"
174
175
  },
175
176
  "optionalDependencies": {
176
- "@tryghost/html-to-mobiledoc": "1.3.2",
177
+ "@tryghost/html-to-mobiledoc": "1.3.4",
177
178
  "sqlite3": "5.0.2"
178
179
  },
179
180
  "devDependencies": {
@@ -182,7 +183,7 @@
182
183
  "coffeescript": "2.6.1",
183
184
  "cssnano": "5.0.11",
184
185
  "eslint": "7.32.0",
185
- "eslint-plugin-ghost": "2.7.0",
186
+ "eslint-plugin-ghost": "2.8.0",
186
187
  "grunt": "1.4.1",
187
188
  "grunt-bg-shell": "2.3.3",
188
189
  "grunt-contrib-clean": "2.0.0",
@@ -200,7 +201,7 @@
200
201
  "mock-knex": "0.4.10",
201
202
  "nock": "13.2.1",
202
203
  "papaparse": "5.3.1",
203
- "postcss": "8.3.11",
204
+ "postcss": "8.4.1",
204
205
  "rewire": "5.0.0",
205
206
  "should": "13.2.3",
206
207
  "sinon": "11.1.2",