@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
@@ -1,7 +1,8 @@
1
- import { async, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
1
+ import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
2
2
  import isEqual from "lodash/isEqual";
3
3
  import { difference, keys } from "lodash";
4
4
  import _ from "lodash/fp";
5
+ import EE from "@strapi/strapi/dist/utils/ee";
5
6
  import { scheduleJob } from "node-schedule";
6
7
  import * as yup from "yup";
7
8
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
@@ -76,10 +77,28 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
76
77
  return false;
77
78
  }
78
79
  };
80
+ async function deleteActionsOnDisableDraftAndPublish({
81
+ oldContentTypes,
82
+ contentTypes: contentTypes2
83
+ }) {
84
+ if (!oldContentTypes) {
85
+ return;
86
+ }
87
+ for (const uid in contentTypes2) {
88
+ if (!oldContentTypes[uid]) {
89
+ continue;
90
+ }
91
+ const oldContentType = oldContentTypes[uid];
92
+ const contentType = contentTypes2[uid];
93
+ if (contentTypes$1.hasDraftAndPublish(oldContentType) && !contentTypes$1.hasDraftAndPublish(contentType)) {
94
+ await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
95
+ }
96
+ }
97
+ }
79
98
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
80
99
  const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
81
100
  if (deletedContentTypes.length) {
82
- await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
101
+ await mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
83
102
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
84
103
  });
85
104
  }
@@ -98,7 +117,7 @@ async function migrateIsValidAndStatusReleases() {
98
117
  }
99
118
  }
100
119
  });
101
- async.map(releasesWithoutStatus, async (release2) => {
120
+ mapAsync(releasesWithoutStatus, async (release2) => {
102
121
  const actions = release2.actions;
103
122
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
104
123
  for (const action of notValidatedActions) {
@@ -129,7 +148,7 @@ async function migrateIsValidAndStatusReleases() {
129
148
  }
130
149
  }
131
150
  });
132
- async.map(publishedReleases, async (release2) => {
151
+ mapAsync(publishedReleases, async (release2) => {
133
152
  return strapi.db.query(RELEASE_MODEL_UID).update({
134
153
  where: {
135
154
  id: release2.id
@@ -142,9 +161,11 @@ async function migrateIsValidAndStatusReleases() {
142
161
  }
143
162
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
144
163
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
145
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
164
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
165
+ (uid) => oldContentTypes[uid]?.options?.draftAndPublish
166
+ );
146
167
  const releasesAffected = /* @__PURE__ */ new Set();
147
- async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
168
+ mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
148
169
  const oldContentType = oldContentTypes[contentTypeUID];
149
170
  const contentType = contentTypes2[contentTypeUID];
150
171
  if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
@@ -157,8 +178,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
157
178
  release: true
158
179
  }
159
180
  });
160
- await async.map(actions, async (action) => {
161
- if (action.entry) {
181
+ await mapAsync(actions, async (action) => {
182
+ if (action.entry && action.release) {
162
183
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
163
184
  strapi
164
185
  });
@@ -180,21 +201,77 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
180
201
  });
181
202
  }
182
203
  }).then(() => {
183
- async.map(releasesAffected, async (releaseId) => {
204
+ mapAsync(releasesAffected, async (releaseId) => {
184
205
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
185
206
  });
186
207
  });
187
208
  }
188
209
  }
210
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
211
+ if (!oldContentTypes) {
212
+ return;
213
+ }
214
+ const i18nPlugin = strapi.plugin("i18n");
215
+ if (!i18nPlugin) {
216
+ return;
217
+ }
218
+ for (const uid in contentTypes2) {
219
+ if (!oldContentTypes[uid]) {
220
+ continue;
221
+ }
222
+ const oldContentType = oldContentTypes[uid];
223
+ const contentType = contentTypes2[uid];
224
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
225
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
226
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
227
+ locale: null
228
+ }).where({ contentType: uid }).execute();
229
+ }
230
+ }
231
+ }
232
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
233
+ if (!oldContentTypes) {
234
+ return;
235
+ }
236
+ const i18nPlugin = strapi.plugin("i18n");
237
+ if (!i18nPlugin) {
238
+ return;
239
+ }
240
+ for (const uid in contentTypes2) {
241
+ if (!oldContentTypes[uid]) {
242
+ continue;
243
+ }
244
+ const oldContentType = oldContentTypes[uid];
245
+ const contentType = contentTypes2[uid];
246
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
247
+ const { getDefaultLocale } = i18nPlugin.service("locales");
248
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
249
+ const defaultLocale = await getDefaultLocale();
250
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
251
+ locale: defaultLocale
252
+ }).where({ contentType: uid }).execute();
253
+ }
254
+ }
255
+ }
256
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
189
257
  const register = async ({ strapi: strapi2 }) => {
190
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
258
+ if (features$2.isEnabled("cms-content-releases")) {
191
259
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
192
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
260
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
261
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
262
+ }
263
+ if (strapi2.plugin("graphql")) {
264
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
265
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
266
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
193
267
  }
194
268
  };
