@relyio/cli 0.4.0 → 0.5.0

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.
Files changed (2) hide show
  1. package/dist/index.js +265 -1203
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -3687,8 +3687,8 @@ function isCommonProtocol(protocol) {
3687
3687
  return commonProtocols.includes(protocol.toLowerCase());
3688
3688
  }
3689
3689
  var QUERY_ENDPOINT_SEGMENT, QUERY_ENDPOINT_PATTERN, PARAMETER_EXPRESSION_SOURCE, parameterExpressionMatcher, ANCHORED_PARAMETER_EXPRESSION, parseDefault, parseParameterExpression, collectParameterExpressions, endpointPathParameters, ParameterExpressionStringSchema, QUERY_PARAMETER_TYPES, QueryParameterTypeSchema, QueryParameterScalarSchema, QueryParameterSchema, typeOfDefault, declareQueryParameters, SEARCH_SCORE_SORT_KEYS, isSearchScoreSortKey, QUERY_SYSTEM_FIELDS, isQuerySystemField, QUERY_STORAGE_PATH_PREFIX, QUERY_SYSTEM_ROOTS, SEARCH_SELECT_COLUMNS, isSearchSelectColumn, parseDynamicParameter, ParameterizedLimitSchema, ParameterizedOffsetSchema, ScalarSchema, WhereOperatorObjectSchema, FilterOperatorObjectSchema, HavingOperatorObjectSchema, WhereValueSchema, WhereConditionSchema, SortConditionSchema, EnhancedIncludeObjectSchema, IncludeDefinitionSchema, SelectMappingSchema, FilterDefinitionSchema, FullTextSearchSchema, ContentTypesSchema, SearchBlockSchema, AggregationMetricSchema, QueryCardinalitySchema, QueryCachePolicySchema, FetchBlockSchema, AggregateBlockSchema, IncludeSubQueriesSchema, QueryDefinitionSchema, CACHE_POLICY_STRING, normalizeQueryCachePolicy, CONTENT_API_ROUTE_SEGMENTS, RESERVED_TYPE_IDS, getComponentIdFromType, ComponentSchema, ContentTypeSchema, SettingsGroupSchema, SchemaDefinitionSchema;
