@strapi/content-releases 0.0.0-experimental.fc1ac2acd58c8a5a858679956b6d102ac5ee4011 → 0.0.0-experimental.fea7af0bd6b406e4648e4c6669829249f73eb60f

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.
Files changed (105) hide show
  1. package/dist/_chunks/{App-C768ulk4.js → App-HjWtUYmc.js} +233 -261
  2. package/dist/_chunks/App-HjWtUYmc.js.map +1 -0
  3. package/dist/_chunks/{App-0Er6xxcq.mjs → App-gu1aiP6i.mjs} +237 -265
  4. package/dist/_chunks/App-gu1aiP6i.mjs.map +1 -0
  5. package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-3tRbmbY3.mjs} +2 -2
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases-bpIYXOfu.js} +2 -2
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-gcJJ5htG.js → en-HrREghh3.js} +11 -3
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-ltT1TlKQ.mjs} +11 -3
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-BLSMpbpZ.js → index-ZNwxYN8H.js} +338 -31
  14. package/dist/_chunks/index-ZNwxYN8H.js.map +1 -0
  15. package/dist/_chunks/{index-fJx1up7m.mjs → index-mvj9PSKd.mjs} +345 -38
  16. package/dist/_chunks/index-mvj9PSKd.mjs.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +1 -1
  19. package/dist/server/index.js +380 -172
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +380 -173
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +22 -15
  24. package/dist/_chunks/App-0Er6xxcq.mjs.map +0 -1
  25. package/dist/_chunks/App-C768ulk4.js.map +0 -1
  26. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
  27. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
  28. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  29. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  30. package/dist/_chunks/index-BLSMpbpZ.js.map +0 -1
  31. package/dist/_chunks/index-fJx1up7m.mjs.map +0 -1
  32. package/dist/admin/src/components/CMReleasesContainer.d.ts +0 -1
  33. package/dist/admin/src/components/RelativeTime.d.ts +0 -28
  34. package/dist/admin/src/components/ReleaseActionMenu.d.ts +0 -26
  35. package/dist/admin/src/components/ReleaseActionOptions.d.ts +0 -9
  36. package/dist/admin/src/components/ReleaseModal.d.ts +0 -16
  37. package/dist/admin/src/constants.d.ts +0 -58
  38. package/dist/admin/src/index.d.ts +0 -3
  39. package/dist/admin/src/pages/App.d.ts +0 -1
  40. package/dist/admin/src/pages/PurchaseContentReleases.d.ts +0 -2
  41. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +0 -2
  42. package/dist/admin/src/pages/ReleasesPage.d.ts +0 -8
  43. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +0 -181
  44. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +0 -39
  45. package/dist/admin/src/pluginId.d.ts +0 -1
  46. package/dist/admin/src/services/axios.d.ts +0 -29
  47. package/dist/admin/src/services/release.d.ts +0 -369
  48. package/dist/admin/src/store/hooks.d.ts +0 -7
  49. package/dist/admin/src/utils/time.d.ts +0 -1
  50. package/dist/server/src/bootstrap.d.ts +0 -5
  51. package/dist/server/src/bootstrap.d.ts.map +0 -1
  52. package/dist/server/src/constants.d.ts +0 -12
  53. package/dist/server/src/constants.d.ts.map +0 -1
  54. package/dist/server/src/content-types/index.d.ts +0 -99
  55. package/dist/server/src/content-types/index.d.ts.map +0 -1
  56. package/dist/server/src/content-types/release/index.d.ts +0 -48
  57. package/dist/server/src/content-types/release/index.d.ts.map +0 -1
  58. package/dist/server/src/content-types/release/schema.d.ts +0 -47
  59. package/dist/server/src/content-types/release/schema.d.ts.map +0 -1
  60. package/dist/server/src/content-types/release-action/index.d.ts +0 -50
  61. package/dist/server/src/content-types/release-action/index.d.ts.map +0 -1
  62. package/dist/server/src/content-types/release-action/schema.d.ts +0 -49
  63. package/dist/server/src/content-types/release-action/schema.d.ts.map +0 -1
  64. package/dist/server/src/controllers/index.d.ts +0 -18
  65. package/dist/server/src/controllers/index.d.ts.map +0 -1
  66. package/dist/server/src/controllers/release-action.d.ts +0 -9
  67. package/dist/server/src/controllers/release-action.d.ts.map +0 -1
  68. package/dist/server/src/controllers/release.d.ts +0 -11
  69. package/dist/server/src/controllers/release.d.ts.map +0 -1
  70. package/dist/server/src/controllers/validation/release-action.d.ts +0 -3
  71. package/dist/server/src/controllers/validation/release-action.d.ts.map +0 -1
  72. package/dist/server/src/controllers/validation/release.d.ts +0 -2
  73. package/dist/server/src/controllers/validation/release.d.ts.map +0 -1
  74. package/dist/server/src/destroy.d.ts +0 -5
  75. package/dist/server/src/destroy.d.ts.map +0 -1
  76. package/dist/server/src/index.d.ts +0 -3838
  77. package/dist/server/src/index.d.ts.map +0 -1
  78. package/dist/server/src/migrations/index.d.ts +0 -10
  79. package/dist/server/src/migrations/index.d.ts.map +0 -1
  80. package/dist/server/src/register.d.ts +0 -5
  81. package/dist/server/src/register.d.ts.map +0 -1
  82. package/dist/server/src/routes/index.d.ts +0 -35
  83. package/dist/server/src/routes/index.d.ts.map +0 -1
  84. package/dist/server/src/routes/release-action.d.ts +0 -18
  85. package/dist/server/src/routes/release-action.d.ts.map +0 -1
  86. package/dist/server/src/routes/release.d.ts +0 -18
  87. package/dist/server/src/routes/release.d.ts.map +0 -1
  88. package/dist/server/src/services/index.d.ts +0 -3572
  89. package/dist/server/src/services/index.d.ts.map +0 -1
  90. package/dist/server/src/services/release.d.ts +0 -1812
  91. package/dist/server/src/services/release.d.ts.map +0 -1
  92. package/dist/server/src/services/scheduling.d.ts +0 -18
  93. package/dist/server/src/services/scheduling.d.ts.map +0 -1
  94. package/dist/server/src/services/validation.d.ts +0 -14
  95. package/dist/server/src/services/validation.d.ts.map +0 -1
  96. package/dist/server/src/utils/index.d.ts +0 -18
  97. package/dist/server/src/utils/index.d.ts.map +0 -1
  98. package/dist/shared/contracts/release-actions.d.ts +0 -105
  99. package/dist/shared/contracts/release-actions.d.ts.map +0 -1
  100. package/dist/shared/contracts/releases.d.ts +0 -166
  101. package/dist/shared/contracts/releases.d.ts.map +0 -1
  102. package/dist/shared/types.d.ts +0 -24
  103. package/dist/shared/types.d.ts.map +0 -1
  104. package/dist/shared/validation-schemas.d.ts +0 -2
  105. package/dist/shared/validation-schemas.d.ts.map +0 -1
