@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
@@ -1,7 +1,8 @@
1
- import { async, setCreatorFields, convertQueryParams, 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";
@@ -59,10 +60,7 @@ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
59
60
  const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
60
61
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
61
62
  const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
62
- const entry = await strapi2.db.query(contentTypeUid).findOne({
63
- where: { id: entryId },
64
- populate
65
- });
63
+ const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
66
64
  return entry;
67
65
  };
68
66
  const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
@@ -79,10 +77,28 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
79
77
  return false;
80
78
  }
81
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
+ }
82
98
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
83
99
  const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
84
100
  if (deletedContentTypes.length) {
85
- await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
101
+ await mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
86
102
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
87
103
  });
88
104
  }
@@ -101,7 +117,7 @@ async function migrateIsValidAndStatusReleases() {
101
117
  }
102
118
  }
103
119
  });
104
- async.map(releasesWithoutStatus, async (release2) => {
120
+ mapAsync(releasesWithoutStatus, async (release2) => {
105
121
  const actions = release2.actions;
106
122
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
107
123
  for (const action of notValidatedActions) {
@@ -132,7 +148,7 @@ async function migrateIsValidAndStatusReleases() {
132
148
  }
133
149
  }
134
150
  });
135
- async.map(publishedReleases, async (release2) => {
151
+ mapAsync(publishedReleases, async (release2) => {
136
152
  return strapi.db.query(RELEASE_MODEL_UID).update({
137
153
  where: {
138
154
  id: release2.id
@@ -145,9 +161,11 @@ async function migrateIsValidAndStatusReleases() {
145
161
  }
146
162
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
147
163
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
148
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
164
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
165
+ (uid) => oldContentTypes[uid]?.options?.draftAndPublish
166
+ );
149
167
  const releasesAffected = /* @__PURE__ */ new Set();
150
- async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
168
+ mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
151
169
  const oldContentType = oldContentTypes[contentTypeUID];
152
170
  const contentType = contentTypes2[contentTypeUID];
153
171
  if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
@@ -160,8 +178,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
160
178
  release: true
161
179
  }
162
180
  });
163
- await async.map(actions, async (action) => {
164
- if (action.entry) {
181
+ await mapAsync(actions, async (action) => {
182
+ if (action.entry && action.release) {
165
183
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
166
184
  strapi
167
185
  });
@@ -183,21 +201,71 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
183
201
  });
184
202
  }
185
203
  }).then(() => {
186
- async.map(releasesAffected, async (releaseId) => {
204
+ mapAsync(releasesAffected, async (releaseId) => {
187
205
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
188
206
  });
189
207
  });
190
208
  }
191
209
  }
210
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
211
+ if (!oldContentTypes) {
212
+ return;
213
+ }
214
+ for (const uid in contentTypes2) {
215
+ if (!oldContentTypes[uid]) {
216
+ continue;
217
+ }
218
+ const oldContentType = oldContentTypes[uid];
219
+ const contentType = contentTypes2[uid];
220
+ const i18nPlugin = strapi.plugin("i18n");
221
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
222
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
223
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
224
+ locale: null
225
+ }).where({ contentType: uid }).execute();
226
+ }
227
+ }
228
+ }
229
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
230
+ if (!oldContentTypes) {
231
+ return;
232
+ }
233
+ for (const uid in contentTypes2) {
234
+ if (!oldContentTypes[uid]) {
235
+ continue;
236
+ }
237
+ const oldContentType = oldContentTypes[uid];
238
+ const contentType = contentTypes2[uid];
239
+ const i18nPlugin = strapi.plugin("i18n");
240
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
241
+ const { getDefaultLocale } = i18nPlugin.service("locales");
242
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
243
+ const defaultLocale = await getDefaultLocale();
244
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
245
+ locale: defaultLocale
246
+ }).where({ contentType: uid }).execute();
247
+ }
248
+ }
249
+ }
250
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
192
251
  const register = async ({ strapi: strapi2 }) => {
193
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
252
+ if (features$2.isEnabled("cms-content-releases")) {
194
253
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
195
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
254
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
255
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
256
+ }
257
+ if (strapi2.plugin("graphql")) {
258
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
259
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
260
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
196
261
  }
197
262
  };
