@strapi/content-releases 4.20.4 → 5.0.0-alpha.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.
Files changed (101) hide show
  1. package/dist/_chunks/{App-6ugQxqYE.mjs → App-dbdAcsz_.mjs} +296 -296
  2. package/dist/_chunks/App-dbdAcsz_.mjs.map +1 -0
  3. package/dist/_chunks/{App-P1kyM3gT.js → App-zwe_jKPv.js} +301 -301
  4. package/dist/_chunks/App-zwe_jKPv.js.map +1 -0
  5. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-RdapH-9X.mjs} +1 -2
  6. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  7. package/dist/_chunks/{en-gcJJ5htG.js → en-faJDuv3q.js} +1 -2
  8. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  9. package/dist/_chunks/{index-_eBuegHN.mjs → index-RBaVMtyr.mjs} +79 -70
  10. package/dist/_chunks/index-RBaVMtyr.mjs.map +1 -0
  11. package/dist/_chunks/{index-2xzbhaQP.js → index-TBrVNrv9.js} +77 -68
  12. package/dist/_chunks/index-TBrVNrv9.js.map +1 -0
  13. package/dist/admin/index.js +1 -14
  14. package/dist/admin/index.js.map +1 -1
  15. package/dist/admin/index.mjs +1 -14
  16. package/dist/admin/index.mjs.map +1 -1
  17. package/dist/admin/src/components/CMReleasesContainer.d.ts +1 -0
  18. package/dist/admin/src/components/RelativeTime.d.ts +28 -0
  19. package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
  20. package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
  21. package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
  22. package/dist/admin/src/constants.d.ts +58 -0
  23. package/dist/admin/src/index.d.ts +3 -0
  24. package/dist/admin/src/pages/App.d.ts +1 -0
  25. package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
  26. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
  27. package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
  28. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
  29. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
  30. package/dist/admin/src/pluginId.d.ts +1 -0
  31. package/dist/admin/src/services/axios.d.ts +29 -0
  32. package/dist/admin/src/services/release.d.ts +369 -0
  33. package/dist/admin/src/store/hooks.d.ts +7 -0
  34. package/dist/admin/src/utils/time.d.ts +1 -0
  35. package/dist/server/index.js +327 -234
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/server/index.mjs +328 -234
  38. package/dist/server/index.mjs.map +1 -1
  39. package/dist/server/src/bootstrap.d.ts +5 -0
  40. package/dist/server/src/bootstrap.d.ts.map +1 -0
  41. package/dist/server/src/constants.d.ts +12 -0
  42. package/dist/server/src/constants.d.ts.map +1 -0
  43. package/dist/server/src/content-types/index.d.ts +99 -0
  44. package/dist/server/src/content-types/index.d.ts.map +1 -0
  45. package/dist/server/src/content-types/release/index.d.ts +48 -0
  46. package/dist/server/src/content-types/release/index.d.ts.map +1 -0
  47. package/dist/server/src/content-types/release/schema.d.ts +47 -0
  48. package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
  49. package/dist/server/src/content-types/release-action/index.d.ts +50 -0
  50. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
  51. package/dist/server/src/content-types/release-action/schema.d.ts +49 -0
  52. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
  53. package/dist/server/src/controllers/index.d.ts +19 -0
  54. package/dist/server/src/controllers/index.d.ts.map +1 -0
  55. package/dist/server/src/controllers/release-action.d.ts +10 -0
  56. package/dist/server/src/controllers/release-action.d.ts.map +1 -0
  57. package/dist/server/src/controllers/release.d.ts +11 -0
  58. package/dist/server/src/controllers/release.d.ts.map +1 -0
  59. package/dist/server/src/controllers/validation/release-action.d.ts +3 -0
  60. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
  61. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  62. package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
  63. package/dist/server/src/destroy.d.ts +5 -0
  64. package/dist/server/src/destroy.d.ts.map +1 -0
  65. package/dist/server/src/index.d.ts +2095 -0
  66. package/dist/server/src/index.d.ts.map +1 -0
  67. package/dist/server/src/migrations/index.d.ts +12 -0
  68. package/dist/server/src/migrations/index.d.ts.map +1 -0
  69. package/dist/server/src/register.d.ts +5 -0
  70. package/dist/server/src/register.d.ts.map +1 -0
  71. package/dist/server/src/routes/index.d.ts +35 -0
  72. package/dist/server/src/routes/index.d.ts.map +1 -0
  73. package/dist/server/src/routes/release-action.d.ts +18 -0
  74. package/dist/server/src/routes/release-action.d.ts.map +1 -0
  75. package/dist/server/src/routes/release.d.ts +18 -0
  76. package/dist/server/src/routes/release.d.ts.map +1 -0
  77. package/dist/server/src/services/index.d.ts +1826 -0
  78. package/dist/server/src/services/index.d.ts.map +1 -0
  79. package/dist/server/src/services/release.d.ts +66 -0
  80. package/dist/server/src/services/release.d.ts.map +1 -0
  81. package/dist/server/src/services/scheduling.d.ts +18 -0
  82. package/dist/server/src/services/scheduling.d.ts.map +1 -0
  83. package/dist/server/src/services/validation.d.ts +18 -0
  84. package/dist/server/src/services/validation.d.ts.map +1 -0
  85. package/dist/server/src/utils/index.d.ts +14 -0
  86. package/dist/server/src/utils/index.d.ts.map +1 -0
  87. package/dist/shared/contracts/release-actions.d.ts +131 -0
  88. package/dist/shared/contracts/release-actions.d.ts.map +1 -0
  89. package/dist/shared/contracts/releases.d.ts +166 -0
  90. package/dist/shared/contracts/releases.d.ts.map +1 -0
  91. package/dist/shared/types.d.ts +24 -0
  92. package/dist/shared/types.d.ts.map +1 -0
  93. package/dist/shared/validation-schemas.d.ts +2 -0
  94. package/dist/shared/validation-schemas.d.ts.map +1 -0
  95. package/package.json +24 -30
  96. package/dist/_chunks/App-6ugQxqYE.mjs.map +0 -1
  97. package/dist/_chunks/App-P1kyM3gT.js.map +0 -1
  98. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  99. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  100. package/dist/_chunks/index-2xzbhaQP.js.map +0 -1
  101. package/dist/_chunks/index-_eBuegHN.mjs.map +0 -1
