@contrail/flexplm 1.7.4-alpha.368c8b1 → 1.7.4-alpha.72422a6
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/lib/publish/base-process-publish-assortment.js +22 -37
- package/lib/util/data-converter.js +0 -18
- package/package.json +66 -67
- package/scripts/copy-template.js +5 -9
- package/lib/cli/commands/config-create.d.ts +0 -5
- package/lib/cli/commands/config-create.js +0 -85
- package/lib/cli/commands/config-create.spec.d.ts +0 -1
- package/lib/cli/commands/config-create.spec.js +0 -80
- package/lib/cli/commands/config-upload.d.ts +0 -19
- package/lib/cli/commands/config-upload.js +0 -249
- package/lib/cli/commands/config-upload.spec.d.ts +0 -1
- package/lib/cli/commands/config-upload.spec.js +0 -94
- package/lib/cli/config-index.d.ts +0 -5
- package/lib/cli/config-index.js +0 -61
- package/lib/cli/config-index.spec.d.ts +0 -1
- package/lib/cli/config-index.spec.js +0 -68
- package/lib/cli/template/config-template.json.template +0 -30
|
@@ -9,7 +9,6 @@ const map_utils_1 = require("../util/map-utils");
|
|
|
9
9
|
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
10
10
|
const fsPromise = require("fs/promises");
|
|
11
11
|
const path = require("path");
|
|
12
|
-
const app_framework_1 = require("@contrail/app-framework");
|
|
13
12
|
const event_short_message_status_1 = require("../util/event-short-message-status");
|
|
14
13
|
class BaseProcessPublishAssortment {
|
|
15
14
|
constructor(_config, _dc, _mapFileUtil) {
|
|
@@ -29,7 +28,8 @@ class BaseProcessPublishAssortment {
|
|
|
29
28
|
let publisher;
|
|
30
29
|
const assortmentId = event.assortmentId;
|
|
31
30
|
try {
|
|
32
|
-
console.info('process-start
|
|
31
|
+
console.info('process-start: assortmentId: ' + assortmentId
|
|
32
|
+
+ ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
|
|
33
33
|
let seasonFed;
|
|
34
34
|
try {
|
|
35
35
|
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
@@ -74,9 +74,9 @@ class BaseProcessPublishAssortment {
|
|
|
74
74
|
e.publishInfo = publishInfo;
|
|
75
75
|
}
|
|
76
76
|
catch (e2) {
|
|
77
|
-
console.
|
|
77
|
+
console.error('getPublishInfo failed: ' + e2.message);
|
|
78
78
|
}
|
|
79
|
-
console.
|
|
79
|
+
console.error('process failed: assortmentId: ' + assortmentId + ': ' + e.message);
|
|
80
80
|
throw e;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -315,19 +315,17 @@ class BaseProcessPublishAssortment {
|
|
|
315
315
|
}
|
|
316
316
|
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
317
317
|
try {
|
|
318
|
-
console.info('downloadHydratedChangeDetail-start');
|
|
319
318
|
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
320
319
|
const response = await fetch(link);
|
|
321
320
|
const data = await response.json();
|
|
322
321
|
return data;
|
|
323
322
|
}
|
|
324
323
|
catch (e) {
|
|
325
|
-
console.
|
|
324
|
+
console.error('Error hydratedDetailDownloadLink: ' + e.message);
|
|
326
325
|
}
|
|
327
326
|
return undefined;
|
|
328
327
|
}
|
|
329
328
|
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
330
|
-
console.info('downloadAssortmentBaseline-start');
|
|
331
329
|
try {
|
|
332
330
|
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
333
331
|
const response = await fetch(link);
|
|
@@ -335,15 +333,12 @@ class BaseProcessPublishAssortment {
|
|
|
335
333
|
return data;
|
|
336
334
|
}
|
|
337
335
|
catch (e) {
|
|
338
|
-
console.
|
|
336
|
+
console.error('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
339
337
|
}
|
|
340
338
|
return undefined;
|
|
341
339
|
}
|
|
342
340
|
async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
|
|
343
|
-
console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
|
|
344
|
-
console.info(sinceDate);
|
|
345
341
|
const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
|
|
346
|
-
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
347
342
|
if (currentAPCIndex == 0) {
|
|
348
343
|
return [];
|
|
349
344
|
}
|
|
@@ -352,16 +347,13 @@ class BaseProcessPublishAssortment {
|
|
|
352
347
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
353
348
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
354
349
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
console.info('sinceDate: ' + sinceDate);
|
|
358
|
-
console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
|
|
359
|
-
console.info('apcDateMilliseconds: ' + previousApcDate);
|
|
360
|
-
}
|
|
350
|
+
console.info('getDeleteChanges(): apc: ' + assortmentPublishChange?.id
|
|
351
|
+
+ ', index: ' + currentAPCIndex + ', previousApcCreatedOn: ' + apcCreatedOn);
|
|
361
352
|
//if only 1 apc, no processing needed
|
|
362
353
|
if (sinceDateMilliseconds !== previousApcDate) {
|
|
363
|
-
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
364
354
|
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
355
|
+
let scannedCount = 0;
|
|
356
|
+
let addedCount = 0;
|
|
365
357
|
const deleteIds = (apcDeletes.length === 0)
|
|
366
358
|
? []
|
|
367
359
|
: apcDeletes.map(item => item?.itemId);
|
|
@@ -376,19 +368,20 @@ class BaseProcessPublishAssortment {
|
|
|
376
368
|
? apcHistory[i - 1]
|
|
377
369
|
: undefined;
|
|
378
370
|
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
379
|
-
console.info('checking deleteChanges');
|
|
380
371
|
for (const dItem of deleteChanges) {
|
|
381
372
|
const dItemId = dItem?.itemId;
|
|
382
|
-
|
|
373
|
+
scannedCount++;
|
|
383
374
|
if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
|
|
384
|
-
|
|
375
|
+
addedCount++;
|
|
385
376
|
deleteIds.push(dItemId);
|
|
386
377
|
apcDeletes.push(dItem);
|
|
387
378
|
}
|
|
388
379
|
}
|
|
389
380
|
}
|
|
390
381
|
}
|
|
391
|
-
console.info('getDeleteChanges()
|
|
382
|
+
console.info('getDeleteChanges(): baselineItems: ' + currentAssortmentItemIds.length
|
|
383
|
+
+ ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
|
|
384
|
+
+ ', deleteIds: ' + deleteIds.length);
|
|
392
385
|
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
393
386
|
}
|
|
394
387
|
return apcDeletes;
|
|
@@ -402,7 +395,6 @@ class BaseProcessPublishAssortment {
|
|
|
402
395
|
return itemIds;
|
|
403
396
|
}
|
|
404
397
|
async downloadDeleteChanges(apc) {
|
|
405
|
-
console.info('deleteDataDownloadLink-start');
|
|
406
398
|
try {
|
|
407
399
|
const link = apc?.deleteDataDownloadLink;
|
|
408
400
|
const response = await fetch(link);
|
|
@@ -410,19 +402,17 @@ class BaseProcessPublishAssortment {
|
|
|
410
402
|
return data;
|
|
411
403
|
}
|
|
412
404
|
catch (e) {
|
|
413
|
-
console.
|
|
405
|
+
console.error('Error deleteDataDownloadLink: ' + e.message);
|
|
414
406
|
}
|
|
415
407
|
return undefined;
|
|
416
408
|
}
|
|
417
409
|
async buildDeleteChanges(apc, previousApc) {
|
|
418
|
-
console.info('buildDeleteChanges()');
|
|
419
410
|
if (apc?.deleteDataDownloadLink) {
|
|
420
411
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
421
412
|
if (deleteChanges) {
|
|
422
413
|
return deleteChanges;
|
|
423
414
|
}
|
|
424
415
|
}
|
|
425
|
-
console.info('pulling down full APC');
|
|
426
416
|
apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
|
|
427
417
|
if (apc?.deleteDataDownloadLink) {
|
|
428
418
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
@@ -434,19 +424,18 @@ class BaseProcessPublishAssortment {
|
|
|
434
424
|
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
435
425
|
}
|
|
436
426
|
let previousBaseline;
|
|
437
|
-
console.info('check previousApc');
|
|
438
427
|
if (previousApc?.assortmentBaselineDownloadLink) {
|
|
439
428
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
440
429
|
}
|
|
441
430
|
else {
|
|
442
|
-
console.info('previousApc pulling down full APC');
|
|
443
431
|
previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
|
|
444
432
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
445
433
|
}
|
|
446
434
|
const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
|
|
447
435
|
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
448
436
|
const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
|
|
449
|
-
console.info('
|
|
437
|
+
console.info('buildDeleteChanges(): no deleteDataDownloadLink on apc ' + apc?.id
|
|
438
|
+
+ '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
|
|
450
439
|
return deleteArray;
|
|
451
440
|
}
|
|
452
441
|
async getItemFederatedIds( /*itemIds*/) {
|
|
@@ -484,7 +473,6 @@ class BaseProcessPublishAssortment {
|
|
|
484
473
|
return deleteItems;
|
|
485
474
|
}
|
|
486
475
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
|
|
487
|
-
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
488
476
|
const releasedForDevelopmentItemIds = [];
|
|
489
477
|
const itemFamilySet = new Set();
|
|
490
478
|
const assortmentItemsArray = fullChange?.assortmentItems;
|
|
@@ -536,7 +524,6 @@ class BaseProcessPublishAssortment {
|
|
|
536
524
|
return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
537
525
|
}
|
|
538
526
|
async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
|
|
539
|
-
console.info('processPublish-start');
|
|
540
527
|
const event = {
|
|
541
528
|
assortmentId: pcd.assortmentId,
|
|
542
529
|
assortmentPublishChangeId: pcd.assortmentPublishChangeId
|
|
@@ -720,7 +707,6 @@ class BaseProcessPublishAssortment {
|
|
|
720
707
|
+ '-' + d.getUTCMilliseconds();
|
|
721
708
|
}
|
|
722
709
|
getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
|
|
723
|
-
console.info('getItemFamilyChanges-start');
|
|
724
710
|
const itemFamilyChanges = new Map();
|
|
725
711
|
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
726
712
|
const addIds = adds.map(item => item.id);
|
|
@@ -785,8 +771,7 @@ class BaseProcessPublishAssortment {
|
|
|
785
771
|
}
|
|
786
772
|
else {
|
|
787
773
|
if (!item) {
|
|
788
|
-
console.error('Failed to find deleted item entity');
|
|
789
|
-
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
774
|
+
console.error('Failed to find deleted item entity: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
|
|
790
775
|
continue;
|
|
791
776
|
}
|
|
792
777
|
}
|
|
@@ -814,7 +799,7 @@ class BaseProcessPublishAssortment {
|
|
|
814
799
|
ifc.colorDeletes.push(itemId);
|
|
815
800
|
}
|
|
816
801
|
} //End deletes for loop
|
|
817
|
-
if (
|
|
802
|
+
if (Logger.isDebugOn()) {
|
|
818
803
|
console.debug('returning size: ' + itemFamilyChanges.size);
|
|
819
804
|
for (const [key, value] of itemFamilyChanges) {
|
|
820
805
|
console.debug(key + ' => ' + value);
|
|
@@ -823,7 +808,6 @@ class BaseProcessPublishAssortment {
|
|
|
823
808
|
return itemFamilyChanges;
|
|
824
809
|
}
|
|
825
810
|
async getEventsForPublishChangeData(publishChangeData) {
|
|
826
|
-
console.info('getEventsForPublishChangeData-start');
|
|
827
811
|
const seasonalPayloads = [];
|
|
828
812
|
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
829
813
|
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
@@ -831,6 +815,8 @@ class BaseProcessPublishAssortment {
|
|
|
831
815
|
seasonalPayloads.push(...events);
|
|
832
816
|
}
|
|
833
817
|
}
|
|
818
|
+
console.info('getEventsForPublishChangeData: itemFamilies: '
|
|
819
|
+
+ publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
|
|
834
820
|
return seasonalPayloads;
|
|
835
821
|
}
|
|
836
822
|
/**Returns the events for a given ItemFamilyChanges object
|
|
@@ -850,7 +836,6 @@ class BaseProcessPublishAssortment {
|
|
|
850
836
|
* @returns
|
|
851
837
|
*/
|
|
852
838
|
async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
|
|
853
|
-
console.info('getEventsForItemFamilyChanges()');
|
|
854
839
|
const events = [];
|
|
855
840
|
const LCSSeason = Object.assign({}, seasonFed);
|
|
856
841
|
const assortment = await this.getAssortment(assortmentId);
|
|
@@ -175,18 +175,12 @@ class DataConverter {
|
|
|
175
175
|
}
|
|
176
176
|
async getObjectReferenceValue(prop, newData, inflateObjRef = false) {
|
|
177
177
|
const slug = prop['slug'];
|
|
178
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
179
|
-
console.debug('getObjectReferenceValue-prop: ' + slug);
|
|
180
|
-
}
|
|
181
178
|
let value = newData[slug];
|
|
182
179
|
const entityType = prop['referencedTypeRootSlug'];
|
|
183
180
|
const entityId = newData[slug + 'Id'];
|
|
184
181
|
if ((!value || typeof value === 'string') && inflateObjRef) {
|
|
185
182
|
if (entityId) {
|
|
186
183
|
if (this.objRefCache[entityId]) {
|
|
187
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
188
|
-
console.debug('cache hit: ' + entityId);
|
|
189
|
-
}
|
|
190
184
|
return this.objRefCache[entityId];
|
|
191
185
|
}
|
|
192
186
|
const criteria = {
|
|
@@ -432,9 +426,6 @@ class DataConverter {
|
|
|
432
426
|
return "";
|
|
433
427
|
}
|
|
434
428
|
if (this.objRefCache[ctx.cacheKey]) {
|
|
435
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
436
|
-
console.debug(`object reference cache hit: ${ctx.cacheKey}`);
|
|
437
|
-
}
|
|
438
429
|
return this.objRefCache[ctx.cacheKey];
|
|
439
430
|
}
|
|
440
431
|
const objectReferenceId = ctx.useIdentityService
|
|
@@ -634,9 +625,6 @@ class DataConverter {
|
|
|
634
625
|
}
|
|
635
626
|
let cacheUser = DataConverter.getFromStaticCache(nd.email);
|
|
636
627
|
if (cacheUser) {
|
|
637
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
638
|
-
console.debug('user cache hit: ' + nd.email);
|
|
639
|
-
}
|
|
640
628
|
await this.processGroupMemberCheck(prop, nd.email);
|
|
641
629
|
return cacheUser;
|
|
642
630
|
}
|
|
@@ -706,18 +694,12 @@ class DataConverter {
|
|
|
706
694
|
*/
|
|
707
695
|
async getUserListValue(prop, newData) {
|
|
708
696
|
const slug = prop['slug'];
|
|
709
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
710
|
-
console.debug('getUserListValue-prop: ' + slug);
|
|
711
|
-
}
|
|
712
697
|
const entityId = newData[slug + 'Id'];
|
|
713
698
|
if (!entityId) {
|
|
714
699
|
return {};
|
|
715
700
|
}
|
|
716
701
|
const cacheUser = DataConverter.getFromStaticCache(entityId);
|
|
717
702
|
if (cacheUser) {
|
|
718
|
-
if (app_framework_1.Logger.isDebugOn()) {
|
|
719
|
-
console.debug('user cache hit: ' + entityId);
|
|
720
|
-
}
|
|
721
703
|
return Object.assign({}, cacheUser);
|
|
722
704
|
}
|
|
723
705
|
const user = await this.getUserById(entityId);
|
package/package.json
CHANGED
|
@@ -1,67 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@contrail/flexplm",
|
|
3
|
-
"version": "1.7.4-alpha.
|
|
4
|
-
"description": "Library used for integration with flexplm.",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"types": "lib/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"build": "tsc
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"test": "jest",
|
|
21
|
-
"test-
|
|
22
|
-
"test-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"tslint": "^
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"@contrail/
|
|
61
|
-
"@contrail/
|
|
62
|
-
"@contrail/
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@contrail/flexplm",
|
|
3
|
+
"version": "1.7.4-alpha.72422a6",
|
|
4
|
+
"description": "Library used for integration with flexplm.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"lib/**/*",
|
|
12
|
+
"scripts/copy-template.js"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc; node scripts/copy-template.js",
|
|
16
|
+
"build:win": "tsc && node scripts/copy-template.js",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
18
|
+
"lint": "tslint -p tsconfig.json",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"test-watch": "jest --watch",
|
|
21
|
+
"test-debug": "jest --runInBand",
|
|
22
|
+
"test-coverage": "jest --coverage"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [],
|
|
25
|
+
"author": "VibeIQ",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/jest": "^29.5.2",
|
|
29
|
+
"jest": "^29.5.0",
|
|
30
|
+
"prettier": "^1.19.1",
|
|
31
|
+
"ts-jest": "^29.1.1",
|
|
32
|
+
"tslint": "^5.11.0",
|
|
33
|
+
"tslint-config-prettier": "^1.18.0",
|
|
34
|
+
"typescript": "^4.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"typescript": ">=4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"typescript": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"jest": {
|
|
45
|
+
"moduleFileExtensions": [
|
|
46
|
+
"js",
|
|
47
|
+
"json",
|
|
48
|
+
"ts"
|
|
49
|
+
],
|
|
50
|
+
"rootDir": "src",
|
|
51
|
+
"testRegex": ".spec.ts$",
|
|
52
|
+
"transform": {
|
|
53
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
54
|
+
},
|
|
55
|
+
"coverageDirectory": "../coverage",
|
|
56
|
+
"testEnvironment": "node"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@contrail/app-framework": "^1.4.3",
|
|
60
|
+
"@contrail/sdk": "^1.5.10",
|
|
61
|
+
"@contrail/transform-data": "^1.3.2",
|
|
62
|
+
"@contrail/util": "^1.3.1",
|
|
63
|
+
"axios": "^1.4.0",
|
|
64
|
+
"p-limit": "^3.1.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/scripts/copy-template.js
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const SRC = path.join('src', 'cli', 'template', 'mapping-template.ts.template');
|
|
6
|
+
const DST = path.join('lib', 'cli', 'template', 'mapping-template.ts.template');
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
fs.mkdirSync(path.dirname(DST), { recursive: true });
|
|
12
|
-
fs.copyFileSync(SRC, DST);
|
|
13
|
-
console.log(`Copied ${SRC} -> ${DST}`);
|
|
14
|
-
}
|
|
8
|
+
fs.mkdirSync(path.dirname(DST), { recursive: true });
|
|
9
|
+
fs.copyFileSync(SRC, DST);
|
|
10
|
+
console.log(`Copied ${SRC} -> ${DST}`);
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.ConfigCreateCommand = void 0;
|
|
27
|
-
const fs = __importStar(require("fs"));
|
|
28
|
-
const path = __importStar(require("path"));
|
|
29
|
-
const readline = __importStar(require("readline"));
|
|
30
|
-
const TEMPLATE_FILENAME = 'config-template.json.template';
|
|
31
|
-
const ORG_PLACEHOLDER = '<ORG_NAME>';
|
|
32
|
-
const APP_IDENTIFIER_PLACEHOLDER = '<APP_IDENTIFIER>';
|
|
33
|
-
const DEFAULT_APP_IDENTIFIER = '@vibeiq/flexplm-connector';
|
|
34
|
-
class ConfigCreateCommand {
|
|
35
|
-
prompt(rl, question) {
|
|
36
|
-
return new Promise((resolve) => {
|
|
37
|
-
rl.question(question, (answer) => resolve(answer));
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
findTemplate() {
|
|
41
|
-
const candidates = [
|
|
42
|
-
path.join(__dirname, '..', 'template', TEMPLATE_FILENAME),
|
|
43
|
-
path.join(__dirname, '..', '..', '..', 'src', 'cli', 'template', TEMPLATE_FILENAME),
|
|
44
|
-
];
|
|
45
|
-
for (const candidate of candidates) {
|
|
46
|
-
if (fs.existsSync(candidate)) {
|
|
47
|
-
return candidate;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
throw new Error(`Could not locate ${TEMPLATE_FILENAME}. Tried:\n ${candidates.join('\n ')}`);
|
|
51
|
-
}
|
|
52
|
-
async run() {
|
|
53
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
-
const onSigint = () => {
|
|
55
|
-
rl.close();
|
|
56
|
-
process.stdout.write('\n');
|
|
57
|
-
process.exit(130);
|
|
58
|
-
};
|
|
59
|
-
process.once('SIGINT', onSigint);
|
|
60
|
-
let orgName;
|
|
61
|
-
let appIdentifier;
|
|
62
|
-
try {
|
|
63
|
-
orgName = (await this.prompt(rl, 'orgName: ')).trim();
|
|
64
|
-
appIdentifier = (await this.prompt(rl, `appIdentifier (default: ${DEFAULT_APP_IDENTIFIER}): `)).trim() || DEFAULT_APP_IDENTIFIER;
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
process.removeListener('SIGINT', onSigint);
|
|
68
|
-
rl.close();
|
|
69
|
-
}
|
|
70
|
-
if (!orgName) {
|
|
71
|
-
throw new Error('orgName is required');
|
|
72
|
-
}
|
|
73
|
-
const templatePath = this.findTemplate();
|
|
74
|
-
const templateBody = fs.readFileSync(templatePath, 'utf8');
|
|
75
|
-
const rendered = templateBody.split(ORG_PLACEHOLDER).join(orgName).split(APP_IDENTIFIER_PLACEHOLDER).join(appIdentifier);
|
|
76
|
-
const outPath = path.resolve(process.cwd(), `${orgName}-flexplmConfig.json`);
|
|
77
|
-
if (fs.existsSync(outPath)) {
|
|
78
|
-
throw new Error(`Refusing to overwrite existing file: ${outPath}`);
|
|
79
|
-
}
|
|
80
|
-
fs.writeFileSync(outPath, rendered, 'utf8');
|
|
81
|
-
console.log(`Created ${outPath}`);
|
|
82
|
-
console.log('"orgName" and "appIdentifier" are pre-filled, but this file is not complete yet: FlexPLM connectivity also requires apiHost, userName, and password. See the "_availableAttributes" block in the file for these and other attributes this org can set (identifierAtts, LCSMaterial, etc.) — add the ones you need as real top-level keys, then delete "_availableAttributes"; the connector ignores it and applies defaults at runtime for anything you omit.');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
exports.ConfigCreateCommand = ConfigCreateCommand;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
const fs = __importStar(require("fs"));
|
|
27
|
-
const os = __importStar(require("os"));
|
|
28
|
-
const path = __importStar(require("path"));
|
|
29
|
-
let answers = [];
|
|
30
|
-
jest.mock('readline', () => ({
|
|
31
|
-
createInterface: () => ({
|
|
32
|
-
question: (_q, cb) => cb(answers.shift() || ''),
|
|
33
|
-
close: () => { },
|
|
34
|
-
}),
|
|
35
|
-
}));
|
|
36
|
-
const config_create_1 = require("./config-create");
|
|
37
|
-
describe('ConfigCreateCommand', () => {
|
|
38
|
-
let tempDir;
|
|
39
|
-
let originalCwd;
|
|
40
|
-
let logSpy;
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
originalCwd = process.cwd();
|
|
43
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flexplm-config-create-'));
|
|
44
|
-
process.chdir(tempDir);
|
|
45
|
-
logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
46
|
-
answers = [];
|
|
47
|
-
});
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
logSpy.mockRestore();
|
|
50
|
-
process.chdir(originalCwd);
|
|
51
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
52
|
-
});
|
|
53
|
-
it('writes <orgName>-flexplmConfig.json with orgName and appIdentifier filled in', async () => {
|
|
54
|
-
answers = ['acme', '@vibeiq/flexplm-connector'];
|
|
55
|
-
await new config_create_1.ConfigCreateCommand().run();
|
|
56
|
-
const outPath = path.join(tempDir, 'acme-flexplmConfig.json');
|
|
57
|
-
expect(fs.existsSync(outPath)).toBe(true);
|
|
58
|
-
const config = JSON.parse(fs.readFileSync(outPath, 'utf8'));
|
|
59
|
-
expect(config.orgName).toEqual('acme');
|
|
60
|
-
expect(config.appIdentifier).toEqual('@vibeiq/flexplm-connector');
|
|
61
|
-
});
|
|
62
|
-
it('defaults appIdentifier to @vibeiq/flexplm-connector when left blank', async () => {
|
|
63
|
-
answers = ['acme', ''];
|
|
64
|
-
await new config_create_1.ConfigCreateCommand().run();
|
|
65
|
-
const outPath = path.join(tempDir, 'acme-flexplmConfig.json');
|
|
66
|
-
const config = JSON.parse(fs.readFileSync(outPath, 'utf8'));
|
|
67
|
-
expect(config.appIdentifier).toEqual('@vibeiq/flexplm-connector');
|
|
68
|
-
});
|
|
69
|
-
it('throws when orgName is empty', async () => {
|
|
70
|
-
answers = [' '];
|
|
71
|
-
await expect(new config_create_1.ConfigCreateCommand().run()).rejects.toThrow(/orgName is required/);
|
|
72
|
-
});
|
|
73
|
-
it('refuses to overwrite an existing file', async () => {
|
|
74
|
-
const existing = path.join(tempDir, 'acme-flexplmConfig.json');
|
|
75
|
-
fs.writeFileSync(existing, 'do not clobber', 'utf8');
|
|
76
|
-
answers = ['acme', '@vibeiq/flexplm-connector'];
|
|
77
|
-
await expect(new config_create_1.ConfigCreateCommand().run()).rejects.toThrow(/Refusing to overwrite/);
|
|
78
|
-
expect(fs.readFileSync(existing, 'utf8')).toEqual('do not clobber');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
interface ConfigUploadOptions {
|
|
2
|
-
filePath: string;
|
|
3
|
-
message?: string;
|
|
4
|
-
branch?: string;
|
|
5
|
-
skipGit: boolean;
|
|
6
|
-
updateConfig: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare class ConfigUploadCommand {
|
|
9
|
-
static parseArgs(args: string[]): ConfigUploadOptions;
|
|
10
|
-
static buildCommitMessage(userMessage: string, fileId: string): string;
|
|
11
|
-
private prompt;
|
|
12
|
-
private promptHidden;
|
|
13
|
-
private runGit;
|
|
14
|
-
private tryRunGit;
|
|
15
|
-
private commitToGit;
|
|
16
|
-
run(args: string[]): Promise<void>;
|
|
17
|
-
private setConfigFileOnAppOrg;
|
|
18
|
-
}
|
|
19
|
-
export {};
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.ConfigUploadCommand = void 0;
|
|
27
|
-
const child_process_1 = require("child_process");
|
|
28
|
-
const fs = __importStar(require("fs"));
|
|
29
|
-
const path = __importStar(require("path"));
|
|
30
|
-
const readline = __importStar(require("readline"));
|
|
31
|
-
const sdk_1 = require("@contrail/sdk");
|
|
32
|
-
class ConfigUploadCommand {
|
|
33
|
-
static parseArgs(args) {
|
|
34
|
-
let filePath;
|
|
35
|
-
let message;
|
|
36
|
-
let branch;
|
|
37
|
-
let skipGit = false;
|
|
38
|
-
let updateConfig = false;
|
|
39
|
-
for (let i = 0; i < args.length; i++) {
|
|
40
|
-
const a = args[i];
|
|
41
|
-
if (a === '-m') {
|
|
42
|
-
message = args[++i];
|
|
43
|
-
if (message === undefined) {
|
|
44
|
-
throw new Error('-m requires a commit message argument');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
else if (a === '-b') {
|
|
48
|
-
branch = args[++i];
|
|
49
|
-
if (branch === undefined) {
|
|
50
|
-
throw new Error('-b requires a branch name argument');
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else if (a === '--skip-git' || a === '--skipGit') {
|
|
54
|
-
skipGit = true;
|
|
55
|
-
}
|
|
56
|
-
else if (a === '--update-config') {
|
|
57
|
-
updateConfig = true;
|
|
58
|
-
}
|
|
59
|
-
else if (a.startsWith('-')) {
|
|
60
|
-
throw new Error(`Unknown option: ${a}`);
|
|
61
|
-
}
|
|
62
|
-
else if (!filePath) {
|
|
63
|
-
filePath = a;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
throw new Error(`Unexpected argument: ${a}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (!filePath) {
|
|
70
|
-
throw new Error('upload: missing <path.json> argument');
|
|
71
|
-
}
|
|
72
|
-
return { filePath, message, branch, skipGit, updateConfig };
|
|
73
|
-
}
|
|
74
|
-
static buildCommitMessage(userMessage, fileId) {
|
|
75
|
-
const lines = userMessage.split(/\r?\n/);
|
|
76
|
-
lines[0] = `${lines[0]} [fileId: ${fileId}]`;
|
|
77
|
-
return lines.join('\n');
|
|
78
|
-
}
|
|
79
|
-
prompt(question) {
|
|
80
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
81
|
-
return new Promise((resolve) => {
|
|
82
|
-
rl.question(question, (answer) => {
|
|
83
|
-
rl.close();
|
|
84
|
-
resolve(answer.trim());
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
promptHidden(question) {
|
|
89
|
-
return new Promise((resolve) => {
|
|
90
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
91
|
-
const rlAny = rl;
|
|
92
|
-
rlAny._writeToOutput = (str) => {
|
|
93
|
-
if (str.includes(question)) {
|
|
94
|
-
rlAny.output.write(str);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
rl.question(question, (answer) => {
|
|
98
|
-
rl.close();
|
|
99
|
-
process.stdout.write('\n');
|
|
100
|
-
resolve(answer);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
runGit(args, cwd) {
|
|
105
|
-
return (0, child_process_1.execFileSync)('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
106
|
-
}
|
|
107
|
-
tryRunGit(args, cwd) {
|
|
108
|
-
try {
|
|
109
|
-
const stdout = this.runGit(args, cwd);
|
|
110
|
-
return { ok: true, stdout, stderr: '' };
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
return {
|
|
114
|
-
ok: false,
|
|
115
|
-
stdout: err && err.stdout ? err.stdout.toString() : '',
|
|
116
|
-
stderr: err && err.stderr ? err.stderr.toString() : (err && err.message) || '',
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async commitToGit(absPath, fileId, options) {
|
|
121
|
-
const repoDir = path.dirname(absPath);
|
|
122
|
-
const relPath = path.basename(absPath);
|
|
123
|
-
const versionCheck = this.tryRunGit(['--version'], repoDir);
|
|
124
|
-
if (!versionCheck.ok) {
|
|
125
|
-
console.log('git command not available; skipping git commit.');
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const insideRepo = this.tryRunGit(['rev-parse', '--is-inside-work-tree'], repoDir);
|
|
129
|
-
if (!insideRepo.ok || insideRepo.stdout.trim() !== 'true') {
|
|
130
|
-
console.log(`Not inside a git working tree (${repoDir}); skipping git commit.`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const tracked = this.tryRunGit(['ls-files', '--error-unmatch', relPath], repoDir);
|
|
134
|
-
if (!tracked.ok) {
|
|
135
|
-
const answer = (await this.prompt(`File is not tracked by git: ${relPath}\nAdd it to git? (Y/n): `)).toLowerCase();
|
|
136
|
-
if (answer === 'n' || answer === 'no') {
|
|
137
|
-
console.log('Nothing was done in git.');
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (options.branch) {
|
|
142
|
-
const branchResult = this.tryRunGit(['checkout', '-b', options.branch], repoDir);
|
|
143
|
-
if (!branchResult.ok) {
|
|
144
|
-
throw new Error(`Failed to create branch "${options.branch}": ${branchResult.stderr.trim()}`);
|
|
145
|
-
}
|
|
146
|
-
console.log(`Created and switched to branch "${options.branch}"`);
|
|
147
|
-
}
|
|
148
|
-
let message = options.message;
|
|
149
|
-
if (!message) {
|
|
150
|
-
message = await this.prompt('Commit message: ');
|
|
151
|
-
if (!message) {
|
|
152
|
-
throw new Error('A commit message is required');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const finalMessage = ConfigUploadCommand.buildCommitMessage(message, fileId);
|
|
156
|
-
const addResult = this.tryRunGit(['add', '--', relPath], repoDir);
|
|
157
|
-
if (!addResult.ok) {
|
|
158
|
-
throw new Error(`git add failed: ${addResult.stderr.trim()}`);
|
|
159
|
-
}
|
|
160
|
-
const commitResult = this.tryRunGit(['commit', '-m', finalMessage, '--', relPath], repoDir);
|
|
161
|
-
if (!commitResult.ok) {
|
|
162
|
-
throw new Error(`git commit failed: ${commitResult.stderr.trim() || commitResult.stdout.trim()}`);
|
|
163
|
-
}
|
|
164
|
-
console.log(commitResult.stdout.trim());
|
|
165
|
-
}
|
|
166
|
-
async run(args) {
|
|
167
|
-
const options = ConfigUploadCommand.parseArgs(args);
|
|
168
|
-
const absPath = path.resolve(process.cwd(), options.filePath);
|
|
169
|
-
if (!fs.existsSync(absPath)) {
|
|
170
|
-
throw new Error(`File not found: ${absPath}`);
|
|
171
|
-
}
|
|
172
|
-
if (!absPath.endsWith('.json')) {
|
|
173
|
-
throw new Error(`Expected a .json file, got: ${absPath}`);
|
|
174
|
-
}
|
|
175
|
-
const raw = fs.readFileSync(absPath, 'utf8');
|
|
176
|
-
let config;
|
|
177
|
-
try {
|
|
178
|
-
config = JSON.parse(raw);
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
throw new Error(`File is not valid JSON: ${absPath}\n${err && err.message ? err.message : err}`);
|
|
182
|
-
}
|
|
183
|
-
const orgName = config && config.orgName;
|
|
184
|
-
const appIdentifier = config && config.appIdentifier;
|
|
185
|
-
if (!orgName) {
|
|
186
|
-
throw new Error(`Config file is missing "orgName": ${absPath}`);
|
|
187
|
-
}
|
|
188
|
-
if (!appIdentifier) {
|
|
189
|
-
throw new Error(`Config file is missing "appIdentifier": ${absPath}`);
|
|
190
|
-
}
|
|
191
|
-
let email = process.env.CONTRAIL_CLI_EMAIL;
|
|
192
|
-
let password = process.env.CONTRAIL_CLI_PASSWORD;
|
|
193
|
-
if (!email) {
|
|
194
|
-
email = await this.prompt('Email: ');
|
|
195
|
-
}
|
|
196
|
-
if (!password) {
|
|
197
|
-
password = await this.promptHidden('Password: ');
|
|
198
|
-
}
|
|
199
|
-
if (!email || !password) {
|
|
200
|
-
throw new Error('Email and password are required');
|
|
201
|
-
}
|
|
202
|
-
await (0, sdk_1.login)({ orgSlug: orgName, email, password });
|
|
203
|
-
console.log(`Logged in to org "${orgName}" as ${email}`);
|
|
204
|
-
const apps = await new sdk_1.Entities().get({
|
|
205
|
-
entityName: 'app',
|
|
206
|
-
criteria: { identifier: appIdentifier },
|
|
207
|
-
});
|
|
208
|
-
if (!apps || apps.length !== 1) {
|
|
209
|
-
throw new Error(`Expected exactly one app with identifier "${appIdentifier}" in org "${orgName}", found ${apps ? apps.length : 0}`);
|
|
210
|
-
}
|
|
211
|
-
const app = apps[0];
|
|
212
|
-
const buffer = fs.readFileSync(absPath);
|
|
213
|
-
const fileName = path.basename(absPath);
|
|
214
|
-
const fileOwner = `app:${app.id}`;
|
|
215
|
-
const uploadedFile = await new sdk_1.Files().createAndUploadFileFromBuffer(buffer, 'application/json', fileName, fileOwner);
|
|
216
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
217
|
-
const responsePath = `${absPath}.uploaded-${timestamp}.json`;
|
|
218
|
-
fs.writeFileSync(responsePath, JSON.stringify(uploadedFile, null, 2), 'utf8');
|
|
219
|
-
console.log(`Wrote response to ${responsePath}`);
|
|
220
|
-
console.log(`FILE ID: ${uploadedFile.id}`);
|
|
221
|
-
if (!options.skipGit) {
|
|
222
|
-
await this.commitToGit(absPath, uploadedFile.id, options);
|
|
223
|
-
}
|
|
224
|
-
if (options.updateConfig) {
|
|
225
|
-
await this.setConfigFileOnAppOrg(app.id, appIdentifier, orgName, uploadedFile.id);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
async setConfigFileOnAppOrg(appId, appIdentifier, orgName, fileId) {
|
|
229
|
-
const appOrgs = await new sdk_1.Entities().get({
|
|
230
|
-
entityName: 'app-org',
|
|
231
|
-
criteria: { appId },
|
|
232
|
-
});
|
|
233
|
-
if (!appOrgs || appOrgs.length === 0) {
|
|
234
|
-
throw new Error(`Failed to set the file onto the app config for "${appIdentifier}" because it is not installed in org "${orgName}". Install it via the admin console before using --update-config. You can paste the uploaded file's ID into the app config without needing to re-run this command.`);
|
|
235
|
-
}
|
|
236
|
-
if (appOrgs.length > 1) {
|
|
237
|
-
throw new Error(`Failed to set the file onto the app config for "${appIdentifier}" in org "${orgName}" because ${appOrgs.length} installations were identified. Expected one. Please contact customer support for assistance.`);
|
|
238
|
-
}
|
|
239
|
-
const appOrg = appOrgs[0];
|
|
240
|
-
const nextAppConfig = { ...(appOrg.appConfig || {}), configFile: fileId };
|
|
241
|
-
await new sdk_1.Entities().update({
|
|
242
|
-
entityName: 'app-org',
|
|
243
|
-
id: appOrg.id,
|
|
244
|
-
object: { appConfig: nextAppConfig },
|
|
245
|
-
});
|
|
246
|
-
console.log(`Successfully set "appConfig.configFile" for installed "${appIdentifier}" to new FILE ID: "${fileId}" on org "${orgName}"`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
exports.ConfigUploadCommand = ConfigUploadCommand;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
jest.mock('@contrail/sdk', () => ({
|
|
4
|
-
Entities: jest.fn(),
|
|
5
|
-
Files: jest.fn(),
|
|
6
|
-
login: jest.fn(),
|
|
7
|
-
}));
|
|
8
|
-
const config_upload_1 = require("./config-upload");
|
|
9
|
-
describe('ConfigUploadCommand.parseArgs', () => {
|
|
10
|
-
it('parses a bare file path', () => {
|
|
11
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json']);
|
|
12
|
-
expect(opts).toEqual({
|
|
13
|
-
filePath: 'config.json',
|
|
14
|
-
message: undefined,
|
|
15
|
-
branch: undefined,
|
|
16
|
-
skipGit: false,
|
|
17
|
-
updateConfig: false,
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
it('parses -m commit message option', () => {
|
|
21
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '-m', 'my message']);
|
|
22
|
-
expect(opts.message).toEqual('my message');
|
|
23
|
-
expect(opts.skipGit).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
it('parses -b branch option', () => {
|
|
26
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '-b', 'feature/x']);
|
|
27
|
-
expect(opts.branch).toEqual('feature/x');
|
|
28
|
-
});
|
|
29
|
-
it('parses --skip-git flag', () => {
|
|
30
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '--skip-git']);
|
|
31
|
-
expect(opts.skipGit).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
it('accepts the legacy --skipGit alias', () => {
|
|
34
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '--skipGit']);
|
|
35
|
-
expect(opts.skipGit).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
it('parses --update-config flag', () => {
|
|
38
|
-
const opts = config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '--update-config']);
|
|
39
|
-
expect(opts.updateConfig).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
it('throws when -m is missing its value', () => {
|
|
42
|
-
expect(() => config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '-m'])).toThrow(/-m requires a commit message/);
|
|
43
|
-
});
|
|
44
|
-
it('throws on unknown option', () => {
|
|
45
|
-
expect(() => config_upload_1.ConfigUploadCommand.parseArgs(['config.json', '--bogus'])).toThrow(/Unknown option: --bogus/);
|
|
46
|
-
});
|
|
47
|
-
it('throws when no file path is provided', () => {
|
|
48
|
-
expect(() => config_upload_1.ConfigUploadCommand.parseArgs([])).toThrow(/missing <path\.json>/);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('ConfigUploadCommand.buildCommitMessage', () => {
|
|
52
|
-
it('appends fileId to the first line of a single-line message', () => {
|
|
53
|
-
expect(config_upload_1.ConfigUploadCommand.buildCommitMessage('initial commit', 'abc123')).toEqual('initial commit [fileId: abc123]');
|
|
54
|
-
});
|
|
55
|
-
it('handles CRLF line endings', () => {
|
|
56
|
-
const result = config_upload_1.ConfigUploadCommand.buildCommitMessage('header\r\nbody', 'fid');
|
|
57
|
-
expect(result).toEqual('header [fileId: fid]\nbody');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe('ConfigUploadCommand.run', () => {
|
|
61
|
-
const fs = require('fs');
|
|
62
|
-
const os = require('os');
|
|
63
|
-
const path = require('path');
|
|
64
|
-
let tempDir;
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flexplm-config-upload-'));
|
|
67
|
-
});
|
|
68
|
-
afterEach(() => {
|
|
69
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
70
|
-
});
|
|
71
|
-
it('throws when the config file is missing orgName', async () => {
|
|
72
|
-
const filePath = path.join(tempDir, 'bad.json');
|
|
73
|
-
fs.writeFileSync(filePath, JSON.stringify({ appIdentifier: '@vibeiq/flexplm-connector' }), 'utf8');
|
|
74
|
-
await expect(new config_upload_1.ConfigUploadCommand().run([filePath, '--skip-git'])).rejects.toThrow(/missing "orgName"/);
|
|
75
|
-
});
|
|
76
|
-
it('throws when the config file is missing appIdentifier', async () => {
|
|
77
|
-
const filePath = path.join(tempDir, 'bad.json');
|
|
78
|
-
fs.writeFileSync(filePath, JSON.stringify({ orgName: 'acme' }), 'utf8');
|
|
79
|
-
await expect(new config_upload_1.ConfigUploadCommand().run([filePath, '--skip-git'])).rejects.toThrow(/missing "appIdentifier"/);
|
|
80
|
-
});
|
|
81
|
-
it('throws when the file is not valid JSON', async () => {
|
|
82
|
-
const filePath = path.join(tempDir, 'bad.json');
|
|
83
|
-
fs.writeFileSync(filePath, '{not json', 'utf8');
|
|
84
|
-
await expect(new config_upload_1.ConfigUploadCommand().run([filePath, '--skip-git'])).rejects.toThrow(/not valid JSON/);
|
|
85
|
-
});
|
|
86
|
-
it('throws when the file does not exist', async () => {
|
|
87
|
-
await expect(new config_upload_1.ConfigUploadCommand().run([path.join(tempDir, 'nope.json'), '--skip-git'])).rejects.toThrow(/File not found/);
|
|
88
|
-
});
|
|
89
|
-
it('throws when the file is not a .json file', async () => {
|
|
90
|
-
const filePath = path.join(tempDir, 'config.txt');
|
|
91
|
-
fs.writeFileSync(filePath, '{}', 'utf8');
|
|
92
|
-
await expect(new config_upload_1.ConfigUploadCommand().run([filePath, '--skip-git'])).rejects.toThrow(/Expected a \.json file/);
|
|
93
|
-
});
|
|
94
|
-
});
|
package/lib/cli/config-index.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.main = exports.ConfigCli = void 0;
|
|
5
|
-
const config_create_1 = require("./commands/config-create");
|
|
6
|
-
const config_upload_1 = require("./commands/config-upload");
|
|
7
|
-
const USAGE = `Usage: flexplm-config <command> [args]
|
|
8
|
-
|
|
9
|
-
Commands:
|
|
10
|
-
create Scaffold a new connector config .json file in the current directory
|
|
11
|
-
upload <path.json> [opts] Upload a connector config .json file to VibeIQ
|
|
12
|
-
|
|
13
|
-
Upload options:
|
|
14
|
-
-m <message> Git commit message (prompted if omitted)
|
|
15
|
-
-b <branch> Create a new git branch before committing
|
|
16
|
-
--skip-git Skip the post-upload git commit (default: commit)
|
|
17
|
-
--update-config Patch the app-org appConfig.configFile with the uploaded file ID without needing to paste into the admin console
|
|
18
|
-
|
|
19
|
-
Environment (upload):
|
|
20
|
-
CONTRAIL_CLI_EMAIL VibeIQ user email
|
|
21
|
-
CONTRAIL_CLI_PASSWORD VibeIQ user password
|
|
22
|
-
`;
|
|
23
|
-
class ConfigCli {
|
|
24
|
-
async main() {
|
|
25
|
-
const [, , command, ...rest] = process.argv;
|
|
26
|
-
switch (command) {
|
|
27
|
-
case 'create':
|
|
28
|
-
await new config_create_1.ConfigCreateCommand().run();
|
|
29
|
-
return;
|
|
30
|
-
case 'upload':
|
|
31
|
-
if (!rest[0]) {
|
|
32
|
-
console.error('upload: missing <path.json> argument');
|
|
33
|
-
console.error(USAGE);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
await new config_upload_1.ConfigUploadCommand().run(rest);
|
|
37
|
-
return;
|
|
38
|
-
case undefined:
|
|
39
|
-
case '-h':
|
|
40
|
-
case '--help':
|
|
41
|
-
case 'help':
|
|
42
|
-
console.log(USAGE);
|
|
43
|
-
return;
|
|
44
|
-
default:
|
|
45
|
-
console.error(`Unknown command: ${command}`);
|
|
46
|
-
console.error(USAGE);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
exports.ConfigCli = ConfigCli;
|
|
52
|
-
function main() {
|
|
53
|
-
return new ConfigCli().main();
|
|
54
|
-
}
|
|
55
|
-
exports.main = main;
|
|
56
|
-
if (require.main === module) {
|
|
57
|
-
main().catch((err) => {
|
|
58
|
-
console.error(err && err.message ? err.message : err);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const createRunMock = jest.fn().mockResolvedValue(undefined);
|
|
4
|
-
const uploadRunMock = jest.fn().mockResolvedValue(undefined);
|
|
5
|
-
jest.mock('./commands/config-create', () => ({
|
|
6
|
-
ConfigCreateCommand: jest.fn().mockImplementation(() => ({ run: createRunMock })),
|
|
7
|
-
}));
|
|
8
|
-
jest.mock('./commands/config-upload', () => ({
|
|
9
|
-
ConfigUploadCommand: jest.fn().mockImplementation(() => ({ run: uploadRunMock })),
|
|
10
|
-
}));
|
|
11
|
-
const config_index_1 = require("./config-index");
|
|
12
|
-
describe('config cli main dispatcher', () => {
|
|
13
|
-
let originalArgv;
|
|
14
|
-
let logSpy;
|
|
15
|
-
let errorSpy;
|
|
16
|
-
let exitSpy;
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
originalArgv = process.argv;
|
|
19
|
-
createRunMock.mockClear();
|
|
20
|
-
uploadRunMock.mockClear();
|
|
21
|
-
logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
22
|
-
errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
23
|
-
exitSpy = jest.spyOn(process, 'exit').mockImplementation(((code) => {
|
|
24
|
-
throw new Error(`__EXIT__:${code}`);
|
|
25
|
-
}));
|
|
26
|
-
});
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
process.argv = originalArgv;
|
|
29
|
-
logSpy.mockRestore();
|
|
30
|
-
errorSpy.mockRestore();
|
|
31
|
-
exitSpy.mockRestore();
|
|
32
|
-
});
|
|
33
|
-
function setArgv(...args) {
|
|
34
|
-
process.argv = ['node', 'cli', ...args];
|
|
35
|
-
}
|
|
36
|
-
it('dispatches the create command', async () => {
|
|
37
|
-
setArgv('create');
|
|
38
|
-
await (0, config_index_1.main)();
|
|
39
|
-
expect(createRunMock).toHaveBeenCalledTimes(1);
|
|
40
|
-
expect(uploadRunMock).not.toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
it('dispatches the upload command and forwards remaining args', async () => {
|
|
43
|
-
setArgv('upload', 'config.json', '-m', 'msg', '--skip-git');
|
|
44
|
-
await (0, config_index_1.main)();
|
|
45
|
-
expect(uploadRunMock).toHaveBeenCalledWith(['config.json', '-m', 'msg', '--skip-git']);
|
|
46
|
-
});
|
|
47
|
-
it('exits when upload is missing its argument', async () => {
|
|
48
|
-
setArgv('upload');
|
|
49
|
-
await expect((0, config_index_1.main)()).rejects.toThrow('__EXIT__:1');
|
|
50
|
-
expect(uploadRunMock).not.toHaveBeenCalled();
|
|
51
|
-
expect(errorSpy).toHaveBeenCalledWith(expect.stringMatching(/missing <path\.json>/));
|
|
52
|
-
});
|
|
53
|
-
it.each(['help', '-h', '--help'])('prints usage for %s', async (helpFlag) => {
|
|
54
|
-
setArgv(helpFlag);
|
|
55
|
-
await (0, config_index_1.main)();
|
|
56
|
-
expect(logSpy).toHaveBeenCalledWith(expect.stringMatching(/Usage: flexplm-config/));
|
|
57
|
-
});
|
|
58
|
-
it('prints usage when no command is provided', async () => {
|
|
59
|
-
setArgv();
|
|
60
|
-
await (0, config_index_1.main)();
|
|
61
|
-
expect(logSpy).toHaveBeenCalledWith(expect.stringMatching(/Usage: flexplm-config/));
|
|
62
|
-
});
|
|
63
|
-
it('exits on an unknown command', async () => {
|
|
64
|
-
setArgv('bogus');
|
|
65
|
-
await expect((0, config_index_1.main)()).rejects.toThrow('__EXIT__:1');
|
|
66
|
-
expect(errorSpy).toHaveBeenCalledWith(expect.stringMatching(/Unknown command: bogus/));
|
|
67
|
-
});
|
|
68
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"orgName": "<ORG_NAME>",
|
|
3
|
-
"appIdentifier": "<APP_IDENTIFIER>",
|
|
4
|
-
|
|
5
|
-
"_availableAttributes": {
|
|
6
|
-
"_note": "Reference only — the connector never reads this key. Add any of the attributes below as real top-level keys if this org needs them, then delete this whole \"_availableAttributes\" block.",
|
|
7
|
-
"apiHost": "FlexPLM API host base URL. Required — throws NEED_CONFIG_VALUES if missing",
|
|
8
|
-
"userName": "FlexPLM user name used for Basic Auth. Required; rewritten to a () => string getter",
|
|
9
|
-
"password": "FlexPLM password used for Basic Auth. Required; rewritten to a () => string getter",
|
|
10
|
-
"urlContext": "Path prefix for FlexPLM URLs. Default: '/Windchill'",
|
|
11
|
-
"itemPreDevelopmentLifecycleStages": "Item lifecycle stages during which items are not synced to FlexPLM. Default: ['concept']",
|
|
12
|
-
"logLevel": "Possible values: error/warn/info/debug. Default: info",
|
|
13
|
-
"identifierAtts": "Deprecated. Map of FlexPLM object class to identifier attribute name(s), e.g. { \"LCSProduct\": [\"itemNumber\"] }",
|
|
14
|
-
"plmEnviornment": "Sent as the PLM_ENV header on every FlexPLM request. Only used by 1 customer. NOTE: this key is intentionally spelled \"plmEnviornment\" (matching the connector's actual config key) — do not \"fix\" the spelling to \"plmEnvironment\", it will silently stop working",
|
|
15
|
-
"propertyMapping": "Deprecated. Reserved for custom property-mapping overrides",
|
|
16
|
-
"complexConfig": "Object of additional attributes (see below) merged onto the top level of the config, then deleted; __proto__/constructor/prototype keys are stripped for safety. e.g. { \"complexConfig\": { \"csrfEndpoint\": \"...\" } }",
|
|
17
|
-
"csrfEndpoint (in complexConfig)": "CSRF token endpoint path. Default: '/servlet/rest/security/csrf'",
|
|
18
|
-
"dataConverter.useDisplayForEnumerationMatching (in complexConfig)": "Default: false",
|
|
19
|
-
"dataConverter.verboseDebug (in complexConfig)": "Default: false",
|
|
20
|
-
"flexplmConnect.staticHeaders (in complexConfig)": "Extra static headers added to every FlexPLM request",
|
|
21
|
-
"LCSMaterial.processAsItem (in complexConfig)": "true routes LCSMaterial to item:material instead of custom-entity. Default: false",
|
|
22
|
-
"max_thumbnail_size (in complexConfig)": "Max thumbnail size in bytes. Default: 5 MB",
|
|
23
|
-
"payloadDefaultAsArray (in complexConfig)": "Whether outbound payload values default to arrays. Default: true",
|
|
24
|
-
"search.<entityType>.useIdentityServiceForInboundData (in complexConfig)": "entityType is one of: item, color, custom-entity, project-item. Default: false",
|
|
25
|
-
"sendMode.ASYNC_PUBLISH_SEASON (in complexConfig)": "Default: 'vibeiqfile'",
|
|
26
|
-
"syncOptionSets (in complexConfig)": "Array of { flexInternalName, vibeSlug }",
|
|
27
|
-
"useDistinctRestEndPointForImages (in complexConfig)": "Default: false; when true routes image fetch through /rfa/vibeiq/image instead of the raw URL",
|
|
28
|
-
"vibeEventEndpoint (in complexConfig)": "Endpoint VibeIQ posts inbound events to. Default: '/rfa/vibeiq/vibeEvents'"
|
|
29
|
-
}
|
|
30
|
-
}
|