420 lines
10 KiB
JavaScript
Raw Normal View History

2015-10-01 00:30:16 +02:00
'use strict';
2018-05-04 17:02:27 +02:00
const Koa = require('koa');
2019-08-21 11:05:33 +02:00
const Router = require('koa-router');
2019-04-05 16:11:09 +02:00
const _ = require('lodash');
const { createLogger } = require('@strapi/logger');
2021-05-10 15:36:09 +02:00
const { Database } = require('@strapi/database');
2021-08-03 09:12:58 +02:00
const loadConfiguration = require('./core/app-configuration');
2021-08-05 11:32:56 +02:00
const { createHTTPServer } = require('./server');
2021-08-05 12:24:48 +02:00
const { createContainer } = require('./container');
2021-08-05 11:32:56 +02:00
const utils = require('./utils');
2021-07-08 11:20:13 +02:00
const initializeMiddlewares = require('./middlewares');
2021-08-23 22:12:16 +02:00
const createStrapiFs = require('./services/fs');
2019-12-17 10:35:04 +01:00
const createEventHub = require('./services/event-hub');
2019-12-17 11:24:14 +01:00
const createWebhookRunner = require('./services/webhook-runner');
2021-07-08 11:20:13 +02:00
const { webhookModel, createWebhookStore } = require('./services/webhook-store');
const { createCoreStore, coreStoreModel } = require('./services/core-store');
const createEntityService = require('./services/entity-service');
const entityValidator = require('./services/entity-validator');
const createTelemetry = require('./services/metrics');
const createUpdateNotifier = require('./utils/update-notifier');
2021-08-03 09:12:58 +02:00
const createStartupLogger = require('./utils/startup-logger');
const ee = require('./utils/ee');
2021-08-06 18:09:49 +02:00
const contentTypesRegistry = require('./core/registries/content-types');
const servicesRegistry = require('./core/registries/services');
const policiesRegistry = require('./core/registries/policies');
const middlewaresRegistry = require('./core/registries/middlewares');
const controllersRegistry = require('./core/registries/controllers');
const modulesRegistry = require('./core/registries/modules');
2021-08-20 13:12:37 +02:00
const pluginsRegistry = require('./core/registries/plugins');
2021-08-06 18:09:49 +02:00
const createConfigProvider = require('./core/registries/config');
2021-08-24 17:56:49 +02:00
const bootstrap = require('./core/bootstrap');
const loaders = require('./core/loaders');
2019-12-17 10:35:04 +01:00
2021-02-16 12:23:51 +01:00
const LIFECYCLES = {
REGISTER: 'register',
BOOTSTRAP: 'bootstrap',
2021-02-16 12:23:51 +01:00
};
class Strapi {
2020-01-10 12:25:41 +01:00
constructor(opts = {}) {
2021-08-05 11:32:56 +02:00
this.dir = opts.dir || process.cwd();
2021-08-06 18:09:49 +02:00
const appConfig = loadConfiguration(this.dir, opts);
this.container = createContainer(this);
this.container.register('config', createConfigProvider(appConfig));
this.container.register('content-types', contentTypesRegistry(this));
this.container.register('services', servicesRegistry(this));
this.container.register('policies', policiesRegistry(this));
this.container.register('middlewares', middlewaresRegistry(this));
this.container.register('controllers', controllersRegistry(this));
this.container.register('modules', modulesRegistry(this));
2021-08-20 13:12:37 +02:00
this.container.register('plugins', pluginsRegistry(this));
2021-08-05 11:32:56 +02:00
2021-08-06 18:09:49 +02:00
this.isLoaded = false;
2017-08-02 13:17:40 +02:00
this.reload = this.reload();
2016-11-04 16:00:19 +01:00
this.app = new Koa();
2019-08-21 11:05:33 +02:00
this.router = new Router();
2021-08-05 11:32:56 +02:00
this.server = createHTTPServer(this, this.app);
2019-12-17 10:35:04 +01:00
this.fs = createStrapiFs(this);
this.eventHub = createEventHub();
2021-08-03 09:12:58 +02:00
this.startupLogger = createStartupLogger(this);
2021-08-06 18:09:49 +02:00
this.app.proxy = this.config.get('server.proxy');
this.log = createLogger(this.config.get('logger', {}));
createUpdateNotifier(this).notify();
2019-12-17 10:35:04 +01:00
}
2021-08-06 18:09:49 +02:00
get config() {
return this.container.get('config');
}
get EE() {
return ee({ dir: this.dir, logger: this.log });
}
2021-08-11 10:05:20 +02:00
service(uid) {
return this.container.get('services').get(uid);
}
2021-08-23 21:50:13 +02:00
controller(uid) {
return this.container.get('controllers').get(uid);
}
contentType(name) {
return this.container.get('content-types').get(name);
2021-08-23 21:50:13 +02:00
}
get contentTypes() {
return this.container.get('content-types').getAll();
2021-08-23 21:50:13 +02:00
}
2021-08-24 17:56:49 +02:00
policy(name) {
return this.container.get('policies').get(name);
}
middleware(name) {
return this.container.get('middlewares').get(name);
}
2021-08-06 18:09:49 +02:00
plugin(name) {
2021-08-20 13:12:37 +02:00
return this.container.get('plugins').get(name);
2021-08-06 18:09:49 +02:00
}
2021-08-20 13:12:37 +02:00
get plugins() {
return this.container.get('plugins').getAll();
2021-08-06 18:09:49 +02:00
}
2021-08-09 20:13:05 +02:00
async start() {
try {
if (!this.isLoaded) {
await this.load();
}
this.app.use(this.router.routes()).use(this.router.allowedMethods());
// Launch server.
2021-08-09 20:13:05 +02:00
await this.listen();
return this;
} catch (error) {
2021-08-17 19:28:10 +02:00
return this.stopWithError(error);
}
}
async destroy() {
2021-08-09 20:13:05 +02:00
await this.server.destroy();
2020-12-29 17:44:14 +01:00
await Promise.all(
Object.values(this.plugins).map(plugin => {
if (_.has(plugin, 'destroy') && typeof plugin.destroy === 'function') {
return plugin.destroy();
}
})
);
if (_.has(this, 'admin')) {
await this.admin.destroy();
}
this.eventHub.removeAllListeners();
if (_.has(this, 'db')) {
await this.db.destroy();
}
this.telemetry.destroy();
delete global.strapi;
}
2021-08-09 20:13:05 +02:00
sendStartupTelemetry() {
// Get database clients
const databaseClients = _.map(this.config.get('connections'), _.property('settings.client'));
// Emit started event.
// do not await to avoid slower startup
this.telemetry.send('didStartServer', {
database: databaseClients,
plugins: this.config.installedPlugins,
providers: this.config.installedProviders,
});
}
async openAdmin({ isInitialized }) {
const shouldOpenAdmin =
this.config.environment === 'development' &&
this.config.get('server.admin.autoOpen', true) !== false;
if (shouldOpenAdmin || !isInitialized) {
await utils.openBrowser(this.config);
}
}
async postListen() {
const isInitialized = await utils.isInitialized(this);
this.startupLogger.logStartupMessage({ isInitialized });
this.sendStartupTelemetry();
this.openAdmin({ isInitialized });
}
/**
* Add behaviors to the server
*/
2021-08-09 20:13:05 +02:00
async listen() {
return new Promise((resolve, reject) => {
const onListen = async error => {
if (error) {
return reject(error);
}
2021-08-09 20:13:05 +02:00
try {
await this.postListen();
2021-08-09 20:13:05 +02:00
resolve();
} catch (error) {
reject(error);
}
};
2021-08-09 20:13:05 +02:00
const listenSocket = this.config.get('server.socket');
2021-08-03 09:12:58 +02:00
2021-08-09 20:13:05 +02:00
if (listenSocket) {
return this.server.listen(listenSocket, onListen);
2021-08-03 09:12:58 +02:00
}
2021-08-09 20:13:05 +02:00
const { host, port } = this.config.get('server');
return this.server.listen(port, host, onListen);
});
2016-07-26 11:57:50 +02:00
}
stopWithError(err, customMessage) {
2019-04-05 16:11:09 +02:00
this.log.debug(`⛔️ Server wasn't able to start properly.`);
if (customMessage) {
this.log.error(customMessage);
}
2021-06-17 16:17:15 +02:00
2019-04-05 16:11:09 +02:00
this.log.error(err);
return this.stop();
}
stop(exitCode = 1) {
2021-08-05 11:32:56 +02:00
this.server.destroy();
2017-09-04 15:38:29 +02:00
2021-07-08 11:20:13 +02:00
if (this.config.get('autoReload')) {
process.send('stop');
}
2017-09-04 15:38:29 +02:00
// Kill process
process.exit(exitCode);
2016-07-26 11:57:50 +02:00
}
2021-08-24 17:56:49 +02:00
async loadAdmin() {
await loaders.loadAdmin(this);
}
2021-08-03 09:12:58 +02:00
2021-08-24 17:56:49 +02:00
async loadPlugins() {
await loaders.loadPlugins(this);
}
2021-08-23 21:50:13 +02:00
2021-08-24 17:56:49 +02:00
async loadPolicies() {
await loaders.loadPolicies(this);
2021-08-03 09:12:58 +02:00
}
2021-08-24 17:56:49 +02:00
async loadAPIs() {
this.api = await loaders.loadAPIs(this);
}
2021-08-20 13:12:37 +02:00
2021-08-24 17:56:49 +02:00
async loadComponents() {
this.components = await loaders.loadComponents(this);
}
async loadMiddlewares() {
this.middleware = await loaders.loadMiddlewares(this);
2021-08-20 13:12:37 +02:00
}
2017-07-24 19:58:03 +02:00
async load() {
this.app.use(async (ctx, next) => {
if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) {
ctx.set('strapi', 'You are so French!');
ctx.status = 204;
2017-09-06 11:06:18 +02:00
} else {
await next();
}
});
2021-08-24 17:56:49 +02:00
await Promise.all([
this.loadPlugins(),
this.loadAdmin(),
this.loadAPIs(),
this.loadComponents(),
this.loadMiddlewares(),
this.loadPolicies(),
]);
2021-07-08 11:20:13 +02:00
await bootstrap(this);
2020-01-10 12:42:57 +01:00
// init webhook runner
this.webhookRunner = createWebhookRunner({
eventHub: this.eventHub,
logger: this.log,
configuration: this.config.get('server.webhooks', {}),
2020-01-10 12:42:57 +01:00
});
2021-02-16 12:23:51 +01:00
await this.runLifecyclesFunctions(LIFECYCLES.REGISTER);
2021-07-19 16:47:24 +02:00
2021-06-17 16:17:15 +02:00
const contentTypes = [
2021-05-17 16:34:19 +02:00
coreStoreModel,
webhookModel,
2021-07-19 16:47:24 +02:00
...Object.values(strapi.contentTypes),
2021-05-17 16:34:19 +02:00
...Object.values(strapi.components),
];
2021-06-28 12:34:29 +02:00
this.db = await Database.init({
2021-05-17 16:34:19 +02:00
...this.config.get('database'),
2021-06-17 16:17:15 +02:00
models: Database.transformContentTypes(contentTypes),
2021-05-17 16:34:19 +02:00
});
2021-02-12 12:52:29 +01:00
2021-07-05 18:35:16 +02:00
await this.db.schema.sync();
2019-09-20 12:44:24 +02:00
2021-07-08 11:20:13 +02:00
this.store = createCoreStore({
environment: this.config.environment,
db: this.db,
});
this.webhookStore = createWebhookStore({ db: this.db });
2019-12-17 11:24:14 +01:00
2021-07-08 11:20:13 +02:00
await this.startWebhooks();
2019-12-17 20:59:57 +01:00
this.entityValidator = entityValidator;
this.entityService = createEntityService({
2021-07-19 19:36:40 +02:00
strapi: this,
db: this.db,
eventHub: this.eventHub,
entityValidator: this.entityValidator,
});
this.telemetry = createTelemetry(this);
2021-08-09 17:33:02 +02:00
// Initialize middlewares.
2021-07-08 11:20:13 +02:00
await initializeMiddlewares.call(this);
2021-02-16 12:23:51 +01:00
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
2021-07-08 11:20:13 +02:00
this.isLoaded = true;
return this;
2016-07-26 11:57:50 +02:00
}
2021-07-08 11:20:13 +02:00
async startWebhooks() {
const webhooks = await this.webhookStore.findWebhooks();
webhooks.forEach(webhook => this.webhookRunner.add(webhook));
}
2019-12-17 20:59:57 +01:00
2017-08-02 11:25:18 +02:00
reload() {
2018-03-28 20:13:09 +02:00
const state = {
shouldReload: 0,
2018-03-28 20:13:09 +02:00
};
const reload = function() {
2018-08-31 13:47:10 +02:00
if (state.shouldReload > 0) {
// Reset the reloading state
2018-08-31 13:47:10 +02:00
state.shouldReload -= 1;
reload.isReloading = false;
2018-03-28 20:13:09 +02:00
return;
}
2021-08-13 15:35:19 +02:00
if (this.config.get('autoReload')) {
2021-08-04 20:13:35 +02:00
this.server.destroy();
2018-01-04 16:03:34 +01:00
process.send('reload');
}
2017-08-02 11:25:18 +02:00
};
2018-03-28 20:13:09 +02:00
Object.defineProperty(reload, 'isWatching', {
configurable: true,
enumerable: true,
2018-05-18 14:22:24 +02:00
set: value => {
2018-03-28 20:13:09 +02:00
// Special state when the reloader is disabled temporarly (see GraphQL plugin example).
2018-08-31 13:47:10 +02:00
if (state.isWatching === false && value === true) {
state.shouldReload += 1;
}
2018-03-28 20:13:09 +02:00
state.isWatching = value;
},
get: () => {
return state.isWatching;
2018-05-18 14:22:24 +02:00
},
2018-03-28 20:13:09 +02:00
});
2017-08-02 11:25:18 +02:00
reload.isReloading = false;
reload.isWatching = true;
return reload;
2016-07-26 11:57:50 +02:00
}
2021-02-16 12:23:51 +01:00
async runLifecyclesFunctions(lifecycleName) {
const execLifecycle = async fn => {
if (!fn) {
return;
}
2019-08-12 15:35:40 +02:00
return fn();
};
2019-08-12 15:35:40 +02:00
2021-02-16 12:23:51 +01:00
const configPath = `functions.${lifecycleName}`;
2021-02-15 11:38:50 +01:00
2021-02-16 12:23:51 +01:00
// plugins
2021-08-20 15:23:02 +02:00
await this.container.get('modules')[lifecycleName]();
2019-04-05 16:11:09 +02:00
2021-02-16 12:23:51 +01:00
// user
2021-07-08 11:20:13 +02:00
await execLifecycle(this.config.get(configPath));
2021-02-16 12:23:51 +01:00
// admin
await this.admin[lifecycleName]();
2016-07-26 11:57:50 +02:00
}
2021-06-28 22:37:19 +02:00
getModel(uid) {
2021-07-01 13:52:01 +02:00
return this.contentTypes[uid] || this.components[uid];
}
2019-05-16 21:37:45 +02:00
/**
* Binds queries with a specific model
2021-06-30 20:00:03 +02:00
* @param {string} uid
* @returns {}
2019-05-16 21:37:45 +02:00
*/
2021-06-30 20:00:03 +02:00
query(uid) {
return this.db.query(uid);
}
2016-07-26 11:57:50 +02:00
}
2019-04-11 09:32:16 +02:00
module.exports = options => {
2021-07-20 12:12:30 +02:00
const strapi = new Strapi(options);
2019-04-11 09:32:16 +02:00
global.strapi = strapi;
return strapi;
};
2021-06-30 20:00:03 +02:00
module.exports.Strapi = Strapi;