@@ -3,7 +3,6 @@ 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");
7
6
  const nodeSchedule = require("node-schedule");
8
7
  const yup = require("yup");
9
8
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -27,7 +26,6 @@ function _interopNamespace(e) {
27
26
  }
28
27
  const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
29
28
  const ___default = /* @__PURE__ */ _interopDefault(_);
30
- const EE__default = /* @__PURE__ */ _interopDefault(EE);
31
29
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
32
30
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
33
31
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -84,7 +82,10 @@ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
84
82
  const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
85
83
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
86
84
  const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
87
- const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
85
+ const entry = await strapi2.db.query(contentTypeUid).findOne({
86
+ where: { id: entryId },
87
+ populate
88
+ });
88
89
  return entry;
89
90
  };
90
91
  const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
@@ -101,28 +102,10 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
101
102
  return false;
102
103
  }
103
104
  };
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
- }
122
105
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
123
106
  const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
124
107
  if (deletedContentTypes.length) {
125
- await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
108
+ await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
126
109
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
127
110
  });
128
111
  }
@@ -141,7 +124,7 @@ async function migrateIsValidAndStatusReleases() {
141
124
  }
142
125
  }
143
126
  });
144
- utils.mapAsync(releasesWithoutStatus, async (release2) => {
127
+ utils.async.map(releasesWithoutStatus, async (release2) => {
145
128
  const actions = release2.actions;
146
129
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
147
130
  for (const action of notValidatedActions) {
@@ -172,7 +155,7 @@ async function migrateIsValidAndStatusReleases() {
172
155
  }
173
156
  }
174
157
  });
175
- utils.mapAsync(publishedReleases, async (release2) => {
158
+ utils.async.map(publishedReleases, async (release2) => {
176
159
  return strapi.db.query(RELEASE_MODEL_UID).update({
177
160
  where: {
178
161
  id: release2.id
@@ -185,11 +168,9 @@ async function migrateIsValidAndStatusReleases() {
185
168
  }
186
169
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
187
170
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
188
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
189
- (uid) => oldContentTypes[uid]?.options?.draftAndPublish
190
- );
171
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
191
172
  const releasesAffected = /* @__PURE__ */ new Set();
192
- utils.mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
173
+ utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
193
174
  const oldContentType = oldContentTypes[contentTypeUID];
194
175
  const contentType = contentTypes2[contentTypeUID];
195
176
  if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
@@ -202,8 +183,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
202
183
  release: true
203
184
  }
204
185
  });
205
- await utils.mapAsync(actions, async (action) => {
206
- if (action.entry) {
186
+ await utils.async.map(actions, async (action) => {
187
+ if (action.entry && action.release) {
207
188
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
208
189
  strapi
209
190
  });
@@ -225,26 +206,67 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
225
206
  });
226
207
  }
227
208
  }).then(() => {
228
- utils.mapAsync(releasesAffected, async (releaseId) => {
209
+ utils.async.map(releasesAffected, async (releaseId) => {
229
210
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
230
211
  });
231
212
  });
232
213
  }
233
214
  }
234
- const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
215
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
216
+ if (!oldContentTypes) {
217
+ return;
218
+ }
219
+ for (const uid in contentTypes2) {
220
+ if (!oldContentTypes[uid]) {
221
+ continue;
222
+ }
223
+ const oldContentType = oldContentTypes[uid];
224
+ const contentType = contentTypes2[uid];
225
+ const i18nPlugin = strapi.plugin("i18n");
226
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
227
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
228
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
229
+ locale: null
230
+ }).where({ contentType: uid }).execute();
231
+ }
232
+ }
233
+ }
234
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
235
+ if (!oldContentTypes) {
236
+ return;
237
+ }
238
+ for (const uid in contentTypes2) {
239
+ if (!oldContentTypes[uid]) {
240
+ continue;
241
+ }
242
+ const oldContentType = oldContentTypes[uid];
243
+ const contentType = contentTypes2[uid];
244
+ const i18nPlugin = strapi.plugin("i18n");
245
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
246
+ const { getDefaultLocale } = i18nPlugin.service("locales");
247
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
248
+ const defaultLocale = await getDefaultLocale();
249
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
250
+ locale: defaultLocale
251
+ }).where({ contentType: uid }).execute();
252
+ }
253
+ }
254
+ }
235
255
  const register = async ({ strapi: strapi2 }) => {
236
- if (features$2.isEnabled("cms-content-releases")) {
256
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
237
257
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
238
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
239
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
258
+ strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized);
259
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
260
+ }
261
+ if (strapi2.plugin("graphql")) {
262
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
263
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
264
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
240
265
  }