263
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
198
264
  const bootstrap = async ({ strapi: strapi2 }) => {
199
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
200
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
265
+ if (features$1.isEnabled("cms-content-releases")) {
266
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
267
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
268
+ );
201
269
  strapi2.db.lifecycles.subscribe({
202
270
  models: contentTypesWithDraftAndPublish,
203
271
  async afterDelete(event) {
@@ -233,7 +301,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
233
301
  */
234
302
  async beforeDeleteMany(event) {
235
303
  const { model, params } = event;
236
- if (model.kind === "collectionType") {
304
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
237
305
  const { where } = params;
238
306
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
239
307
  event.state.entriesToDelete = entriesToDelete;
@@ -315,27 +383,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
315
383
  }
316
384
  }
317
385
  });
318
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
319
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
320
- strapi2.log.error(
321
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
322
- );
323
- throw err;
324
- });
325
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
326
- strapi2.webhookStore.addAllowedEvent(key, value);
327
- });
328
- }
386
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
387
+ strapi2.log.error(
388
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
389
+ );
390
+ throw err;
391
+ });
392
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
393
+ strapi2.webhookStore.addAllowedEvent(key, value);
394
+ });
329
395
  }
330
396
  };
331
397
  const destroy = async ({ strapi: strapi2 }) => {
332
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
333
- const scheduledJobs = getService("scheduling", {
334
- strapi: strapi2
335
- }).getAll();
336
- for (const [, job] of scheduledJobs) {
337
- job.cancel();
338
- }
398
+ const scheduledJobs = getService("scheduling", {
399
+ strapi: strapi2
400
+ }).getAll();
401
+ for (const [, job] of scheduledJobs) {
402
+ job.cancel();
339
403
  }
340
404
  };
341
405
  const schema$1 = {
@@ -460,6 +524,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
460
524
  release: release2
461
525
  });
462
526
  };
527
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
528
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
529
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
530
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
531
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
532
+ try {
533
+ if (actionType === "publish") {
534
+ await entityManagerService.publish(entry, uid);
535
+ } else {
536
+ await entityManagerService.unpublish(entry, uid);
537
+ }
538
+ } catch (error) {
539
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
540
+ ;
541
+ else {
542
+ throw error;
543
+ }
544
+ }
545
+ };
546
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
547
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
548
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
549
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
550
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
551
+ filters: {
552
+ id: {
553
+ $in: entriesToPublishIds
554
+ }
555
+ },
556
+ populate
557
+ });
558
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
559
+ filters: {
560
+ id: {
561
+ $in: entriestoUnpublishIds
562
+ }
563
+ },
564
+ populate
565
+ });
566
+ if (entriesToPublish.length > 0) {
567
+ await entityManagerService.publishMany(entriesToPublish, uid);
568
+ }
569
+ if (entriesToUnpublish.length > 0) {
570
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
571
+ }
572
+ };
573
+ const getFormattedActions = async (releaseId) => {
574
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
575
+ where: {
576
+ release: {
577
+ id: releaseId
578
+ }
579
+ },
580
+ populate: {
581
+ entry: {
582
+ fields: ["id"]
583
+ }
584
+ }
585
+ });
586
+ if (actions.length === 0) {
587
+ throw new errors.ValidationError("No entries to publish");
588
+ }
589
+ const collectionTypeActions = {};
590
+ const singleTypeActions = [];
591
+ for (const action of actions) {
592
+ const contentTypeUid = action.contentType;
593
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
594
+ if (!collectionTypeActions[contentTypeUid]) {
595
+ collectionTypeActions[contentTypeUid] = {
596
+ entriesToPublishIds: [],
597
+ entriesToUnpublishIds: []
598
+ };
599
+ }
600
+ if (action.type === "publish") {
601
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
602
+ } else {
603
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
604
+ }
605
+ } else {
606
+ singleTypeActions.push({
607
+ uid: contentTypeUid,
608
+ action: action.type,
609
+ id: action.entry.id
610
+ });
611
+ }
612
+ }
613
+ return { collectionTypeActions, singleTypeActions };
614
+ };
463
615
  return {
464
616
  async create(releaseData, { user }) {
465
617
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
@@ -473,13 +625,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
473
625
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
474
626
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
475
627
  ]);
