@strapi/content-releases 0.0.0-experimental.e3e48deb89bd0a1b6cc69b698696566fa7854a95 → 0.0.0-experimental.e47108ccbbc4ad1bfaf4526fa6b70d6ace1ca7a9

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-PQlYzNfb.mjs → App-XbK-TdJn.mjs} +258 -283
  2. package/dist/_chunks/App-XbK-TdJn.mjs.map +1 -0
  3. package/dist/_chunks/{App-lzeJz92X.js → App-ftICpqDz.js} +256 -281
  4. package/dist/_chunks/App-ftICpqDz.js.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-4CUzVH2g.js} +2 -3
  10. package/dist/_chunks/en-4CUzVH2g.js.map +1 -0
  11. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-pOJ6G5fC.mjs} +2 -3
  12. package/dist/_chunks/en-pOJ6G5fC.mjs.map +1 -0
  13. package/dist/_chunks/{index--4AgLDzb.mjs → index-8LrruHqK.mjs} +33 -27
  14. package/dist/_chunks/index-8LrruHqK.mjs.map +1 -0
  15. package/dist/_chunks/{index-Nf1JfI-m.js → index-RYVGXFeL.js} +29 -23
  16. package/dist/_chunks/index-RYVGXFeL.js.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 +344 -203
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +344 -204
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +21 -14
  24. package/dist/_chunks/App-PQlYzNfb.mjs.map +0 -1
  25. package/dist/_chunks/App-lzeJz92X.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--4AgLDzb.mjs.map +0 -1
  31. package/dist/_chunks/index-Nf1JfI-m.js.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 -2092
  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 -1826
  89. package/dist/server/src/services/index.d.ts.map +0 -1
  90. package/dist/server/src/services/release.d.ts +0 -66
  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 -14
  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";
@@ -82,10 +84,7 @@ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
82
84
  const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
83
85
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
84
86
  const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
85
- const entry = await strapi2.db.query(contentTypeUid).findOne({
86
- where: { id: entryId },
87
- populate
88
- });
87
+ const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
89
88
  return entry;
90
89
  };
91
90
  const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
@@ -102,10 +101,28 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
102
101
  return false;
103
102
  }
104
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
+ }
105
122
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
106
123
  const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
107
124
  if (deletedContentTypes.length) {
108
- await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
125
+ await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
109
126
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
110
127
  });
111
128
  }
@@ -124,7 +141,7 @@ async function migrateIsValidAndStatusReleases() {
124
141
  }
125
142
  }
126
143
  });
127
- utils.async.map(releasesWithoutStatus, async (release2) => {
144
+ utils.mapAsync(releasesWithoutStatus, async (release2) => {
128
145
  const actions = release2.actions;
129
146
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
130
147
  for (const action of notValidatedActions) {
@@ -155,7 +172,7 @@ async function migrateIsValidAndStatusReleases() {
155
172
  }
156
173
  }
157
174
  });
158
- utils.async.map(publishedReleases, async (release2) => {
175
+ utils.mapAsync(publishedReleases, async (release2) => {
159
176
  return strapi.db.query(RELEASE_MODEL_UID).update({
160
177
  where: {
161
178
  id: release2.id
@@ -168,9 +185,11 @@ async function migrateIsValidAndStatusReleases() {
168
185
  }
169
186
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
170
187
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
171
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
188
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
189
+ (uid) => oldContentTypes[uid]?.options?.draftAndPublish
190
+ );
172
191
  const releasesAffected = /* @__PURE__ */ new Set();
173
- utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
192
+ utils.mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
174
193
  const oldContentType = oldContentTypes[contentTypeUID];
175
194
  const contentType = contentTypes2[contentTypeUID];
176
195
  if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
@@ -183,8 +202,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
183
202
  release: true
184
203
  }
185
204
  });
186
- await utils.async.map(actions, async (action) => {
187
- if (action.entry) {
205
+ await utils.mapAsync(actions, async (action) => {
206
+ if (action.entry && action.release) {
188
207
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
189
208
  strapi
190
209
  });
@@ -206,21 +225,71 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
206
225
  });
207
226
  }
208
227
  }).then(() => {
209
- utils.async.map(releasesAffected, async (releaseId) => {
228
+ utils.mapAsync(releasesAffected, async (releaseId) => {
210
229
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
211
230
  });
212
231
  });
213
232
  }
214
233
  }
234
+ async function disableContentTypeLocalized({ 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
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
247
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
248
+ locale: null
249
+ }).where({ contentType: uid }).execute();
250
+ }
251
+ }
252
+ }
253
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
254
+ if (!oldContentTypes) {
255
+ return;
256
+ }
257
+ for (const uid in contentTypes2) {
258
+ if (!oldContentTypes[uid]) {
259
+ continue;
260
+ }
261
+ const oldContentType = oldContentTypes[uid];
262
+ const contentType = contentTypes2[uid];
263
+ const i18nPlugin = strapi.plugin("i18n");
264
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
265
+ const { getDefaultLocale } = i18nPlugin.service("locales");
266
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
267
+ const defaultLocale = await getDefaultLocale();
268
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
269
+ locale: defaultLocale
270
+ }).where({ contentType: uid }).execute();
271
+ }
272
+ }
273
+ }
274
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
215
275
  const register = async ({ strapi: strapi2 }) => {
216
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
276
+ if (features$2.isEnabled("cms-content-releases")) {
217
277
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
218
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
278
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
279
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
280
+ }
281
+ if (strapi2.plugin("graphql")) {
282
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
283
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
284
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
219
285
  }
220
286
  };