241
266
  };
242
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
243
267
  const bootstrap = async ({ strapi: strapi2 }) => {
244
- if (features$1.isEnabled("cms-content-releases")) {
245
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
246
- (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
247
- );
268
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
269
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
248
270
  strapi2.db.lifecycles.subscribe({
249
271
  models: contentTypesWithDraftAndPublish,
250
272
  async afterDelete(event) {
@@ -280,7 +302,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
280
302
  */
281
303
  async beforeDeleteMany(event) {
282
304
  const { model, params } = event;
283
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
305
+ if (model.kind === "collectionType") {
284
306
  const { where } = params;
285
307
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
286
308
  event.state.entriesToDelete = entriesToDelete;
@@ -362,27 +384,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
362
384
  }
363
385
  }
364
386
  });
365
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
366
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
367
- strapi2.log.error(
368
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
369
- );
370
- throw err;
371
- });
372
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
373
- strapi2.webhookStore.addAllowedEvent(key, value);
374
- });
375
- }
387
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
388
+ strapi2.log.error(
389
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
390
+ );
391
+ throw err;
392
+ });
393
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
394
+ strapi2.webhookStore.addAllowedEvent(key, value);
395
+ });
376
396
  }
377
397
  };
378
398
  const destroy = async ({ strapi: strapi2 }) => {
379
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
380
- const scheduledJobs = getService("scheduling", {
381
- strapi: strapi2
382
- }).getAll();
383
- for (const [, job] of scheduledJobs) {
384
- job.cancel();
385
- }
399
+ const scheduledJobs = getService("scheduling", {
400
+ strapi: strapi2
401
+ }).getAll();
402
+ for (const [, job] of scheduledJobs) {
403
+ job.cancel();
386
404
  }
387
405
  };
