@contrail/cli 3.5.0-alpha.dd20a19 → 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 (87) hide show
  1. package/README.md +3 -35
  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.js +102 -0
  25. package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +0 -1
  26. package/node_modules/@contrail/loader/lib/constants.d.ts +0 -3
  27. package/node_modules/@contrail/loader/lib/constants.js +1 -4
  28. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +9 -23
  29. package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +112 -62
  30. package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +0 -1
  31. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +0 -1
  32. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +8 -22
  33. package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +43 -1
  34. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +5 -0
  35. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +562 -0
  36. package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +231 -0
  37. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +77 -3
  38. package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +82 -0
  39. package/node_modules/@contrail/loader/lib/pipeline/preview.js +0 -5
  40. package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
  41. package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
  42. package/node_modules/@contrail/loader/lib/pipeline/transform.js +18 -0
  43. package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +3 -2
  44. package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +5 -2
  45. package/node_modules/@contrail/loader/lib/pipeline/types.js +2 -0
  46. package/node_modules/@contrail/loader/lib/pipeline/validate.js +184 -437
  47. package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +157 -53
  48. package/node_modules/@contrail/loader/package.json +3 -2
  49. package/node_modules/@contrail/sdk/.github/workflows/publish-to-npm.yml +120 -0
  50. package/node_modules/@contrail/sdk/lib/auth/login.js +42 -3
  51. package/node_modules/@contrail/sdk/lib/core/request.d.ts +3 -0
  52. package/node_modules/@contrail/sdk/lib/core/request.js +100 -4
  53. package/node_modules/@contrail/sdk/lib/entities/entities.client.js +1 -1
  54. package/node_modules/@contrail/sdk/lib/entities/entity.resource.registry.js +1 -0
  55. package/node_modules/@contrail/sdk/lib/files/files.d.ts +13 -3
  56. package/node_modules/@contrail/sdk/lib/files/files.js +66 -25
  57. package/node_modules/@contrail/sdk/package.json +4 -1
  58. package/node_modules/validate-color/LICENSE +21 -0
  59. package/node_modules/validate-color/README.md +265 -0
  60. package/node_modules/validate-color/lib/index.d.ts +10 -0
  61. package/node_modules/validate-color/lib/index.js +1 -0
  62. package/node_modules/validate-color/package.json +91 -0
  63. package/node_modules/validate-color/test/redos.js +29 -0
  64. package/oclif.manifest.json +5 -80
  65. package/package.json +3 -6
  66. package/dist/api/dash0/client.d.ts +0 -13
  67. package/dist/api/dash0/client.js +0 -53
  68. package/dist/api/dash0/spans.d.ts +0 -8
  69. package/dist/api/dash0/spans.js +0 -91
  70. package/dist/api/dash0/types.d.ts +0 -84
  71. package/dist/api/dash0/types.js +0 -4
  72. package/dist/commands/event-workflows/assess-impact.d.ts +0 -19
  73. package/dist/commands/event-workflows/assess-impact.js +0 -104
  74. package/dist/helpers/event-workflow-impact/aggregate.d.ts +0 -23
  75. package/dist/helpers/event-workflow-impact/aggregate.js +0 -49
  76. package/dist/helpers/event-workflow-impact/aggregate.spec.js +0 -109
  77. package/dist/helpers/event-workflow-impact/classify.d.ts +0 -13
  78. package/dist/helpers/event-workflow-impact/classify.js +0 -135
  79. package/dist/helpers/event-workflow-impact/classify.spec.js +0 -138
  80. package/dist/helpers/event-workflow-impact/render.d.ts +0 -12
  81. package/dist/helpers/event-workflow-impact/render.js +0 -62
  82. package/dist/helpers/event-workflow-impact/subtree.d.ts +0 -20
  83. package/dist/helpers/event-workflow-impact/subtree.js +0 -93
  84. package/dist/helpers/event-workflow-impact/subtree.spec.d.ts +0 -1
  85. package/dist/helpers/event-workflow-impact/subtree.spec.js +0 -103
  86. /package/{dist/helpers/event-workflow-impact/aggregate.spec.d.ts → node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts} +0 -0
  87. /package/{dist/helpers/event-workflow-impact/classify.spec.d.ts → node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts} +0 -0
