Merge pull request #11212 from strapi/v4/internal-cleanup

V4/internal cleanup
This commit is contained in:
Alexandre BODIN 2021-10-08 12:01:01 +02:00 committed by GitHub
commit 2f5a5e02a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 425 additions and 92 deletions

View File

@ -1,9 +1,7 @@
'use strict';
const { prop } = require('lodash/fp');
const getService = name => {
return prop(`admin.services.${name}`, strapi);
return strapi.service(`admin::${name}`);
};
module.exports = {

View File

@ -208,4 +208,9 @@ program
.description('List all the application hooks')
.action(getLocalScript('hooks/list'));
program
.command('services:list')
.description('List all the application services')
.action(getLocalScript('services/list'));
program.parseAsync(process.argv);

View File

@ -70,6 +70,7 @@ class Strapi {
this.startupLogger = createStartupLogger(this);
this.log = createLogger(this.config.get('logger', {}));
this.cron = createCronService();
this.telemetry = createTelemetry(this);
createUpdateNotifier(this).notify();
}
@ -310,7 +311,7 @@ class Strapi {
this.hook('strapi::content-types.afterSync').register(draftAndPublishSync.enable);
}
async load() {
async register() {
await Promise.all([
this.loadApp(),
this.loadPlugins(),
@ -332,8 +333,14 @@ class Strapi {
this.registerInternalHooks();
this.telemetry.register();
await this.runLifecyclesFunctions(LIFECYCLES.REGISTER);
return this;
}
async bootstrap() {
const contentTypes = [
coreStoreModel,
webhookModel,
@ -360,7 +367,7 @@ class Strapi {
const cronTasks = this.config.get('server.cron.tasks', {});
this.cron.add(cronTasks);
this.telemetry = createTelemetry(this);
this.telemetry.bootstrap();
let oldContentTypes;
if (await this.db.connection.schema.hasTable(coreStoreModel.collectionName)) {
@ -399,6 +406,13 @@ class Strapi {
this.cron.start();
return this;
}
async load() {
await this.register();
await this.bootstrap();
this.isLoaded = true;
return this;

View File

@ -6,17 +6,15 @@ const chalk = require('chalk');
const strapi = require('../../index');
module.exports = async function() {
const app = await strapi().load();
const app = await strapi().register();
const list = app.contentTypes;
const list = app.container.get('content-types').keys();
const infoTable = new CLITable({
head: [chalk.blue('Name')],
});
Object.keys(list).forEach(name => {
infoTable.push([name]);
});
list.forEach(name => infoTable.push([name]));
console.log(infoTable.toString());

View File

@ -6,17 +6,15 @@ const chalk = require('chalk');
const strapi = require('../../index');
module.exports = async function() {
const app = await strapi().load();
const app = await strapi().register();
const list = app.hooks;
const list = app.container.get('hooks').keys();
const infoTable = new CLITable({
head: [chalk.blue('Name')],
});
Object.keys(list).forEach(name => {
infoTable.push([name]);
});
list.forEach(name => infoTable.push([name]));
console.log(infoTable.toString());

View File

@ -6,17 +6,15 @@ const chalk = require('chalk');
const strapi = require('../../index');
module.exports = async function() {
const app = await strapi().load();
const app = await strapi().register();
const list = app.middlewares;
const list = app.container.get('middlewares').keys();
const infoTable = new CLITable({
head: [chalk.blue('Name')],
});
Object.keys(list).forEach(name => {
infoTable.push([name]);
});
list.forEach(name => infoTable.push([name]));
console.log(infoTable.toString());

View File

@ -6,17 +6,15 @@ const chalk = require('chalk');
const strapi = require('../../index');
module.exports = async function() {
const app = await strapi().load();
const app = await strapi().register();
const list = app.policies;
const list = app.container.get('policies').keys();
const infoTable = new CLITable({
head: [chalk.blue('Name')],
});
Object.keys(list).forEach(name => {
infoTable.push([name]);
});
list.forEach(name => infoTable.push([name]));
console.log(infoTable.toString());

View File

@ -0,0 +1,22 @@
'use strict';
const CLITable = require('cli-table3');
const chalk = require('chalk');
const strapi = require('../../index');
module.exports = async function() {
const app = await strapi().register();
const list = app.container.get('services').keys();
const infoTable = new CLITable({
head: [chalk.blue('Name')],
});
list.forEach(name => infoTable.push([name]));
console.log(infoTable.toString());
await app.destroy();
};

View File

@ -20,32 +20,76 @@ const contentTypesRegistry = () => {
const contentTypes = {};
return {
get(ctUID) {
return contentTypes[ctUID];
/**
* Returns this list of registered contentTypes uids
* @returns {string[]}
*/
keys() {
return Object.keys(contentTypes);
},
/**
* Returns the instance of a contentType. Instantiate the contentType if not already done
* @param {string} uid
* @returns
*/
get(uid) {
return contentTypes[uid];
},
/**
* Returns a map with all the contentTypes in a namespace
* @param {string} namespace
*/
getAll(namespace) {
return pickBy((_, uid) => hasNamespace(uid, namespace))(contentTypes);
},
add(namespace, rawContentTypes) {
validateKeySameToSingularName(rawContentTypes);
for (const rawCtName in rawContentTypes) {
/**
* Registers a contentType
* @param {string} uid
* @param {Object} contentType
*/
set(uid, contentType) {
contentTypes[uid] = contentType;
return this;
},
/**
* Registers a map of contentTypes for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Object }} newContentTypes
*/
add(namespace, newContentTypes) {
validateKeySameToSingularName(newContentTypes);
for (const rawCtName in newContentTypes) {
const uid = addNamespace(rawCtName, namespace);
if (has(uid, contentTypes)) {
throw new Error(`Content-type ${uid} has already been registered.`);
}
contentTypes[uid] = createContentType(uid, rawContentTypes[rawCtName]);
contentTypes[uid] = createContentType(uid, newContentTypes[rawCtName]);
}
},
/**
* Wraps a contentType to extend it
* @param {string} uid
* @param {(contentType: Object) => Object} extendFn
*/
extend(ctUID, extendFn) {
const currentContentType = this.get(ctUID);
if (!currentContentType) {
throw new Error(`Content-Type ${ctUID} doesn't exist`);
}
const newContentType = extendFn(currentContentType);
contentTypes[ctUID] = newContentType;
return this;
},
};
};

View File

@ -0,0 +1,20 @@
type Handler = (context: any) => any;
type AsyncHook = {
handlers: Handler[];
register(handler: Handler): this;
delete(handler: Handler): this;
call(): Promise<void>;
};
type SyncHook = {
get handlers(): Handler[];
register(handler: Handler): this;
delete(handler: Handler): this;
call(): void;
};
export type Hook = AsyncHook|SyncHook

View File

@ -1,26 +1,58 @@
'use strict';
const { pickBy, has } = require('lodash/fp');
const { pickBy } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
/**
* @typedef {import('./hooks').Hook} Hook
*/
const hooksRegistry = () => {
const hooks = {};
return {
get(hookUID) {
return hooks[hookUID];
/**
* Returns this list of registered hooks uids
* @returns {string[]}
*/
keys() {
return Object.keys(hooks);
},
/**
* Returns the instance of a hook.
* @param {string} uid
* @returns {Hook}
*/
get(uid) {
return hooks[uid];
},
/**
* Returns a map with all the hooks in a namespace
* @param {string} namespace
* @returns {{ [key: string]: Hook }}
*/
getAll(namespace) {
return pickBy((_, uid) => hasNamespace(uid, namespace))(hooks);
},
set(uid, hook) {
if (has(uid, hooks)) {
throw new Error(`hook ${uid} has already been registered.`);
}
/**
* Registers a hook
* @param {string} uid
* @param {Hook} hook
*/
set(uid, hook) {
hooks[uid] = hook;
return this;
},
/**
* Registers a map of hooks for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Hook }} newHooks
* @returns
*/
add(namespace, hooks) {
for (const hookName in hooks) {
const hook = hooks[hookName];
@ -31,6 +63,24 @@ const hooksRegistry = () => {
return this;
},
/**
* Wraps a hook to extend it
* @param {string} uid
* @param {(hook: Hook) => Hook} extendFn
*/
extend(uid, extendFn) {
const currentHook = this.get(uid);
if (!currentHook) {
throw new Error(`Hook ${uid} doesn't exist`);
}
const newHook = extendFn(currentHook);
hooks[uid] = newHook;
return this;
},
};
};

View File

@ -0,0 +1,5 @@
import { BaseContext, Middleware as KoaMiddleware } from 'koa';
import { Strapi } from '../../';
import { MiddlewareFactory } from '../../middlewares';
export type Middleware = KoaMiddleware | MiddlewareFactory;

View File

@ -3,16 +3,57 @@
const { pickBy, has } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
/**
* @typedef {import('./middlewares').Middleware} Middleware
*/
// TODO: move instantiation part here instead of in the server service
const middlewaresRegistry = () => {
const middlewares = {};
return {
get(middlewareUID) {
return middlewares[middlewareUID];
/**
* Returns this list of registered middlewares uids
* @returns {string[]}
*/
keys() {
return Object.keys(middlewares);
},
/**
* Returns the instance of a middleware. Instantiate the middleware if not already done
* @param {string} uid
* @returns {Middleware}
*/
get(uid) {
return middlewares[uid];
},
/**
* Returns a map with all the middlewares in a namespace
* @param {string} namespace
* @returns {{ [key: string]: Middleware }}
*/
getAll(namespace) {
return pickBy((_, uid) => hasNamespace(uid, namespace))(middlewares);
},
/**
* Registers a middleware
* @param {string} uid
* @param {Middleware} middleware
*/
set(uid, middleware) {
middlewares[uid] = middleware;
return this;
},
/**
* Registers a map of middlewares for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Middleware }} newMiddlewares
* @returns
*/
add(namespace, rawMiddlewares) {
for (const middlewareName in rawMiddlewares) {
const middleware = rawMiddlewares[middlewareName];
@ -24,6 +65,24 @@ const middlewaresRegistry = () => {
middlewares[uid] = middleware;
}
},
/**
* Wraps a middleware to extend it
* @param {string} uid
* @param {(middleware: Middleware) => Middleware} extendFn
*/
extend(uid, extendFn) {
const currentMiddleware = this.get(uid);
if (!currentMiddleware) {
throw new Error(`Middleware ${uid} doesn't exist`);
}
const newMiddleware = extendFn(currentMiddleware);
middlewares[uid] = newMiddleware;
return this;
},
};
};

View File

@ -0,0 +1,9 @@
import { BaseContext } from 'koa';
import { Strapi } from '../../';
interface PolicyContext extends BaseContext {
type: string;
is(name): boolean;
}
export type Policy = (ctx: PolicyContext, { strapi: Strapi }) => boolean | undefined;

View File

@ -3,16 +3,57 @@
const { pickBy, has } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
/**
* @typedef {import('./policies').Policy} Policy
*/
// TODO: move instantiation part here instead of in the policy utils
const policiesRegistry = () => {
const policies = {};
return {
get(policyUID) {
return policies[policyUID];
/**
* Returns this list of registered policies uids
* @returns {string[]}
*/
keys() {
return Object.keys(policies);
},
/**
* Returns the instance of a policy. Instantiate the policy if not already done
* @param {string} uid
* @returns {Policy}
*/
get(uid) {
return policies[uid];
},
/**
* Returns a map with all the policies in a namespace
* @param {string} namespace
* @returns {{ [key: string]: Policy }}
*/
getAll(namespace) {
return pickBy((_, uid) => hasNamespace(uid, namespace))(policies);
},
/**
* Registers a policy
* @param {string} uid
* @param {Policy} policy
*/
set(uid, policy) {
policies[uid] = policy;
return this;
},
/**
* Registers a map of policies for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Policy }} newPolicies
* @returns
*/
add(namespace, newPolicies) {
for (const policyName in newPolicies) {
const policy = newPolicies[policyName];
@ -24,13 +65,23 @@ const policiesRegistry = () => {
policies[uid] = policy;
}
},
extend(policyUID, extendFn) {
const currentPolicy = this.get(policyUID);
/**
* Wraps a policy to extend it
* @param {string} uid
* @param {(policy: Policy) => Policy} extendFn
*/
extend(uid, extendFn) {
const currentPolicy = this.get(uid);
if (!currentPolicy) {
throw new Error(`Policy ${policyUID} doesn't exist`);
throw new Error(`Policy ${uid} doesn't exist`);
}
const newPolicy = extendFn(currentPolicy);
policies[policyUID] = newPolicy;
policies[uid] = newPolicy;
return this;
},
};
};

View File

@ -0,0 +1,7 @@
import { Strapi } from '../../';
export type Service = {
[key: string]: (...args: any) => any;
};
export type ServiceFactory = ({ strapi: Strapi }) => Service;

View File

@ -4,11 +4,29 @@ const _ = require('lodash');
const { pickBy, has } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
/**
* @typedef {import('./services').Service} Service
* @typedef {import('./services').ServiceFactory} ServiceFactory
*/
const servicesRegistry = strapi => {
const services = {};
const instantiatedServices = {};
return {
/**
* Returns this list of registered services uids
* @returns {string[]}
*/
keys() {
return Object.keys(services);
},
/**
* Returns the instance of a service. Instantiate the service if not already done
* @param {string} uid
* @returns {Service}
*/
get(uid) {
if (instantiatedServices[uid]) {
return instantiatedServices[uid];
@ -16,21 +34,40 @@ const servicesRegistry = strapi => {
const service = services[uid];
if (service) {
instantiatedServices[uid] = service({ strapi });
instantiatedServices[uid] = typeof service === 'function' ? service({ strapi }) : service;
return instantiatedServices[uid];
}
return undefined;
},
/**
* Returns a map with all the services in a namespace
* @param {string} namespace
* @returns {{ [key: string]: Service }}
*/
getAll(namespace) {
const filteredServices = pickBy((_, uid) => hasNamespace(uid, namespace))(services);
return _.mapValues(filteredServices, (service, serviceUID) => this.get(serviceUID));
},
set(uid, value) {
instantiatedServices[uid] = value;
/**
* Registers a service
* @param {string} uid
* @param {Service|ServiceFactory} service
*/
set(uid, service) {
services[uid] = service;
return this;
},
/**
* Registers a map of services for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Service|ServiceFactory }} newServices
* @returns
*/
add(namespace, newServices) {
for (const serviceName in newServices) {
const service = newServices[serviceName];
@ -44,13 +81,23 @@ const servicesRegistry = strapi => {
return this;
},
extend(serviceUID, extendFn) {
const currentService = this.get(serviceUID);
/**
* Wraps a service to extend it
* @param {string} uid
* @param {(service: Service) => Service} extendFn
*/
extend(uid, extendFn) {
const currentService = this.get(uid);
if (!currentService) {
throw new Error(`Service ${serviceUID} doesn't exist`);
throw new Error(`Service ${uid} doesn't exist`);
}
const newService = extendFn(currentService);
instantiatedServices[serviceUID] = newService;
instantiatedServices[uid] = newService;
return this;
},
};
};

View File

@ -1,4 +1,4 @@
import { Strapi } from '../';
import { Middleware } from 'koa';
export type MiddlewareFactory = (options: any, ctx: { strapi: Strapi }) => Middleware | null;
export type MiddlewareFactory = (config: any, ctx: { strapi: Strapi }) => Middleware | null;

View File

@ -24,7 +24,7 @@ describe('metrics', () => {
server: {
use,
},
});
}).register();
expect(use).toHaveBeenCalled();
});
@ -46,7 +46,7 @@ describe('metrics', () => {
server: {
use,
},
});
}).register();
expect(use).not.toHaveBeenCalled();
});
@ -80,6 +80,7 @@ describe('metrics', () => {
projectType: 'Community',
},
});
fetch.mockClear();
});

View File

@ -30,42 +30,44 @@ const createTelemetryInstance = strapi => {
const sender = createSender(strapi);
const sendEvent = wrapWithRateLimit(sender, { limitedEvents: LIMITED_EVENTS });
if (!isDisabled) {
const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
crons.push(pingCron);
strapi.server.use(createMiddleware({ sendEvent }));
}
if (strapi.EE === true && ee.isEE === true) {
const pingDisabled =
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
const sendLicenseCheck = () => {
return sendEvent(
'didCheckLicense',
{
licenseInfo: {
...ee.licenseInfo,
projectHash: hashProject(strapi),
dependencyHash: hashDep(strapi),
},
},
{
headers: { 'x-strapi-project': 'enterprise' },
}
);
};
if (!pingDisabled) {
const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
crons.push(licenseCron);
sendLicenseCheck();
}
}
return {
register() {
if (!isDisabled) {
const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
crons.push(pingCron);
strapi.server.use(createMiddleware({ sendEvent }));
}
},
bootstrap() {
if (strapi.EE === true && ee.isEE === true) {
const pingDisabled =
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
const sendLicenseCheck = () => {
return sendEvent(
'didCheckLicense',
{
licenseInfo: {
...ee.licenseInfo,
projectHash: hashProject(strapi),
dependencyHash: hashDep(strapi),
},
},
{
headers: { 'x-strapi-project': 'enterprise' },
}
);
};
if (!pingDisabled) {
const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
crons.push(licenseCron);
sendLicenseCheck();
}
}
},
destroy() {
// clear open handles
crons.forEach(cron => cron.cancel());

View File

@ -82,7 +82,7 @@ const createAsyncSeriesWaterfallHook = () => ({
const createAsyncParallelHook = () => ({
...createHook(),
call(context) {
async call(context) {
const promises = this.handlers.map(handler => handler(cloneDeep(context)));
return Promise.all(promises);

View File

@ -2,6 +2,7 @@
const _ = require('lodash');
// TODO: remove this tmp fix and migrate tests
let strapiInstance;
Object.defineProperty(global, 'strapi', {
get() {
@ -17,5 +18,11 @@ Object.defineProperty(global, 'strapi', {
plugin.contentType = name => plugin.contentTypes[name];
plugin.policy = name => plugin.policies[name];
});
strapiInstance.service = name => {
if (name.startsWith('admin::')) {
return strapiInstance.admin.services[name.split('admin::')[1]];
}
};
},
});