388
406
  const schema$1 = {
@@ -507,6 +525,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
507
525
  release: release2
508
526
  });
509
527
  };
528
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
529
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
530
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
531
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
532
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
533
+ try {
534
+ if (actionType === "publish") {
535
+ await entityManagerService.publish(entry, uid);
536
+ } else {
537
+ await entityManagerService.unpublish(entry, uid);
538
+ }
539
+ } catch (error) {
540
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
541
+ ;
542
+ else {
543
+ throw error;
544
+ }
545
+ }
546
+ };
547
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
548
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
549
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
550
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
551
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
552
+ filters: {
553
+ id: {
554
+ $in: entriesToPublishIds
555
+ }
556
+ },
557
+ populate
558
+ });
559
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
560
+ filters: {
561
+ id: {
562
+ $in: entriestoUnpublishIds
563
+ }
564
+ },
565
+ populate
566
+ });
567
+ if (entriesToPublish.length > 0) {
568
+ await entityManagerService.publishMany(entriesToPublish, uid);
569
+ }
570
+ if (entriesToUnpublish.length > 0) {
571
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
572
+ }
573
+ };
574
+ const getFormattedActions = async (releaseId) => {
575
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
576
+ where: {
577
+ release: {
578
+ id: releaseId
579
+ }
580
+ },
581
+ populate: {
582
+ entry: {
583
+ fields: ["id"]
584
+ }
585
+ }
586
+ });
587
+ if (actions.length === 0) {
588
+ throw new utils.errors.ValidationError("No entries to publish");
589
+ }
590
+ const collectionTypeActions = {};
591
+ const singleTypeActions = [];
592
+ for (const action of actions) {
593
+ const contentTypeUid = action.contentType;
594
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
595
+ if (!collectionTypeActions[contentTypeUid]) {
596
+ collectionTypeActions[contentTypeUid] = {
597
+ entriesToPublishIds: [],
598
+ entriesToUnpublishIds: []
599
+ };
600
+ }
601
+ if (action.type === "publish") {
602
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
603
+ } else {
604
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
605
+ }
606
+ } else {
607
+ singleTypeActions.push({
608
+ uid: contentTypeUid,
609
+ action: action.type,
610
+ id: action.entry.id
611
+ });
612
+ }
613
+ }
614
+ return { collectionTypeActions, singleTypeActions };
615
+ };
510
616
  return {
511
617
  async create(releaseData, { user }) {
512
618
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
@@ -520,13 +626,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
520
626
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
521
627
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
522
628
  ]);
523
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
629
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
524
630
  data: {
525
631
  ...releaseWithCreatorFields,
526
632
  status: "empty"
527
633
  }
528
634
  });
