@contrail/cli 3.5.1-alpha.68d57e0 → 3.5.1-alpha.9da7ac5

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 (43) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@contrail/loader/lib/color-processor.js +23 -9
  3. package/node_modules/@contrail/loader/lib/color-processor.spec.js +62 -112
  4. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.d.ts +1 -1
  5. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.js +0 -2
  6. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +0 -4
  7. package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +1 -0
  8. package/node_modules/@contrail/loader/lib/constants.d.ts +3 -0
  9. package/node_modules/@contrail/loader/lib/constants.js +4 -1
  10. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +23 -9
  11. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +62 -112
  12. package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +1 -0
  13. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +1 -0
  14. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +22 -8
  15. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +1 -43
  16. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +3 -77
  17. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +0 -82
  18. package/node_modules/@contrail/loader/lib/pipeline/preview.js +5 -0
  19. package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
  20. package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
  21. package/node_modules/@contrail/loader/lib/pipeline/transform.js +0 -18
  22. package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +2 -3
  23. package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +2 -5
  24. package/node_modules/@contrail/loader/lib/pipeline/types.js +0 -2
  25. package/node_modules/@contrail/loader/lib/pipeline/validate.js +437 -184
  26. package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +53 -157
  27. package/node_modules/@contrail/loader/package.json +2 -3
  28. package/oclif.manifest.json +1 -1
  29. package/package.json +2 -2
  30. package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +0 -26
  31. package/node_modules/@contrail/loader/lib/common/color-validation.js +0 -163
  32. package/node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts +0 -1
  33. package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +0 -102
  34. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +0 -5
  35. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +0 -562
  36. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts +0 -1
  37. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +0 -231
  38. package/node_modules/validate-color/LICENSE +0 -21
  39. package/node_modules/validate-color/README.md +0 -265
  40. package/node_modules/validate-color/lib/index.d.ts +0 -10
  41. package/node_modules/validate-color/lib/index.js +0 -1
  42. package/node_modules/validate-color/package.json +0 -91
  43. package/node_modules/validate-color/test/redos.js +0 -29
@@ -2,33 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = void 0;
4
4
  const types_1 = require("@contrail/types");
5
- const lodash_1 = require("lodash");
6
- const data_transformer_functions_1 = require("../common/data-transformer-functions");
7
5
  const entity_processor_v2_1 = require("../entities-v2/entity-processor-v2");
8
6
  const processor_registry_1 = require("../processor-registry");
9
7
  const entities_helper_1 = require("../entities-v2/entities-helper");
10
- const entities_helper_2 = require("../common/entities-helper");
11
- const chunk_by_url_length_1 = require("../common/chunk-by-url-length");
12
- const pLimit = require('p-limit');
13
8
  const types_2 = require("./types");
14
9
  const CONFIG_CATEGORY = types_2.PreValidationCategory.CONFIG;
15
10
  const MISSING_REQUIRED_FIELD_CATEGORY = types_2.PreValidationCategory.MISSING_REQUIRED_FIELD;
16
11
  const REFERENCE_NOT_FOUND_CATEGORY = types_2.PreValidationCategory.REFERENCE_DATA_NOT_FOUND;
17
12
  const FAMILY_INCONSISTENT_CATEGORY = types_2.PreValidationCategory.FAMILY_PROPERTY_VALUES_INCONSISTENT;
18
13
  const SELF_REFERENCE_CATEGORY = types_2.PreValidationCategory.SELF_REFERENCE;
19
- function issueTypeToCategory(issueType) {
20
- switch (issueType) {
21
- case types_2.IssueType.INVALID_DATA:
22
- return types_2.PreValidationCategory.INVALID_DATA;
23
- case types_2.IssueType.MISSING_DATA:
24
- return types_2.PreValidationCategory.MISSING_REQUIRED_FIELD;
25
- case types_2.IssueType.DUPLICATE_FEDERATED_ID:
26
- return types_2.PreValidationCategory.DUPLICATE_FEDERATED_ID;
27
- case types_2.IssueType.CONFIG:
28
- default:
29
- return types_2.PreValidationCategory.CONFIG;
30
- }
31
- }
32
14
  function isEmpty(value) {
33
15
  return value === null || value === undefined || value === '';
34
16
  }
