@contrail/cli 3.5.0 → 3.5.1-alpha.63493a5

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 (67) hide show
  1. package/README.md +3 -3
  2. package/dist/api/types.d.ts +0 -5
  3. package/dist/api/types.js +1 -36
  4. package/dist/commands/types/getAll.js +0 -18
  5. package/dist/commands/types/loadAll.js +0 -2
  6. package/dist/commands/types/mergeTypeProperties.js +0 -5
  7. package/dist/commands/types/uploadOptionSetHierarchies.js +0 -117
  8. package/dist/commands/types/uploadOptionSets.js +1 -1
  9. package/dist/commands/types/uploadRuleSets.js +2 -85
  10. package/dist/commands/types/uploadTypePolicies.js +2 -86
  11. package/dist/commands/types/uploadTypePropertyPolicies.d.ts +0 -1
  12. package/dist/commands/types/uploadTypePropertyPolicies.js +3 -139
  13. package/dist/commands/types/uploadUsageInfo.d.ts +3 -0
  14. package/dist/commands/types/uploadUsageInfo.js +4 -1
  15. package/dist/helpers/types/type-uploader.js +1 -1
  16. package/dist/templates/typescript/tsup.config.ts +0 -0
  17. package/node_modules/@contrail/loader/lib/color-processor.js +9 -23
  18. package/node_modules/@contrail/loader/lib/color-processor.spec.js +112 -62
  19. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.d.ts +1 -1
  20. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.js +2 -0
  21. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +4 -0
  22. package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +26 -0
  23. package/node_modules/@contrail/loader/lib/common/color-validation.js +163 -0
  24. package/node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts +1 -0
  25. package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +102 -0
  26. package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +0 -1
  27. package/node_modules/@contrail/loader/lib/constants.d.ts +0 -3
  28. package/node_modules/@contrail/loader/lib/constants.js +1 -4
  29. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +9 -23
  30. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +112 -62
  31. package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +0 -1
  32. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +0 -1
  33. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +8 -22
  34. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +43 -1
  35. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +5 -0
  36. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +562 -0
  37. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts +1 -0
  38. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +231 -0
  39. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +77 -3
  40. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +82 -0
  41. package/node_modules/@contrail/loader/lib/pipeline/preview.js +0 -5
  42. package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
  43. package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
  44. package/node_modules/@contrail/loader/lib/pipeline/transform.js +18 -0
  45. package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +3 -2
  46. package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +5 -2
  47. package/node_modules/@contrail/loader/lib/pipeline/types.js +2 -0
  48. package/node_modules/@contrail/loader/lib/pipeline/validate.js +184 -437
  49. package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +157 -53
  50. package/node_modules/@contrail/loader/package.json +3 -2
  51. package/node_modules/@contrail/sdk/.github/workflows/publish-to-npm.yml +120 -0
  52. package/node_modules/@contrail/sdk/lib/auth/login.js +42 -3
  53. package/node_modules/@contrail/sdk/lib/core/request.d.ts +3 -0
  54. package/node_modules/@contrail/sdk/lib/core/request.js +100 -4
  55. package/node_modules/@contrail/sdk/lib/entities/entities.client.js +1 -1
  56. package/node_modules/@contrail/sdk/lib/entities/entity.resource.registry.js +1 -0
  57. package/node_modules/@contrail/sdk/lib/files/files.d.ts +13 -3
  58. package/node_modules/@contrail/sdk/lib/files/files.js +66 -25
  59. package/node_modules/@contrail/sdk/package.json +4 -1
  60. package/node_modules/validate-color/LICENSE +21 -0
  61. package/node_modules/validate-color/README.md +265 -0
  62. package/node_modules/validate-color/lib/index.d.ts +10 -0
  63. package/node_modules/validate-color/lib/index.js +1 -0
  64. package/node_modules/validate-color/package.json +91 -0
  65. package/node_modules/validate-color/test/redos.js +29 -0
  66. package/oclif.manifest.json +5 -2
  67. package/package.json +3 -3
