@strapi/content-releases 0.0.0-next.37dd1e3ff22e1635b69683abadd444912ae0dbff → 0.0.0-next.44f19b3d2f81d983c343a219aa2781ee0deecb5f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  const utils = require("@strapi/utils");
3
+ const lodash = require("lodash");
3
4
  const _ = require("lodash/fp");
5
+ const EE = require("@strapi/strapi/dist/utils/ee");
4
6
  const yup = require("yup");
5
7
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
6
8
  function _interopNamespace(e) {
@@ -22,6 +24,7 @@ function _interopNamespace(e) {
22
24
  return Object.freeze(n);
23
25
  }
24
26
  const ___default = /* @__PURE__ */ _interopDefault(_);
27
+ const EE__default = /* @__PURE__ */ _interopDefault(EE);
25
28
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
26
29
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
27
30
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -69,40 +72,47 @@ const ACTIONS = [
69
72
  pluginName: "content-releases"
70
73
  }
71
74
  ];
72
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
73
- return strapi2.plugin("content-releases").service(name);
74
- };
75
+ async function deleteActionsOnDisableDraftAndPublish({
76
+ oldContentTypes,
77
+ contentTypes: contentTypes2
78
+ }) {
79
+ if (!oldContentTypes) {
80
+ return;
81
+ }
82
+ for (const uid in contentTypes2) {
83
+ if (!oldContentTypes[uid]) {
84
+ continue;
85
+ }
86
+ const oldContentType = oldContentTypes[uid];
87
+ const contentType = contentTypes2[uid];
88
+ if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
89
+ await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
90
+ }
91
+ }
92
+ }
93
+ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
94
+ const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
95
+ if (deletedContentTypes.length) {
96
+ await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
97
+ return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
98
+ });
99
+ }
100
+ }
75
101
  const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