287
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
221
288
  const bootstrap = async ({ strapi: strapi2 }) => {
222
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
223
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
289
+ if (features$1.isEnabled("cms-content-releases")) {
290
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
291
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
292
+ );
224
293
  strapi2.db.lifecycles.subscribe({
225
294
  models: contentTypesWithDraftAndPublish,
226
295
  async afterDelete(event) {
@@ -256,7 +325,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
256
325
  */
257
326
  async beforeDeleteMany(event) {
258
327
  const { model, params } = event;
259
- if (model.kind === "collectionType") {
328
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
260
329
  const { where } = params;
261
330
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
262
331
  event.state.entriesToDelete = entriesToDelete;
@@ -338,27 +407,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
338
407
  }
339
408
  }
340
409
  });
341
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
342
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
343
- strapi2.log.error(
344
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
345
- );
346
- throw err;
347
- });
348
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
349
- strapi2.webhookStore.addAllowedEvent(key, value);
350
- });
351
- }
410
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
411
+ strapi2.log.error(
412
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
413
+ );
414
+ throw err;
415
+ });
416
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
417
+ strapi2.webhookStore.addAllowedEvent(key, value);
418
+ });
352
419
  }
353
420
  };
354
421
  const destroy = async ({ strapi: strapi2 }) => {
355
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
356
- const scheduledJobs = getService("scheduling", {
357
- strapi: strapi2
358
- }).getAll();
359
- for (const [, job] of scheduledJobs) {
360
- job.cancel();
361
- }
422
+ const scheduledJobs = getService("scheduling", {
423
+ strapi: strapi2
424
+ }).getAll();
425
+ for (const [, job] of scheduledJobs) {
426
+ job.cancel();
362
427
  }
363
428
  };
364
429
  const schema$1 = {
@@ -483,6 +548,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
483
548
  release: release2
484
549
  });
485
550
  };