@@ -118,6 +118,59 @@ 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
+ });
121
174
  it('should skip empty values in option set validation', async () => {
122
175
  const analysis = makeAnalysis({
123
176
  optionSetUsingTypePropertiesBySlug: {
@@ -531,59 +584,6 @@ describe('validate', () => {
531
584
  expect(conflict).toBeUndefined();
532
585
  });
533
586
  });
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,4 +1211,108 @@ 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
+ });
1214
1318
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/loader",
3
- "version": "2.0.6",
3
+ "version": "2.0.9",
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,7 +36,8 @@
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"
39
+ "p-limit": "^3.0.0",
40
+ "validate-color": "^2.2.4"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/jest": "^29.5.2",
@@ -0,0 +1,120 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ paths-ignore:
7
+ - '.github/**'
8
+ pull_request:
9
+ paths-ignore:
10
+ - '.github/**'
11
+
12
+ jobs:
13
+ check-version:
14
+ name: Verify version is available on npm
15
+ if: github.event_name == 'pull_request'
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Check version not already published
21
+ run: |
22
+ PACKAGE_NAME=$(node -p "require('./package.json').name")
23
+ VERSION=$(node -p "require('./package.json').version")
24
+ echo "Checking if ${PACKAGE_NAME}@${VERSION} exists on npm..."
25
+ if npm view "${PACKAGE_NAME}@${VERSION}" version 2>/dev/null; then
26
+ echo "::error::Version ${VERSION} is already published on npm. Bump the version in package.json before merging."
27
+ exit 1
28
+ else
29
+ echo "Version ${VERSION} is available."
30
+ fi
31
+
32
+ check-dependencies:
33
+ name: Verify no private dependencies
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ - name: Verify no private dependencies
39
+ run: |
40
+ node -e "
41
+ const deps = require('./package.json').dependencies || {};
42
+ const { execSync } = require('child_process');
43
+ let failed = false;
44
+ for (const pkg of Object.keys(deps)) {
45
+ try {
46
+ execSync('npm view ' + pkg, { stdio: 'pipe' });
47
+ } catch {
48
+ console.error('::error::Dependency ' + pkg + ' is not publicly accessible on npm');
49
+ failed = true;
50
+ }
51
+ }
52
+ if (failed) process.exit(1);
53
+ console.log('All ' + Object.keys(deps).length + ' dependencies are public.');
54
+ "
55
+
56
+ publish-alpha:
57
+ name: Publish alpha
58
+ needs: check-dependencies
59
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ id-token: write
63
+ contents: read
64
+ outputs:
65
+ version: ${{ steps.publish.outputs.version }}
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+
69
+ - uses: actions/setup-node@v4
70
+ with:
71
+ node-version: '24'
72
+ registry-url: 'https://registry.npmjs.org'
73
+
74
+ - name: Install and build
75
+ run: npm ci && npm run build
76
+
77
+ - name: Publish alpha
78
+ id: publish
79
+ run: |
80
+ SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-7)
81
+ VERSION=$(node -p "require('./package.json').version")-alpha.${SHORT_SHA}
82
+ npm version $VERSION --no-git-tag-version
83
+ OUTPUT=$(npm publish --tag alpha 2>&1) && {
84
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
85
+ } || {
86
+ if echo "$OUTPUT" | grep -q "You cannot publish over the previously published versions"; then
87
+ echo "::warning::Alpha ${VERSION} already published, skipping."
88
+ else
89
+ echo "::error::Failed to publish alpha ${VERSION}"
90
+ echo "$OUTPUT"
91
+ exit 1
92
+ fi
93
+ }
94
+
95
+ publish-release:
96
+ name: Publish release
97
+ needs: check-dependencies
98
+ if: github.event_name == 'push'
99
+ runs-on: ubuntu-latest
100
+ permissions:
101
+ id-token: write
102
+ contents: read
103
+ outputs:
104
+ version: ${{ steps.publish.outputs.version }}
105
+ steps:
106
+ - uses: actions/checkout@v4
107
+
108
+ - uses: actions/setup-node@v4
109
+ with:
110
+ node-version: '24'
111
+ registry-url: 'https://registry.npmjs.org'
112
+
113
+ - name: Install and build
114
+ run: npm ci && npm run build
115
+
116
+ - name: Publish release
117
+ id: publish
118
+ run: |
119
+ npm publish
120
+ echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
@@ -93,8 +93,16 @@ function usernameAuthentication(email, password, orgSlug) {
93
93
  cognitoUser.authenticateUser(authDetails, {
94
94
  onSuccess: (result) => __awaiter(this, void 0, void 0, function* () {
95
95
  const user = result;
96
- const config = yield setAuthContext(user.idToken.jwtToken, orgSlug);
97
- resolve(config);
96
+ const cognitoAccessToken = user.idToken.jwtToken;
97
+ try {
98
+ const apiTokens = yield exchangeCognitoTokensForApiTokens(cognitoAccessToken);
99
+ const config = yield setAuthContext(apiTokens.accessToken, orgSlug, apiTokens.refreshToken);
100
+ resolve(config);
101
+ }
102
+ catch (error) {
103
+ console.error('Failed to process Cognito token:', error);
104
+ reject(error);
105
+ }
98
106
  }),
99
107
  onFailure: (err) => {
100
108
  reject(err);
@@ -103,11 +111,42 @@ function usernameAuthentication(email, password, orgSlug) {
103
111
  });
104
112
  });
105
113
  }