476
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
628
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
477
629
  data: {
478
630
  ...releaseWithCreatorFields,
479
631
  status: "empty"
480
632
  }
481
633
  });
482
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
634
+ if (releaseWithCreatorFields.scheduledAt) {
483
635
  const schedulingService = getService("scheduling", { strapi: strapi2 });
484
636
  await schedulingService.set(release2.id, release2.scheduledAt);
485
637
  }
@@ -487,19 +639,17 @@ const createReleaseService = ({ strapi: strapi2 }) => {
487
639
  return release2;
488
640
  },
489
641
  async findOne(id, query = {}) {
490
- const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query);
491
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
492
- ...dbQuery,
493
- where: { id }
642
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
643
+ ...query
494
644
  });
495
645
  return release2;
496
646
  },
497
647
  findPage(query) {
498
- const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
499
- return strapi2.db.query(RELEASE_MODEL_UID).findPage({
500
- ...dbQuery,
648
+ return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
649
+ ...query,
501
650
  populate: {
502
651
  actions: {
652
+ // @ts-expect-error Ignore missing properties
503
653
  count: true
504
654
  }
505
655
  }
@@ -591,24 +741,26 @@ const createReleaseService = ({ strapi: strapi2 }) => {
591
741
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
592
742
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
593
743
  ]);
594
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
744
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
595
745
  if (!release2) {
596
746
  throw new errors.NotFoundError(`No release found for id ${id}`);
597
747
  }
598
748
  if (release2.releasedAt) {
599
749
  throw new errors.ValidationError("Release already published");
600
750
  }
601
- const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
602
- where: { id },
751
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
752
+ /*
753
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
754
+ * is not compatible with the type we are passing here: UpdateRelease.Request['body']
755
+ */
756
+ // @ts-expect-error see above
603
757
  data: releaseWithCreatorFields
604
758
  });
605
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
606
- const schedulingService = getService("scheduling", { strapi: strapi2 });
607
- if (releaseData.scheduledAt) {
608
- await schedulingService.set(id, releaseData.scheduledAt);
609
- } else if (release2.scheduledAt) {
610
- schedulingService.cancel(id);
611
- }
759
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
760
+ if (releaseData.scheduledAt) {
761
+ await schedulingService.set(id, releaseData.scheduledAt);
762
+ } else if (release2.scheduledAt) {
763
+ schedulingService.cancel(id);
612
764
  }
613
765
  this.updateReleaseStatus(id);
614
766
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -622,7 +774,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
622
774
  validateEntryContentType(action.entry.contentType),
623
775
  validateUniqueEntry(releaseId, action)
624
776
  ]);
625
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
777
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
626
778
  if (!release2) {
627
779
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
628
780
  }
@@ -632,7 +784,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
632
784
  const { entry, type } = action;
633
785
  const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
634
786
  const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
635
- const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
787
+ const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
636
788
  data: {
637
789
  type,
638
790
  contentType: entry.contentType,
@@ -645,41 +797,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
645
797
  },
646
798
  release: releaseId
647
799
  },
648
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
800
+ populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
649
801
  });
650
802
  this.updateReleaseStatus(releaseId);
651
803
  return releaseAction2;
652
804
  },
653
805
  async findActions(releaseId, query) {
654
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
655
- where: { id: releaseId },
656
- select: ["id"]
806
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
807
+ fields: ["id"]
657
808
  });
658
809
  if (!release2) {
659
810
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
660
811
  }
661
- const dbQuery = convertQueryParams.transformParamsToQuery(
662
- RELEASE_ACTION_MODEL_UID,
663
- query ?? {}
664
- );
665
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
666
- ...dbQuery,
812
+ return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
813
+ ...query,
667
814
  populate: {
668
815
  entry: {
669
816
  populate: "*"
670
817
  }
671
818
  },