551
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
552
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
553
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
554
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
555
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
556
+ try {
557
+ if (actionType === "publish") {
558
+ await entityManagerService.publish(entry, uid);
559
+ } else {
560
+ await entityManagerService.unpublish(entry, uid);
561
+ }
562
+ } catch (error) {
563
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
564
+ ;
565
+ else {
566
+ throw error;
567
+ }
568
+ }
569
+ };
570
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
571
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
572
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
573
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
574
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
575
+ filters: {
576
+ id: {
577
+ $in: entriesToPublishIds
578
+ }
579
+ },
580
+ populate
581
+ });
582
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
583
+ filters: {
584
+ id: {
585
+ $in: entriestoUnpublishIds
586
+ }
587
+ },
588
+ populate
589
+ });
590
+ if (entriesToPublish.length > 0) {
591
+ await entityManagerService.publishMany(entriesToPublish, uid);
592
+ }
593
+ if (entriesToUnpublish.length > 0) {
594
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
595
+ }
596
+ };
597
+ const getFormattedActions = async (releaseId) => {
598
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
599
+ where: {
600
+ release: {
601
+ id: releaseId
602
+ }
603
+ },
604
+ populate: {
605
+ entry: {
606
+ fields: ["id"]
607
+ }
608
+ }
609
+ });
610
+ if (actions.length === 0) {
611
+ throw new utils.errors.ValidationError("No entries to publish");
612
+ }
613
+ const collectionTypeActions = {};
614
+ const singleTypeActions = [];
615
+ for (const action of actions) {
616
+ const contentTypeUid = action.contentType;
617
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
618
+ if (!collectionTypeActions[contentTypeUid]) {
619
+ collectionTypeActions[contentTypeUid] = {
620
+ entriesToPublishIds: [],
621
+ entriesToUnpublishIds: []
622
+ };
623
+ }
624
+ if (action.type === "publish") {
625
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
626
+ } else {
627
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
628
+ }
629
+ } else {
630
+ singleTypeActions.push({
631
+ uid: contentTypeUid,
632
+ action: action.type,
633
+ id: action.entry.id
634
+ });
635
+ }
636
+ }
637
+ return { collectionTypeActions, singleTypeActions };
638
+ };
486
639
  return {
487
640
  async create(releaseData, { user }) {
488
641
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
@@ -496,13 +649,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
496
649
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
497
650
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
498
651
  ]);
499
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
652
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
500
653
  data: {
501
654
  ...releaseWithCreatorFields,
502
655
  status: "empty"
503
656
  }
504
657
  });
505
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
658
+ if (releaseWithCreatorFields.scheduledAt) {
506
659
  const schedulingService = getService("scheduling", { strapi: strapi2 });
507
660
  await schedulingService.set(release2.id, release2.scheduledAt);
508
661
  }
@@ -510,19 +663,17 @@ const createReleaseService = ({ strapi: strapi2 }) => {
510
663
  return release2;
511
664
  },
512
665
  async findOne(id, query = {}) {
513
- const dbQuery = utils.convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query);
514
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
515
- ...dbQuery,
516
- where: { id }
666
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
667
+ ...query
517
668
  });
518
669
  return release2;
519
670
  },
520
671
  findPage(query) {
521
- const dbQuery = utils.convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
522
- return strapi2.db.query(RELEASE_MODEL_UID).findPage({
523
- ...dbQuery,
672
+ return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
673
+ ...query,
524
674
  populate: {
525
675
  actions: {
676
+ // @ts-expect-error Ignore missing properties
526
677
  count: true
527
678
  }
528
679
  }
@@ -614,24 +765,26 @@ const createReleaseService = ({ strapi: strapi2 }) => {
614
765
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
615
766
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
616
767
  ]);
617
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
768
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
618
769
  if (!release2) {
619
770
  throw new utils.errors.NotFoundError(`No release found for id ${id}`);
620
771
  }
621
772
  if (release2.releasedAt) {
622
773
  throw new utils.errors.ValidationError("Release already published");
623
774
  }
624
- const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
625
- where: { id },
775
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
776
+ /*
777
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
778
+ * is not compatible with the type we are passing here: UpdateRelease.Request['body']
779
+ */
780
+ // @ts-expect-error see above
626
781
  data: releaseWithCreatorFields
627
782
  });
628
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
629
- const schedulingService = getService("scheduling", { strapi: strapi2 });
630
- if (releaseData.scheduledAt) {
631
- await schedulingService.set(id, releaseData.scheduledAt);
632
- } else if (release2.scheduledAt) {
633
- schedulingService.cancel(id);
634
- }
783
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
784
+ if (releaseData.scheduledAt) {
785
+ await schedulingService.set(id, releaseData.scheduledAt);
786
+ } else if (release2.scheduledAt) {
787
+ schedulingService.cancel(id);
635
788
  }
636
789
  this.updateReleaseStatus(id);
637
790
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -645,7 +798,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
645
798
  validateEntryContentType(action.entry.contentType),
646
799
  validateUniqueEntry(releaseId, action)
647
800
  ]);
648
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
801
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
649
802
  if (!release2) {
650
803
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
651
804
  }
@@ -655,7 +808,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
655
808
  const { entry, type } = action;
656
809
  const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
657
810
  const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
658
- const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
811
+ const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
659
812
  data: {
660
813
  type,
661
814
  contentType: entry.contentType,
@@ -668,41 +821,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
668
821
  },
