@strapi/strapi 4.5.0-beta.0 → 4.5.1

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/Strapi.js CHANGED
@@ -127,7 +127,7 @@ class Strapi {
127
127
  }
128
128
 
129
129
  get EE() {
130
- return ee({ dir: this.dirs.dist.root, logger: this.log });
130
+ return ee({ dir: this.dirs.app.root, logger: this.log });
131
131
  }
132
132
 
133
133
  get services() {
@@ -44,6 +44,7 @@ module.exports = async ({ buildDestDir, forceBuild = true, optimization, srcDir
44
44
  options: {
45
45
  backend: serverUrl,
46
46
  adminPath: addSlash(adminPath),
47
+ telemetryIsDisabled: strapiInstance.telemetry.isDisabled,
47
48
  },
48
49
  })
49
50
  .then(() => {
@@ -2,9 +2,7 @@
2
2
 
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
- const { templateConfiguration, env } = require('@strapi/utils');
6
-
7
- const importDefault = require('../../utils/import-default');
5
+ const { templateConfiguration, env, importDefault } = require('@strapi/utils');
8
6
 
9
7
  const loadJsFile = (file) => {
10
8
  try {
@@ -4,8 +4,7 @@ const { join, extname, basename } = require('path');
4
4
  const { existsSync } = require('fs-extra');
5
5
  const _ = require('lodash');
6
6
  const fse = require('fs-extra');
7
- const { isKebabCase } = require('@strapi/utils');
8
- const { importDefault } = require('../../utils');
7
+ const { isKebabCase, importDefault } = require('@strapi/utils');
9
8
 
10
9
  const DEFAULT_CONTENT_TYPE = {
11
10
  schema: {},
@@ -2,8 +2,7 @@
2
2
 
3
3
  const { join, extname, basename } = require('path');
4
4
  const fse = require('fs-extra');
5
-
6
- const { importDefault } = require('../../utils');
5
+ const { importDefault } = require('@strapi/utils');
7
6
 
8
7
  // TODO:: allow folders with index.js inside for bigger policies
9
8
  module.exports = async function loadMiddlewares(strapi) {
@@ -2,8 +2,7 @@
2
2
 
3
3
  const { join, extname, basename } = require('path');
4
4
  const fse = require('fs-extra');
5
-
6
- const { importDefault } = require('../../utils');
5
+ const { importDefault } = require('@strapi/utils');
7
6
 
8
7
  // TODO:: allow folders with index.js inside for bigger policies
9
8
  module.exports = async function loadPolicies(strapi) {
@@ -2,9 +2,7 @@
2
2
 
3
3
  const { resolve } = require('path');
4
4
  const { statSync, existsSync } = require('fs');
5
- const { yup } = require('@strapi/utils');
6
-
7
- const { importDefault } = require('../../utils');
5
+ const { yup, importDefault } = require('@strapi/utils');
8
6
 
9
7
  const srcSchema = yup
10
8
  .object()
@@ -1,7 +1,25 @@
1
1
  'use strict';
2
2
 
3
3
  const { has } = require('lodash/fp');
4
- const validators = require('../../services/entity-validator/validators');
4
+
5
+ const ALLOWED_TYPES = [
6
+ 'biginteger',
7
+ 'boolean',
8
+ 'date',
9
+ 'datetime',
10
+ 'decimal',
11
+ 'email',
12
+ 'enumeration',
13
+ 'float',
14
+ 'integer',
15
+ 'json',
16
+ 'password',
17
+ 'richtext',
18
+ 'string',
19
+ 'text',
20
+ 'time',
21
+ 'uid',
22
+ ];
5
23
 
6
24
  const customFieldsRegistry = (strapi) => {
7
25
  const customFields = {};
@@ -27,7 +45,7 @@ const customFieldsRegistry = (strapi) => {
27
45
  }
28
46
 
29
47
  const { name, plugin, type } = cf;
30
- if (!has(type, validators)) {
48
+ if (!ALLOWED_TYPES.includes(type)) {
31
49
  throw new Error(
32
50
  `Custom field type: '${type}' is not a valid Strapi type or it can't be used with a Custom Field`
33
51
  );
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const _ = require('lodash');
5
5
  const fse = require('fs-extra');
6
6
 
7
- const { importDefault } = require('../utils');
7
+ const { importDefault } = require('@strapi/utils');
8
8
  const glob = require('./glob');
9
9
  const filePathToPath = require('./filepath-to-prop-path');
10
10
 
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ const { existsSync } = require('fs');
3
4
  const { resolve } = require('path');
4
5
  const { defaultsDeep } = require('lodash/fp');
5
6
  const favicon = require('koa-favicon');
6
7
 
7
8
  const defaults = {
8
- path: 'favicon.ico',
9
+ path: 'favicon.png',
9
10
  maxAge: 86400000,
10
11
  };
11
12
 
@@ -13,7 +14,19 @@ const defaults = {
13
14
  * @type {import('./').MiddlewareFactory}
14
15
  */
15
16
  module.exports = (config, { strapi }) => {
16
- const { maxAge, path: faviconPath } = defaultsDeep(defaults, config);
17
+ const { maxAge, path: faviconDefaultPath } = defaultsDeep(defaults, config);
18
+ const { root: appRoot } = strapi.dirs.app;
19
+ let faviconPath = faviconDefaultPath;
17
20
 
18
- return favicon(resolve(strapi.dirs.app.root, faviconPath), { maxAge });
21
+ /** TODO (v5): Updating the favicon to use a png caused
22
+ * https://github.com/strapi/strapi/issues/14693
23
+ *
24
+ * This check ensures backwards compatibility until
25
+ * the next major version
26
+ */
27
+ if (!existsSync(resolve(appRoot, faviconPath))) {
28
+ faviconPath = 'favicon.ico';
29
+ }
30
+
31
+ return favicon(resolve(appRoot, faviconPath), { maxAge });
19
32
  };
@@ -267,44 +267,34 @@ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamic
267
267
  }
268
268
  };
269
269
 
270
- const deleteComponents = async (uid, entityToDelete) => {
270
+ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } = {}) => {
271
271
  const { attributes = {} } = strapi.getModel(uid);
272
272
 
273
273
  for (const attributeName of Object.keys(attributes)) {
274
274
  const attribute = attributes[attributeName];
275
275
 
276
- if (attribute.type === 'component') {
277
- const { component: componentUID } = attribute;
278
-
279
- // Load attribute value if it's not already loaded
280
- const value =
281
- entityToDelete[attributeName] ||
282
- (await strapi.query(uid).load(entityToDelete, attributeName));
283
-
284
- if (!value) {
285
- continue;
286
- }
287
-
288
- if (Array.isArray(value)) {
289
- await Promise.all(value.map((subValue) => deleteComponent(componentUID, subValue)));
276
+ if (attribute.type === 'component' || attribute.type === 'dynamiczone') {
277
+ let value;
278
+ if (loadComponents) {
279
+ value = await strapi.query(uid).load(entityToDelete, attributeName);
290
280
  } else {
291
- await deleteComponent(componentUID, value);
281
+ value = entityToDelete[attributeName];
292
282
  }
293
283
 
294
- continue;
295
- }
296
-
297
- if (attribute.type === 'dynamiczone') {
298
- const value =
299
- entityToDelete[attributeName] ||
300
- (await strapi.query(uid).load(entityToDelete, attributeName));
301
-
302
284
  if (!value) {
303
285
  continue;
304
286
  }
305
287
 
306
- if (Array.isArray(value)) {
307
- await Promise.all(value.map((subValue) => deleteComponent(subValue.__component, subValue)));
288
+ if (attribute.type === 'component') {
289
+ const { component: componentUID } = attribute;
290
+ await Promise.all(
291
+ _.castArray(value).map((subValue) => deleteComponent(componentUID, subValue))
292
+ );
293
+ } else {
294
+ // delete dynamic zone components
295
+ await Promise.all(
296
+ _.castArray(value).map((subValue) => deleteComponent(subValue.__component, subValue))
297
+ );
308
298
  }
309
299
 
310
300
  continue;
@@ -219,7 +219,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
219
219
  const componentsToDelete = await getComponents(uid, entityToDelete);
220
220
 
221
221
  await db.query(uid).delete({ where: { id: entityToDelete.id } });
222
- await deleteComponents(uid, { ...entityToDelete, ...componentsToDelete });
222
+ await deleteComponents(uid, componentsToDelete, { loadComponents: false });
223
223
 
224
224
  await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
225
225
 
@@ -244,7 +244,9 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
244
244
  );
245
245
 
246
246
  const deletedEntities = await db.query(uid).deleteMany(query);
247
- await Promise.all(componentsToDelete.map((compos) => deleteComponents(uid, compos)));
247
+ await Promise.all(
248
+ componentsToDelete.map((compos) => deleteComponents(uid, compos, { loadComponents: false }))
249
+ );
248
250
 
249
251
  // Trigger webhooks. One for each entity
250
252
  await Promise.all(entitiesToDelete.map((entity) => this.emitEvent(uid, ENTRY_DELETE, entity)));
@@ -5,7 +5,8 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const { has, assoc, prop, isObject } = require('lodash/fp');
8
+ const { uniqBy, castArray, isNil } = require('lodash');
9
+ const { has, assoc, prop, isObject, isEmpty, merge } = require('lodash/fp');
9
10
  const strapiUtils = require('@strapi/utils');
10
11
  const validators = require('./validators');
11
12
 
@@ -222,10 +223,136 @@ const createValidateEntity =
222
223
  entity,
223
224
  },
224
225
  { isDraft }
225
- ).required();
226
+ )
227
+ .test('relations-test', 'check that all relations exist', async function (data) {
228
+ try {
229
+ await checkRelationsExist(buildRelationsStore({ uid: model.uid, data }));
230
+ } catch (e) {
231
+ return this.createError({
232
+ path: this.path,
233
+ message: e.message,
234
+ });
235
+ }
236
+ return true;
237
+ })
238
+ .required();
239
+
226
240
  return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
227
241
  };
228
242
 
243
+ /**
244
+ * Builds an object containing all the media and relations being associated with an entity
245
+ * @param {String} uid of the model
246
+ * @param {Object} data
247
+ * @returns {Object}
248
+ */
249
+ const buildRelationsStore = ({ uid, data }) => {
250
+ if (isEmpty(data)) {
251
+ return {};
252
+ }
253
+ const currentModel = strapi.getModel(uid);
254
+
255
+ return Object.keys(currentModel.attributes).reduce((result, attributeName) => {
256
+ const attribute = currentModel.attributes[attributeName];
257
+ const value = data[attributeName];
258
+
259
+ if (isNil(value)) {
260
+ return result;
261
+ }
262
+
263
+ switch (attribute.type) {
264
+ case 'relation':
265
+ case 'media': {
266
+ if (attribute.relation === 'morphToMany' || attribute.relation === 'morphToOne') {
267
+ // TODO: handle polymorphic relations
268
+ break;
269
+ }
270
+
271
+ const target = attribute.type === 'media' ? 'plugin::upload.file' : attribute.target;
272
+ // As there are multiple formats supported for associating relations
273
+ // with an entity, the value here can be an: array, object or number.
274
+ let source;
275
+ if (Array.isArray(value)) {
276
+ source = value;
277
+ } else if (isObject(value)) {
278
+ source = value.connect ?? value.set ?? [];
279
+ } else {
280
+ source = castArray(value);
281
+ }
282
+ const idArray = source.map((v) => ({ id: v.id || v }));
283
+
284
+ // Update the relationStore to keep track of all associations being made
285
+ // with relations and media.
286
+ result[target] = result[target] || [];
287
+ result[target].push(...idArray);
288
+ break;
289
+ }
290
+ case 'component': {
291
+ return castArray(value).reduce(
292
+ (relationsStore, componentValue) =>
293
+ merge(
294
+ relationsStore,
295
+ buildRelationsStore({
296
+ uid: attribute.component,
297
+ data: componentValue,
298
+ })
299
+ ),
300
+ result
301
+ );
302
+ }
303
+ case 'dynamiczone': {
304
+ return value.reduce(
305
+ (relationsStore, dzValue) =>
306
+ merge(
307
+ relationsStore,
308
+ buildRelationsStore({
309
+ uid: dzValue.__component,
310
+ data: dzValue,
311
+ })
312
+ ),
313
+ result
314
+ );
315
+ }
316
+ default:
317
+ break;
318
+ }
319
+
320
+ return result;
321
+ }, {});
322
+ };
323
+
324
+ /**
325
+ * Iterate through the relations store and validates that every relation or media
326
+ * mentioned exists
327
+ */
328
+ const checkRelationsExist = async (relationsStore = {}) => {
329
+ const promises = [];
330
+
331
+ for (const [key, value] of Object.entries(relationsStore)) {
332
+ const evaluate = async () => {
333
+ const uniqueValues = uniqBy(value, `id`);
334
+ const count = await strapi.db.query(key).count({
335
+ where: {
336
+ id: {
337
+ $in: uniqueValues.map((v) => v.id),
338
+ },
339
+ },
340
+ });
341
+
342
+ if (count !== uniqueValues.length) {
343
+ throw new ValidationError(
344
+ `${
345
+ uniqueValues.length - count
346
+ } relation(s) of type ${key} associated with this entity do not exist`
347
+ );
348
+ }
349
+ };
350
+ promises.push(evaluate());
351
+ }
352
+
353
+ return Promise.all(promises);
354
+ };
355
+
229
356
  module.exports = {
230
357
  validateEntityCreation: createValidateEntity('creation'),
231
358
  validateEntityUpdate: createValidateEntity('update'),
@@ -2,6 +2,7 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { propOr, isArray, isNil } = require('lodash/fp');
5
+ const { importDefault } = require('@strapi/utils');
5
6
 
6
7
  const getMiddlewareConfig = propOr([], 'config.middlewares');
7
8
 
@@ -119,7 +120,7 @@ const resolveCustomMiddleware = (resolve, strapi) => {
119
120
  }
120
121
 
121
122
  try {
122
- return require(modulePath);
123
+ return importDefault(modulePath);
123
124
  } catch (err) {
124
125
  throw new Error(`Could not load middleware "${modulePath}".`);
125
126
  }
@@ -3,11 +3,9 @@
3
3
  const openBrowser = require('./open-browser');
4
4
  const isInitialized = require('./is-initialized');
5
5
  const getDirs = require('./get-dirs');
6
- const importDefault = require('./import-default');
7
6
 
8
7
  module.exports = {
9
8
  isInitialized,
10
9
  openBrowser,
11
10
  getDirs,
12
- importDefault,
13
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.5.0-beta.0",
3
+ "version": "4.5.1",
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",
@@ -78,25 +78,25 @@
78
78
  "test:unit": "jest --verbose"
79
79
  },
80
80
  "dependencies": {
81
- "@koa/cors": "3.4.2",
81
+ "@koa/cors": "3.4.3",
82
82
  "@koa/router": "10.1.1",
83
- "@strapi/admin": "4.5.0-beta.0",
84
- "@strapi/database": "4.5.0-beta.0",
85
- "@strapi/generate-new": "4.5.0-beta.0",
86
- "@strapi/generators": "4.5.0-beta.0",
87
- "@strapi/logger": "4.5.0-beta.0",
88
- "@strapi/permissions": "4.5.0-beta.0",
89
- "@strapi/plugin-content-manager": "4.5.0-beta.0",
90
- "@strapi/plugin-content-type-builder": "4.5.0-beta.0",
91
- "@strapi/plugin-email": "4.5.0-beta.0",
92
- "@strapi/plugin-upload": "4.5.0-beta.0",
93
- "@strapi/typescript-utils": "4.5.0-beta.0",
94
- "@strapi/utils": "4.5.0-beta.0",
83
+ "@strapi/admin": "4.5.1",
84
+ "@strapi/database": "4.5.1",
85
+ "@strapi/generate-new": "4.5.1",
86
+ "@strapi/generators": "4.5.1",
87
+ "@strapi/logger": "4.5.1",
88
+ "@strapi/permissions": "4.5.1",
89
+ "@strapi/plugin-content-manager": "4.5.1",
90
+ "@strapi/plugin-content-type-builder": "4.5.1",
91
+ "@strapi/plugin-email": "4.5.1",
92
+ "@strapi/plugin-upload": "4.5.1",
93
+ "@strapi/typescript-utils": "4.5.1",
94
+ "@strapi/utils": "4.5.1",
95
95
  "bcryptjs": "2.4.3",
96
96
  "boxen": "5.1.2",
97
97
  "chalk": "4.1.2",
98
98
  "chokidar": "3.5.2",
99
- "ci-info": "3.3.2",
99
+ "ci-info": "3.5.0",
100
100
  "cli-table3": "0.6.2",
101
101
  "commander": "8.2.0",
102
102
  "configstore": "5.0.1",
@@ -140,5 +140,5 @@
140
140
  "node": ">=14.19.1 <=18.x.x",
141
141
  "npm": ">=6.0.0"
142
142
  },
143
- "gitHead": "ee98b9a9cbb6e0e07e781ff9e87eb170c72e50df"
143
+ "gitHead": "8c20ea2b5c5a115b78454086ea270dcd59b06004"
144
144
  }
@@ -1,10 +0,0 @@
1
- 'use strict';
2
-
3
- const importDefault =
4
- (this && this.importDefault) ||
5
- function (modName) {
6
- const mod = require(modName);
7
- return mod && mod.__esModule ? mod.default : mod;
8
- };
9
-
10
- module.exports = importDefault;