@strapi/utils 5.0.0-beta.6 → 5.0.0-beta.8

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/index.mjs CHANGED
@@ -283,6 +283,15 @@ const isVisibleAttribute = (model, attributeName) => {
283
283
  const getOptions = (model) => ___default.assign({ draftAndPublish: false }, ___default.get(model, "options", {}));
284
284
  const hasDraftAndPublish = (model) => ___default.get(model, "options.draftAndPublish", false) === true;
285
285
  const isDraft = (data, model) => hasDraftAndPublish(model) && ___default.get(data, PUBLISHED_AT_ATTRIBUTE$1) === null;
286
+ const isSchema = (data) => {
287
+ return typeof data === "object" && data !== null && "modelType" in data && typeof data.modelType === "string" && ["component", "contentType"].includes(data.modelType);
288
+ };
289
+ const isComponentSchema = (data) => {
290
+ return isSchema(data) && data.modelType === "component";
291
+ };
292
+ const isContentTypeSchema = (data) => {
293
+ return isSchema(data) && data.modelType === "contentType";
294
+ };
286
295
  const isSingleType = ({ kind = COLLECTION_TYPE }) => kind === SINGLE_TYPE;
287
296
  const isCollectionType = ({ kind = COLLECTION_TYPE }) => kind === COLLECTION_TYPE;
288
297
  const isKind = (kind) => (model) => model.kind === kind;
@@ -303,14 +312,19 @@ const isPrivateAttribute = (model, attributeName) => {
303
312
  return getStoredPrivateAttributes(model).includes(attributeName);
304
313
  };
305
314
  const isScalarAttribute = (attribute) => {
306
- return !["media", "component", "relation", "dynamiczone"].includes(attribute?.type);
315
+ return attribute && !["media", "component", "relation", "dynamiczone"].includes(attribute.type);
316
+ };
317
+ const getDoesAttributeRequireValidation = (attribute) => {
318
+ return attribute.required || attribute.unique || Object.prototype.hasOwnProperty.call(attribute, "max") || Object.prototype.hasOwnProperty.call(attribute, "min") || Object.prototype.hasOwnProperty.call(attribute, "maxLength") || Object.prototype.hasOwnProperty.call(attribute, "minLength");
307
319
  };
308
320
  const isMediaAttribute = (attribute) => attribute?.type === "media";
309
321
  const isRelationalAttribute = (attribute) => attribute?.type === "relation";
322
+ const HAS_RELATION_REORDERING = ["manyToMany", "manyToOne", "oneToMany"];
323
+ const hasRelationReordering = (attribute) => isRelationalAttribute(attribute) && HAS_RELATION_REORDERING.includes(attribute.relation);
310
324
  const isComponentAttribute = (attribute) => ["component", "dynamiczone"].includes(attribute?.type);
311
- const isDynamicZoneAttribute = (attribute) => attribute?.type === "dynamiczone";
325
+ const isDynamicZoneAttribute = (attribute) => !!attribute && attribute.type === "dynamiczone";
312
326
  const isMorphToRelationalAttribute = (attribute) => {
313
- return isRelationalAttribute(attribute) && attribute?.relation?.startsWith?.("morphTo");
327
+ return !!attribute && isRelationalAttribute(attribute) && attribute.relation?.startsWith?.("morphTo");
314
328
  };
315
329
  const getComponentAttributes = (schema) => {
316
330
  return ___default.reduce(
@@ -357,6 +371,7 @@ const contentTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
357
371
  getComponentAttributes,
358
372
  getContentTypeRoutePrefix,
359
373
  getCreatorFields,
374
+ getDoesAttributeRequireValidation,
360
375
  getNonVisibleAttributes,
361
376
  getNonWritableAttributes,
362
377
  getOptions,
@@ -367,8 +382,11 @@ const contentTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
367
382
  getVisibleAttributes,
368
383
  getWritableAttributes,
369
384
  hasDraftAndPublish,
385
+ hasRelationReordering,
370
386
  isCollectionType,
371
387
  isComponentAttribute,
388
+ isComponentSchema,
389
+ isContentTypeSchema,
372
390
  isDraft,
373
391
  isDynamicZoneAttribute,
374
392
  isKind,
@@ -377,6 +395,7 @@ const contentTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
377
395
  isPrivateAttribute,
378
396
  isRelationalAttribute,
379
397
  isScalarAttribute,
398
+ isSchema,
380
399
  isSingleType,
381
400
  isTypedAttribute,
382
401
  isVisibleAttribute,
@@ -531,28 +550,29 @@ const providerFactory = (options = {}) => {
531
550
  };
532
551
  const traverseEntity = async (visitor2, options, entity) => {
533
552
  const { path = { raw: null, attribute: null }, schema, getModel } = options;
553
+ let parent = options.parent;
534
554
  const traverseMorphRelationTarget = async (visitor22, path2, entry) => {
535
555
  const targetSchema = getModel(entry.__type);
536
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
556
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
537
557
  return traverseEntity(visitor22, traverseOptions, entry);
538
558
  };
539
559
  const traverseRelationTarget = (schema2) => async (visitor22, path2, entry) => {
540
- const traverseOptions = { schema: schema2, path: path2, getModel };
560
+ const traverseOptions = { schema: schema2, path: path2, getModel, parent };
541
561
  return traverseEntity(visitor22, traverseOptions, entry);
542
562
  };
543
563
  const traverseMediaTarget = async (visitor22, path2, entry) => {
544
564
  const targetSchemaUID = "plugin::upload.file";
545
565
  const targetSchema = getModel(targetSchemaUID);
546
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
566
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
547
567
  return traverseEntity(visitor22, traverseOptions, entry);
548
568
  };
549
569
  const traverseComponent = async (visitor22, path2, schema2, entry) => {
550
- const traverseOptions = { schema: schema2, path: path2, getModel };
570
+ const traverseOptions = { schema: schema2, path: path2, getModel, parent };
551
571
  return traverseEntity(visitor22, traverseOptions, entry);
552
572
  };
553
573
  const visitDynamicZoneEntry = async (visitor22, path2, entry) => {
554
574
  const targetSchema = getModel(entry.__component);
555
- const traverseOptions = { schema: targetSchema, path: path2, getModel };
575
+ const traverseOptions = { schema: targetSchema, path: path2, getModel, parent };
556
576
  return traverseEntity(visitor22, traverseOptions, entry);
557
577
  };
558
578
  if (!isObject(entity) || isNil(schema)) {
@@ -564,9 +584,6 @@ const traverseEntity = async (visitor2, options, entity) => {
564
584
  for (let i = 0; i < keys.length; i += 1) {
565
585
  const key = keys[i];
566
586
  const attribute = schema.attributes[key];
567
- if (isNil(attribute)) {
568
- continue;
569
- }
570
587
  const newPath = { ...path };
571
588
  newPath.raw = isNil(path.raw) ? key : `${path.raw}.${key}`;
572
589
  if (!isNil(attribute)) {
@@ -579,13 +596,15 @@ const traverseEntity = async (visitor2, options, entity) => {
579
596
  value: copy[key],
580
597
  attribute,
581
598
  path: newPath,
582
- getModel
599
+ getModel,
600
+ parent
583
601
  };
584
602
  await visitor2(visitorOptions, visitorUtils);
585
603
  const value = copy[key];
586
- if (isNil(value)) {
604
+ if (isNil(value) || isNil(attribute)) {
587
605
  continue;
588
606
  }
607
+ parent = { schema, key, attribute, path: newPath };
589
608
  if (isRelationalAttribute(attribute)) {
590
609
  const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
591
610
  const method = isMorphRelation ? traverseMorphRelationTarget : traverseRelationTarget(getModel(attribute.target));
@@ -2153,13 +2172,16 @@ const index$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
2153
2172
  sanitizers,
2154
2173
  visitors: index$4
2155
2174
  }, Symbol.toStringTag, { value: "Module" }));
2156
- const throwInvalidParam = ({ key, path }) => {
2157
- const msg = path && path !== key ? `Invalid parameter ${key} at ${path}` : `Invalid parameter ${key}`;
2158
- throw new ValidationError(msg);
2175
+ const throwInvalidKey = ({ key, path }) => {
2176
+ const msg = path && path !== key ? `Invalid key ${key} at ${path}` : `Invalid key ${key}`;
2177
+ throw new ValidationError(msg, {
2178
+ key,
2179
+ path
2180
+ });
2159
2181
  };
2160
2182
  const visitor$3 = ({ key, attribute, path }) => {
2161
2183
  if (attribute?.type === "password") {
2162
- throwInvalidParam({ key, path: path.attribute });
2184
+ throwInvalidKey({ key, path: path.attribute });
2163
2185
  }
2164
2186
  };
2165
2187
  const visitor$2 = ({ schema, key, attribute, path }) => {
@@ -2168,7 +2190,7 @@ const visitor$2 = ({ schema, key, attribute, path }) => {
2168
2190
  }
2169
2191
  const isPrivate = attribute.private === true || isPrivateAttribute(schema, key);
2170
2192
  if (isPrivate) {
2171
- throwInvalidParam({ key, path: path.attribute });
2193
+ throwInvalidKey({ key, path: path.attribute });
2172
2194
  }
2173
2195
  };
2174
2196
  const ACTIONS_TO_VERIFY = ["find"];
@@ -2186,7 +2208,7 @@ const throwRestrictedRelations = (auth) => async ({ data, key, attribute, schema
2186
2208
  const scopes = ACTIONS_TO_VERIFY.map((action) => `${element.__type}.${action}`);
2187
2209
  const isAllowed = await hasAccessToSomeScopes(scopes, auth);
2188
2210
  if (!isAllowed) {
2189
- throwInvalidParam({ key, path: path.attribute });
2211
+ throwInvalidKey({ key, path: path.attribute });
2190
2212
  }
2191
2213
  }
2192
2214
  };
@@ -2194,7 +2216,7 @@ const throwRestrictedRelations = (auth) => async ({ data, key, attribute, schema
2194
2216
  const scopes = ACTIONS_TO_VERIFY.map((action) => `${attribute.target}.${action}`);
2195
2217
  const isAllowed = await hasAccessToSomeScopes(scopes, auth);
2196
2218
  if (!isAllowed) {
2197
- throwInvalidParam({ key, path: path.attribute });
2219
+ throwInvalidKey({ key, path: path.attribute });
2198
2220
  }
2199
2221
  };
2200
2222
  const isCreatorRelation = [CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE].includes(key);
@@ -2220,12 +2242,12 @@ const hasAccessToSomeScopes = async (scopes, auth) => {
2220
2242
  };
2221
2243
  const visitor$1 = ({ key, attribute, path }) => {
2222
2244
  if (isMorphToRelationalAttribute(attribute)) {
2223
- throwInvalidParam({ key, path: path.attribute });
2245
+ throwInvalidKey({ key, path: path.attribute });
2224
2246
  }
2225
2247
  };
2226
2248
  const visitor = ({ key, attribute, path }) => {
2227
2249
  if (isDynamicZoneAttribute(attribute)) {
2228
- throwInvalidParam({ key, path: path.attribute });
2250
+ throwInvalidKey({ key, path: path.attribute });
2229
2251
  }
2230
2252
  };
2231
2253
  const throwDisallowedFields = (allowedFields = null) => ({ key, path: { attribute: path } }) => {
@@ -2247,7 +2269,7 @@ const throwDisallowedFields = (allowedFields = null) => ({ key, path: { attribut
2247
2269
  if (isPathAllowed) {
2248
2270
  return;
2249
2271
  }
2250
- throwInvalidParam({ key, path });
2272
+ throwInvalidKey({ key, path });
2251
2273
  };
2252
2274
  const getContainedPaths = (path) => {
2253
2275
  const parts = toPath(path);
@@ -2257,7 +2279,7 @@ const getContainedPaths = (path) => {
2257
2279
  };
2258
2280
  const throwRestrictedFields = (restrictedFields = null) => ({ key, path: { attribute: path } }) => {
2259
2281
  if (restrictedFields === null) {
2260
- throwInvalidParam({ key, path });
2282
+ throwInvalidKey({ key, path });
2261
2283
  }
2262
2284
  if (!(isArray(restrictedFields) && restrictedFields.every(isString))) {
2263
2285
  throw new TypeError(
@@ -2265,15 +2287,45 @@ const throwRestrictedFields = (restrictedFields = null) => ({ key, path: { attri
2265
2287
  );
2266
2288
  }
2267
2289
  if (restrictedFields.includes(path)) {
2268
- throwInvalidParam({ key, path });
2290
+ throwInvalidKey({ key, path });
2269
2291
  }
2270
2292
  const isRestrictedNested = restrictedFields.some(
2271
2293
  (allowedPath) => path?.toString().startsWith(`${allowedPath}.`)
2272
2294
  );
2273
2295
  if (isRestrictedNested) {
2274
- throwInvalidParam({ key, path });
2296
+ throwInvalidKey({ key, path });
2275
2297
  }
2276
2298
  };
2299
+ const ID_FIELDS = [constants$1.DOC_ID_ATTRIBUTE, constants$1.DOC_ID_ATTRIBUTE];
2300
+ const ALLOWED_ROOT_LEVEL_FIELDS = [...ID_FIELDS];
2301
+ const MORPH_TO_ALLOWED_FIELDS = ["__type"];
2302
+ const DYNAMIC_ZONE_ALLOWED_FIELDS = ["__component"];
2303
+ const RELATION_REORDERING_FIELDS = ["connect", "disconnect", "set", "options"];
2304
+ const throwUnrecognizedFields = ({ key, attribute, path, schema, parent }) => {
2305
+ if (attribute) {
2306
+ return;
2307
+ }
2308
+ if (path.attribute === null) {
2309
+ if (ALLOWED_ROOT_LEVEL_FIELDS.includes(key)) {
2310
+ return;
2311
+ }
2312
+ return throwInvalidKey({ key, path: attribute });
2313
+ }
2314
+ if (isMorphToRelationalAttribute(parent?.attribute) && MORPH_TO_ALLOWED_FIELDS.includes(key)) {
2315
+ return;
2316
+ }
2317
+ if (isComponentSchema(schema) && isDynamicZoneAttribute(parent?.attribute) && DYNAMIC_ZONE_ALLOWED_FIELDS.includes(key)) {
2318
+ return;
2319
+ }
2320
+ if (hasRelationReordering(parent?.attribute) && RELATION_REORDERING_FIELDS.includes(key)) {
2321
+ return;
2322
+ }
2323
+ const canUseID = isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute);
2324
+ if (canUseID && !ID_FIELDS.includes(key)) {
2325
+ return;
2326
+ }
2327
+ throwInvalidKey({ key, path: attribute });
2328
+ };
2277
2329
  const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2278
2330
  __proto__: null,
2279
2331
  throwDisallowedFields,
@@ -2282,7 +2334,8 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
2282
2334
  throwPassword: visitor$3,
2283
2335
  throwPrivate: visitor$2,
2284
2336
  throwRestrictedFields,
2285
- throwRestrictedRelations
2337
+ throwRestrictedRelations,
2338
+ throwUnrecognizedFields
2286
2339
  }, Symbol.toStringTag, { value: "Module" }));
2287
2340
  const { ID_ATTRIBUTE: ID_ATTRIBUTE$1, DOC_ID_ATTRIBUTE: DOC_ID_ATTRIBUTE$1 } = constants$1;
2288
2341
  const throwPasswords = (ctx) => async (entity) => {
@@ -2303,7 +2356,7 @@ const defaultValidateFilters = curry((ctx, filters2) => {
2303
2356
  }
2304
2357
  const isAttribute = !!attribute;
2305
2358
  if (!isAttribute && !isOperator(key)) {
2306
- throwInvalidParam({ key, path: path.attribute });
2359
+ throwInvalidKey({ key, path: path.attribute });
2307
2360
  }
2308
2361
  }, ctx),
2309
2362
  // dynamic zones from filters
@@ -2328,7 +2381,7 @@ const defaultValidateSort = curry((ctx, sort2) => {
2328
2381
  return;
2329
2382
  }
2330
2383
  if (!attribute) {
2331
- throwInvalidParam({ key, path: path.attribute });
2384
+ throwInvalidKey({ key, path: path.attribute });
2332
2385
  }
2333
2386
  }, ctx),
2334
2387
  // dynamic zones from sort
@@ -2345,7 +2398,7 @@ const defaultValidateSort = curry((ctx, sort2) => {
2345
2398
  return;
2346
2399
  }
2347
2400
  if (!isScalarAttribute(attribute) && isEmpty(value)) {
2348
- throwInvalidParam({ key, path: path.attribute });
2401
+ throwInvalidKey({ key, path: path.attribute });
2349
2402
  }
2350
2403
  }, ctx)
2351
2404
  )(sort2);
@@ -2361,7 +2414,7 @@ const defaultValidateFields = curry((ctx, fields2) => {
2361
2414
  return;
2362
2415
  }
2363
2416
  if (isNil(attribute) || !isScalarAttribute(attribute)) {
2364
- throwInvalidParam({ key, path: path.attribute });
2417
+ throwInvalidKey({ key, path: path.attribute });
2365
2418
  }
2366
2419
  }, ctx),
2367
2420
  // private fields
@@ -2456,15 +2509,18 @@ const createAPIValidators = (opts) => {
2456
2509
  (data2) => {
2457
2510
  if (isObject(data2)) {
2458
2511
  if (ID_ATTRIBUTE in data2) {
2459
- throwInvalidParam({ key: ID_ATTRIBUTE });
2512
+ throwInvalidKey({ key: ID_ATTRIBUTE });
2460
2513
  }
2461
2514
  if (DOC_ID_ATTRIBUTE in data2) {
2462
- throwInvalidParam({ key: DOC_ID_ATTRIBUTE });
2515
+ throwInvalidKey({ key: DOC_ID_ATTRIBUTE });
2463
2516
  }
2464
2517
  }
2518
+ return data2;
2465
2519
  },
2466
2520
  // non-writable attributes
2467
- traverseEntity$1(throwRestrictedFields(nonWritableAttributes), { schema, getModel })
2521
+ traverseEntity$1(throwRestrictedFields(nonWritableAttributes), { schema, getModel }),
2522
+ // unrecognized attributes
2523
+ traverseEntity$1(throwUnrecognizedFields, { schema, getModel })
2468
2524
  ];
2469
2525
  if (auth) {
2470
2526
  transforms.push(
@@ -2475,7 +2531,14 @@ const createAPIValidators = (opts) => {
2475
2531
  );
2476
2532
  }
2477
2533
  opts?.validators?.input?.forEach((validator) => transforms.push(validator(schema)));
2478
- await pipe(...transforms)(data);
2534
+ try {
2535
+ await pipe(...transforms)(data);
2536
+ } catch (e) {
2537
+ if (e instanceof ValidationError) {
2538
+ e.details.source = "body";
2539
+ }
2540
+ throw e;
2541
+ }
2479
2542
  };
2480
2543
  const validateQuery = async (query, schema, { auth } = {}) => {
2481
2544
  if (!schema) {
@@ -2512,7 +2575,15 @@ const createAPIValidators = (opts) => {
2512
2575
  })
2513
2576
  );
2514
2577
  }
2515
- await pipe(...transforms)(filters2);
2578
+ try {
2579
+ await pipe(...transforms)(filters2);
2580
+ } catch (e) {
2581
+ if (e instanceof ValidationError) {
2582
+ e.details.source = "query";
2583
+ e.details.param = "filters";
2584
+ }
2585
+ throw e;
2586
+ }
2516
2587
  };
2517
2588
  const validateSort = async (sort2, schema, { auth } = {}) => {
2518
2589
  if (!schema) {
@@ -2527,14 +2598,30 @@ const createAPIValidators = (opts) => {
2527
2598
  })
2528
2599
  );
2529
2600
  }
2530
- await pipe(...transforms)(sort2);
2601
+ try {
2602
+ await pipe(...transforms)(sort2);
2603
+ } catch (e) {
2604
+ if (e instanceof ValidationError) {
2605
+ e.details.source = "query";
2606
+ e.details.param = "sort";
2607
+ }
2608
+ throw e;
2609
+ }
2531
2610
  };
2532
2611
  const validateFields = async (fields2, schema) => {
2533
2612
  if (!schema) {
2534
2613
  throw new Error("Missing schema in validateFields");
2535
2614
  }
2536
2615
  const transforms = [defaultValidateFields({ schema, getModel })];
2537
- await pipe(...transforms)(fields2);
2616
+ try {
2617
+ await pipe(...transforms)(fields2);
2618
+ } catch (e) {
2619
+ if (e instanceof ValidationError) {
2620
+ e.details.source = "query";
2621
+ e.details.param = "fields";
2622
+ }
2623
+ throw e;
2624
+ }
2538
2625
  };
2539
2626
  const validatePopulate = async (populate2, schema, { auth } = {}) => {
2540
2627
  if (!schema) {
@@ -2549,7 +2636,15 @@ const createAPIValidators = (opts) => {
2549
2636
  })
2550
2637
  );
2551
2638
  }
2552
- await pipe(...transforms)(populate2);
2639
+ try {
2640
+ await pipe(...transforms)(populate2);
2641
+ } catch (e) {
2642
+ if (e instanceof ValidationError) {
2643
+ e.details.source = "query";
2644
+ e.details.param = "populate";
2645
+ }
2646
+ throw e;
2647
+ }
2553
2648
  };
2554
2649
  return {
2555
2650
  input: validateInput,
@@ -2633,8 +2728,57 @@ const withDefaultPagination = (args, { defaults: defaults2 = {}, maxLimit = -1 }
2633
2728
  );
2634
2729
  return replacePaginationAttributes(args);
2635
2730
  };
2731
+ const transformPagedPaginationInfo = (paginationInfo, total) => {
2732
+ if (!isNil(paginationInfo.page)) {
2733
+ const page = paginationInfo.page;
2734
+ const pageSize = paginationInfo.pageSize ?? total;
2735
+ return {
2736
+ page,
2737
+ pageSize,
2738
+ pageCount: pageSize > 0 ? Math.ceil(total / pageSize) : 0,
2739
+ total
2740
+ };
2741
+ }
2742
+ if (!isNil(paginationInfo.start)) {
2743
+ const start = paginationInfo.start;
2744
+ const limit = paginationInfo.limit ?? total;
2745
+ return {
2746
+ page: Math.floor(start / limit) + 1,
2747
+ pageSize: limit,
2748
+ pageCount: limit > 0 ? Math.ceil(total / limit) : 0,
2749
+ total
2750
+ };
2751
+ }
2752
+ return {
2753
+ ...paginationInfo,
2754
+ page: 1,
2755
+ pageSize: 10,
2756
+ pageCount: 1,
2757
+ total
2758
+ };
2759
+ };
2760
+ const transformOffsetPaginationInfo = (paginationInfo, total) => {
2761
+ if (!isNil(paginationInfo.page)) {
2762
+ const limit = paginationInfo.pageSize ?? total;
2763
+ const start = (paginationInfo.page - 1) * limit;
2764
+ return { start, limit, total };
2765
+ }
2766
+ if (!isNil(paginationInfo.start)) {
2767
+ const start = paginationInfo.start;
2768
+ const limit = paginationInfo.limit ?? total;
2769
+ return { start, limit, total };
2770
+ }
2771
+ return {
2772
+ ...paginationInfo,
2773
+ start: 0,
2774
+ limit: 10,
2775
+ total
2776
+ };
2777
+ };
2636
2778
  const pagination = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2637
2779
  __proto__: null,
2780
+ transformOffsetPaginationInfo,
2781
+ transformPagedPaginationInfo,
2638
2782
  withDefaultPagination
2639
2783
  }, Symbol.toStringTag, { value: "Module" }));
2640
2784
  const SUPPORTED_PACKAGE_MANAGERS = ["npm", "yarn"];