669
822
  release: releaseId
670
823
  },
671
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
824
+ populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
672
825
  });
673
826
  this.updateReleaseStatus(releaseId);
674
827
  return releaseAction2;
675
828
  },
676
829
  async findActions(releaseId, query) {
677
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
678
- where: { id: releaseId },
679
- select: ["id"]
830
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
831
+ fields: ["id"]
680
832
  });
681
833
  if (!release2) {
682
834
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
683
835
  }
684
- const dbQuery = utils.convertQueryParams.transformParamsToQuery(
685
- RELEASE_ACTION_MODEL_UID,
686
- query ?? {}
687
- );
688
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
689
- ...dbQuery,
836
+ return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
837
+ ...query,
690
838
  populate: {
691
839
  entry: {
692
840
  populate: "*"
693
841
  }
694
842
  },
695
- where: {
843
+ filters: {
696
844
  release: releaseId
697
845
  }
698
846
  });
699
847
  },
700
848
  async countActions(query) {
701
- const dbQuery = utils.convertQueryParams.transformParamsToQuery(
702
- RELEASE_ACTION_MODEL_UID,
703
- query ?? {}
704
- );
705
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
849
+ return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
706
850
  },
707
851
  async groupActions(actions, groupBy) {
708
852
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -783,11 +927,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
783
927
  return componentsMap;
784
928
  },
785
929
  async delete(releaseId) {
786
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
787
- where: { id: releaseId },
930
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
788
931
  populate: {
789
932
  actions: {
790
- select: ["id"]
933
+ fields: ["id"]
791
934
  }
792
935
  }
793
936
  });
@@ -805,13 +948,9 @@ const createReleaseService = ({ strapi: strapi2 }) => {
805
948
  }
806
949
  }
807
950
  });
808
- await strapi2.db.query(RELEASE_MODEL_UID).delete({
809
- where: {
810
- id: releaseId
811
- }
812
- });
951
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
813
952
  });
814
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
953
+ if (release2.scheduledAt) {
815
954
  const schedulingService = getService("scheduling", { strapi: strapi2 });
816
955
  await schedulingService.cancel(release2.id);
817
956
  }
@@ -819,132 +958,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
819
958
  return release2;
820
959
  },