@@ -44,15 +26,24 @@ async function validate(rows, config, columnAnalysis, client) {
44
26
  if (!rows?.length) {
45
27
  return buildResult(issues);
46
28
  }
47
- if (columnAnalysis.identifierIssues?.length) {
48
- issues.push(...columnAnalysis.identifierIssues);
49
- }
50
29
  const processorIssues = runProcessorValidation(rows, config);
51
30
  issues.push(...processorIssues);
52
31
  const itemNameIssues = validateItemNames(rows, config);
53
32
  issues.push(...itemNameIssues);
54
33
  const identifierConflictIssues = validateStaticIdentifierConflicts(config);
55
34
  issues.push(...identifierConflictIssues);
35
+ const projectItemColumnIssues = validateProjectItemColumns(rows, config);
36
+ issues.push(...projectItemColumnIssues);
37
+ if (client && projectItemColumnIssues.length === 0) {
38
+ const resolutionIssues = await validateAndResolveProjectIdentifiers(rows, config, client);
39
+ issues.push(...resolutionIssues);
40
+ }
41
+ const assortmentColumnIssues = validateAssortmentColumns(rows, config);
42
+ issues.push(...assortmentColumnIssues);
43
+ if (client && assortmentColumnIssues.length === 0) {
44
+ const assortmentIssues = await validateAndResolveAssortmentIdentifiers(rows, config, client);
45
+ issues.push(...assortmentIssues);
46
+ }
56
47
  const partialUpdateIssues = validatePartialAssortmentConfig(rows, config);
57
48
  issues.push(...partialUpdateIssues);
58
49
  const typePathExistenceIssues = validateTypePathExistence(columnAnalysis.typesByPath);
@@ -63,12 +54,6 @@ async function validate(rows, config, columnAnalysis, client) {
63
54
  issues.push(...suffixIssues);
64
55
  const selfRefIssues = validateSelfReferences(rows, config, columnAnalysis.typesByPath);
65
56
  issues.push(...selfRefIssues);
66
- if (client) {
67
- const objectRefIssues = await validateObjectReferences(rows, columnAnalysis.typesByPath, client);
68
- issues.push(...objectRefIssues);
69
- }
70
- const transformIssues = promoteTransformValidationErrors(columnAnalysis);
71
- issues.push(...transformIssues);
72
57
  const optionSetIssues = validateOptionSets(rows, columnAnalysis.typePropertyMaps);
73
58
  issues.push(...optionSetIssues);
74
59
  const booleanIssues = validateBooleanFormats(rows, columnAnalysis.typePropertyMaps);
@@ -158,7 +143,7 @@ function runProcessorValidation(rows, config) {
158
143
  for (const colIssue of error.columnIssues ?? []) {
159
144
  issues.push({
160
145
  severity: types_2.PreValidationSeverity.ERROR,
161
- category: issueTypeToCategory(colIssue.issueType),
146
+ category: CONFIG_CATEGORY,
162
147
  columnName: colIssue.columnName,
163
148
  propertySlug: colIssue.columnName,
164
149
  propertyType: '',
@@ -171,7 +156,7 @@ function runProcessorValidation(rows, config) {
171
156
  for (const colIssue of warning.columnIssues ?? []) {
172
157
  issues.push({
173
158
  severity: types_2.PreValidationSeverity.WARNING,
174
- category: issueTypeToCategory(colIssue.issueType),
159
+ category: CONFIG_CATEGORY,
175
160
  columnName: colIssue.columnName,
176
161
  propertySlug: colIssue.columnName,
177
162
  propertyType: '',
@@ -182,89 +167,48 @@ function runProcessorValidation(rows, config) {
182
167
  }
183
168
  return issues;
184
169
  }
185
- function promoteTransformValidationErrors(columnAnalysis) {
186
- const issues = [];
187
- const buckets = [
188
- ...(columnAnalysis.errors ?? []),
189
- ...(columnAnalysis.warnings ?? []),
190
- ];
191
- const optionSetMap = columnAnalysis.typePropertyMaps?.optionSetUsingTypePropertiesBySlug ?? {};
192
- const isOptionSetColumn = (col) => Boolean(optionSetMap[col] || optionSetMap[(0, data_transformer_functions_1.removeOptionOrFamilySuffix)(col)]);
193
- const grouped = new Map();
194
- for (const issue of buckets) {
195
- for (const ci of issue?.columnIssues ?? []) {
196
- if (ci?.issueType !== types_2.IssueType.VALIDATION_ERROR)
197
- continue;
198
- if (isOptionSetColumn(ci.columnName))
199
- continue;
200
- const key = `${ci.columnName}::${ci.message}`;
201
- if (!grouped.has(key)) {
202
- grouped.set(key, { columnName: ci.columnName, message: ci.message, affectedRows: [] });
203
- }
204
- grouped.get(key).affectedRows.push({
205
- row: issue.rowNumber ?? -1,
206
- value: ci.data ?? '',
207
- });
208
- }
209
- }
210
- for (const { columnName, message, affectedRows } of grouped.values()) {
211
- issues.push({
212
- severity: types_2.PreValidationSeverity.ERROR,
213
- category: types_2.PreValidationCategory.OPTION_SET,
214
- columnName,
215
- propertySlug: (0, data_transformer_functions_1.removeOptionOrFamilySuffix)(columnName),
216
- propertyType: 'multi_select',
217
- message,
218
- affectedRows,
219
- });
220
- }
221
- return issues;
222
- }
223
170
  function validateOptionSets(rows, typePropertyMaps) {
224
171
  const { optionSetUsingTypePropertiesBySlug } = typePropertyMaps;
225
172
  const issues = [];
226
173
  const skipColumns = new Set();
227
- const propertyForColumn = (key) => optionSetUsingTypePropertiesBySlug[key] ??
228
- optionSetUsingTypePropertiesBySlug[(0, data_transformer_functions_1.removeOptionOrFamilySuffix)(key)];
229
174
  const affectedRowsByColumn = {};
230
- const propertyByColumn = {};
231
175
  forEachRow(rows, (row, rowNumber) => {
232
176
  for (const [key, value] of Object.entries(row)) {
233
177
  if (isEmpty(value))
234
178
  continue;
235
- const property = propertyForColumn(key);
236
- if (!property)
179
+ if (!optionSetUsingTypePropertiesBySlug[key])
237
180
  continue;
238
181
  if (!affectedRowsByColumn[key])
239
182
  affectedRowsByColumn[key] = [];
240
183
  affectedRowsByColumn[key].push({ row: rowNumber, value: String(value) });
241
- propertyByColumn[key] = property;
242
184
  }
243
185
  });
244
- for (const [columnName, property] of Object.entries(propertyByColumn)) {
186
+ for (const [slug, property] of Object.entries(optionSetUsingTypePropertiesBySlug)) {
187
+ if (!affectedRowsByColumn[slug])
188
+ continue;
245
189
  if (!property.typePropertyOptionSetId) {
246
190
  issues.push({
247
191
  severity: types_2.PreValidationSeverity.ERROR,
248
192
  category: types_2.PreValidationCategory.OPTION_SET,
249
- columnName,
193
+ columnName: slug,
250
194
  propertySlug: property.slug,
251
195
  propertyType: property.propertyType,
252
- message: `Property "${columnName}" is type ${PROPERTY_TYPE_DISPLAY[property.propertyType] ?? property.propertyType} but has no option set attached. Attach an option set to this property or change the property type.`,
253
- affectedRows: affectedRowsByColumn[columnName],
196
+ message: `Property "${slug}" is type ${PROPERTY_TYPE_DISPLAY[property.propertyType] ?? property.propertyType} but has no option set attached. Attach an option set to this property or change the property type.`,
197
+ affectedRows: affectedRowsByColumn[slug],
254
198
  });
255
- skipColumns.add(columnName);
199
+ skipColumns.add(slug);
256
200
  }
257
201
  else if (!property.options || property.options.length === 0) {
258
202
  issues.push({
259
203
  severity: types_2.PreValidationSeverity.ERROR,
260
204
  category: types_2.PreValidationCategory.OPTION_SET,
261
- columnName,
205
+ columnName: slug,
262
206
  propertySlug: property.slug,
263
207
  propertyType: property.propertyType,
264
- message: `Option set for property "${columnName}" is empty. Values in this column cannot be validated. Populate the option set or remove data from this column.`,
265
- affectedRows: affectedRowsByColumn[columnName],
208
+ message: `Option set for property "${slug}" is empty. Values in this column cannot be validated. Populate the option set or remove data from this column.`,
209
+ affectedRows: affectedRowsByColumn[slug],
266
210
  });
267
- skipColumns.add(columnName);
211
+ skipColumns.add(slug);
268
212
  }
269
213
  }
270
214
  const issuesByColumn = {};
@@ -272,7 +216,7 @@ function validateOptionSets(rows, typePropertyMaps) {
272
216
  for (const [key, value] of Object.entries(row)) {
273
217
  if (isEmpty(value))
274
218
  continue;
275
- const property = propertyForColumn(key);
219
+ const property = optionSetUsingTypePropertiesBySlug[key];
276
220
  if (!property)
277
221
  continue;
278
222
  if (skipColumns.has(key))
@@ -654,6 +598,211 @@ function validateAllOverrideSuffixes(rows, config, columnAnalysis) {
654
598
  }
655
599
  return issues;
656
600
  }
601
+ async function validateAndResolveProjectIdentifiers(rows, config, client) {
602
+ const isProjectItemLoad = config.loadType.some((lt) => lt === types_2.LoadType.PROJECT_ITEM);
603
+ if (!isProjectItemLoad)
604
+ return [];
605
+ const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier);
606
+ if (hasWorkspaceIdentifier) {
607
+ return validateWorkspaceIdentifiers(rows, client);
608
+ }
609
+ return validateProjectIds(rows, client);
610
+ }
611
+ async function validateWorkspaceIdentifiers(rows, client) {
612
+ const issues = [];
613
+ const cache = {};
614
+ const uniqueIdentifiers = new Set();
615
+ for (const row of rows) {
616
+ if (row.workspaceIdentifier)
617
+ uniqueIdentifiers.add(row.workspaceIdentifier);
618
+ }
619
+ for (const wsIdentifier of uniqueIdentifiers) {
620
+ try {
621
+ const project = await resolveProjectIdFromWorkspace(client, wsIdentifier);
622
+ cache[wsIdentifier] = project;
623
+ if (!project) {
624
+ const affectedRows = [];
625
+ rows.forEach((row, index) => {
626
+ if (row.workspaceIdentifier === wsIdentifier) {
627
+ affectedRows.push({ row: index + 2, value: wsIdentifier });
628
+ }
629
+ });
630
+ issues.push({
631
+ severity: types_2.PreValidationSeverity.ERROR,
632
+ category: REFERENCE_NOT_FOUND_CATEGORY,
633
+ columnName: 'workspaceIdentifier',
634
+ propertySlug: 'workspaceIdentifier',
635
+ propertyType: 'string',
636
+ message: `Could not resolve workspace identifier "${wsIdentifier}" to a project. Verify the workspace exists and is a PROJECT type workspace.`,
637
+ affectedRows,
638
+ });
639
+ }
640
+ }
641
+ catch (e) {
642
+ issues.push({
643
+ severity: types_2.PreValidationSeverity.ERROR,
644
+ category: REFERENCE_NOT_FOUND_CATEGORY,
645
+ columnName: 'workspaceIdentifier',
646
+ propertySlug: 'workspaceIdentifier',
647
+ propertyType: 'string',
648
+ message: `Failed to resolve workspace identifier "${wsIdentifier}": ${e.message}`,
649
+ affectedRows: [],
650
+ });
651
+ }
652
+ }
653
+ if (issues.length === 0) {
654
+ for (const row of rows) {
655
+ if (row.workspaceIdentifier) {
656
+ const project = cache[row.workspaceIdentifier];
657
+ row.projectId = project.id;
658
+ row.projectName = project.name;
659
+ delete row.workspaceIdentifier;
660
+ }
661
+ }
662
+ }
663
+ return issues;
664
+ }
665
+ async function validateProjectIds(rows, client) {
666
+ const issues = [];
667
+ const nameCache = {};
668
+ const uniqueProjectIds = new Set();
669
+ for (const row of rows) {
670
+ if (row.projectId)
671
+ uniqueProjectIds.add(row.projectId);
672
+ }
673
+ if (uniqueProjectIds.size === 0)
674
+ return [];
675
+ for (const projectId of uniqueProjectIds) {
676
+ try {
677
+ const project = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
678
+ entityName: 'project',
679
+ id: projectId,
680
+ skipHydrateRelationships: true,
681
+ });
682
+ if (!project) {
683
+ const affectedRows = [];
684
+ rows.forEach((row, index) => {
685
+ if (row.projectId === projectId) {
686
+ affectedRows.push({ row: index + 2, value: projectId });
687
+ }
688
+ });
689
+ issues.push({
690
+ severity: types_2.PreValidationSeverity.ERROR,
691
+ category: REFERENCE_NOT_FOUND_CATEGORY,
692
+ columnName: 'projectId',
693
+ propertySlug: 'projectId',
694
+ propertyType: 'string',
695
+ message: `Project "${projectId}" not found. Verify the project ID is correct and the project exists.`,
696
+ affectedRows,
697
+ });
698
+ }
699
+ else {
700
+ nameCache[projectId] = project.name || project.identifier || projectId;
701
+ }
702
+ }
703
+ catch (e) {
704
+ const affectedRows = [];
705
+ rows.forEach((row, index) => {
706
+ if (row.projectId === projectId) {
707
+ affectedRows.push({ row: index + 2, value: projectId });
708
+ }
709
+ });
710
+ issues.push({
711
+ severity: types_2.PreValidationSeverity.ERROR,
712
+ category: REFERENCE_NOT_FOUND_CATEGORY,
713
+ columnName: 'projectId',
714
+ propertySlug: 'projectId',
715
+ propertyType: 'string',
716
+ message: `Failed to validate project "${projectId}": ${e.message}`,
717
+ affectedRows,
718
+ });
719
+ }
720
+ }
721
+ if (issues.length === 0) {
722
+ for (const row of rows) {
723
+ if (row.projectId && nameCache[row.projectId]) {
724
+ row.projectName = nameCache[row.projectId];
725
+ }
726
+ }
727
+ }
728
+ return issues;
729
+ }
730
+ async function resolveProjectIdFromWorkspace(client, workspaceIdentifier) {
731
+ const workspaces = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
732
+ entityName: 'workspace',
733
+ criteria: {
734
+ identifier: workspaceIdentifier,
735
+ workspaceType: 'PROJECT',
736
+ },
737
+ skipHydrateRelationships: true,
738
+ });
739
+ if (!workspaces?.length)
740
+ return null;
741
+ const workspaceId = workspaces[0]?.id;
742
+ if (!workspaceId)
743
+ return null;
744
+ const workspace = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
745
+ entityName: 'workspace',
746
+ id: workspaceId,
747
+ relations: ['project'],
748
+ skipHydrateRelationships: true,
749
+ });
750
+ const project = workspace?.project;
751
+ if (!project?.id)
752
+ return null;
753
+ return { id: project.id, name: project.name || project.id };
754
+ }
755
+ function validateProjectItemColumns(rows, config) {
756
+ const isProjectItemLoad = config.loadType.some((lt) => lt === types_2.LoadType.PROJECT_ITEM);
757
+ if (!isProjectItemLoad)
758
+ return [];
759
+ const issues = [];
760
+ const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier !== undefined);
761
+ const hasProjectId = rows.some((row) => row.projectId !== undefined);
762
+ if (hasWorkspaceIdentifier && hasProjectId) {
763
+ issues.push({
764
+ severity: types_2.PreValidationSeverity.ERROR,
765
+ category: CONFIG_CATEGORY,
766
+ columnName: 'workspaceIdentifier / projectId',
767
+ propertySlug: '',
768
+ propertyType: '',
769
+ message: 'Rows contain both "workspaceIdentifier" and "projectId" columns. Use one or the other to identify the target project.',
770
+ affectedRows: [],
771
+ });
772
+ return issues;
773
+ }
774
+ if (!hasWorkspaceIdentifier && !hasProjectId) {
775
+ issues.push({
776
+ severity: types_2.PreValidationSeverity.ERROR,
777
+ category: CONFIG_CATEGORY,
778
+ columnName: 'workspaceIdentifier / projectId',
779
+ propertySlug: '',
780
+ propertyType: '',
781
+ message: 'PROJECT_ITEM loads require a "workspaceIdentifier" or "projectId" column (or a config-level workspaceIdentifier) to identify the target project.',
782
+ affectedRows: [],
783
+ });
784
+ return issues;
785
+ }
786
+ const columnName = hasWorkspaceIdentifier ? 'workspaceIdentifier' : 'projectId';
787
+ const emptyRows = [];
788
+ forEachRow(rows, (row, rowNumber) => {
789
+ if (isEmpty(row[columnName])) {
790
+ emptyRows.push({ row: rowNumber, value: '' });
791
+ }
792
+ });
793
+ if (emptyRows.length > 0) {
794
+ issues.push({
795
+ severity: types_2.PreValidationSeverity.ERROR,
796
+ category: MISSING_REQUIRED_FIELD_CATEGORY,
797
+ columnName,
798
+ propertySlug: columnName,
799
+ propertyType: 'string',
800
+ message: `"${columnName}" cannot be empty for project-item rows. ${emptyRows.length} row(s) have an empty value.`,
801
+ affectedRows: emptyRows,
802
+ });
803
+ }
804
+ return issues;
805
+ }
657
806
  function validateStaticIdentifierConflicts(config) {
658
807
  const issues = [];
659
808
  const conditionalTargets = new Set((config.conditionalColumns || []).map((col) => col.toProperty));
@@ -678,6 +827,211 @@ function validateStaticIdentifierConflicts(config) {
678
827
  }
679
828
  return issues;
680
829
  }
830
+ function validateAssortmentColumns(rows, config) {
831
+ const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
832
+ if (!isAssortmentLoad)
833
+ return [];
834
+ const issues = [];
835
+ const hasAssortmentId = rows.some((row) => row.assortmentId !== undefined);
836
+ const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier !== undefined);
837
+ const assortmentIdFromConditions = (config.conditionalColumns || []).some((col) => (col.toProperty === 'assortmentId' || col.toProperty === 'assortmentIdentifier') && col.conditions?.length);
838
+ if (!hasAssortmentId && !hasAssortmentIdentifier) {
839
+ issues.push({
840
+ severity: types_2.PreValidationSeverity.ERROR,
841
+ category: CONFIG_CATEGORY,
842
+ columnName: 'assortmentId / assortmentIdentifier',
843
+ propertySlug: '',
844
+ propertyType: '',
845
+ message: 'ASSORTMENT loads require an "assortmentId" or "assortmentIdentifier" column (or a config-level assortmentId/assortmentIdentifier) to identify the target assortment.',
846
+ affectedRows: [],
847
+ });
848
+ return issues;
849
+ }
850
+ const bothRows = [];
851
+ forEachRow(rows, (row, rowNumber) => {
852
+ const hasId = !isEmpty(row.assortmentId);
853
+ const hasIdent = !isEmpty(row.assortmentIdentifier);
854
+ if (hasId && hasIdent) {
855
+ bothRows.push({ row: rowNumber, value: `${row.assortmentId} / ${row.assortmentIdentifier}` });
856
+ }
857
+ });
858
+ if (bothRows.length > 0) {
859
+ issues.push({
860
+ severity: types_2.PreValidationSeverity.ERROR,
861
+ category: CONFIG_CATEGORY,
862
+ columnName: 'assortmentId / assortmentIdentifier',
863
+ propertySlug: '',
864
+ propertyType: '',
865
+ message: `${bothRows.length} row(s) have both "assortmentId" and "assortmentIdentifier". Each row must use one or the other.`,
866
+ affectedRows: bothRows,
867
+ });
868
+ return issues;
869
+ }
870
+ const emptyIdRows = [];
871
+ const emptyIdentRows = [];
872
+ forEachRow(rows, (row, rowNumber) => {
873
+ if (hasAssortmentId && row.assortmentId !== undefined) {
874
+ if (isEmpty(row.assortmentId)) {
875
+ emptyIdRows.push({ row: rowNumber, value: '' });
876
+ }
877
+ }
878
+ else if (hasAssortmentId && row.assortmentId === undefined && !assortmentIdFromConditions) {
879
+ emptyIdRows.push({ row: rowNumber, value: '' });
880
+ }
881
+ if (hasAssortmentIdentifier && row.assortmentIdentifier !== undefined) {
882
+ if (isEmpty(row.assortmentIdentifier)) {
883
+ emptyIdentRows.push({ row: rowNumber, value: '' });
884
+ }
885
+ }
886
+ });
887
+ if (emptyIdRows.length > 0) {
888
+ issues.push({
889
+ severity: types_2.PreValidationSeverity.ERROR,
890
+ category: MISSING_REQUIRED_FIELD_CATEGORY,
891
+ columnName: 'assortmentId',
892
+ propertySlug: 'assortmentId',
893
+ propertyType: 'string',
894
+ message: `"assortmentId" cannot be empty for assortment rows. ${emptyIdRows.length} row(s) have an empty value.`,
895
+ affectedRows: emptyIdRows,
896
+ });
897
+ }
898
+ if (emptyIdentRows.length > 0) {
899
+ issues.push({
900
+ severity: types_2.PreValidationSeverity.ERROR,
901
+ category: MISSING_REQUIRED_FIELD_CATEGORY,
902
+ columnName: 'assortmentIdentifier',
903
+ propertySlug: 'assortmentIdentifier',
904
+ propertyType: 'string',
905
+ message: `"assortmentIdentifier" cannot be empty for assortment rows. ${emptyIdentRows.length} row(s) have an empty value.`,
906
+ affectedRows: emptyIdentRows,
907
+ });
908
+ }
909
+ return issues;
910
+ }
911
+ async function validateAndResolveAssortmentIdentifiers(rows, config, client) {
912
+ const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
913
+ if (!isAssortmentLoad)
914
+ return [];
915
+ const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier);
916
+ if (hasAssortmentIdentifier) {
917
+ return resolveAssortmentIdentifiers(rows, client);
918
+ }
919
+ return validateAssortmentIds(rows, client);
920
+ }
921
+ async function resolveAssortmentIdentifiers(rows, client) {
922
+ const issues = [];
923
+ const cache = {};
924
+ const uniqueIdentifiers = new Set();
925
+ for (const row of rows) {
926
+ if (row.assortmentIdentifier)
927
+ uniqueIdentifiers.add(row.assortmentIdentifier);
928
+ }
929
+ for (const identifier of uniqueIdentifiers) {
930
+ try {
931
+ const assortments = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
932
+ entityName: 'assortment',
933
+ criteria: { identifier },
934
+ skipHydrateRelationships: true,
935
+ });
936
+ if (!assortments?.length) {
937
+ cache[identifier] = null;
938
+ const affectedRows = [];
939
+ rows.forEach((row, index) => {
940
+ if (row.assortmentIdentifier === identifier) {
941
+ affectedRows.push({ row: index + 2, value: identifier });
942
+ }
943
+ });
944
+ issues.push({
945
+ severity: types_2.PreValidationSeverity.ERROR,
946
+ category: REFERENCE_NOT_FOUND_CATEGORY,
947
+ columnName: 'assortmentIdentifier',
948
+ propertySlug: 'assortmentIdentifier',
949
+ propertyType: 'string',
950
+ message: `Assortment with identifier "${identifier}" not found. Verify the identifier is correct and the assortment exists.`,
951
+ affectedRows,
952
+ });
953
+ }
954
+ else {
955
+ const assortment = assortments[0];
956
+ cache[identifier] = { id: assortment.id, name: assortment.name || assortment.id };
957
+ }
958
+ }
959
+ catch (e) {
960
+ issues.push({
961
+ severity: types_2.PreValidationSeverity.ERROR,
962
+ category: REFERENCE_NOT_FOUND_CATEGORY,
963
+ columnName: 'assortmentIdentifier',
964
+ propertySlug: 'assortmentIdentifier',
965
+ propertyType: 'string',
966
+ message: `Failed to resolve assortment identifier "${identifier}": ${e.message}`,
967
+ affectedRows: [],
968
+ });
969
+ }
970
+ }
971
+ if (issues.length === 0) {
972
+ for (const row of rows) {
973
+ if (row.assortmentIdentifier) {
974
+ const assortment = cache[row.assortmentIdentifier];
975
+ row.assortmentId = assortment.id;
976
+ row.assortmentName = assortment.name;
977
+ delete row.assortmentIdentifier;
978
+ }
979
+ }
980
+ }
981
+ return issues;
982
+ }
983
+ async function validateAssortmentIds(rows, client) {
984
+ const issues = [];
985
+ const nameCache = {};
986
+ const uniqueIds = new Set();
987
+ for (const row of rows) {
988
+ if (row.assortmentId)
989
+ uniqueIds.add(row.assortmentId);
990
+ }
991
+ if (uniqueIds.size === 0)
992
+ return [];
993
+ for (const assortmentId of uniqueIds) {
994
+ try {
995
+ const assortment = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
996
+ entityName: 'assortment',
997
+ id: assortmentId,
998
+ skipHydrateRelationships: true,
999
+ });
1000
+ if (assortment) {
1001
+ nameCache[assortmentId] = assortment.name || assortmentId;
1002
+ }
1003
+ }
1004
+ catch (e) {
1005
+ const affectedRows = [];
1006
+ rows.forEach((row, index) => {
1007
+ if (row.assortmentId === assortmentId) {
1008
+ affectedRows.push({ row: index + 2, value: assortmentId });
1009
+ }
1010
+ });
1011
+ const parsedDetails = typeof e?.details === 'string' ? JSON.parse(e.details).statusCode : e?.details?.statusCode;
1012
+ const message = parsedDetails === 404
1013
+ ? `Assortment "${assortmentId}" not found. Verify the assortment ID is correct and the assortment exists.`
1014
+ : `Failed to validate assortment "${assortmentId}": ${e.message}`;
1015
+ issues.push({
1016
+ severity: types_2.PreValidationSeverity.ERROR,
1017
+ category: REFERENCE_NOT_FOUND_CATEGORY,
1018
+ columnName: 'assortmentId',
1019
+ propertySlug: 'assortmentId',
1020
+ propertyType: 'string',
1021
+ message,
1022
+ affectedRows,
1023
+ });
1024
+ }
1025
+ }
1026
+ if (issues.length === 0) {
1027
+ for (const row of rows) {
1028
+ if (row.assortmentId && nameCache[row.assortmentId]) {
1029
+ row.assortmentName = nameCache[row.assortmentId];
1030
+ }
1031
+ }
1032
+ }
1033
+ return issues;
1034
+ }
681
1035
  function validatePartialAssortmentConfig(rows, config) {
682
1036
  const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
683
1037
  if (!isAssortmentLoad || !config.partialAssortmentUpdate)
@@ -776,107 +1130,6 @@ function buildObjectRefColumnChecks(typesByPath, isItemBased) {
776
1130
  }
777
1131
  return checks;
778
1132
  }