3690
- var init_chunk_L6HCY63G = __esm({
3691
- "../../domain/schema/dist/chunk-L6HCY63G.js"() {
3690
+ var init_chunk_LKXAZEW4 = __esm({
3691
+ "../../domain/schema/dist/chunk-LKXAZEW4.js"() {
3692
3692
  init_chunk_CE6CNF7I();
3693
3693
  init_chunk_QG7U6KYM();
3694
3694
  init_chunk_W6RFZ2TV();
@@ -5187,6 +5187,9 @@ function classifyCompatibility(diff2) {
5187
5187
  }
5188
5188
  return { level, safeToMerge, additionsOnly, breakingChanges };
5189
5189
  }
5190
+ function isCrossVersionCheckCode(code) {
5191
+ return Object.hasOwn(CROSS_VERSION_CHECKS, code);
5192
+ }
5190
5193
  function isConditionObject(value) {
5191
5194
  return typeof value === "object" && value !== null && !Array.isArray(value);
5192
5195
  }
@@ -5339,10 +5342,10 @@ function notComparedWarning(part, refusals) {
5339
5342
  suggestion: "Nothing to change for it: this deploy replaces the deployed definition, and the next deploy is compared against all of it"
5340
5343
  };
5341
5344
  }
5342
- var USAGE_CONTAINERS, BaseValidator, ValidationUtils, NamingValidator, StructuralValidator, FieldConfigValidator, CrossReferenceValidator, ComponentValidator, ComponentFieldTypeValidator, ComponentValidationUtils, ComponentReusabilityValidator, ComponentFieldStructureValidator, ComponentUsageContextValidator, ComponentNestingValidator, FieldsetValidator, CONTENT_TYPE_PROPS, COMPONENT_PROPS, GROUP_PROPS, QUERY_BASIC_PROPS, QUERY_OPTIONAL_PROPS, QUERY_DSL_BLOCKS, LOCALE_PROPS, ROLE_PROPS, WORKFLOW_STAGE_PROPS, VIEW_PROPS, FIELDSET_PROPS, ASSET_TYPE_PROPS, ASSET_KIND_BLOCKS, ASSET_KIND_KEY_MARKER, FILE_LOCALIZATION_MARKER, FIELD_REQUIRED_CHANGE_KEY, SchemaDiffer, ADDED_LABEL, CROSS_VERSION_CHECKS, EvolutionValidator, HierarchicalContentValidator, FieldRulesValidator, LocalizationValidator, LocaleConfigValidator, SettingsGroupsValidator, SingletonContentTypesValidator, FORBIDDEN_TYPES, AssetTypesValidator, FieldUIValidator, BENEATH, ANY_JOIN, DslQueryValidator, DataIntegrityValidator, RolesValidator, EVOLUTION_VALIDATOR_KEY, CROSS_VERSION_CHECKS_SKIPPED, ValidationPipeline, SchemaValidator, FIELD_SECTIONS, OPERATION_TIER, DEPLOY_VALIDATION_CONFIG;
5345
+ var USAGE_CONTAINERS, BaseValidator, ValidationUtils, NamingValidator, StructuralValidator, FieldConfigValidator, CrossReferenceValidator, ComponentValidator, ComponentFieldTypeValidator, ComponentValidationUtils, ComponentReusabilityValidator, ComponentFieldStructureValidator, ComponentUsageContextValidator, ComponentNestingValidator, FieldsetValidator, CONTENT_TYPE_PROPS, COMPONENT_PROPS, GROUP_PROPS, QUERY_BASIC_PROPS, QUERY_OPTIONAL_PROPS, QUERY_DSL_BLOCKS, LOCALE_PROPS, ROLE_PROPS, WORKFLOW_STAGE_PROPS, VIEW_PROPS, FIELDSET_PROPS, ASSET_TYPE_PROPS, ASSET_KIND_BLOCKS, ASSET_KIND_KEY_MARKER, FILE_LOCALIZATION_MARKER, FIELD_REQUIRED_CHANGE_KEY, SchemaDiffer, ADDED_LABEL, CROSS_VERSION_CHECKS, CROSS_VERSION_CODES, EvolutionValidator, HierarchicalContentValidator, FieldRulesValidator, LocalizationValidator, LocaleConfigValidator, SettingsGroupsValidator, SingletonContentTypesValidator, FORBIDDEN_TYPES, AssetTypesValidator, FieldUIValidator, BENEATH, ANY_JOIN, DslQueryValidator, DataIntegrityValidator, RolesValidator, EVOLUTION_VALIDATOR_KEY, CROSS_VERSION_CHECKS_SKIPPED, ValidationPipeline, SchemaValidator, FIELD_SECTIONS, OPERATION_TIER, DEPLOY_VALIDATION_CONFIG;
5343
5346
  var init_dist2 = __esm({
5344
5347
  "../../domain/schema/dist/index.js"() {
5345
- init_chunk_L6HCY63G();
5348
+ init_chunk_LKXAZEW4();
5346
5349
  init_chunk_EGQAMYAG();
5347
5350
  init_chunk_LDBRHF5K();
5348
5351
  init_chunk_GICJZDXP();
@@ -5651,32 +5654,7 @@ var init_dist2 = __esm({
5651
5654
  this.checkNamingConventions(name, type, path25, result);
5652
5655
  }
5653
5656
  validateFieldName(field, path25, result, reservedKeywords) {
5654
- if (!field.id) {
5655
- result.errors.push(
5656
- this.createError(
5657
- "MISSING_FIELD_ID",
5658
- "Field is missing required id property",
5659
- path25,
5660
- "error",
5661
- "Add an id property to the field",
5662
- true
5663
- )
5664
- );
5665
- return;
5666
- }
5667
5657
  const fieldId = field.id;
5668
- if (!ValidationUtils.isValidName(fieldId)) {
5669
- result.errors.push(
5670
- this.createError(
5671
- "INVALID_FIELD_NAME",
5672
- `Field id '${fieldId}' is invalid. Must start with a letter and contain only letters, numbers, and underscores`,
5673
- `${path25}.id`,
5674
- "error",
5675
- 'Use a valid identifier format (e.g., "myField", "my_field")',
5676
- true
5677
- )
5678
- );
5679
- }
5680
5658
  if (reservedKeywords.includes(fieldId.toLowerCase())) {
5681
5659
  const suggestion = ValidationUtils.findBestMatch(fieldId, [
5682
5660
  "my" + fieldId,
@@ -5760,31 +5738,13 @@ var init_dist2 = __esm({
5760
5738
  feature = "structural";
5761
5739
  async validate(index) {
5762
5740
  const result = this.createResult();
5763
- try {
5764
- SchemaDefinitionSchema.parse(index.schema);
5765
- this.validateContentTypeStructure(index, result);
5766
- this.validateComponentStructure(index, result);
5767
- this.validateQueryStructure(index, result);
5768
- this.validateSettingsStructure(index, result);
5769
- } catch (error2) {
5770
- if (error2 instanceof z.ZodError) {
5771
- this.convertZodErrors(error2, result);
5772
- } else {
5773
- result.errors.push(
5774
- this.createError(
5775
- "STRUCTURAL_VALIDATION_FAILED",
5776
- "Schema structural validation failed",
5777
- "root",
5778
- "error",
5779
- "Check schema format and required properties"
5780
- )
5781
- );
5782
- }
5783
- }
5741
+ this.validateContentTypeStructure(index, result);
5742
+ this.validateComponentStructure(index, result);
5743
+ this.validateSettingsStructure(index, result);
5784
5744
  return result;
5785
5745
  }
5786
5746
  validateContentTypeStructure(index, result) {
5787
- if (!index.schema.contentTypes || index.contentTypes.ids.length === 0) {
5747
+ if (index.contentTypes.ids.length === 0) {
5788
5748
  result.warnings.push(
5789
5749
  this.createError(
5790
5750
  "NO_CONTENT_TYPES",
@@ -5796,17 +5756,6 @@ var init_dist2 = __esm({
5796
5756
  );
5797
5757
  }
5798
5758
  for (const [typeId, contentType] of index.contentTypes.entries) {
5799
- if (!contentType.fields || contentType.fields.length === 0 && !contentType.fieldsets) {
5800
- result.warnings.push(
5801
- this.createError(
5802
- "EMPTY_CONTENT_TYPE",
5803
- `Content type '${typeId}' has no fields`,
5804
- `contentTypes.${typeId}`,
5805
- "warning",
5806
- "Add fields to make this content type useful"
5807
- )
5808
- );
5809
- }
5810
5759
  this.validateFieldsStructure(
5811
5760
  contentType.fields || [],
5812
5761
  `contentTypes.${typeId}.fields`,
@@ -5816,7 +5765,7 @@ var init_dist2 = __esm({
5816
5765
  }
5817
5766
  validateComponentStructure(index, result) {
5818
5767
  for (const [componentId, component] of index.components.entries) {
5819
- if (!component.fields || component.fields.length === 0) {
5768
+ if (component.fields.length === 0) {
5820
5769
  result.errors.push(
5821
5770
  this.createError(
5822
5771
  "EMPTY_COMPONENT",
@@ -5831,147 +5780,23 @@ var init_dist2 = __esm({
5831
5780
  }
5832
5781
  }
5833
5782
  }
5834
- validateQueryStructure(index, result) {
5835
- for (const [queryId, query] of Object.entries(index.schema.queries || {})) {
5836
- if (!query.endpoint) {
5837
- result.errors.push(
5838
- this.createError(
5839
- "MISSING_QUERY_ENDPOINT",
5840
- `Query '${queryId}' is missing endpoint`,
5841
- `queries.${queryId}`,
5842
- "error",
5843
- "All queries must have an endpoint property"
5844
- )
5845
- );
5846
- }
5847
- const blocks = queryBlocksOf(query);
5848
- if (blocks.length === 0) {
5849
- result.errors.push(
5850
- this.createError(
5851
- "MISSING_QUERY_TYPE",
5852
- `Query '${queryId}' must have one of: ${QUERY_BLOCKS_SPELLED}`,
5853
- `queries.${queryId}`,
5854
- "error",
5855
- `Add a ${QUERY_BLOCKS_SPELLED} block to define the query operation`
5856
- )
5857
- );
5858
- }
5859
- if (query.fetch) {
5860
- this.validateFetchStructure(query.fetch, queryId, result);
5861
- }
5862
- if (query.aggregate) {
5863
- this.validateAggregateStructure(query.aggregate, queryId, Boolean(query.fetch), result);
5864
- }
5865
- if (query.search) {
5866
- this.validateSearchStructure(query.search, queryId, result);
5867
- }
5868
- }
5869
- }
5870
- validateFetchStructure(fetch2, queryId, result) {
5871
- const basePath = `queries.${queryId}.fetch`;
5872
- if (!fetch2.contentType && !fetch2.singleton) {
5873
- result.errors.push(
5874
- this.createError(
5875
- "FETCH_MISSING_TARGET",
5876
- `Fetch operation in query '${queryId}' must specify contentType or singleton`,
5877
- basePath,
5878
- "error",
5879
- "Add contentType or singleton property to the fetch operation"
5880
- )
5881
- );
5882
- }
5883
- if (fetch2.contentType && fetch2.singleton) {
5884
- result.errors.push(
5885
- this.createError(
5886
- "FETCH_MULTIPLE_TARGETS",
5887
- `Fetch operation in query '${queryId}' cannot have both contentType and singleton`,
5888
- basePath,
5889
- "error",
5890
- "Use either contentType OR singleton, not both"
5891
- )
5892
- );
5893
- }
5894
- }
5895
- validateAggregateStructure(aggregate, queryId, hasFetch, result) {
5896
- const basePath = `queries.${queryId}.aggregate`;
5897
- if (!hasFetch && !aggregate.source) {
5898
- result.errors.push(
5899
- this.createError(
5900
- "AGGREGATE_MISSING_SOURCE",
5901
- `Aggregate operation in query '${queryId}' must specify source content type when not used with fetch`,
5902
- `${basePath}.source`,
5903
- "error",
5904
- "Add source property to specify which content type to aggregate, or use with a fetch block"
5905
- )
5906
- );
5907
- }
5908
- }
5909
- validateSearchStructure(search, queryId, result) {
5910
- const basePath = `queries.${queryId}.search`;
5911
- if (!search.contentType && !search.contentTypes) {
5912
- result.errors.push(
5913
- this.createError(
5914
- "SEARCH_MISSING_CONTENT_TYPE",
5915
- `Search operation in query '${queryId}' must specify contentType or contentTypes`,
5916
- basePath,
5917
- "error",
5918
- "Add contentType for single type search, or contentTypes for multi-type/global search"
5919
- )
5920
- );
5921
- }
5922
- if (!search.query) {
5923
- result.errors.push(
5924
- this.createError(
5925
- "SEARCH_MISSING_QUERY",
5926
- `Search operation in query '${queryId}' must specify a search query`,
5927
- `${basePath}.query`,
5928
- "error",
5929
- 'Add query property with search string or parameter like "${q}"'
5930
- )
5931
- );
5932
- }
5933
- }
5934
5783
  validateSettingsStructure(index, result) {
5935
5784
  for (const [settingsId, settingsGroup] of index.settingsGroups.entries) {
5936
- if (!settingsGroup.fields || settingsGroup.fields.length === 0) {
5937
- result.warnings.push(
5938
- this.createError(
5939
- "EMPTY_SETTINGS_GROUP",
5940
- `Settings group '${settingsId}' has no fields`,
5941
- `settingsGroups.${settingsId}`,
5942
- "warning",
5943
- "Settings groups should have configuration fields"
5944
- )
5945
- );
5946
- } else {
5947
- this.validateFieldsStructure(
5948
- settingsGroup.fields,
5949
- `settingsGroups.${settingsId}.fields`,
5950
- result
5951
- );
5952
- }
5785
+ this.validateFieldsStructure(
5786
+ settingsGroup.fields,
5787
+ `settingsGroups.${settingsId}.fields`,
5788
+ result
5789
+ );
5953
5790
  }
5954
5791
  }
5955
5792
  validateFieldsStructure(fields, basePath, result) {
5956
- for (const [index, field] of fields.entries()) {
5957
- const fieldIdentifier = field.id || `[${index}]`;
5958
- const fieldPath = `${basePath}.${fieldIdentifier}`;
5959
- if (!field.id) {
5960
- result.errors.push(
5961
- this.createError(
5962
- "MISSING_FIELD_ID",
5963
- `Field at index ${index} is missing id`,
5964
- fieldPath,
5965
- "error",
5966
- "All fields must have an id property"
5967
- )
5968
- );
5969
- }
5793
+ for (const field of fields) {
5794
+ const fieldPath = `${basePath}.${field.id}`;
5970
5795
  if (!field.type) {
5971
5796
  result.errors.push(
5972
5797
  this.createError(
5973
5798
  "MISSING_FIELD_TYPE",
5974
- `Field '${field.id || `at index ${index}`}' is missing type`,
5799
+ `Field '${field.id}' is missing type`,
5975
5800
  fieldPath,
5976
5801
  "error",
5977
5802
  "All fields must have a type property"
@@ -6044,53 +5869,6 @@ var init_dist2 = __esm({
6044
5869
  }
6045
5870
  }
6046
5871
  }
6047
- convertZodErrors(zodError, result) {
6048
- for (const error2 of zodError.errors) {
6049
- const path25 = error2.path.join(".");
6050
- let message = error2.message;
6051
- let suggestion;
6052
- switch (error2.code) {
6053
- case "invalid_enum_value": {
6054
- const options = "options" in error2 ? error2.options : [];
6055
- message = `Invalid value. Expected one of: ${options.join(", ")}`;
6056
- suggestion = `Use one of the valid options: ${options.join(", ")}`;
6057
- break;
6058
- }
6059
- case "invalid_type": {
6060
- const expected = error2.expected;
6061
- const received = error2.received;
6062
- message = `Expected ${expected}, received ${received}`;
6063
- suggestion = `Change the value to be of type ${expected}`;
6064
- break;
6065
- }
6066
- case "too_small": {
6067
- const minimum = error2.minimum;
6068
- message = `Value is too small. Minimum: ${minimum}`;
6069
- suggestion = `Increase the value to at least ${minimum}`;
6070
- break;
6071
- }
6072
- case "too_big": {
6073
- const maximum = error2.maximum;
6074
- message = `Value is too large. Maximum: ${maximum}`;
6075
- suggestion = `Decrease the value to at most ${maximum}`;
6076
- break;
6077
- }
6078
- case "invalid_string": {
6079
- if (error2.validation === "email") {
6080
- message = "Invalid email format";
6081
- suggestion = "Provide a valid email address";
6082
- } else if (error2.validation === "url") {
6083
- message = "Invalid URL format";
6084
- suggestion = "Provide a valid URL starting with http:// or https://";
6085
- }
6086
- break;
6087
- }
6088
- }
6089
- result.errors.push(
6090
- this.createError("SCHEMA_VALIDATION_ERROR", message, path25 || "root", "error", suggestion)
6091
- );
6092
- }
6093
- }
6094
5872
  };
6095
5873
  FieldConfigValidator = class extends BaseValidator {
6096
5874
  feature = "fieldConfigurations";
@@ -6888,20 +6666,7 @@ var init_dist2 = __esm({
6888
6666
  }
6889
6667
  }
6890
6668
  } else {
6891
- if (contentTypeFields.has(field.id)) {
6892
- result.errors.push(
6893
- this.createError(
6894
- "DUPLICATE_FIELD_NAME",
6895
- `Duplicate field name '${field.id}' in content type '${contentTypeId}'`,
6896
- `contentTypes.${contentTypeId}.fields.${field.id}`,
6897
- "error",
6898
- "Field names must be unique within a content type",
6899
- true
6900
- )
6901
- );
6902
- } else {
6903
- contentTypeFields.set(field.id, field.type);
6904
- }
6669
+ contentTypeFields.set(field.id, field.type);
6905
6670
  }
6906
6671
  }
6907
6672
  }
@@ -6925,20 +6690,7 @@ var init_dist2 = __esm({
6925
6690
  for (const [componentId, component] of index.components.entries) {
6926
6691
  const fieldTypes = /* @__PURE__ */ new Map();
6927
6692
  if (component.fields)
6928
- for (const [index2, field] of component.fields.entries()) {
6929
- if (!field.id) {
6930
- result.errors.push(
6931
- this.createError(
6932
- "COMPONENT_FIELD_MISSING_ID",
6933
- `Field at index ${index2} in component '${componentId}' is missing id`,
6934
- `components.${componentId}.fields[${index2}]`,
6935
- "error",
6936
- "All component fields must have unique identifiers",
6937
- true
6938
- )
6939
- );
6940
- continue;
6941
- }
6693
+ for (const field of component.fields) {
6942
6694
  if (!field.type) {
6943
6695
  result.errors.push(
6944
6696
  this.createError(
@@ -6985,7 +6737,7 @@ var init_dist2 = __esm({
6985
6737
  }
6986
6738
  for (const [index2, field] of fields.entries()) {
6987
6739
  const fieldPath = `components.${componentId}.fields.${field.id || `[${index2}]`}`;
6988
- if (!field.id || !field.type) {
6740
+ if (!field.type) {
6989
6741
  result.errors.push(
6990
6742
  this.createError(
6991
6743
  "INCOMPLETE_FIELD_STRUCTURE",
@@ -7031,17 +6783,6 @@ var init_dist2 = __esm({
7031
6783
  if (component.fields)
7032
6784
  for (const field of component.fields) {
7033
6785
  const fieldPath = `components.${componentId}.fields.${field.id}`;
7034
- if (field.id && field.id.includes(".")) {
7035
- result.warnings.push(
7036
- this.createError(
7037
- "FIELD_NAME_SERIALIZATION_RISK",
7038
- `Field '${field.id}' in component '${componentId}' contains dots which may cause serialization issues`,
7039
- fieldPath,
7040
- "warning",
7041
- "Use underscores or camelCase instead of dots in field names"
7042
- )
7043
- );
7044
- }
7045
6786
  if (field.id && field.id.length > 50) {
7046
6787
  result.warnings.push(
7047
6788
  this.createError(
@@ -7103,22 +6844,6 @@ var init_dist2 = __esm({
7103
6844
  for (const [componentId, component] of index.components.entries) {
7104
6845
  const usagePattern = componentUsagePatterns[componentId];
7105
6846
  if (!usagePattern) continue;
7106
- if (this.isSeoComponent(componentId, component)) {
7107
- for (const contentTypeId of usagePattern.usedInContentTypes) {
7108
- const contentType = index.schema.contentTypes?.[contentTypeId];
7109
- if (contentType && this.isNonPublicContentType(contentType)) {
7110
- result.warnings.push(
7111
- this.createError(
7112
- "SEO_COMPONENT_IN_PRIVATE_CONTENT",
7113
- `SEO component '${componentId}' is used in non-public content type '${contentTypeId}'`,
7114
- `contentTypes.${contentTypeId}`,
7115
- "warning",
7116
- "Consider whether SEO metadata is needed for internal/private content types"
7117
- )
7118
- );
7119
- }
7120
- }
7121
- }
7122
6847
  if (this.isSystemComponent(componentId, component)) {
7123
6848
  for (const contentTypeId of usagePattern.usedInContentTypes) {
7124
6849
  result.warnings.push(
@@ -7278,20 +7003,6 @@ var init_dist2 = __esm({
7278
7003
  }
7279
7004
  return patterns;
7280
7005
  }
7281
- isSeoComponent(componentId, component) {
7282
- const seoKeywords = ["seo", "meta", "og", "twitter", "social"];
7283
- const componentName = componentId.toLowerCase();
7284
- return seoKeywords.some((keyword) => componentName.includes(keyword)) || (component.fields || []).some(
7285
- (field) => field.id && seoKeywords.some((keyword) => field.id.toLowerCase().includes(keyword))
7286
- );
7287
- }
7288
- isNonPublicContentType(contentType) {
7289
- const internalKeywords = ["admin", "system", "internal", "private", "draft"];
7290
- const ct = contentType;
7291
- const typeName = ct.id?.toLowerCase() || "";
7292
- const visibility = ct.metadata?.visibility;
7293
- return internalKeywords.some((keyword) => typeName.includes(keyword)) || visibility === "private";
7294
- }
7295
7006
  isSystemComponent(componentId, _component) {
7296
7007
  const systemKeywords = ["system", "admin", "internal", "config"];
7297
7008
  return systemKeywords.some((keyword) => componentId.toLowerCase().includes(keyword));
@@ -7305,15 +7016,12 @@ var init_dist2 = __esm({
7305
7016
  );
7306
7017
  return componentField ? {
7307
7018
  fieldName: componentField.id,
7308
- hasRules: (componentField.rules || []).length > 0,
7309
- isRequired: componentField.required || false
7019
+ hasRules: (componentField.rules || []).length > 0
7310
7020
  } : null;
7311
7021
  }).filter(Boolean);
7312
7022
  if (usagePatterns.length < 2) return false;
7313
7023
  const firstPattern = usagePatterns[0];
7314
- return usagePatterns.some(
7315
- (pattern) => pattern && (pattern.hasRules !== firstPattern?.hasRules || pattern.isRequired !== firstPattern?.isRequired)
7316
- );
7024
+ return usagePatterns.some((pattern) => pattern && pattern.hasRules !== firstPattern?.hasRules);
7317
7025
  }
7318
7026
  };
7319
7027
  ComponentNestingValidator = class extends BaseValidator {
@@ -8641,13 +8349,36 @@ var init_dist2 = __esm({
8641
8349
  settingsGroups: "settings groups"
8642
8350
  };
8643
8351
  CROSS_VERSION_CHECKS = {
8644
- FIELD_TYPE_CHANGE_FORBIDDEN: "a field's type change",
8645
- REFERENCE_TARGET_REMOVED: "a removed reference target",
8646
- MISSING_DEPRECATION_MARKER: "a replacement field beside an undeprecated one"
8352
+ FIELD_TYPE_CHANGE_FORBIDDEN: {
8353
+ names: "a field's type change",
8354
+ severity: "error",
8355
+ fixable: false
8356
+ },
8357
+ REFERENCE_TARGET_REMOVED: {
8358
+ names: "a removed reference target",
8359
+ severity: "error",
8360
+ fixable: false
8361
+ },
8362
+ MISSING_DEPRECATION_MARKER: {
8363
+ names: "a replacement field beside an undeprecated one",
8364
+ severity: "warning",
8365
+ fixable: true
8366
+ }
8647
8367
  };
8368
+ CROSS_VERSION_CODES = Object.keys(CROSS_VERSION_CHECKS);
8648
8369
  EvolutionValidator = class extends BaseValidator {
8649
8370
  feature = "schemaEvolution";
8650
8371
  schemaDiffer = new SchemaDiffer();
8372
+ /**
8373
+ * Each cross-version check's finder, under the code {@link CROSS_VERSION_CHECKS} reports it by. A
8374
+ * code the table gains is a missing property here until it has a finder, and a finder under a code
8375
+ * the table does not name is an excess one.
8376
+ */
8377
+ crossVersionFinders = {
8378
+ FIELD_TYPE_CHANGE_FORBIDDEN: (comparison) => this.fieldTypeChanges(comparison),
8379
+ REFERENCE_TARGET_REMOVED: (comparison) => this.removedReferenceTargets(comparison),
8380
+ MISSING_DEPRECATION_MARKER: (comparison) => this.undeprecatedReplacements(comparison)
8381
+ };
8651
8382
  async validate(index) {
8652
8383
  const result = this.createResult();
8653
8384
  this.validateEvolutionReadiness(index, result);
@@ -8660,13 +8391,22 @@ var init_dist2 = __esm({
8660
8391
  const result = this.createResult();
8661
8392
  const newIndex = buildSchemaIndex(newSchema);
8662
8393
  this.validateEvolutionReadiness(newIndex, result);
8663
- this.validateFieldTypeImmutability(oldSchema, newSchema, result);
8664
- this.validateDeprecationWorkflow(oldSchema, newSchema, result);
8665
- this.validateReferenceTargetRemovals(oldSchema, newIndex, result);
8394
+ const comparison = {
8395
+ previous: oldSchema,
8396
+ next: newIndex,
8397
+ diff: this.schemaDiffer.computeDiff(oldSchema, newSchema)
8398
+ };
8399
+ for (const code of CROSS_VERSION_CODES) {
8400
+ const { severity, fixable } = CROSS_VERSION_CHECKS[code];
8401
+ const reported = severity === "error" ? result.errors : result.warnings;
8402
+ for (const { message, path: path25, suggestion } of this.crossVersionFinders[code](comparison)) {
8403
+ reported.push(this.createError(code, message, path25, severity, suggestion, fixable));
8404
+ }
8405
+ }
8666
8406
  return result;
8667
8407
  }
8668
8408
  /**
8669
- * Flags reference fields that still target a content/asset type removed in
8409
+ * Reference fields that still target a content/asset type removed in
8670
8410
  * this change. `DataIntegrityValidator` already errors on a dangling target
8671
8411
  * (REFERENCE_TARGET_NOT_FOUND) when it validates the new schema in isolation;
8672
8412
  * this diff-aware pass upgrades the same situation to an attributed breaking
@@ -8676,99 +8416,81 @@ var init_dist2 = __esm({
8676
8416
  * Reference fields inside asset types are covered too — {@link BaseValidator.traverseFields}
8677
8417
  * walks asset type fields by default.
8678
8418
  */
8679
- validateReferenceTargetRemovals(oldSchema, newIndex, result) {
8680
- const diff2 = this.schemaDiffer.computeDiff(oldSchema, newIndex.schema);
8419
+ removedReferenceTargets({ next, diff: diff2 }) {
8681
8420
  const removedTargets = /* @__PURE__ */ new Map();
8682
8421
  for (const id of diff2.removed.contentTypes) removedTargets.set(id, "content");
8683
8422
  for (const id of diff2.removed.singletonContentTypes) removedTargets.set(id, "content");
8684
8423
  for (const id of diff2.removed.assetTypes) removedTargets.set(id, "asset");
8685
- if (removedTargets.size === 0) return;
8686
- this.traverseFields(newIndex, (field, path25) => {
8424
+ const findings = [];
8425
+ if (removedTargets.size === 0) return findings;
8426
+ this.traverseFields(next, (field, path25) => {
8687
8427
  if (field.type !== "reference") return;
8688
8428
  const targets = normalizeReferenceTargets(field.config?.to);
8689
8429
  for (const [index, targetId] of targets.entries()) {
8690
8430
  const family = removedTargets.get(targetId);
8691
8431
  if (!family) continue;
8692
- result.errors.push(
8693
- this.createError(
8694
- "REFERENCE_TARGET_REMOVED",
8695
- `Reference field '${field.id}' targets ${family} type '${targetId}', which was removed in this change`,
8696
- `${path25}.config.to[${index}]`,
8697
- "error",
8698
- `Restore the ${family} type '${targetId}', or remove/repoint the reference field '${field.id}' before deploying`,
8699
- false
8700
- )
8701
- );
8432
+ findings.push({
8433
+ message: `Reference field '${field.id}' targets ${family} type '${targetId}', which was removed in this change`,
8434
+ path: `${path25}.config.to[${index}]`,
8435
+ suggestion: `Restore the ${family} type '${targetId}', or remove/repoint the reference field '${field.id}' before deploying`
8436
+ });
8702
8437
  }
8703
8438
  });
8439
+ return findings;
8704
8440
  }
8705
8441
  /**
8706
- * Enforces the field type immutability rule
8442
+ * The field type immutability rule: a field keeps the type it was deployed with.
8707
8443
  */
8708
- validateFieldTypeImmutability(oldSchema, newSchema, result) {
8709
- const oldFieldTypes = this.buildFieldTypeHistory(oldSchema);
8710
- const newFieldTypes = this.buildFieldTypeHistory(newSchema);
8444
+ fieldTypeChanges({ previous, next }) {
8445
+ const oldFieldTypes = this.buildFieldTypeHistory(previous);
8446
+ const newFieldTypes = this.buildFieldTypeHistory(next.schema);
8447
+ const findings = [];
8711
8448
  for (const [fieldKey, oldField] of oldFieldTypes.entries()) {
8712
8449
  const newField = newFieldTypes.get(fieldKey);
8713
8450
  if (newField && oldField.fieldType !== newField.fieldType) {
8714
8451
  const oldType = this.normalizeFieldType(oldField.fieldType);
8715
8452
  const newType = this.normalizeFieldType(newField.fieldType);
8716
- result.errors.push(
8717
- this.createError(
8718
- "FIELD_TYPE_CHANGE_FORBIDDEN",
8719
- `Field type cannot be changed after deployment: "${oldField.fieldId}" (${oldType} \u2192 ${newType})`,
8720
- newField.path,
8721
- "error",
8722
- `Use deprecation workflow:
8453
+ findings.push({
8454
+ message: `Field type cannot be changed after deployment: "${oldField.fieldId}" (${oldType} \u2192 ${newType})`,
8455
+ path: newField.path,
8456
+ suggestion: `Use deprecation workflow:
8723
8457
  \u250C\u2500 Correct approach
8724
8458
  \u2502 fields:
8725
8459
  \u2502 \u251C\u2500 ${oldField.fieldId}:
8726
8460
  \u2502 \u2502 type: "${oldField.fieldType}"
8727
8461
  \u2502 \u2502 deprecated: true # Mark old field as deprecated
8728
8462
  \u2502 \u2514\u2500 ${oldField.fieldId}_v2:
8729
- \u2502 type: "${newField.fieldType}" # Create new field with correct type`,
8730
- false
8731
- )
8732
- );
8463
+ \u2502 type: "${newField.fieldType}" # Create new field with correct type`
8464
+ });
8733
8465
  }
8734
8466
  }
8467
+ return findings;
8735
8468
  }
8736
8469
  /**
8737
- * Validates proper deprecation workflow when introducing new fields
8470
+ * A field added beside one it appears to replace, which is not marked deprecated.
8738
8471
  */
8739
- validateDeprecationWorkflow(oldSchema, newSchema, result) {
8740
- const diff2 = this.schemaDiffer.computeDiff(oldSchema, newSchema);
8472
+ undeprecatedReplacements({ previous, next, diff: diff2 }) {
8473
+ const findings = [];
8741
8474
  for (const contentTypeChange of diff2.modified.contentTypes) {
8742
8475
  for (const addedFieldId of contentTypeChange.changes.addedFields) {
8743
8476
  const potentialOriginalField = this.findPotentialOriginalField(
8744
8477
  addedFieldId,
8745
- oldSchema,
8478
+ previous,
8746
8479
  contentTypeChange.name
8747
8480
  );
8748
- if (potentialOriginalField) {
8749
- const isOriginalDeprecated = this.isFieldDeprecated(
8750
- potentialOriginalField,
8751
- newSchema,
8752
- contentTypeChange.name
8753
- );
8754
- if (!isOriginalDeprecated) {
8755
- result.warnings.push(
8756
- this.createError(
8757
- "MISSING_DEPRECATION_MARKER",
8758
- `Field '${addedFieldId}' appears to be a replacement for '${potentialOriginalField}'. Consider marking the original field as deprecated.`,
8759
- `contentTypes.${contentTypeChange.name}.fields.${potentialOriginalField}`,
8760
- "warning",
8761
- `Mark the original field as deprecated:
8481
+ if (potentialOriginalField && !this.isFieldDeprecated(potentialOriginalField, next.schema, contentTypeChange.name)) {
8482
+ findings.push({
8483
+ message: `Field '${addedFieldId}' appears to be a replacement for '${potentialOriginalField}'. Consider marking the original field as deprecated.`,
8484
+ path: `contentTypes.${contentTypeChange.name}.fields.${potentialOriginalField}`,
8485
+ suggestion: `Mark the original field as deprecated:
8762
8486
  - id: "${potentialOriginalField}"
8763
8487
  type: "..."
8764
- deprecated: true`,
8765
- true
8766
- )
8767
- );
8768
- }
8488
+ deprecated: true`
8489
+ });
8769
8490
  }
8770
8491
  }
8771
8492
  }
8493
+ return findings;
8772
8494
  }
8773
8495
  /**
8774
8496
  * Builds a map of field types from a schema for comparison
@@ -9013,9 +8735,7 @@ var init_dist2 = __esm({
9013
8735
  const safeChanges = [];
9014
8736
  const evolutionResult = await this.validateSchemaEvolution(oldSchema, newSchema);
9015
8737
  for (const error2 of evolutionResult.errors) {
9016
- if (error2.code === "FIELD_TYPE_CHANGE_FORBIDDEN" || error2.code === "REFERENCE_TARGET_REMOVED") {
9017
- breakingChanges.push(error2.message);
9018
- }
8738
+ if (isCrossVersionCheckCode(error2.code)) breakingChanges.push(error2.message);
9019
8739
  }
9020
8740
  const diff2 = this.schemaDiffer.computeDiff(oldSchema, newSchema);
9021
8741
  const compatibility = classifyCompatibility(diff2);
@@ -9226,19 +8946,6 @@ var init_dist2 = __esm({
9226
8946
  return fieldIds;
9227
8947
  }
9228
8948
  validateFieldRules(rules, fieldId, fieldPath, result, allFieldIds, _context) {
9229
- if (!Array.isArray(rules)) {
9230
- result.errors.push(
9231
- this.createError(
9232
- "INVALID_RULES_FORMAT",
9233
- "Field rules must be an array",
9234
- `${fieldPath}.rules`,
9235
- "error",
9236
- "Change rules to an array format: rules: [...]",
9237
- true
9238
- )
9239
- );
9240
- return;
9241
- }
9242
8949
  const ruleTypes = /* @__PURE__ */ new Set();
9243
8950
  for (const [ruleIndex, rule] of rules.entries()) {
9244
8951
  const rulePath = `${fieldPath}.rules[${ruleIndex}]`;
@@ -9261,86 +8968,16 @@ var init_dist2 = __esm({
9261
8968
  this.checkCircularDependencies(rules, fieldId, fieldPath, result, allFieldIds);
9262
8969
  this.validateRuleLogicConsistency(rules, fieldId, fieldPath, result);
9263
8970
  }
8971
+ /**
8972
+ * A rule's shape — its type, its action, its message, a mode, a non-empty list of conditions and
8973
+ * each condition's operator — is the parse's (`FieldRuleSchema`), which refuses every one this
8974
+ * checked; none of those eleven checks could fire (REL-1497, `parse-refuses-first.test.ts`). What
8975
+ * is left is what the parse cannot know: which fields a condition names, and whether its operator
8976
+ * has the value it compares with.
8977
+ */
9264
8978
  validateSingleRule(rule, rulePath, result, allFieldIds, currentFieldId) {
9265
- const validRuleTypes = ["visibility", "requirement", "validation", "readOnly"];
9266
- if (!validRuleTypes.includes(rule.type)) {
9267
- result.errors.push(
9268
- this.createError(
9269
- "INVALID_RULE_TYPE",
9270
- `Invalid rule type '${rule.type}'. Must be one of: ${validRuleTypes.join(", ")}`,
9271
- `${rulePath}.type`,
9272
- "error",
9273
- `Use one of the valid rule types: ${validRuleTypes.join(", ")}`,
9274
- true
9275
- )
9276
- );
9277
- }
9278
- const hasConditions = rule.conditions && rule.conditions.conditions && rule.conditions.conditions.length > 0;
9279
- if (!hasConditions) {
9280
- result.errors.push(
9281
- this.createError(
9282
- "MISSING_RULE_CONDITION",
9283
- "Rule must specify conditions with at least one condition entry",
9284
- rulePath,
9285
- "error",
9286
- "Add a conditions object to define when the rule applies",
9287
- true
9288
- )
9289
- );
9290
- }
9291
- if (hasConditions) {
9292
- this.validateConditions(
9293
- rule.conditions,
9294
- `${rulePath}.conditions`,
9295
- result,
9296
- allFieldIds,
9297
- currentFieldId
9298
- );
9299
- }
9300
- this.validateRuleSpecificProperties(rule, rulePath, result);
9301
- }
9302
- validateConditions(conditions, conditionsPath, result, allFieldIds, currentFieldId) {
9303
- if (!conditions) return;
9304
- const validModes = ["all", "any", "none"];
9305
- if (!validModes.includes(conditions.mode)) {
9306
- result.errors.push(
9307
- this.createError(
9308
- "INVALID_CONDITIONS_MODE",
9309
- `Invalid conditions mode '${conditions.mode}'. Must be one of: ${validModes.join(", ")}`,
9310
- `${conditionsPath}.mode`,
9311
- "error",
9312
- `Use one of: ${validModes.join(", ")}`,
9313
- true
9314
- )
9315
- );
9316
- }
9317
- if (!conditions.conditions || !Array.isArray(conditions.conditions)) {
9318
- result.errors.push(
9319
- this.createError(
9320
- "INVALID_CONDITIONS_ARRAY",
9321
- "Conditions must include a 'conditions' array",
9322
- `${conditionsPath}.conditions`,
9323
- "error",
9324
- "Add a conditions array with at least one condition",
9325
- true
9326
- )
9327
- );
9328
- return;
9329
- }
9330
- if (conditions.conditions.length === 0) {
9331
- result.errors.push(
9332
- this.createError(
9333
- "EMPTY_CONDITIONS_ARRAY",
9334
- "Conditions array cannot be empty",
9335
- `${conditionsPath}.conditions`,
9336
- "error",
9337
- "Add at least one condition to the array",
9338
- true
9339
- )
9340
- );
9341
- return;
9342
- }
9343
- for (const [index, condition] of conditions.conditions.entries()) {
8979
+ const conditionsPath = `${rulePath}.conditions`;
8980
+ for (const [index, condition] of rule.conditions.conditions.entries()) {
9344
8981
  this.validateSingleCondition(
9345
8982
  condition,
9346
8983
  `${conditionsPath}.conditions[${index}]`,
@@ -9387,28 +9024,6 @@ var init_dist2 = __esm({
9387
9024
  )
9388
9025
  );
9389
9026
  }
9390
- const validOperators = [
9391
- "equals",
9392
- "not_equals",
9393
- "contains",
9394
- "not_contains",
9395
- "greater_than",
9396
- "less_than",
9397
- "is_empty",
9398
- "is_not_empty"
9399
- ];
9400
- if (!validOperators.includes(condition.operator)) {
9401
- result.errors.push(
9402
- this.createError(
9403
- "INVALID_CONDITION_OPERATOR",
9404
- `Invalid operator '${condition.operator}'. Must be one of: ${validOperators.join(", ")}`,
9405
- `${conditionPath}.operator`,
9406
- "error",
9407
- `Use one of: ${validOperators.join(", ")}`,
9408
- true
9409
- )
9410
- );
9411
- }
9412
9027
  const operatorsRequiringValue = [
9413
9028
  "equals",
9414
9029
  "not_equals",
@@ -9444,70 +9059,6 @@ var init_dist2 = __esm({
9444
9059
  );
9445
9060
  }
9446
9061
  }
9447
- validateRuleSpecificProperties(rule, rulePath, result) {
9448
- switch (rule.type) {
9449
- case "visibility": {
9450
- if ("action" in rule && rule.action && !["show", "hide"].includes(rule.action)) {
9451
- result.errors.push(
9452
- this.createError(
9453
- "INVALID_VISIBILITY_ACTION",
9454
- `Invalid visibility action '${rule.action}'. Must be 'show' or 'hide'`,
9455
- `${rulePath}.action`,
9456
- "error",
9457
- "Use 'show' or 'hide'",
9458
- true
9459
- )
9460
- );
9461
- }
9462
- break;
9463
- }
9464
- case "requirement": {
9465
- if ("action" in rule && rule.action && !["require", "optional"].includes(rule.action)) {
9466
- result.errors.push(
9467
- this.createError(
9468
- "INVALID_REQUIREMENT_ACTION",
9469
- `Invalid requirement action '${rule.action}'. Must be 'require' or 'optional'`,
9470
- `${rulePath}.action`,
9471
- "error",
9472
- "Use 'require' or 'optional'",
9473
- true
9474
- )
9475
- );
9476
- }
9477
- break;
9478
- }
9479
- case "validation": {
9480
- if ("message" in rule && rule.message && typeof rule.message !== "string") {
9481
- result.errors.push(
9482
- this.createError(
9483
- "INVALID_VALIDATION_MESSAGE",
9484
- "Validation message must be a string",
9485
- `${rulePath}.message`,
9486
- "error",
9487
- "Provide a string message for validation errors",
9488
- true
9489
- )
9490
- );
9491
- }
9492
- break;
9493
- }
9494
- case "readOnly": {
9495
- if ("action" in rule && rule.action && !["enable", "disable"].includes(rule.action)) {
9496
- result.errors.push(
9497
- this.createError(
9498
- "INVALID_READONLY_ACTION",
9499
- `Invalid readOnly action '${rule.action}'. Must be 'enable' or 'disable'`,
9500
- `${rulePath}.action`,
9501
- "error",
9502
- "Use 'enable' or 'disable'",
9503
- true
9504
- )
9505
- );
9506
- }
9507
- break;
9508
- }
9509
- }
9510
- }
9511
9062
  checkCircularDependencies(rules, _fieldId, _fieldPath, _result, _allFieldIds) {
9512
9063
  const referencedFields = /* @__PURE__ */ new Set();
9513
9064
  for (const rule of rules) {
@@ -9582,6 +9133,15 @@ var init_dist2 = __esm({
9582
9133
  });
9583
9134
  return fieldIds;
9584
9135
  }
9136
+ /**
9137
+ * A declared `localization`, in the one of its three forms the parse admits
9138
+ * (`LocalizationConfigSchema`): a boolean, a list of locale codes, or `{ enabled, requiredLocales? }`
9139
+ * with `enabled` a boolean and `requiredLocales` a list. This refused any other form
9140
+ * (`INVALID_LOCALIZATION_FORMAT`), an `enabled` that is not a boolean
9141
+ * (`INVALID_LOCALIZATION_ENABLED`) and a `requiredLocales` that is not a list
9142
+ * (`INVALID_REQUIRED_LOCALES`) — the parse refuses all three first, so none could fire (REL-1497,
9143
+ * `parse-refuses-first.test.ts`).
9144
+ */
9585
9145
  validateFieldLocalization(field, fieldPath, result, _allFieldIds, _context) {
9586
9146
  const localization = field.localization;
9587
9147
  const localizationPath = `${fieldPath}.localization`;
@@ -9589,24 +9149,8 @@ var init_dist2 = __esm({
9589
9149
  this.validateBooleanLocalization(localization, localizationPath, result, field);
9590
9150
  } else if (Array.isArray(localization)) {
9591
9151
  this.validateArrayLocalization(localization, localizationPath, result, field);
9592
- } else if (typeof localization === "object" && localization !== null) {
9593
- this.validateObjectLocalization(
9594
- localization,
9595
- localizationPath,
9596
- result,
9597
- field
9598
- );
9599
- } else {
9600
- result.errors.push(
9601
- this.createError(
9602
- "INVALID_LOCALIZATION_FORMAT",
9603
- "Localization must be a boolean, string array, or configuration object",
9604
- localizationPath,
9605
- "error",
9606
- "Use true/false, array of locale codes, or { enabled: boolean, ... } object",
9607
- true
9608
- )
9609
- );
9152
+ } else if (localization !== void 0) {
9153
+ this.validateRequiredLocales(localization.requiredLocales, localizationPath, result);
9610
9154
  }
9611
9155
  }
9612
9156
  validateBooleanLocalization(enabled, localizationPath, result, field) {
@@ -9664,36 +9208,10 @@ var init_dist2 = __esm({
9664
9208
  );
9665
9209
  }
9666
9210
  }
9667
- validateObjectLocalization(config, localizationPath, result, _field) {
9668
- if (config.enabled !== void 0 && typeof config.enabled !== "boolean") {
9669
- result.errors.push(
9670
- this.createError(
9671
- "INVALID_LOCALIZATION_ENABLED",
9672
- "Localization 'enabled' property must be a boolean",
9673
- `${localizationPath}.enabled`,
9674
- "error",
9675
- "Use true or false",
9676
- true
9677
- )
9678
- );
9679
- }
9680
- if (config.requiredLocales) {
9681
- if (Array.isArray(config.requiredLocales)) {
9682
- for (const [index, locale] of config.requiredLocales.entries()) {
9683
- this.validateLocaleCode(locale, `${localizationPath}.requiredLocales[${index}]`, result);
9684
- }
9685
- } else {
9686
- result.errors.push(
9687
- this.createError(
9688
- "INVALID_REQUIRED_LOCALES",
9689
- "Required locales must be an array of locale codes",
9690
- `${localizationPath}.requiredLocales`,
9691
- "error",
9692
- "Use array format: ['en-US', 'fr-FR']",
9693
- true
9694
- )
9695
- );
9696
- }
9211
+ validateRequiredLocales(requiredLocales, localizationPath, result) {
9212
+ if (requiredLocales === void 0) return;
9213
+ for (const [index, locale] of requiredLocales.entries()) {
9214
+ this.validateLocaleCode(locale, `${localizationPath}.requiredLocales[${index}]`, result);
9697
9215
  }
9698
9216
  }
9699
9217
  validateFieldFallback(field, fieldPath, result, allFieldIds, _context) {
@@ -10080,93 +9598,13 @@ var init_dist2 = __esm({
10080
9598
  });
10081
9599
  }
10082
9600
  /**
10083
- * Validate locale configuration for consistency and best practices
9601
+ * The locale list's shape — exactly one default, unique codes, fallbacks to declared locales,
9602
+ * none to itself and no cycle — is the parse's (`LocalesArraySchema`), which refuses each first,
9603
+ * so the six checks this made of it could not fire (REL-1497, `parse-refuses-first.test.ts`).
9604
+ * What is left is advice the parse does not give.
10084
9605
  */
10085
9606
  validateLocaleConfiguration(locales, result) {
10086
- const defaultLocales = locales.filter((l) => l.default === true);
10087
- if (defaultLocales.length === 0) {
10088
- result.errors.push(
10089
- this.createError(
10090
- "NO_DEFAULT_LOCALE",
10091
- "No default locale defined. Exactly one locale must be marked as default.",
10092
- "locales",
10093
- "error",
10094
- "Add 'default: true' to one of your locale definitions",
10095
- true
10096
- )
10097
- );
10098
- } else if (defaultLocales.length > 1) {
10099
- result.errors.push(
10100
- this.createError(
10101
- "MULTIPLE_DEFAULT_LOCALES",
10102
- `Multiple default locales defined: ${defaultLocales.map((l) => l.code).join(", ")}`,
10103
- "locales",
10104
- "error",
10105
- "Only one locale can be the default",
10106
- true
10107
- )
10108
- );
10109
- }
10110
- const seenCodes = /* @__PURE__ */ new Set();
10111
- for (const [index, locale] of locales.entries()) {
10112
- if (seenCodes.has(locale.code)) {
10113
- result.errors.push(
10114
- this.createError(
10115
- "DUPLICATE_LOCALE_CODE",
10116
- `Duplicate locale code '${locale.code}'`,
10117
- `locales[${index}]`,
10118
- "error",
10119
- "Each locale must have a unique code",
10120
- true
10121
- )
10122
- );
10123
- }
10124
- seenCodes.add(locale.code);
10125
- }
10126
- const localeCodes = new Set(locales.map((l) => l.code));
10127
- for (const [index, locale] of locales.entries()) {
10128
- if (!locale.fallback) continue;
10129
- for (const [fallbackIndex, fallbackCode] of locale.fallback.entries()) {
10130
- if (!localeCodes.has(fallbackCode)) {
10131
- result.errors.push(
10132
- this.createError(
10133
- "INVALID_LOCALE_FALLBACK",
10134
- `Locale '${locale.code}' has fallback to non-existent locale '${fallbackCode}'`,
10135
- `locales[${index}].fallback[${fallbackIndex}]`,
10136
- "error",
10137
- `Reference an existing locale: ${[...localeCodes].join(", ")}`,
10138
- true
10139
- )
10140
- );
10141
- }
10142
- if (fallbackCode === locale.code) {
10143
- result.errors.push(
10144
- this.createError(
10145
- "SELF_REFERENCING_LOCALE_FALLBACK",
10146
- `Locale '${locale.code}' references itself in fallback chain`,
10147
- `locales[${index}].fallback[${fallbackIndex}]`,
10148
- "error",
10149
- "Remove self-reference from fallback chain",
10150
- true
10151
- )
10152
- );
10153
- }
10154
- }
10155
- const circularPath = this.detectCircularFallback(locale.code, locales);
10156
- if (circularPath) {
10157
- result.errors.push(
10158
- this.createError(
10159
- "CIRCULAR_LOCALE_FALLBACK",
10160
- `Circular fallback chain detected: ${circularPath.join(" \u2192 ")}`,
10161
- `locales[${index}].fallback`,
10162
- "error",
10163
- "Remove circular references in fallback chain",
10164
- true
10165
- )
10166
- );
10167
- }
10168
- }
10169
- const defaultLocale = defaultLocales[0];
9607
+ const defaultLocale = locales.find((l) => l.default === true);
10170
9608
  if (defaultLocale?.fallback && defaultLocale.fallback.length > 0) {
10171
9609
  result.warnings.push(
10172
9610
  this.createError(
@@ -10224,34 +9662,6 @@ var init_dist2 = __esm({
10224
9662
  }
10225
9663
  });
10226
9664
  }
10227
- /**
10228
- * Detects circular fallback chains starting from a given locale
10229
- */
10230
- detectCircularFallback(startCode, locales) {
10231
- const visited = /* @__PURE__ */ new Set();
10232
- const path25 = [];
10233
- const localeMap = new Map(locales.map((l) => [l.code, l]));
10234
- const visit = (code) => {
10235
- if (visited.has(code)) {
10236
- const cycleStart = path25.indexOf(code);
10237
- return [...path25.slice(cycleStart), code];
10238
- }
10239
- const locale = localeMap.get(code);
10240
- if (!locale?.fallback || locale.fallback.length === 0) {
10241
- return null;
10242
- }
10243
- visited.add(code);
10244
- path25.push(code);
10245
- for (const fallbackCode of locale.fallback) {
10246
- const cycle = visit(fallbackCode);
10247
- if (cycle) return cycle;
10248
- }
10249
- path25.pop();
10250
- visited.delete(code);
10251
- return null;
10252
- };
10253
- return visit(startCode);
10254
- }
10255
9665
  };
10256
9666
  SettingsGroupsValidator = class extends BaseValidator {
10257
9667
  feature = "settingsGroups";
@@ -10328,53 +9738,27 @@ var init_dist2 = __esm({
10328
9738
  )
10329
9739
  );
10330
9740
  }
10331
- if (!settingsGroup.displayName || typeof settingsGroup.displayName !== "string") {
10332
- result.errors.push(
9741
+ if (settingsGroup.displayName.length < 3) {
9742
+ result.warnings.push(
10333
9743
  this.createError(
10334
- "MISSING_SETTINGS_GROUP_DISPLAY_NAME",
10335
- `Settings group '${groupId}' must have a displayName`,
9744
+ "SHORT_SETTINGS_GROUP_DISPLAY_NAME",
9745
+ `Settings group '${groupId}' has a very short displayName`,
10336
9746
  `${groupPath}.displayName`,
10337
- "error",
10338
- "Add a descriptive displayName property",
10339
- true
9747
+ "warning",
9748
+ "Use a more descriptive displayName (at least 3 characters)"
10340
9749
  )
10341
9750
  );
10342
- } else {
10343
- if (settingsGroup.displayName.length < 3) {
10344
- result.warnings.push(
10345
- this.createError(
10346
- "SHORT_SETTINGS_GROUP_DISPLAY_NAME",
10347
- `Settings group '${groupId}' has a very short displayName`,
10348
- `${groupPath}.displayName`,
10349
- "warning",
10350
- "Use a more descriptive displayName (at least 3 characters)"
10351
- )
10352
- );
10353
- }
10354
9751
  }
10355
9752
  if (settingsGroup.description) {
10356
- if (typeof settingsGroup.description === "string") {
10357
- stats.groupsWithDescriptions++;
10358
- if (settingsGroup.description.length < 10) {
10359
- result.warnings.push(
10360
- this.createError(
10361
- "SHORT_SETTINGS_GROUP_DESCRIPTION",
10362
- `Settings group '${groupId}' has a very short description`,
10363
- `${groupPath}.description`,
10364
- "warning",
10365
- "Provide a more detailed description for better understanding"
10366
- )
10367
- );
10368
- }
10369
- } else {
10370
- result.errors.push(
9753
+ stats.groupsWithDescriptions++;
9754
+ if (settingsGroup.description.length < 10) {
9755
+ result.warnings.push(
10371
9756
  this.createError(
10372
- "INVALID_SETTINGS_GROUP_DESCRIPTION",
10373
- `Settings group '${groupId}' description must be a string`,
9757
+ "SHORT_SETTINGS_GROUP_DESCRIPTION",
9758
+ `Settings group '${groupId}' has a very short description`,
10374
9759
  `${groupPath}.description`,
10375
- "error",
10376
- "Use a string description",
10377
- true
9760
+ "warning",
9761
+ "Provide a more detailed description for better understanding"
10378
9762
  )
10379
9763
  );
10380
9764
  }
@@ -10389,47 +9773,13 @@ var init_dist2 = __esm({
10389
9773
  )
10390
9774
  );
10391
9775
  }
10392
- if (!settingsGroup.fields || !Array.isArray(settingsGroup.fields)) {
10393
- result.errors.push(
10394
- this.createError(
10395
- "MISSING_SETTINGS_FIELDS_ARRAY",
10396
- `Settings group '${groupId}' must have a fields array`,
10397
- `${groupPath}.fields`,
10398
- "error",
10399
- "Add a fields array with field configurations",
10400
- true
10401
- )
10402
- );
10403
- } else if (settingsGroup.fields.length === 0) {
10404
- result.warnings.push(
10405
- this.createError(
10406
- "EMPTY_SETTINGS_GROUP_FIELDS",
10407
- `Settings group '${groupId}' has no fields`,
10408
- `${groupPath}.fields`,
10409
- "warning",
10410
- "Add fields or remove this empty group"
10411
- )
10412
- );
10413
- }
10414
9776
  }
10415
9777
  validateSettingsGroupFields(groupId, settingsGroup, result, allFieldIds, stats) {
10416
9778
  const groupPath = `settingsGroups.${groupId}`;
10417
- if (!Array.isArray(settingsGroup.fields)) {
10418
- return;
10419
- }
10420
9779
  stats.totalFields += settingsGroup.fields.length;
10421
- const groupFieldIds = /* @__PURE__ */ new Set();
10422
9780
  for (const [index, field] of settingsGroup.fields.entries()) {
10423
9781
  const fieldPath = `${groupPath}.fields[${index}]`;
10424
- this.validateSettingsField(
10425
- field,
10426
- fieldPath,
10427
- result,
10428
- groupFieldIds,
10429
- allFieldIds,
10430
- groupId,
10431
- stats
10432
- );
9782
+ this.validateSettingsField(field, fieldPath, result, allFieldIds, groupId, stats);
10433
9783
  }
10434
9784
  if (settingsGroup.fields.length > 15) {
10435
9785
  result.warnings.push(
@@ -10443,57 +9793,21 @@ var init_dist2 = __esm({
10443
9793
  );
10444
9794
  }
10445
9795
  if (settingsGroup.fields.length >= 8 && settingsGroup.fields.length <= 15) {
10446
- result.info.push(
10447
- this.createError(
10448
- "CONSIDER_SUBGROUPING",
10449
- `Settings group '${groupId}' with ${settingsGroup.fields.length} fields might benefit from logical subgrouping`,
10450
- `${groupPath}`,
10451
- "info",
10452
- "Consider organizing related fields together or using field prefixes for clarity"
10453
- )
10454
- );
10455
- }
10456
- }
10457
- validateSettingsField(field, fieldPath, result, groupFieldIds, allFieldIds, groupId, stats) {
10458
- if (!field.id || typeof field.id !== "string") {
10459
- result.errors.push(
10460
- this.createError(
10461
- "MISSING_FIELD_ID",
10462
- "Field must have an id property",
10463
- `${fieldPath}.id`,
10464
- "error",
10465
- "Add a unique string id for this field",
10466
- true
10467
- )
10468
- );
10469
- return;
10470
- }
10471
- if (groupFieldIds.has(field.id)) {
10472
- result.errors.push(
10473
- this.createError(
10474
- "DUPLICATE_FIELD_ID",
10475
- `Duplicate field ID '${field.id}' in settings group '${groupId}'`,
10476
- `${fieldPath}.id`,
10477
- "error",
10478
- "Use unique field IDs within each settings group",
10479
- true
10480
- )
10481
- );
10482
- } else {
10483
- groupFieldIds.add(field.id);
10484
- }
10485
- if (!ValidationUtils.isValidName(field.id)) {
10486
- result.warnings.push(
9796
+ result.info.push(
10487
9797
  this.createError(
10488
- "INVALID_FIELD_ID_FORMAT",
10489
- `Field ID '${field.id}' should follow naming conventions`,
10490
- `${fieldPath}.id`,
10491
- "warning",
10492
- "Use camelCase or snake_case for field IDs"
9798
+ "CONSIDER_SUBGROUPING",
9799
+ `Settings group '${groupId}' with ${settingsGroup.fields.length} fields might benefit from logical subgrouping`,
9800
+ `${groupPath}`,
9801
+ "info",
9802
+ "Consider organizing related fields together or using field prefixes for clarity"
10493
9803
  )
10494
9804
  );
10495
9805
  }
10496
- if (!field.type || typeof field.type !== "string") {
9806
+ }
9807
+ validateSettingsField(field, fieldPath, result, allFieldIds, groupId, stats) {
9808
+ if (field.type) {
9809
+ this.validateFieldTypeForSettings(field, fieldPath, result);
9810
+ } else {
10497
9811
  result.errors.push(
10498
9812
  this.createError(
10499
9813
  "MISSING_FIELD_TYPE",
@@ -10504,8 +9818,6 @@ var init_dist2 = __esm({
10504
9818
  true
10505
9819
  )
10506
9820
  );
10507
- } else {
10508
- this.validateFieldTypeForSettings(field, fieldPath, result);
10509
9821
  }
10510
9822
  if (field.localization !== void 0) {
10511
9823
  this.validateSettingsFieldLocalization(field, fieldPath, result);
@@ -10768,7 +10080,7 @@ var init_dist2 = __esm({
10768
10080
  singletonsWithSlugFields: 0,
10769
10081
  singletonsWithUniqueFields: 0
10770
10082
  };
10771
- if (!index.schema.singletonContentTypes || index.singletonContentTypes.ids.length === 0) {
10083
+ if (index.singletonContentTypes.ids.length === 0) {
10772
10084
  result.info.push(
10773
10085
  this.createError(
10774
10086
  "NO_SINGLETON_CONTENT_TYPES",
@@ -10807,18 +10119,6 @@ var init_dist2 = __esm({
10807
10119
  }
10808
10120
  validateSingletonStructure(singletonId, singletonType, result, stats) {
10809
10121
  const singletonPath = `singletonContentTypes.${singletonId}`;
10810
- if (!ValidationUtils.isValidName(singletonId)) {
10811
- result.errors.push(
10812
- this.createError(
10813
- "INVALID_SINGLETON_ID",
10814
- `Singleton content type ID '${singletonId}' is invalid`,
10815
- singletonPath,
10816
- "error",
10817
- "Use camelCase or snake_case starting with a letter",
10818
- true
10819
- )
10820
- );
10821
- }
10822
10122
  const reservedKeywords = ValidationUtils.getReservedKeywords();
10823
10123
  if (reservedKeywords.includes(singletonId.toLowerCase())) {
10824
10124
  result.errors.push(
@@ -10832,18 +10132,7 @@ var init_dist2 = __esm({
10832
10132
  )
10833
10133
  );
10834
10134
  }
10835
- if (!singletonType.displayName || typeof singletonType.displayName !== "string") {
10836
- result.errors.push(
10837
- this.createError(
10838
- "MISSING_SINGLETON_DISPLAY_NAME",
10839
- `Singleton content type '${singletonId}' must have a displayName`,
10840
- `${singletonPath}.displayName`,
10841
- "error",
10842
- "Add a descriptive displayName property",
10843
- true
10844
- )
10845
- );
10846
- } else {
10135
+ if (singletonType.displayName) {
10847
10136
  if (singletonType.displayName.length < 3) {
10848
10137
  result.warnings.push(
10849
10138
  this.createError(
@@ -10855,30 +10144,28 @@ var init_dist2 = __esm({
10855
10144
  )
10856
10145
  );
10857
10146
  }
10147
+ } else {
10148
+ result.errors.push(
10149
+ this.createError(
10150
+ "MISSING_SINGLETON_DISPLAY_NAME",
10151
+ `Singleton content type '${singletonId}' must have a displayName`,
10152
+ `${singletonPath}.displayName`,
10153
+ "error",
10154
+ "Add a descriptive displayName property",
10155
+ true
10156
+ )
10157
+ );
10858
10158
  }
10859
10159
  if (singletonType.description) {
10860
- if (typeof singletonType.description === "string") {
10861
- stats.singletonsWithDescriptions++;
10862
- if (singletonType.description.length < 10) {
10863
- result.warnings.push(
10864
- this.createError(
10865
- "SHORT_SINGLETON_DESCRIPTION",
10866
- `Singleton content type '${singletonId}' has a very short description`,
10867
- `${singletonPath}.description`,
10868
- "warning",
10869
- "Provide a more detailed description for better understanding"
10870
- )
10871
- );
10872
- }
10873
- } else {
10874
- result.errors.push(
10160
+ stats.singletonsWithDescriptions++;
10161
+ if (singletonType.description.length < 10) {
10162
+ result.warnings.push(
10875
10163
  this.createError(
10876
- "INVALID_SINGLETON_DESCRIPTION",
10877
- `Singleton content type '${singletonId}' description must be a string`,
10164
+ "SHORT_SINGLETON_DESCRIPTION",
10165
+ `Singleton content type '${singletonId}' has a very short description`,
10878
10166
  `${singletonPath}.description`,
10879
- "error",
10880
- "Use a string description",
10881
- true
10167
+ "warning",
10168
+ "Provide a more detailed description for better understanding"
10882
10169
  )
10883
10170
  );
10884
10171
  }
@@ -10906,21 +10193,7 @@ var init_dist2 = __esm({
10906
10193
  );
10907
10194
  stats.singletonsWithHierarchical++;
10908
10195
  }
10909
- const hasDirectFields = singletonType.fields && Array.isArray(singletonType.fields) && singletonType.fields.length > 0;
10910
- const hasFieldsets = singletonType.fieldsets && Array.isArray(singletonType.fieldsets) && singletonType.fieldsets.length > 0;
10911
- if (!hasDirectFields && !hasFieldsets) {
10912
- result.errors.push(
10913
- this.createError(
10914
- "SINGLETON_NO_FIELDS",
10915
- `Singleton content type '${singletonId}' has no fields or fieldsets`,
10916
- `${singletonPath}.fields`,
10917
- "error",
10918
- "Add fields or fieldsets to define the singleton's structure",
10919
- true
10920
- )
10921
- );
10922
- }
10923
- if (hasFieldsets) {
10196
+ if (singletonType.fieldsets && singletonType.fieldsets.length > 0) {
10924
10197
  stats.singletonsWithFieldsets++;
10925
10198
  }
10926
10199
  }
@@ -10928,26 +10201,9 @@ var init_dist2 = __esm({
10928
10201
  const singletonPath = `singletonContentTypes.${singletonId}`;
10929
10202
  const allFields = this.getAllFieldsFromSingleton(singletonType);
10930
10203
  stats.totalFields += allFields.length;
10931
- const fieldIds = /* @__PURE__ */ new Set();
10932
10204
  let hasLocalizationEnabled = false;
10933
10205
  for (const [index, field] of allFields.entries()) {
10934
10206
  const fieldPath = this.getFieldPath(singletonPath, field, index, singletonType);
10935
- if (field.id) {
10936
- if (fieldIds.has(field.id)) {
10937
- result.errors.push(
10938
- this.createError(
10939
- "DUPLICATE_SINGLETON_FIELD_ID",
10940
- `Duplicate field ID '${field.id}' in singleton '${singletonId}'`,
10941
- fieldPath,
10942
- "error",
10943
- "Use unique field IDs within each singleton",
10944
- true
10945
- )
10946
- );
10947
- } else {
10948
- fieldIds.add(field.id);
10949
- }
10950
- }
10951
10207
  this.validateSingletonFieldType(field, fieldPath, result, stats, singletonId);
10952
10208
  if (isFieldLocalized(field)) {
10953
10209
  hasLocalizationEnabled = true;
@@ -10993,20 +10249,6 @@ var init_dist2 = __esm({
10993
10249
  );
10994
10250
  stats.singletonsWithSlugFields++;
10995
10251
  }
10996
- if (field.type === "reference") {
10997
- const config = field.config || {};
10998
- if (config.maxItems && config.maxItems > 10) {
10999
- result.info.push(
11000
- this.createError(
11001
- "LARGE_REFERENCE_LIST_IN_SINGLETON",
11002
- `Singleton field '${field.id}' allows ${config.maxItems} references`,
11003
- fieldPath,
11004
- "info",
11005
- "Consider if a large reference list is appropriate for singleton content, or if this should be a regular content type"
11006
- )
11007
- );
11008
- }
11009
- }
11010
10252
  const problematicFieldNames = ["title", "name", "identifier", "key", "index"];
11011
10253
  if (field.id && problematicFieldNames.some((name) => field.id.toLowerCase().includes(name))) {
11012
10254
  result.info.push(
@@ -11317,18 +10559,6 @@ var init_dist2 = __esm({
11317
10559
  }
11318
10560
  validateStructure(assetTypeId, assetType, result) {
11319
10561
  const path25 = `assetTypes.${assetTypeId}`;
11320
- if (!ValidationUtils.isValidName(assetTypeId)) {
11321
- result.errors.push(
11322
- this.createError(
11323
- "INVALID_ASSET_TYPE_ID",
11324
- `Asset type ID '${assetTypeId}' is invalid`,
11325
- path25,
11326
- "error",
11327
- "Use camelCase or snake_case starting with a letter",
11328
- true
11329
- )
11330
- );
11331
- }
11332
10562
  if (ValidationUtils.getReservedKeywords().includes(assetTypeId.toLowerCase())) {
11333
10563
  result.errors.push(
11334
10564
  this.createError(
@@ -11341,7 +10571,7 @@ var init_dist2 = __esm({
11341
10571
  )
11342
10572
  );
11343
10573
  }
11344
- if (!assetType.displayName || typeof assetType.displayName !== "string") {
10574
+ if (!assetType.displayName) {
11345
10575
  result.errors.push(
11346
10576
  this.createError(
11347
10577
  "MISSING_ASSET_TYPE_DISPLAY_NAME",
@@ -11353,48 +10583,12 @@ var init_dist2 = __esm({
11353
10583
  )
11354
10584
  );
11355
10585
  }
11356
- if (assetType.displayField) {
11357
- const exists = this.getAllFields(assetType).some((f) => f.id === assetType.displayField);
11358
- if (!exists) {
11359
- result.errors.push(
11360
- this.createError(
11361
- "INVALID_ASSET_TYPE_DISPLAY_FIELD",
11362
- `Asset type '${assetTypeId}' displayField '${assetType.displayField}' does not exist`,
11363
- `${path25}.displayField`,
11364
- "error",
11365
- "Reference an existing field id",
11366
- true
11367
- )
11368
- );
11369
- }
11370
- }
11371
10586
  }
11372
10587
  validateFields(assetTypeId, assetType, result, componentIds) {
11373
10588
  const path25 = `assetTypes.${assetTypeId}`;
11374
10589
  const fields = this.getAllFields(assetType);
11375
- const fieldIds = /* @__PURE__ */ new Set();
11376
10590
  for (const field of fields) {
11377
- const fieldPath = `${path25}.fields.${field.id || "[unknown]"}`;
11378
- if (!field.id) {
11379
- result.errors.push(
11380
- this.createError("MISSING_FIELD_ID", "Field must have an id", `${fieldPath}.id`, "error")
11381
- );
11382
- continue;
11383
- }
11384
- if (fieldIds.has(field.id)) {
11385
- result.errors.push(
11386
- this.createError(
11387
- "DUPLICATE_ASSET_TYPE_FIELD_ID",
11388
- `Duplicate field ID '${field.id}' in asset type '${assetTypeId}'`,
11389
- `${fieldPath}.id`,
11390
- "error",
11391
- "Use unique field IDs within each asset type",
11392
- true
11393
- )
11394
- );
11395
- } else {
11396
- fieldIds.add(field.id);
11397
- }
10591
+ const fieldPath = `${path25}.fields.${field.id}`;
11398
10592
  if (field.type && FORBIDDEN_TYPES.has(field.type)) {
11399
10593
  result.errors.push(
11400
10594
  this.createError(
@@ -11454,33 +10648,6 @@ var init_dist2 = __esm({
11454
10648
  }
11455
10649
  validateKind(assetTypeId, assetType, result, schemaLocales, stats) {
11456
10650
  const path25 = `assetTypes.${assetTypeId}`;
11457
- const presentKinds = ASSET_KINDS.filter((k) => assetType[k] !== void 0);
11458
- if (presentKinds.length === 0) {
11459
- result.errors.push(
11460
- this.createError(
11461
- "MISSING_ASSET_KIND",
11462
- `Asset type '${assetTypeId}' must declare exactly one kind key (image, video, or file)`,
11463
- path25,
11464
- "error",
11465
- "Add one of image, video, or file",
11466
- true
11467
- )
11468
- );
11469
- return;
11470
- }
11471
- if (presentKinds.length > 1) {
11472
- result.errors.push(
11473
- this.createError(
11474
- "MULTIPLE_ASSET_KINDS",
11475
- `Asset type '${assetTypeId}' declares multiple kind keys (${presentKinds.join(", ")}). Use exactly one.`,
11476
- path25,
11477
- "error",
11478
- "Keep only one of image, video, or file",
11479
- true
11480
- )
11481
- );
11482
- return;
11483
- }
11484
10651
  const resolved = resolveAssetType(assetType);
11485
10652
  if (!resolved) return;
11486
10653
  const kind = resolved.kind;
@@ -11636,20 +10803,7 @@ var init_dist2 = __esm({
11636
10803
  validateTransformLimits(path25, transform, result) {
11637
10804
  const width = transform.width;
11638
10805
  const height = transform.height;
11639
- let format2 = transform.format;
11640
- if (format2 === "heic") {
11641
- result.errors.push(
11642
- this.createError(
11643
- "INVALID_OUTPUT_FORMAT_HEIC",
11644
- `Output format 'heic' is not supported; HEIC is a decodable input only. Use avif/webp/jpeg/png.`,
11645
- `${path25}.format`,
11646
- "error",
11647
- "Choose a web-safe output format",
11648
- true
11649
- )
11650
- );
11651
- format2 = void 0;
11652
- }
10806
+ const format2 = transform.format;
11653
10807
  const maxDim = format2 === "avif" ? CF_IMAGES_LIMITS.maxDimensionAvifPx : CF_IMAGES_LIMITS.maxDimensionPx;
11654
10808
  if (width && width > maxDim) {
11655
10809
  result.errors.push(
@@ -11689,24 +10843,8 @@ var init_dist2 = __esm({
11689
10843
  }
11690
10844
  }
11691
10845
  validateVideoKind(assetTypeId, kindPath, kindConfig, schemaLocales, result) {
11692
- const poster = kindConfig.poster;
11693
10846
  const captions = kindConfig.captions;
11694
10847
  const watermark = kindConfig.watermark;
11695
- if (poster && typeof poster.defaultTimestampPct === "number") {
11696
- const pct = poster.defaultTimestampPct;
11697
- if (pct < 0 || pct > 1) {
11698
- result.errors.push(
11699
- this.createError(
11700
- "INVALID_POSTER_TIMESTAMP",
11701
- `video.poster.defaultTimestampPct must be between 0.0 and 1.0 (got ${pct})`,
11702
- `${kindPath}.poster.defaultTimestampPct`,
11703
- "error",
11704
- "Use a value between 0.0 and 1.0",
11705
- true
11706
- )
11707
- );
11708
- }
11709
- }
11710
10848
  if (captions?.requiredLocales && Array.isArray(captions.requiredLocales)) {
11711
10849
  for (const locale of captions.requiredLocales) {
11712
10850
  if (!schemaLocales.has(locale)) {
@@ -14476,30 +13614,6 @@ var init_dist2 = __esm({
14476
13614
  );
14477
13615
  }
14478
13616
  }
14479
- if (config.maxItems !== void 0 && (!Number.isInteger(config.maxItems) || config.maxItems <= 0)) {
14480
- result.errors.push(
14481
- this.createError(
14482
- "INVALID_MAX_ITEMS",
14483
- `Field '${field.id}' has invalid maxItems: ${config.maxItems}`,
14484
- `${path25}.config.maxItems`,
14485
- "error",
14486
- "maxItems must be a positive integer",
14487
- true
14488
- )
14489
- );
14490
- }
14491
- if (config.minItems !== void 0 && config.maxItems !== void 0 && config.minItems > config.maxItems) {
14492
- result.errors.push(
14493
- this.createError(
14494
- "INVALID_ITEMS_RANGE",
14495
- `Field '${field.id}' has minItems greater than maxItems`,
14496
- `${path25}.config`,
14497
- "error",
14498
- "Ensure minItems \u2264 maxItems",
14499
- true
14500
- )
14501
- );
14502
- }
14503
13617
  }
14504
13618
  validateRichTextFieldIntegrity(field, path25, result, index) {
14505
13619
  const config = field.config || {};
@@ -14535,22 +13649,6 @@ var init_dist2 = __esm({
14535
13649
  );
14536
13650
  }
14537
13651
  }
14538
- if (config.allowedBlocks && Array.isArray(config.allowedBlocks)) {
14539
- const validBlocks = ["paragraph", "heading", "list", "quote", "code", "divider", "table"];
14540
- for (const [index2, block] of config.allowedBlocks.entries()) {
14541
- if (!validBlocks.includes(block)) {
14542
- result.warnings.push(
14543
- this.createError(
14544
- "UNKNOWN_RICH_TEXT_BLOCK",
14545
- `Rich text field '${field.id}' allows unknown block type '${block}'`,
14546
- `${path25}.config.allowedBlocks[${index2}]`,
14547
- "warning",
14548
- `Use standard block types: ${validBlocks.join(", ")}`
14549
- )
14550
- );
14551
- }
14552
- }
14553
- }
14554
13652
  }
14555
13653
  validateEnumFieldIntegrity(field, path25, result) {
14556
13654
  const config = field.config || {};
@@ -14646,35 +13744,18 @@ var init_dist2 = __esm({
14646
13744
  this.validateRequiredFieldDistribution(index, result);
14647
13745
  }
14648
13746
  validateRuleDependencies(rule, rulePath, context, result) {
14649
- const conditionsList = rule.conditions?.list ?? rule.conditions?.conditions;
14650
- if (conditionsList) {
14651
- for (const [condIndex, condition] of conditionsList.entries()) {
14652
- if (condition.field && !this.fieldExistsInContext(condition.field, context)) {
14653
- result.errors.push(
14654
- this.createError(
14655
- "CONDITION_REFERENCES_NONEXISTENT_FIELD",
14656
- `Rule condition references non-existent field '${condition.field}'`,
14657
- `${rulePath}.conditions.list[${condIndex}].field`,
14658
- "error",
14659
- "Reference only existing fields in conditions",
14660
- true
14661
- )
14662
- );
14663
- }
14664
- if (condition.field && condition.operator) {
14665
- const referencedField = this.findFieldInContext(condition.field, context);
14666
- if (referencedField && !this.isCompatibleComparator(condition.operator, referencedField.type)) {
14667
- result.warnings.push(
14668
- this.createError(
14669
- "INCOMPATIBLE_RULE_OPERATOR",
14670
- `Operator '${condition.operator}' may not be compatible with field type '${referencedField.type}'`,
14671
- `${rulePath}.conditions.list[${condIndex}].operator`,
14672
- "warning",
14673
- "Verify that the operator is appropriate for the field type"
14674
- )
14675
- );
14676
- }
14677
- }
13747
+ for (const [condIndex, condition] of rule.conditions.conditions.entries()) {
13748
+ if (condition.field && !this.fieldExistsInContext(condition.field, context)) {
13749
+ result.errors.push(
13750
+ this.createError(
13751
+ "CONDITION_REFERENCES_NONEXISTENT_FIELD",
13752
+ `Rule condition references non-existent field '${condition.field}'`,
13753
+ `${rulePath}.conditions.conditions[${condIndex}].field`,
13754
+ "error",
13755
+ "Reference only existing fields in conditions",
13756
+ true
13757
+ )
13758
+ );
14678
13759
  }
14679
13760
  }
14680
13761
  }
@@ -14866,11 +13947,11 @@ var init_dist2 = __esm({
14866
13947
  validateComponentIntegrity(index, result) {
14867
13948
  for (const [componentId, component] of index.components.entries) {
14868
13949
  const componentPath = `components.${componentId}`;
14869
- if (!component.fields || component.fields.length < 2) {
13950
+ if (component.fields.length < 2) {
14870
13951
  result.warnings.push(
14871
13952
  this.createError(
14872
13953
  "COMPONENT_TOO_SIMPLE",
14873
- `Component '${componentId}' has only ${component.fields?.length || 0} field(s)`,
13954
+ `Component '${componentId}' has only ${component.fields.length} field(s)`,
14874
13955
  componentPath,
14875
13956
  "warning",
14876
13957
  "Components should group multiple related fields to justify reusability"
@@ -15110,14 +14191,6 @@ var init_dist2 = __esm({
15110
14191
  }
15111
14192
  return null;
15112
14193
  }
15113
- isCompatibleComparator(comparator, fieldType) {
15114
- const numericComparators = ["greaterThan", "lessThan"];
15115
- const numericTypes = ["integer", "decimal"];
15116
- if (numericComparators.includes(comparator) && !numericTypes.includes(fieldType)) {
15117
- return false;
15118
- }
15119
- return true;
15120
- }
15121
14194
  getAllFields(contentType) {
15122
14195
  const fields = [];
15123
14196
  if (contentType.fields) {
@@ -15240,18 +14313,6 @@ var init_dist2 = __esm({
15240
14313
  validateRoleStructure(role, rolePath, result, stats) {
15241
14314
  if (role.baseRole) {
15242
14315
  stats.rolesWithBaseRole++;
15243
- if (!isBuiltInRole(role.baseRole)) {
15244
- result.errors.push(
15245
- this.createError(
15246
- "INVALID_BASE_ROLE",
15247
- `Role '${role.id}' has invalid baseRole '${role.baseRole}'`,
15248
- `${rolePath}.baseRole`,
15249
- "error",
15250
- `Valid base roles are: ${BUILT_IN_ROLES.join(", ")}`,
15251
- true
15252
- )
15253
- );
15254
- }
15255
14316
  }
15256
14317
  if (role.includes && role.includes.length > 0) {
15257
14318
  stats.rolesWithIncludes++;
@@ -15259,17 +14320,6 @@ var init_dist2 = __esm({
15259
14320
  if (role.permissions && role.permissions.length > 0) {
15260
14321
  stats.rolesWithExplicitPermissions++;
15261
14322
  }
15262
- if (role.displayName.length > 128) {
15263
- result.warnings.push(
15264
- this.createError(
15265
- "LONG_ROLE_DISPLAY_NAME",
15266
- `Role '${role.id}' has a very long displayName (${role.displayName.length} chars)`,
15267
- `${rolePath}.displayName`,
15268
- "warning",
15269
- "Consider using a shorter display name for better UI presentation"
15270
- )
15271
- );
15272
- }
15273
14323
  if (!role.description) {
15274
14324
  result.info.push(
15275
14325
  this.createError(
@@ -15346,21 +14396,6 @@ var init_dist2 = __esm({
15346
14396
  }
15347
14397
  for (const [permIndex, permission] of role.permissions.entries()) {
15348
14398
  const permPath = `${rolePath}.permissions[${permIndex}]`;
15349
- for (const [actionIndex, action] of permission.actions.entries()) {
15350
- if (!ROLE_ACTIONS.includes(action)) {
15351
- const suggestion = ValidationUtils.findBestMatch(action, [...ROLE_ACTIONS]);
15352
- result.errors.push(
15353
- this.createError(
15354
- "INVALID_ROLE_ACTION",
15355
- `Role '${role.id}' has invalid action '${action}'`,
15356
- `${permPath}.actions[${actionIndex}]`,
15357
- "error",
15358
- suggestion ? `Did you mean '${suggestion}'? Valid actions: ${ROLE_ACTIONS.join(", ")}` : `Valid actions are: ${ROLE_ACTIONS.join(", ")}`,
15359
- true
15360
- )
15361
- );
15362
- }
15363
- }
15364
14399
  for (const [tagIndex, tag] of permission.tags.entries()) {
15365
14400
  stats.totalAccessTagsUsed.add(tag);
15366
14401
  if (!definedAccessTags.has(tag)) {
@@ -15565,7 +14600,7 @@ var init_dist2 = __esm({
15565
14600
  }
15566
14601
  };
15567
14602
  EVOLUTION_VALIDATOR_KEY = "evolution";
15568
- CROSS_VERSION_CHECKS_SKIPPED = Object.entries(CROSS_VERSION_CHECKS).map(([code, check]) => `${check} (${code})`).join(", ");
14603
+ CROSS_VERSION_CHECKS_SKIPPED = Object.entries(CROSS_VERSION_CHECKS).map(([code, check]) => `${check.names} (${code})`).join(", ");
15569
14604
  ValidationPipeline = class {
15570
14605
  config;
15571
14606
  validators = /* @__PURE__ */ new Map();
@@ -16796,7 +15831,7 @@ async function preview(options) {
16796
15831
  return failed(extractApiError(error2, "Pre-deploy validation failed"));
16797
15832
  }
16798
15833
  const validationErrors = status.validationErrors ?? [];
16799
- const validationWarnings = status.validationWarnings ?? [];
15834
+ const validationWarnings = status.validationWarnings;
16800
15835
  const gitCommitSha = getCurrentGitCommitSha() || void 0;
16801
15836
  const previewData = {
16802
15837
  branchName,
@@ -16889,7 +15924,8 @@ async function run(options) {
16889
15924
  version: deployResult.version,
16890
15925
  durationMs: Date.now() - start,
16891
15926
  preview: previewData,
16892
- verified
15927
+ verified,
15928
+ validationWarnings: deployResult.validationWarnings
16893
15929
  };
16894
15930
  }
16895
15931
  async function verifyDeploy(branchId, projectRoot) {
@@ -18836,6 +17872,7 @@ async function runLocalVsRemote(options) {
18836
17872
  hasChanges: status.hasChanges ?? false,
18837
17873
  changes: { ...DEFAULT_CHANGES2, ...status.changes },
18838
17874
  validationErrors: status.validationErrors ?? [],
17875
+ validationWarnings: status.validationWarnings,
18839
17876
  localSchema: loaded.schema
18840
17877
  };
18841
17878
  }
@@ -22524,7 +21561,7 @@ var init_banner = __esm({
22524
21561
  init_dev_cockpit_context();
22525
21562
  init_dev_cockpit_format();
22526
21563
  init_startup_info();
22527
- CLI_VERSION2 = "0.4.0" ;
21564
+ CLI_VERSION2 = "0.5.0" ;
22528
21565
  BRAND_GLYPH = "\u25B6";
22529
21566
  BRAND_NAME = "RelyIO";
22530
21567
  LABEL_WIDTH = 9;
@@ -30679,6 +29716,13 @@ function displayDeploySuccess(version, branchName) {
30679
29716
 
30680
29717
  // src/utils/json-output.ts
30681
29718
  init_change_buckets();
29719
+ function listIssues(issues) {
29720
+ return issues.map((issue) => ({
29721
+ path: issue.path,
29722
+ message: issue.message,
29723
+ ...issue.code && { code: issue.code }
29724
+ }));
29725
+ }
30682
29726
  function listBucket(bucket) {
30683
29727
  return {
30684
29728
  added: bucket.added,
@@ -30722,25 +29766,23 @@ function createStatusJsonOutput(params) {
30722
29766
  complexity: statistics?.complexity
30723
29767
  },
30724
29768
  ...failure && { error: failure },
30725
- ...validationErrors.length > 0 && {
30726
- errors: validationErrors.map((e) => ({
30727
- path: e.path,
30728
- message: e.message,
30729
- ...e.code && { code: e.code }
30730
- }))
30731
- },
30732
- ...validationWarnings.length > 0 && {
30733
- warnings: validationWarnings.map((w) => ({
30734
- path: w.path,
30735
- message: w.message,
30736
- ...w.code && { code: w.code }
30737
- }))
30738
- },
29769
+ ...validationErrors.length > 0 && { errors: listIssues(validationErrors) },
29770
+ ...validationWarnings.length > 0 && { warnings: listIssues(validationWarnings) },
30739
29771
  ...divergence && { divergence }
30740
29772
  };
30741
29773
  }
30742
29774
  function createDeployJsonOutput(params) {
30743
- const { success: success2, branchName, branchId, version, changes, gitCommitSha, notes, error: error2 } = params;
29775
+ const {
29776
+ success: success2,
29777
+ branchName,
29778
+ branchId,
29779
+ version,
29780
+ changes,
29781
+ gitCommitSha,
29782
+ notes,
29783
+ validationWarnings,
29784
+ error: error2
29785
+ } = params;
30744
29786
  return {
30745
29787
  success: success2,
30746
29788
  ...version && { version, deployedAt: (/* @__PURE__ */ new Date()).toISOString() },
@@ -30754,6 +29796,7 @@ function createDeployJsonOutput(params) {
30754
29796
  },
30755
29797
  ...gitCommitSha && { git: { commitSha: gitCommitSha } },
30756
29798
  ...notes && { notes },
29799
+ warnings: listIssues(validationWarnings),
30757
29800
  ...error2 && { error: error2 }
30758
29801
  };
30759
29802
  }
@@ -30787,7 +29830,8 @@ function createDiffJsonOutput(params) {
30787
29830
  schemaSourcePath,
30788
29831
  hasChanges,
30789
29832
  changes,
30790
- localSchema
29833
+ localSchema,
29834
+ validationWarnings
30791
29835
  } = params;
30792
29836
  const fieldCount = (key, name) => {
30793
29837
  const section = localSchema?.[key];
@@ -30813,7 +29857,8 @@ function createDiffJsonOutput(params) {
30813
29857
  deleted: bucket.deleted,
30814
29858
  ...bucket.enabled && { enabled: bucket.enabled }
30815
29859
  })),
30816
- summary: countChanges(changes)
29860
+ summary: countChanges(changes),
29861
+ warnings: listIssues(validationWarnings)
30817
29862
  };
30818
29863
  }
30819
29864
  function countOf(count, noun) {
@@ -31265,10 +30310,16 @@ function nothingToDeploy(preview2) {
31265
30310
  branchName: preview2.branchName,
31266
30311
  branchId: preview2.branchId,
31267
30312
  changes: preview2.changes,
31268
- gitCommitSha: preview2.gitCommitSha
30313
+ gitCommitSha: preview2.gitCommitSha,
30314
+ validationWarnings: preview2.validationWarnings
31269
30315
  })
31270
30316
  );
31271
30317
  }
30318
+ var issueKey = (issue) => JSON.stringify([issue.code, issue.path, issue.message]);
30319
+ function warningsNotShown(deployed, shown) {
30320
+ const seen = new Set(shown.map((warning2) => issueKey(warning2)));
30321
+ return deployed.filter((warning2) => !seen.has(issueKey(warning2)));
30322
+ }
31272
30323
  async function handleDeploy(options) {
31273
30324
  const { json = false } = options;
31274
30325
  process.on("SIGINT", () => interrupted(json));
@@ -31379,24 +30430,26 @@ async function handleDeploy(options) {
31379
30430
  displayInfo("Your schema is already up to date with the remote branch");
31380
30431
  return;
31381
30432
  }
31382
- displaySection("Deployment Preview", "sparkles");
31383
- console.log(
31384
- THEME.primary("\u2502 ") + THEME.muted(
31385
- `Target: ${previewData.branchName} (${previewData.branchId.slice(0, 8)}...)`
31386
- )
31387
- );
31388
- console.log(
31389
- THEME.primary("\u2502 ") + THEME.muted(`Schema: ${formatSchemaSource(previewData)}`)
31390
- );
31391
- if (previewData.gitCommitSha) {
30433
+ if (!json) {
30434
+ displaySection("Deployment Preview", "sparkles");
31392
30435
  console.log(
31393
- THEME.primary("\u2502 ") + THEME.muted(`Git SHA: ${previewData.gitCommitSha.slice(0, 8)}...`)
30436
+ THEME.primary("\u2502 ") + THEME.muted(
30437
+ `Target: ${previewData.branchName} (${previewData.branchId.slice(0, 8)}...)`
30438
+ )
31394
30439
  );
31395
- }
31396
- displaySectionEnd();
31397
- displaySchemaChanges(previewData.changes);
31398
- if (previewData.validationWarnings.length > 0) {
31399
- console.log(formatValidationErrors(previewData.validationWarnings));
30440
+ console.log(
30441
+ THEME.primary("\u2502 ") + THEME.muted(`Schema: ${formatSchemaSource(previewData)}`)
30442
+ );
30443
+ if (previewData.gitCommitSha) {
30444
+ console.log(
30445
+ THEME.primary("\u2502 ") + THEME.muted(`Git SHA: ${previewData.gitCommitSha.slice(0, 8)}...`)
30446
+ );
30447
+ }
30448
+ displaySectionEnd();
30449
+ displaySchemaChanges(previewData.changes);
30450
+ if (previewData.validationWarnings.length > 0) {
30451
+ console.log(formatValidationErrors(previewData.validationWarnings));
30452
+ }
31400
30453
  }
31401
30454
  let notes;
31402
30455
  if (!json && !options.yes) {
@@ -31493,12 +30546,17 @@ async function handleDeploy(options) {
31493
30546
  version: result.version,
31494
30547
  changes: result.preview.changes,
31495
30548
  gitCommitSha: result.preview.gitCommitSha,
31496
- notes
30549
+ notes,
30550
+ validationWarnings: result.validationWarnings
31497
30551
  })
31498
30552
  );
31499
30553
  }
31500
30554
  deploySpinner?.succeed("Schema deployment completed successfully");
31501
30555
  displayDeploySuccess(result.version, result.preview.branchName);
30556
+ const unseenWarnings = warningsNotShown(result.validationWarnings, previewData.validationWarnings);
30557
+ if (unseenWarnings.length > 0) {
30558
+ console.log(formatValidationErrors(unseenWarnings));
30559
+ }
31502
30560
  console.log();
31503
30561
  switch (result.verified) {
31504
30562
  case "passed": {
@@ -32133,7 +31191,7 @@ function sandboxCommands(program2) {
32133
31191
  }
32134
31192
 
32135
31193
  // src/commands/version.ts
32136
- var CLI_VERSION = "0.4.0" ;
31194
+ var CLI_VERSION = "0.5.0" ;
32137
31195
  async function handleVersion(_options) {
32138
31196
  console.log(heading("RelyIO CLI Version Information"));
32139
31197
  const versionInfo = [
@@ -32504,7 +31562,7 @@ async function performStatusCheck(branchName, options) {
32504
31562
  hasChanges: response.hasChanges ?? false,
32505
31563
  ...response.changes ?? defaultChanges,
32506
31564
  validationErrors: response.validationErrors ?? [],
32507
- validationWarnings: response.validationWarnings ?? [],
31565
+ validationWarnings: response.validationWarnings,
32508
31566
  recommendations: response.recommendations ?? [],
32509
31567
  statistics: response.statistics,
32510
31568
  complexityAnalysis: response.complexityAnalysis
@@ -33679,19 +32737,23 @@ async function handleDiff(options) {
33679
32737
  schemaSourcePath: result.schemaSourcePath,
33680
32738
  hasChanges: result.hasChanges,
33681
32739
  changes: result.changes,
33682
- localSchema: result.localSchema
32740
+ localSchema: result.localSchema,
32741
+ validationWarnings: result.validationWarnings
33683
32742
  })
33684
32743
  );
33685
32744
  return;
33686
32745
  }
33687
32746
  displayDiffHeader(result.branchName, result.branchId);
33688
- if (!result.hasChanges) {
32747
+ if (result.hasChanges) {
32748
+ displayDiffSummary(result.changes);
32749
+ displayDetailedDiff(result.changes, result.localSchema);
32750
+ } else {
33689
32751
  console.log(THEME3.muted("No changes between local schema and remote branch"));
33690
32752
  console.log();
33691
- return;
33692
32753
  }
33693
- displayDiffSummary(result.changes);
33694
- displayDetailedDiff(result.changes, result.localSchema);
32754
+ if (result.validationWarnings.length > 0) {
32755
+ console.log(formatValidationErrors(result.validationWarnings));
32756
+ }
33695
32757
  return;
33696
32758
  }
33697
32759
  if (json) {
@@ -36403,7 +35465,7 @@ Examples:
36403
35465
  }
36404
35466
 
36405
35467
  // src/index.ts
36406
- var CLI_VERSION3 = "0.4.0" ;
35468
+ var CLI_VERSION3 = "0.5.0" ;
36407
35469
  var program = new Command();
36408
35470
  program.name("relyio").description("RelyIO CLI - Content Management, Simplified").version(CLI_VERSION3);
36409
35471
  registerGlobalOptions(program);