529
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
635
+ if (releaseWithCreatorFields.scheduledAt) {
530
636
  const schedulingService = getService("scheduling", { strapi: strapi2 });
531
637
  await schedulingService.set(release2.id, release2.scheduledAt);
532
638
  }
@@ -534,17 +640,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
534
640
  return release2;
535
641
  },
536
642
  async findOne(id, query = {}) {
537
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
538
- ...query
643
+ const dbQuery = utils.convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query);
644
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
645
+ ...dbQuery,
646
+ where: { id }
539
647
  });
540
648
  return release2;
541
649
  },
542
650
  findPage(query) {
543
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
544
- ...query,
651
+ const dbQuery = utils.convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
652
+ return strapi2.db.query(RELEASE_MODEL_UID).findPage({
653
+ ...dbQuery,
545
654
  populate: {
546
655
  actions: {
547
- // @ts-expect-error Ignore missing properties
548
656
  count: true
549
657
  }
550
658
  }
@@ -636,28 +744,22 @@ const createReleaseService = ({ strapi: strapi2 }) => {
636
744
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
637
745
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
638
746
  ]);
639
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
747
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
640
748
  if (!release2) {
641
749
  throw new utils.errors.NotFoundError(`No release found for id ${id}`);
642
750
  }
643
751
  if (release2.releasedAt) {
644
752
  throw new utils.errors.ValidationError("Release already published");
645
753
  }
646
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
647
- /*
648
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
649
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
650
- */
651
- // @ts-expect-error see above
754
+ const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
755
+ where: { id },
652
756
  data: releaseWithCreatorFields
653
757
  });
654
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
655
- const schedulingService = getService("scheduling", { strapi: strapi2 });
656
- if (releaseData.scheduledAt) {
657
- await schedulingService.set(id, releaseData.scheduledAt);
658
- } else if (release2.scheduledAt) {
659
- schedulingService.cancel(id);
660
- }
758
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
759
+ if (releaseData.scheduledAt) {
760
+ await schedulingService.set(id, releaseData.scheduledAt);
761
+ } else if (release2.scheduledAt) {
762
+ schedulingService.cancel(id);
661
763
  }
662
764
  this.updateReleaseStatus(id);
663
765
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -671,7 +773,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
671
773
  validateEntryContentType(action.entry.contentType),
672
774
  validateUniqueEntry(releaseId, action)
673
775
  ]);
674
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
776
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
675
777
  if (!release2) {
676
778
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
677
779
  }
@@ -681,7 +783,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
681
783
  const { entry, type } = action;
682
784
  const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
683
785
  const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
684
- const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
786
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
685
787
  data: {
686
788
  type,
687
789
  contentType: entry.contentType,
@@ -694,32 +796,41 @@ const createReleaseService = ({ strapi: strapi2 }) => {
694
796
  },
695
797
  release: releaseId
696
798
  },
697
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
799
+ populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
698
800
  });
699
801
  this.updateReleaseStatus(releaseId);
700
802
  return releaseAction2;
701
803
  },
702
804
  async findActions(releaseId, query) {
703
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
704
- fields: ["id"]
805
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
806
+ where: { id: releaseId },
807
+ select: ["id"]
705
808
  });
706
809
  if (!release2) {
707
810
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
708
811
  }
709
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
710
- ...query,
812
+ const dbQuery = utils.convertQueryParams.transformParamsToQuery(
813
+ RELEASE_ACTION_MODEL_UID,
814
+ query ?? {}
815
+ );
816
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
817
+ ...dbQuery,
711
818
  populate: {
712
819
  entry: {
713
820
  populate: "*"
714
821
  }
715
822
  },
716
- filters: {
823
+ where: {
717
824
  release: releaseId
718
825
  }
719
826
  });
720
827
  },
721
828
  async countActions(query) {
722
- return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
829
+ const dbQuery = utils.convertQueryParams.transformParamsToQuery(
830
+ RELEASE_ACTION_MODEL_UID,
831
+ query ?? {}
832
+ );
833
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
723
834
  },