821
960
  async publish(releaseId) {
822
- try {
823
- const releaseWithPopulatedActionEntries = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
824
- where: { id: releaseId },
825
- populate: {
826
- actions: {
827
- populate: {
828
- entry: {
829
- select: ["id"]
830
- }
831
- }
832
- }
833
- }
834
- });
835
- if (!releaseWithPopulatedActionEntries) {
961
+ const {
962
+ release: release2,
963
+ error
964
+ } = await strapi2.db.transaction(async ({ trx }) => {
965
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
966
+ if (!lockedRelease) {
836
967
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
837
968
  }
838
- if (releaseWithPopulatedActionEntries.releasedAt) {
969
+ if (lockedRelease.releasedAt) {
839
970
  throw new utils.errors.ValidationError("Release already published");
840
971
  }
841
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
842
- throw new utils.errors.ValidationError("No entries to publish");
843
- }
844
- const collectionTypeActions = {};
845
- const singleTypeActions = [];
846
- for (const action of releaseWithPopulatedActionEntries.actions) {
847
- const contentTypeUid = action.contentType;
848
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
849
- if (!collectionTypeActions[contentTypeUid]) {
850
- collectionTypeActions[contentTypeUid] = {
851
- entriestoPublishIds: [],
852
- entriesToUnpublishIds: []
853
- };
854
- }
855
- if (action.type === "publish") {
856
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
857
- } else {
858
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
859
- }
860
- } else {
861
- singleTypeActions.push({
862
- uid: contentTypeUid,
863
- action: action.type,
864
- id: action.entry.id
865
- });
866
- }
972
+ if (lockedRelease.status === "failed") {
973
+ throw new utils.errors.ValidationError("Release failed to publish");
867
974
  }
868
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
869
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
870
- await strapi2.db.transaction(async () => {
871
- for (const { uid, action, id } of singleTypeActions) {
872
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
873
- const entry = await strapi2.db.query(uid).findOne({ where: { id }, populate });
874
- try {
875
- if (action === "publish") {
876
- await entityManagerService.publish(entry, uid);
877
- } else {
878
- await entityManagerService.unpublish(entry, uid);
879
- }
880
- } catch (error) {
881
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
882
- } else {
883
- throw error;
884
- }
885
- }
886
- }
887
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
888
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
889
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
890
- const entriesToPublish = await strapi2.db.query(contentTypeUid).findMany({
891
- where: {
892
- id: {
893
- $in: entriestoPublishIds
894
- }
895
- },
896
- populate
897
- });
898
- const entriesToUnpublish = await strapi2.db.query(contentTypeUid).findMany({
899
- where: {
900
- id: {
901
- $in: entriesToUnpublishIds
902
- }
903
- },
904
- populate
905
- });
906
- if (entriesToPublish.length > 0) {
907
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
975
+ try {
976
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
977
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
978
+ releaseId
979
+ );
980
+ await strapi2.db.transaction(async () => {
981
+ for (const { uid, action, id } of singleTypeActions) {
982
+ await publishSingleTypeAction(uid, action, id);
908
983
  }
909
- if (entriesToUnpublish.length > 0) {
910
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
984
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
985
+ const uid = contentTypeUid;
986
+ await publishCollectionTypeAction(
987
+ uid,
988
+ collectionTypeActions[uid].entriesToPublishIds,
989
+ collectionTypeActions[uid].entriesToUnpublishIds
990
+ );
911
991
  }
912
- }
913
- });
914
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).update({
915
- where: { id: releaseId },
916
- data: {
917
- releasedAt: /* @__PURE__ */ new Date()
918
- },
919
- populate: {
920
- actions: {
921
- count: true
992
+ });
993
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
994
+ where: {
995
+ id: releaseId
996
+ },
997
+ data: {
998
+ status: "done",
999
+ releasedAt: /* @__PURE__ */ new Date()
922
1000
  }
923
- }
924
- });
925
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1001
+ });
926
1002
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
927
1003
  isPublished: true,
928
- release: release2
1004
+ release: release22
929
1005
  });
930
- }
931
- strapi2.telemetry.send("didPublishContentRelease");
932
- return release2;
933
- } catch (error) {
934
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1006
+ strapi2.telemetry.send("didPublishContentRelease");
1007
+ return { release: release22, error: null };
1008
+ } catch (error2) {
935
1009
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
936
1010
  isPublished: false,
937
- error
1011
+ error: error2
938
1012
  });
939
- }
940
- strapi2.db.query(RELEASE_MODEL_UID).update({
941
- where: { id: releaseId },
942
- data: {
1013
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
943
1014
  status: "failed"
944
- }
945
- });
1015
+ }).transacting(trx).execute();
1016
+ return {
1017
+ release: null,
1018
+ error: error2
1019
+ };
1020
+ }
1021
+ });
1022
+ if (error) {
946
1023
  throw error;
947
1024
  }
1025
+ return release2;
948
1026
  },
949
1027
  async updateAction(actionId, releaseId, update) {
950
1028
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1031,13 +1109,16 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1031
1109
  }
1032
1110
  };
1033
1111
  };
1112
+ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1113
+ constructor(message) {
1114
+ super(message);
1115
+ this.name = "AlreadyOnReleaseError";
1116
+ }
1117
+ }
1034
1118
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1035
1119
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1036
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1037
- where: {
1038
- id: releaseId
1039
- },
1040
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1120
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1121
+ populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1041
1122
  });
1042
1123
  if (!release2) {
1043
1124
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -1046,7 +1127,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1046
1127
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1047
1128
  );