672
- where: {
819
+ filters: {
673
820
  release: releaseId
674
821
  }
675
822
  });
676
823
  },
677
824
  async countActions(query) {
678
- const dbQuery = convertQueryParams.transformParamsToQuery(
679
- RELEASE_ACTION_MODEL_UID,
680
- query ?? {}
681
- );
682
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
825
+ return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
683
826
  },
684
827
  async groupActions(actions, groupBy) {
685
828
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -760,11 +903,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
760
903
  return componentsMap;
761
904
  },
762
905
  async delete(releaseId) {
763
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
764
- where: { id: releaseId },
906
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
765
907
  populate: {
766
908
  actions: {
767
- select: ["id"]
909
+ fields: ["id"]
768
910
  }
769
911
  }
770
912
  });
@@ -782,13 +924,9 @@ const createReleaseService = ({ strapi: strapi2 }) => {
782
924
  }
783
925
  }
784
926
  });
785
- await strapi2.db.query(RELEASE_MODEL_UID).delete({
786
- where: {
787
- id: releaseId
788
- }
789
- });
927
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
790
928
  });
791
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
929
+ if (release2.scheduledAt) {
792
930
  const schedulingService = getService("scheduling", { strapi: strapi2 });
793
931
  await schedulingService.cancel(release2.id);
794
932
  }
@@ -796,132 +934,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
796
934
  return release2;
797
935
  },
798
936
  async publish(releaseId) {
799
- try {
800
- const releaseWithPopulatedActionEntries = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
801
- where: { id: releaseId },
802
- populate: {
803
- actions: {
804
- populate: {
805
- entry: {
806
- select: ["id"]
807
- }
808
- }
809
- }
810
- }
811
- });
812
- if (!releaseWithPopulatedActionEntries) {
937
+ const {
938
+ release: release2,
939
+ error
940
+ } = await strapi2.db.transaction(async ({ trx }) => {
941
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
942
+ if (!lockedRelease) {
813
943
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
814
944
  }
815
- if (releaseWithPopulatedActionEntries.releasedAt) {
945
+ if (lockedRelease.releasedAt) {
816
946
  throw new errors.ValidationError("Release already published");
817
947
  }
818
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
819
- throw new errors.ValidationError("No entries to publish");
820
- }
821
- const collectionTypeActions = {};
822
- const singleTypeActions = [];
823
- for (const action of releaseWithPopulatedActionEntries.actions) {
824
- const contentTypeUid = action.contentType;
825
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
826
- if (!collectionTypeActions[contentTypeUid]) {
827
- collectionTypeActions[contentTypeUid] = {
828
- entriestoPublishIds: [],
829
- entriesToUnpublishIds: []
830
- };
831
- }
832
- if (action.type === "publish") {
833
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
834
- } else {
835
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
836
- }
837
- } else {
838
- singleTypeActions.push({
839
- uid: contentTypeUid,
840
- action: action.type,
841
- id: action.entry.id
842
- });
843
- }
948
+ if (lockedRelease.status === "failed") {
949
+ throw new errors.ValidationError("Release failed to publish");
844
950
  }
845
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
846
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
847
- await strapi2.db.transaction(async () => {
848
- for (const { uid, action, id } of singleTypeActions) {
849
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
850
- const entry = await strapi2.db.query(uid).findOne({ where: { id }, populate });
851
- try {
852
- if (action === "publish") {
853
- await entityManagerService.publish(entry, uid);
854
- } else {
855
- await entityManagerService.unpublish(entry, uid);
856
- }
857
- } catch (error) {
858
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
859
- } else {
860
- throw error;
861
- }
862
- }
863
- }
864
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
865
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
866
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
867
- const entriesToPublish = await strapi2.db.query(contentTypeUid).findMany({
868
- where: {
869
- id: {
870
- $in: entriestoPublishIds
871
- }
872
- },
873
- populate
874
- });
875
- const entriesToUnpublish = await strapi2.db.query(contentTypeUid).findMany({
876
- where: {
877
- id: {
878
- $in: entriesToUnpublishIds
879
- }
880
- },
881
- populate
882
- });
883
- if (entriesToPublish.length > 0) {
884
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
951
+ try {
952
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
953
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
954
+ releaseId
955
+ );
956
+ await strapi2.db.transaction(async () => {
957
+ for (const { uid, action, id } of singleTypeActions) {
958
+ await publishSingleTypeAction(uid, action, id);
885
959
  }
886
- if (entriesToUnpublish.length > 0) {
887
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
960
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
961
+ const uid = contentTypeUid;
962
+ await publishCollectionTypeAction(
963
+ uid,
964
+ collectionTypeActions[uid].entriesToPublishIds,
965
+ collectionTypeActions[uid].entriesToUnpublishIds
966
+ );
888
967
  }
889
- }
890
- });
891
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).update({
892
- where: { id: releaseId },
893
- data: {
894
- releasedAt: /* @__PURE__ */ new Date()
895
- },
896
- populate: {
897
- actions: {
898
- count: true
968
+ });
969
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
970
+ where: {
971
+ id: releaseId
972
+ },
973
+ data: {
974
+ status: "done",
975
+ releasedAt: /* @__PURE__ */ new Date()
899
976
  }
900
- }
901
- });
902
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
977
+ });
903
978
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
904
979
  isPublished: true,