@@ -3,6 +3,7 @@ const utils = require("@strapi/utils");
3
3
  const isEqual = require("lodash/isEqual");
4
4
  const lodash = require("lodash");
5
5
  const _ = require("lodash/fp");
6
+ const EE = require("@strapi/strapi/dist/utils/ee");
6
7
  const nodeSchedule = require("node-schedule");
7
8
  const yup = require("yup");
8
9
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -26,6 +27,7 @@ function _interopNamespace(e) {
26
27
  }
27
28
  const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
28
29
  const ___default = /* @__PURE__ */ _interopDefault(_);
30
+ const EE__default = /* @__PURE__ */ _interopDefault(EE);
29
31
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
30
32
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
31
33
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -99,10 +101,28 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
99
101
  return false;
100
102
  }
101
103
  };
104
+ async function deleteActionsOnDisableDraftAndPublish({
105
+ oldContentTypes,
106
+ contentTypes: contentTypes2
107
+ }) {
108
+ if (!oldContentTypes) {
109
+ return;
110
+ }
111
+ for (const uid in contentTypes2) {
112
+ if (!oldContentTypes[uid]) {
113
+ continue;
114
+ }
115
+ const oldContentType = oldContentTypes[uid];
116
+ const contentType = contentTypes2[uid];
117
+ if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
118
+ await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
119
+ }
120
+ }
121
+ }
102
122
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
103
123
  const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
