@stackbit/sdk 2.0.7 → 2.0.8-staging.2

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.
@@ -4,6 +4,7 @@ import chokidar from 'chokidar';
4
4
  import semver from 'semver';
5
5
  import _ from 'lodash';
6
6
 
7
+ import * as StackbitTypes from '@stackbit/types';
7
8
  import {
8
9
  ModelsSource,
9
10
  Field,
@@ -697,7 +698,9 @@ export function mergeConfigModelsWithExternalModels({
697
698
  break;
698
699
  }
699
700
 
700
- return Object.assign({}, externalFieldProps, fieldSpecificProps, { type: fieldType });
701
+ const validations = mergeFieldValidations(externalFieldProps, stackbitFieldProps);
702
+
703
+ return Object.assign({}, externalFieldProps, fieldSpecificProps, { type: fieldType }, validations ? { validations } : undefined);
701
704
  });
702
705
 
703
706
  const mergedField = Object.assign(
@@ -718,6 +721,14 @@ export function mergeConfigModelsWithExternalModels({
718
721
  ])
719
722
  );
720
723
 
724
+ // Merge list field specific properties that weren't merged in mapListItemsPropsOrSelfSpecificProps
725
+ if (externalField.type === 'list') {
726
+ const validations = mergeFieldValidations(externalField, stackbitField);
727
+ if (validations) {
728
+ mergedField.validations = validations;
729
+ }
730
+ }
731
+
721
732
  const externalActions = 'actions' in externalField && Array.isArray(externalField.actions) ? externalField.actions : [];
722
733
  const configActions = 'actions' in stackbitField && Array.isArray(stackbitField.actions) ? stackbitField.actions : [];
