mirror of
https://github.com/strapi/strapi.git
synced 2025-12-26 14:44:31 +00:00
chore: introduce providers to simplify main class
This commit is contained in:
parent
eb61511884
commit
55ae34e5dc
@ -531,14 +531,6 @@ TODO
|
||||
TODO
|
||||
:::
|
||||
|
||||
### `strapi.startWebhooks()`
|
||||
|
||||
- Returns: Promise
|
||||
|
||||
:::info
|
||||
TODO
|
||||
:::
|
||||
|
||||
### `strapi.reload()`
|
||||
|
||||
:::info
|
||||
|
||||
@ -60,6 +60,10 @@ const containerMock = {
|
||||
switch (container) {
|
||||
case 'content-types':
|
||||
return contentTypesContainer;
|
||||
case 'webhookStore':
|
||||
return {
|
||||
addAllowedEvent: jest.fn(),
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -91,9 +95,6 @@ const strapiMock = {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
webhookStore: {
|
||||
addAllowedEvent: jest.fn(),
|
||||
},
|
||||
} as unknown as Core.Strapi;
|
||||
|
||||
const reviewWorkflowsService = reviewWorkflowsServiceFactory({ strapi: strapiMock });
|
||||
|
||||
@ -100,7 +100,7 @@ function persistStagesJoinTables({ strapi }: { strapi: Core.Strapi }) {
|
||||
|
||||
const registerWebhookEvents = async ({ strapi }: { strapi: Core.Strapi }) =>
|
||||
Object.entries(webhookEvents).forEach(([eventKey, event]) =>
|
||||
strapi.webhookStore.addAllowedEvent(eventKey, event)
|
||||
strapi.get('webhookStore').addAllowedEvent(eventKey, event)
|
||||
);
|
||||
|
||||
export default ({ strapi }: { strapi: Core.Strapi }) => {
|
||||
|
||||
@ -46,13 +46,13 @@ const updateWebhookValidator = webhookValidator.shape({
|
||||
|
||||
export default {
|
||||
async listWebhooks(ctx: Context) {
|
||||
const webhooks = await strapi.webhookStore.findWebhooks();
|
||||
const webhooks = await strapi.get('webhookStore').findWebhooks();
|
||||
ctx.send({ data: webhooks } satisfies GetWebhooks.Response);
|
||||
},
|
||||
|
||||
async getWebhook(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||
const webhook = await strapi.get('webhookStore').findWebhook(id);
|
||||
|
||||
if (!webhook) {
|
||||
return ctx.notFound('webhook.notFound');
|
||||
@ -66,9 +66,9 @@ export default {
|
||||
|
||||
await validateYupSchema(webhookValidator)(body);
|
||||
|
||||
const webhook = await strapi.webhookStore.createWebhook(body);
|
||||
const webhook = await strapi.get('webhookStore').createWebhook(body);
|
||||
|
||||
strapi.webhookRunner.add(webhook);
|
||||
strapi.get('webhookRunner').add(webhook);
|
||||
|
||||
ctx.created({ data: webhook } satisfies CreateWebhook.Response);
|
||||
},
|
||||
@ -79,13 +79,13 @@ export default {
|
||||
|
||||
await validateYupSchema(updateWebhookValidator)(body);
|
||||
|
||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||
const webhook = await strapi.get('webhookStore').findWebhook(id);
|
||||
|
||||
if (!webhook) {
|
||||
return ctx.notFound('webhook.notFound');
|
||||
}
|
||||
|
||||
const updatedWebhook = await strapi.webhookStore.updateWebhook(id, {
|
||||
const updatedWebhook = await strapi.get('webhookStore').updateWebhook(id, {
|
||||
...webhook,
|
||||
...body,
|
||||
});
|
||||
@ -94,22 +94,22 @@ export default {
|
||||
return ctx.notFound('webhook.notFound');
|
||||
}
|
||||
|
||||
strapi.webhookRunner.update(updatedWebhook);
|
||||
strapi.get('webhookRunner').update(updatedWebhook);
|
||||
|
||||
ctx.send({ data: updatedWebhook } satisfies UpdateWebhook.Response);
|
||||
},
|
||||
|
||||
async deleteWebhook(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||
const webhook = await strapi.get('webhookStore').findWebhook(id);
|
||||
|
||||
if (!webhook) {
|
||||
return ctx.notFound('webhook.notFound');
|
||||
}
|
||||
|
||||
await strapi.webhookStore.deleteWebhook(id);
|
||||
await strapi.get('webhookStore').deleteWebhook(id);
|
||||
|
||||
strapi.webhookRunner.remove(webhook);
|
||||
strapi.get('webhookRunner').remove(webhook);
|
||||
|
||||
ctx.body = { data: webhook } satisfies DeleteWebhook.Response;
|
||||
},
|
||||
@ -122,11 +122,11 @@ export default {
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||
const webhook = await strapi.get('webhookStore').findWebhook(id);
|
||||
|
||||
if (webhook) {
|
||||
await strapi.webhookStore.deleteWebhook(id);
|
||||
strapi.webhookRunner.remove(webhook);
|
||||
await strapi.get('webhookStore').deleteWebhook(id);
|
||||
strapi.get('webhookRunner').remove(webhook);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,13 +136,11 @@ export default {
|
||||
async triggerWebhook(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
|
||||
const webhook = await strapi.webhookStore.findWebhook(id);
|
||||
const webhook = await strapi.get('webhookStore').findWebhook(id);
|
||||
|
||||
const response = await strapi.webhookRunner.run(
|
||||
webhook as Modules.WebhookStore.Webhook,
|
||||
'trigger-test',
|
||||
{}
|
||||
);
|
||||
const response = await strapi
|
||||
.get('webhookRunner')
|
||||
.run(webhook as Modules.WebhookStore.Webhook, 'trigger-test', {});
|
||||
|
||||
ctx.body = { data: response } satisfies TriggerWebhook.Response;
|
||||
},
|
||||
|
||||
@ -4,7 +4,7 @@ import history from './history';
|
||||
|
||||
export default async () => {
|
||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||
strapi.webhookStore.addAllowedEvent(key, value);
|
||||
strapi.get('webhookStore').addAllowedEvent(key, value);
|
||||
});
|
||||
|
||||
getService('field-sizes').setCustomFieldInputSizes();
|
||||
|
||||
@ -21,8 +21,11 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => {
|
||||
const query = strapi.db.query(HISTORY_VERSION_UID);
|
||||
|
||||
const getRetentionDays = (strapi: Core.Strapi) => {
|
||||
const featureConfig = strapi.ee.features.get('cms-content-history');
|
||||
|
||||
const licenseRetentionDays =
|
||||
strapi.ee.features.get('cms-content-history')?.options.retentionDays;
|
||||
typeof featureConfig === 'object' && featureConfig?.options.retentionDays;
|
||||
|
||||
const userRetentionDays: number = strapi.config.get('admin.history.retentionDays');
|
||||
|
||||
// Allow users to override the license retention days, but not to increase it
|
||||
|
||||
@ -154,7 +154,7 @@ export const bootstrap = async ({ strapi }: { strapi: Core.Strapi }) => {
|
||||
});
|
||||
|
||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||
strapi.webhookStore.addAllowedEvent(key, value);
|
||||
strapi.get('webhookStore').addAllowedEvent(key, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -60,8 +60,10 @@ const createReleaseValidationService = ({ strapi }: { strapi: Core.Strapi }) =>
|
||||
},
|
||||
async validatePendingReleasesLimit() {
|
||||
// Use the maximum releases option if it exists, otherwise default to 3
|
||||
const featureCfg = strapi.ee.features.get('cms-content-releases');
|
||||
|
||||
const maximumPendingReleases =
|
||||
strapi.ee.features.get('cms-content-releases')?.options?.maximumReleases || 3;
|
||||
(typeof featureCfg === 'object' && featureCfg?.options?.maximumReleases) || 3;
|
||||
|
||||
const [, pendingReleasesCount] = await strapi.db.query(RELEASE_MODEL_UID).findWithCount({
|
||||
filters: {
|
||||
|
||||
@ -3,30 +3,25 @@ import _ from 'lodash';
|
||||
import { isFunction } from 'lodash/fp';
|
||||
import { Logger, createLogger } from '@strapi/logger';
|
||||
import { Database } from '@strapi/database';
|
||||
import { hooks } from '@strapi/utils';
|
||||
|
||||
import type { Core, Modules, UID, Schema } from '@strapi/types';
|
||||
|
||||
import loadConfiguration from './configuration';
|
||||
import { loadConfiguration } from './configuration';
|
||||
|
||||
import * as factories from './factories';
|
||||
|
||||
import * as utils from './utils';
|
||||
import * as registries from './registries';
|
||||
import * as loaders from './loaders';
|
||||
import { Container } from './container';
|
||||
import createStrapiFs from './services/fs';
|
||||
import createEventHub from './services/event-hub';
|
||||
import { createServer } from './services/server';
|
||||
import createWebhookRunner, { WebhookRunner } from './services/webhook-runner';
|
||||
import { webhookModel, createWebhookStore } from './services/webhook-store';
|
||||
import { createCoreStore, coreStoreModel } from './services/core-store';
|
||||
import { createReloader } from './services/reloader';
|
||||
|
||||
import { providers } from './providers';
|
||||
import createEntityService from './services/entity-service';
|
||||
import createQueryParamService from './services/query-params';
|
||||
|
||||
import createCronService from './services/cron';
|
||||
import entityValidator from './services/entity-validator';
|
||||
import createTelemetry from './services/metrics';
|
||||
import requestContext from './services/request-context';
|
||||
import createAuth from './services/auth';
|
||||
import createCustomFields from './services/custom-fields';
|
||||
@ -35,225 +30,115 @@ import getNumberOfDynamicZones from './services/utils/dynamic-zones';
|
||||
import { FeaturesService, createFeaturesService } from './services/features';
|
||||
import { createDocumentService } from './services/document-service';
|
||||
|
||||
// TODO: move somewhere else
|
||||
import * as draftAndPublishSync from './migrations/draft-publish';
|
||||
import { coreStoreModel } from './services/core-store';
|
||||
import { createConfigProvider } from './services/config';
|
||||
|
||||
/**
|
||||
* Resolve the working directories based on the instance options.
|
||||
*
|
||||
* Behavior:
|
||||
* - `appDir` is the directory where Strapi will write every file (schemas, generated APIs, controllers or services)
|
||||
* - `distDir` is the directory where Strapi will read configurations, schemas and any compiled code
|
||||
*
|
||||
* Default values:
|
||||
* - If `appDir` is `undefined`, it'll be set to `process.cwd()`
|
||||
* - If `distDir` is `undefined`, it'll be set to `appDir`
|
||||
*/
|
||||
const resolveWorkingDirectories = (opts: { appDir?: string; distDir?: string }) => {
|
||||
const cwd = process.cwd();
|
||||
class Strapi extends Container implements Core.Strapi {
|
||||
app: any;
|
||||
|
||||
const appDir = opts.appDir ? path.resolve(cwd, opts.appDir) : cwd;
|
||||
const distDir = opts.distDir ? path.resolve(cwd, opts.distDir) : appDir;
|
||||
isLoaded: boolean = false;
|
||||
|
||||
return { app: appDir, dist: distDir };
|
||||
};
|
||||
internal_config: Record<string, unknown> = {};
|
||||
|
||||
const reloader = (strapi: Strapi) => {
|
||||
const state = {
|
||||
shouldReload: 0,
|
||||
isWatching: true,
|
||||
};
|
||||
constructor(opts: StrapiOptions) {
|
||||
super();
|
||||
|
||||
function reload() {
|
||||
if (state.shouldReload > 0) {
|
||||
// Reset the reloading state
|
||||
state.shouldReload -= 1;
|
||||
reload.isReloading = false;
|
||||
return;
|
||||
}
|
||||
this.internal_config = loadConfiguration(opts);
|
||||
|
||||
if (strapi.config.get('autoReload')) {
|
||||
process.send?.('reload');
|
||||
this.registerInternalServices();
|
||||
|
||||
for (const provider of providers) {
|
||||
provider.init?.(this);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(reload, 'isWatching', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
set(value) {
|
||||
// Special state when the reloader is disabled temporarly (see GraphQL plugin example).
|
||||
if (state.isWatching === false && value === true) {
|
||||
state.shouldReload += 1;
|
||||
}
|
||||
state.isWatching = value;
|
||||
},
|
||||
get() {
|
||||
return state.isWatching;
|
||||
},
|
||||
});
|
||||
get admin(): Core.Module {
|
||||
return this.get('admin');
|
||||
}
|
||||
|
||||
reload.isReloading = false;
|
||||
reload.isWatching = true;
|
||||
get EE(): boolean {
|
||||
// @ts-expect-error: init is private
|
||||
this.ee.init(this.dirs.app.root, this.log);
|
||||
return utils.ee.isEE;
|
||||
}
|
||||
|
||||
return reload;
|
||||
};
|
||||
get ee(): Core.Strapi['ee'] {
|
||||
return utils.ee;
|
||||
}
|
||||
|
||||
class Strapi extends Container implements Core.Strapi {
|
||||
server: Modules.Server.Server;
|
||||
get dirs(): Core.StrapiDirectories {
|
||||
return this.config.get('dirs');
|
||||
}
|
||||
|
||||
log: Logger;
|
||||
get reload(): Core.Reloader {
|
||||
return this.get('reload');
|
||||
}
|
||||
|
||||
fs: Core.StrapiFS;
|
||||
get db(): Database {
|
||||
return this.get('db');
|
||||
}
|
||||
|
||||
eventHub: Modules.EventHub.EventHub;
|
||||
get requestContext(): Modules.RequestContext.RequestContext {
|
||||
return this.get('requestContext');
|
||||
}
|
||||
|
||||
startupLogger: Core.StartupLogger;
|
||||
get customFields(): Modules.CustomFields.CustomFields {
|
||||
return this.get('customFields');
|
||||
}
|
||||
|
||||
cron: Modules.Cron.CronService;
|
||||
|
||||
webhookRunner: WebhookRunner;
|
||||
|
||||
webhookStore: Modules.WebhookStore.WebhookStore;
|
||||
|
||||
store: Modules.CoreStore.CoreStore;
|
||||
|
||||
entityValidator: Modules.EntityValidator.EntityValidator;
|
||||
get entityValidator(): Modules.EntityValidator.EntityValidator {
|
||||
return this.get('entityValidator');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated `strapi.entityService` will be removed in the next major version
|
||||
*/
|
||||
entityService: Modules.EntityService.EntityService;
|
||||
get entityService(): Modules.EntityService.EntityService {
|
||||
return this.get('entityService');
|
||||
}
|
||||
|
||||
documents: Modules.Documents.Service;
|
||||
get documents(): Modules.Documents.Service {
|
||||
return this.get('documents');
|
||||
}
|
||||
|
||||
telemetry: Modules.Metrics.TelemetryService;
|
||||
get features(): FeaturesService {
|
||||
return this.get('features');
|
||||
}
|
||||
|
||||
requestContext: Modules.RequestContext.RequestContext;
|
||||
get fetch(): Modules.Fetch.Fetch {
|
||||
return this.get('fetch');
|
||||
}
|
||||
|
||||
customFields: Modules.CustomFields.CustomFields;
|
||||
get cron(): Modules.Cron.CronService {
|
||||
return this.get('cron');
|
||||
}
|
||||
|
||||
fetch: Modules.Fetch.Fetch;
|
||||
get log(): Logger {
|
||||
return this.get('logger');
|
||||
}
|
||||
|
||||
dirs: Core.StrapiDirectories;
|
||||
get startupLogger(): Core.StartupLogger {
|
||||
return this.get('startupLogger');
|
||||
}
|
||||
|
||||
admin?: Core.Module;
|
||||
get eventHub(): Modules.EventHub.EventHub {
|
||||
return this.get('eventHub');
|
||||
}
|
||||
|
||||
isLoaded: boolean;
|
||||
get fs(): Core.StrapiFS {
|
||||
return this.get('fs');
|
||||
}
|
||||
|
||||
db: Database;
|
||||
get server(): Modules.Server.Server {
|
||||
return this.get('server');
|
||||
}
|
||||
|
||||
app: any;
|
||||
get telemetry(): Modules.Metrics.TelemetryService {
|
||||
return this.get('telemetry');
|
||||
}
|
||||
|
||||
EE?: boolean;
|
||||
|
||||
reload: Core.Reloader;
|
||||
|
||||
features: FeaturesService;
|
||||
|
||||
// @ts-expect-error - Assigned in constructor
|
||||
ee: Core.Strapi['ee'];
|
||||
|
||||
constructor(opts: StrapiOptions = {}) {
|
||||
super();
|
||||
|
||||
utils.destroyOnSignal(this);
|
||||
|
||||
const rootDirs = resolveWorkingDirectories(opts);
|
||||
|
||||
// Load the app configuration from the dist directory
|
||||
const appConfig = loadConfiguration(rootDirs, opts);
|
||||
|
||||
// Instantiate the Strapi container
|
||||
this.add('config', registries.config(appConfig, this))
|
||||
.add('content-types', registries.contentTypes())
|
||||
.add('components', registries.components())
|
||||
.add('services', registries.services(this))
|
||||
.add('policies', registries.policies())
|
||||
.add('middlewares', registries.middlewares())
|
||||
.add('hooks', registries.hooks())
|
||||
.add('controllers', registries.controllers(this))
|
||||
.add('modules', registries.modules(this))
|
||||
.add('plugins', registries.plugins(this))
|
||||
.add('custom-fields', registries.customFields(this))
|
||||
.add('apis', registries.apis(this))
|
||||
.add('sanitizers', registries.sanitizers())
|
||||
.add('validators', registries.validators())
|
||||
.add('query-params', createQueryParamService(this))
|
||||
.add('content-api', createContentAPI(this))
|
||||
.add('auth', createAuth())
|
||||
.add('models', registries.models());
|
||||
|
||||
// Create a mapping of every useful directory (for the app, dist and static directories)
|
||||
this.dirs = utils.getDirs(rootDirs, { strapi: this });
|
||||
|
||||
// Strapi state management variables
|
||||
this.isLoaded = false;
|
||||
this.reload = reloader(this);
|
||||
|
||||
// Instantiate the Koa app & the HTTP server
|
||||
this.server = createServer(this);
|
||||
|
||||
// Strapi utils instantiation
|
||||
this.fs = createStrapiFs(this);
|
||||
this.eventHub = createEventHub();
|
||||
this.startupLogger = utils.createStartupLogger(this);
|
||||
|
||||
const logConfig = {
|
||||
level: 'http', // Strapi defaults to level 'http'
|
||||
...this.config.get('logger'), // DEPRECATED
|
||||
...this.config.get('server.logger.config'),
|
||||
};
|
||||
|
||||
this.log = createLogger(logConfig);
|
||||
this.cron = createCronService();
|
||||
this.telemetry = createTelemetry(this);
|
||||
this.requestContext = requestContext;
|
||||
this.customFields = createCustomFields(this);
|
||||
this.fetch = utils.createStrapiFetch(this);
|
||||
this.features = createFeaturesService(this);
|
||||
this.db = new Database(
|
||||
_.merge(this.config.get('database'), {
|
||||
settings: {
|
||||
migrations: {
|
||||
dir: path.join(this.dirs.app.root, 'database/migrations'),
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// init webhook runner
|
||||
this.webhookRunner = createWebhookRunner({
|
||||
eventHub: this.eventHub,
|
||||
logger: this.log,
|
||||
configuration: this.config.get('server.webhooks', {}),
|
||||
fetch: this.fetch,
|
||||
});
|
||||
|
||||
this.store = createCoreStore({ db: this.db });
|
||||
this.webhookStore = createWebhookStore({ db: this.db });
|
||||
|
||||
this.entityValidator = entityValidator;
|
||||
this.entityService = createEntityService({
|
||||
strapi: this,
|
||||
db: this.db,
|
||||
});
|
||||
|
||||
this.documents = createDocumentService(this);
|
||||
|
||||
utils.createUpdateNotifier(this).notify();
|
||||
|
||||
Object.defineProperty<Strapi>(this, 'EE', {
|
||||
get: () => {
|
||||
utils.ee.init(this.dirs.app.root, this.log);
|
||||
return utils.ee.isEE;
|
||||
},
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
Object.defineProperty<Strapi>(this, 'ee', {
|
||||
get: () => utils.ee,
|
||||
configurable: false,
|
||||
});
|
||||
get store(): Modules.CoreStore.CoreStore {
|
||||
return this.get('coreStore');
|
||||
}
|
||||
|
||||
get config() {
|
||||
@ -358,24 +243,45 @@ class Strapi extends Container implements Core.Strapi {
|
||||
}
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
this.log.info('Shutting down Strapi');
|
||||
await this.server.destroy();
|
||||
await this.runLifecyclesFunctions(utils.LIFECYCLES.DESTROY);
|
||||
|
||||
this.eventHub.destroy();
|
||||
|
||||
await this.db?.destroy();
|
||||
|
||||
this.telemetry.destroy();
|
||||
this.cron.destroy();
|
||||
|
||||
process.removeAllListeners();
|
||||
|
||||
// @ts-expect-error: Allow clean delete of global.strapi to allow re-instanciation
|
||||
delete global.strapi;
|
||||
|
||||
this.log.info('Strapi has been shut down');
|
||||
// TODO: split into more providers
|
||||
registerInternalServices() {
|
||||
// Instantiate the Strapi container
|
||||
this.add('config', () => createConfigProvider(this.internal_config, this))
|
||||
.add('query-params', createQueryParamService(this))
|
||||
.add('content-api', createContentAPI(this))
|
||||
.add('auth', createAuth())
|
||||
.add('server', () => createServer(this))
|
||||
.add('fs', () => createStrapiFs(this))
|
||||
.add('eventHub', () => createEventHub())
|
||||
.add('startupLogger', () => utils.createStartupLogger(this))
|
||||
.add('logger', () => {
|
||||
return createLogger({
|
||||
level: 'http', // Strapi defaults to level 'http'
|
||||
...this.config.get('logger'), // DEPRECATED
|
||||
...this.config.get('server.logger.config'),
|
||||
});
|
||||
})
|
||||
.add('fetch', () => utils.createStrapiFetch(this))
|
||||
.add('features', () => createFeaturesService(this))
|
||||
.add('requestContext', requestContext)
|
||||
.add('customFields', createCustomFields(this))
|
||||
.add('entityValidator', entityValidator)
|
||||
.add('entityService', () => createEntityService({ strapi: this, db: this.db }))
|
||||
.add('documents', () => createDocumentService(this))
|
||||
.add(
|
||||
'db',
|
||||
() =>
|
||||
new Database(
|
||||
_.merge(this.config.get('database'), {
|
||||
settings: {
|
||||
migrations: {
|
||||
dir: path.join(this.dirs.app.root, 'database/migrations'),
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
.add('reload', () => createReloader(this));
|
||||
}
|
||||
|
||||
sendStartupTelemetry() {
|
||||
@ -475,24 +381,22 @@ class Strapi extends Container implements Core.Strapi {
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
registerInternalHooks() {
|
||||
this.get('hooks').set('strapi::content-types.beforeSync', hooks.createAsyncParallelHook());
|
||||
this.get('hooks').set('strapi::content-types.afterSync', hooks.createAsyncParallelHook());
|
||||
async load() {
|
||||
await this.register();
|
||||
await this.bootstrap();
|
||||
|
||||
this.hook('strapi::content-types.beforeSync').register(draftAndPublishSync.disable);
|
||||
this.hook('strapi::content-types.afterSync').register(draftAndPublishSync.enable);
|
||||
this.isLoaded = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async register() {
|
||||
await loaders.loadApplicationContext(this);
|
||||
|
||||
this.get('models').add(coreStoreModel).add(webhookModel);
|
||||
|
||||
this.registerInternalHooks();
|
||||
|
||||
this.telemetry.register();
|
||||
for (const provider of providers) {
|
||||
await provider.register?.(this);
|
||||
}
|
||||
|
||||
await this.runLifecyclesFunctions(utils.LIFECYCLES.REGISTER);
|
||||
|
||||
// NOTE: Swap type customField for underlying data type
|
||||
utils.convertCustomFieldType(this);
|
||||
|
||||
@ -510,13 +414,6 @@ class Strapi extends Container implements Core.Strapi {
|
||||
|
||||
await this.db.init({ models });
|
||||
|
||||
if (this.config.get('server.cron.enabled', true)) {
|
||||
const cronTasks = this.config.get('server.cron.tasks', {});
|
||||
this.cron.add(cronTasks);
|
||||
}
|
||||
|
||||
this.telemetry.bootstrap();
|
||||
|
||||
let oldContentTypes;
|
||||
if (await this.db.getSchemaConnection().hasTable(coreStoreModel.tableName)) {
|
||||
oldContentTypes = await this.store.get({
|
||||
@ -549,8 +446,6 @@ class Strapi extends Container implements Core.Strapi {
|
||||
value: this.contentTypes,
|
||||
});
|
||||
|
||||
await this.startWebhooks();
|
||||
|
||||
await this.server.initMiddlewares();
|
||||
this.server.initRouting();
|
||||
|
||||
@ -558,41 +453,39 @@ class Strapi extends Container implements Core.Strapi {
|
||||
|
||||
await this.runLifecyclesFunctions(utils.LIFECYCLES.BOOTSTRAP);
|
||||
|
||||
this.cron.start();
|
||||
for (const provider of providers) {
|
||||
await provider.bootstrap?.(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.register();
|
||||
await this.bootstrap();
|
||||
async destroy() {
|
||||
this.log.info('Shutting down Strapi');
|
||||
await this.runLifecyclesFunctions(utils.LIFECYCLES.DESTROY);
|
||||
|
||||
this.isLoaded = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async startWebhooks() {
|
||||
const webhooks = await this.webhookStore?.findWebhooks();
|
||||
if (!webhooks) {
|
||||
return;
|
||||
for (const provider of providers) {
|
||||
await provider.destroy?.(this);
|
||||
}
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
this.webhookRunner?.add(webhook);
|
||||
}
|
||||
await this.server.destroy();
|
||||
|
||||
this.eventHub.destroy();
|
||||
|
||||
await this.db?.destroy();
|
||||
|
||||
process.removeAllListeners();
|
||||
|
||||
// @ts-expect-error: Allow clean delete of global.strapi to allow re-instanciation
|
||||
delete global.strapi;
|
||||
|
||||
this.log.info('Strapi has been shut down');
|
||||
}
|
||||
|
||||
async runLifecyclesFunctions(lifecycleName: 'register' | 'bootstrap' | 'destroy') {
|
||||
// plugins
|
||||
await this.get('modules')[lifecycleName]();
|
||||
|
||||
// admin
|
||||
const adminLifecycleFunction = this.admin && this.admin[lifecycleName];
|
||||
if (isFunction(adminLifecycleFunction)) {
|
||||
await adminLifecycleFunction({ strapi: this });
|
||||
}
|
||||
|
||||
// user
|
||||
const userLifecycleFunction = this.app && this.app[lifecycleName];
|
||||
if (isFunction(userLifecycleFunction)) {
|
||||
@ -621,8 +514,8 @@ class Strapi extends Container implements Core.Strapi {
|
||||
}
|
||||
|
||||
export interface StrapiOptions {
|
||||
appDir?: string;
|
||||
distDir?: string;
|
||||
appDir: string;
|
||||
distDir: string;
|
||||
autoReload?: boolean;
|
||||
serveAdminPanel?: boolean;
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ const RESTRICTED_FILENAMES = [
|
||||
'packageJsonStrapi',
|
||||
'info',
|
||||
'autoReload',
|
||||
'dirs',
|
||||
|
||||
// probably mistaken/typo filenames
|
||||
...Object.keys(MISTAKEN_FILENAMES),
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { join, resolve } from 'path';
|
||||
import { get } from 'lodash/fp';
|
||||
|
||||
import type { Core } from '@strapi/types';
|
||||
import type { StrapiOptions } from '../Strapi';
|
||||
|
||||
export type Options = {
|
||||
app: string;
|
||||
@ -7,8 +10,8 @@ export type Options = {
|
||||
};
|
||||
|
||||
export const getDirs = (
|
||||
{ app: appDir, dist: distDir }: Options,
|
||||
{ strapi }: { strapi: Core.Strapi }
|
||||
{ appDir, distDir }: StrapiOptions,
|
||||
config: { server: Partial<Core.Config.Server> }
|
||||
): Core.StrapiDirectories => ({
|
||||
dist: {
|
||||
root: distDir,
|
||||
@ -31,6 +34,6 @@ export const getDirs = (
|
||||
config: join(appDir, 'config'),
|
||||
},
|
||||
static: {
|
||||
public: resolve(appDir, strapi.config.get('server.dirs.public')),
|
||||
public: resolve(appDir, get('server.dirs.public', config)),
|
||||
},
|
||||
});
|
||||
@ -5,9 +5,12 @@ import _ from 'lodash';
|
||||
import { omit } from 'lodash/fp';
|
||||
import dotenv from 'dotenv';
|
||||
import type { Core } from '@strapi/types';
|
||||
import { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } from './urls';
|
||||
|
||||
import { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } from './urls';
|
||||
import loadConfigDir from './config-loader';
|
||||
import { getDirs } from './get-dirs';
|
||||
|
||||
import type { StrapiOptions } from '../Strapi';
|
||||
|
||||
dotenv.config({ path: process.env.ENV_PATH });
|
||||
|
||||
@ -45,9 +48,8 @@ const defaultConfig = {
|
||||
} satisfies Partial<Core.Config.Api>,
|
||||
};
|
||||
|
||||
export default (dirs: { app: string; dist: string }, initialConfig: any = {}) => {
|
||||
const { app: appDir, dist: distDir } = dirs;
|
||||
const { autoReload = false, serveAdminPanel = true } = initialConfig;
|
||||
export const loadConfiguration = (opts: StrapiOptions) => {
|
||||
const { appDir, distDir, autoReload = false, serveAdminPanel = true } = opts;
|
||||
|
||||
const pkgJSON = require(path.resolve(appDir, 'package.json'));
|
||||
|
||||
@ -83,6 +85,7 @@ export default (dirs: { app: string; dist: string }, initialConfig: any = {}) =>
|
||||
_.set(config, 'admin.url', adminUrl);
|
||||
_.set(config, 'admin.path', adminPath);
|
||||
_.set(config, 'admin.absoluteUrl', getAbsoluteAdminUrl(config));
|
||||
_.set(config, 'dirs', getDirs(opts, config));
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ interface EE {
|
||||
enabled: boolean;
|
||||
licenseInfo: {
|
||||
licenseKey?: string;
|
||||
features?: Array<{ name: string } | string>;
|
||||
features?: Array<{ name: string; [key: string]: any } | string>;
|
||||
expireAt?: string;
|
||||
seats?: number;
|
||||
type?: string;
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import Strapi, { type StrapiOptions } from './Strapi';
|
||||
import { destroyOnSignal, resolveWorkingDirectories, createUpdateNotifier } from './utils';
|
||||
|
||||
export { default as compileStrapi } from './compile';
|
||||
export * as factories from './factories';
|
||||
|
||||
export const createStrapi = (options: StrapiOptions = {}): Core.Strapi => {
|
||||
const strapi = new Strapi(options);
|
||||
export const createStrapi = (options: Partial<StrapiOptions> = {}): Core.Strapi => {
|
||||
const strapi = new Strapi({
|
||||
...options,
|
||||
...resolveWorkingDirectories(options),
|
||||
});
|
||||
|
||||
destroyOnSignal(strapi);
|
||||
createUpdateNotifier(strapi);
|
||||
|
||||
// TODO: deprecate and remove in next major
|
||||
global.strapi = strapi as unknown as Required<Core.Strapi>;
|
||||
global.strapi = strapi;
|
||||
|
||||
return strapi;
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import type { Core, Struct } from '@strapi/types';
|
||||
import { getGlobalId } from '../domain/content-type';
|
||||
|
||||
export default async function loadAdmin(strapi: Core.Strapi) {
|
||||
strapi.admin = require('@strapi/admin/strapi-server');
|
||||
// strapi.admin = require('@strapi/admin/strapi-server');
|
||||
|
||||
strapi.get('services').add(`admin::`, strapi.admin?.services);
|
||||
strapi.get('controllers').add(`admin::`, strapi.admin?.controllers);
|
||||
|
||||
@ -6,7 +6,6 @@ import loadMiddlewares from './middlewares';
|
||||
import loadComponents from './components';
|
||||
import loadPolicies from './policies';
|
||||
import loadPlugins from './plugins';
|
||||
import loadAdmin from './admin';
|
||||
import loadSanitizers from './sanitizers';
|
||||
import loadValidators from './validators';
|
||||
|
||||
@ -16,7 +15,6 @@ export async function loadApplicationContext(strapi: Core.Strapi) {
|
||||
loadSanitizers(strapi),
|
||||
loadValidators(strapi),
|
||||
loadPlugins(strapi),
|
||||
loadAdmin(strapi),
|
||||
loadAPIs(strapi),
|
||||
loadComponents(strapi),
|
||||
loadMiddlewares(strapi),
|
||||
|
||||
23
packages/core/core/src/providers/admin.ts
Normal file
23
packages/core/core/src/providers/admin.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import loadAdmin from '../loaders/admin';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi.add('admin', () => require('@strapi/admin/strapi-server'));
|
||||
},
|
||||
|
||||
async register(strapi: Core.Strapi) {
|
||||
await loadAdmin(strapi);
|
||||
|
||||
await strapi.get('admin')?.register({ strapi });
|
||||
},
|
||||
|
||||
async bootstrap(strapi: Core.Strapi) {
|
||||
await strapi.get('admin')?.bootstrap({ strapi });
|
||||
},
|
||||
|
||||
async destroy(strapi: Core.Strapi) {
|
||||
await strapi.get('admin')?.destroy({ strapi });
|
||||
},
|
||||
};
|
||||
10
packages/core/core/src/providers/coreStore.ts
Normal file
10
packages/core/core/src/providers/coreStore.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import { createCoreStore, coreStoreModel } from '../services/core-store';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi.get('models').add(coreStoreModel);
|
||||
strapi.add('coreStore', () => createCoreStore({ db: strapi.db }));
|
||||
},
|
||||
};
|
||||
20
packages/core/core/src/providers/cron.ts
Normal file
20
packages/core/core/src/providers/cron.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import createCronService from '../services/cron';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi.add('cron', () => createCronService());
|
||||
},
|
||||
async bootstrap(strapi: Core.Strapi) {
|
||||
if (strapi.config.get('server.cron.enabled', true)) {
|
||||
const cronTasks = strapi.config.get('server.cron.tasks', {});
|
||||
strapi.get('cron').add(cronTasks);
|
||||
}
|
||||
|
||||
strapi.get('cron').start();
|
||||
},
|
||||
async destroy(strapi: Core.Strapi) {
|
||||
strapi.get('cron').destroy();
|
||||
},
|
||||
};
|
||||
15
packages/core/core/src/providers/index.ts
Normal file
15
packages/core/core/src/providers/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import admin from './admin';
|
||||
import coreStore from './coreStore';
|
||||
import cron from './cron';
|
||||
import registries from './registries';
|
||||
import telemetry from './telemetry';
|
||||
import webhooks from './webhooks';
|
||||
|
||||
type Provider = {
|
||||
init?: (strapi: any) => void;
|
||||
register?: (strapi: any) => Promise<void>;
|
||||
bootstrap?: (strapi: any) => Promise<void>;
|
||||
destroy?: (strapi: any) => Promise<void>;
|
||||
};
|
||||
|
||||
export const providers: Provider[] = [registries, admin, coreStore, webhooks, telemetry, cron];
|
||||
38
packages/core/core/src/providers/registries.ts
Normal file
38
packages/core/core/src/providers/registries.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
import { hooks } from '@strapi/utils';
|
||||
|
||||
import * as registries from '../registries';
|
||||
import { loadApplicationContext } from '../loaders';
|
||||
import * as draftAndPublishSync from '../migrations/draft-publish';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi
|
||||
.add('content-types', () => registries.contentTypes())
|
||||
.add('components', () => registries.components())
|
||||
.add('services', () => registries.services(strapi))
|
||||
.add('policies', () => registries.policies())
|
||||
.add('middlewares', () => registries.middlewares())
|
||||
.add('hooks', () => registries.hooks())
|
||||
.add('controllers', () => registries.controllers(strapi))
|
||||
.add('modules', () => registries.modules(strapi))
|
||||
.add('plugins', () => registries.plugins(strapi))
|
||||
.add('custom-fields', () => registries.customFields(strapi))
|
||||
.add('apis', () => registries.apis(strapi))
|
||||
.add('models', () => registries.models())
|
||||
.add('sanitizers', registries.sanitizers())
|
||||
.add('validators', registries.validators());
|
||||
},
|
||||
async register(strapi: Core.Strapi) {
|
||||
await loadApplicationContext(strapi);
|
||||
|
||||
strapi.get('hooks').set('strapi::content-types.beforeSync', hooks.createAsyncParallelHook());
|
||||
strapi.get('hooks').set('strapi::content-types.afterSync', hooks.createAsyncParallelHook());
|
||||
|
||||
strapi.hook('strapi::content-types.beforeSync').register(draftAndPublishSync.disable);
|
||||
strapi.hook('strapi::content-types.afterSync').register(draftAndPublishSync.enable);
|
||||
},
|
||||
// async bootstrap(strapi: Core.Strapi) {
|
||||
// },
|
||||
// async destroy(strapi: Core.Strapi) {},
|
||||
};
|
||||
18
packages/core/core/src/providers/telemetry.ts
Normal file
18
packages/core/core/src/providers/telemetry.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import createTelemetry from '../services/metrics';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi.add('telemetry', () => createTelemetry(strapi));
|
||||
},
|
||||
async register(strapi: Core.Strapi) {
|
||||
strapi.get('telemetry').register();
|
||||
},
|
||||
async bootstrap(strapi: Core.Strapi) {
|
||||
strapi.get('telemetry').bootstrap();
|
||||
},
|
||||
async destroy(strapi: Core.Strapi) {
|
||||
strapi.get('telemetry').destroy();
|
||||
},
|
||||
};
|
||||
30
packages/core/core/src/providers/webhooks.ts
Normal file
30
packages/core/core/src/providers/webhooks.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
import { createWebhookStore, webhookModel } from '../services/webhook-store';
|
||||
import createWebhookRunner from '../services/webhook-runner';
|
||||
|
||||
export default {
|
||||
init(strapi: Core.Strapi) {
|
||||
strapi.get('models').add(webhookModel);
|
||||
|
||||
strapi.add('webhookStore', () => createWebhookStore({ db: strapi.db }));
|
||||
strapi.add('webhookRunner', () =>
|
||||
createWebhookRunner({
|
||||
eventHub: strapi.eventHub,
|
||||
logger: strapi.log,
|
||||
configuration: strapi.config.get('server.webhooks', {}),
|
||||
fetch: strapi.fetch,
|
||||
})
|
||||
);
|
||||
},
|
||||
async bootstrap(strapi: Core.Strapi) {
|
||||
const webhooks = await strapi.get('webhookStore').findWebhooks();
|
||||
if (!webhooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
strapi.get('webhookRunner').add(webhook);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -1,31 +1,31 @@
|
||||
import configProvider from '../config';
|
||||
import { createConfigProvider } from '../../services/config';
|
||||
|
||||
const logLevel = 'warn';
|
||||
|
||||
describe('config', () => {
|
||||
test('returns objects for partial paths', () => {
|
||||
const config = configProvider({ default: { child: 'val' } });
|
||||
const config = createConfigProvider({ default: { child: 'val' } });
|
||||
expect(config.get('default')).toEqual({ child: 'val' });
|
||||
});
|
||||
|
||||
test('supports full string paths', () => {
|
||||
const config = configProvider({ default: { child: 'val' } });
|
||||
const config = createConfigProvider({ default: { child: 'val' } });
|
||||
expect(config.get('default.child')).toEqual('val');
|
||||
});
|
||||
|
||||
test('supports array paths', () => {
|
||||
const config = configProvider({ default: { child: 'val' } });
|
||||
const config = createConfigProvider({ default: { child: 'val' } });
|
||||
expect(config.get(['default', 'child'])).toEqual('val');
|
||||
});
|
||||
|
||||
test('accepts initial values', () => {
|
||||
const config = configProvider({ default: 'val', foo: 'bar' });
|
||||
const config = createConfigProvider({ default: 'val', foo: 'bar' });
|
||||
expect(config.get('default')).toEqual('val');
|
||||
expect(config.get('foo')).toEqual('bar');
|
||||
});
|
||||
|
||||
test('accepts uid in paths', () => {
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'api::myapi': { foo: 'val' },
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
@ -39,7 +39,7 @@ describe('config', () => {
|
||||
test('`get` supports `plugin::` prefix', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -51,7 +51,7 @@ describe('config', () => {
|
||||
test('`get` supports `plugin::model` in array path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -64,7 +64,7 @@ describe('config', () => {
|
||||
test('`get` supports `plugin.` prefix in string path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -76,7 +76,7 @@ describe('config', () => {
|
||||
test('`get` supports `plugin.model` prefix in array path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -88,7 +88,7 @@ describe('config', () => {
|
||||
test('`get` supports `plugin` + `model` in array path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -99,7 +99,7 @@ describe('config', () => {
|
||||
test('`set` supports `plugin.` prefix in string path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
config.set('plugin.myplugin.thing', 'val');
|
||||
@ -112,7 +112,7 @@ describe('config', () => {
|
||||
test('`set` supports `plugin.` prefix in array path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
config.set(['plugin.myplugin', 'thing'], 'val');
|
||||
@ -125,7 +125,7 @@ describe('config', () => {
|
||||
test('`has` supports `plugin.` prefix in string path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -138,7 +138,7 @@ describe('config', () => {
|
||||
test('`has` supports `plugin.` prefix in array path', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -152,7 +152,7 @@ describe('config', () => {
|
||||
const consoleSpy = jest.spyOn(console, logLevel).mockImplementation(() => {});
|
||||
|
||||
const logSpy = jest.fn();
|
||||
const config = configProvider(
|
||||
const config = createConfigProvider(
|
||||
{
|
||||
'plugin::myplugin': { foo: 'bar' },
|
||||
},
|
||||
@ -166,7 +166,7 @@ describe('config', () => {
|
||||
});
|
||||
|
||||
test('get does NOT support deprecation for other prefixes', () => {
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'api::myapi': { foo: 'bar' },
|
||||
});
|
||||
|
||||
@ -174,7 +174,7 @@ describe('config', () => {
|
||||
});
|
||||
|
||||
test('set does NOT support deprecation for other prefixes', () => {
|
||||
const config = configProvider({
|
||||
const config = createConfigProvider({
|
||||
'api::myapi': { foo: 'bar' },
|
||||
});
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ export { default as controllers } from './controllers';
|
||||
export { default as modules } from './modules';
|
||||
export { default as plugins } from './plugins';
|
||||
export { default as customFields } from './custom-fields';
|
||||
export { default as config } from './config';
|
||||
export { default as apis } from './apis';
|
||||
export { default as sanitizers } from './sanitizers';
|
||||
export { default as validators } from './validators';
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
import { get, set, has, isString, isNumber, isArray, type PropertyPath } from 'lodash';
|
||||
|
||||
type State = {
|
||||
config: Config;
|
||||
};
|
||||
|
||||
type Config = Record<string, unknown>;
|
||||
|
||||
export default (initialConfig = {}, strapi?: Core.Strapi | Core.Strapi): Core.ConfigProvider => {
|
||||
const _config: Config = { ...initialConfig }; // not deep clone because it would break some config
|
||||
export const createConfigProvider = (
|
||||
initialConfig: Record<string, unknown> = {},
|
||||
strapi?: Core.Strapi | Core.Strapi
|
||||
): Core.ConfigProvider => {
|
||||
const state: State = {
|
||||
config: { ...initialConfig }, // not deep clone because it would break some config
|
||||
};
|
||||
|
||||
// Accessing model configs with dot (.) was deprecated between v4->v5, but to avoid a major breaking change
|
||||
// we will still support certain namespaces, currently only 'plugin.'
|
||||
@ -39,16 +48,16 @@ export default (initialConfig = {}, strapi?: Core.Strapi | Core.Strapi): Core.Co
|
||||
};
|
||||
|
||||
return {
|
||||
..._config, // TODO: to remove
|
||||
...state.config, // TODO: to remove
|
||||
get(path: PropertyPath, defaultValue?: unknown) {
|
||||
return get(_config, transformDeprecatedPaths(path), defaultValue);
|
||||
return get(state.config, transformDeprecatedPaths(path), defaultValue);
|
||||
},
|
||||
set(path: PropertyPath, val: unknown) {
|
||||
set(_config, transformDeprecatedPaths(path), val);
|
||||
set(state.config, transformDeprecatedPaths(path), val);
|
||||
return this;
|
||||
},
|
||||
has(path: PropertyPath) {
|
||||
return has(_config, transformDeprecatedPaths(path));
|
||||
return has(state.config, transformDeprecatedPaths(path));
|
||||
},
|
||||
};
|
||||
};
|
||||
41
packages/core/core/src/services/reloader.ts
Normal file
41
packages/core/core/src/services/reloader.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { Core } from '@strapi/types';
|
||||
|
||||
export const createReloader = (strapi: Core.Strapi) => {
|
||||
const state = {
|
||||
shouldReload: 0,
|
||||
isWatching: true,
|
||||
};
|
||||
|
||||
function reload() {
|
||||
if (state.shouldReload > 0) {
|
||||
// Reset the reloading state
|
||||
state.shouldReload -= 1;
|
||||
reload.isReloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strapi.config.get('autoReload')) {
|
||||
process.send?.('reload');
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(reload, 'isWatching', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
set(value) {
|
||||
// Special state when the reloader is disabled temporarly (see GraphQL plugin example).
|
||||
if (state.isWatching === false && value === true) {
|
||||
state.shouldReload += 1;
|
||||
}
|
||||
state.isWatching = value;
|
||||
},
|
||||
get() {
|
||||
return state.isWatching;
|
||||
},
|
||||
});
|
||||
|
||||
reload.isReloading = false;
|
||||
reload.isWatching = true;
|
||||
|
||||
return reload;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
export { openBrowser } from './open-browser';
|
||||
export { isInitialized } from './is-initialized';
|
||||
export { getDirs } from './get-dirs';
|
||||
export { getDirs } from '../configuration/get-dirs';
|
||||
export { ee } from './ee';
|
||||
export { createUpdateNotifier } from './update-notifier';
|
||||
export { createStrapiFetch, Fetch } from './fetch';
|
||||
@ -9,3 +9,4 @@ export { createStartupLogger } from './startup-logger';
|
||||
export { transformContentTypesToModels } from './transform-content-types-to-models';
|
||||
export { destroyOnSignal } from './signals';
|
||||
export { LIFECYCLES } from './lifecycles';
|
||||
export { resolveWorkingDirectories } from './resolve-working-dirs';
|
||||
|
||||
21
packages/core/core/src/utils/resolve-working-dirs.ts
Normal file
21
packages/core/core/src/utils/resolve-working-dirs.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import path from 'node:path';
|
||||
|
||||
/**
|
||||
* Resolve the working directories based on the instance options.
|
||||
*
|
||||
* Behavior:
|
||||
* - `appDir` is the directory where Strapi will write every file (schemas, generated APIs, controllers or services)
|
||||
* - `distDir` is the directory where Strapi will read configurations, schemas and any compiled code
|
||||
*
|
||||
* Default values:
|
||||
* - If `appDir` is `undefined`, it'll be set to `process.cwd()`
|
||||
* - If `distDir` is `undefined`, it'll be set to `appDir`
|
||||
*/
|
||||
export const resolveWorkingDirectories = (opts: { appDir?: string; distDir?: string }) => {
|
||||
const cwd = process.cwd();
|
||||
|
||||
const appDir = opts.appDir ? path.resolve(cwd, opts.appDir) : cwd;
|
||||
const distDir = opts.distDir ? path.resolve(cwd, opts.distDir) : appDir;
|
||||
|
||||
return { appDir, distDir };
|
||||
};
|
||||
@ -83,21 +83,21 @@ export const createUpdateNotifier = (strapi: Core.Strapi) => {
|
||||
console.log(message);
|
||||
};
|
||||
|
||||
return {
|
||||
notify({ checkInterval = CHECK_INTERVAL, notifInterval = NOTIF_INTERVAL } = {}) {
|
||||
// TODO v6: Remove this warning
|
||||
if (env.bool('STRAPI_DISABLE_UPDATE_NOTIFICATION', false)) {
|
||||
strapi.log.warn(
|
||||
'STRAPI_DISABLE_UPDATE_NOTIFICATION is no longer supported. Instead, set logger.updates.enabled to false in your server configuration.'
|
||||
);
|
||||
}
|
||||
function notify({ checkInterval = CHECK_INTERVAL, notifInterval = NOTIF_INTERVAL } = {}) {
|
||||
// TODO v6: Remove this warning
|
||||
if (env.bool('STRAPI_DISABLE_UPDATE_NOTIFICATION', false)) {
|
||||
strapi.log.warn(
|
||||
'STRAPI_DISABLE_UPDATE_NOTIFICATION is no longer supported. Instead, set logger.updates.enabled to false in your server configuration.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!strapi.config.get('server.logger.updates.enabled') || !config) {
|
||||
return;
|
||||
}
|
||||
if (!strapi.config.get('server.logger.updates.enabled') || !config) {
|
||||
return;
|
||||
}
|
||||
|
||||
display(notifInterval);
|
||||
checkUpdate(checkInterval); // doesn't need to await
|
||||
},
|
||||
};
|
||||
display(notifInterval);
|
||||
checkUpdate(checkInterval); // doesn't need to await
|
||||
}
|
||||
|
||||
notify();
|
||||
};
|
||||
|
||||
@ -17,8 +17,6 @@ export interface Strapi extends Container {
|
||||
eventHub: Modules.EventHub.EventHub;
|
||||
startupLogger: StartupLogger;
|
||||
cron: Modules.Cron.CronService;
|
||||
webhookRunner: Modules.WebhookRunner.WebhookRunner;
|
||||
webhookStore: Modules.WebhookStore.WebhookStore;
|
||||
store: Modules.CoreStore.CoreStore;
|
||||
/**
|
||||
* @deprecated will be removed in the next major
|
||||
@ -35,7 +33,7 @@ export interface Strapi extends Container {
|
||||
customFields: Modules.CustomFields.CustomFields;
|
||||
fetch: Modules.Fetch.Fetch;
|
||||
dirs: StrapiDirectories;
|
||||
admin?: Core.Module;
|
||||
admin: Core.Module;
|
||||
isLoaded: boolean;
|
||||
db: Database;
|
||||
app: any;
|
||||
@ -46,7 +44,7 @@ export interface Strapi extends Container {
|
||||
features: {
|
||||
isEnabled: (feature: string) => boolean;
|
||||
list: () => { name: string; [key: string]: any }[];
|
||||
get: (feature: string) => { name: string; [key: string]: any };
|
||||
get: (feature: string) => string | { name: string; [key: string]: any } | undefined;
|
||||
};
|
||||
};
|
||||
features: Modules.Features.FeaturesService;
|
||||
@ -84,10 +82,8 @@ export interface Strapi extends Container {
|
||||
listen(): Promise<void>;
|
||||
stopWithError(err: unknown, customMessage?: string): never;
|
||||
stop(exitCode?: number): never;
|
||||
registerInternalHooks(): void;
|
||||
register(): Promise<Strapi>;
|
||||
bootstrap(): Promise<Strapi>;
|
||||
startWebhooks(): Promise<void>;
|
||||
runLifecyclesFunctions(lifecycleName: 'register' | 'bootstrap' | 'destroy'): Promise<void>;
|
||||
getModel<TSchemaUID extends UID.Schema>(
|
||||
uid: TSchemaUID
|
||||
@ -151,14 +147,3 @@ export interface StrapiDirectories {
|
||||
config: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StrapiOptions {
|
||||
appDir?: string;
|
||||
distDir?: string;
|
||||
autoReload?: boolean;
|
||||
serveAdminPanel?: boolean;
|
||||
}
|
||||
|
||||
export interface StrapiConstructor {
|
||||
new (options?: StrapiOptions): Strapi;
|
||||
}
|
||||
|
||||
@ -13,12 +13,12 @@ export type * as UID from './uid';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line vars-on-top,no-var
|
||||
var strapi: Required<Strapi>;
|
||||
var strapi: Strapi;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
strapi: Required<Strapi>;
|
||||
strapi: Strapi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,16 @@ describe('Upload plugin bootstrap function', () => {
|
||||
const registerMany = jest.fn(() => {});
|
||||
|
||||
global.strapi = {
|
||||
get(name: string) {
|
||||
switch (name) {
|
||||
case 'webhookStore':
|
||||
return {
|
||||
addAllowedEvent: jest.fn(),
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
dirs: {
|
||||
dist: { root: process.cwd() },
|
||||
app: { root: process.cwd() },
|
||||
@ -65,9 +75,6 @@ describe('Upload plugin bootstrap function', () => {
|
||||
set: setStore,
|
||||
};
|
||||
},
|
||||
webhookStore: {
|
||||
addAllowedEvent: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
|
||||
await bootstrap({ strapi });
|
||||
|
||||
@ -50,7 +50,7 @@ export async function bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||
|
||||
const registerWebhookEvents = async () =>
|
||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||
strapi.webhookStore.addAllowedEvent(key, value);
|
||||
strapi.get('webhookStore').addAllowedEvent(key, value);
|
||||
});
|
||||
|
||||
const registerPermissionActions = async () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user