269
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
195
270
  const bootstrap = async ({ strapi: strapi2 }) => {
196
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
197
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
271
+ if (features$1.isEnabled("cms-content-releases")) {
272
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
273
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
274
+ );
198
275
  strapi2.db.lifecycles.subscribe({
199
276
  models: contentTypesWithDraftAndPublish,
200
277
  async afterDelete(event) {
@@ -230,7 +307,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
230
307
  */
231
308
  async beforeDeleteMany(event) {
232
309
  const { model, params } = event;
233
- if (model.kind === "collectionType") {
310
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
234
311
  const { where } = params;
235
312
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
236
313
  event.state.entriesToDelete = entriesToDelete;
@@ -312,27 +389,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
312
389
  }
313
390
  }
314
391
  });
315
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
316
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
317
- strapi2.log.error(
318
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
319
- );
320
- throw err;
321
- });
322
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
323
- strapi2.webhookStore.addAllowedEvent(key, value);
324
- });
325
- }
392
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
393
+ strapi2.log.error(
394
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
395
+ );
396
+ throw err;
397
+ });
398
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
399
+ strapi2.webhookStore.addAllowedEvent(key, value);
400
+ });
326
401
  }
327
402
  };
328
403
  const destroy = async ({ strapi: strapi2 }) => {
329
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
330
- const scheduledJobs = getService("scheduling", {
331
- strapi: strapi2
332
- }).getAll();
333
- for (const [, job] of scheduledJobs) {
334
- job.cancel();
335
- }
404
+ const scheduledJobs = getService("scheduling", {
405
+ strapi: strapi2
406
+ }).getAll();
407
+ for (const [, job] of scheduledJobs) {
408
+ job.cancel();
336
409
  }
337
410
  };
338
411
  const schema$1 = {
@@ -457,6 +530,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
457
530
  release: release2
458
531
  });
459
532
  };
