@strapi/content-releases 4.18.1-experimental.0 → 4.19.0

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,10 +72,87 @@ const ACTIONS = [
69
72
  pluginName: "content-releases"
70
73
  }
71
74
  ];
72
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
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
+ }
101
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
73
102
  const register = async ({ strapi: strapi2 }) => {
74
- if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
103
+ if (features$2.isEnabled("cms-content-releases")) {
75
104
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
105
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
106
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
107
+ }
108
+ };
109
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
110
+ const bootstrap = async ({ strapi: strapi2 }) => {
111
+ if (features$1.isEnabled("cms-content-releases")) {
112
+ strapi2.db.lifecycles.subscribe({
113
+ afterDelete(event) {
114
+ const { model, result } = event;
115
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
116
+ const { id } = result;
117
+ strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
118
+ where: {
119
+ target_type: model.uid,
120
+ target_id: id
121
+ }
122
+ });
123
+ }
124
+ },
125
+ /**
126
+ * deleteMany hook doesn't return the deleted entries ids
127
+ * so we need to fetch them before deleting the entries to save the ids on our state
128
+ */
129
+ async beforeDeleteMany(event) {
130
+ const { model, params } = event;
131
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
132
+ const { where } = params;
133
+ const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
134
+ event.state.entriesToDelete = entriesToDelete;
135
+ }
136
+ },
137
+ /**
138
+ * We delete the release actions related to deleted entries
139
+ * We make this only after deleteMany is succesfully executed to avoid errors
140
+ */
141
+ async afterDeleteMany(event) {
142
+ const { model, state } = event;
143
+ const entriesToDelete = state.entriesToDelete;
144
+ if (entriesToDelete) {
145
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
146
+ where: {
147
+ target_type: model.uid,
148
+ target_id: {
149
+ $in: entriesToDelete.map((entry) => entry.id)
150
+ }
151
+ }
152
+ });
153
+ }
154
+ }
155
+ });
76
156
  }
77
157
  };
78
158
  const schema$1 = {
@@ -169,18 +249,26 @@ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
169
249
  const getGroupName = (queryValue) => {
170
250
  switch (queryValue) {
171
251
  case "contentType":
172
- return "entry.contentType.displayName";
252
+ return "contentType.displayName";
173
253
  case "action":
174
254
  return "type";
175
255
  case "locale":
176
- return ___default.default.getOr("No locale", "entry.locale.name");
256
+ return ___default.default.getOr("No locale", "locale.name");
177
257
  default:
178
- return "entry.contentType.displayName";
258
+ return "contentType.displayName";
179
259
  }
180
260
  };
181
261
  const createReleaseService = ({ strapi: strapi2 }) => ({
182
262
  async create(releaseData, { user }) {
183
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
+ ]);
184
272
  return strapi2.entityService.create(RELEASE_MODEL_UID, {
185
273
  data: releaseWithCreatorFields
186
274
  });
@@ -202,51 +290,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
202
290
  }
203
291
  });
204
292
  },
205
- async findManyForContentTypeEntry(contentTypeUid, entryId, {
206
- hasEntryAttached
207
- } = {
208
- hasEntryAttached: false
209
- }) {
210
- const whereActions = hasEntryAttached ? {
211
- // Find all Releases where the content type entry is present
212
- actions: {
213
- target_type: contentTypeUid,
214
- target_id: entryId
215
- }
216
- } : {
217
- // Find all Releases where the content type entry is not present
218
- $or: [
219
- {
220
- $not: {
221
- actions: {
222
- target_type: contentTypeUid,
223
- target_id: entryId
224
- }
225
- }
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
226
299
  },
227
- {
228
- actions: null
300
+ releasedAt: {
301
+ $null: true
229
302
  }
230
- ]
231
- };
232
- const populateAttachedAction = hasEntryAttached ? {
233
- // Filter the action to get only the content type entry
234
- actions: {
235
- 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: {
236
333
  target_type: contentTypeUid,
237
334
  target_id: entryId
238
335
  }
239
336
  }
240
- } : {};
337
+ });
241
338
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
242
339
  where: {
243
- ...whereActions,
340
+ $or: [
341
+ {
342
+ id: {
343
+ $notIn: releasesRelated.map((release2) => release2.id)
344
+ }
345
+ },
346
+ {
347
+ actions: null
348
+ }
349
+ ],
244
350
  releasedAt: {
245
351
  $null: true
246
352
  }
247
- },
248
- populate: {
249
- ...populateAttachedAction
250
353
  }
251
354
  });
252
355
  return releases.map((release2) => {
@@ -262,19 +365,23 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
262
365
  });
263
366
  },
