@contrail/cli 3.5.0-alpha.dd20a19 → 3.5.1-alpha.68d57e0

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 (72) hide show
  1. package/README.md +1 -33
  2. package/node_modules/@contrail/loader/lib/color-processor.js +9 -23
  3. package/node_modules/@contrail/loader/lib/color-processor.spec.js +112 -62
  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 +2 -0
  6. package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +4 -0
  7. package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +26 -0
  8. package/node_modules/@contrail/loader/lib/common/color-validation.js +163 -0
  9. package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +102 -0
  10. package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +0 -1
  11. package/node_modules/@contrail/loader/lib/constants.d.ts +0 -3
  12. package/node_modules/@contrail/loader/lib/constants.js +1 -4
  13. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +9 -23
  14. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +112 -62
  15. package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +0 -1
  16. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +0 -1
  17. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +8 -22
  18. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +43 -1
  19. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +5 -0
  20. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +562 -0
  21. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +231 -0
  22. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +77 -3
  23. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +82 -0
  24. package/node_modules/@contrail/loader/lib/pipeline/preview.js +0 -5
  25. package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
  26. package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
  27. package/node_modules/@contrail/loader/lib/pipeline/transform.js +18 -0
  28. package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +3 -2
  29. package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +5 -2
  30. package/node_modules/@contrail/loader/lib/pipeline/types.js +2 -0
  31. package/node_modules/@contrail/loader/lib/pipeline/validate.js +184 -437
  32. package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +157 -53
  33. package/node_modules/@contrail/loader/package.json +3 -2
  34. package/node_modules/@contrail/sdk/.github/workflows/publish-to-npm.yml +120 -0
  35. package/node_modules/@contrail/sdk/lib/auth/login.js +42 -3
  36. package/node_modules/@contrail/sdk/lib/core/request.d.ts +3 -0
  37. package/node_modules/@contrail/sdk/lib/core/request.js +100 -4
  38. package/node_modules/@contrail/sdk/lib/entities/entities.client.js +1 -1
  39. package/node_modules/@contrail/sdk/lib/entities/entity.resource.registry.js +1 -0
  40. package/node_modules/@contrail/sdk/lib/files/files.d.ts +13 -3
  41. package/node_modules/@contrail/sdk/lib/files/files.js +66 -25
  42. package/node_modules/@contrail/sdk/package.json +4 -1
  43. package/node_modules/validate-color/LICENSE +21 -0
  44. package/node_modules/validate-color/README.md +265 -0
  45. package/node_modules/validate-color/lib/index.d.ts +10 -0
  46. package/node_modules/validate-color/lib/index.js +1 -0
  47. package/node_modules/validate-color/package.json +91 -0
  48. package/node_modules/validate-color/test/redos.js +29 -0
  49. package/oclif.manifest.json +1 -79
  50. package/package.json +3 -6
  51. package/dist/api/dash0/client.d.ts +0 -13
  52. package/dist/api/dash0/client.js +0 -53
  53. package/dist/api/dash0/spans.d.ts +0 -8
  54. package/dist/api/dash0/spans.js +0 -91
  55. package/dist/api/dash0/types.d.ts +0 -84
  56. package/dist/api/dash0/types.js +0 -4
  57. package/dist/commands/event-workflows/assess-impact.d.ts +0 -19
  58. package/dist/commands/event-workflows/assess-impact.js +0 -104
  59. package/dist/helpers/event-workflow-impact/aggregate.d.ts +0 -23
  60. package/dist/helpers/event-workflow-impact/aggregate.js +0 -49
  61. package/dist/helpers/event-workflow-impact/aggregate.spec.js +0 -109
  62. package/dist/helpers/event-workflow-impact/classify.d.ts +0 -13
  63. package/dist/helpers/event-workflow-impact/classify.js +0 -135
  64. package/dist/helpers/event-workflow-impact/classify.spec.js +0 -138
  65. package/dist/helpers/event-workflow-impact/render.d.ts +0 -12
  66. package/dist/helpers/event-workflow-impact/render.js +0 -62
  67. package/dist/helpers/event-workflow-impact/subtree.d.ts +0 -20
  68. package/dist/helpers/event-workflow-impact/subtree.js +0 -93
  69. package/dist/helpers/event-workflow-impact/subtree.spec.d.ts +0 -1
  70. package/dist/helpers/event-workflow-impact/subtree.spec.js +0 -103
  71. /package/{dist/helpers/event-workflow-impact/aggregate.spec.d.ts → node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts} +0 -0
  72. /package/{dist/helpers/event-workflow-impact/classify.spec.d.ts → node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts} +0 -0