533
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
534
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
535
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
536
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
537
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
538
+ try {
539
+ if (actionType === "publish") {
540
+ await entityManagerService.publish(entry, uid);
541
+ } else {
542
+ await entityManagerService.unpublish(entry, uid);
543
+ }
544
+ } catch (error) {
545
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
546
+ ;
547
+ else {
548
+ throw error;
549
+ }
550
+ }
551
+ };
552
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
553
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
554
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
555
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
556
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
557
+ filters: {
558
+ id: {
559
+ $in: entriesToPublishIds
560
+ }
561
+ },
562
+ populate
563
+ });
564
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
565
+ filters: {
566
+ id: {
567
+ $in: entriestoUnpublishIds
568
+ }
569
+ },
570
+ populate
571
+ });
572
+ if (entriesToPublish.length > 0) {
573
+ await entityManagerService.publishMany(entriesToPublish, uid);
574
+ }
575
+ if (entriesToUnpublish.length > 0) {
576
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
577
+ }
578
+ };
579
+ const getFormattedActions = async (releaseId) => {
580
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
581
+ where: {
582
+ release: {
583
+ id: releaseId
584
+ }
585
+ },
586
+ populate: {
587
+ entry: {
588
+ fields: ["id"]
589
+ }
590
+ }
591
+ });
592
+ if (actions.length === 0) {
593
+ throw new errors.ValidationError("No entries to publish");
594
+ }
595
+ const collectionTypeActions = {};
596
+ const singleTypeActions = [];
597
+ for (const action of actions) {
598
+ const contentTypeUid = action.contentType;
599
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
600
+ if (!collectionTypeActions[contentTypeUid]) {
601
+ collectionTypeActions[contentTypeUid] = {
602
+ entriesToPublishIds: [],
603
+ entriesToUnpublishIds: []
604
+ };
605
+ }
606
+ if (action.type === "publish") {
607
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
608
+ } else {
609
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
610
+ }
611
+ } else {
612
+ singleTypeActions.push({
613
+ uid: contentTypeUid,
614
+ action: action.type,
615
+ id: action.entry.id
616
+ });
617
+ }
618
+ }
619
+ return { collectionTypeActions, singleTypeActions };
620
+ };
460
621
  return {
461
622
  async create(releaseData, { user }) {
462
623
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
@@ -476,7 +637,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
476
637
  status: "empty"
477
638
  }
478
639
  });
479
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
640
+ if (releaseWithCreatorFields.scheduledAt) {
480
641
  const schedulingService = getService("scheduling", { strapi: strapi2 });
481
642
  await schedulingService.set(release2.id, release2.scheduledAt);
482
643
  }
@@ -500,12 +661,18 @@ const createReleaseService = ({ strapi: strapi2 }) => {
500
661
  }
501
662
  });
502
663
  },
503
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
664
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
665
+ let entries = entriesIds;
666
+ if (!Array.isArray(entriesIds)) {
667
+ entries = [entriesIds];
668
+ }
504
669
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
505
670
  where: {
506
671
  actions: {
507
672
  target_type: contentTypeUid,
508
- target_id: entryId
673
+ target_id: {
674
+ $in: entries
675
+ }
509
676
  },
510
677
  releasedAt: {
511
678
  $null: true
@@ -516,18 +683,25 @@ const createReleaseService = ({ strapi: strapi2 }) => {
516
683
  actions: {
517
684
  where: {
518
685
  target_type: contentTypeUid,
519
- target_id: entryId
686
+ target_id: {
687
+ $in: entries
688
+ }
689
+ },
690
+ populate: {
691
+ entry: {
692
+ select: ["id"]
693
+ }
520
694
  }
521
695
  }
522
696
  }
523
697
  });
524
698
  return releases.map((release2) => {
525
699
  if (release2.actions?.length) {
526
- const [actionForEntry] = release2.actions;
700
+ const actionsForEntry = release2.actions;
527
701
  delete release2.actions;
528
702
  return {
529
703
  ...release2,
530
- action: actionForEntry
704
+ actions: actionsForEntry
531
705
  };
532
706
  }
533
707
  return release2;
@@ -601,13 +775,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
601
775
  // @ts-expect-error see above
602
776
  data: releaseWithCreatorFields
603
777
  });
604
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
605
- const schedulingService = getService("scheduling", { strapi: strapi2 });
606
- if (releaseData.scheduledAt) {
607
- await schedulingService.set(id, releaseData.scheduledAt);
608
- } else if (release2.scheduledAt) {
609
- schedulingService.cancel(id);
610
- }
778
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
779
+ if (releaseData.scheduledAt) {
780
+ await schedulingService.set(id, releaseData.scheduledAt);
781
+ } else if (release2.scheduledAt) {
782
+ schedulingService.cancel(id);
611
783
  }
612
784
  this.updateReleaseStatus(id);
613
785
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -773,7 +945,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
773
945
  });
774
946
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
775
947
  });
776
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
948
+ if (release2.scheduledAt) {
777
949
  const schedulingService = getService("scheduling", { strapi: strapi2 });
778
950
  await schedulingService.cancel(release2.id);
779
951
  }