104
124
  if (deletedContentTypes.length) {
105
- await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
125
+ await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
106
126
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
107
127
  });
108
128
  }
@@ -121,7 +141,7 @@ async function migrateIsValidAndStatusReleases() {
121
141
  }
122
142
  }
123
143
  });
124
- utils.async.map(releasesWithoutStatus, async (release2) => {
144
+ utils.mapAsync(releasesWithoutStatus, async (release2) => {
125
145
  const actions = release2.actions;
126
146
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
127
147
  for (const action of notValidatedActions) {
@@ -152,7 +172,7 @@ async function migrateIsValidAndStatusReleases() {
152
172
  }
153
173
  }
154
174
  });
155
- utils.async.map(publishedReleases, async (release2) => {
175
+ utils.mapAsync(publishedReleases, async (release2) => {
156
176
  return strapi.db.query(RELEASE_MODEL_UID).update({
157
177
  where: {
158
178
  id: release2.id
@@ -165,9 +185,11 @@ async function migrateIsValidAndStatusReleases() {
165
185
  }
166
186
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
167
187
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
168
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
188
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
189
+ (uid) => oldContentTypes[uid]?.options?.draftAndPublish
190
+ );
169
191
  const releasesAffected = /* @__PURE__ */ new Set();
170
- utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
192
+ utils.mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
171
193
  const oldContentType = oldContentTypes[contentTypeUID];
172
194
  const contentType = contentTypes2[contentTypeUID];
173
195
  if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
@@ -180,8 +202,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
180
202
  release: true
181
203
  }
182
204
  });
183
- await utils.async.map(actions, async (action) => {
184
- if (action.entry) {
205
+ await utils.mapAsync(actions, async (action) => {
206
+ if (action.entry && action.release) {
185
207
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
186
208
  strapi
187
209
  });
@@ -203,21 +225,77 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
203
225
  });
204
226
  }
205
227
  }).then(() => {
206
- utils.async.map(releasesAffected, async (releaseId) => {
228
+ utils.mapAsync(releasesAffected, async (releaseId) => {
207
229
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
208
230
  });
209
231
  });
210
232
  }
211
233
  }
234
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
235
+ if (!oldContentTypes) {
236
+ return;
237
+ }
238
+ const i18nPlugin = strapi.plugin("i18n");
239
+ if (!i18nPlugin) {
240
+ return;
241
+ }
242
+ for (const uid in contentTypes2) {
243
+ if (!oldContentTypes[uid]) {
244
+ continue;
245
+ }
246
+ const oldContentType = oldContentTypes[uid];
247
+ const contentType = contentTypes2[uid];
248
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
249
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
250
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
251
+ locale: null
252
+ }).where({ contentType: uid }).execute();
253
+ }
254
+ }
255
+ }
256
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
257
+ if (!oldContentTypes) {
258
+ return;
259
+ }
260
+ const i18nPlugin = strapi.plugin("i18n");
261
+ if (!i18nPlugin) {
262
+ return;
263
+ }
264
+ for (const uid in contentTypes2) {
265
+ if (!oldContentTypes[uid]) {
266
+ continue;
267
+ }
268
+ const oldContentType = oldContentTypes[uid];
269
+ const contentType = contentTypes2[uid];
270
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
271
+ const { getDefaultLocale } = i18nPlugin.service("locales");
272
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
273
+ const defaultLocale = await getDefaultLocale();
274
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
275
+ locale: defaultLocale
276
+ }).where({ contentType: uid }).execute();
277
+ }
278
+ }
279
+ }
280
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
212
281
  const register = async ({ strapi: strapi2 }) => {
213
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
282
+ if (features$2.isEnabled("cms-content-releases")) {
214
283
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
215
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
284
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
285
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
+ }
287
+ if (strapi2.plugin("graphql")) {
288
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
289
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
290
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
216
291
  }
217
292
  };