106
- function setAuthContext(bearerToken, orgSlug) {
114
+ function exchangeCognitoTokensForApiTokens(cognitoAccessToken) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ const apiGateway = CONFIGURATION.apiGateway;
117
+ const url = apiGateway + '/auth/cognito/';
118
+ const response = yield fetch(url, {
119
+ method: 'POST',
120
+ headers: {
121
+ Accept: 'application/json',
122
+ 'Content-Type': 'application/json',
123
+ },
124
+ body: JSON.stringify({
125
+ accessToken: cognitoAccessToken,
126
+ }),
127
+ });
128
+ if (!response.ok) {
129
+ throw new Error(`Failed to authenticate with Cognito token: ${response.status}`);
130
+ }
131
+ const data = yield response.json();
132
+ if (!data.accessToken) {
133
+ throw new Error('Response from /auth/cognito/ missing required field: accessToken');
134
+ }
135
+ if (!data.refreshToken) {
136
+ throw new Error('Response from /auth/cognito/ missing required field: refreshToken');
137
+ }
138
+ return {
139
+ accessToken: data.accessToken,
140
+ refreshToken: data.refreshToken,
141
+ };
142
+ });
143
+ }
144
+ function setAuthContext(bearerToken, orgSlug, apiRefreshToken) {
107
145
  return __awaiter(this, void 0, void 0, function* () {
108
146
  let apiGateway = CONFIGURATION.apiGateway;
109
147
  auth_1.Auth.config = {
110
148
  apiUserToken: bearerToken,
149
+ apiRefreshToken: apiRefreshToken,
111
150
  apiGateway,
112
151
  orgSlug,
113
152
  };
@@ -1,9 +1,12 @@
1
1
  import { InvocationContext } from '../config/config';
2
2
  export declare const AUTH_TOKEN_REQUIRED = "Authentication token must be set to make a contrail call.";
3
+ export declare function _resetTokenRefreshPromise(): void;
3
4
  export declare class Request {
4
5
  private static MAX_RETRIES;
5
6
  private static RETRY_DELAY;
6
7
  static request(resource: any, params?: any, ctx?: InvocationContext): Promise<any>;
8
+ private static requestWithRetries;
9
+ private static isTokenExpiredError;
7
10
  private static delay;
8
11
  static logError(error: {}, resource: any, params?: {}): void;
9
12
  static sanitizeParametersString(params: any): string;
@@ -12,19 +12,108 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.Request = exports.AUTH_TOKEN_REQUIRED = void 0;
15
+ exports.Request = exports._resetTokenRefreshPromise = exports.AUTH_TOKEN_REQUIRED = void 0;
16
16
  const node_fetch_1 = __importDefault(require("node-fetch"));
17
17
  const config_1 = require("../config/config");
18
+ const auth_1 = require("../auth/auth");
18
19
  exports.AUTH_TOKEN_REQUIRED = 'Authentication token must be set to make a contrail call.';
19
20
  class ApiError extends Error {
20
- constructor(message, details) {
21
+ constructor(message, details, errorCode) {
21
22
  super(message);
22
23
  this.details = details;
24
+ this.errorCode = errorCode;
23
25
  this.details = details;
26
+ this.errorCode = errorCode;
24
27
  }
25
28
  }
29
+ let tokenRefreshPromise = null;
30
+ function performTokenRefresh() {
31
+ var _a, _b, _c;
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const apiRefreshToken = (_a = auth_1.Auth.config) === null || _a === void 0 ? void 0 : _a.apiRefreshToken;
34
+ const apiGateway = (_b = auth_1.Auth.config) === null || _b === void 0 ? void 0 : _b.apiGateway;
35
+ if (!apiRefreshToken) {
36
+ throw new Error('No API refresh token available. Please login again.');
37
+ }
38
+ if (!apiGateway) {
39
+ throw new Error('API Gateway not configured. Ensure the application environment is properly configured.');
40
+ }
41
+ const url = apiGateway + '/auth/jwt_refresh/';
42
+ try {
43
+ const response = yield (0, node_fetch_1.default)(url, {
44
+ method: 'POST',
45
+ headers: {
46
+ Accept: 'application/json',
47
+ 'Content-Type': 'application/json',
48
+ },
49
+ body: JSON.stringify({
50
+ refreshToken: apiRefreshToken,
51
+ }),
52
+ });
53
+ if (!response.ok) {
54
+ const errorText = yield response.text();
55
+ throw new Error(`Token refresh failed with status ${response.status}: ${errorText}`);
56
+ }
57
+ const data = yield response.json();
58
+ if (!data.accessToken) {
59
+ throw new Error(`Token refresh response did not contain a new access token.`);
60
+ }
61
+ auth_1.Auth.config.apiUserToken = data.accessToken;
62
+ (0, config_1.setConfig)(auth_1.Auth.config);
63
+ console.log('Access token refreshed successfully');
64
+ return data.accessToken;
65
+ }
66
+ catch (error) {
67
+ throw new Error(`Token refresh failed: ${(_c = error === null || error === void 0 ? void 0 : error.message) !== null && _c !== void 0 ? _c : String(error)}. Please login again with your credentials.`);
68
+ }
69
+ });
70
+ }
71
+ function refreshAccessToken() {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ if (tokenRefreshPromise) {
74
+ console.log('Token refresh already in progress, waiting for completion...');
75
+ return tokenRefreshPromise;
76
+ }
77
+ tokenRefreshPromise = performTokenRefresh();
78
+ try {
79
+ const newToken = yield tokenRefreshPromise;
80
+ return newToken;
81
+ }
82
+ finally {
83
+ tokenRefreshPromise = null;
84
+ }
85
+ });
86
+ }
87
+ function _resetTokenRefreshPromise() {
88
+ tokenRefreshPromise = null;
89
+ }
90
+ exports._resetTokenRefreshPromise = _resetTokenRefreshPromise;
26
91
  class Request {
27
92
  static request(resource, params = { method: 'get' }, ctx) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
95
+ try {
96
+ return yield this.requestWithRetries(resource, params, ctx);
97
+ }
98
+ catch (error) {
99
+ if (this.isTokenExpiredError(error)) {
100
+ console.log(`[SDK Request: Token Expired] Attempting to refresh access token...`);
101
+ try {
102
+ yield refreshAccessToken();
103
+ params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
104
+ console.log(`[SDK Request: Token Refreshed] Retrying request...`);
105
+ return yield this.requestWithRetries(resource, params, ctx);
106
+ }
107
+ catch (refreshError) {
108
+ console.error(`[SDK Request: Token Refresh Failed] ${refreshError === null || refreshError === void 0 ? void 0 : refreshError.message}`, refreshError);
109
+ throw new ApiError('Access token refresh failed after TOKEN_EXPIRED error. Unable to proceed with request', refreshError);
110
+ }
111
+ }
112
+ throw error;
113
+ }
114
+ });
115
+ }
116
+ static requestWithRetries(resource, params, ctx) {
28
117
  var _a;
29
118
  return __awaiter(this, void 0, void 0, function* () {
30
119
  const userCtx = ctx || (0, config_1.getGlobalContext)();
@@ -33,7 +122,6 @@ class Request {
33
122
  const url = API_GATEWAY + resource;
34
123
  const method = (params === null || params === void 0 ? void 0 : params.method) ? params.method.toUpperCase() : 'GET';
35
124
  const fullResource = `${method} ${url.split('/')[3]}${resource}`;
36
- params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
37
125
  let res;
38
126
  let attempt = 0;
39
127
  while (attempt <= this.MAX_RETRIES) {
@@ -72,11 +160,16 @@ class Request {
72
160
  else {
73
161
  const error = yield res.text();
74
162
  const errorJson = this.tryParseStringIntoObject(error);
163
+ const isTokenExpired = errorJson && typeof errorJson === 'object' && errorJson.errorCode === 'TOKEN_EXPIRED';
164
+ if (isTokenExpired) {
165
+ throw new ApiError('Token expired', errorJson, 'TOKEN_EXPIRED');
166
+ }
75
167
  if (errorHandler) {
76
168
  const retryCallback = () => __awaiter(this, void 0, void 0, function* () {
169
+ params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
77
170
  return yield (0, node_fetch_1.default)(url, params);
78
171
  });
79
- console.log(`[###### SDK Request: ERROR] code ${error.statusCode} resource: ${fullResource}`);
172
+ console.log(`[###### SDK Request: ERROR] code ${res.status} resource: ${fullResource}`);
80
173
  console.log(`[###### SDK Request: ERROR] params ${Request.sanitizeParametersString(params)}`);
81
174
  const request = Object.assign(Object.assign({}, params), { url, method });
82
175
  return yield errorHandler.handle(res, errorJson, retryCallback, request);
@@ -97,6 +190,9 @@ class Request {
97
190
  }
98
191
  });
99
192
  }
193
+ static isTokenExpiredError(error) {
194
+ return error instanceof ApiError && error.errorCode === 'TOKEN_EXPIRED';
195
+ }
100
196
  static delay(ms) {
101
197
  return new Promise((resolve) => setTimeout(resolve, ms));
102
198
  }
@@ -131,7 +131,7 @@ class Entities {
131
131
  resource += `skipHydrateRelationships=${options.skipHydrateRelationships}&`;
132
132
  }
133
133
  if (options.federatedIds) {
134
- resource += `ids=${options.federatedIds.map((id) => id.replace(/,/g, '_COMMA_')).join(',')}&`;
134
+ resource += `ids=${options.federatedIds.map((id) => encodeURIComponent(id.replace(/,/g, '_COMMA_'))).join(',')}&`;
135
135
  }
136
136
  if (options.criteria) {
137
137
  resource += 'criteria=' + JSURL.stringify(options.criteria);
@@ -55,3 +55,4 @@ EntityResourceRegistry.registerEntityResourceName('open-search', 'open-search');
55
55
  EntityResourceRegistry.registerEntityResourceName('project-configurator', 'project-configurator');
56
56
  EntityResourceRegistry.registerEntityResourceName('identity', 'identities');
57
57
  EntityResourceRegistry.registerEntityResourceName('entity-policy', 'entity-policies');
58
+ EntityResourceRegistry.registerEntityResourceName('measurement-spec-entry', 'measurement-spec-entries');
@@ -1,9 +1,19 @@
1
+ /// <reference types="node" />
1
2
  export declare class Files {
2
3
  private ctx;
3
4
  constructor(ctx?: any);
4
5
  createAndUploadFile(file: File, ownedByReference?: string, ttl?: number): Promise<any>;
5
- createAndUploadFileFromBuffer(buffer: any, contentType: string, fileName: string, ownedByReference?: string, ttl?: number): Promise<any>;
6
- uploadFile(uploadPost: any, file: File): Promise<void>;
7
- uploadFileAsStream(uploadPost: any, buffer: File, options?: any): Promise<unknown>;
6
+ createAndUploadFileFromBuffer(buffer: Buffer, contentType: string, fileName: string, ownedByReference?: string, ttl?: number): Promise<any>;
7
+ uploadFile(uploadPost: {
8
+ url: string;
9
+ fields: Record<string, string>;
10
+ }, file: File): Promise<void>;
11
+ uploadFileAsStream(uploadPost: {
12
+ url: string;
13
+ fields: Record<string, string>;
14
+ }, buffer: Buffer | File, options?: {
15
+ contentType?: string;
16
+ }): Promise<void>;
17
+ private withRetry;
8
18
  private appendUploadHeaders;
9
19
  }