724
835
  async groupActions(actions, groupBy) {
725
836
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -800,10 +911,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
800
911
  return componentsMap;
801
912
  },
802
913
  async delete(releaseId) {
803
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
914
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
915
+ where: { id: releaseId },
804
916
  populate: {
805
917
  actions: {
806
- fields: ["id"]
918
+ select: ["id"]
807
919
  }
808
920
  }
809
921
  });
@@ -821,9 +933,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
821
933
  }
822
934
  }
823
935
  });
824
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
936
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
937
+ where: {
938
+ id: releaseId
939
+ }
940
+ });
825
941
  });
826
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
942
+ if (release2.scheduledAt) {
827
943
  const schedulingService = getService("scheduling", { strapi: strapi2 });
828
944
  await schedulingService.cancel(release2.id);
829
945
  }
@@ -831,145 +947,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
831
947
  return release2;
832
948
  },
833
949
  async publish(releaseId) {
834
- try {
835
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
836
- RELEASE_MODEL_UID,
837
- releaseId,
838
- {
839
- populate: {
840
- actions: {
841
- populate: {
842
- entry: {
843
- fields: ["id"]
844
- }
845
- }
846
- }
847
- }
848
- }
849
- );
850
- if (!releaseWithPopulatedActionEntries) {
950
+ const {
951
+ release: release2,
952
+ error
953
+ } = await strapi2.db.transaction(async ({ trx }) => {
954
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
955
+ if (!lockedRelease) {
851
956
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
852
957
  }
853
- if (releaseWithPopulatedActionEntries.releasedAt) {
958
+ if (lockedRelease.releasedAt) {
854
959
  throw new utils.errors.ValidationError("Release already published");
855
960
  }
856
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
857
- throw new utils.errors.ValidationError("No entries to publish");
961
+ if (lockedRelease.status === "failed") {
962
+ throw new utils.errors.ValidationError("Release failed to publish");
858
963
  }
859
- const collectionTypeActions = {};
860
- const singleTypeActions = [];
861
- for (const action of releaseWithPopulatedActionEntries.actions) {
862
- const contentTypeUid = action.contentType;
863
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
864
- if (!collectionTypeActions[contentTypeUid]) {
865
- collectionTypeActions[contentTypeUid] = {
866
- entriestoPublishIds: [],
867
- entriesToUnpublishIds: []
868
- };
869
- }
870
- if (action.type === "publish") {
871
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
872
- } else {
873
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
874
- }
875
- } else {
876
- singleTypeActions.push({
877
- uid: contentTypeUid,
878
- action: action.type,
879
- id: action.entry.id
880
- });
881
- }
882
- }
883
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
884
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
885
- await strapi2.db.transaction(async () => {
886
- for (const { uid, action, id } of singleTypeActions) {
887
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
888
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
889
- try {
890
- if (action === "publish") {
891
- await entityManagerService.publish(entry, uid);
892
- } else {
893
- await entityManagerService.unpublish(entry, uid);
894
- }
895
- } catch (error) {
896
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
897
- } else {
898
- throw error;
899
- }
900
- }
901
- }
902
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
903
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
904
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
905
- const entriesToPublish = await strapi2.entityService.findMany(
906
- contentTypeUid,
907
- {
908
- filters: {
909
- id: {
910
- $in: entriestoPublishIds
911
- }
912
- },
913
- populate
914
- }
915
- );
916
- const entriesToUnpublish = await strapi2.entityService.findMany(
917
- contentTypeUid,
918
- {
919
- filters: {
920
- id: {
921
- $in: entriesToUnpublishIds
922
- }
923
- },
924
- populate
925
- }
926
- );
927
- if (entriesToPublish.length > 0) {
928
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
964
+ try {
965
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
966
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
967
+ releaseId
968
+ );
969
+ await strapi2.db.transaction(async () => {
970
+ for (const { uid, action, id } of singleTypeActions) {
971
+ await publishSingleTypeAction(uid, action, id);
929
972
  }
930
- if (entriesToUnpublish.length > 0) {
931
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
973
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
974
+ const uid = contentTypeUid;
975
+ await publishCollectionTypeAction(
976
+ uid,
977
+ collectionTypeActions[uid].entriesToPublishIds,
978
+ collectionTypeActions[uid].entriesToUnpublishIds
979
+ );
932
980
  }
933
- }
934
- });
935
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
936
- data: {
937
- /*
938
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
939
- */
940
- // @ts-expect-error see above
941
- releasedAt: /* @__PURE__ */ new Date()
942
- },
943
- populate: {
944
- actions: {
945
- // @ts-expect-error is not expecting count but it is working
946
- count: true
981
+ });
982
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
983
+ where: {
984
+ id: releaseId
985
+ },
986
+ data: {
987
+ status: "done",
988
+ releasedAt: /* @__PURE__ */ new Date()
947
989
  }
948
- }
949
- });
950
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
990
+ });
951
991
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
952
992
  isPublished: true,