293
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
218
294
  const bootstrap = async ({ strapi: strapi2 }) => {
219
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
220
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
295
+ if (features$1.isEnabled("cms-content-releases")) {
296
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
297
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
298
+ );
221
299
  strapi2.db.lifecycles.subscribe({
222
300
  models: contentTypesWithDraftAndPublish,
223
301
  async afterDelete(event) {
@@ -253,7 +331,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
253
331
  */
254
332
  async beforeDeleteMany(event) {
255
333
  const { model, params } = event;
256
- if (model.kind === "collectionType") {
334
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
257
335
  const { where } = params;
258
336
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
259
337
  event.state.entriesToDelete = entriesToDelete;
@@ -335,27 +413,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
335
413
  }
336
414
  }
337
415
  });
338
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
339
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
340
- strapi2.log.error(
341
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
342
- );
343
- throw err;
344
- });
345
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
346
- strapi2.webhookStore.addAllowedEvent(key, value);
347
- });
348
- }
416
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
417
+ strapi2.log.error(
418
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
419
+ );
420
+ throw err;
421
+ });
422
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
423
+ strapi2.webhookStore.addAllowedEvent(key, value);
424
+ });
349
425
  }
350
426
  };
351
427
  const destroy = async ({ strapi: strapi2 }) => {
352
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
353
- const scheduledJobs = getService("scheduling", {
354
- strapi: strapi2
355
- }).getAll();
356
- for (const [, job] of scheduledJobs) {
357
- job.cancel();
358
- }
428
+ const scheduledJobs = getService("scheduling", {
429
+ strapi: strapi2
430
+ }).getAll();
431
+ for (const [, job] of scheduledJobs) {
432
+ job.cancel();
359
433
  }
360
434
  };
361
435
  const schema$1 = {
@@ -480,6 +554,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
480
554
  release: release2
481
555
  });
482
556
  };
557
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
558
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
559
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
560
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
561
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
562
+ try {
563
+ if (actionType === "publish") {
564
+ await entityManagerService.publish(entry, uid);
565
+ } else {
566
+ await entityManagerService.unpublish(entry, uid);
567
+ }
568
+ } catch (error) {
569
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
570
+ ;
571
+ else {
572
+ throw error;
573
+ }
574
+ }
575
+ };
576
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
577
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
578
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
579
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
580
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
581
+ filters: {
582
+ id: {
583
+ $in: entriesToPublishIds
584
+ }
585
+ },
586
+ populate
587
+ });
588
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
589
+ filters: {
590
+ id: {
591
+ $in: entriestoUnpublishIds
592
+ }
593
+ },
594
+ populate
595
+ });
596
+ if (entriesToPublish.length > 0) {
597
+ await entityManagerService.publishMany(entriesToPublish, uid);
598
+ }
599
+ if (entriesToUnpublish.length > 0) {
600
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
601
+ }
602
+ };
603
+ const getFormattedActions = async (releaseId) => {
604
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
605
+ where: {
606
+ release: {
607
+ id: releaseId
608
+ }
609
+ },
610
+ populate: {
611
+ entry: {
612
+ fields: ["id"]
613
+ }
614
+ }
615
+ });
616
+ if (actions.length === 0) {
617
+ throw new utils.errors.ValidationError("No entries to publish");
618
+ }
619
+ const collectionTypeActions = {};
620
+ const singleTypeActions = [];
621
+ for (const action of actions) {
622
+ const contentTypeUid = action.contentType;
623
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
624
+ if (!collectionTypeActions[contentTypeUid]) {
625
+ collectionTypeActions[contentTypeUid] = {
626
+ entriesToPublishIds: [],
627
+ entriesToUnpublishIds: []
628
+ };
629
+ }
630
+ if (action.type === "publish") {
631
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
632
+ } else {
633
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
634
+ }
635
+ } else {
636
+ singleTypeActions.push({
637
+ uid: contentTypeUid,
638
+ action: action.type,
639
+ id: action.entry.id
640
+ });
641
+ }
642
+ }
643
+ return { collectionTypeActions, singleTypeActions };
644
+ };
483
645
  return {
484
646
  async create(releaseData, { user }) {
485
647
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
@@ -499,7 +661,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
499
661
  status: "empty"
500
662
  }
501
663
  });
