0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-27 22:49:56 -05:00
ghost/core/server/config/index.js

519 lines
18 KiB
JavaScript
Raw Normal View History

// # Config
// General entry point for all configuration data
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
var path = require('path'),
Promise = require('bluebird'),
chalk = require('chalk'),
crypto = require('crypto'),
fs = require('fs'),
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
url = require('url'),
_ = require('lodash'),
validator = require('validator'),
readDirectory = require('../utils/read-directory'),
readThemes = require('../utils/read-themes'),
errors = require('../errors'),
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
configUrl = require('./url'),
packageInfo = require('../../../package.json'),
i18n = require('../i18n'),
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
appRoot = path.resolve(__dirname, '../../../'),
corePath = path.resolve(appRoot, 'core/'),
testingEnvs = ['testing', 'testing-mysql', 'testing-pg'],
defaultConfig = {};
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
function ConfigManager(config) {
/**
* Our internal true representation of our current config object.
* @private
* @type {Object}
*/
this._config = {};
// Allow other modules to be externally accessible.
this.urlJoin = configUrl.urlJoin;
this.urlFor = configUrl.urlFor;
this.urlPathForPost = configUrl.urlPathForPost;
Prep shared API URL util for use on external sites refs #5942, #6150 There were a few key problems I was looking to solve with this: - Introduce a single point of truth for what the URL for accessing the API should be - Provide a simple way to configure the utility (much like a true SDK) As of this commit, this utility is still automatically available in a Ghost theme. To use it on an external site, the code would look like: ``` <script type="text/javascript" src="http://my-ghost-blog.com/shared/ghost-url.min.js"></script> <script type="text/javascript"> ghost.init({ clientId: "<your-client-id>", clientSecret: "<your-client-secret>" }); </script> ``` To achieve this, there have been a number of changes: - A new `apiUrl` function has been added to config, which calculates the correct URL. This needs to be unified with the other url generation functions as a separate piece of work. - The serveSharedFile middleware has been updated, so that it can serve files from / or /shared and to substitute `{{api-url}}` as it does `{{blog-url}}`. - ghost-url.js and ghost-url.min.js have been updated to be served via the serveSharedFile middleware - ghost-url.js has been changed slightly, to take the url from an inline variable which is substituted the first time it is served - `{{ghost_head}}` has been updated, removing the api url handling which is now in config/url.js and removing the configuration of the utility in favour of calling `init()` after the script is required - `{{ghost_head}}` has also had the meta tags for client id and secret removed - tests have been updated
2015-12-15 10:41:53 +00:00
this.apiUrl = configUrl.apiUrl;
this.getBaseUrl = configUrl.getBaseUrl;
// If we're given an initial config object then we can set it.
if (config && _.isObject(config)) {
this.set(config);
}
}
// Are we using sockets? Custom socket or the default?
ConfigManager.prototype.getSocket = function () {
var socketConfig,
values = {
path: path.join(this._config.paths.contentPath, process.env.NODE_ENV + '.socket'),
permissions: '660'
};
if (this._config.server.hasOwnProperty('socket')) {
socketConfig = this._config.server.socket;
if (_.isString(socketConfig)) {
values.path = socketConfig;
return values;
}
if (_.isObject(socketConfig)) {
values.path = socketConfig.path || values.path;
values.permissions = socketConfig.permissions || values.permissions;
return values;
}
}
return false;
};
ConfigManager.prototype.init = function (rawConfig) {
var self = this;
// Cache the config.js object's environment
// object so we can later refer to it.
// Note: this is not the entirety of config.js,
// just the object appropriate for this NODE_ENV
self.set(rawConfig);
return self.loadThemes()
.then(function () {
return self.loadApps();
})
.then(function () {
return self._config;
});
};
ConfigManager.prototype.loadThemes = function () {
var self = this;
return readThemes(self._config.paths.themePath)
.then(function (result) {
self._config.paths.availableThemes = result;
});
};
ConfigManager.prototype.loadApps = function () {
var self = this;
return readDirectory(self._config.paths.appPath)
.then(function (result) {
self._config.paths.availableApps = result;
});
};
/**
* Allows you to set the config object.
* @param {Object} config Only accepts an object at the moment.
*/
ConfigManager.prototype.set = function (config) {
var localPath = '',
defaultStorageAdapter = 'local-file-store',
defaultSchedulingAdapter = 'SchedulingDefault',
activeStorageAdapter,
activeSchedulingAdapter,
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
contentPath,
schedulingPath,
subdir,
assetHash;
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Merge passed in config object onto our existing config object.
// We're using merge here as it doesn't assign `undefined` properties
// onto our cached config object. This allows us to only update our
// local copy with properties that have been explicitly set.
_.merge(this._config, config);
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Special case for the database config, which should be overridden not merged
if (config && config.database) {
this._config.database = config.database;
}
// Special case for the them.navigation JSON object, which should be overridden not merged
if (config && config.theme && config.theme.navigation) {
this._config.theme.navigation = config.theme.navigation;
}
// Special case for theme.timezone, which should be overridden not merged
if (config && config.theme && config.theme.timezone) {
this._config.theme.timezone = config.theme.timezone;
} else {
// until we have set the timezone from settings, we use the default
this._config.theme = this._config.theme ? this._config.theme : {};
this._config.theme.timezone = 'Etc/UTC';
}
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Protect against accessing a non-existant object.
// This ensures there's always at least a paths object
// because it's referenced in multiple places.
this._config.paths = this._config.paths || {};
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Parse local path location
if (this._config.url) {
localPath = url.parse(this._config.url).path;
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Remove trailing slash
if (localPath !== '/') {
localPath = localPath.replace(/\/$/, '');
}
}
subdir = localPath === '/' ? '' : localPath;
if (!_.isEmpty(subdir)) {
this._config.slugs.protected.push(subdir.split('/').pop());
}
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// Allow contentPath to be over-written by passed in config object
// Otherwise default to default content path location
contentPath = this._config.paths.contentPath || path.resolve(appRoot, 'content');
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
assetHash = this._config.assetHash ||
(crypto.createHash('md5').update(packageInfo.version + Date.now()).digest('hex')).substring(0, 10);
// read storage adapter from config file or attach default adapter
this._config.storage = this._config.storage || {};
activeStorageAdapter = this._config.storage.active || defaultStorageAdapter;
// read scheduling adapter(s) from config file or attach default adapter
this._config.scheduling = this._config.scheduling || {};
activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter;
// storage.active can be an object like {images: 'my-custom-image-storage-adapter', themes: 'local-file-storage'}
// we ensure that passing a string to storage.active still works, but internal it's always an object
if (_.isString(activeStorageAdapter)) {
this._config.storage = _.merge(this._config.storage, {
active: {
images: activeStorageAdapter,
themes: defaultStorageAdapter
}
});
} else {
// ensure there is a default image storage adapter
if (!this._config.storage.active.images) {
this._config.storage.active.images = defaultStorageAdapter;
}
// ensure there is a default theme storage adapter
// @TODO: right now we only support theme uploads to local file storage
// @TODO: we need to change reading themes from disk on bootstrap (see loadThemes)
this._config.storage.active.themes = defaultStorageAdapter;
}
if (activeSchedulingAdapter === defaultSchedulingAdapter) {
schedulingPath = path.join(corePath, '/server/scheduling/');
} else {
schedulingPath = path.join(contentPath, '/scheduling/');
}
_.merge(this._config, {
ghostVersion: packageInfo.version,
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
paths: {
appRoot: appRoot,
subdir: subdir,
config: this._config.paths.config || path.join(appRoot, 'config.js'),
configExample: path.join(appRoot, 'config.example.js'),
corePath: corePath,
storagePath: {
default: path.join(corePath, '/server/storage/'),
custom: path.join(contentPath, 'storage/')
},
contentPath: contentPath,
themePath: path.resolve(contentPath, 'themes'),
appPath: path.resolve(contentPath, 'apps'),
imagesPath: path.resolve(contentPath, 'images'),
internalAppPath: path.join(corePath, '/server/apps/'),
imagesRelPath: 'content/images',
adminViews: path.join(corePath, '/server/views/'),
helperTemplates: path.join(corePath, '/server/helpers/tpl/'),
availableThemes: this._config.paths.availableThemes || {},
availableApps: this._config.paths.availableApps || {},
clientAssets: path.join(corePath, '/built/assets/')
},
maintenance: {},
scheduling: {
active: activeSchedulingAdapter,
path: schedulingPath
},
theme: {
// normalise the URL by removing any trailing slash
url: this._config.url ? this._config.url.replace(/\/$/, '') : ''
},
routeKeywords: {
tag: 'tag',
author: 'author',
page: 'page',
preview: 'p',
private: 'private',
✨ [FEATURE] AMP⚡ (#7229) closes #6588, #7095 * `ImageObject` with image dimensions (#7152, #7151, #7153) - Returns meta data as promise - returns a new Promise from meta data - uses `Promise.props()` to resolve `getClient()` and `getMetaData()` - Adds 'image-size' util The util returns an object like this ``` { height: 50, url: 'http://myblog.com/images/cat.jpg', width: 50 }; ``` if the dimensions can be fetched and rejects with error, if not. In case we get a locally stored image or a not complete url (like `//www.gravatar.com/andsoon`), we add the protocol to the incomplete one and use `urlFor()` to get the absolute URL. If the request fails or `image-size` is not able to read the file, we reject with error. - adds 'image-size' module to dependencies - adds `getImageSizeFromUrl` function that returns image dimensions - In preparation of AMP support and to improve our schema.org JSON-LD and structured data, I made the following changes: - Changes the following properties to be `Objects`, which have a `url` property by default and a `dimensions` property, if `width` and `height` are available: - `metaData.coverImage` - `metaData.authorImage` - `metaData.blog.logo` - Checks cache by calling `getCachedImageSizeFromUrl`. If image dimensions were fetched already, returns them from cache instead of fetching them again. - If we have image dimensions on hand, the output in our JSON-LD changes from normal urls to be full `ImageObjects`. Applies to all images and logos. - Special case for `publisher.logo` as it has size restrictions: if the image doesn't fulfil the restrictions (<=600 width and <=60 height), we simply output the url instead, so like before. - Adds new property for schema.org JSON-LD: `mainEntityOfPage` as an Object. - Adds additional Open Graph data (if we have the image size): `og:image:width` and `og:image:height` - Adds/updates tests * AMP router and controller (#7171, #7157) Implements AMP in `/apps/`: - renders `amp.hbs` if route is `/:slug/amp/` - updates `setResponseContext` to set context to `['amp', 'post']` for a amp post and `['amp', 'page']` for a page, but will not render amp template for a page - updates `context_spec` - registers 'amp' as new internal app - adds the `amp.hbs` template to `core/server/apps/amp` which will be the default template for AMP posts. - adds `isAmpURL` to `post-lookup` * 🎨 Use `context` in meta as array (#7205) Instead of reading the first value of the context array, we're checking if it includes certain context values. This is a preparation change for AMP, where the context will be delivered as `['amp', 'post']`. * ✨ AMP helpers (#7174, #7216, #7215, #7223) - Adds AMP helpers `{{amp_content}}`, `{{amp_component}}` and `{{amp_ghost_head}}` to support AMP: - `{{amp_content}}`: - Adds `Amperize` as dependency - AMP app uses new helper `{{amp_content}}` to render AMP HTML - `Amperize` transforms regular HTML into AMP HTML - Adds test for `{{amp_content}}` helper - Adds 'Sanitize-HTML` as dependendy - After the HTML get 'amperized' we still might have some HTML tags, which are prohibited in AMP HTML, so we use `sanitize-html` to remove those. With every update, `Amperize` gets and it is able to transform more HTML tags, they valid AMP HTML tags (e. g. `video` and `amp-video`) and will therefore not be removed. - `{{amp_ghost_head}}`: - registers `{{amp_ghost_head}}` helper, but uses `{{ghost_head}}` code - uses `{{amp_ghost_head}}` in `amp.hbs` instead of `{{ghost_head}}` - `{{ghost_head}}`: - Render `amphtml` link in metadata for post, which links to the amp post (`getAmpUrl`) - Updates all test in metadata to support `amp` context - Changes context conditionals to work with full array instead of first array value - Adds conditionals, so no additional javascript gets rendered in `{{ghost_head}}` - Removes trailing `/amp/` in URLs, so only `amphtml` link on regular post renders it - Adds a conditional, so no code injection will be included, for an `amp` context. - `{{amp_components}}`: - AMP app uses new helper `{{amp_components}}` to render necessary script tags for AMP extended components as `amp-iframe`, `amp-anime` and `amp-form` - Adds test for `{{amp_components}}`
2016-08-22 18:49:27 +02:00
subscribe: 'subscribe',
amp: 'amp'
},
✨ [FEATURE] AMP⚡ (#7229) closes #6588, #7095 * `ImageObject` with image dimensions (#7152, #7151, #7153) - Returns meta data as promise - returns a new Promise from meta data - uses `Promise.props()` to resolve `getClient()` and `getMetaData()` - Adds 'image-size' util The util returns an object like this ``` { height: 50, url: 'http://myblog.com/images/cat.jpg', width: 50 }; ``` if the dimensions can be fetched and rejects with error, if not. In case we get a locally stored image or a not complete url (like `//www.gravatar.com/andsoon`), we add the protocol to the incomplete one and use `urlFor()` to get the absolute URL. If the request fails or `image-size` is not able to read the file, we reject with error. - adds 'image-size' module to dependencies - adds `getImageSizeFromUrl` function that returns image dimensions - In preparation of AMP support and to improve our schema.org JSON-LD and structured data, I made the following changes: - Changes the following properties to be `Objects`, which have a `url` property by default and a `dimensions` property, if `width` and `height` are available: - `metaData.coverImage` - `metaData.authorImage` - `metaData.blog.logo` - Checks cache by calling `getCachedImageSizeFromUrl`. If image dimensions were fetched already, returns them from cache instead of fetching them again. - If we have image dimensions on hand, the output in our JSON-LD changes from normal urls to be full `ImageObjects`. Applies to all images and logos. - Special case for `publisher.logo` as it has size restrictions: if the image doesn't fulfil the restrictions (<=600 width and <=60 height), we simply output the url instead, so like before. - Adds new property for schema.org JSON-LD: `mainEntityOfPage` as an Object. - Adds additional Open Graph data (if we have the image size): `og:image:width` and `og:image:height` - Adds/updates tests * AMP router and controller (#7171, #7157) Implements AMP in `/apps/`: - renders `amp.hbs` if route is `/:slug/amp/` - updates `setResponseContext` to set context to `['amp', 'post']` for a amp post and `['amp', 'page']` for a page, but will not render amp template for a page - updates `context_spec` - registers 'amp' as new internal app - adds the `amp.hbs` template to `core/server/apps/amp` which will be the default template for AMP posts. - adds `isAmpURL` to `post-lookup` * 🎨 Use `context` in meta as array (#7205) Instead of reading the first value of the context array, we're checking if it includes certain context values. This is a preparation change for AMP, where the context will be delivered as `['amp', 'post']`. * ✨ AMP helpers (#7174, #7216, #7215, #7223) - Adds AMP helpers `{{amp_content}}`, `{{amp_component}}` and `{{amp_ghost_head}}` to support AMP: - `{{amp_content}}`: - Adds `Amperize` as dependency - AMP app uses new helper `{{amp_content}}` to render AMP HTML - `Amperize` transforms regular HTML into AMP HTML - Adds test for `{{amp_content}}` helper - Adds 'Sanitize-HTML` as dependendy - After the HTML get 'amperized' we still might have some HTML tags, which are prohibited in AMP HTML, so we use `sanitize-html` to remove those. With every update, `Amperize` gets and it is able to transform more HTML tags, they valid AMP HTML tags (e. g. `video` and `amp-video`) and will therefore not be removed. - `{{amp_ghost_head}}`: - registers `{{amp_ghost_head}}` helper, but uses `{{ghost_head}}` code - uses `{{amp_ghost_head}}` in `amp.hbs` instead of `{{ghost_head}}` - `{{ghost_head}}`: - Render `amphtml` link in metadata for post, which links to the amp post (`getAmpUrl`) - Updates all test in metadata to support `amp` context - Changes context conditionals to work with full array instead of first array value - Adds conditionals, so no additional javascript gets rendered in `{{ghost_head}}` - Removes trailing `/amp/` in URLs, so only `amphtml` link on regular post renders it - Adds a conditional, so no code injection will be included, for an `amp` context. - `{{amp_components}}`: - AMP app uses new helper `{{amp_components}}` to render necessary script tags for AMP extended components as `amp-iframe`, `amp-anime` and `amp-form` - Adds test for `{{amp_components}}`
2016-08-22 18:49:27 +02:00
internalApps: ['private-blogging', 'subscribers', 'amp'],
slugs: {
// Used by generateSlug to generate slugs for posts, tags, users, ..
// reserved slugs are reserved but can be extended/removed by apps
// protected slugs cannot be changed or removed
reserved: ['admin', 'app', 'apps', 'archive', 'archives', 'categories',
'category', 'dashboard', 'feed', 'ghost-admin', 'login', 'logout',
'page', 'pages', 'post', 'posts', 'public', 'register', 'setup',
'signin', 'signout', 'signup', 'user', 'users', 'wp-admin', 'wp-login'],
✨ [FEATURE] AMP⚡ (#7229) closes #6588, #7095 * `ImageObject` with image dimensions (#7152, #7151, #7153) - Returns meta data as promise - returns a new Promise from meta data - uses `Promise.props()` to resolve `getClient()` and `getMetaData()` - Adds 'image-size' util The util returns an object like this ``` { height: 50, url: 'http://myblog.com/images/cat.jpg', width: 50 }; ``` if the dimensions can be fetched and rejects with error, if not. In case we get a locally stored image or a not complete url (like `//www.gravatar.com/andsoon`), we add the protocol to the incomplete one and use `urlFor()` to get the absolute URL. If the request fails or `image-size` is not able to read the file, we reject with error. - adds 'image-size' module to dependencies - adds `getImageSizeFromUrl` function that returns image dimensions - In preparation of AMP support and to improve our schema.org JSON-LD and structured data, I made the following changes: - Changes the following properties to be `Objects`, which have a `url` property by default and a `dimensions` property, if `width` and `height` are available: - `metaData.coverImage` - `metaData.authorImage` - `metaData.blog.logo` - Checks cache by calling `getCachedImageSizeFromUrl`. If image dimensions were fetched already, returns them from cache instead of fetching them again. - If we have image dimensions on hand, the output in our JSON-LD changes from normal urls to be full `ImageObjects`. Applies to all images and logos. - Special case for `publisher.logo` as it has size restrictions: if the image doesn't fulfil the restrictions (<=600 width and <=60 height), we simply output the url instead, so like before. - Adds new property for schema.org JSON-LD: `mainEntityOfPage` as an Object. - Adds additional Open Graph data (if we have the image size): `og:image:width` and `og:image:height` - Adds/updates tests * AMP router and controller (#7171, #7157) Implements AMP in `/apps/`: - renders `amp.hbs` if route is `/:slug/amp/` - updates `setResponseContext` to set context to `['amp', 'post']` for a amp post and `['amp', 'page']` for a page, but will not render amp template for a page - updates `context_spec` - registers 'amp' as new internal app - adds the `amp.hbs` template to `core/server/apps/amp` which will be the default template for AMP posts. - adds `isAmpURL` to `post-lookup` * 🎨 Use `context` in meta as array (#7205) Instead of reading the first value of the context array, we're checking if it includes certain context values. This is a preparation change for AMP, where the context will be delivered as `['amp', 'post']`. * ✨ AMP helpers (#7174, #7216, #7215, #7223) - Adds AMP helpers `{{amp_content}}`, `{{amp_component}}` and `{{amp_ghost_head}}` to support AMP: - `{{amp_content}}`: - Adds `Amperize` as dependency - AMP app uses new helper `{{amp_content}}` to render AMP HTML - `Amperize` transforms regular HTML into AMP HTML - Adds test for `{{amp_content}}` helper - Adds 'Sanitize-HTML` as dependendy - After the HTML get 'amperized' we still might have some HTML tags, which are prohibited in AMP HTML, so we use `sanitize-html` to remove those. With every update, `Amperize` gets and it is able to transform more HTML tags, they valid AMP HTML tags (e. g. `video` and `amp-video`) and will therefore not be removed. - `{{amp_ghost_head}}`: - registers `{{amp_ghost_head}}` helper, but uses `{{ghost_head}}` code - uses `{{amp_ghost_head}}` in `amp.hbs` instead of `{{ghost_head}}` - `{{ghost_head}}`: - Render `amphtml` link in metadata for post, which links to the amp post (`getAmpUrl`) - Updates all test in metadata to support `amp` context - Changes context conditionals to work with full array instead of first array value - Adds conditionals, so no additional javascript gets rendered in `{{ghost_head}}` - Removes trailing `/amp/` in URLs, so only `amphtml` link on regular post renders it - Adds a conditional, so no code injection will be included, for an `amp` context. - `{{amp_components}}`: - AMP app uses new helper `{{amp_components}}` to render necessary script tags for AMP extended components as `amp-iframe`, `amp-anime` and `amp-form` - Adds test for `{{amp_components}}`
2016-08-22 18:49:27 +02:00
protected: ['ghost', 'rss', 'amp']
},
// used in middleware/validation/upload.js
// if we finish the data/importer logic, each type selects an importer
uploads: {
subscribers: {
extensions: ['.csv'],
contentTypes: ['text/csv', 'application/csv', 'application/octet-stream']
},
images: {
extensions: ['.jpg', '.jpeg', '.gif', '.png', '.svg', '.svgz'],
contentTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']
},
db: {
extensions: ['.json', '.zip'],
contentTypes: ['application/octet-stream', 'application/json', 'application/zip', 'application/x-zip-compressed']
},
themes: {
extensions: ['.zip'],
contentTypes: ['application/zip', 'application/x-zip-compressed']
}
},
deprecatedItems: ['updateCheck', 'mail.fromaddress'],
// create a hash for cache busting assets
assetHash: assetHash,
preloadHeaders: this._config.preloadHeaders || false,
times: {
cannotScheduleAPostBeforeInMinutes: 2,
publishAPostBySchedulerToleranceInMinutes: 2
}
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
});
// Also pass config object to
// configUrl object to maintain
// clean dependency tree
configUrl.setConfig(this._config);
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
// For now we're going to copy the current state of this._config
// so it's directly accessible on the instance.
// @TODO: perhaps not do this? Put access of the config object behind
// a function?
_.extend(this, this._config);
};
/**
* Allows you to read the config object.
* @return {Object} The config object.
*/
ConfigManager.prototype.get = function () {
return this._config;
};
ConfigManager.prototype.load = function (configFilePath) {
var self = this;
self._config.paths.config = process.env.GHOST_CONFIG || configFilePath || self._config.paths.config;
/* Check for config file and copy from config.example.js
if one doesn't exist. After that, start the server. */
return new Promise(function (resolve, reject) {
fs.stat(self._config.paths.config, function (err) {
var exists = (err) ? false : true,
pendingConfig;
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
if (!exists) {
pendingConfig = self.writeFile();
}
Promise.resolve(pendingConfig).then(function () {
return self.validate();
}).then(function (rawConfig) {
return self.init(rawConfig);
}).then(resolve)
.catch(reject);
});
Improve bootstrap flow of a Ghost application addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
2014-01-05 01:40:53 -05:00
});
};
/* Check for config file and copy from config.example.js
if one doesn't exist. After that, start the server. */
ConfigManager.prototype.writeFile = function () {
var configPath = this._config.paths.config,
configExamplePath = this._config.paths.configExample;
return new Promise(function (resolve, reject) {
fs.stat(configExamplePath, function checkTemplate(err) {
var templateExists = (err) ? false : true,
read,
write,
error;
if (!templateExists) {
error = new Error(i18n.t('errors.config.couldNotLocateConfigFile.error'));
error.context = appRoot;
error.help = i18n.t('errors.config.couldNotLocateConfigFile.help');
return reject(error);
}
// Copy config.example.js => config.js
read = fs.createReadStream(configExamplePath);
read.on('error', function (err) {
errors.logError(
new Error(i18n.t('errors.config.couldNotOpenForReading.error', {file: 'config.example.js'})),
appRoot,
i18n.t('errors.config.couldNotOpenForReading.help'));
reject(err);
});
write = fs.createWriteStream(configPath);
write.on('error', function (err) {
errors.logError(
new Error(i18n.t('errors.config.couldNotOpenForWriting.error', {file: 'config.js'})),
appRoot,
i18n.t('errors.config.couldNotOpenForWriting.help'));
reject(err);
});
write.on('finish', resolve);
read.pipe(write);
});
});
};
/**
* Read config.js file from file system using node's require
* @param {String} envVal Which environment we're in.
* @return {Object} The config object.
*/
ConfigManager.prototype.readFile = function (envVal) {
return require(this._config.paths.config)[envVal];
};
/**
* Validates the config object has everything we want and in the form we want.
* @return {Promise.<Object>} Returns a promise that resolves to the config object.
*/
ConfigManager.prototype.validate = function () {
var envVal = process.env.NODE_ENV || undefined,
hasHostAndPort,
hasSocket,
config,
parsedUrl;
try {
config = this.readFile(envVal);
}
catch (e) {
return Promise.reject(e);
}
// Check that our url is valid
if (!validator.isURL(config.url, {protocols: ['http', 'https'], require_protocol: true})) {
errors.logError(
new Error(i18n.t('errors.config.invalidUrlInConfig.description'),
config.url,
i18n.t('errors.config.invalidUrlInConfig.help')));
return Promise.reject(new Error(i18n.t('errors.config.invalidUrlInConfig.error')));
}
parsedUrl = url.parse(config.url || 'invalid', false, true);
if (/\/ghost(\/|$)/.test(parsedUrl.pathname)) {
errors.logError(
new Error(i18n.t('errors.config.urlCannotContainGhostSubdir.description'),
config.url,
i18n.t('errors.config.urlCannotContainGhostSubdir.help')));
return Promise.reject(new Error(i18n.t('errors.config.urlCannotContainGhostSubdir.error')));
}
// Check that we have database values
if (!config.database || !config.database.client) {
errors.logError(
new Error(i18n.t('errors.config.dbConfigInvalid.description')),
JSON.stringify(config.database),
i18n.t('errors.config.dbConfigInvalid.help'));
return Promise.reject(new Error(i18n.t('errors.config.dbConfigInvalid.error')));
}
hasHostAndPort = config.server && !!config.server.host && !!config.server.port;
hasSocket = config.server && !!config.server.socket;
// Check for valid server host and port values
if (!config.server || !(hasHostAndPort || hasSocket)) {
errors.logError(
new Error(i18n.t('errors.config.invalidServerValues.description')),
JSON.stringify(config.server),
i18n.t('errors.config.invalidServerValues.help'));
return Promise.reject(new Error(i18n.t('errors.config.invalidServerValues.error')));
}
return Promise.resolve(config);
};
/**
* Helper method for checking the state of a particular privacy flag
* @param {String} privacyFlag The flag to check
* @returns {boolean}
*/
ConfigManager.prototype.isPrivacyDisabled = function (privacyFlag) {
if (!this.privacy) {
return false;
}
if (this.privacy.useTinfoil === true) {
return true;
}
return this.privacy[privacyFlag] === false;
};
/**
* Check if any of the currently set config items are deprecated, and issues a warning.
*/
ConfigManager.prototype.checkDeprecated = function () {
var self = this;
_.each(this.deprecatedItems, function (property) {
self.displayDeprecated(self._config, property.split('.'), []);
});
};
ConfigManager.prototype.displayDeprecated = function (item, properties, address) {
var self = this,
property = properties.shift(),
errorText,
explanationText,
helpText;
address.push(property);
if (item.hasOwnProperty(property)) {
if (properties.length) {
return self.displayDeprecated(item[property], properties, address);
}
errorText = i18n.t('errors.config.deprecatedProperty.error', {property: chalk.bold(address.join('.'))});
explanationText = i18n.t('errors.config.deprecatedProperty.explanation');
helpText = i18n.t('errors.config.deprecatedProperty.help', {url: 'http://support.ghost.org/config'});
errors.logWarn(errorText, explanationText, helpText);
}
};
if (testingEnvs.indexOf(process.env.NODE_ENV) > -1) {
defaultConfig = require('../../../config.example')[process.env.NODE_ENV];
}
module.exports = new ConfigManager(defaultConfig);