905
- release: release2
980
+ release: release22
906
981
  });
907
- }
908
- strapi2.telemetry.send("didPublishContentRelease");
909
- return release2;
910
- } catch (error) {
911
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
982
+ strapi2.telemetry.send("didPublishContentRelease");
983
+ return { release: release22, error: null };
984
+ } catch (error2) {
912
985
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
913
986
  isPublished: false,
914
- error
987
+ error: error2
915
988
  });
916
- }
917
- strapi2.db.query(RELEASE_MODEL_UID).update({
918
- where: { id: releaseId },
919
- data: {
989
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
920
990
  status: "failed"
921
- }
922
- });
991
+ }).transacting(trx).execute();
992
+ return {
993
+ release: null,
994
+ error: error2
995
+ };
996
+ }
997
+ });
998
+ if (error) {
923
999
  throw error;
924
1000
  }
1001
+ return release2;
925
1002
  },
926
1003
  async updateAction(actionId, releaseId, update) {
927
1004
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1008,13 +1085,16 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1008
1085
  }
1009
1086
  };
1010
1087
  };
1088
+ class AlreadyOnReleaseError extends errors.ApplicationError {
1089
+ constructor(message) {
1090
+ super(message);
1091
+ this.name = "AlreadyOnReleaseError";
1092
+ }
1093
+ }
1011
1094
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1012
1095
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1013
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1014
- where: {
1015
- id: releaseId
1016
- },
1017
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1096
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1097
+ populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1018
1098
  });
1019
1099
  if (!release2) {
1020
1100
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -1023,7 +1103,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1023
1103
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1024
1104
  );