502
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
664
+ if (releaseWithCreatorFields.scheduledAt) {
503
665
  const schedulingService = getService("scheduling", { strapi: strapi2 });
504
666
  await schedulingService.set(release2.id, release2.scheduledAt);
505
667
  }
@@ -523,12 +685,18 @@ const createReleaseService = ({ strapi: strapi2 }) => {
523
685
  }
524
686
  });
525
687
  },
526
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
688
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
689
+ let entries = entriesIds;
690
+ if (!Array.isArray(entriesIds)) {
691
+ entries = [entriesIds];
692
+ }
527
693
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
528
694
  where: {
529
695
  actions: {
530
696
  target_type: contentTypeUid,
531
- target_id: entryId
697
+ target_id: {
698
+ $in: entries
699
+ }
532
700
  },
533
701
  releasedAt: {
534
702
  $null: true
@@ -539,18 +707,25 @@ const createReleaseService = ({ strapi: strapi2 }) => {
539
707
  actions: {
540
708
  where: {
541
709
  target_type: contentTypeUid,
542
- target_id: entryId
710
+ target_id: {
711
+ $in: entries
712
+ }
713
+ },
714
+ populate: {
715
+ entry: {
716
+ select: ["id"]
717
+ }
543
718
  }
544
719
  }
545
720
  }
546
721
  });
547
722
  return releases.map((release2) => {
548
723
  if (release2.actions?.length) {
549
- const [actionForEntry] = release2.actions;
724
+ const actionsForEntry = release2.actions;
550
725
  delete release2.actions;
551
726
  return {
552
727
  ...release2,
553
- action: actionForEntry
728
+ actions: actionsForEntry
554
729
  };
555
730
  }
556
731
  return release2;
@@ -624,13 +799,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
624
799
  // @ts-expect-error see above
625
800
  data: releaseWithCreatorFields
626
801
  });
627
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
628
- const schedulingService = getService("scheduling", { strapi: strapi2 });
629
- if (releaseData.scheduledAt) {
630
- await schedulingService.set(id, releaseData.scheduledAt);
631
- } else if (release2.scheduledAt) {
632
- schedulingService.cancel(id);
633
- }
802
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
803
+ if (releaseData.scheduledAt) {
804
+ await schedulingService.set(id, releaseData.scheduledAt);
805
+ } else if (release2.scheduledAt) {
806
+ schedulingService.cancel(id);
634
807
  }
635
808
  this.updateReleaseStatus(id);
636
809
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -796,7 +969,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
796
969
  });
797
970
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
798
971
  });
799
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
972
+ if (release2.scheduledAt) {
800
973
  const schedulingService = getService("scheduling", { strapi: strapi2 });
801
974
  await schedulingService.cancel(release2.id);
802
975
  }
@@ -804,145 +977,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
804
977
  return release2;
805
978
  },