264
367
  async update(id, releaseData, { user }) {
265
- const updatedRelease = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
266
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
368
+ const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
369
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
370
+ if (!release2) {
371
+ throw new utils.errors.NotFoundError(`No release found for id ${id}`);
372
+ }
373
+ if (release2.releasedAt) {
374
+ throw new utils.errors.ValidationError("Release already published");
375
+ }
376
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
267
377
  /*
268
378
  * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
269
379
  * is not compatible with the type we are passing here: UpdateRelease.Request['body']
270
380
  */
271
381
  // @ts-expect-error see above
272
- data: updatedRelease
382
+ data: releaseWithCreatorFields
273
383
  });
274
- if (!release2) {
275
- throw new utils.errors.NotFoundError(`No release found for id ${id}`);
276
- }
277
- return release2;
384
+ return updatedRelease;
278
385
  },
279
386
  async createAction(releaseId, action) {
280
387
  const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
@@ -284,6 +391,13 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
284
391
  validateEntryContentType(action.entry.contentType),
285
392
  validateUniqueEntry(releaseId, action)
286
393
  ]);
394
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
395
+ if (!release2) {
396
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
397
+ }
398
+ if (release2.releasedAt) {
399
+ throw new utils.errors.ValidationError("Release already published");
400
+ }
287
401
  const { entry, type } = action;
288
402
  return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
289
403
  data: {
@@ -310,7 +424,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
310
424
  return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
311
425
  ...query,
312
426
  populate: {
313
- entry: true
427
+ entry: {
428
+ populate: "*"
429
+ }
314
430
  },
315
431
  filters: {
316
432
  release: releaseId
@@ -330,29 +446,32 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
330
446
  const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
331
447
  contentTypeUids
332
448
  );
333
- const allLocales = await strapi2.plugin("i18n").service("locales").find();
334
- const allLocalesDictionary = allLocales.reduce((acc, locale) => {
335
- acc[locale.code] = { name: locale.name, code: locale.code };
336
- return acc;
337
- }, {});
449
+ const allLocalesDictionary = await this.getLocalesDataForActions();
338
450
  const formattedData = actions.map((action) => {
339
451
  const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
340
452
  return {
341
453
  ...action,
342
- entry: {
343
- id: action.entry.id,
344
- contentType: {
345
- displayName,
346
- mainFieldValue: action.entry[mainField]
347
- },
348
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
349
- 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
350
459
  }
351
460
  };
352
461
  });
353
462
  const groupName = getGroupName(groupBy);
354
463
  return ___default.default.groupBy(groupName)(formattedData);
355
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
+ },
356
475
  async getContentTypesDataForActions(contentTypesUids) {
357
476
  const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
358
477
  const contentTypesData = {};
@@ -367,6 +486,34 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
367
486
  }
368
487
  return contentTypesData;
369
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
+ },
370
517
  async delete(releaseId) {
371
518
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
372
519
  populate: {
@@ -401,7 +548,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
401
548
  populate: {
402
549
  actions: {
403
550
  populate: {
404
- entry: true
551
+ entry: {
552
+ fields: ["id"]
553
+ }
405
554
  }
406
555
  }
407
556
  }
@@ -421,25 +570,49 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
421
570
  const contentTypeUid = action.contentType;
422
571
  if (!actions[contentTypeUid]) {
423
572
  actions[contentTypeUid] = {
424
- publish: [],
425
- unpublish: []
573
+ entriestoPublishIds: [],
574
+ entriesToUnpublishIds: []
426
575
  };
427
576
  }
428
577
  if (action.type === "publish") {
429
- actions[contentTypeUid].publish.push(action.entry);
578
+ actions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
430
579
  } else {
431
- actions[contentTypeUid].unpublish.push(action.entry);
580
+ actions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
432
581
  }
433
582
  }
434
583
  const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
584
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
435
585
  await strapi2.db.transaction(async () => {
436
586
  for (const contentTypeUid of Object.keys(actions)) {
437
- const { publish, unpublish } = actions[contentTypeUid];
438
- if (publish.length > 0) {
439
- await entityManagerService.publishMany(publish, contentTypeUid);
587
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
588
+ const { entriestoPublishIds, entriesToUnpublishIds } = actions[contentTypeUid];
589
+ const entriesToPublish = await strapi2.entityService.findMany(
590
+ contentTypeUid,
591
+ {
592
+ filters: {
593
+ id: {
594
+ $in: entriestoPublishIds
595
+ }
596
+ },
597
+ populate
598
+ }
599
+ );
600
+ const entriesToUnpublish = await strapi2.entityService.findMany(
601
+ contentTypeUid,
602
+ {
603
+ filters: {
604
+ id: {
605
+ $in: entriesToUnpublishIds
606
+ }
607
+ },
608
+ populate
609
+ }
610
+ );
611
+ if (entriesToPublish.length > 0) {
612
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
440
613
  }
441
- if (unpublish.length > 0) {
442
- await entityManagerService.unpublishMany(unpublish, contentTypeUid);
614
+ if (entriesToUnpublish.length > 0) {
615
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
443
616
  }
444
617
  }
445
618
  });
@@ -458,13 +631,18 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
458
631
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
459
632
  where: {
460
633
  id: actionId,
461
- release: releaseId
634
+ release: {
635
+ id: releaseId,
636
+ releasedAt: {
637
+ $null: true
638
+ }
639
+ }
462
640
  },
463
641
  data: update
464
642
  });
465
643
  if (!updatedAction) {
466
644
  throw new utils.errors.NotFoundError(
467
- `Action with id ${actionId} not found in release with id ${releaseId}`
645
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
468
646
  );
469
647
  }
470
648
  return updatedAction;
@@ -473,12 +651,17 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
473
651
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
474
652
  where: {
475
653
  id: actionId,
476
- release: releaseId
654
+ release: {
655
+ id: releaseId,
656
+ releasedAt: {
657
+ $null: true
658
+ }
659
+ }
477
660
  }
478
661
  });