@@ -781,145 +953,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
781
953
  return release2;
782
954
  },
783
955
  async publish(releaseId) {
784
- try {
785
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
786
- RELEASE_MODEL_UID,
787
- releaseId,
788
- {
789
- populate: {
790
- actions: {
791
- populate: {
792
- entry: {
793
- fields: ["id"]
794
- }
795
- }
796
- }
797
- }
798
- }
799
- );
800
- if (!releaseWithPopulatedActionEntries) {
956
+ const {
957
+ release: release2,
958
+ error
959
+ } = await strapi2.db.transaction(async ({ trx }) => {
960
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
961
+ if (!lockedRelease) {
801
962
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
802
963
  }
803
- if (releaseWithPopulatedActionEntries.releasedAt) {
964
+ if (lockedRelease.releasedAt) {
804
965
  throw new errors.ValidationError("Release already published");
805
966
  }
806
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
807
- throw new errors.ValidationError("No entries to publish");
967
+ if (lockedRelease.status === "failed") {
968
+ throw new errors.ValidationError("Release failed to publish");
808
969
  }
809
- const collectionTypeActions = {};
810
- const singleTypeActions = [];
811
- for (const action of releaseWithPopulatedActionEntries.actions) {
812
- const contentTypeUid = action.contentType;
813
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
814
- if (!collectionTypeActions[contentTypeUid]) {
815
- collectionTypeActions[contentTypeUid] = {
816
- entriestoPublishIds: [],
817
- entriesToUnpublishIds: []
818
- };
819
- }
820
- if (action.type === "publish") {
821
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
822
- } else {
823
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
824
- }
825
- } else {
826
- singleTypeActions.push({
827
- uid: contentTypeUid,
828
- action: action.type,
829
- id: action.entry.id
830
- });
831
- }
832
- }
833
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
834
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
835
- await strapi2.db.transaction(async () => {
836
- for (const { uid, action, id } of singleTypeActions) {
837
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
838
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
839
- try {
840
- if (action === "publish") {
841
- await entityManagerService.publish(entry, uid);
842
- } else {
843
- await entityManagerService.unpublish(entry, uid);
844
- }
845
- } catch (error) {
846
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
847
- } else {
848
- throw error;
849
- }
850
- }
851
- }
852
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
853
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
854
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
855
- const entriesToPublish = await strapi2.entityService.findMany(
856
- contentTypeUid,
857
- {
858
- filters: {
859
- id: {
860
- $in: entriestoPublishIds
861
- }
862
- },
863
- populate
864
- }
865
- );
866
- const entriesToUnpublish = await strapi2.entityService.findMany(
867
- contentTypeUid,
868
- {
869
- filters: {
870
- id: {
871
- $in: entriesToUnpublishIds
872
- }
873
- },
874
- populate
875
- }
876
- );
877
- if (entriesToPublish.length > 0) {
878
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
970
+ try {
971
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
972
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
973
+ releaseId
974
+ );
975
+ await strapi2.db.transaction(async () => {
976
+ for (const { uid, action, id } of singleTypeActions) {
977
+ await publishSingleTypeAction(uid, action, id);
879
978
  }
880
- if (entriesToUnpublish.length > 0) {
881
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
979
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
980
+ const uid = contentTypeUid;
981
+ await publishCollectionTypeAction(
982
+ uid,
983
+ collectionTypeActions[uid].entriesToPublishIds,
984
+ collectionTypeActions[uid].entriesToUnpublishIds
985
+ );
882
986
  }
883
- }
884
- });
885
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
886
- data: {
887
- /*
888
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
889
- */
890
- // @ts-expect-error see above
891
- releasedAt: /* @__PURE__ */ new Date()
892
- },
893
- populate: {
894
- actions: {
895
- // @ts-expect-error is not expecting count but it is working
896
- count: true
987
+ });
988
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
989
+ where: {
990
+ id: releaseId
991
+ },
992
+ data: {
993
+ status: "done",
994
+ releasedAt: /* @__PURE__ */ new Date()
897
995
  }
898
- }
899
- });
900
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
996
+ });
901
997
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
902
998
  isPublished: true,