806
979
  async publish(releaseId) {
807
- try {
808
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
809
- RELEASE_MODEL_UID,
810
- releaseId,
811
- {
812
- populate: {
813
- actions: {
814
- populate: {
815
- entry: {
816
- fields: ["id"]
817
- }
818
- }
819
- }
820
- }
821
- }
822
- );
823
- if (!releaseWithPopulatedActionEntries) {
980
+ const {
981
+ release: release2,
982
+ error
983
+ } = await strapi2.db.transaction(async ({ trx }) => {
984
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
985
+ if (!lockedRelease) {
824
986
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
825
987
  }
826
- if (releaseWithPopulatedActionEntries.releasedAt) {
988
+ if (lockedRelease.releasedAt) {
827
989
  throw new utils.errors.ValidationError("Release already published");
828
990
  }
829
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
830
- throw new utils.errors.ValidationError("No entries to publish");
991
+ if (lockedRelease.status === "failed") {
992
+ throw new utils.errors.ValidationError("Release failed to publish");
831
993
  }
832
- const collectionTypeActions = {};
833
- const singleTypeActions = [];
834
- for (const action of releaseWithPopulatedActionEntries.actions) {
835
- const contentTypeUid = action.contentType;
836
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
837
- if (!collectionTypeActions[contentTypeUid]) {
838
- collectionTypeActions[contentTypeUid] = {
839
- entriestoPublishIds: [],
840
- entriesToUnpublishIds: []
841
- };
842
- }
843
- if (action.type === "publish") {
844
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
845
- } else {
846
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
847
- }
848
- } else {
849
- singleTypeActions.push({
850
- uid: contentTypeUid,
851
- action: action.type,
852
- id: action.entry.id
853
- });
854
- }
855
- }
856
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
857
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
858
- await strapi2.db.transaction(async () => {
859
- for (const { uid, action, id } of singleTypeActions) {
860
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
861
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
862
- try {
863
- if (action === "publish") {
864
- await entityManagerService.publish(entry, uid);
865
- } else {
866
- await entityManagerService.unpublish(entry, uid);
867
- }
868
- } catch (error) {
869
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
870
- } else {
871
- throw error;
872
- }
873
- }
874
- }
875
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
876
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
877
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
878
- const entriesToPublish = await strapi2.entityService.findMany(
879
- contentTypeUid,
880
- {
881
- filters: {
882
- id: {
883
- $in: entriestoPublishIds
884
- }
885
- },
886
- populate
887
- }
888
- );
889
- const entriesToUnpublish = await strapi2.entityService.findMany(
890
- contentTypeUid,
891
- {
892
- filters: {
893
- id: {
894
- $in: entriesToUnpublishIds
895
- }
896
- },
897
- populate
898
- }
899
- );
900
- if (entriesToPublish.length > 0) {
901
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
994
+ try {
995
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
996
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
997
+ releaseId
998
+ );
999
+ await strapi2.db.transaction(async () => {
1000
+ for (const { uid, action, id } of singleTypeActions) {
1001
+ await publishSingleTypeAction(uid, action, id);
902
1002
  }
903
- if (entriesToUnpublish.length > 0) {
904
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
1003
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
1004
+ const uid = contentTypeUid;
1005
+ await publishCollectionTypeAction(
1006
+ uid,
1007
+ collectionTypeActions[uid].entriesToPublishIds,
1008
+ collectionTypeActions[uid].entriesToUnpublishIds
1009
+ );
905
1010
  }
906
- }
907
- });
908
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
909
- data: {
910
- /*
911
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
912
- */
913
- // @ts-expect-error see above
914
- releasedAt: /* @__PURE__ */ new Date()
915
- },
916
- populate: {
917
- actions: {
918
- // @ts-expect-error is not expecting count but it is working
919
- count: true
1011
+ });
1012
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
1013
+ where: {
1014
+ id: releaseId
1015
+ },
1016
+ data: {
1017
+ status: "done",
1018
+ releasedAt: /* @__PURE__ */ new Date()
920
1019
  }
921
- }
922
- });
923
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1020
+ });
924
1021
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
925
1022
  isPublished: true,