1048
1129
  if (isEntryInRelease) {
1049
- throw new utils.errors.ValidationError(
1130
+ throw new AlreadyOnReleaseError(
1050
1131
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1051
1132
  );
1052
1133
  }
@@ -1056,9 +1137,17 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1056
1137
  if (!contentType) {
1057
1138
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1058
1139
  }
1140
+ if (!contentType.options?.draftAndPublish) {
1141
+ throw new utils.errors.ValidationError(
1142
+ `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1143
+ );
1144
+ }
1059
1145
  },
1060
1146
  async validatePendingReleasesLimit() {
1061
- const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1147
+ const maximumPendingReleases = (
1148
+ // @ts-expect-error - options is not typed into features
1149
+ EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
1150
+ );
1062
1151
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1063
1152
  filters: {
1064
1153
  releasedAt: {
@@ -1071,8 +1160,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1071
1160
  }
1072
1161
  },
1073
1162
  async validateUniqueNameForPendingRelease(name, id) {
1074
- const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1075
- where: {
1163
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1164
+ filters: {
1076
1165
  releasedAt: {
1077
1166
  $null: true
1078
1167
  },
@@ -1146,7 +1235,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1146
1235
  const services = {
1147
1236
  release: createReleaseService,
1148
1237
  "release-validation": createReleaseValidationService,
1149
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1238
+ scheduling: createSchedulingService
1150
1239
  };
1151
1240
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1152
1241
  name: yup__namespace.string().trim().required(),
@@ -1199,7 +1288,7 @@ const releaseController = {
1199
1288
  }
1200
1289
  };
1201
1290
  });
1202
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1291
+ const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1203
1292
  where: {
1204
1293
  releasedAt: null
1205
1294
  }
@@ -1322,6 +1411,38 @@ const releaseActionController = {
1322
1411
  data: releaseAction2
1323
1412
  };
1324
1413
  },
1414
+ async createMany(ctx) {
1415
+ const releaseId = ctx.params.releaseId;
1416
+ const releaseActionsArgs = ctx.request.body;
1417
+ await Promise.all(
1418
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1419
+ );
1420
+ const releaseService = getService("release", { strapi });
1421
+ const releaseActions = await strapi.db.transaction(async () => {
1422
+ const releaseActions2 = await Promise.all(
1423
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1424
+ try {
1425
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1426
+ return action;
1427
+ } catch (error) {
1428
+ if (error instanceof AlreadyOnReleaseError) {
1429
+ return null;
1430
+ }
1431
+ throw error;
1432
+ }
1433
+ })
1434
+ );
1435
+ return releaseActions2;
1436
+ });
1437
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1438
+ ctx.body = {
1439
+ data: newReleaseActions,
1440
+ meta: {
1441
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1442
+ totalEntries: releaseActions.length
1443
+ }
1444
+ };
1445
+ },
1325
1446
  async findMany(ctx) {
1326
1447
  const releaseId = ctx.params.releaseId;
1327
1448
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1345,7 +1466,7 @@ const releaseActionController = {
1345
1466
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1346
1467
  return acc;
1347
1468
  }, {});
1348
- const sanitizedResults = await utils.async.map(results, async (action) => ({
1469
+ const sanitizedResults = await utils.mapAsync(results, async (action) => ({
1349
1470
  ...action,
1350
1471
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1351
1472
  }));
@@ -1507,6 +1628,22 @@ const releaseAction = {
1507
1628
  ]
1508
1629
  }
1509
1630
  },
1631
+ {
1632
+ method: "POST",
1633
+ path: "/:releaseId/actions/bulk",
1634
+ handler: "release-action.createMany",
1635
+ config: {
1636
+ policies: [
1637
+ "admin::isAuthenticatedAdmin",
1638
+ {
1639
+ name: "admin::hasPermissions",
1640
+ config: {
1641
+ actions: ["plugin::content-releases.create-action"]
1642
+ }
1643
+ }
1644
+ ]
1645
+ }
1646
+ },
1510
1647
  {
1511
1648
  method: "GET",
1512
1649
  path: "/:releaseId/actions",
@@ -1561,8 +1698,9 @@ const routes = {
1561
1698
  release,
1562
1699
  "release-action": releaseAction
1563
1700
  };
1701
+ const { features } = require("@strapi/strapi/dist/utils/ee");
1564
1702
  const getPlugin = () => {
1565
- if (strapi.ee.features.isEnabled("cms-content-releases")) {
1703
+ if (features.isEnabled("cms-content-releases")) {
1566
1704
  return {
1567
1705
  register,
1568
1706
  bootstrap,
@@ -1574,6 +1712,9 @@ const getPlugin = () => {
1574
1712
  };
1575
1713
  }
1576
1714
  return {
1715
+ // Always return register, it handles its own feature check
1716
+ register,
1717
+ // Always return contentTypes to avoid losing data when the feature is disabled
1577
1718
  contentTypes
1578
1719
  };
1579
1720
  };