package/README.md CHANGED
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
17
17
  $ contrail COMMAND
18
18
  running command...
19
19
  $ contrail (--version)
20
- @contrail/cli/3.5.0-alpha.dd20a19 linux-x64 node-v24.14.1
20
+ @contrail/cli/3.5.1-alpha.68d57e0 linux-x64 node-v24.14.1
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -51,7 +51,6 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
51
51
  * [`contrail config list`](#contrail-config-list)
52
52
  * [`contrail config set`](#contrail-config-set)
53
53
  * [`contrail config use [NAME]`](#contrail-config-use-name)
54
- * [`contrail event-workflows assess-impact IDENTIFIER`](#contrail-event-workflows-assess-impact-identifier)
55
54
  * [`contrail file upload FILELOCATION CONTENTTYPE`](#contrail-file-upload-filelocation-contenttype)
56
55
  * [`contrail groups export`](#contrail-groups-export)
57
56
  * [`contrail groups import GROUPSFILE`](#contrail-groups-import-groupsfile)
@@ -537,37 +536,6 @@ EXAMPLES
537
536
  $ contrail config use demo-org
538
537
  ```
539
538
 
540
- ## `contrail event-workflows assess-impact IDENTIFIER`
541
-
542
- Sample Dash0 traces of an event-workflow action and report per-invocation downstream impact.
543
-
544
- ```
545
- USAGE
546
- $ contrail event-workflows assess-impact IDENTIFIER [-l error|warn|info|debug] [-e true|false] [-s <value>] [--lookback <value>]
547
- [--dataset <value>] [-j]
548
-
549
- ARGUMENTS
550
- IDENTIFIER Action identifier (contrail.event_workflow.action.identifier).
551
-
552
- FLAGS
553
- -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
554
- <options: true|false>
555
- -j, --json JSON output (not yet implemented; flag reserved).
556
- -l, --logLevel=<option> The Log Level
557
- <options: error|warn|info|debug>
558
- -s, --samples=<value> [default: 50] Number of distinct traces to sample (10-100).
559
- --dataset=<value> [default: prod] Dash0 dataset.
560
- --lookback=<value> [default: 2d] How far back to search. Format: 30m | 6h | 2d.
561
-
562
- DESCRIPTION
563
- Sample Dash0 traces of an event-workflow action and report per-invocation downstream impact.
564
-
565
- EXAMPLES
566
- $ contrail event-workflows assess-impact @gap-automations:bomCcCostUpdate
567
-
568
- $ contrail event-workflows assess-impact @gap-automations:bomCcCostUpdate --samples 25 --lookback 6h
569
- ```
570
-
571
539
  ## `contrail file upload FILELOCATION CONTENTTYPE`
572
540
 
573
541
  Uploads a file to the VibeIQ platform
@@ -5,14 +5,7 @@ const entity_types_1 = require("@contrail/entity-types");
5
5
  const base_processor_1 = require("./base-processor");
6
6
  const entity_processor_1 = require("./entity-processor");
7
7
  const constants_1 = require("./constants");
8
- function removeHexHash(data) {
9
- for (const row of data ?? []) {
10
- if (row.hex?.startsWith('#')) {
11
- row.hex = row.hex.slice(1);
12
- }
13
- }
14
- return data;
15
- }
8
+ const color_validation_1 = require("./common/color-validation");
16
9
  class ColorProcessor extends base_processor_1.BaseProcessor {
17
10
  static typesToTransformBy = ['color'];
18
11
  static requiredProperties = ['typePath', 'typeId', 'federatedId', 'name', 'hex', 'red', 'green', 'blue'];
@@ -20,7 +13,7 @@ class ColorProcessor extends base_processor_1.BaseProcessor {
20
13
  if (!data) {
21
14
  return [];
22
15
  }
23
- return removeHexHash(data);
16
+ return (0, color_validation_1.applyHexPriorityToRows)(data);
24
17
  }
25
18
  static validateTypePath(data, config, csvHeaders, generalErrors) {
26
19
  const typePathSource = {
@@ -56,20 +49,13 @@ class ColorProcessor extends base_processor_1.BaseProcessor {
56
49
  static validateLoadFile(data, config) {
57
50
  const { errors, warnings, generalErrors, generalWarnings, csvHeaders } = entity_processor_1.EntityProcessor.validateLoadFile(data, config, entity_types_1.LoadType.COLOR);
58
51
  this.validateTypePath(data, config, csvHeaders, generalErrors);
59
- const redColumn = config?.federatedMappings?.red || 'red';
60
- const blueColumn = config?.federatedMappings?.blue || 'blue';
61
- const greenColumn = config?.federatedMappings?.green || 'green';
62
- const hexColumn = config?.federatedMappings?.hex || 'hex';
63
- const hasRedColumn = csvHeaders.includes(redColumn);
64
- const hasBlueColumn = csvHeaders.includes(blueColumn);
65
- const hasGreenColumn = csvHeaders.includes(greenColumn);
66
- const hasHexColumn = csvHeaders.includes(hexColumn);
67
- if (hasHexColumn && hasRedColumn && hasGreenColumn && hasBlueColumn) {
68
- generalWarnings.push({
69
- message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
70
- issueType: entity_types_1.IssueType.CONFIG,
71
- });
72
- }
52
+ data.forEach((row, index) => {
53
+ const { rowErrors, rowWarnings } = (0, color_validation_1.buildColorRowIssues)(row, index + 2, config?.federatedMappings);
54
+ if (rowErrors)
55
+ errors.push(rowErrors);
56
+ if (rowWarnings)
57
+ warnings.push(rowWarnings);
58
+ });
73
59
  return { errors, warnings, generalErrors, generalWarnings };
74
60
  }
75
61
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const color_processor_1 = require("./color-processor");
4
4
  const entity_types_1 = require("@contrail/entity-types");
5
5
  const constants_1 = require("./constants");
6
+ const color_validation_1 = require("./common/color-validation");
6
7
  describe('ColorProcessor', () => {
7
8
  const loaderProcess = {
8
9
  id: 'mock-loader-process-id',
@@ -219,72 +220,38 @@ describe('ColorProcessor', () => {
219
220
  });
220
221
  });
221
222
  describe('color column validation', () => {
222
- it('should warn when both hex and RGB columns are present', () => {
223
+ it('warns per-row when both hex and RGB are populated (hex prioritized)', () => {
223
224
  const data = [
224
- {
225
- name: 'test-color',
226
- red: '255',
227
- green: '0',
228
- blue: '0',
229
- hex: '#ff0000',
230
- },
225
+ { name: 'test-color', red: '255', green: '0', blue: '0', hex: '#ff0000' },
231
226
  ];
232
- const config = {
233
- ...loaderProcess,
234
- federatedMappings: {
235
- ...loaderProcess.federatedMappings,
236
- red: 'red',
237
- green: 'green',
238
- blue: 'blue',
239
- hex: 'hex',
240
- },
241
- };
242
- const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
243
- expect(result.generalWarnings).toContainEqual({
244
- message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
245
- issueType: entity_types_1.IssueType.CONFIG,
246
- });
247
- });
248
- it('should not warn when only RGB columns are present', () => {
249
- const data = [
227
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
228
+ expect(result.warnings).toEqual([
250
229
  {
251
- name: 'test-color',
252
- red: '255',
253
- green: '0',
254
- blue: '0',
255
- },
256
- ];
257
- const config = {
258
- ...loaderProcess,
259
- federatedMappings: {
260
- ...loaderProcess.federatedMappings,
261
- red: 'red',
262
- green: 'green',
263
- blue: 'blue',
230
+ rowNumber: 2,
231
+ columnIssues: [
232
+ {
233
+ columnName: 'hex',
234
+ message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB,
235
+ issueType: entity_types_1.IssueType.INVALID_DATA,
236
+ data: JSON.stringify(data[0]),
237
+ },
238
+ ],
264
239
  },
265
- };
266
- const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
267
- expect(result.generalWarnings).not.toContainEqual({
268
- message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
269
- issueType: entity_types_1.IssueType.CONFIG,
270
- });
240
+ ]);
271
241
  });
272
- it('should not warn when only hex column is present', () => {
242
+ it('does not warn when only RGB is populated and all components are valid', () => {
243
+ const data = [{ name: 'test-color', red: '255', green: '0', blue: '0' }];
244
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
245
+ expect(result.warnings).toEqual([]);
246
+ expect(result.errors).toEqual([]);
247
+ });
248
+ it('does not warn when only a valid hex is populated', () => {
273
249
  const data = [{ name: 'test-color', hex: '#ff0000' }];
274
- const config = {
275
- ...loaderProcess,
276
- federatedMappings: {
277
- ...loaderProcess.federatedMappings,
278
- hex: 'hex',
279
- },
280
- };
281
- const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
282
- expect(result.generalWarnings).not.toContainEqual({
283
- message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
284
- issueType: entity_types_1.IssueType.CONFIG,
285
- });
250
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
251
+ expect(result.warnings).toEqual([]);
252
+ expect(result.errors).toEqual([]);
286
253
  });
287
- it('should handle custom column names from federatedMappings', () => {
254
+ it('warns per-row when both hex and RGB are populated under federatedMappings', () => {
288
255
  const data = [{ name: 'test-color', r: '255', g: '0', b: '0', hexcode: '#ff0000' }];
289
256
  const config = {
290
257
  ...loaderProcess,
@@ -297,11 +264,94 @@ describe('ColorProcessor', () => {
297
264
  },
298
265
  };
299
266
  const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
300
- expect(result.generalWarnings).toContainEqual({
301
- message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
302
- issueType: entity_types_1.IssueType.CONFIG,
267
+ expect(result.warnings).toEqual([
268
+ {
269
+ rowNumber: 2,
270
+ columnIssues: [
271
+ {
272
+ columnName: 'hex',
273
+ message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB,
274
+ issueType: entity_types_1.IssueType.INVALID_DATA,
275
+ data: JSON.stringify(data[0]),
276
+ },
277
+ ],
278
+ },
279
+ ]);
280
+ });
281
+ it('errors when hex is invalid', () => {
282
+ const data = [{ name: 'bad', hex: 'notahex' }];
283
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
284
+ expect(result.errors).toHaveLength(1);
285
+ expect(result.errors[0]).toMatchObject({
286
+ rowNumber: 2,
287
+ columnIssues: [
288
+ {
289
+ columnName: 'hex',
290
+ message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_INVALID('notahex'),
291
+ issueType: entity_types_1.IssueType.INVALID_DATA,
292
+ },
293
+ ],
294
+ });
295
+ });
296
+ it('errors when RGB is partially populated and hex is missing', () => {
297
+ const data = [{ name: 'partial', red: '255', green: '128' }];
298
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
299
+ expect(result.errors).toHaveLength(1);
300
+ expect(result.errors[0].rowNumber).toBe(2);
301
+ expect(result.errors[0].columnIssues).toEqual([
302
+ {
303
+ columnName: 'blue',
304
+ message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE(['blue']),
305
+ issueType: entity_types_1.IssueType.INVALID_DATA,
306
+ data: JSON.stringify(data[0]),
307
+ },
308
+ ]);
309
+ });
310
+ it('errors when an RGB component is out of range or non-numeric', () => {
311
+ const data = [
312
+ { name: 'bad-red', red: '300', green: '0', blue: '0' },
313
+ { name: 'nan-green', red: '10', green: 'foo', blue: '0' },
314
+ ];
315
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
316
+ expect(result.errors).toHaveLength(2);
317
+ expect(result.errors[0]).toMatchObject({
318
+ rowNumber: 2,
319
+ columnIssues: [
320
+ { columnName: 'red', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('red', '300') },
321
+ ],
322
+ });
323
+ expect(result.errors[1]).toMatchObject({
324
+ rowNumber: 3,
325
+ columnIssues: [
326
+ { columnName: 'green', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('green', 'foo') },
327
+ ],
303
328
  });
304
329
  });
330
+ it('does not error when neither hex nor RGB are populated (defaults to black)', () => {
331
+ const data = [{ name: 'no-color' }];
332
+ const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
333
+ expect(result.errors).toEqual([]);
334
+ expect(result.warnings).toEqual([]);
335
+ });
336
+ });
337
+ });
338
+ describe('transformData (hex priority)', () => {
339
+ it('strips red/green/blue when hex is valid', () => {
340
+ const data = [
341
+ { hex: '#ff0000', red: '255', green: '0', blue: '0', name: 'red' },
342
+ ];
343
+ const result = color_processor_1.ColorProcessor.transformData(data);
344
+ expect(result).toEqual([{ hex: 'ff0000', name: 'red' }]);
345
+ });
346
+ it('keeps red/green/blue and removes empty hex when hex is missing', () => {
347
+ const data = [{ red: '255', green: '0', blue: '0', name: 'red' }];
348
+ const result = color_processor_1.ColorProcessor.transformData(data);
349
+ expect(result).toEqual([{ red: '255', green: '0', blue: '0', name: 'red' }]);
350
+ });
351
+ it('defaults to #000000 (without hash) when neither hex nor full RGB is provided', () => {
352
+ const data = [{ name: 'fallback' }];
353
+ const result = color_processor_1.ColorProcessor.transformData(data);
354
+ expect(result).toEqual([{ name: 'fallback', hex: color_validation_1.DEFAULT_HEX }]);
305
355
  });
306
356
  });
307
357
  });
@@ -2,5 +2,5 @@ interface ChunkByUrlLengthOptions {
2
2
  maxBytes?: number;
3
3
  maxCount?: number;
4
4
  }
5
- export declare function chunkByUrlLength(ids: string[], options?: ChunkByUrlLengthOptions): string[][];
5
+ export declare function chunkByUrlLength(ids: readonly string[] | null | undefined, options?: ChunkByUrlLengthOptions): string[][];
6
6
  export {};
@@ -6,6 +6,8 @@ function wireEncodedLength(id) {
6
6
  return encodeURIComponent(id.replace(/,/g, '_COMMA_')).length;
7
7
  }
8
8
  function chunkByUrlLength(ids, options = {}) {
9
+ if (!ids?.length)
10
+ return [];
9
11
  const maxBytes = options.maxBytes ?? constants_1.FEDERATED_ID_FETCH_MAX_URL_BYTES;
10
12
  const maxCount = options.maxCount ?? constants_1.FEDERATED_ID_FETCH_MAX_COUNT;
11
13
  const result = [];
@@ -5,6 +5,10 @@ describe('chunkByUrlLength', () => {
5
5
  it('returns an empty array for empty input', () => {
6
6
  expect((0, chunk_by_url_length_1.chunkByUrlLength)([])).toEqual([]);
7
7
  });
8
+ it('returns an empty array for null/undefined input (matches lodash.chunk behavior)', () => {
9
+ expect((0, chunk_by_url_length_1.chunkByUrlLength)(undefined)).toEqual([]);
10
+ expect((0, chunk_by_url_length_1.chunkByUrlLength)(null)).toEqual([]);
11
+ });
8
12
  it('packs short ids into a single chunk up to maxCount', () => {
9
13
  const ids = Array.from({ length: 50 }, (_, i) => `id-${i}`);
10
14
  const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 10000 });
@@ -0,0 +1,26 @@
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[];
@@ -0,0 +1,163 @@
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;
@@ -0,0 +1,102 @@
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
+ });
@@ -53,7 +53,6 @@ class SizeRangeSanitizer {
53
53
  }
54
54
  const typePropertySlugs = type?.typeProperties?.map((x) => x.slug);
55
55
  if ((0, lodash_1.intersection)(typePropertySlugs, [exports.SIZE_RANGE_SLUG, exports.SIZE_RANGE_TEMPLATE_SLUG]).length !== 2) {
56
- console.debug(`No size ranges for type ${JSON.stringify(type, null, 2)}, skipping.`);
57
56
  continue;
58
57
  }
59
58
  validTypePaths.push(typePath);
@@ -1,9 +1,6 @@
1
1
  export declare const COLOR_PROCESSOR_ERRORS: {
2
2
  readonly TYPEPATH_MISSING: "\"typePath\" with value \"color\" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping \"typePath\" mapping with value \"color\" with one of these methods, then try the load again.";
3
3
  };
4
- export declare const COLOR_PROCESSOR_WARNINGS: {
5
- readonly HEX_IGNORED_WARNING: "\"Hex\" is ignored when red, green, and blue columns are present";
6
- };
7
4
  export declare const CUSTOM_ENTITY_PROCESSOR_ERRORS: {
8
5
  readonly TYPEPATH_MISSING: "\"typePath\" with value \"custom-entity:<subtypepath>\" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping \"typePath\" mapping with value \"custom-entity:<subtypepath>\" with one of these methods, then try the load again.";
9
6
  };