953
- release: release2
993
+ release: release22
954
994
  });
955
- }
956
- strapi2.telemetry.send("didPublishContentRelease");
957
- return release2;
958
- } catch (error) {
959
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
995
+ strapi2.telemetry.send("didPublishContentRelease");
996
+ return { release: release22, error: null };
997
+ } catch (error2) {
960
998
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
961
999
  isPublished: false,
962
- error
1000
+ error: error2
963
1001
  });
964
- }
965
- strapi2.db.query(RELEASE_MODEL_UID).update({
966
- where: { id: releaseId },
967
- data: {
1002
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
968
1003
  status: "failed"
969
- }
970
- });
1004
+ }).transacting(trx).execute();
1005
+ return {
1006
+ release: null,
1007
+ error: error2
1008
+ };
1009
+ }
1010
+ });
1011
+ if (error instanceof Error) {
971
1012
  throw error;
972
1013
  }
1014
+ return release2;
973
1015
  },
974
1016
  async updateAction(actionId, releaseId, update) {
975
1017
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1056,10 +1098,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1056
1098
  }
1057
1099
  };
1058
1100
  };
1101
+ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1102
+ constructor(message) {
1103
+ super(message);
1104
+ this.name = "AlreadyOnReleaseError";
1105
+ }
1106
+ }
1059
1107
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1060
1108
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1061
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1062
- populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1109
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1110
+ where: {
1111
+ id: releaseId
1112
+ },
1113
+ populate: { actions: { populate: { entry: { select: ["id"] } } } }
1063
1114
  });
1064
1115
  if (!release2) {
1065
1116
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -1068,7 +1119,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1068
1119
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1069
1120
  );
