@strapi/content-releases 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c → 0.0.0-next.fc231041206e6f3999b094160cfa05db2892ad54
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.
- package/dist/_chunks/{App-xAkiD42p.mjs → App-HVXzE3i3.mjs} +644 -623
- package/dist/_chunks/App-HVXzE3i3.mjs.map +1 -0
- package/dist/_chunks/{App-OK4Xac-O.js → App-l62gIUTX.js} +635 -614
- package/dist/_chunks/App-l62gIUTX.js.map +1 -0
- package/dist/_chunks/{en-veqvqeEr.mjs → en-RdapH-9X.mjs} +4 -4
- package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
- package/dist/_chunks/{en-r0otWaln.js → en-faJDuv3q.js} +4 -4
- package/dist/_chunks/en-faJDuv3q.js.map +1 -0
- package/dist/_chunks/{index-JvA2_26n.js → index-ML_b3php.js} +33 -14
- package/dist/_chunks/index-ML_b3php.js.map +1 -0
- package/dist/_chunks/{index-exoiSU3V.mjs → index-Ys87ROOe.mjs} +43 -24
- package/dist/_chunks/index-Ys87ROOe.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +554 -170
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +553 -170
- package/dist/server/index.mjs.map +1 -1
- package/package.json +11 -11
- package/dist/_chunks/App-OK4Xac-O.js.map +0 -1
- package/dist/_chunks/App-xAkiD42p.mjs.map +0 -1
- package/dist/_chunks/en-r0otWaln.js.map +0 -1
- package/dist/_chunks/en-veqvqeEr.mjs.map +0 -1
- package/dist/_chunks/index-JvA2_26n.js.map +0 -1
- package/dist/_chunks/index-exoiSU3V.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
2
3
|
import { difference, keys } from "lodash";
|
|
3
4
|
import _ from "lodash/fp";
|
|
4
5
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
@@ -53,6 +54,29 @@ const ACTIONS = [
|
|
|
53
54
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
54
55
|
RELEASES_PUBLISH: "releases.publish"
|
|
55
56
|
};
|
|
57
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
58
|
+
return strapi2.plugin("content-releases").service(name);
|
|
59
|
+
};
|
|
60
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
61
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
62
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
63
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
64
|
+
return entry;
|
|
65
|
+
};
|
|
66
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
67
|
+
try {
|
|
68
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
69
|
+
strapi2.getModel(contentTypeUid),
|
|
70
|
+
entry,
|
|
71
|
+
void 0,
|
|
72
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
73
|
+
entry
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
56
80
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
57
81
|
oldContentTypes,
|
|
58
82
|
contentTypes: contentTypes2
|
|
@@ -79,31 +103,196 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
|
|
|
79
103
|
});
|
|
80
104
|
}
|
|
81
105
|
}
|
|
106
|
+
async function migrateIsValidAndStatusReleases() {
|
|
107
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
108
|
+
where: {
|
|
109
|
+
status: null,
|
|
110
|
+
releasedAt: null
|
|
111
|
+
},
|
|
112
|
+
populate: {
|
|
113
|
+
actions: {
|
|
114
|
+
populate: {
|
|
115
|
+
entry: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
mapAsync(releasesWithoutStatus, async (release2) => {
|
|
121
|
+
const actions = release2.actions;
|
|
122
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
123
|
+
for (const action of notValidatedActions) {
|
|
124
|
+
if (action.entry) {
|
|
125
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
126
|
+
strapi
|
|
127
|
+
});
|
|
128
|
+
if (populatedEntry) {
|
|
129
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
130
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
131
|
+
where: {
|
|
132
|
+
id: action.id
|
|
133
|
+
},
|
|
134
|
+
data: {
|
|
135
|
+
isEntryValid
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
142
|
+
});
|
|
143
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
144
|
+
where: {
|
|
145
|
+
status: null,
|
|
146
|
+
releasedAt: {
|
|
147
|
+
$notNull: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
mapAsync(publishedReleases, async (release2) => {
|
|
152
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
153
|
+
where: {
|
|
154
|
+
id: release2.id
|
|
155
|
+
},
|
|
156
|
+
data: {
|
|
157
|
+
status: "done"
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
163
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
164
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
165
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
166
|
+
);
|
|
167
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
168
|
+
mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
169
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
170
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
171
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
172
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
173
|
+
where: {
|
|
174
|
+
contentType: contentTypeUID
|
|
175
|
+
},
|
|
176
|
+
populate: {
|
|
177
|
+
entry: true,
|
|
178
|
+
release: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await mapAsync(actions, async (action) => {
|
|
182
|
+
if (action.entry && action.release) {
|
|
183
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
184
|
+
strapi
|
|
185
|
+
});
|
|
186
|
+
if (populatedEntry) {
|
|
187
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
188
|
+
strapi
|
|
189
|
+
});
|
|
190
|
+
releasesAffected.add(action.release.id);
|
|
191
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
192
|
+
where: {
|
|
193
|
+
id: action.id
|
|
194
|
+
},
|
|
195
|
+
data: {
|
|
196
|
+
isEntryValid
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}).then(() => {
|
|
204
|
+
mapAsync(releasesAffected, async (releaseId) => {
|
|
205
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
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
|
+
}
|
|
82
250
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
83
251
|
const register = async ({ strapi: strapi2 }) => {
|
|
84
252
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
85
253
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
86
|
-
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
87
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
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();
|
|
88
261
|
}
|
|
89
|
-
};
|
|
90
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
91
|
-
return strapi2.plugin("content-releases").service(name);
|
|
92
262
|
};
|
|
93
263
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
94
264
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
95
265
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
266
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
267
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
268
|
+
);
|
|
96
269
|
strapi2.db.lifecycles.subscribe({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
270
|
+
models: contentTypesWithDraftAndPublish,
|
|
271
|
+
async afterDelete(event) {
|
|
272
|
+
try {
|
|
273
|
+
const { model, result } = event;
|
|
274
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
275
|
+
const { id } = result;
|
|
276
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
277
|
+
where: {
|
|
278
|
+
actions: {
|
|
279
|
+
target_type: model.uid,
|
|
280
|
+
target_id: id
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
285
|
+
where: {
|
|
286
|
+
target_type: model.uid,
|
|
287
|
+
target_id: id
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
for (const release2 of releases) {
|
|
291
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
105
292
|
}
|
|
106
|
-
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
107
296
|
}
|
|
108
297
|
},
|
|
109
298
|
/**
|
|
@@ -123,41 +312,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
123
312
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
124
313
|
*/
|
|
125
314
|
async afterDeleteMany(event) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
315
|
+
try {
|
|
316
|
+
const { model, state } = event;
|
|
317
|
+
const entriesToDelete = state.entriesToDelete;
|
|
318
|
+
if (entriesToDelete) {
|
|
319
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
320
|
+
where: {
|
|
321
|
+
actions: {
|
|
322
|
+
target_type: model.uid,
|
|
323
|
+
target_id: {
|
|
324
|
+
$in: entriesToDelete.map(
|
|
325
|
+
(entry) => entry.id
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
134
329
|
}
|
|
330
|
+
});
|
|
331
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
332
|
+
where: {
|
|
333
|
+
target_type: model.uid,
|
|
334
|
+
target_id: {
|
|
335
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
for (const release2 of releases) {
|
|
340
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
135
341
|
}
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
345
|
+
error
|
|
136
346
|
});
|
|
137
347
|
}
|
|
348
|
+
},
|
|
349
|
+
async afterUpdate(event) {
|
|
350
|
+
try {
|
|
351
|
+
const { model, result } = event;
|
|
352
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
353
|
+
const isEntryValid = await getEntryValidStatus(
|
|
354
|
+
model.uid,
|
|
355
|
+
result,
|
|
356
|
+
{
|
|
357
|
+
strapi: strapi2
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
361
|
+
where: {
|
|
362
|
+
target_type: model.uid,
|
|
363
|
+
target_id: result.id
|
|
364
|
+
},
|
|
365
|
+
data: {
|
|
366
|
+
isEntryValid
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
370
|
+
where: {
|
|
371
|
+
actions: {
|
|
372
|
+
target_type: model.uid,
|
|
373
|
+
target_id: result.id
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
for (const release2 of releases) {
|
|
378
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
383
|
+
}
|
|
138
384
|
}
|
|
139
385
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
}
|
|
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
|
+
});
|
|
151
395
|
}
|
|
152
396
|
};
|
|
153
397
|
const destroy = async ({ strapi: strapi2 }) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
job.cancel();
|
|
160
|
-
}
|
|
398
|
+
const scheduledJobs = getService("scheduling", {
|
|
399
|
+
strapi: strapi2
|
|
400
|
+
}).getAll();
|
|
401
|
+
for (const [, job] of scheduledJobs) {
|
|
402
|
+
job.cancel();
|
|
161
403
|
}
|
|
162
404
|
};
|
|
163
405
|
const schema$1 = {
|
|
@@ -192,6 +434,11 @@ const schema$1 = {
|
|
|
192
434
|
timezone: {
|
|
193
435
|
type: "string"
|
|
194
436
|
},
|
|
437
|
+
status: {
|
|
438
|
+
type: "enumeration",
|
|
439
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
440
|
+
required: true
|
|
441
|
+
},
|
|
195
442
|
actions: {
|
|
196
443
|
type: "relation",
|
|
197
444
|
relation: "oneToMany",
|
|
@@ -244,6 +491,9 @@ const schema = {
|
|
|
244
491
|
relation: "manyToOne",
|
|
245
492
|
target: RELEASE_MODEL_UID,
|
|
246
493
|
inversedBy: "actions"
|
|
494
|
+
},
|
|
495
|
+
isEntryValid: {
|
|
496
|
+
type: "boolean"
|
|
247
497
|
}
|
|
248
498
|
}
|
|
249
499
|
};
|
|
@@ -274,6 +524,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
274
524
|
release: release2
|
|
275
525
|
});
|
|
276
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
|
+
};
|
|
277
615
|
return {
|
|
278
616
|
async create(releaseData, { user }) {
|
|
279
617
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
@@ -288,9 +626,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
288
626
|
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
289
627
|
]);
|
|
290
628
|
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
291
|
-
data:
|
|
629
|
+
data: {
|
|
630
|
+
...releaseWithCreatorFields,
|
|
631
|
+
status: "empty"
|
|
632
|
+
}
|
|
292
633
|
});
|
|
293
|
-
if (
|
|
634
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
294
635
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
295
636
|
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
296
637
|
}
|
|
@@ -415,14 +756,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
415
756
|
// @ts-expect-error see above
|
|
416
757
|
data: releaseWithCreatorFields
|
|
417
758
|
});
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
schedulingService.cancel(id);
|
|
424
|
-
}
|
|
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);
|
|
425
764
|
}
|
|
765
|
+
this.updateReleaseStatus(id);
|
|
426
766
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
427
767
|
return updatedRelease;
|
|
428
768
|
},
|
|
@@ -442,11 +782,14 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
442
782
|
throw new errors.ValidationError("Release already published");
|
|
443
783
|
}
|
|
444
784
|
const { entry, type } = action;
|
|
445
|
-
|
|
785
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
786
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
787
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
446
788
|
data: {
|
|
447
789
|
type,
|
|
448
790
|
contentType: entry.contentType,
|
|
449
791
|
locale: entry.locale,
|
|
792
|
+
isEntryValid,
|
|
450
793
|
entry: {
|
|
451
794
|
id: entry.id,
|
|
452
795
|
__type: entry.contentType,
|
|
@@ -456,6 +799,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
456
799
|
},
|
|
457
800
|
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
458
801
|
});
|
|
802
|
+
this.updateReleaseStatus(releaseId);
|
|
803
|
+
return releaseAction2;
|
|
459
804
|
},
|
|
460
805
|
async findActions(releaseId, query) {
|
|
461
806
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -581,7 +926,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
581
926
|
});
|
|
582
927
|
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
583
928
|
});
|
|
584
|
-
if (
|
|
929
|
+
if (release2.scheduledAt) {
|
|
585
930
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
586
931
|
await schedulingService.cancel(release2.id);
|
|
587
932
|
}
|
|
@@ -589,139 +934,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
589
934
|
return release2;
|
|
590
935
|
},
|
|
591
936
|
async publish(releaseId) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
actions: {
|
|
599
|
-
populate: {
|
|
600
|
-
entry: {
|
|
601
|
-
fields: ["id"]
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
);
|
|
608
|
-
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) {
|
|
609
943
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
610
944
|
}
|
|
611
|
-
if (
|
|
945
|
+
if (lockedRelease.releasedAt) {
|
|
612
946
|
throw new errors.ValidationError("Release already published");
|
|
613
947
|
}
|
|
614
|
-
if (
|
|
615
|
-
throw new errors.ValidationError("
|
|
616
|
-
}
|
|
617
|
-
const collectionTypeActions = {};
|
|
618
|
-
const singleTypeActions = [];
|
|
619
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
620
|
-
const contentTypeUid = action.contentType;
|
|
621
|
-
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
622
|
-
if (!collectionTypeActions[contentTypeUid]) {
|
|
623
|
-
collectionTypeActions[contentTypeUid] = {
|
|
624
|
-
entriestoPublishIds: [],
|
|
625
|
-
entriesToUnpublishIds: []
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
if (action.type === "publish") {
|
|
629
|
-
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
630
|
-
} else {
|
|
631
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
632
|
-
}
|
|
633
|
-
} else {
|
|
634
|
-
singleTypeActions.push({
|
|
635
|
-
uid: contentTypeUid,
|
|
636
|
-
action: action.type,
|
|
637
|
-
id: action.entry.id
|
|
638
|
-
});
|
|
639
|
-
}
|
|
948
|
+
if (lockedRelease.status === "failed") {
|
|
949
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
640
950
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
await entityManagerService.publish(entry, uid);
|
|
650
|
-
} else {
|
|
651
|
-
await entityManagerService.unpublish(entry, uid);
|
|
652
|
-
}
|
|
653
|
-
} catch (error) {
|
|
654
|
-
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
655
|
-
} else {
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
661
|
-
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
662
|
-
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
663
|
-
const entriesToPublish = await strapi2.entityService.findMany(
|
|
664
|
-
contentTypeUid,
|
|
665
|
-
{
|
|
666
|
-
filters: {
|
|
667
|
-
id: {
|
|
668
|
-
$in: entriestoPublishIds
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
populate
|
|
672
|
-
}
|
|
673
|
-
);
|
|
674
|
-
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
675
|
-
contentTypeUid,
|
|
676
|
-
{
|
|
677
|
-
filters: {
|
|
678
|
-
id: {
|
|
679
|
-
$in: entriesToUnpublishIds
|
|
680
|
-
}
|
|
681
|
-
},
|
|
682
|
-
populate
|
|
683
|
-
}
|
|
684
|
-
);
|
|
685
|
-
if (entriesToPublish.length > 0) {
|
|
686
|
-
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);
|
|
687
959
|
}
|
|
688
|
-
|
|
689
|
-
|
|
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
|
+
);
|
|
690
967
|
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
releasedAt: /* @__PURE__ */ new Date()
|
|
700
|
-
},
|
|
701
|
-
populate: {
|
|
702
|
-
actions: {
|
|
703
|
-
// @ts-expect-error is not expecting count but it is working
|
|
704
|
-
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()
|
|
705
976
|
}
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
977
|
+
});
|
|
709
978
|
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
710
979
|
isPublished: true,
|
|
711
|
-
release:
|
|
980
|
+
release: release22
|
|
712
981
|
});
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
} catch (error) {
|
|
717
|
-
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
982
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
983
|
+
return { release: release22, error: null };
|
|
984
|
+
} catch (error2) {
|
|
718
985
|
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
719
986
|
isPublished: false,
|
|
720
|
-
error
|
|
987
|
+
error: error2
|
|
721
988
|
});
|
|
989
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
990
|
+
status: "failed"
|
|
991
|
+
}).transacting(trx).execute();
|
|
992
|
+
return {
|
|
993
|
+
release: null,
|
|
994
|
+
error: error2
|
|
995
|
+
};
|
|
722
996
|
}
|
|
997
|
+
});
|
|
998
|
+
if (error) {
|
|
723
999
|
throw error;
|
|
724
1000
|
}
|
|
1001
|
+
return release2;
|
|
725
1002
|
},
|
|
726
1003
|
async updateAction(actionId, releaseId, update) {
|
|
727
1004
|
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
@@ -760,10 +1037,60 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
760
1037
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
761
1038
|
);
|
|
762
1039
|
}
|
|
1040
|
+
this.updateReleaseStatus(releaseId);
|
|
763
1041
|
return deletedAction;
|
|
1042
|
+
},
|
|
1043
|
+
async updateReleaseStatus(releaseId) {
|
|
1044
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1045
|
+
this.countActions({
|
|
1046
|
+
filters: {
|
|
1047
|
+
release: releaseId
|
|
1048
|
+
}
|
|
1049
|
+
}),
|
|
1050
|
+
this.countActions({
|
|
1051
|
+
filters: {
|
|
1052
|
+
release: releaseId,
|
|
1053
|
+
isEntryValid: false
|
|
1054
|
+
}
|
|
1055
|
+
})
|
|
1056
|
+
]);
|
|
1057
|
+
if (totalActions > 0) {
|
|
1058
|
+
if (invalidActions > 0) {
|
|
1059
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1060
|
+
where: {
|
|
1061
|
+
id: releaseId
|
|
1062
|
+
},
|
|
1063
|
+
data: {
|
|
1064
|
+
status: "blocked"
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1069
|
+
where: {
|
|
1070
|
+
id: releaseId
|
|
1071
|
+
},
|
|
1072
|
+
data: {
|
|
1073
|
+
status: "ready"
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1078
|
+
where: {
|
|
1079
|
+
id: releaseId
|
|
1080
|
+
},
|
|
1081
|
+
data: {
|
|
1082
|
+
status: "empty"
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
764
1085
|
}
|
|
765
1086
|
};
|
|
766
1087
|
};
|
|
1088
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1089
|
+
constructor(message) {
|
|
1090
|
+
super(message);
|
|
1091
|
+
this.name = "AlreadyOnReleaseError";
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
767
1094
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
768
1095
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
769
1096
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -776,7 +1103,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
776
1103
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
777
1104
|
);
|
|
778
1105
|
if (isEntryInRelease) {
|
|
779
|
-
throw new
|
|
1106
|
+
throw new AlreadyOnReleaseError(
|
|
780
1107
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
781
1108
|
);
|
|
782
1109
|
}
|
|
@@ -884,7 +1211,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
884
1211
|
const services = {
|
|
885
1212
|
release: createReleaseService,
|
|
886
1213
|
"release-validation": createReleaseValidationService,
|
|
887
|
-
|
|
1214
|
+
scheduling: createSchedulingService
|
|
888
1215
|
};
|
|
889
1216
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
890
1217
|
name: yup.string().trim().required(),
|
|
@@ -937,7 +1264,12 @@ const releaseController = {
|
|
|
937
1264
|
}
|
|
938
1265
|
};
|
|
939
1266
|
});
|
|
940
|
-
|
|
1267
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1268
|
+
where: {
|
|
1269
|
+
releasedAt: null
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
941
1273
|
}
|
|
942
1274
|
},
|
|
943
1275
|
async findOne(ctx) {
|
|
@@ -1055,6 +1387,38 @@ const releaseActionController = {
|
|
|
1055
1387
|
data: releaseAction2
|
|
1056
1388
|
};
|
|
1057
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
|
+
},
|
|
1058
1422
|
async findMany(ctx) {
|
|
1059
1423
|
const releaseId = ctx.params.releaseId;
|
|
1060
1424
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -1240,6 +1604,22 @@ const releaseAction = {
|
|
|
1240
1604
|
]
|
|
1241
1605
|
}
|
|
1242
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
|
+
},
|
|
1243
1623
|
{
|
|
1244
1624
|
method: "GET",
|
|
1245
1625
|
path: "/:releaseId/actions",
|
|
@@ -1308,6 +1688,9 @@ const getPlugin = () => {
|
|
|
1308
1688
|
};
|
|
1309
1689
|
}
|
|
1310
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
|
|
1311
1694
|
contentTypes
|
|
1312
1695
|
};
|
|
1313
1696
|
};
|