76
102
  const register = async ({ strapi: strapi2 }) => {
77
- if (features$2.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
103
+ if (features$2.isEnabled("cms-content-releases")) {
78
104
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
79
- const releaseActionService = getService("release-action", { strapi: strapi2 });
80
- const eventManager = getService("event-manager", { strapi: strapi2 });
81
- const destroyContentTypeUpdateListener = strapi2.eventHub.on(
82
- "content-type.update",
83
- async ({ contentType }) => {
84
- if (contentType.schema.options.draftAndPublish === false) {
85
- await releaseActionService.deleteManyForContentType(contentType.uid);
86
- }
87
- }
88
- );
89
- eventManager.addDestroyListenerCallback(destroyContentTypeUpdateListener);
90
- const destroyContentTypeDeleteListener = strapi2.eventHub.on(
91
- "content-type.delete",
92
- async ({ contentType }) => {
93
- await releaseActionService.deleteManyForContentType(contentType.uid);
94
- }
95
- );
96
- eventManager.addDestroyListenerCallback(destroyContentTypeDeleteListener);
105
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
106
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
97
107
  }
98
108
  };
99
109
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
100
110
  const bootstrap = async ({ strapi: strapi2 }) => {
101
- if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
111
+ if (features$1.isEnabled("cms-content-releases")) {
102
112
  strapi2.db.lifecycles.subscribe({
103
113
  afterDelete(event) {
104
114
  const { model, result } = event;
105
- if (model.kind === "collectionType" && model.options.draftAndPublish) {
115
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
106
116
  const { id } = result;
107
117
  strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
108
118
  where: {
@@ -118,7 +128,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
118
128
  */
119
129
  async beforeDeleteMany(event) {
120
130
  const { model, params } = event;
121
- if (model.kind === "collectionType" && model.options.draftAndPublish) {
131
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
122
132
  const { where } = params;
123
133
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
124
134
  event.state.entriesToDelete = entriesToDelete;
@@ -233,30 +243,32 @@ const contentTypes = {
233
243
  release: release$1,
234
244
  "release-action": releaseAction$1
235
245
  };
236
- const createReleaseActionService = ({ strapi: strapi2 }) => ({
237
- async deleteManyForContentType(contentTypeUid) {
238
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
239
- where: {
240
- target_type: contentTypeUid
241
- }
242
- });
243
- }
244
- });
246
+ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
247
+ return strapi2.plugin("content-releases").service(name);
248
+ };
245
249
  const getGroupName = (queryValue) => {
246
250
  switch (queryValue) {
247
251
  case "contentType":
248
- return "entry.contentType.displayName";
252
+ return "contentType.displayName";
249
253
  case "action":
250
254
  return "type";
251
255
  case "locale":
252
- return ___default.default.getOr("No locale", "entry.locale.name");
256
+ return ___default.default.getOr("No locale", "locale.name");
253
257
  default:
254
- return "entry.contentType.displayName";
258
+ return "contentType.displayName";
255
259
  }
256
260
  };
257
261
  const createReleaseService = ({ strapi: strapi2 }) => ({
258
262
  async create(releaseData, { user }) {
259
263
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
264
+ const { validatePendingReleasesLimit, validateUniqueNameForPendingRelease } = getService(
265
+ "release-validation",
266
+ { strapi: strapi2 }
267
+ );
268
+ await Promise.all([
269
+ validatePendingReleasesLimit(),
270
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
271
+ ]);
260
272
  return strapi2.entityService.create(RELEASE_MODEL_UID, {
261
273
  data: releaseWithCreatorFields
262
274
  });
@@ -278,51 +290,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
278
290
  }
279
291
  });
280
292
  },
281
- async findManyForContentTypeEntry(contentTypeUid, entryId, {
282
- hasEntryAttached
283
- } = {
284
- hasEntryAttached: false
285
- }) {
286
- const whereActions = hasEntryAttached ? {
287
- // Find all Releases where the content type entry is present
288
- actions: {
289
- target_type: contentTypeUid,
290
- target_id: entryId
291
- }
292
- } : {
293
- // Find all Releases where the content type entry is not present
294
- $or: [
295
- {
296
- $not: {
297
- actions: {
298
- target_type: contentTypeUid,
299
- target_id: entryId
300
- }
301
- }
293
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
294
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
295
+ where: {
296
+ actions: {
297
+ target_type: contentTypeUid,
298
+ target_id: entryId
302
299
  },
303
- {
304
- actions: null
300
+ releasedAt: {
301
+ $null: true
305
302
  }
306
- ]
307
- };
308
- const populateAttachedAction = hasEntryAttached ? {
309
- // Filter the action to get only the content type entry
310
- actions: {
311
- where: {
303
+ },
304
+ populate: {
305
+ // Filter the action to get only the content type entry
306
+ actions: {
307
+ where: {
308
+ target_type: contentTypeUid,
309
+ target_id: entryId
310
+ }
311
+ }
312
+ }
313
+ });
314
+ return releases.map((release2) => {
315
+ if (release2.actions?.length) {
316
+ const [actionForEntry] = release2.actions;
317
+ delete release2.actions;
318
+ return {
319
+ ...release2,
320
+ action: actionForEntry
321
+ };
322
+ }
323
+ return release2;
324
+ });
325
+ },
326
+ async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
327
+ const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
328
+ where: {
329
+ releasedAt: {
330
+ $null: true
331
+ },
332
+ actions: {
312
333
  target_type: contentTypeUid,
313
334
  target_id: entryId
314
335
  }
315
336
  }
316
- } : {};
337
+ });
317
338
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
318
339
  where: {
319
- ...whereActions,
340
+ $or: [
341
+ {
342
+ id: {
343
+ $notIn: releasesRelated.map((release2) => release2.id)
344
+ }
345
+ },
346
+ {
347
+ actions: null
348
+ }
349
+ ],
320
350
  releasedAt: {
321
351
  $null: true
322
352
  }
323
- },
324
- populate: {
325
- ...populateAttachedAction
326
353
  }
327
354
  });
328
355
  return releases.map((release2) => {
@@ -397,7 +424,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
397
424
  return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
398
425
  ...query,
399
426
  populate: {
400
- entry: true
427
+ entry: {
428
+ populate: "*"
429
+ }
401
430
  },
402
431
  filters: {
403
432
  release: releaseId
@@ -417,29 +446,32 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
417
446
  const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
418
447
  contentTypeUids
419
448
  );
420
- const allLocales = await strapi2.plugin("i18n").service("locales").find();
421
- const allLocalesDictionary = allLocales.reduce((acc, locale) => {
422
- acc[locale.code] = { name: locale.name, code: locale.code };
423
- return acc;
424
- }, {});
449
+ const allLocalesDictionary = await this.getLocalesDataForActions();
425
450
  const formattedData = actions.map((action) => {
426
451
  const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
427
452
  return {
428
453
  ...action,
429
- entry: {
430
- id: action.entry.id,
431
- contentType: {
432
- displayName,
433
- mainFieldValue: action.entry[mainField]
434
- },
435
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
436
- status: action.entry.publishedAt ? "published" : "draft"
454
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
455
+ contentType: {
456
+ displayName,
457
+ mainFieldValue: action.entry[mainField],
458
+ uid: action.contentType
437
459
  }
438
460
  };
439
461
  });
440
462
  const groupName = getGroupName(groupBy);
441
463
  return ___default.default.groupBy(groupName)(formattedData);
442
464
  },
465
+ async getLocalesDataForActions() {
466
+ if (!strapi2.plugin("i18n")) {
467
+ return {};
468
+ }
469
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
470
+ return allLocales.reduce((acc, locale) => {
471
+ acc[locale.code] = { name: locale.name, code: locale.code };
472
+ return acc;
473
+ }, {});
474
+ },
443
475
  async getContentTypesDataForActions(contentTypesUids) {
444
476
  const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
445
477
  const contentTypesData = {};
@@ -454,6 +486,34 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
454
486
  }
455
487
  return contentTypesData;
456
488
  },
489
+ getContentTypeModelsFromActions(actions) {
490
+ const contentTypeUids = actions.reduce((acc, action) => {
491
+ if (!acc.includes(action.contentType)) {
492
+ acc.push(action.contentType);
493
+ }
494
+ return acc;
495
+ }, []);
496
+ const contentTypeModelsMap = contentTypeUids.reduce(
497
+ (acc, contentTypeUid) => {
498
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
499
+ return acc;
500
+ },
501
+ {}
502
+ );
503
+ return contentTypeModelsMap;
504
+ },
505
+ async getAllComponents() {
506
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
507
+ const components = await contentManagerComponentsService.findAllComponents();
508
+ const componentsMap = components.reduce(
509
+ (acc, component) => {
510
+ acc[component.uid] = component;
511
+ return acc;
512
+ },
513
+ {}
514
+ );
515
+ return componentsMap;
516
+ },
457
517
  async delete(releaseId) {
458
518
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
459
519
  populate: {
@@ -488,7 +548,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
488
548
  populate: {
489
549
  actions: {
490
550
  populate: {
491
- entry: true
551
+ entry: {
552
+ fields: ["id"]
553
+ }
492
554
  }
493
555
  }
494
556
  }
@@ -503,30 +565,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
503
565
  if (releaseWithPopulatedActionEntries.actions.length === 0) {
504
566
  throw new utils.errors.ValidationError("No entries to publish");
505
567
  }
506
- const actions = {};
568
+ const collectionTypeActions = {};
569
+ const singleTypeActions = [];
507
570
  for (const action of releaseWithPopulatedActionEntries.actions) {
508
571
  const contentTypeUid = action.contentType;
509
- if (!actions[contentTypeUid]) {
510
- actions[contentTypeUid] = {
511
- publish: [],
512
- unpublish: []
513
- };
514
- }
515
- if (action.type === "publish") {
516
- actions[contentTypeUid].publish.push(action.entry);
572
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
573
+ if (!collectionTypeActions[contentTypeUid]) {
574
+ collectionTypeActions[contentTypeUid] = {
575
+ entriestoPublishIds: [],
576
+ entriesToUnpublishIds: []
577
+ };
578
+ }
579
+ if (action.type === "publish") {
580
+ collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
581
+ } else {
582
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
583
+ }
517
584
  } else {
518
- actions[contentTypeUid].unpublish.push(action.entry);
585
+ singleTypeActions.push({
586
+ uid: contentTypeUid,
587
+ action: action.type,
588
+ id: action.entry.id
589
+ });
519
590
  }
520
591
  }
521
592
  const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
593
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
522
594
  await strapi2.db.transaction(async () => {
523
- for (const contentTypeUid of Object.keys(actions)) {
524
- const { publish, unpublish } = actions[contentTypeUid];
525
- if (publish.length > 0) {
526
- await entityManagerService.publishMany(publish, contentTypeUid);
595
+ for (const { uid, action, id } of singleTypeActions) {
596
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
597
+ const entry = await strapi2.entityService.findOne(uid, id, { populate });
598
+ try {
599
+ if (action === "publish") {
600
+ await entityManagerService.publish(entry, uid);
601
+ } else {
602
+ await entityManagerService.unpublish(entry, uid);
603
+ }
604
+ } catch (error) {
605
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
606
+ ;
607
+ else {
608
+ throw error;
609
+ }
527
610
  }
528
- if (unpublish.length > 0) {
529
- await entityManagerService.unpublishMany(unpublish, contentTypeUid);
611
+ }
612
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
613
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
614
+ const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
615
+ const entriesToPublish = await strapi2.entityService.findMany(
616
+ contentTypeUid,
617
+ {
618
+ filters: {
619
+ id: {
620
+ $in: entriestoPublishIds
621
+ }
622
+ },
623
+ populate
624
+ }
625
+ );
626
+ const entriesToUnpublish = await strapi2.entityService.findMany(
627
+ contentTypeUid,
628
+ {
629
+ filters: {
630
+ id: {
631
+ $in: entriesToUnpublishIds
632
+ }
633
+ },
634
+ populate
635
+ }
636
+ );
637
+ if (entriesToPublish.length > 0) {
638
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
639
+ }
640
+ if (entriesToUnpublish.length > 0) {
641
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
530
642
  }
531
643
  }
532
644
  });
@@ -608,31 +720,41 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
608
720
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
609
721
  );
610
722
  }
611
- }
612
- });
613
- const createEventManagerService = () => {
614
- const state = {
615
- destroyListenerCallbacks: []
616
- };
617
- return {
618
- addDestroyListenerCallback(destroyListenerCallback) {
619
- state.destroyListenerCallbacks.push(destroyListenerCallback);
620
- },
621
- destroyAllListeners() {
622
- if (!state.destroyListenerCallbacks.length) {
623
- return;
723
+ },
724
+ async validatePendingReleasesLimit() {
725
+ const maximumPendingReleases = (
726
+ // @ts-expect-error - options is not typed into features
727
+ EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
728
+ );
729
+ const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
730
+ filters: {
731
+ releasedAt: {
732
+ $null: true
733
+ }
624
734
  }
625
- state.destroyListenerCallbacks.forEach((destroyListenerCallback) => {
626
- destroyListenerCallback();
627
- });
735
+ });
736
+ if (pendingReleasesCount >= maximumPendingReleases) {
737
+ throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
628
738
  }
629
- };
630
- };
739
+ },
740
+ async validateUniqueNameForPendingRelease(name) {
741
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
742
+ filters: {
743
+ releasedAt: {
744
+ $null: true
745
+ },
746
+ name
747
+ }
748
+ });
749
+ const isNameUnique = pendingReleases.length === 0;
750
+ if (!isNameUnique) {
751
+ throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
752
+ }
753
+ }
754
+ });
631
755
  const services = {
632
756
  release: createReleaseService,
633
- "release-action": createReleaseActionService,
634
- "release-validation": createReleaseValidationService,
635
- "event-manager": createEventManagerService
757
+ "release-validation": createReleaseValidationService
636
758
  };