1070
1121
  if (isEntryInRelease) {
1071
- throw new utils.errors.ValidationError(
1122
+ throw new AlreadyOnReleaseError(
1072
1123
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1073
1124
  );
1074
1125
  }
@@ -1078,17 +1129,9 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1078
1129
  if (!contentType) {
1079
1130
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1080
1131
  }
1081
- if (!contentType.options?.draftAndPublish) {
1082
- throw new utils.errors.ValidationError(
1083
- `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1084
- );
1085
- }
1086
1132
  },
1087
1133
  async validatePendingReleasesLimit() {
1088
- const maximumPendingReleases = (
1089
- // @ts-expect-error - options is not typed into features
1090
- EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
1091
- );
1134
+ const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1092
1135
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1093
1136
  filters: {
1094
1137
  releasedAt: {
@@ -1101,8 +1144,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1101
1144
  }
1102
1145
  },
1103
1146
  async validateUniqueNameForPendingRelease(name, id) {
1104
- const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1105
- filters: {
1147
+ const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1148
+ where: {
1106
1149
  releasedAt: {
1107
1150
  $null: true
1108
1151
  },
@@ -1176,7 +1219,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1176
1219
  const services = {
1177
1220
  release: createReleaseService,
1178
1221
  "release-validation": createReleaseValidationService,
1179
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1222
+ scheduling: createSchedulingService
1180
1223
  };
1181
1224
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1182
1225
  name: yup__namespace.string().trim().required(),
@@ -1229,7 +1272,7 @@ const releaseController = {
1229
1272
  }
1230
1273
  };
1231
1274
  });
1232
- const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1275
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1233
1276
  where: {
1234
1277
  releasedAt: null
1235
1278
  }
@@ -1352,6 +1395,38 @@ const releaseActionController = {
1352
1395
  data: releaseAction2
1353
1396
  };
1354
1397
  },
1398
+ async createMany(ctx) {
1399
+ const releaseId = ctx.params.releaseId;
1400
+ const releaseActionsArgs = ctx.request.body;
1401
+ await Promise.all(
1402
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1403
+ );
1404
+ const releaseService = getService("release", { strapi });
1405
+ const releaseActions = await strapi.db.transaction(async () => {
1406
+ const releaseActions2 = await Promise.all(
1407
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1408
+ try {
1409
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1410
+ return action;
1411
+ } catch (error) {
1412
+ if (error instanceof AlreadyOnReleaseError) {
1413
+ return null;
1414
+ }
1415
+ throw error;
1416
+ }
1417
+ })
1418
+ );
1419
+ return releaseActions2;
1420
+ });
1421
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1422
+ ctx.body = {
1423
+ data: newReleaseActions,
1424
+ meta: {
1425
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1426
+ totalEntries: releaseActions.length
1427
+ }
1428
+ };
1429
+ },
1355
1430
  async findMany(ctx) {
1356
1431
  const releaseId = ctx.params.releaseId;
1357
1432
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1375,7 +1450,7 @@ const releaseActionController = {
1375
1450
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1376
1451
  return acc;
1377
1452
  }, {});
1378
- const sanitizedResults = await utils.mapAsync(results, async (action) => ({
1453
+ const sanitizedResults = await utils.async.map(results, async (action) => ({
1379
1454
  ...action,
1380
1455
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1381
1456
  }));
@@ -1537,6 +1612,22 @@ const releaseAction = {
1537
1612
  ]
1538
1613
  }
1539
1614
  },
1615
+ {
1616
+ method: "POST",
1617
+ path: "/:releaseId/actions/bulk",
1618
+ handler: "release-action.createMany",
1619
+ config: {
1620
+ policies: [
1621
+ "admin::isAuthenticatedAdmin",
1622
+ {
1623
+ name: "admin::hasPermissions",
1624
+ config: {
1625
+ actions: ["plugin::content-releases.create-action"]
1626
+ }
1627
+ }
1628
+ ]
1629
+ }
1630
+ },
1540
1631
  {
1541
1632
  method: "GET",
1542
1633
  path: "/:releaseId/actions",
@@ -1591,9 +1682,8 @@ const routes = {
1591
1682
  release,
1592
1683
  "release-action": releaseAction
1593
1684
  };
1594
- const { features } = require("@strapi/strapi/dist/utils/ee");
1595
1685
  const getPlugin = () => {
1596
- if (features.isEnabled("cms-content-releases")) {
1686
+ if (strapi.ee.features.isEnabled("cms-content-releases")) {
1597
1687
  return {
1598
1688
  register,
1599
1689
  bootstrap,
@@ -1605,6 +1695,9 @@ const getPlugin = () => {
1605
1695
  };
1606
1696
  }
1607
1697
  return {
1698
+ // Always return register, it handles its own feature check
1699
+ register,
1700
+ // Always return contentTypes to avoid losing data when the feature is disabled
1608
1701
  contentTypes
1609
1702
  };
1610
1703
  };