@strapi/strapi 4.11.2 → 4.11.4
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/lib/commands/actions/telemetry/disable/action.js +7 -2
- package/lib/commands/actions/telemetry/enable/action.js +7 -2
- package/lib/services/entity-service/components.js +110 -0
- package/lib/services/entity-service/index.d.ts +5 -0
- package/lib/services/entity-service/index.js +50 -0
- package/lib/services/metrics/sender.js +1 -1
- package/lib/types/core/attributes/date.d.ts +1 -1
- package/lib/utils/success.js +6 -2
- package/package.json +15 -15
|
@@ -28,14 +28,19 @@ const writePackageJSON = async (path, file, spacing) => {
|
|
|
28
28
|
|
|
29
29
|
const sendEvent = async (uuid) => {
|
|
30
30
|
try {
|
|
31
|
+
const event = 'didOptOutTelemetry';
|
|
32
|
+
|
|
31
33
|
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
32
34
|
method: 'POST',
|
|
33
35
|
body: JSON.stringify({
|
|
34
|
-
event
|
|
36
|
+
event,
|
|
35
37
|
deviceId: machineID(),
|
|
36
38
|
groupProperties: { projectId: uuid },
|
|
37
39
|
}),
|
|
38
|
-
headers: {
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Strapi-Event': event,
|
|
43
|
+
},
|
|
39
44
|
});
|
|
40
45
|
} catch (e) {
|
|
41
46
|
// ...
|
|
@@ -53,14 +53,19 @@ const generateNewPackageJSON = (packageObj) => {
|
|
|
53
53
|
|
|
54
54
|
const sendEvent = async (uuid) => {
|
|
55
55
|
try {
|
|
56
|
+
const event = 'didOptInTelemetry';
|
|
57
|
+
|
|
56
58
|
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
57
59
|
method: 'POST',
|
|
58
60
|
body: JSON.stringify({
|
|
59
|
-
event
|
|
61
|
+
event,
|
|
60
62
|
deviceId: machineID(),
|
|
61
63
|
groupProperties: { projectId: uuid },
|
|
62
64
|
}),
|
|
63
|
-
headers: {
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'X-Strapi-Event': event,
|
|
68
|
+
},
|
|
64
69
|
});
|
|
65
70
|
} catch (e) {
|
|
66
71
|
// ...
|
|
@@ -322,6 +322,95 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
|
|
|
322
322
|
}
|
|
323
323
|
};
|
|
324
324
|
|
|
325
|
+
const cloneComponents = async (uid, entityToClone, data) => {
|
|
326
|
+
const { attributes = {} } = strapi.getModel(uid);
|
|
327
|
+
|
|
328
|
+
const componentBody = {};
|
|
329
|
+
const componentData = await getComponents(uid, entityToClone);
|
|
330
|
+
|
|
331
|
+
for (const attributeName of Object.keys(attributes)) {
|
|
332
|
+
const attribute = attributes[attributeName];
|
|
333
|
+
|
|
334
|
+
// If the attribute is not set or on the component to clone, skip it
|
|
335
|
+
if (!has(attributeName, data) && !has(attributeName, componentData)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (attribute.type === 'component') {
|
|
340
|
+
const { component: componentUID, repeatable = false } = attribute;
|
|
341
|
+
|
|
342
|
+
const componentValue = has(attributeName, data)
|
|
343
|
+
? data[attributeName]
|
|
344
|
+
: componentData[attributeName];
|
|
345
|
+
|
|
346
|
+
if (componentValue === null) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (repeatable === true) {
|
|
351
|
+
if (!Array.isArray(componentValue)) {
|
|
352
|
+
throw new Error('Expected an array to create repeatable component');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
356
|
+
const components = await mapAsync(
|
|
357
|
+
componentValue,
|
|
358
|
+
(value) => cloneComponent(componentUID, value),
|
|
359
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
|
|
363
|
+
return {
|
|
364
|
+
id,
|
|
365
|
+
__pivot: {
|
|
366
|
+
field: attributeName,
|
|
367
|
+
component_type: componentUID,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
} else {
|
|
372
|
+
const component = await cloneComponent(componentUID, componentValue);
|
|
373
|
+
componentBody[attributeName] = component && {
|
|
374
|
+
id: component.id,
|
|
375
|
+
__pivot: {
|
|
376
|
+
field: attributeName,
|
|
377
|
+
component_type: componentUID,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (attribute.type === 'dynamiczone') {
|
|
386
|
+
const dynamiczoneValues = has(attributeName, data)
|
|
387
|
+
? data[attributeName]
|
|
388
|
+
: componentData[attributeName];
|
|
389
|
+
|
|
390
|
+
if (!Array.isArray(dynamiczoneValues)) {
|
|
391
|
+
throw new Error('Expected an array to create repeatable component');
|
|
392
|
+
}
|
|
393
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
394
|
+
componentBody[attributeName] = await mapAsync(
|
|
395
|
+
dynamiczoneValues,
|
|
396
|
+
async (value) => {
|
|
397
|
+
const { id } = await cloneComponent(value.__component, value);
|
|
398
|
+
return {
|
|
399
|
+
id,
|
|
400
|
+
__component: value.__component,
|
|
401
|
+
__pivot: {
|
|
402
|
+
field: attributeName,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return componentBody;
|
|
413
|
+
};
|
|
325
414
|
/** *************************
|
|
326
415
|
Component queries
|
|
327
416
|
************************** */
|
|
@@ -377,6 +466,26 @@ const deleteComponent = async (uid, componentToDelete) => {
|
|
|
377
466
|
await strapi.query(uid).delete({ where: { id: componentToDelete.id } });
|
|
378
467
|
};
|
|
379
468
|
|
|
469
|
+
const cloneComponent = async (uid, data) => {
|
|
470
|
+
const model = strapi.getModel(uid);
|
|
471
|
+
|
|
472
|
+
if (!has('id', data)) {
|
|
473
|
+
return createComponent(uid, data);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const componentData = await cloneComponents(uid, { id: data.id }, data);
|
|
477
|
+
const transform = pipe(
|
|
478
|
+
// Make sure we don't save the component with a pre-defined ID
|
|
479
|
+
omit('id'),
|
|
480
|
+
// Remove the component data from the original data object ...
|
|
481
|
+
(payload) => omitComponentData(model, payload),
|
|
482
|
+
// ... and assign the newly created component instead
|
|
483
|
+
assign(componentData)
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
return strapi.query(uid).clone(data.id, { data: transform(data) });
|
|
487
|
+
};
|
|
488
|
+
|
|
380
489
|
module.exports = {
|
|
381
490
|
omitComponentData,
|
|
382
491
|
getComponents,
|
|
@@ -384,4 +493,5 @@ module.exports = {
|
|
|
384
493
|
updateComponents,
|
|
385
494
|
deleteComponents,
|
|
386
495
|
deleteComponent,
|
|
496
|
+
cloneComponents,
|
|
387
497
|
};
|
|
@@ -84,6 +84,11 @@ export interface EntityService {
|
|
|
84
84
|
entityId: ID,
|
|
85
85
|
params: Params<T>
|
|
86
86
|
): Promise<any>;
|
|
87
|
+
clone<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
88
|
+
uid: K,
|
|
89
|
+
cloneId: ID,
|
|
90
|
+
params: Params<T>
|
|
91
|
+
): Promise<any>;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
export default function (opts: {
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
createComponents,
|
|
17
17
|
updateComponents,
|
|
18
18
|
deleteComponents,
|
|
19
|
+
cloneComponents,
|
|
19
20
|
} = require('./components');
|
|
20
21
|
const { pickSelectionParams } = require('./params');
|
|
21
22
|
const { applyTransforms } = require('./attributes');
|
|
@@ -267,6 +268,55 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
267
268
|
return entityToDelete;
|
|
268
269
|
},
|
|
269
270
|
|
|
271
|
+
async clone(uid, cloneId, opts) {
|
|
272
|
+
const wrappedParams = await this.wrapParams(opts, { uid, action: 'clone' });
|
|
273
|
+
const { data, files } = wrappedParams;
|
|
274
|
+
|
|
275
|
+
const model = strapi.getModel(uid);
|
|
276
|
+
|
|
277
|
+
const entityToClone = await db.query(uid).findOne({ where: { id: cloneId } });
|
|
278
|
+
|
|
279
|
+
if (!entityToClone) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const isDraft = contentTypesUtils.isDraft(entityToClone, model);
|
|
283
|
+
|
|
284
|
+
const validData = await entityValidator.validateEntityUpdate(
|
|
285
|
+
model,
|
|
286
|
+
data,
|
|
287
|
+
{
|
|
288
|
+
isDraft,
|
|
289
|
+
},
|
|
290
|
+
entityToClone
|
|
291
|
+
);
|
|
292
|
+
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
|
|
293
|
+
|
|
294
|
+
// TODO: wrap into transaction
|
|
295
|
+
const componentData = await cloneComponents(uid, entityToClone, validData);
|
|
296
|
+
|
|
297
|
+
const entityData = creationPipeline(
|
|
298
|
+
Object.assign(omitComponentData(model, validData), componentData),
|
|
299
|
+
{
|
|
300
|
+
contentType: model,
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
let entity = await db.query(uid).clone(cloneId, {
|
|
305
|
+
...query,
|
|
306
|
+
data: entityData,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
310
|
+
if (files && Object.keys(files).length > 0) {
|
|
311
|
+
await this.uploadFiles(uid, Object.assign(entityData, entity), files);
|
|
312
|
+
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { ENTRY_CREATE } = ALLOWED_WEBHOOK_EVENTS;
|
|
316
|
+
await this.emitEvent(uid, ENTRY_CREATE, entity);
|
|
317
|
+
|
|
318
|
+
return entity;
|
|
319
|
+
},
|
|
270
320
|
// FIXME: used only for the CM to be removed
|
|
271
321
|
async deleteMany(uid, opts) {
|
|
272
322
|
const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
|
|
@@ -8,6 +8,6 @@ export type Date = Attribute.OfType<'date'> &
|
|
|
8
8
|
Attribute.RequiredOption &
|
|
9
9
|
Attribute.UniqueOption;
|
|
10
10
|
|
|
11
|
-
export type DateValue = Date;
|
|
11
|
+
export type DateValue = globalThis.Date | string;
|
|
12
12
|
|
|
13
13
|
export type GetDateValue<T extends Attribute.Attribute> = T extends Date ? DateValue : never;
|
package/lib/utils/success.js
CHANGED
|
@@ -17,13 +17,17 @@ try {
|
|
|
17
17
|
process.env.npm_config_global === 'true' ||
|
|
18
18
|
JSON.parse(process.env.npm_config_argv).original.includes('global')
|
|
19
19
|
) {
|
|
20
|
+
const event = 'didInstallStrapi';
|
|
20
21
|
fetch('https://analytics.strapi.io/api/v2/track', {
|
|
21
22
|
method: 'POST',
|
|
22
23
|
body: JSON.stringify({
|
|
23
|
-
event
|
|
24
|
+
event,
|
|
24
25
|
deviceId: machineID(),
|
|
25
26
|
}),
|
|
26
|
-
headers: {
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
'X-Strapi-Event': event,
|
|
30
|
+
},
|
|
27
31
|
}).catch(() => {});
|
|
28
32
|
}
|
|
29
33
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.11.
|
|
3
|
+
"version": "4.11.4",
|
|
4
4
|
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -81,19 +81,19 @@
|
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@koa/cors": "3.4.3",
|
|
83
83
|
"@koa/router": "10.1.1",
|
|
84
|
-
"@strapi/admin": "4.11.
|
|
85
|
-
"@strapi/data-transfer": "4.11.
|
|
86
|
-
"@strapi/database": "4.11.
|
|
87
|
-
"@strapi/generate-new": "4.11.
|
|
88
|
-
"@strapi/generators": "4.11.
|
|
89
|
-
"@strapi/logger": "4.11.
|
|
90
|
-
"@strapi/permissions": "4.11.
|
|
91
|
-
"@strapi/plugin-content-manager": "4.11.
|
|
92
|
-
"@strapi/plugin-content-type-builder": "4.11.
|
|
93
|
-
"@strapi/plugin-email": "4.11.
|
|
94
|
-
"@strapi/plugin-upload": "4.11.
|
|
95
|
-
"@strapi/typescript-utils": "4.11.
|
|
96
|
-
"@strapi/utils": "4.11.
|
|
84
|
+
"@strapi/admin": "4.11.4",
|
|
85
|
+
"@strapi/data-transfer": "4.11.4",
|
|
86
|
+
"@strapi/database": "4.11.4",
|
|
87
|
+
"@strapi/generate-new": "4.11.4",
|
|
88
|
+
"@strapi/generators": "4.11.4",
|
|
89
|
+
"@strapi/logger": "4.11.4",
|
|
90
|
+
"@strapi/permissions": "4.11.4",
|
|
91
|
+
"@strapi/plugin-content-manager": "4.11.4",
|
|
92
|
+
"@strapi/plugin-content-type-builder": "4.11.4",
|
|
93
|
+
"@strapi/plugin-email": "4.11.4",
|
|
94
|
+
"@strapi/plugin-upload": "4.11.4",
|
|
95
|
+
"@strapi/typescript-utils": "4.11.4",
|
|
96
|
+
"@strapi/utils": "4.11.4",
|
|
97
97
|
"bcryptjs": "2.4.3",
|
|
98
98
|
"boxen": "5.1.2",
|
|
99
99
|
"chalk": "4.1.2",
|
|
@@ -142,5 +142,5 @@
|
|
|
142
142
|
"node": ">=14.19.1 <=18.x.x",
|
|
143
143
|
"npm": ">=6.0.0"
|
|
144
144
|
},
|
|
145
|
-
"gitHead": "
|
|
145
|
+
"gitHead": "8d325a695386ab9b578b360bf768cfa25173050f"
|
|
146
146
|
}
|