@@ -2,15 +2,33 @@
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");
5
7
  const entity_processor_v2_1 = require("../entities-v2/entity-processor-v2");
6
8
  const processor_registry_1 = require("../processor-registry");
7
9
  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');
8
13
  const types_2 = require("./types");
9
14
  const CONFIG_CATEGORY = types_2.PreValidationCategory.CONFIG;
10
15
  const MISSING_REQUIRED_FIELD_CATEGORY = types_2.PreValidationCategory.MISSING_REQUIRED_FIELD;
11
16
  const REFERENCE_NOT_FOUND_CATEGORY = types_2.PreValidationCategory.REFERENCE_DATA_NOT_FOUND;
12
17
  const FAMILY_INCONSISTENT_CATEGORY = types_2.PreValidationCategory.FAMILY_PROPERTY_VALUES_INCONSISTENT;
13
18
  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
+ }
14
32
  function isEmpty(value) {
15
33
  return value === null || value === undefined || value === '';
16
34
  }
@@ -26,24 +44,15 @@ async function validate(rows, config, columnAnalysis, client) {
26
44
  if (!rows?.length) {
27
45
  return buildResult(issues);
28
46
  }
47
+ if (columnAnalysis.identifierIssues?.length) {
48
+ issues.push(...columnAnalysis.identifierIssues);
49
+ }
29
50
  const processorIssues = runProcessorValidation(rows, config);
30
51
  issues.push(...processorIssues);
31
52
  const itemNameIssues = validateItemNames(rows, config);
32
53
  issues.push(...itemNameIssues);
33
54
  const identifierConflictIssues = validateStaticIdentifierConflicts(config);
34
55
  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
- }
47
56
  const partialUpdateIssues = validatePartialAssortmentConfig(rows, config);
48
57
  issues.push(...partialUpdateIssues);
49
58
  const typePathExistenceIssues = validateTypePathExistence(columnAnalysis.typesByPath);
@@ -54,6 +63,12 @@ async function validate(rows, config, columnAnalysis, client) {
54
63
  issues.push(...suffixIssues);
55
64
  const selfRefIssues = validateSelfReferences(rows, config, columnAnalysis.typesByPath);
56
65
  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);
57
72
  const optionSetIssues = validateOptionSets(rows, columnAnalysis.typePropertyMaps);
58
73
  issues.push(...optionSetIssues);
59
74
  const booleanIssues = validateBooleanFormats(rows, columnAnalysis.typePropertyMaps);