903
- release: release2
999
+ release: release22
904
1000
  });
905
- }
906
- strapi2.telemetry.send("didPublishContentRelease");
907
- return release2;
908
- } catch (error) {
909
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1001
+ strapi2.telemetry.send("didPublishContentRelease");
1002
+ return { release: release22, error: null };
1003
+ } catch (error2) {
910
1004
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
911
1005
  isPublished: false,
912
- error
1006
+ error: error2
913
1007
  });
914
- }
915
- strapi2.db.query(RELEASE_MODEL_UID).update({
916
- where: { id: releaseId },
917
- data: {
1008
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
918
1009
  status: "failed"
919
- }
920
- });
1010
+ }).transacting(trx).execute();
1011
+ return {
1012
+ release: null,
1013
+ error: error2
1014
+ };
1015
+ }
1016
+ });
1017
+ if (error) {
921
1018
  throw error;
922
1019
  }
1020
+ return release2;
923
1021
  },
924
1022
  async updateAction(actionId, releaseId, update) {
925
1023
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1006,6 +1104,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1006
1104
  }
1007
1105
  };
1008
1106
  };
1107
+ class AlreadyOnReleaseError extends errors.ApplicationError {
1108
+ constructor(message) {
1109
+ super(message);
1110
+ this.name = "AlreadyOnReleaseError";
1111
+ }
1112
+ }
1009
1113
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1010
1114
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1011
1115
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -1018,7 +1122,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1018
1122
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1019
1123
  );
