@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.
- package/README.md +1 -1
- package/node_modules/@contrail/loader/lib/color-processor.js +23 -9
- package/node_modules/@contrail/loader/lib/color-processor.spec.js +62 -112
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.d.ts +1 -1
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.js +0 -2
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +0 -4
- package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +1 -0
- package/node_modules/@contrail/loader/lib/constants.d.ts +3 -0
- package/node_modules/@contrail/loader/lib/constants.js +4 -1
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +23 -9
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +62 -112
- package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +1 -0
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +1 -0
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +22 -8
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +1 -43
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +3 -77
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +0 -82
- package/node_modules/@contrail/loader/lib/pipeline/preview.js +5 -0
- package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
- package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
- package/node_modules/@contrail/loader/lib/pipeline/transform.js +0 -18
- package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +2 -3
- package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +2 -5
- package/node_modules/@contrail/loader/lib/pipeline/types.js +0 -2
- package/node_modules/@contrail/loader/lib/pipeline/validate.js +437 -184
- package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +53 -157
- package/node_modules/@contrail/loader/package.json +2 -3
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +0 -26
- package/node_modules/@contrail/loader/lib/common/color-validation.js +0 -163
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts +0 -1
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +0 -102
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +0 -5
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +0 -562
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts +0 -1
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +0 -231
- package/node_modules/validate-color/LICENSE +0 -21
- package/node_modules/validate-color/README.md +0 -265
- package/node_modules/validate-color/lib/index.d.ts +0 -10
- package/node_modules/validate-color/lib/index.js +0 -1
- package/node_modules/validate-color/package.json +0 -91
- package/node_modules/validate-color/test/redos.js +0 -29
|
@@ -118,59 +118,6 @@ describe('validate', () => {
|
|
|
118
118
|
expect(multiSelectIssues).toHaveLength(1);
|
|
119
119
|
expect(multiSelectIssues[0].suggestedFix?.type).toBe(types_3.SuggestedFixType.CHANGE_SEPARATOR);
|
|
120
120
|
});
|
|
121
|
-
it('should validate Family/Option-suffixed columns against the base option set', async () => {
|
|
122
|
-
const analysis = makeAnalysis({
|
|
123
|
-
optionSetUsingTypePropertiesBySlug: {
|
|
124
|
-
vfAdvConceptsTech: {
|
|
125
|
-
slug: 'vfAdvConceptsTech',
|
|
126
|
-
label: 'Advanced Concepts Tech',
|
|
127
|
-
propertyType: 'multi_select',
|
|
128
|
-
typePropertyOptionSetId: 'os-suffix',
|
|
129
|
-
options: [
|
|
130
|
-
{ value: 'ortholite', display: 'Ortholite' },
|
|
131
|
-
{ value: 'hoverSpring', display: 'Hover Spring' },
|
|
132
|
-
],
|
|
133
|
-
optionLookupByValue: {
|
|
134
|
-
ortholite: { value: 'ortholite', display: 'Ortholite' },
|
|
135
|
-
hoverSpring: { value: 'hoverSpring', display: 'Hover Spring' },
|
|
136
|
-
},
|
|
137
|
-
optionLookupByDisplay: {
|
|
138
|
-
Ortholite: { value: 'ortholite', display: 'Ortholite' },
|
|
139
|
-
'Hover Spring': { value: 'hoverSpring', display: 'Hover Spring' },
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
const rows = [
|
|
145
|
-
{ federatedId: '1', name: 'A', vfAdvConceptsTechFamily: 'ortholite,GRIPMAX SLIP RESISTANT,hoverSpring' },
|
|
146
|
-
];
|
|
147
|
-
const result = await (0, validate_1.validate)(rows, baseConfig, analysis);
|
|
148
|
-
expect(result.valid).toBe(false);
|
|
149
|
-
const issues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.OPTION_SET && i.columnName === 'vfAdvConceptsTechFamily');
|
|
150
|
-
expect(issues.length).toBeGreaterThan(0);
|
|
151
|
-
const offending = issues.find((i) => i.message.includes('GRIPMAX SLIP RESISTANT'));
|
|
152
|
-
expect(offending).toBeDefined();
|
|
153
|
-
});
|
|
154
|
-
it('flags missing option set on suffixed columns (no typePropertyOptionSetId)', async () => {
|
|
155
|
-
const analysis = makeAnalysis({
|
|
156
|
-
optionSetUsingTypePropertiesBySlug: {
|
|
157
|
-
lifecycleStage: {
|
|
158
|
-
slug: 'lifecycleStage',
|
|
159
|
-
label: 'Lifecycle Stage',
|
|
160
|
-
propertyType: 'choice',
|
|
161
|
-
typePropertyOptionSetId: undefined,
|
|
162
|
-
options: [],
|
|
163
|
-
optionLookupByValue: {},
|
|
164
|
-
optionLookupByDisplay: {},
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
const rows = [{ federatedId: '1', name: 'A', lifecycleStageOption: 'concept' }];
|
|
169
|
-
const result = await (0, validate_1.validate)(rows, baseConfig, analysis);
|
|
170
|
-
const issues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.OPTION_SET && i.columnName === 'lifecycleStageOption');
|
|
171
|
-
expect(issues.length).toBe(1);
|
|
172
|
-
expect(issues[0].message).toContain('no option set attached');
|
|
173
|
-
});
|
|
174
121
|
it('should skip empty values in option set validation', async () => {
|
|
175
122
|
const analysis = makeAnalysis({
|
|
176
123
|
optionSetUsingTypePropertiesBySlug: {
|
|
@@ -584,6 +531,59 @@ describe('validate', () => {
|
|
|
584
531
|
expect(conflict).toBeUndefined();
|
|
585
532
|
});
|
|
586
533
|
});
|
|
534
|
+
describe('validateAssortmentColumns', () => {
|
|
535
|
+
it('should skip for non-ASSORTMENT loads', async () => {
|
|
536
|
+
const config = { loadType: [types_1.LoadType.ITEM] };
|
|
537
|
+
const rows = [{ itemFamilyFederatedId: 'fam-1', name: 'A' }];
|
|
538
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
539
|
+
const assortmentIssue = result.issues.find((i) => i.columnName?.includes('assortmentId'));
|
|
540
|
+
expect(assortmentIssue).toBeUndefined();
|
|
541
|
+
});
|
|
542
|
+
it('should error when no rows have assortmentId or assortmentIdentifier', async () => {
|
|
543
|
+
const config = { loadType: [types_1.LoadType.ASSORTMENT] };
|
|
544
|
+
const rows = [{ itemFamilyFederatedId: 'fam-1', name: 'A' }];
|
|
545
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
546
|
+
const issue = result.issues.find((i) => i.message.includes('ASSORTMENT loads require'));
|
|
547
|
+
expect(issue).toBeDefined();
|
|
548
|
+
expect(result.valid).toBe(false);
|
|
549
|
+
});
|
|
550
|
+
it('should error when rows have both assortmentId and assortmentIdentifier', async () => {
|
|
551
|
+
const config = { loadType: [types_1.LoadType.ASSORTMENT] };
|
|
552
|
+
const rows = [{ itemFamilyFederatedId: 'fam-1', name: 'A', assortmentId: 'id-1', assortmentIdentifier: 'ident-1' }];
|
|
553
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
554
|
+
const issue = result.issues.find((i) => i.message.includes('both "assortmentId" and "assortmentIdentifier"'));
|
|
555
|
+
expect(issue).toBeDefined();
|
|
556
|
+
});
|
|
557
|
+
it('should error when assortmentId values are empty', async () => {
|
|
558
|
+
const config = { loadType: [types_1.LoadType.ASSORTMENT] };
|
|
559
|
+
const rows = [{ itemFamilyFederatedId: 'fam-1', name: 'A', assortmentId: '' }];
|
|
560
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
561
|
+
const issue = result.issues.find((i) => i.message.includes('"assortmentId" cannot be empty'));
|
|
562
|
+
expect(issue).toBeDefined();
|
|
563
|
+
});
|
|
564
|
+
it('should not error when rows have valid assortmentId', async () => {
|
|
565
|
+
const config = { loadType: [types_1.LoadType.ASSORTMENT] };
|
|
566
|
+
const rows = [{ itemFamilyFederatedId: 'fam-1', name: 'A', assortmentId: 'assort-1' }];
|
|
567
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
568
|
+
const columnIssue = result.issues.find((i) => i.message.includes('ASSORTMENT loads require'));
|
|
569
|
+
expect(columnIssue).toBeUndefined();
|
|
570
|
+
});
|
|
571
|
+
it('should not error on rows missing assortmentId when derived from conditional columns with conditions', async () => {
|
|
572
|
+
const config = {
|
|
573
|
+
loadType: [types_1.LoadType.ASSORTMENT],
|
|
574
|
+
conditionalColumns: [
|
|
575
|
+
{ toProperty: 'assortmentId', conditions: [{ conditional: "{gender} === 'mens'", value: 'assort-1' }] },
|
|
576
|
+
],
|
|
577
|
+
};
|
|
578
|
+
const rows = [
|
|
579
|
+
{ itemFamilyFederatedId: 'fam-1', name: 'A', assortmentId: 'assort-1' },
|
|
580
|
+
{ itemFamilyFederatedId: 'fam-2', name: 'B' },
|
|
581
|
+
];
|
|
582
|
+
const result = await (0, validate_1.validate)(rows, config, makeAnalysis());
|
|
583
|
+
const emptyIssue = result.issues.find((i) => i.message.includes('"assortmentId" cannot be empty'));
|
|
584
|
+
expect(emptyIssue).toBeUndefined();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
587
|
describe('validatePartialAssortmentConfig', () => {
|
|
588
588
|
it('should skip for non-ASSORTMENT loads', async () => {
|
|
589
589
|
const config = { loadType: [types_1.LoadType.ITEM], partialAssortmentUpdate: true };
|
|
@@ -1211,108 +1211,4 @@ describe('validate', () => {
|
|
|
1211
1211
|
expect(unmappedPropIssues[0].message).toContain('noLabel');
|
|
1212
1212
|
});
|
|
1213
1213
|
});
|
|
1214
|
-
describe('object reference resolution validation', () => {
|
|
1215
|
-
const colorConfig = {
|
|
1216
|
-
loadType: [types_1.LoadType.COLOR],
|
|
1217
|
-
federatedMappings: { federatedId: 'federatedId', name: 'name' },
|
|
1218
|
-
};
|
|
1219
|
-
const typesByPathWithVendorRef = {
|
|
1220
|
-
color: {
|
|
1221
|
-
type: { typeProperties: [{ slug: 'vendor', propertyType: 'object_reference' }] },
|
|
1222
|
-
allPropertiesSlugs: ['vendor'],
|
|
1223
|
-
typePropertyToValueType: {},
|
|
1224
|
-
objectReferenceProperties: [
|
|
1225
|
-
{
|
|
1226
|
-
slug: 'vendor',
|
|
1227
|
-
propertyType: 'object_reference',
|
|
1228
|
-
referencedTypeRootSlug: 'vendor',
|
|
1229
|
-
referencedTypePath: 'vendor',
|
|
1230
|
-
},
|
|
1231
|
-
],
|
|
1232
|
-
userListProperties: [],
|
|
1233
|
-
},
|
|
1234
|
-
};
|
|
1235
|
-
it('should emit one warning per unique missing value with affected rows', async () => {
|
|
1236
|
-
const mockClient = {
|
|
1237
|
-
get: jest.fn().mockResolvedValue([{ federatedId: 'acme', id: 'vendor-acme' }]),
|
|
1238
|
-
};
|
|
1239
|
-
const rows = [
|
|
1240
|
-
{ federatedId: 'c-1', name: 'Red', vendor: 'acme' },
|
|
1241
|
-
{ federatedId: 'c-2', name: 'Blue', vendor: 'missing-vendor' },
|
|
1242
|
-
{ federatedId: 'c-3', name: 'Green', vendor: 'another-missing' },
|
|
1243
|
-
{ federatedId: 'c-4', name: 'Yellow', vendor: 'missing-vendor' },
|
|
1244
|
-
];
|
|
1245
|
-
const analysis = makeAnalysis({}, typesByPathWithVendorRef);
|
|
1246
|
-
const result = await (0, validate_1.validate)(rows, colorConfig, analysis, mockClient);
|
|
1247
|
-
const refIssues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.REFERENCE_DATA_NOT_FOUND &&
|
|
1248
|
-
i.columnName === 'vendor');
|
|
1249
|
-
expect(refIssues).toHaveLength(2);
|
|
1250
|
-
for (const issue of refIssues) {
|
|
1251
|
-
expect(issue.severity).toBe(types_3.PreValidationSeverity.WARNING);
|
|
1252
|
-
expect(issue.propertyType).toBe('object_reference');
|
|
1253
|
-
}
|
|
1254
|
-
const byValue = new Map(refIssues.map((i) => [i.affectedRows[0].value, i]));
|
|
1255
|
-
expect(byValue.get('missing-vendor').affectedRows.map((r) => r.row).sort()).toEqual([3, 5]);
|
|
1256
|
-
expect(byValue.get('missing-vendor').message).toContain('"missing-vendor"');
|
|
1257
|
-
expect(byValue.get('another-missing').affectedRows.map((r) => r.row)).toEqual([4]);
|
|
1258
|
-
expect(byValue.get('another-missing').message).toContain('"another-missing"');
|
|
1259
|
-
});
|
|
1260
|
-
it('should not emit a warning when all references resolve', async () => {
|
|
1261
|
-
const mockClient = {
|
|
1262
|
-
get: jest.fn().mockResolvedValue([
|
|
1263
|
-
{ federatedId: 'acme', id: 'vendor-acme' },
|
|
1264
|
-
{ federatedId: 'foo', id: 'vendor-foo' },
|
|
1265
|
-
]),
|
|
1266
|
-
};
|
|
1267
|
-
const rows = [
|
|
1268
|
-
{ federatedId: 'c-1', name: 'Red', vendor: 'acme' },
|
|
1269
|
-
{ federatedId: 'c-2', name: 'Blue', vendor: 'foo' },
|
|
1270
|
-
];
|
|
1271
|
-
const analysis = makeAnalysis({}, typesByPathWithVendorRef);
|
|
1272
|
-
const result = await (0, validate_1.validate)(rows, colorConfig, analysis, mockClient);
|
|
1273
|
-
const refIssues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.REFERENCE_DATA_NOT_FOUND);
|
|
1274
|
-
expect(refIssues).toHaveLength(0);
|
|
1275
|
-
});
|
|
1276
|
-
it('should skip the lookup entirely when no client is provided', async () => {
|
|
1277
|
-
const rows = [{ federatedId: 'c-1', name: 'Red', vendor: 'missing' }];
|
|
1278
|
-
const analysis = makeAnalysis({}, typesByPathWithVendorRef);
|
|
1279
|
-
const result = await (0, validate_1.validate)(rows, colorConfig, analysis);
|
|
1280
|
-
const refIssues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.REFERENCE_DATA_NOT_FOUND);
|
|
1281
|
-
expect(refIssues).toHaveLength(0);
|
|
1282
|
-
});
|
|
1283
|
-
it('should prefix custom-entity referenced fed ids when looking up', async () => {
|
|
1284
|
-
const customEntityTypesByPath = {
|
|
1285
|
-
color: {
|
|
1286
|
-
type: { typeProperties: [{ slug: 'category', propertyType: 'object_reference' }] },
|
|
1287
|
-
allPropertiesSlugs: ['category'],
|
|
1288
|
-
typePropertyToValueType: {},
|
|
1289
|
-
objectReferenceProperties: [
|
|
1290
|
-
{
|
|
1291
|
-
slug: 'category',
|
|
1292
|
-
propertyType: 'object_reference',
|
|
1293
|
-
referencedTypeRootSlug: 'custom-entity',
|
|
1294
|
-
referencedTypePath: 'custom-entity:category',
|
|
1295
|
-
},
|
|
1296
|
-
],
|
|
1297
|
-
userListProperties: [],
|
|
1298
|
-
},
|
|
1299
|
-
};
|
|
1300
|
-
const mockClient = {
|
|
1301
|
-
get: jest.fn().mockResolvedValue([
|
|
1302
|
-
{ federatedId: 'custom-entity:category:resolved', id: 'cat-1' },
|
|
1303
|
-
]),
|
|
1304
|
-
};
|
|
1305
|
-
const rows = [
|
|
1306
|
-
{ federatedId: 'c-1', name: 'Red', category: 'resolved' },
|
|
1307
|
-
{ federatedId: 'c-2', name: 'Blue', category: 'unresolved' },
|
|
1308
|
-
];
|
|
1309
|
-
const analysis = makeAnalysis({}, customEntityTypesByPath);
|
|
1310
|
-
const result = await (0, validate_1.validate)(rows, colorConfig, analysis, mockClient);
|
|
1311
|
-
const refIssues = result.issues.filter((i) => i.category === types_3.PreValidationCategory.REFERENCE_DATA_NOT_FOUND &&
|
|
1312
|
-
i.columnName === 'category');
|
|
1313
|
-
expect(refIssues).toHaveLength(1);
|
|
1314
|
-
expect(refIssues[0].affectedRows).toHaveLength(1);
|
|
1315
|
-
expect(refIssues[0].affectedRows[0].value).toBe('unresolved');
|
|
1316
|
-
});
|
|
1317
|
-
});
|
|
1318
1214
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/loader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Client library for helping with loader jobs.",
|
|
5
5
|
"homepage": "https://archergreysorg.github.io/contrail-loader/",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
"form-data": "^4.0.0",
|
|
37
37
|
"lodash": "^4.17.21",
|
|
38
38
|
"node-fetch": "^2.6.0",
|
|
39
|
-
"p-limit": "^3.0.0"
|
|
40
|
-
"validate-color": "^2.2.4"
|
|
39
|
+
"p-limit": "^3.0.0"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
42
|
"@types/jest": "^29.5.2",
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/cli",
|
|
3
|
-
"version": "3.5.1-alpha.
|
|
3
|
+
"version": "3.5.1-alpha.9da7ac5",
|
|
4
4
|
"description": "VibeIQ's Contrail Platform CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"contrail-cli": "./bin/run",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@contrail/entity-types": "^1.6.1",
|
|
34
|
-
"@contrail/loader": "^2.0.
|
|
34
|
+
"@contrail/loader": "^2.0.6",
|
|
35
35
|
"@contrail/org-config-migration": "^1.1.13",
|
|
36
36
|
"@contrail/sdk": "^1.5.13",
|
|
37
37
|
"@contrail/types": "^3.1.5",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ProcessingIssues } from '@contrail/entity-types';
|
|
2
|
-
export declare const COLOR_VALIDATION_MESSAGES: {
|
|
3
|
-
readonly HEX_INVALID: (hex: string) => string;
|
|
4
|
-
readonly RGB_INCOMPLETE: (missingColumns: string[]) => string;
|
|
5
|
-
readonly RGB_OUT_OF_RANGE: (column: string, value: string) => string;
|
|
6
|
-
readonly HEX_PRIORITIZED_OVER_RGB: "Both \"hex\" and red/green/blue values are provided — \"hex\" will be used and red/green/blue will be ignored for this row.";
|
|
7
|
-
};
|
|
8
|
-
export declare const DEFAULT_HEX = "000000";
|
|
9
|
-
export declare function isValidHex(hex: string): boolean;
|
|
10
|
-
export declare function parseRgbComponent(value: any): number | null;
|
|
11
|
-
export interface ColorRowValidationResult {
|
|
12
|
-
errors: {
|
|
13
|
-
columnName: string;
|
|
14
|
-
message: string;
|
|
15
|
-
}[];
|
|
16
|
-
warnings: {
|
|
17
|
-
columnName: string;
|
|
18
|
-
message: string;
|
|
19
|
-
}[];
|
|
20
|
-
}
|
|
21
|
-
export declare function validateColorRow(row: Record<string, any>, federatedMappings?: Record<string, string>): ColorRowValidationResult;
|
|
22
|
-
export declare function buildColorRowIssues(row: Record<string, any>, rowNumber: number, federatedMappings?: Record<string, string>): {
|
|
23
|
-
rowErrors: ProcessingIssues | null;
|
|
24
|
-
rowWarnings: ProcessingIssues | null;
|
|
25
|
-
};
|
|
26
|
-
export declare function applyHexPriorityToRows<T extends Record<string, any>>(rows: T[]): T[];
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.applyHexPriorityToRows = exports.buildColorRowIssues = exports.validateColorRow = exports.parseRgbComponent = exports.isValidHex = exports.DEFAULT_HEX = exports.COLOR_VALIDATION_MESSAGES = void 0;
|
|
4
|
-
const entity_types_1 = require("@contrail/entity-types");
|
|
5
|
-
const validate_color_1 = require("validate-color");
|
|
6
|
-
exports.COLOR_VALIDATION_MESSAGES = {
|
|
7
|
-
HEX_INVALID: (hex) => `"hex" value "${hex}" is not a valid hex color code (expected 3, 4, 6, or 8 hex digits, optionally prefixed with #).`,
|
|
8
|
-
RGB_INCOMPLETE: (missingColumns) => `Row has partial RGB data — "${missingColumns.join('", "')}" must also be provided when using RGB. Provide all of red/green/blue, or use "hex" instead.`,
|
|
9
|
-
RGB_OUT_OF_RANGE: (column, value) => `"${column}" value "${value}" is not a valid RGB component (must be a whole number between 0 and 255).`,
|
|
10
|
-
HEX_PRIORITIZED_OVER_RGB: 'Both "hex" and red/green/blue values are provided — "hex" will be used and red/green/blue will be ignored for this row.',
|
|
11
|
-
};
|
|
12
|
-
exports.DEFAULT_HEX = '000000';
|
|
13
|
-
const RGB_TARGET_KEYS = ['red', 'green', 'blue'];
|
|
14
|
-
const HEX_TARGET_KEY = 'hex';
|
|
15
|
-
function getColorValue(row, federatedMappings, targetKey) {
|
|
16
|
-
const target = row[targetKey];
|
|
17
|
-
if (!isEmpty(target))
|
|
18
|
-
return target;
|
|
19
|
-
const sourceColumn = federatedMappings?.[targetKey];
|
|
20
|
-
if (sourceColumn && sourceColumn !== targetKey)
|
|
21
|
-
return row[sourceColumn];
|
|
22
|
-
return target;
|
|
23
|
-
}
|
|
24
|
-
function isEmpty(value) {
|
|
25
|
-
return value === null || value === undefined || value === '';
|
|
26
|
-
}
|
|
27
|
-
function isValidHex(hex) {
|
|
28
|
-
if (typeof hex !== 'string' || hex.length === 0)
|
|
29
|
-
return false;
|
|
30
|
-
const normalized = hex.startsWith('#') ? hex : `#${hex}`;
|
|
31
|
-
return (0, validate_color_1.validateHTMLColorHex)(normalized);
|
|
32
|
-
}
|
|
33
|
-
exports.isValidHex = isValidHex;
|
|
34
|
-
function parseRgbComponent(value) {
|
|
35
|
-
if (isEmpty(value))
|
|
36
|
-
return null;
|
|
37
|
-
if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255)
|
|
38
|
-
return value;
|
|
39
|
-
if (typeof value === 'string') {
|
|
40
|
-
const trimmed = value.trim();
|
|
41
|
-
if (trimmed === '' || !/^-?\d+$/.test(trimmed))
|
|
42
|
-
return null;
|
|
43
|
-
const n = Number(trimmed);
|
|
44
|
-
if (!Number.isInteger(n) || n < 0 || n > 255)
|
|
45
|
-
return null;
|
|
46
|
-
return n;
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
exports.parseRgbComponent = parseRgbComponent;
|
|
51
|
-
function validateColorRow(row, federatedMappings) {
|
|
52
|
-
const errors = [];
|
|
53
|
-
const warnings = [];
|
|
54
|
-
const hexValue = getColorValue(row, federatedMappings, HEX_TARGET_KEY);
|
|
55
|
-
const rgbValues = {
|
|
56
|
-
red: getColorValue(row, federatedMappings, 'red'),
|
|
57
|
-
green: getColorValue(row, federatedMappings, 'green'),
|
|
58
|
-
blue: getColorValue(row, federatedMappings, 'blue'),
|
|
59
|
-
};
|
|
60
|
-
const rgbProvided = RGB_TARGET_KEYS.filter((k) => !isEmpty(rgbValues[k]));
|
|
61
|
-
const hexProvided = !isEmpty(hexValue);
|
|
62
|
-
if (hexProvided) {
|
|
63
|
-
if (!isValidHex(String(hexValue))) {
|
|
64
|
-
errors.push({
|
|
65
|
-
columnName: HEX_TARGET_KEY,
|
|
66
|
-
message: exports.COLOR_VALIDATION_MESSAGES.HEX_INVALID(String(hexValue)),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (rgbProvided.length > 0) {
|
|
70
|
-
warnings.push({
|
|
71
|
-
columnName: HEX_TARGET_KEY,
|
|
72
|
-
message: exports.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return { errors, warnings };
|
|
76
|
-
}
|
|
77
|
-
if (rgbProvided.length === 0) {
|
|
78
|
-
return { errors, warnings };
|
|
79
|
-
}
|
|
80
|
-
if (rgbProvided.length < RGB_TARGET_KEYS.length) {
|
|
81
|
-
const missing = RGB_TARGET_KEYS.filter((k) => isEmpty(rgbValues[k]));
|
|
82
|
-
for (const k of missing) {
|
|
83
|
-
errors.push({
|
|
84
|
-
columnName: k,
|
|
85
|
-
message: exports.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE([...missing]),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return { errors, warnings };
|
|
89
|
-
}
|
|
90
|
-
for (const k of RGB_TARGET_KEYS) {
|
|
91
|
-
if (parseRgbComponent(rgbValues[k]) === null) {
|
|
92
|
-
errors.push({
|
|
93
|
-
columnName: k,
|
|
94
|
-
message: exports.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE(k, String(rgbValues[k])),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return { errors, warnings };
|
|
99
|
-
}
|
|
100
|
-
exports.validateColorRow = validateColorRow;
|
|
101
|
-
function buildColorRowIssues(row, rowNumber, federatedMappings) {
|
|
102
|
-
const { errors, warnings } = validateColorRow(row, federatedMappings);
|
|
103
|
-
const rowJson = JSON.stringify(row);
|
|
104
|
-
const rowErrors = errors.length
|
|
105
|
-
? {
|
|
106
|
-
rowNumber,
|
|
107
|
-
columnIssues: errors.map((e) => ({
|
|
108
|
-
columnName: e.columnName,
|
|
109
|
-
message: e.message,
|
|
110
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
111
|
-
data: rowJson,
|
|
112
|
-
})),
|
|
113
|
-
}
|
|
114
|
-
: null;
|
|
115
|
-
const rowWarnings = warnings.length
|
|
116
|
-
? {
|
|
117
|
-
rowNumber,
|
|
118
|
-
columnIssues: warnings.map((w) => ({
|
|
119
|
-
columnName: w.columnName,
|
|
120
|
-
message: w.message,
|
|
121
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
122
|
-
data: rowJson,
|
|
123
|
-
})),
|
|
124
|
-
}
|
|
125
|
-
: null;
|
|
126
|
-
return { rowErrors, rowWarnings };
|
|
127
|
-
}
|
|
128
|
-
exports.buildColorRowIssues = buildColorRowIssues;
|
|
129
|
-
function applyHexPriorityToRows(rows) {
|
|
130
|
-
for (const r of rows ?? []) {
|
|
131
|
-
if (!r)
|
|
132
|
-
continue;
|
|
133
|
-
const row = r;
|
|
134
|
-
const hexRaw = row.hex;
|
|
135
|
-
const hexNormalized = typeof hexRaw === 'string' && hexRaw.startsWith('#') ? hexRaw.slice(1) : hexRaw;
|
|
136
|
-
const hexValid = !isEmpty(hexNormalized) && isValidHex(String(hexNormalized));
|
|
137
|
-
if (hexValid) {
|
|
138
|
-
row.hex = hexNormalized;
|
|
139
|
-
delete row.red;
|
|
140
|
-
delete row.green;
|
|
141
|
-
delete row.blue;
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const rgbAllValid = parseRgbComponent(row.red) !== null &&
|
|
145
|
-
parseRgbComponent(row.green) !== null &&
|
|
146
|
-
parseRgbComponent(row.blue) !== null;
|
|
147
|
-
if (rgbAllValid) {
|
|
148
|
-
if (!isEmpty(hexNormalized)) {
|
|
149
|
-
row.hex = hexNormalized;
|
|
150
|
-
}
|
|
151
|
-
else if ('hex' in row) {
|
|
152
|
-
delete row.hex;
|
|
153
|
-
}
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
row.hex = exports.DEFAULT_HEX;
|
|
157
|
-
delete row.red;
|
|
158
|
-
delete row.green;
|
|
159
|
-
delete row.blue;
|
|
160
|
-
}
|
|
161
|
-
return rows;
|
|
162
|
-
}
|
|
163
|
-
exports.applyHexPriorityToRows = applyHexPriorityToRows;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const color_validation_1 = require("./color-validation");
|
|
4
|
-
describe('isValidHex', () => {
|
|
5
|
-
it.each(['#ff0000', 'ff0000', '#FFF', 'fff', '#1a2b3c', '#FFFFFFFF'])('accepts %s', (hex) => {
|
|
6
|
-
expect((0, color_validation_1.isValidHex)(hex)).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
it.each(['', 'notahex', 'gggggg', '#12345', '#1234567', ' ', '#'])('rejects %s', (hex) => {
|
|
9
|
-
expect((0, color_validation_1.isValidHex)(hex)).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
});
|
|
12
|
-
describe('parseRgbComponent', () => {
|
|
13
|
-
it('parses valid integers in range', () => {
|
|
14
|
-
expect((0, color_validation_1.parseRgbComponent)('0')).toBe(0);
|
|
15
|
-
expect((0, color_validation_1.parseRgbComponent)('255')).toBe(255);
|
|
16
|
-
expect((0, color_validation_1.parseRgbComponent)(128)).toBe(128);
|
|
17
|
-
});
|
|
18
|
-
it('rejects out-of-range, non-integer, and empty values', () => {
|
|
19
|
-
expect((0, color_validation_1.parseRgbComponent)('-1')).toBe(null);
|
|
20
|
-
expect((0, color_validation_1.parseRgbComponent)('256')).toBe(null);
|
|
21
|
-
expect((0, color_validation_1.parseRgbComponent)('1.5')).toBe(null);
|
|
22
|
-
expect((0, color_validation_1.parseRgbComponent)('foo')).toBe(null);
|
|
23
|
-
expect((0, color_validation_1.parseRgbComponent)('')).toBe(null);
|
|
24
|
-
expect((0, color_validation_1.parseRgbComponent)(null)).toBe(null);
|
|
25
|
-
expect((0, color_validation_1.parseRgbComponent)(undefined)).toBe(null);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
describe('validateColorRow', () => {
|
|
29
|
-
it('returns no errors when only valid hex is present', () => {
|
|
30
|
-
expect((0, color_validation_1.validateColorRow)({ hex: '#ff0000' })).toEqual({ errors: [], warnings: [] });
|
|
31
|
-
});
|
|
32
|
-
it('returns no errors when only valid RGB is present', () => {
|
|
33
|
-
expect((0, color_validation_1.validateColorRow)({ red: '255', green: '0', blue: '0' })).toEqual({
|
|
34
|
-
errors: [],
|
|
35
|
-
warnings: [],
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
it('errors on invalid hex', () => {
|
|
39
|
-
const result = (0, color_validation_1.validateColorRow)({ hex: 'zzz' });
|
|
40
|
-
expect(result.errors).toEqual([
|
|
41
|
-
{ columnName: 'hex', message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_INVALID('zzz') },
|
|
42
|
-
]);
|
|
43
|
-
});
|
|
44
|
-
it('warns when both hex and RGB are populated', () => {
|
|
45
|
-
const result = (0, color_validation_1.validateColorRow)({ hex: '#fff', red: '1', green: '2', blue: '3' });
|
|
46
|
-
expect(result.errors).toEqual([]);
|
|
47
|
-
expect(result.warnings).toEqual([
|
|
48
|
-
{ columnName: 'hex', message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB },
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
it('errors per-missing-column when RGB is partial and hex is missing', () => {
|
|
52
|
-
const result = (0, color_validation_1.validateColorRow)({ red: '255' });
|
|
53
|
-
expect(result.errors).toEqual([
|
|
54
|
-
{ columnName: 'green', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE(['green', 'blue']) },
|
|
55
|
-
{ columnName: 'blue', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE(['green', 'blue']) },
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
it('errors when an RGB component is out of range', () => {
|
|
59
|
-
const result = (0, color_validation_1.validateColorRow)({ red: '999', green: '0', blue: '0' });
|
|
60
|
-
expect(result.errors).toEqual([
|
|
61
|
-
{ columnName: 'red', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('red', '999') },
|
|
62
|
-
]);
|
|
63
|
-
});
|
|
64
|
-
it('reports no errors when neither hex nor RGB is present', () => {
|
|
65
|
-
expect((0, color_validation_1.validateColorRow)({ name: 'foo' })).toEqual({ errors: [], warnings: [] });
|
|
66
|
-
});
|
|
67
|
-
it('resolves federatedMappings to source columns', () => {
|
|
68
|
-
const row = { hexcode: '#fff', r: '255', g: '0', b: '0' };
|
|
69
|
-
const mappings = { hex: 'hexcode', red: 'r', green: 'g', blue: 'b' };
|
|
70
|
-
const result = (0, color_validation_1.validateColorRow)(row, mappings);
|
|
71
|
-
expect(result.errors).toEqual([]);
|
|
72
|
-
expect(result.warnings).toEqual([
|
|
73
|
-
{ columnName: 'hex', message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB },
|
|
74
|
-
]);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe('applyHexPriorityToRows', () => {
|
|
78
|
-
it('strips RGB columns when hex is valid', () => {
|
|
79
|
-
const result = (0, color_validation_1.applyHexPriorityToRows)([
|
|
80
|
-
{ hex: '#ff0000', red: '255', green: '0', blue: '0', name: 'foo' },
|
|
81
|
-
]);
|
|
82
|
-
expect(result).toEqual([{ hex: 'ff0000', name: 'foo' }]);
|
|
83
|
-
});
|
|
84
|
-
it('keeps RGB and removes empty hex when hex is absent', () => {
|
|
85
|
-
const result = (0, color_validation_1.applyHexPriorityToRows)([{ red: '1', green: '2', blue: '3', name: 'foo' }]);
|
|
86
|
-
expect(result).toEqual([{ red: '1', green: '2', blue: '3', name: 'foo' }]);
|
|
87
|
-
});
|
|
88
|
-
it('defaults to DEFAULT_HEX when neither valid hex nor full RGB is present', () => {
|
|
89
|
-
const result = (0, color_validation_1.applyHexPriorityToRows)([{ name: 'foo' }]);
|
|
90
|
-
expect(result).toEqual([{ name: 'foo', hex: color_validation_1.DEFAULT_HEX }]);
|
|
91
|
-
});
|
|
92
|
-
it('treats invalid hex as missing and falls through to RGB or default', () => {
|
|
93
|
-
const result = (0, color_validation_1.applyHexPriorityToRows)([
|
|
94
|
-
{ hex: 'zzz', red: '1', green: '2', blue: '3', name: 'with-rgb' },
|
|
95
|
-
{ hex: 'zzz', name: 'no-rgb' },
|
|
96
|
-
]);
|
|
97
|
-
expect(result).toEqual([
|
|
98
|
-
{ hex: 'zzz', red: '1', green: '2', blue: '3', name: 'with-rgb' },
|
|
99
|
-
{ hex: color_validation_1.DEFAULT_HEX, name: 'no-rgb' },
|
|
100
|
-
]);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { EntitiesClient, LoaderConfig, PreValidationIssue } from './types';
|
|
2
|
-
export declare function isProjectItemAndAssortmentLoad(config: LoaderConfig): boolean;
|
|
3
|
-
export declare function resolveIdentifiers(rows: Record<string, any>[], config: LoaderConfig, client?: EntitiesClient): Promise<{
|
|
4
|
-
issues: PreValidationIssue[];
|
|
5
|
-
}>;
|