@@ -143,7 +158,7 @@ function runProcessorValidation(rows, config) {
143
158
  for (const colIssue of error.columnIssues ?? []) {
144
159
  issues.push({
145
160
  severity: types_2.PreValidationSeverity.ERROR,
146
- category: CONFIG_CATEGORY,
161
+ category: issueTypeToCategory(colIssue.issueType),
147
162
  columnName: colIssue.columnName,
148
163
  propertySlug: colIssue.columnName,
149
164
  propertyType: '',
@@ -156,7 +171,7 @@ function runProcessorValidation(rows, config) {
156
171
  for (const colIssue of warning.columnIssues ?? []) {
157
172
  issues.push({
158
173
  severity: types_2.PreValidationSeverity.WARNING,
159
- category: CONFIG_CATEGORY,
174
+ category: issueTypeToCategory(colIssue.issueType),
160
175
  columnName: colIssue.columnName,
161
176
  propertySlug: colIssue.columnName,
162
177
  propertyType: '',
@@ -167,48 +182,89 @@ function runProcessorValidation(rows, config) {
167
182
  }
168
183
  return issues;
169
184
  }
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
+ }
170
223
  function validateOptionSets(rows, typePropertyMaps) {
171
224
  const { optionSetUsingTypePropertiesBySlug } = typePropertyMaps;
172
225
  const issues = [];
173
226
  const skipColumns = new Set();
227
+ const propertyForColumn = (key) => optionSetUsingTypePropertiesBySlug[key] ??
228
+ optionSetUsingTypePropertiesBySlug[(0, data_transformer_functions_1.removeOptionOrFamilySuffix)(key)];
174
229
  const affectedRowsByColumn = {};
230
+ const propertyByColumn = {};
175
231
  forEachRow(rows, (row, rowNumber) => {
176
232
  for (const [key, value] of Object.entries(row)) {
177
233
  if (isEmpty(value))
178
234
  continue;
179
- if (!optionSetUsingTypePropertiesBySlug[key])
235
+ const property = propertyForColumn(key);
236
+ if (!property)
180
237
  continue;
181
238
  if (!affectedRowsByColumn[key])
182
239
  affectedRowsByColumn[key] = [];
183
240
  affectedRowsByColumn[key].push({ row: rowNumber, value: String(value) });
241
+ propertyByColumn[key] = property;
184
242
  }
185
243
  });
186
- for (const [slug, property] of Object.entries(optionSetUsingTypePropertiesBySlug)) {
187
- if (!affectedRowsByColumn[slug])
188
- continue;
244
+ for (const [columnName, property] of Object.entries(propertyByColumn)) {
189
245
  if (!property.typePropertyOptionSetId) {
190
246
  issues.push({
191
247
  severity: types_2.PreValidationSeverity.ERROR,
192
248
  category: types_2.PreValidationCategory.OPTION_SET,
193
- columnName: slug,
249
+ columnName,
194
250
  propertySlug: property.slug,
195
251
  propertyType: property.propertyType,
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],
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],
198
254
  });
199
- skipColumns.add(slug);
255
+ skipColumns.add(columnName);
200
256
  }
201
257
  else if (!property.options || property.options.length === 0) {
202
258
  issues.push({
203
259
  severity: types_2.PreValidationSeverity.ERROR,
204
260
  category: types_2.PreValidationCategory.OPTION_SET,
205
- columnName: slug,
261
+ columnName,
206
262
  propertySlug: property.slug,
207
263
  propertyType: property.propertyType,
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],
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],
210
266
  });
211
- skipColumns.add(slug);
267
+ skipColumns.add(columnName);
212
268
  }
213
269
  }
214
270
  const issuesByColumn = {};
@@ -216,7 +272,7 @@ function validateOptionSets(rows, typePropertyMaps) {
216
272
  for (const [key, value] of Object.entries(row)) {
217
273
  if (isEmpty(value))
218
274
  continue;
219
- const property = optionSetUsingTypePropertiesBySlug[key];
275
+ const property = propertyForColumn(key);
220
276
  if (!property)
221
277
  continue;
222
278
  if (skipColumns.has(key))
@@ -598,211 +654,6 @@ function validateAllOverrideSuffixes(rows, config, columnAnalysis) {
598
654
  }
599
655
  return issues;
600
656
  }
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
- }
806
657
  function validateStaticIdentifierConflicts(config) {
807
658
  const issues = [];
808
659
  const conditionalTargets = new Set((config.conditionalColumns || []).map((col) => col.toProperty));
@@ -827,211 +678,6 @@ function validateStaticIdentifierConflicts(config) {
827
678
  }
828
679
  return issues;
829
680
  }
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
- }
1035
681
  function validatePartialAssortmentConfig(rows, config) {
1036
682
  const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
1037
683
  if (!isAssortmentLoad || !config.partialAssortmentUpdate)
@@ -1130,6 +776,107 @@ function buildObjectRefColumnChecks(typesByPath, isItemBased) {
1130
776
  }
1131
777
  return checks;
1132
778
  }
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
+ }
1133
880
  const NUMERIC_PROPERTY_TYPES = new Set(['number', 'currency', 'percent', 'sequence']);
1134
881
  function validateNumberPrecision(rows, typePropertyMaps) {
1135
882
  const { typePropertiesBySlug } = typePropertyMaps;