1020
1124
  if (isEntryInRelease) {
1021
- throw new errors.ValidationError(
1125
+ throw new AlreadyOnReleaseError(
1022
1126
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1023
1127
  );
1024
1128
  }
@@ -1028,9 +1132,17 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1028
1132
  if (!contentType) {
1029
1133
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1030
1134
  }
1135
+ if (!contentType.options?.draftAndPublish) {
1136
+ throw new errors.ValidationError(
1137
+ `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1138
+ );
1139
+ }
1031
1140
  },
1032
1141
  async validatePendingReleasesLimit() {
1033
- const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1142
+ const maximumPendingReleases = (
1143
+ // @ts-expect-error - options is not typed into features
1144
+ EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
1145
+ );
1034
1146
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1035
1147
  filters: {
1036
1148
  releasedAt: {
@@ -1118,7 +1230,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1118
1230
  const services = {
1119
1231
  release: createReleaseService,
1120
1232
  "release-validation": createReleaseValidationService,
1121
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1233
+ scheduling: createSchedulingService
1122
1234
  };
1123
1235
  const RELEASE_SCHEMA = yup.object().shape({
1124
1236
  name: yup.string().trim().required(),
@@ -1205,6 +1317,33 @@ const releaseController = {
1205
1317
  };
1206
1318
  ctx.body = { data };
1207
1319
  },
1320
+ async mapEntriesToReleases(ctx) {
1321
+ const { contentTypeUid, entriesIds } = ctx.query;
1322
+ if (!contentTypeUid || !entriesIds) {
1323
+ throw new errors.ValidationError("Missing required query parameters");
1324
+ }
1325
+ const releaseService = getService("release", { strapi });
1326
+ const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1327
+ contentTypeUid,
1328
+ entriesIds
1329
+ );
1330
+ const mappedEntriesInReleases = releasesWithActions.reduce(
1331
+ (acc, release2) => {
1332
+ release2.actions.forEach((action) => {
1333
+ if (!acc[action.entry.id]) {
1334
+ acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1335
+ } else {
1336
+ acc[action.entry.id].push({ id: release2.id, name: release2.name });
1337
+ }
1338
+ });
1339
+ return acc;
1340
+ },
1341
+ {}
1342
+ );
1343
+ ctx.body = {
1344
+ data: mappedEntriesInReleases
1345
+ };
1346
+ },
1208
1347
  async create(ctx) {
1209
1348
  const user = ctx.state.user;
1210
1349
  const releaseArgs = ctx.request.body;
@@ -1294,6 +1433,38 @@ const releaseActionController = {
1294
1433
  data: releaseAction2
1295
1434
  };
1296
1435
  },
1436
+ async createMany(ctx) {
1437
+ const releaseId = ctx.params.releaseId;
1438
+ const releaseActionsArgs = ctx.request.body;
1439
+ await Promise.all(
1440
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1441
+ );
1442
+ const releaseService = getService("release", { strapi });
1443
+ const releaseActions = await strapi.db.transaction(async () => {
1444
+ const releaseActions2 = await Promise.all(
1445
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1446
+ try {
1447
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1448
+ return action;
1449
+ } catch (error) {
1450
+ if (error instanceof AlreadyOnReleaseError) {
1451
+ return null;
1452
+ }
1453
+ throw error;
1454
+ }
1455
+ })
1456
+ );
1457
+ return releaseActions2;
1458
+ });
1459
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1460
+ ctx.body = {
1461
+ data: newReleaseActions,
1462
+ meta: {
1463
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1464
+ totalEntries: releaseActions.length
1465
+ }
1466
+ };
1467
+ },
1297
1468
  async findMany(ctx) {
1298
1469
  const releaseId = ctx.params.releaseId;
1299
1470
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1317,7 +1488,7 @@ const releaseActionController = {
1317
1488
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1318
1489
  return acc;
1319
1490
  }, {});
1320
- const sanitizedResults = await async.map(results, async (action) => ({
1491
+ const sanitizedResults = await mapAsync(results, async (action) => ({
1321
1492
  ...action,
1322
1493
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1323
1494
  }));
@@ -1362,6 +1533,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
1362
1533
  const release = {
1363
1534
  type: "admin",
1364
1535
  routes: [
1536
+ {
1537
+ method: "GET",
1538
+ path: "/mapEntriesToReleases",
1539
+ handler: "release.mapEntriesToReleases",
1540
+ config: {
1541
+ policies: [
1542
+ "admin::isAuthenticatedAdmin",
1543
+ {
1544
+ name: "admin::hasPermissions",
1545
+ config: {
1546
+ actions: ["plugin::content-releases.read"]
1547
+ }
1548
+ }
1549
+ ]
1550
+ }
1551
+ },
1365
1552
  {
1366
1553
  method: "POST",
1367
1554
  path: "/",
@@ -1479,6 +1666,22 @@ const releaseAction = {
1479
1666
  ]
1480
1667
  }
1481
1668
  },
1669
+ {
1670
+ method: "POST",
1671
+ path: "/:releaseId/actions/bulk",
1672
+ handler: "release-action.createMany",
1673
+ config: {
1674
+ policies: [
1675
+ "admin::isAuthenticatedAdmin",
1676
+ {
1677
+ name: "admin::hasPermissions",
1678
+ config: {
1679
+ actions: ["plugin::content-releases.create-action"]
1680
+ }
1681
+ }
1682
+ ]
1683
+ }
1684
+ },
1482
1685
  {
1483
1686
  method: "GET",
1484
1687
  path: "/:releaseId/actions",
@@ -1533,8 +1736,9 @@ const routes = {
1533
1736
  release,
1534
1737
  "release-action": releaseAction
1535
1738
  };
1739
+ const { features } = require("@strapi/strapi/dist/utils/ee");
1536
1740
  const getPlugin = () => {
1537
- if (strapi.ee.features.isEnabled("cms-content-releases")) {
1741
+ if (features.isEnabled("cms-content-releases")) {
1538
1742
  return {
1539
1743
  register,
1540
1744
  bootstrap,
@@ -1546,6 +1750,9 @@ const getPlugin = () => {
1546
1750
  };
1547
1751
  }
1548
1752
  return {
1753
+ // Always return register, it handles its own feature check
1754
+ register,
1755
+ // Always return contentTypes to avoid losing data when the feature is disabled
1549
1756
  contentTypes
1550
1757
  };
1551
1758
  };