926
- release: release2
1023
+ release: release22
927
1024
  });
928
- }
929
- strapi2.telemetry.send("didPublishContentRelease");
930
- return release2;
931
- } catch (error) {
932
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1025
+ strapi2.telemetry.send("didPublishContentRelease");
1026
+ return { release: release22, error: null };
1027
+ } catch (error2) {
933
1028
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
934
1029
  isPublished: false,
935
- error
1030
+ error: error2
936
1031
  });
937
- }
938
- strapi2.db.query(RELEASE_MODEL_UID).update({
939
- where: { id: releaseId },
940
- data: {
1032
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
941
1033
  status: "failed"
942
- }
943
- });
1034
+ }).transacting(trx).execute();
1035
+ return {
1036
+ release: null,
1037
+ error: error2
1038
+ };
1039
+ }
1040
+ });
1041
+ if (error) {
944
1042
  throw error;
945
1043
  }
1044
+ return release2;
946
1045
  },
947
1046
  async updateAction(actionId, releaseId, update) {
948
1047
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1029,6 +1128,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1029
1128
  }
1030
1129
  };
1031
1130
  };
1131
+ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1132
+ constructor(message) {
1133
+ super(message);
1134
+ this.name = "AlreadyOnReleaseError";
1135
+ }
1136
+ }
1032
1137
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1033
1138
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1034
1139
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -1041,7 +1146,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1041
1146
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1042
1147
  );