723
734
  if (externalActions.length || configActions.length) {
@@ -733,6 +744,35 @@ export function mergeConfigModelsWithExternalModels({
733
744
  return Object.values(mergedModelsByName);
734
745
  }
735
746
 
747
+ function mergeFieldValidations<T extends StackbitTypes.FieldSpecificProps>(
748
+ externalFieldProps: T,
749
+ stackbitFieldProps: FieldExtension | DistributePartialListItems<FieldListItems>
750
+ ): T['validations'] {
751
+ const externalFieldValidations = externalFieldProps.validations;
752
+ const configFieldValidation = stackbitFieldProps.validations;
753
+ if (externalFieldValidations && configFieldValidation) {
754
+ const extendedValidations: StackbitTypes.FieldValidationsCustom = {};
755
+ // concatenate custom validation functions
756
+ if (
757
+ 'validate' in externalFieldValidations &&
758
+ externalFieldValidations.validate &&
759
+ 'validate' in configFieldValidation &&
760
+ configFieldValidation.validate
761
+ ) {
762
+ const externalFileTypes = Array.isArray(externalFieldValidations.validate)
763
+ ? externalFieldValidations.validate
764
+ : [externalFieldValidations.validate];
765
+ const stackbitFileTypes = Array.isArray(configFieldValidation.validate) ? configFieldValidation.validate : [configFieldValidation.validate];
766
+ extendedValidations.validate = [...externalFileTypes, ...stackbitFileTypes];
767
+ }
768
+ return Object.assign({}, externalFieldValidations, configFieldValidation, extendedValidations);
769
+ } else if (configFieldValidation) {
770
+ return configFieldValidation;
771
+ } else {
772
+ return externalFieldValidations;
773
+ }
774
+ }
775
+
736
776
  function normalizeConfig(rawConfig: StackbitConfigWithPaths): Config {
737
777
  const stackbitVersion = String(_.get(rawConfig, 'stackbitVersion', LATEST_STACKBIT_VERSION));
738
778
  const ver = semver.coerce(stackbitVersion);
@@ -34,6 +34,8 @@ function getModelsFromValidationState(state: Joi.State): Model[] {
34
34
  return config.models ?? [];
35
35
  }
36
36
 
37
+ const arrayOfStrings = Joi.array().items(Joi.string());
38
+
37
39
  const fieldNamePattern = /^[a-zA-Z0-9_$]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
38
40
  const fieldNameError =
39
41
  'Invalid field name "{{#value}}" at "{{#label}}". A field name must contain only alphanumeric characters, ' +
@@ -265,7 +267,7 @@ const importSchema = Joi.alternatives().conditional('.type', {
265
267
 
266
268
  const presetSourceFilesSchema = Joi.object<PresetSourceFiles>({
267
269
  type: Joi.string().valid(Joi.override, 'files').required(),
268
- presetDirs: Joi.array().items(Joi.string()).required()
270
+ presetDirs: arrayOfStrings.required()
269
271
  });
270
272
 
271
273
  const presetSourceSchema = Joi.object<PresetSource>({
@@ -276,7 +278,7 @@ const presetSourceSchema = Joi.object<PresetSource>({
276
278
 
277
279
  const modelsSourceFilesSchema = Joi.object<ModelsSourceFiles>({
278
280
  type: Joi.string().valid(Joi.override, 'files').required(),
279
- modelDirs: Joi.array().items(Joi.string()).required()
281
+ modelDirs: arrayOfStrings.required()
280
282
  });
281
283
 
282
284
  const modelsSourceContentfulSchema = Joi.object<ModelsSourceContentful>({
@@ -400,6 +402,67 @@ const customActionFieldSchema = customActionBaseSchema.concat(
400
402
  })
401
403
  );
402
404
 
405
+ const stringOrNumber = Joi.alternatives().try(Joi.string(), Joi.number());
406
+
407
+ const fieldValidationSchema = Joi.object({
408
+ unique: Joi.boolean(),
409
+ validate: Joi.alternatives().try(Joi.function(), Joi.array().items(Joi.function())),
410
+
411
+ // string and text
412
+ regexp: Joi.string(),
413
+ regexpNot: Joi.string(),
414
+ regexpPattern: Joi.string(),
415
+
416
+ // ranges
417
+ min: stringOrNumber,
418
+ max: stringOrNumber,
419
+ after: Joi.string(),
420
+ before: Joi.string(),
421
+ exact: Joi.number(),
422
+ step: Joi.number(),
423
+ lessThan: Joi.number(),
424
+ greaterThan: Joi.number(),
425
+
426
+ // file
427
+ fileMinSize: Joi.number(),
428
+ fileMaxSize: Joi.number(),
429
+ fileTypes: arrayOfStrings,
430
+ fileTypeGroups: arrayOfStrings,
431
+
432
+ // image
433
+ minWidth: Joi.number(),
434
+ maxWidth: Joi.number(),
435
+ minHeight: Joi.number(),
436
+ maxHeight: Joi.number(),
437
+ minWidthToHeightRatio: stringOrNumber,
438
+ maxWidthToHeightRatio: stringOrNumber,
439
+
440
+ errors: Joi.object({
441
+ unique: Joi.string(),
442
+ regexp: Joi.string(),
443
+ regexpNot: Joi.string(),
444
+ regexpPattern: Joi.string(),
445
+ min: Joi.string(),
446
+ max: Joi.string(),
447
+ after: Joi.string(),
448
+ before: Joi.string(),
449
+ exact: Joi.string(),
450
+ step: Joi.string(),
451
+ lessThan: Joi.string(),
452
+ greaterThan: Joi.string(),
453
+ fileMinSize: Joi.string(),
454
+ fileMaxSize: Joi.string(),
455
+ fileTypes: Joi.string(),
456
+ fileTypeGroups: Joi.string(),
457
+ minWidth: Joi.string(),
458
+ maxWidth: Joi.string(),
459
+ minHeight: Joi.string(),
460
+ maxHeight: Joi.string(),
461
+ minWidthToHeightRatio: Joi.string(),
462
+ maxWidthToHeightRatio: Joi.string()
463
+ })
464
+ });
465
+
403
466
  const customControlTypesSchema = Joi.string().valid('custom-modal-html', 'custom-inline-html', 'custom-modal-script', 'custom-inline-script');
404
467
  const fieldCommonPropsSchema = Joi.object({
405
468
  type: Joi.string()
@@ -409,6 +472,7 @@ const fieldCommonPropsSchema = Joi.object({
409
472
  label: Joi.string(),
410
473
  description: Joi.string().allow(''),
411
474
  required: Joi.boolean(),
475
+ validations: fieldValidationSchema,
412
476
  default: Joi.any(),
413
477
  group: inGroups,
414
478
  const: Joi.any(),
@@ -474,7 +538,7 @@ const enumFieldPartialSchema = Joi.object({
474
538
  is: 'palette-colors',
475
539
  then: Joi.array().items(
476
540
  enumFieldBaseOptionSchema.append({
477
- colors: Joi.array().items(Joi.string()).required()
541
+ colors: arrayOfStrings.required()
478
542
  })
479
543
  )
480
544
  }
@@ -589,8 +653,8 @@ const contentModelSchema = Joi.object<ContentModel>({
589
653
  singleInstance: Joi.boolean(),
590
654
  file: Joi.string(),
591
655
  folder: Joi.string(),
592
- match: Joi.array().items(Joi.string()).single(),
593
- exclude: Joi.array().items(Joi.string()).single()
656
+ match: arrayOfStrings.single(),
657
+ exclude: arrayOfStrings.single()
594
658
  })
595
659
  .without('file', ['folder', 'match', 'exclude'])
596
660
  .when('.isPage', {
@@ -667,7 +731,7 @@ const baseModelSchema = {
667
731
  readOnly: Joi.boolean(),
668
732
  labelField: labelFieldSchema,
669
733
  variantField: variantFieldSchema,
670
- groups: Joi.array().items(Joi.string()),
734
+ groups: arrayOfStrings,
671
735
  context: Joi.any(),
672
736
  preview: Joi.alternatives().try(objectPreviewSchema, Joi.function()),
673
737
  fieldGroups: fieldGroupsSchema,
@@ -695,8 +759,8 @@ const dataModelSchema: Joi.ObjectSchema<DataModel & { srcType: string; srcProjec
695
759
  singleInstance: Joi.boolean(),
696
760
  file: Joi.string(),
697
761
  folder: Joi.string(),
698
- match: Joi.array().items(Joi.string()).single(),
699
- exclude: Joi.array().items(Joi.string()).single(),
762
+ match: arrayOfStrings.single(),
763
+ exclude: arrayOfStrings.single(),
700
764
  isList: Joi.boolean()
701
765
  })
702
766
  .when('.isList', {
@@ -733,8 +797,8 @@ const pageModelSchema: Joi.ObjectSchema<PageModel & { srcType: string; srcProjec
733
797
  singleInstance: Joi.boolean(),
734
798
  file: Joi.string(),
735
799
  folder: Joi.string(),
736
- match: Joi.array().items(Joi.string()).single(),
737
- exclude: Joi.array().items(Joi.string()).single(),
800
+ match: arrayOfStrings.single(),
801
+ exclude: arrayOfStrings.single(),
738
802
  hideContent: Joi.boolean()
739
803
  })
740
804
  .when('.file', {
@@ -983,9 +1047,9 @@ export const stackbitConfigBaseSchema = Joi.object<StackbitConfigWithPaths>({
983
1047
  dataDir: Joi.string().allow('', null),
984
1048
  pageLayoutKey: Joi.string().allow(null),
985
1049
  objectTypeKey: Joi.string(),
986
- excludePages: Joi.array().items(Joi.string()).single(),
1050
+ excludePages: arrayOfStrings.single(),
987
1051
  styleObjectModelName: Joi.string(),
988
- logicFields: Joi.array().items(Joi.string()),
1052
+ logicFields: arrayOfStrings,
989
1053
  contentModels: Joi.any(), // contentModels should have been already validated by now
990
1054
  presetSource: presetSourceSchema,
991
1055
  modelsSource: modelsSourceSchema,
@@ -995,11 +1059,11 @@ export const stackbitConfigBaseSchema = Joi.object<StackbitConfigWithPaths>({
995
1059
  models: Joi.any(),
996
1060
  modelExtensions: Joi.array().items(Joi.any()),
997
1061
  presetReferenceBehavior: Joi.string().valid('copyReference', 'duplicateContents'),
998
- nonDuplicatableModels: Joi.array().items(Joi.string()).when('presetReferenceBehavior', {
1062
+ nonDuplicatableModels: arrayOfStrings.when('presetReferenceBehavior', {
999
1063
  is: 'copyReference',
1000
1064
  then: Joi.forbidden()
1001
1065
  }),
1002
- duplicatableModels: Joi.array().items(Joi.string()).when('presetReferenceBehavior', {
1066
+ duplicatableModels: arrayOfStrings.when('presetReferenceBehavior', {
1003
1067
  is: 'duplicateContents',
1004
1068
  then: Joi.forbidden()
1005
1069
  }),