637
759
  const RELEASE_SCHEMA = yup__namespace.object().shape({
638
760
  name: yup__namespace.string().trim().required()
@@ -652,9 +774,7 @@ const releaseController = {
652
774
  const contentTypeUid = query.contentTypeUid;
653
775
  const entryId = query.entryId;
654
776
  const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
655
- const data = await releaseService.findManyForContentTypeEntry(contentTypeUid, entryId, {
656
- hasEntryAttached
657
- });
777
+ const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
658
778
  ctx.body = { data };
659
779
  } else {
660
780
  const query = await permissionsManager.sanitizeQuery(ctx.query);
@@ -742,8 +862,27 @@ const releaseController = {
742
862
  const id = ctx.params.id;
743
863
  const releaseService = getService("release", { strapi });
744
864
  const release2 = await releaseService.publish(id, { user });
865
+ const [countPublishActions, countUnpublishActions] = await Promise.all([
866
+ releaseService.countActions({
867
+ filters: {
868
+ release: id,
869
+ type: "publish"
870
+ }
871
+ }),
872
+ releaseService.countActions({
873
+ filters: {
874
+ release: id,
875
+ type: "unpublish"
876
+ }
877
+ })
878
+ ]);
745
879
  ctx.body = {
746
- data: release2
880
+ data: release2,
881
+ meta: {
882
+ totalEntries: countPublishActions + countUnpublishActions,
883
+ totalPublishedEntries: countPublishActions,
884
+ totalUnpublishedEntries: countUnpublishActions
885
+ }
747
886
  };
748
887
  }
749
888
  };
@@ -782,11 +921,30 @@ const releaseActionController = {
782
921
  sort: query.groupBy === "action" ? "type" : query.groupBy,
783
922
  ...query
784
923
  });
785
- const groupedData = await releaseService.groupActions(results, query.groupBy);
924
+ const contentTypeOutputSanitizers = results.reduce((acc, action) => {
925
+ if (acc[action.contentType]) {
926
+ return acc;
927
+ }
928
+ const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
929
+ ability: ctx.state.userAbility,
930
+ model: action.contentType
931
+ });
932
+ acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
933
+ return acc;
934
+ }, {});
935
+ const sanitizedResults = await utils.mapAsync(results, async (action) => ({
936
+ ...action,
937
+ entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
938
+ }));
939
+ const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
940
+ const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
941
+ const components = await releaseService.getAllComponents();
786
942
  ctx.body = {
787
943
  data: groupedData,
788
944
  meta: {
789
- pagination
945
+ pagination,
946
+ contentTypes: contentTypes2,
947
+ components
790
948
  }
791
949
  };
792
950
  },
@@ -992,19 +1150,14 @@ const routes = {
992
1150
  };
993
1151
  const { features } = require("@strapi/strapi/dist/utils/ee");
994
1152
  const getPlugin = () => {
995
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1153
+ if (features.isEnabled("cms-content-releases")) {
996
1154
  return {
997
1155
  register,
998
1156
  bootstrap,
999
1157
  contentTypes,
1000
1158
  services,
1001
1159
  controllers,
1002
- routes,
1003
- destroy() {
1004
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1005
- getService("event-manager").destroyAllListeners();
1006
- }
1007
- }
1160
+ routes
1008
1161
  };
1009
1162
  }
1010
1163
  return {