1043
1148
  if (isEntryInRelease) {
1044
- throw new utils.errors.ValidationError(
1149
+ throw new AlreadyOnReleaseError(
1045
1150
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1046
1151
  );
1047
1152
  }
@@ -1051,9 +1156,17 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1051
1156
  if (!contentType) {
1052
1157
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1053
1158
  }
1159
+ if (!contentType.options?.draftAndPublish) {
1160
+ throw new utils.errors.ValidationError(
1161
+ `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1162
+ );
1163
+ }
1054
1164
  },
1055
1165
  async validatePendingReleasesLimit() {
1056
- const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1166
+ const maximumPendingReleases = (
1167
+ // @ts-expect-error - options is not typed into features
1168
+ EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
1169
+ );
1057
1170
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1058
1171
  filters: {
1059
1172
  releasedAt: {
@@ -1141,7 +1254,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1141
1254
  const services = {
1142
1255
  release: createReleaseService,
1143
1256
  "release-validation": createReleaseValidationService,
1144
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1257
+ scheduling: createSchedulingService
1145
1258
  };
1146
1259
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1147
1260
  name: yup__namespace.string().trim().required(),
@@ -1228,6 +1341,33 @@ const releaseController = {
1228
1341
  };
1229
1342
  ctx.body = { data };
1230
1343
  },
1344
+ async mapEntriesToReleases(ctx) {
1345
+ const { contentTypeUid, entriesIds } = ctx.query;
1346
+ if (!contentTypeUid || !entriesIds) {
1347
+ throw new utils.errors.ValidationError("Missing required query parameters");
1348
+ }
1349
+ const releaseService = getService("release", { strapi });
1350
+ const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1351
+ contentTypeUid,
1352
+ entriesIds
1353
+ );
1354
+ const mappedEntriesInReleases = releasesWithActions.reduce(
1355
+ (acc, release2) => {
1356
+ release2.actions.forEach((action) => {
1357
+ if (!acc[action.entry.id]) {
1358
+ acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1359
+ } else {
1360
+ acc[action.entry.id].push({ id: release2.id, name: release2.name });
1361
+ }
1362
+ });
1363
+ return acc;
1364
+ },
1365
+ {}
1366
+ );
1367
+ ctx.body = {
1368
+ data: mappedEntriesInReleases
1369
+ };
1370
+ },
1231
1371
  async create(ctx) {
1232
1372
  const user = ctx.state.user;
1233
1373
  const releaseArgs = ctx.request.body;
@@ -1317,6 +1457,38 @@ const releaseActionController = {
1317
1457
  data: releaseAction2
1318
1458
  };
1319
1459
  },
1460
+ async createMany(ctx) {
1461
+ const releaseId = ctx.params.releaseId;
1462
+ const releaseActionsArgs = ctx.request.body;
1463
+ await Promise.all(
1464
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1465
+ );
1466
+ const releaseService = getService("release", { strapi });
1467
+ const releaseActions = await strapi.db.transaction(async () => {
1468
+ const releaseActions2 = await Promise.all(
1469
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1470
+ try {
1471
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1472
+ return action;
1473
+ } catch (error) {
1474
+ if (error instanceof AlreadyOnReleaseError) {
1475
+ return null;
1476
+ }
1477
+ throw error;
1478
+ }
1479
+ })
1480
+ );
1481
+ return releaseActions2;
1482
+ });
1483
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1484
+ ctx.body = {
1485
+ data: newReleaseActions,
1486
+ meta: {
1487
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1488
+ totalEntries: releaseActions.length
1489
+ }
1490
+ };
1491
+ },
1320
1492
  async findMany(ctx) {
1321
1493
  const releaseId = ctx.params.releaseId;
1322
1494
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1340,7 +1512,7 @@ const releaseActionController = {
1340
1512
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1341
1513
  return acc;
1342
1514
  }, {});
1343
- const sanitizedResults = await utils.async.map(results, async (action) => ({
1515
+ const sanitizedResults = await utils.mapAsync(results, async (action) => ({
1344
1516
  ...action,
1345
1517
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1346
1518
  }));
@@ -1385,6 +1557,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
1385
1557
  const release = {
1386
1558
  type: "admin",
1387
1559
  routes: [
1560
+ {
1561
+ method: "GET",
1562
+ path: "/mapEntriesToReleases",
1563
+ handler: "release.mapEntriesToReleases",
1564
+ config: {
1565
+ policies: [
1566
+ "admin::isAuthenticatedAdmin",
1567
+ {
1568
+ name: "admin::hasPermissions",
1569
+ config: {
1570
+ actions: ["plugin::content-releases.read"]
1571
+ }
1572
+ }
1573
+ ]
1574
+ }
1575
+ },
1388
1576
  {
1389
1577
  method: "POST",
1390
1578
  path: "/",
@@ -1502,6 +1690,22 @@ const releaseAction = {
1502
1690
  ]
1503
1691
  }
1504
1692
  },
1693
+ {
1694
+ method: "POST",
1695
+ path: "/:releaseId/actions/bulk",
1696
+ handler: "release-action.createMany",
1697
+ config: {
1698
+ policies: [
1699
+ "admin::isAuthenticatedAdmin",
1700
+ {
1701
+ name: "admin::hasPermissions",
1702
+ config: {
1703
+ actions: ["plugin::content-releases.create-action"]
1704
+ }
1705
+ }
1706
+ ]
1707
+ }
1708
+ },
1505
1709
  {
1506
1710
  method: "GET",
1507
1711
  path: "/:releaseId/actions",
@@ -1556,8 +1760,9 @@ const routes = {
1556
1760
  release,
1557
1761
  "release-action": releaseAction
1558
1762
  };
1763
+ const { features } = require("@strapi/strapi/dist/utils/ee");
1559
1764
  const getPlugin = () => {
1560
- if (strapi.ee.features.isEnabled("cms-content-releases")) {
1765
+ if (features.isEnabled("cms-content-releases")) {
1561
1766
  return {
1562
1767
  register,
1563
1768
  bootstrap,
@@ -1569,6 +1774,9 @@ const getPlugin = () => {
1569
1774
  };
1570
1775
  }
1571
1776
  return {
1777
+ // Always return register, it handles its own feature check
1778
+ register,
1779
+ // Always return contentTypes to avoid losing data when the feature is disabled
1572
1780
  contentTypes
1573
1781
  };
1574
1782
  };