479
662
  if (!deletedAction) {
480
663
  throw new utils.errors.NotFoundError(
481
- `Action with id ${actionId} not found in release with id ${releaseId}`
664
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
482
665
  );
483
666
  }
484
667
  return deletedAction;
@@ -511,9 +694,42 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
511
694
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
512
695
  );
513
696
  }
697
+ },
698
+ async validatePendingReleasesLimit() {
699
+ const maximumPendingReleases = (
700
+ // @ts-expect-error - options is not typed into features
701
+ EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
702
+ );
703
+ const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
704
+ filters: {
705
+ releasedAt: {
706
+ $null: true
707
+ }
708
+ }
709
+ });
710
+ if (pendingReleasesCount >= maximumPendingReleases) {
711
+ throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
712
+ }
713
+ },
714
+ async validateUniqueNameForPendingRelease(name) {
715
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
716
+ filters: {
717
+ releasedAt: {
718
+ $null: true
719
+ },
720
+ name
721
+ }
722
+ });
723
+ const isNameUnique = pendingReleases.length === 0;
724
+ if (!isNameUnique) {
725
+ throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
726
+ }
514
727
  }
515
728
  });
516
- const services = { release: createReleaseService, "release-validation": createReleaseValidationService };
729
+ const services = {
730
+ release: createReleaseService,
731
+ "release-validation": createReleaseValidationService
732
+ };
517
733
  const RELEASE_SCHEMA = yup__namespace.object().shape({
518
734
  name: yup__namespace.string().trim().required()
519
735
  }).required().noUnknown();
@@ -532,9 +748,7 @@ const releaseController = {
532
748
  const contentTypeUid = query.contentTypeUid;
533
749
  const entryId = query.entryId;
534
750
  const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
535
- const data = await releaseService.findManyForContentTypeEntry(contentTypeUid, entryId, {
536
- hasEntryAttached
537
- });
751
+ const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
538
752
  ctx.body = { data };
539
753
  } else {
540
754
  const query = await permissionsManager.sanitizeQuery(ctx.query);
@@ -662,11 +876,30 @@ const releaseActionController = {
662
876
  sort: query.groupBy === "action" ? "type" : query.groupBy,
663
877
  ...query
664
878
  });
665
- const groupedData = await releaseService.groupActions(results, query.groupBy);
879
+ const contentTypeOutputSanitizers = results.reduce((acc, action) => {
880
+ if (acc[action.contentType]) {
881
+ return acc;
882
+ }
883
+ const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
884
+ ability: ctx.state.userAbility,
885
+ model: action.contentType
886
+ });
887
+ acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
888
+ return acc;
889
+ }, {});
890
+ const sanitizedResults = await utils.mapAsync(results, async (action) => ({
891
+ ...action,
892
+ entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
893
+ }));
894
+ const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
895
+ const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
896
+ const components = await releaseService.getAllComponents();
666
897
  ctx.body = {
667
898
  data: groupedData,
668
899
  meta: {
669
- pagination
900
+ pagination,
901
+ contentTypes: contentTypes2,
902
+ components
670
903
  }
671
904
  };
672
905
  },
@@ -688,10 +921,8 @@ const releaseActionController = {
688
921
  async delete(ctx) {
689
922
  const actionId = ctx.params.actionId;
690
923
  const releaseId = ctx.params.releaseId;
691
- const deletedReleaseAction = await getService("release", { strapi }).deleteAction(
692
- actionId,
693
- releaseId
694
- );
924
+ const releaseService = getService("release", { strapi });
925
+ const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
695
926
  ctx.body = {
696
927
  data: deletedReleaseAction
697
928
  };
@@ -874,9 +1105,10 @@ const routes = {
874
1105
  };
875
1106
  const { features } = require("@strapi/strapi/dist/utils/ee");
876
1107
  const getPlugin = () => {
877
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1108
+ if (features.isEnabled("cms-content-releases")) {
878
1109
  return {
879
1110
  register,
1111
+ bootstrap,
880
1112
  contentTypes,
881
1113
  services,
882
1114
  controllers,