779
- async function validateObjectReferences(rows, typesByPath, client) {
780
- const objectRefColumns = [];
781
- for (const typePath of Object.keys(typesByPath ?? {})) {
782
- const typeObj = typesByPath[typePath];
783
- for (const prop of typeObj?.objectReferenceProperties ?? []) {
784
- objectRefColumns.push({
785
- slug: prop.slug,
786
- rootType: prop.referencedTypeRootSlug,
787
- referencedTypePath: prop.referencedTypePath,
788
- });
789
- }
790
- }
791
- if (objectRefColumns.length === 0)
792
- return [];
793
- const fedIdsByRootType = {};
794
- const prefixFedId = (fedId, referencedTypePath) => {
795
- if (referencedTypePath?.startsWith('custom-entity') && !fedId.startsWith('custom-entity:')) {
796
- return `${referencedTypePath}:${fedId}`;
797
- }
798
- return fedId;
799
- };
800
- for (const row of rows) {
801
- for (const ref of objectRefColumns) {
802
- if (!ref.rootType)
803
- continue;
804
- const variants = (0, entities_helper_2.getPropertyVariants)(row, ref.slug, 'values');
805
- for (const rawValue of variants) {
806
- if (isEmpty(rawValue))
807
- continue;
808
- const fedId = prefixFedId(String(rawValue), ref.referencedTypePath);
809
- if (!fedIdsByRootType[ref.rootType])
810
- fedIdsByRootType[ref.rootType] = [];
811
- fedIdsByRootType[ref.rootType].push(fedId);
812
- }
813
- }
814
- }
815
- const limit = pLimit(5);
816
- const resolvedFedIds = new Set();
817
- for (const rootType of Object.keys(fedIdsByRootType)) {
818
- const uniqueFedIds = [...new Set(fedIdsByRootType[rootType])];
819
- if (uniqueFedIds.length === 0)
820
- continue;
821
- const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(uniqueFedIds);
822
- const promises = [];
823
- for (const chunk of chunks) {
824
- if (!chunk?.length)
825
- continue;
826
- promises.push(limit(async () => {
827
- const batch = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
828
- entityName: rootType,
829
- federatedIds: (0, lodash_1.uniq)(chunk),
830
- skipHydrateRelationships: true,
831
- });
832
- if (Array.isArray(batch)) {
833
- for (const entity of batch) {
834
- if (entity?.federatedId)
835
- resolvedFedIds.add(entity.federatedId);
836
- }
837
- }
838
- }));
839
- }
840
- await Promise.all(promises);
841
- }
842
- const missingByColumnAndValue = {};
843
- forEachRow(rows, (row, rowNumber) => {
844
- for (const ref of objectRefColumns) {
845
- if (!ref.rootType)
846
- continue;
847
- const variants = (0, entities_helper_2.getPropertyVariants)(row, ref.slug, 'keyValue');
848
- for (const variant of variants) {
849
- if (isEmpty(variant.value))
850
- continue;
851
- const rawValue = String(variant.value);
852
- const lookupKey = prefixFedId(rawValue, ref.referencedTypePath);
853
- if (resolvedFedIds.has(lookupKey))
854
- continue;
855
- if (!missingByColumnAndValue[variant.key])
856
- missingByColumnAndValue[variant.key] = new Map();
857
- const byValue = missingByColumnAndValue[variant.key];
858
- if (!byValue.has(rawValue))
859
- byValue.set(rawValue, []);
860
- byValue.get(rawValue).push({ row: rowNumber, value: rawValue });
861
- }
862
- }
863
- });
864
- const issues = [];
865
- for (const [columnName, byValue] of Object.entries(missingByColumnAndValue)) {
866
- for (const [missingValue, affectedRows] of byValue) {
867
- issues.push({
868
- severity: types_2.PreValidationSeverity.WARNING,
869
- category: REFERENCE_NOT_FOUND_CATEGORY,
870
- columnName,
871
- propertySlug: columnName,
872
- propertyType: 'object_reference',
873
- message: `Value "${missingValue}" does not match any existing entity for object reference property "${columnName}".`,
874
- affectedRows,
875
- });
876
- }
877
- }
878
- return issues;
879
- }
880
1133
  const NUMERIC_PROPERTY_TYPES = new Set(['number', 'currency', 'percent', 'sequence']);
881
1134
  function validateNumberPrecision(rows, typePropertyMaps) {
882
1135
  const { typePropertiesBySlug } = typePropertyMaps;