1025
1105
  if (isEntryInRelease) {
1026
- throw new errors.ValidationError(
1106
+ throw new AlreadyOnReleaseError(
1027
1107
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1028
1108
  );
1029
1109
  }
@@ -1033,9 +1113,17 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1033
1113
  if (!contentType) {
1034
1114
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1035
1115
  }
1116
+ if (!contentType.options?.draftAndPublish) {
1117
+ throw new errors.ValidationError(
1118
+ `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1119
+ );
1120
+ }
1036
1121
  },
1037
1122
  async validatePendingReleasesLimit() {
1038
- const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1123
+ const maximumPendingReleases = (
1124
+ // @ts-expect-error - options is not typed into features
1125
+ EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
1126
+ );
1039
1127
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1040
1128
  filters: {
1041
1129
  releasedAt: {
@@ -1048,8 +1136,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1048
1136
  }
1049
1137
  },
1050
1138
  async validateUniqueNameForPendingRelease(name, id) {
1051
- const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1052
- where: {
1139
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1140
+ filters: {
1053
1141
  releasedAt: {
1054
1142
  $null: true
1055
1143
  },
@@ -1123,7 +1211,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1123
1211
  const services = {
1124
1212
  release: createReleaseService,
1125
1213
  "release-validation": createReleaseValidationService,
1126
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1214
+ scheduling: createSchedulingService
1127
1215
  };
1128
1216
  const RELEASE_SCHEMA = yup.object().shape({
1129
1217
  name: yup.string().trim().required(),
@@ -1176,7 +1264,7 @@ const releaseController = {
1176
1264
  }
1177
1265
  };
1178
1266
  });
1179
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1267
+ const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1180
1268
  where: {
1181
1269
  releasedAt: null
1182
1270
  }
@@ -1299,6 +1387,38 @@ const releaseActionController = {
1299
1387
  data: releaseAction2
1300
1388
  };
1301
1389
  },
1390
+ async createMany(ctx) {
1391
+ const releaseId = ctx.params.releaseId;
1392
+ const releaseActionsArgs = ctx.request.body;
1393
+ await Promise.all(
1394
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1395
+ );
1396
+ const releaseService = getService("release", { strapi });
1397
+ const releaseActions = await strapi.db.transaction(async () => {
1398
+ const releaseActions2 = await Promise.all(
1399
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1400
+ try {
1401
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1402
+ return action;
1403
+ } catch (error) {
1404
+ if (error instanceof AlreadyOnReleaseError) {
1405
+ return null;
1406
+ }
1407
+ throw error;
1408
+ }
1409
+ })
1410
+ );
1411
+ return releaseActions2;
1412
+ });
1413
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1414
+ ctx.body = {
1415
+ data: newReleaseActions,
1416
+ meta: {
1417
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1418
+ totalEntries: releaseActions.length
1419
+ }
1420
+ };
1421
+ },
1302
1422
  async findMany(ctx) {
1303
1423
  const releaseId = ctx.params.releaseId;
1304
1424
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1322,7 +1442,7 @@ const releaseActionController = {
1322
1442
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1323
1443
  return acc;
1324
1444
  }, {});
1325
- const sanitizedResults = await async.map(results, async (action) => ({
1445
+ const sanitizedResults = await mapAsync(results, async (action) => ({
1326
1446
  ...action,
1327
1447
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1328
1448
  }));
@@ -1484,6 +1604,22 @@ const releaseAction = {
1484
1604
  ]
1485
1605
  }
1486
1606
  },
1607
+ {
1608
+ method: "POST",
1609
+ path: "/:releaseId/actions/bulk",
1610
+ handler: "release-action.createMany",
1611
+ config: {
1612
+ policies: [
1613
+ "admin::isAuthenticatedAdmin",
1614
+ {
1615
+ name: "admin::hasPermissions",
1616
+ config: {
1617
+ actions: ["plugin::content-releases.create-action"]
1618
+ }
1619
+ }
1620
+ ]
1621
+ }
1622
+ },
1487
1623
  {
1488
1624
  method: "GET",
1489
1625
  path: "/:releaseId/actions",
@@ -1538,8 +1674,9 @@ const routes = {
1538
1674
  release,
1539
1675
  "release-action": releaseAction
1540
1676
  };
1677
+ const { features } = require("@strapi/strapi/dist/utils/ee");
1541
1678
  const getPlugin = () => {
1542
- if (strapi.ee.features.isEnabled("cms-content-releases")) {
1679
+ if (features.isEnabled("cms-content-releases")) {
1543
1680
  return {
1544
1681
  register,
1545
1682
  bootstrap,
@@ -1551,6 +1688,9 @@ const getPlugin = () => {
1551
1688
  };
1552
1689
  }
1553
1690
  return {
1691
+ // Always return register, it handles its own feature check
1692
+ register,
1693
+ // Always return contentTypes to avoid losing data when the feature is disabled
1554
1694
  contentTypes
1555
1695
  };
1556
1696
  };