Merge pull request #19943 from strapi/v5/core

chore: internal core refactorings
This commit is contained in:
Alexandre BODIN 2024-04-03 17:37:12 +02:00 committed by GitHub
commit c9159be3ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
152 changed files with 729 additions and 623 deletions

View File

@ -531,14 +531,6 @@ TODO
TODO
:::
### `strapi.startWebhooks()`
- Returns: Promise
:::info
TODO
:::
### `strapi.reload()`
:::info

View File

@ -82,7 +82,7 @@ module.exports = async () => {
subCategory: 'options',
},
];
await strapi.admin.services.permission.actionProvider.registerMany(actions);
await strapi.service('admin::permission').actionProvider.registerMany(actions);
};
```
@ -136,7 +136,7 @@ module.exports = async () => {
handler: (user) => ({ 'createdBy.id': user.id }),
},
];
await strapi.admin.services.permission.conditionProvider.registerMany(conditions);
await strapi.service('admin::permission').conditionProvider.registerMany(conditions);
};
```

View File

@ -104,7 +104,7 @@ describe('Stages', () => {
});
test('cannot transition', async () => {
global.strapi.admin.services['stage-permissions'].can.mockReturnValueOnce(false);
global.strapi.service('admin::stage-permissions').can.mockReturnValueOnce(false);
const ctx: any = {
...baseCtx,
@ -145,7 +145,7 @@ describe('Stages', () => {
});
test('cannot transition', async () => {
global.strapi.admin.services['stage-permissions'].can.mockReturnValueOnce(false);
global.strapi.service('admin::stage-permissions').can.mockReturnValueOnce(false);
const ctx: any = {
...baseCtx,

View File

@ -18,7 +18,7 @@ const providerAuthenticationFlow = compose([
export default {
async getProviders(ctx: Context) {
const { providerRegistry } = strapi.admin.services.passport;
const { providerRegistry } = strapi.service('admin::passport');
ctx.body = providerRegistry.getAll().map(toProviderDTO);
},
@ -56,7 +56,7 @@ export default {
params: { provider: providerName },
} = ctx;
const { providerRegistry } = strapi.admin.services.passport;
const { providerRegistry } = strapi.service('admin::passport');
if (!providerRegistry.has(providerName)) {
throw new ValidationError(`Invalid provider supplied: ${providerName}`);

View File

@ -12,7 +12,7 @@ import createAuditLogsService from './services/audit-logs';
import reviewWorkflowsMiddlewares from './middlewares/review-workflows';
import { getService } from './utils';
export default async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default async ({ strapi }: { strapi: Core.Strapi }) => {
const auditLogsIsEnabled = strapi.config.get('admin.auditLogs.enabled', true);
if (auditLogsIsEnabled) {

View File

@ -130,7 +130,7 @@ describe('Audit logs service', () => {
const mockFindMany = jest.fn();
const mockDeleteExpiredEvents = jest.fn();
let strapi = {} as Core.LoadedStrapi;
let strapi = {} as Core.Strapi;
beforeAll(() => {
// @ts-expect-error - register is a mock
@ -178,7 +178,7 @@ describe('Audit logs service', () => {
return cb(opt);
},
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
});
afterEach(() => {

View File

@ -29,7 +29,7 @@ const strapiMock = {
},
},
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
let validationService: any;

View File

@ -60,6 +60,10 @@ const containerMock = {
switch (container) {
case 'content-types':
return contentTypesContainer;
case 'webhookStore':
return {
addAllowedEvent: jest.fn(),
};
default:
return null;
}
@ -91,10 +95,7 @@ const strapiMock = {
return null;
}
},
webhookStore: {
addAllowedEvent: jest.fn(),
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
const reviewWorkflowsService = reviewWorkflowsServiceFactory({ strapi: strapiMock });

View File

@ -116,7 +116,7 @@ const strapiMock = {
},
},
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
const stagesService = stageFactory({ strapi: strapiMock });

View File

@ -97,7 +97,7 @@ const strapiMock = {
};
}
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
const ctMap: Record<string, any> = {
[WORKFLOW_MODEL_UID]: workflowCT.schema,

View File

@ -61,7 +61,7 @@ const getEventMap = (defaultEvents: any) => {
}, {} as any);
};
const getRetentionDays = (strapi: Core.LoadedStrapi) => {
const getRetentionDays = (strapi: Core.Strapi) => {
const licenseRetentionDays = strapi.ee.features.get('audit-logs')?.options.retentionDays;
const userRetentionDays = strapi.config.get('admin.auditLogs.retentionDays');
@ -79,7 +79,7 @@ const getRetentionDays = (strapi: Core.LoadedStrapi) => {
return licenseRetentionDays;
};
const createAuditLogsService = (strapi: Core.LoadedStrapi) => {
const createAuditLogsService = (strapi: Core.Strapi) => {
// Manage internal service state privately
const state = {} as any;

View File

@ -3,7 +3,7 @@ import type { Core } from '@strapi/types';
import { getService } from '../utils';
const getSSOProvidersList = async () => {
const { providerRegistry } = strapi.admin.services.passport;
const { providerRegistry } = strapi.service('admin::passport');
return providerRegistry.getAll().map(({ uid }: { uid: string }) => uid);
};

View File

@ -8,7 +8,7 @@ const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
const getWeeklyCronScheduleAt = (date: Date) =>
`${date.getSeconds()} ${date.getMinutes()} ${date.getHours()} * * ${date.getDay()}`;
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const metrics = getService('review-workflows-metrics', { strapi });
const workflowsService = getService('workflows', { strapi });

View File

@ -62,7 +62,7 @@ const setReviewWorkflowAttributes = (contentType: any) => {
setAssigneeAttribute(contentType);
};
function extendReviewWorkflowContentTypes({ strapi }: { strapi: Core.LoadedStrapi }) {
function extendReviewWorkflowContentTypes({ strapi }: { strapi: Core.Strapi }) {
const extendContentType = (contentTypeUID: any) => {
strapi.get('content-types').extend(contentTypeUID, setReviewWorkflowAttributes);
};
@ -74,7 +74,7 @@ function extendReviewWorkflowContentTypes({ strapi }: { strapi: Core.LoadedStrap
])(strapi.contentTypes);
}
function persistStagesJoinTables({ strapi }: { strapi: Core.LoadedStrapi }) {
function persistStagesJoinTables({ strapi }: { strapi: Core.Strapi }) {
return async ({ contentTypes }: any) => {
const getStageTableToPersist = (contentTypeUID: any) => {
// Persist the stage join table
@ -98,12 +98,12 @@ function persistStagesJoinTables({ strapi }: { strapi: Core.LoadedStrapi }) {
};
}
const registerWebhookEvents = async ({ strapi }: { strapi: Core.LoadedStrapi }) =>
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.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const workflowsService = getService('workflows', { strapi });
const stagesService = getService('stages', { strapi });
const workflowsValidationService = getService('review-workflows-validation', { strapi });

View File

@ -7,7 +7,7 @@ import { STAGE_TRANSITION_UID } from '../../constants/workflows';
const { ApplicationError } = errors;
const validActions = [STAGE_TRANSITION_UID];
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const roleService = getService('role');
const permissionService = getService('permission');

View File

@ -8,7 +8,7 @@ const { ApplicationError, ValidationError } = errors;
const sanitizedStageFields = ['id', 'name', 'workflow', 'color'];
const sanitizeStageFields = pick(sanitizedStageFields);
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const metrics = getService('review-workflows-metrics', { strapi });
const stagePermissionsService = getService('stage-permissions', { strapi });
const workflowsValidationService = getService('review-workflows-validation', { strapi });

View File

@ -7,7 +7,7 @@ import { clampMaxWorkflows, clampMaxStagesPerWorkflow } from '../../utils/review
const { ValidationError } = errors;
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
return {
limits: {
numberOfWorkflows: MAX_WORKFLOWS,

View File

@ -4,7 +4,7 @@ import { difference, merge } from 'lodash/fp';
import { getService } from '../../../utils';
import { WORKFLOW_MODEL_UID } from '../../../constants/workflows';
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const contentManagerContentTypeService = strapi
.plugin('content-manager')
.service('content-types');

View File

@ -7,7 +7,7 @@ import { getWorkflowContentTypeFilter } from '../../../utils/review-workflows';
import workflowsContentTypesFactory from './content-types';
const { ApplicationError } = errors;
const processFilters = ({ strapi }: { strapi: Core.LoadedStrapi }, filters: any = {}) => {
const processFilters = ({ strapi }: { strapi: Core.Strapi }, filters: any = {}) => {
const processedFilters = { ...filters };
if (isString(filters.contentTypes)) {
@ -27,7 +27,7 @@ const processPopulate = (populate: any) => {
return WORKFLOW_POPULATE;
};
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const workflowsContentTypes = workflowsContentTypesFactory({ strapi });
const workflowsValidationService = getService('review-workflows-validation', { strapi });
const metrics = getService('review-workflows-metrics', { strapi });

View File

@ -23,7 +23,7 @@ const transformTableName = (table: string | PersistedTable) => {
* @param {RegExp} regex
* @returns {Promise<string[]>}
*/
export async function findTables({ strapi }: { strapi: Core.LoadedStrapi }, regex: any) {
export async function findTables({ strapi }: { strapi: Core.Strapi }, regex: any) {
// @ts-expect-error - getTables is not typed into the schema inspector
const tables = await strapi.db.dialect.schemaInspector.getTables();
return tables.filter((tableName: string) => regex.test(tableName));
@ -33,7 +33,7 @@ export async function findTables({ strapi }: { strapi: Core.LoadedStrapi }, rege
* Add tables name to the reserved tables in core store
*/
async function addPersistTables(
{ strapi }: { strapi: Core.LoadedStrapi },
{ strapi }: { strapi: Core.Strapi },
tableNames: Array<string | PersistedTable>
) {
const persistedTables = await getPersistedTables({ strapi });
@ -69,7 +69,7 @@ async function addPersistTables(
* @returns {Promise<string[]>}
*/
async function getPersistedTables({ strapi }: { strapi: Core.LoadedStrapi }) {
async function getPersistedTables({ strapi }: { strapi: Core.Strapi }) {
const persistedTables: any = await strapi.store.get({
type: 'core',
key: 'persisted_tables',
@ -86,7 +86,7 @@ async function getPersistedTables({ strapi }: { strapi: Core.LoadedStrapi }) {
* @returns {Promise<void>}
*/
async function setPersistedTables(
{ strapi }: { strapi: Core.LoadedStrapi },
{ strapi }: { strapi: Core.Strapi },
tableNames: Array<string | PersistedTable>
) {
await strapi.store.set({

View File

@ -20,7 +20,7 @@ export const getVisibleContentTypesUID = pipe([
export const hasStageAttribute = has(['attributes', ENTITY_STAGE_ATTRIBUTE]);
export const getWorkflowContentTypeFilter = (
{ strapi }: { strapi: Core.LoadedStrapi },
{ strapi }: { strapi: Core.Strapi },
contentType: any
) => {
if (strapi.db.dialect.supportsOperator('$jsonSupersetOf')) {

View File

@ -11,7 +11,7 @@ const providerOptionsUpdateSchema = yup.object().shape({
if (roleId === null) {
return true;
}
return strapi.admin.services.role.exists({ id: roleId });
return strapi.service('admin::role').exists({ id: roleId });
}),
ssoLockedRoles: yup
.array()
@ -23,7 +23,7 @@ const providerOptionsUpdateSchema = yup.object().shape({
'is-valid-role',
'You must submit a valid role for the SSO Locked roles',
(roleId) => {
return strapi.admin.services.role.exists({ id: roleId });
return strapi.service('admin::role').exists({ id: roleId });
}
)
),

View File

@ -21,10 +21,10 @@ const rolesDeleteSchema = yup
'Roles deletion checks have failed',
async function rolesDeletionChecks(ids) {
try {
await strapi.admin.services.role.checkRolesIdForDeletion(ids);
await strapi.service('admin::role').checkRolesIdForDeletion(ids);
if (strapi.ee.features.isEnabled('sso')) {
await strapi.admin.services.role.ssoCheckRolesIdForDeletion(ids);
await strapi.service('admin::role').ssoCheckRolesIdForDeletion(ids);
}
} catch (e: any) {
return this.createError({ path: 'ids', message: e.message });
@ -44,10 +44,10 @@ const roleDeleteSchema = yup
'Role deletion checks have failed',
async function noAdminSingleDelete(id) {
try {
await strapi.admin.services.role.checkRolesIdForDeletion([id]);
await strapi.service('admin::role').checkRolesIdForDeletion([id]);
if (strapi.ee.features.isEnabled('sso')) {
await strapi.admin.services.role.ssoCheckRolesIdForDeletion([id]);
await strapi.service('admin::role').ssoCheckRolesIdForDeletion([id]);
}
} catch (e: any) {
return this.createError({ path: 'id', message: e.message });

View File

@ -38,7 +38,7 @@ describe('Admin Controller', () => {
'packageJsonStrapi.telemetryDisabled',
null
);
expect(global.strapi.admin.services.user.exists).toHaveBeenCalled();
expect(global.strapi.service('admin::user').exists).toHaveBeenCalled();
expect(result.data).toBeDefined();
expect(result.data).toStrictEqual({
uuid: 'foo',

View File

@ -195,7 +195,7 @@ describe('Permission Controller', () => {
await permissionController.check(ctx);
expect(localTestData.ability.can).toHaveBeenCalled();
expect(strapi.admin.services.permission.engine.checkMany).toHaveBeenCalled();
expect(strapi.service('admin::permission').engine.checkMany).toHaveBeenCalled();
expect(ctx.body.data).toHaveLength(localTestData.permissions.valid.length);
});
});

View File

@ -58,7 +58,7 @@ export default {
async find(ctx: Context) {
const userService = getService('user');
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: ctx.state.userAbility,
model: 'admin::user',
});

View File

@ -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;
},

View File

@ -4,7 +4,7 @@ import adminAuthStrategy from './strategies/admin';
import apiTokenAuthStrategy from './strategies/api-token';
export default ({ strapi }: { strapi: Core.Strapi }) => {
const passportMiddleware = strapi.admin?.services.passport.init();
const passportMiddleware = strapi.service('admin::passport').init();
strapi.server.api('admin').use(passportMiddleware);
strapi.get('auth').register('admin', adminAuthStrategy);

View File

@ -11,7 +11,7 @@ const authEventsMapper = {
const valueIsFunctionType = ([, value]: [any, any]) => isFunction(value);
const keyIsValidEventName = ([key]: any) => {
return Object.keys(strapi.admin.services.passport.authEventsMapper).includes(key);
return Object.keys(strapi.service('admin::passport').authEventsMapper).includes(key);
};
const getPassportStrategies = () => [createLocalStrategy(strapi)] as Strategy[];
@ -19,7 +19,7 @@ const getPassportStrategies = () => [createLocalStrategy(strapi)] as Strategy[];
const registerAuthEvents = () => {
// @ts-expect-error - TODO: migrate auth service to TS
const { events = {} } = strapi.config.get('admin.auth', {});
const { authEventsMapper } = strapi.admin.services.passport;
const { authEventsMapper } = strapi.service('admin::passport');
const eventList = Object.entries(events).filter(keyIsValidEventName).filter(valueIsFunctionType);
@ -30,7 +30,8 @@ const registerAuthEvents = () => {
};
const init = () => {
strapi.admin.services.passport
strapi
.service('admin::passport')
.getPassportStrategies()
.forEach((strategy: Strategy) => passport.use(strategy));

View File

@ -341,7 +341,7 @@ const flattenTokenPermissions = (token: DatabaseTransferToken): TransferToken =>
* Assert that a token's permissions are valid
*/
const assertTokenPermissionsValidity = (attributes: TokenUpdatePayload) => {
const permissionService = strapi.admin.services.transfer.permission;
const permissionService = strapi.service('admin::transfer').permission;
const validPermissions = permissionService.providers.action.keys();
const invalidPermissions = difference(attributes.permissions, validPermissions);

View File

@ -41,7 +41,7 @@ export const arrayOfConditionNames = yup
.array()
.of(yup.string())
.test('is-an-array-of-conditions', 'is not a plugin name', function (value) {
const ids = strapi.admin.services.permission.conditionProvider.keys();
const ids = strapi.service('admin::permission').conditionProvider.keys();
return _.isUndefined(value) || _.difference(value, ids).length === 0
? true
: this.createError({ path: this.path, message: `contains conditions that don't exist` });

View File

@ -18,7 +18,7 @@ const rolesDeleteSchema = yup
.required()
.test('roles-deletion-checks', 'Roles deletion checks have failed', async function (ids) {
try {
await strapi.admin.services.role.checkRolesIdForDeletion(ids);
await strapi.service('admin::role').checkRolesIdForDeletion(ids);
} catch (e) {
// @ts-expect-error yup types
return this.createError({ path: 'ids', message: e.message });
@ -34,7 +34,7 @@ const roleDeleteSchema = yup
.required()
.test('no-admin-single-delete', 'Role deletion checks have failed', async function (id) {
try {
await strapi.admin.services.role.checkRolesIdForDeletion([id]);
await strapi.service('admin::role').checkRolesIdForDeletion([id]);
} catch (e) {
// @ts-expect-error yup types
return this.createError({ path: 'id', message: e.message });

View File

@ -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();

View File

@ -9,7 +9,7 @@ import { getSchemaAttributesDiff } from './utils';
const DEFAULT_RETENTION_DAYS = 90;
const createHistoryService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => {
const state: {
deleteExpiredJob: ReturnType<typeof scheduleJob> | null;
isInitialized: boolean;
@ -20,9 +20,12 @@ const createHistoryService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const query = strapi.db.query(HISTORY_VERSION_UID);
const getRetentionDays = (strapi: Core.LoadedStrapi) => {
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

View File

@ -15,11 +15,11 @@ export default async (ctx: Context, next: Next) => {
return ctx.send({ error: 'contentType.notFound' }, 404);
}
let target;
let controllers;
if (!ct.plugin || ct.plugin === 'admin') {
target = strapi.admin;
controllers = strapi.admin.controllers;
} else {
target = strapi.plugin(ct.plugin);
controllers = strapi.plugin(ct.plugin).controllers;
}
const { route }: { route: Core.Route } = ctx.state;
@ -41,7 +41,7 @@ export default async (ctx: Context, next: Next) => {
const [controller, action] = actionConfig.split('.');
if (controller && action) {
return target.controllers[controller.toLowerCase()][action](ctx, next);
return controllers[controller.toLowerCase()][action](ctx, next);
}
}

View File

@ -21,7 +21,7 @@ const configurationService = createConfigurationService({
},
});
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
export default ({ strapi }: { strapi: Core.Strapi }) => ({
findAllComponents() {
const { toContentManagerModel } = getService('data-mapper');

View File

@ -19,7 +19,7 @@ const configurationService = createConfigurationService({
},
});
const service = ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
const service = ({ strapi }: { strapi: Core.Strapi }) => ({
findAllContentTypes() {
const { toContentManagerModel } = getService('data-mapper');

View File

@ -36,7 +36,7 @@ const emitEvent = async (uid: UID.ContentType, event: string, document: Document
});
};
const documentManager = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const documentManager = ({ strapi }: { strapi: Core.Strapi }) => {
return {
async findOne(id: string, uid: UID.CollectionType, opts: DocServiceParams<'findOne'>[1] = {}) {
return strapi.documents(uid).findOne(id, opts);

View File

@ -69,7 +69,7 @@ export interface GetMetadataOptions {
availableStatus?: boolean;
}
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
export default ({ strapi }: { strapi: Core.Strapi }) => ({
/**
* Returns available locales of a document for the current status
*/

View File

@ -50,7 +50,7 @@ const fieldSizes: Record<string, FieldSize> = {
uid: defaultSize,
};
const createFieldSizesService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const createFieldSizesService = ({ strapi }: { strapi: Core.Strapi }) => {
const fieldSizesService = {
getAllFieldSizes() {
return fieldSizes;

View File

@ -5,7 +5,7 @@ import type { Configuration } from '../../../shared/contracts/content-types';
const { getRelationalFields } = relations;
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default ({ strapi }: { strapi: Core.Strapi }) => {
const sendDidConfigureListView = async (
contentType: Struct.ContentTypeSchema,
configuration: Configuration

View File

@ -19,9 +19,9 @@ type Query = {
};
const createPermissionChecker =
(strapi: Core.LoadedStrapi) =>
(strapi: Core.Strapi) =>
({ userAbility, model }: { userAbility: any; model: string }) => {
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: userAbility,
model,
});
@ -113,6 +113,6 @@ const createPermissionChecker =
};
};
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
export default ({ strapi }: { strapi: Core.Strapi }) => ({
create: createPermissionChecker(strapi),
});

View File

@ -4,7 +4,7 @@ import { contentTypes as contentTypesUtils } from '@strapi/utils';
import type { Core, Struct } from '@strapi/types';
import { getService } from '../utils';
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
export default ({ strapi }: { strapi: Core.Strapi }) => ({
canConfigureContentType({
userAbility,
contentType,
@ -91,6 +91,6 @@ export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
},
];
await strapi.admin.services.permission.actionProvider.registerMany(actions);
await strapi.service('admin::permission').actionProvider.registerMany(actions);
},
});

View File

@ -3,7 +3,7 @@ import slugify from '@sindresorhus/slugify';
import type { Core, Schema, UID } from '@strapi/types';
export default ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async generateUIDField({
contentTypeUID,
field,

View File

@ -15,6 +15,14 @@ const mockGraphQlShadowCrud = jest.fn(() => ({
}));
describe('register', () => {
const strapi = {
service(name: string) {
switch (name) {
case 'admin::permission':
return this.admin.services.permission;
default:
throw new Error(`Service ${name} not found`);
}
},
ee: {
features: {
isEnabled: jest.fn(),
@ -61,7 +69,7 @@ describe('register', () => {
strapi.ee.features.isEnabled.mockReturnValue(true);
register({ strapi });
expect(strapi.admin.services.permission.actionProvider.registerMany).toHaveBeenCalledWith(
expect(strapi.service('admin::permission').actionProvider.registerMany).toHaveBeenCalledWith(
ACTIONS
);
});
@ -70,7 +78,7 @@ describe('register', () => {
strapi.ee.features.isEnabled.mockReturnValue(false);
register({ strapi });
expect(strapi.admin.services.permission.actionProvider.registerMany).not.toHaveBeenCalled();
expect(strapi.service('admin::permission').actionProvider.registerMany).not.toHaveBeenCalled();
});
it('should exclude the release and release action models from the GraphQL schema when the feature is enabled', async () => {

View File

@ -4,7 +4,7 @@ import type { Core, Data, UID } from '@strapi/types';
import { RELEASE_ACTION_MODEL_UID, RELEASE_MODEL_UID, ALLOWED_WEBHOOK_EVENTS } from './constants';
import { getEntryValidStatus, getService } from './utils';
export const bootstrap = async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export const bootstrap = async ({ strapi }: { strapi: Core.Strapi }) => {
if (strapi.ee.features.isEnabled('cms-content-releases')) {
const contentTypesWithDraftAndPublish = Object.keys(strapi.contentTypes).filter(
(uid: any) => strapi.contentTypes[uid]?.options?.draftAndPublish
@ -154,7 +154,7 @@ export const bootstrap = async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
});
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
strapi.webhookStore.addAllowedEvent(key, value);
strapi.get('webhookStore').addAllowedEvent(key, value);
});
}
};

View File

@ -262,7 +262,7 @@ describe('Release controller', () => {
// @ts-expect-error partial context
await releaseController.findOne(ctx);
expect(strapi.admin.services.user.sanitizeUser).toHaveBeenCalled();
expect(strapi.service('admin::user').sanitizeUser).toHaveBeenCalled();
});
});
});

View File

@ -75,7 +75,7 @@ const releaseActionController = {
async findMany(ctx: Koa.Context) {
const releaseId: GetReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: ctx.state.userAbility,
model: RELEASE_ACTION_MODEL_UID,
});
@ -97,8 +97,9 @@ const releaseActionController = {
return acc;
}
const contentTypePermissionsManager =
strapi.admin.services.permission.createPermissionsManager({
const contentTypePermissionsManager = strapi
.service('admin::permission')
.createPermissionsManager({
ability: ctx.state.userAbility,
model: action.contentType,
});

View File

@ -19,7 +19,7 @@ type ReleaseWithPopulatedActions = Release & { actions: { count: number } };
const releaseController = {
async findMany(ctx: Koa.Context) {
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: ctx.state.userAbility,
model: RELEASE_MODEL_UID,
});
@ -89,7 +89,7 @@ const releaseController = {
const sanitizedRelease = {
...release,
createdBy: release.createdBy
? strapi.admin.services.user.sanitizeUser(release.createdBy)
? strapi.service('admin::user').sanitizeUser(release.createdBy)
: null,
};
@ -115,7 +115,7 @@ const releaseController = {
const releaseService = getService('release', { strapi });
const release = await releaseService.create(releaseArgs, { user });
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: ctx.state.userAbility,
model: RELEASE_MODEL_UID,
});
@ -135,7 +135,7 @@ const releaseController = {
const releaseService = getService('release', { strapi });
const release = await releaseService.update(id, releaseArgs, { user });
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
const permissionsManager = strapi.service('admin::permission').createPermissionsManager({
ability: ctx.state.userAbility,
model: RELEASE_MODEL_UID,
});

View File

@ -4,7 +4,7 @@ import { Core } from '@strapi/types';
import { Release } from '../../shared/contracts/releases';
import { getService } from './utils';
export const destroy = async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export const destroy = async ({ strapi }: { strapi: Core.Strapi }) => {
const scheduledJobs: Map<Release['id'], Job> = getService('scheduling', {
strapi,
}).getAll();

View File

@ -11,9 +11,9 @@ import {
enableContentTypeLocalized,
} from './migrations';
export const register = async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export const register = async ({ strapi }: { strapi: Core.Strapi }) => {
if (strapi.ee.features.isEnabled('cms-content-releases')) {
await strapi.admin.services.permission.actionProvider.registerMany(ACTIONS);
await strapi.service('admin::permission').actionProvider.registerMany(ACTIONS);
strapi
.hook('strapi::content-types.beforeSync')

View File

@ -48,7 +48,7 @@ const getGroupName = (queryValue?: ReleaseActionGroupBy) => {
}
};
const createReleaseService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const createReleaseService = ({ strapi }: { strapi: Core.Strapi }) => {
const dispatchWebhook = (
event: string,
{ isPublished, release, error }: { isPublished: boolean; release?: any; error?: unknown }

View File

@ -6,7 +6,7 @@ import { Release } from '../../../shared/contracts/releases';
import { getService } from '../utils';
import { RELEASE_MODEL_UID } from '../constants';
const createSchedulingService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const createSchedulingService = ({ strapi }: { strapi: Core.Strapi }) => {
const scheduledJobs = new Map<Release['id'], Job>();
return {
@ -21,7 +21,7 @@ const createSchedulingService = ({ strapi }: { strapi: Core.LoadedStrapi }) => {
const job = scheduleJob(scheduleDate, async () => {
try {
await getService('release').publish(releaseId);
await getService('release', { strapi }).publish(releaseId);
// @TODO: Trigger webhook with success message
} catch (error) {
// @TODO: Trigger webhook with error message

View File

@ -11,7 +11,7 @@ export class AlreadyOnReleaseError extends errors.ApplicationError<'AlreadyOnRel
}
}
const createReleaseValidationService = ({ strapi }: { strapi: Core.LoadedStrapi }) => ({
const createReleaseValidationService = ({ strapi }: { strapi: Core.Strapi }) => ({
async validateUniqueEntry(
releaseId: CreateReleaseAction.Request['params']['releaseId'],
releaseActionArgs: CreateReleaseAction.Request['body']
@ -60,8 +60,10 @@ const createReleaseValidationService = ({ strapi }: { strapi: Core.LoadedStrapi
},
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: {

View File

@ -1,8 +1,8 @@
import type { UID, Data } from '@strapi/types';
import type { UID, Data, Core } from '@strapi/types';
export const getService = (
name: 'release' | 'release-validation' | 'scheduling' | 'release-action' | 'event-manager',
{ strapi } = { strapi: global.strapi }
{ strapi }: { strapi: Core.Strapi }
) => {
return strapi.plugin('content-releases').service(name);
};
@ -10,7 +10,7 @@ export const getService = (
export const getPopulatedEntry = async (
contentTypeUid: UID.ContentType,
entryId: Data.ID,
{ strapi } = { strapi: global.strapi }
{ strapi }: { strapi: Core.Strapi }
) => {
const populateBuilderService = strapi.plugin('content-manager').service('populate-builder');
// @ts-expect-error - populateBuilderService should be a function but is returning service
@ -27,7 +27,7 @@ export const getPopulatedEntry = async (
export const getEntryValidStatus = async (
contentTypeUid: UID.ContentType,
entry: { id: Data.ID; [key: string]: any },
{ strapi } = { strapi: global.strapi }
{ strapi }: { strapi: Core.Strapi }
) => {
try {
// Same function used by entity-manager to validate entries before publishing

View File

@ -1,6 +1,6 @@
import type { Core } from '@strapi/types';
export default async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
export default async ({ strapi }: { strapi: Core.Strapi }) => {
const actions = [
{
section: 'plugins',
@ -10,5 +10,5 @@ export default async ({ strapi }: { strapi: Core.LoadedStrapi }) => {
},
];
await strapi.admin.services.permission.actionProvider.registerMany(actions);
await strapi.service('admin::permission').actionProvider.registerMany(actions);
};

View File

@ -3,29 +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';
@ -34,208 +30,113 @@ 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 {
return utils.ee.isEE;
}
return reload;
};
get ee(): Core.Strapi['ee'] {
return utils.ee;
}
export type LoadedStrapi = Required<Strapi>;
get dirs(): Core.StrapiDirectories {
return this.config.get('dirs');
}
class Strapi extends Container implements Core.Strapi {
server: Modules.Server.Server;
get reload(): Core.Reloader {
return this.get('reload');
}
log: Logger;
get db(): Database {
return this.get('db');
}
fs: Core.StrapiFS;
get requestContext(): Modules.RequestContext.RequestContext {
return this.get('requestContext');
}
eventHub: Modules.EventHub.EventHub;
get customFields(): Modules.CustomFields.CustomFields {
return this.get('customFields');
}
startupLogger: Core.StartupLogger;
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'),
},
},
})
);
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() {
@ -340,24 +241,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() {
@ -457,32 +379,25 @@ 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);
// @ts-expect-error: init is internal
this.ee.init(this.dirs.app.root, this.log);
this.get('models').add(coreStoreModel).add(webhookModel);
// init webhook runner
this.webhookRunner = createWebhookRunner({
eventHub: this.eventHub,
logger: this.log,
configuration: this.config.get('server.webhooks', {}),
fetch: this.fetch,
});
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);
@ -500,24 +415,6 @@ class Strapi extends Container implements Core.Strapi {
await this.db.init({ models });
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);
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({
@ -550,8 +447,6 @@ class Strapi extends Container implements Core.Strapi {
value: this.contentTypes,
});
await this.startWebhooks();
await this.server.initMiddlewares();
this.server.initRouting();
@ -559,41 +454,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 as this & Required<Core.Strapi>;
}
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)) {
@ -622,8 +515,8 @@ class Strapi extends Container implements Core.Strapi {
}
export interface StrapiOptions {
appDir?: string;
distDir?: string;
appDir: string;
distDir: string;
autoReload?: boolean;
serveAdminPanel?: boolean;
}

View File

@ -35,6 +35,7 @@ const RESTRICTED_FILENAMES = [
'packageJsonStrapi',
'info',
'autoReload',
'dirs',
// probably mistaken/typo filenames
...Object.keys(MISTAKEN_FILENAMES),

View File

@ -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)),
},
});

View File

@ -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;
};

View File

@ -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;

View File

@ -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 Core.LoadedStrapi;
global.strapi = strapi;
return strapi;
};

View File

@ -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);

View File

@ -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),

View File

@ -0,0 +1,22 @@
import { defineProvider } from './provider';
import loadAdmin from '../loaders/admin';
export default defineProvider({
init(strapi) {
strapi.add('admin', () => require('@strapi/admin/strapi-server'));
},
async register(strapi) {
await loadAdmin(strapi);
await strapi.get('admin')?.register({ strapi });
},
async bootstrap(strapi) {
await strapi.get('admin')?.bootstrap({ strapi });
},
async destroy(strapi) {
await strapi.get('admin')?.destroy({ strapi });
},
});

View File

@ -0,0 +1,9 @@
import { defineProvider } from './provider';
import { createCoreStore, coreStoreModel } from '../services/core-store';
export default defineProvider({
init(strapi) {
strapi.get('models').add(coreStoreModel);
strapi.add('coreStore', () => createCoreStore({ db: strapi.db }));
},
});

View File

@ -0,0 +1,19 @@
import { defineProvider } from './provider';
import createCronService from '../services/cron';
export default defineProvider({
init(strapi) {
strapi.add('cron', () => createCronService());
},
async bootstrap(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) {
strapi.get('cron').destroy();
},
});

View File

@ -0,0 +1,10 @@
import admin from './admin';
import coreStore from './coreStore';
import cron from './cron';
import registries from './registries';
import telemetry from './telemetry';
import webhooks from './webhooks';
import type { Provider } from './provider';
export const providers: Provider[] = [registries, admin, coreStore, webhooks, telemetry, cron];

View File

@ -0,0 +1,10 @@
import type { Core } from '@strapi/types';
export type Provider = {
init?: (strapi: Core.Strapi) => void;
register?: (strapi: Core.Strapi) => Promise<void>;
bootstrap?: (strapi: Core.Strapi) => Promise<void>;
destroy?: (strapi: Core.Strapi) => Promise<void>;
};
export const defineProvider = (provider: Provider) => provider;

View File

@ -0,0 +1,35 @@
import { hooks } from '@strapi/utils';
import { defineProvider } from './provider';
import * as registries from '../registries';
import { loadApplicationContext } from '../loaders';
import * as draftAndPublishSync from '../migrations/draft-publish';
export default defineProvider({
init(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) {
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);
},
});

View File

@ -0,0 +1,17 @@
import { defineProvider } from './provider';
import createTelemetry from '../services/metrics';
export default defineProvider({
init(strapi) {
strapi.add('telemetry', () => createTelemetry(strapi));
},
async register(strapi) {
strapi.get('telemetry').register();
},
async bootstrap(strapi) {
strapi.get('telemetry').bootstrap();
},
async destroy(strapi) {
strapi.get('telemetry').destroy();
},
});

View File

@ -0,0 +1,29 @@
import { defineProvider } from './provider';
import { createWebhookStore, webhookModel } from '../services/webhook-store';
import createWebhookRunner from '../services/webhook-runner';
export default defineProvider({
init(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) {
const webhooks = await strapi.get('webhookStore').findWebhooks();
if (!webhooks) {
return;
}
for (const webhook of webhooks) {
strapi.get('webhookRunner').add(webhook);
}
},
});

View File

@ -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' },
});

View File

@ -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';

View File

@ -1,13 +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.LoadedStrapi
export const createConfigProvider = (
initialConfig: Record<string, unknown> = {},
strapi?: Core.Strapi
): Core.ConfigProvider => {
const _config: Config = { ...initialConfig }; // not deep clone because it would break some config
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.'
@ -42,16 +48,16 @@ export default (
};
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));
},
};
};

View File

@ -27,7 +27,7 @@ describe('Extract document ids from relation data', () => {
db: {
query: jest.fn((uid) => ({ findMany: findManyQueries[uid] })),
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
});
it('Load single document id', async () => {

View File

@ -1,5 +1,3 @@
import type { Core } from '@strapi/types';
import { PRODUCT_UID, CATEGORY_UID, models } from './utils';
import { transformParamsDocumentId } from '../id-transform';
@ -39,7 +37,7 @@ describe('Transform relational data', () => {
db: {
query: jest.fn((uid) => ({ findMany: findManyQueries[uid] })),
},
} as unknown as Core.LoadedStrapi;
} as any;
beforeEach(() => {
findCategories.mockReturnValue([

View File

@ -1,5 +1,3 @@
import type { Core } from '@strapi/types';
import { PRODUCT_UID, SHOP_UID, models } from './utils';
import { transformParamsDocumentId } from '../id-transform';
@ -33,7 +31,7 @@ describe('Transform relational data', () => {
db: {
query: jest.fn((uid) => ({ findMany: findManyQueries[uid] })),
},
} as unknown as Core.LoadedStrapi;
} as any;
beforeEach(() => {
findShops.mockReturnValue([

View File

@ -33,7 +33,7 @@ describe('Transform relational data', () => {
db: {
query: jest.fn((uid) => ({ findMany: findManyQueries[uid] })),
},
} as unknown as Core.LoadedStrapi;
} as unknown as Core.Strapi;
beforeEach(() => {
findCategories.mockReturnValue([

View File

@ -1,4 +1,3 @@
import { Core } from '@strapi/types';
import { PRODUCT_UID, CATEGORY_UID, models } from './utils';
import { transformPopulate } from '../populate';
@ -23,7 +22,7 @@ describe('transformPopulate', () => {
})),
},
},
} as unknown as Core.LoadedStrapi;
} as any;
});
// TODO: Are these all realistic formats for populate?

View 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;
};

View File

@ -31,7 +31,7 @@ export default (strapi: Core.Strapi) => {
const registerAdminRoutes = (strapi: Core.Strapi) => {
const generateRouteScope = createRouteScopeGenerator(`admin::`);
_.forEach(strapi.admin?.routes, (router) => {
_.forEach(strapi.admin.routes, (router) => {
router.type = router.type || 'admin';
router.prefix = router.prefix || `/admin`;
router.routes.forEach((route) => {

View File

@ -106,8 +106,6 @@ export interface WebhookStore {
}
const createWebhookStore = ({ db }: { db: Database }): WebhookStore => {
const webhookQueries = db.query('strapi::webhook');
return {
allowedEvents: new Map([]),
addAllowedEvent(key, value) {
@ -123,18 +121,19 @@ const createWebhookStore = ({ db }: { db: Database }): WebhookStore => {
return this.allowedEvents.get(key);
},
async findWebhooks() {
const results = await webhookQueries.findMany();
const results = await db.query('strapi::webhook').findMany();
return results.map(fromDBObject);
},
async findWebhook(id) {
const result = await webhookQueries.findOne({ where: { id } });
const result = await db.query('strapi::webhook').findOne({ where: { id } });
return result ? fromDBObject(result) : null;
},
async createWebhook(data) {
await webhookEventValidator(this.allowedEvents, data.events);
return webhookQueries
return db
.query('strapi::webhook')
.create({
data: toDBObject({ ...data, isEnabled: true }),
})
@ -143,7 +142,7 @@ const createWebhookStore = ({ db }: { db: Database }): WebhookStore => {
async updateWebhook(id, data) {
await webhookEventValidator(this.allowedEvents, data.events);
const webhook = await webhookQueries.update({
const webhook = await db.query('strapi::webhook').update({
where: { id },
data: toDBObject(data),
});
@ -151,7 +150,7 @@ const createWebhookStore = ({ db }: { db: Database }): WebhookStore => {
return webhook ? fromDBObject(webhook) : null;
},
async deleteWebhook(id) {
const webhook = await webhookQueries.delete({ where: { id } });
const webhook = await db.query('strapi::webhook').delete({ where: { id } });
return webhook ? fromDBObject(webhook) : null;
},
};

View File

@ -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';

View 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 };
};

View File

@ -43,6 +43,7 @@ export const createUpdateNotifier = (strapi: Core.Strapi) => {
} catch {
// we don't have write access to the file system
// we silence the error
return;
}
const checkUpdate = async (checkInterval: number) => {
@ -83,21 +84,17 @@ 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.'
);
}
// 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(NOTIF_INTERVAL);
checkUpdate(CHECK_INTERVAL); // doesn't need to await
};

View File

@ -25,13 +25,13 @@ export const collect = <T = unknown>(stream: Readable): Promise<T[]> => {
export const getStrapiFactory =
<
T extends {
[key in keyof Partial<Core.LoadedStrapi>]: unknown;
[key in keyof Partial<Core.Strapi>]: unknown;
},
>(
properties?: T
) =>
(additionalProperties?: Partial<T>) => {
return { ...properties, ...additionalProperties } as Core.LoadedStrapi;
return { ...properties, ...additionalProperties } as Core.Strapi;
};
/**
@ -113,7 +113,7 @@ export const destinationStages = [
/**
* Update the global store with the given strapi value
*/
export const setGlobalStrapi = (strapi: Core.LoadedStrapi): void => {
export const setGlobalStrapi = (strapi: Core.Strapi): void => {
(global as unknown as Global).strapi = strapi;
};

View File

@ -25,7 +25,7 @@ export const VALID_CONFLICT_STRATEGIES = ['restore'];
export const DEFAULT_CONFLICT_STRATEGY = 'restore';
export interface ILocalStrapiDestinationProviderOptions {
getStrapi(): Core.LoadedStrapi | Promise<Core.LoadedStrapi>; // return an initialized instance of Strapi
getStrapi(): Core.Strapi | Promise<Core.Strapi>; // return an initialized instance of Strapi
autoDestroy?: boolean; // shut down the instance returned by getStrapi() at the end of the transfer
restore?: restore.IRestoreOptions; // erase data in strapi database before transfer; required if strategy is 'restore'
@ -39,7 +39,7 @@ class LocalStrapiDestinationProvider implements IDestinationProvider {
options: ILocalStrapiDestinationProviderOptions;
strapi?: Core.LoadedStrapi;
strapi?: Core.Strapi;
transaction?: Transaction;

View File

@ -7,10 +7,7 @@ import { IConfiguration, Transaction } from '../../../../../../types';
const omitInvalidCreationAttributes = omit(['id']);
const restoreCoreStore = async <T extends { value: unknown }>(
strapi: Core.LoadedStrapi,
values: T
) => {
const restoreCoreStore = async <T extends { value: unknown }>(strapi: Core.Strapi, values: T) => {
const data = omitInvalidCreationAttributes(values);
return strapi.db.query('strapi::core-store').create({
data: {
@ -20,15 +17,12 @@ const restoreCoreStore = async <T extends { value: unknown }>(
});
};
const restoreWebhooks = async <T extends { value: unknown }>(
strapi: Core.LoadedStrapi,
values: T
) => {
const restoreWebhooks = async <T extends { value: unknown }>(strapi: Core.Strapi, values: T) => {
const data = omitInvalidCreationAttributes(values);
return strapi.db.query('strapi::webhook').create({ data });
};
export const restoreConfigs = async (strapi: Core.LoadedStrapi, config: IConfiguration) => {
export const restoreConfigs = async (strapi: Core.Strapi, config: IConfiguration) => {
if (config.type === 'core-store') {
return restoreCoreStore(strapi, config.value as { value: unknown });
}
@ -39,7 +33,7 @@ export const restoreConfigs = async (strapi: Core.LoadedStrapi, config: IConfigu
};
export const createConfigurationWriteStream = async (
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
transaction?: Transaction
) => {
return new Writable({

View File

@ -10,7 +10,7 @@ import * as queries from '../../../../queries';
import { resolveComponentUID } from '../../../../../utils/components';
interface IEntitiesRestoreStreamOptions {
strapi: Core.LoadedStrapi;
strapi: Core.Strapi;
updateMappingTable<TSchemaUID extends UID.Schema>(
type: TSchemaUID,
oldID: number,

View File

@ -22,7 +22,7 @@ interface IDeleteResults {
aggregate: { [uid: string]: { count: number } };
}
export const deleteRecords = async (strapi: Core.LoadedStrapi, options: IRestoreOptions) => {
export const deleteRecords = async (strapi: Core.Strapi, options: IRestoreOptions) => {
const entities = await deleteEntitiesRecords(strapi, options);
const configuration = await deleteConfigurationRecords(strapi, options);
@ -34,7 +34,7 @@ export const deleteRecords = async (strapi: Core.LoadedStrapi, options: IRestore
};
const deleteEntitiesRecords = async (
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
options: IRestoreOptions = {}
): Promise<IDeleteResults> => {
const { entities } = options;
@ -112,7 +112,7 @@ const deleteEntitiesRecords = async (
};
const deleteConfigurationRecords = async (
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
options: IRestoreOptions = {}
): Promise<IDeleteResults> => {
const { coreStore = true, webhook = true } = options?.configuration ?? {};

View File

@ -26,7 +26,7 @@ const isForeignKeyConstraintError = (e: Error) => {
export const createLinksWriteStream = (
mapID: (uid: string, id: number) => number | undefined,
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
transaction?: Transaction,
onWarning?: (message: string) => void
) => {

View File

@ -8,7 +8,7 @@ import type { IAsset, IFile } from '../../../../types';
function getFileStream(
filepath: string,
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
isLocal = false
): PassThrough | ReadStream {
if (isLocal) {
@ -43,7 +43,7 @@ function getFileStream(
function getFileStats(
filepath: string,
strapi: Core.LoadedStrapi,
strapi: Core.Strapi,
isLocal = false
): Promise<{ size: number }> {
if (isLocal) {
@ -95,7 +95,7 @@ async function signFile(file: IFile) {
/**
* Generate and consume assets streams in order to stream each file individually
*/
export const createAssetsStream = (strapi: Core.LoadedStrapi): Duplex => {
export const createAssetsStream = (strapi: Core.Strapi): Duplex => {
const generator: () => AsyncGenerator<IAsset, void> = async function* () {
const stream: Readable = strapi.db
.queryBuilder('plugin::upload.file')

View File

@ -8,7 +8,7 @@ import type { IConfiguration } from '../../../../types';
/**
* Create a readable stream that export the Strapi app configuration
*/
export const createConfigurationStream = (strapi: Core.LoadedStrapi): Readable => {
export const createConfigurationStream = (strapi: Core.Strapi): Readable => {
return Readable.from(
(async function* configurationGenerator(): AsyncGenerator<IConfiguration> {
// Core Store

View File

@ -7,7 +7,7 @@ import { IEntity } from '../../../../types';
/**
* Generate and consume content-types streams in order to stream each entity individually
*/
export const createEntitiesStream = (strapi: Core.LoadedStrapi): Readable => {
export const createEntitiesStream = (strapi: Core.Strapi): Readable => {
const contentTypes: Struct.ContentTypeSchema[] = Object.values(strapi.contentTypes);
async function* contentTypeStreamGenerator() {

View File

@ -11,7 +11,7 @@ import * as utils from '../../../utils';
import { assertValidStrapi } from '../../../utils/providers';
export interface ILocalStrapiSourceProviderOptions {
getStrapi(): Core.LoadedStrapi | Promise<Core.LoadedStrapi>; // return an initialized instance of Strapi
getStrapi(): Core.Strapi | Promise<Core.Strapi>; // return an initialized instance of Strapi
autoDestroy?: boolean; // shut down the instance returned by getStrapi() at the end of the transfer
}
@ -27,7 +27,7 @@ class LocalStrapiSourceProvider implements ISourceProvider {
options: ILocalStrapiSourceProviderOptions;
strapi?: Core.LoadedStrapi;
strapi?: Core.Strapi;
constructor(options: ILocalStrapiSourceProviderOptions) {
this.options = options;

View File

@ -7,7 +7,7 @@ import { createLinkQuery } from '../../queries/link';
/**
* Create a Readable which will stream all the links from a Strapi instance
*/
export const createLinksStream = (strapi: Core.LoadedStrapi): Readable => {
export const createLinksStream = (strapi: Core.Strapi): Readable => {
const uids = [...Object.keys(strapi.contentTypes), ...Object.keys(strapi.components)] as string[];
// Async generator stream that returns every link from a Strapi instance

View File

@ -18,7 +18,7 @@ const sanitizeComponentLikeAttributes = <T extends Struct.Schema>(
const omitInvalidCreationAttributes = omit(['id']);
const createEntityQuery = (strapi: Core.LoadedStrapi): any => {
const createEntityQuery = (strapi: Core.Strapi): any => {
const components = {
async assignToEntity(uid: UID.Schema, data: any) {
const model = strapi.getModel(uid);

View File

@ -6,7 +6,7 @@ import { ILink } from '../../../types';
// TODO: Remove any types when we'll have types for DB metadata
export const createLinkQuery = (strapi: Core.LoadedStrapi, trx?: Knex.Transaction) => {
export const createLinkQuery = (strapi: Core.Strapi, trx?: Knex.Transaction) => {
const query = () => {
const { connection } = strapi.db;

View File

@ -304,7 +304,7 @@ export const createPullController = handlerControllerFactory<Partial<PullHandler
this.provider = createLocalStrapiSourceProvider({
autoDestroy: false,
getStrapi: () => strapi as Core.LoadedStrapi,
getStrapi: () => strapi as Core.Strapi,
});
return { transferID: this.transferID };

Some files were not shown because too many files have changed in this diff Show More