@contrail/flexplm 1.7.5 → 1.7.6-alpha.a804d5a

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.
@@ -29,7 +29,8 @@ class BaseProcessPublishAssortment {
29
29
  let publisher;
30
30
  const assortmentId = event.assortmentId;
31
31
  try {
32
- console.info('process-start!');
32
+ console.info('process-start: assortmentId: ' + assortmentId
33
+ + ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
33
34
  let seasonFed;
34
35
  try {
35
36
  seasonFed = await this.getSeasonFederation(assortmentId);
@@ -74,9 +75,9 @@ class BaseProcessPublishAssortment {
74
75
  e.publishInfo = publishInfo;
75
76
  }
76
77
  catch (e2) {
77
- console.log('catch e2: ' + e2.message);
78
+ console.error('getPublishInfo failed: ' + e2.message);
78
79
  }
79
- console.log('catch e: ' + e.message);
80
+ console.error('process failed: assortmentId: ' + assortmentId + ': ' + e.message);
80
81
  throw e;
81
82
  }
82
83
  }
@@ -315,19 +316,17 @@ class BaseProcessPublishAssortment {
315
316
  }
316
317
  async downloadHydratedChangeDetail(assortmentPublishChange) {
317
318
  try {
318
- console.info('downloadHydratedChangeDetail-start');
319
319
  const link = assortmentPublishChange?.hydratedDetailDownloadLink;
320
320
  const response = await fetch(link);
321
321
  const data = await response.json();
322
322
  return data;
323
323
  }
324
324
  catch (e) {
325
- console.log('Error hydratedDetailDownloadLink: ' + e.message);
325
+ console.error('Error hydratedDetailDownloadLink: ' + e.message);
326
326
  }
327
327
  return undefined;
328
328
  }
329
329
  async downloadAssortmentBaseline(assortmentPublishChange) {
330
- console.info('downloadAssortmentBaseline-start');
331
330
  try {
332
331
  const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
333
332
  const response = await fetch(link);
@@ -335,15 +334,12 @@ class BaseProcessPublishAssortment {
335
334
  return data;
336
335
  }
337
336
  catch (e) {
338
- console.log('Error assortmentBaselineDownloadLink: ' + e.message);
337
+ console.error('Error assortmentBaselineDownloadLink: ' + e.message);
339
338
  }
340
339
  return undefined;
341
340
  }
342
341
  async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
343
- console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
344
- console.info(sinceDate);
345
342
  const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
346
- console.info(' currentAPCIndex: ' + currentAPCIndex);
347
343
  if (currentAPCIndex == 0) {
348
344
  return [];
349
345
  }
@@ -352,16 +348,13 @@ class BaseProcessPublishAssortment {
352
348
  const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
353
349
  let previousApcDate = Date.parse(apcCreatedOn);
354
350
  const sinceDateMilliseconds = sinceDate.getTime();
355
- if (app_framework_1.Logger.isInfoOn()) {
356
- console.info('apcCreatedOn: ' + apcCreatedOn);
357
- console.info('sinceDate: ' + sinceDate);
358
- console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
359
- console.info('apcDateMilliseconds: ' + previousApcDate);
360
- }
351
+ console.info('getDeleteChanges(): apc: ' + assortmentPublishChange?.id
352
+ + ', index: ' + currentAPCIndex + ', previousApcCreatedOn: ' + apcCreatedOn);
361
353
  //if only 1 apc, no processing needed
362
354
  if (sinceDateMilliseconds !== previousApcDate) {
363
- console.info('sinceDateMilliseconds !== apcDateMilliseconds');
364
355
  const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
356
+ let scannedCount = 0;
357
+ let addedCount = 0;
365
358
  const deleteIds = (apcDeletes.length === 0)
366
359
  ? []
367
360
  : apcDeletes.map(item => item?.itemId);
@@ -376,19 +369,20 @@ class BaseProcessPublishAssortment {
376
369
  ? apcHistory[i - 1]
377
370
  : undefined;
378
371
  const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
379
- console.info('checking deleteChanges');
380
372
  for (const dItem of deleteChanges) {
381
373
  const dItemId = dItem?.itemId;
382
- console.info(dItemId);
374
+ scannedCount++;
383
375
  if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
384
- console.info('adding');
376
+ addedCount++;
385
377
  deleteIds.push(dItemId);
386
378
  apcDeletes.push(dItem);
387
379
  }
388
380
  }
389
381
  }
390
382
  }
391
- console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
383
+ console.info('getDeleteChanges(): baselineItems: ' + currentAssortmentItemIds.length
384
+ + ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
385
+ + ', deleteIds: ' + deleteIds.length);
392
386
  console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
393
387
  }
394
388
  return apcDeletes;
@@ -402,7 +396,6 @@ class BaseProcessPublishAssortment {
402
396
  return itemIds;
403
397
  }
404
398
  async downloadDeleteChanges(apc) {
405
- console.info('deleteDataDownloadLink-start');
406
399
  try {
407
400
  const link = apc?.deleteDataDownloadLink;
408
401
  const response = await fetch(link);
@@ -410,19 +403,17 @@ class BaseProcessPublishAssortment {
410
403
  return data;
411
404
  }
412
405
  catch (e) {
413
- console.log('Error deleteDataDownloadLink: ' + e.message);
406
+ console.error('Error deleteDataDownloadLink: ' + e.message);
414
407
  }
415
408
  return undefined;
416
409
  }
417
410
  async buildDeleteChanges(apc, previousApc) {
418
- console.info('buildDeleteChanges()');
419
411
  if (apc?.deleteDataDownloadLink) {
420
412
  const deleteChanges = await this.downloadDeleteChanges(apc);
421
413
  if (deleteChanges) {
422
414
  return deleteChanges;
423
415
  }
424
416
  }
425
- console.info('pulling down full APC');
426
417
  apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
427
418
  if (apc?.deleteDataDownloadLink) {
428
419
  const deleteChanges = await this.downloadDeleteChanges(apc);
@@ -434,19 +425,18 @@ class BaseProcessPublishAssortment {
434
425
  throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
435
426
  }
436
427
  let previousBaseline;
437
- console.info('check previousApc');
438
428
  if (previousApc?.assortmentBaselineDownloadLink) {
439
429
  previousBaseline = await this.downloadAssortmentBaseline(previousApc);
440
430
  }
441
431
  else {
442
- console.info('previousApc pulling down full APC');
443
432
  previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
444
433
  previousBaseline = await this.downloadAssortmentBaseline(previousApc);
445
434
  }
446
435
  const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
447
436
  //building deletes based on previous baseline; because some APCs don't have delete data
448
437
  const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
449
- console.info('deleteArray.length: ' + deleteArray.length);
438
+ console.info('buildDeleteChanges(): no deleteDataDownloadLink on apc ' + apc?.id
439
+ + '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
450
440
  return deleteArray;
451
441
  }
452
442
  async getItemFederatedIds( /*itemIds*/) {
@@ -484,7 +474,6 @@ class BaseProcessPublishAssortment {
484
474
  return deleteItems;
485
475
  }
486
476
  getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
487
- console.info('getReleasedForDevelopmentItemAndFamilyIds');
488
477
  const releasedForDevelopmentItemIds = [];
489
478
  const itemFamilySet = new Set();
490
479
  const assortmentItemsArray = fullChange?.assortmentItems;
@@ -561,7 +550,6 @@ class BaseProcessPublishAssortment {
561
550
  return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
562
551
  }
563
552
  async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
564
- console.info('processPublish-start');
565
553
  const event = {
566
554
  assortmentId: pcd.assortmentId,
567
555
  assortmentPublishChangeId: pcd.assortmentPublishChangeId
@@ -745,7 +733,6 @@ class BaseProcessPublishAssortment {
745
733
  + '-' + d.getUTCMilliseconds();
746
734
  }
747
735
  getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
748
- console.info('getItemFamilyChanges-start');
749
736
  const itemFamilyChanges = new Map();
750
737
  const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
751
738
  const addIds = adds.map(item => item.id);
@@ -836,8 +823,7 @@ class BaseProcessPublishAssortment {
836
823
  }
837
824
  else {
838
825
  if (!item) {
839
- console.error('Failed to find deleted item entity');
840
- console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
826
+ console.error('Failed to find deleted item entity: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
841
827
  continue;
842
828
  }
843
829
  }
@@ -878,7 +864,6 @@ class BaseProcessPublishAssortment {
878
864
  return itemFamilyChanges;
879
865
  }
880
866
  async getEventsForPublishChangeData(publishChangeData) {
881
- console.info('getEventsForPublishChangeData-start');
882
867
  const seasonalPayloads = [];
883
868
  for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
884
869
  const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
@@ -886,6 +871,8 @@ class BaseProcessPublishAssortment {
886
871
  seasonalPayloads.push(...events);
887
872
  }
888
873
  }
874
+ console.info('getEventsForPublishChangeData: itemFamilies: '
875
+ + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
889
876
  return seasonalPayloads;
890
877
  }
891
878
  /**Returns the events for a given ItemFamilyChanges object
@@ -905,7 +892,6 @@ class BaseProcessPublishAssortment {
905
892
  * @returns
906
893
  */
907
894
  async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
908
- console.info('getEventsForItemFamilyChanges()');
909
895
  const events = [];
910
896
  const LCSSeason = Object.assign({}, seasonFed);
911
897
  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);
@@ -5,10 +5,19 @@ export declare class FlexPLMConnect {
5
5
  private staticHeaders;
6
6
  payloadSendAsArray: boolean;
7
7
  constructor(_config: FCConfig, endPoint?: any, payloadAsArray?: any);
8
+ private get sessionOptions();
9
+ private getSessionKey;
10
+ private getCachedSession;
11
+ private storeSession;
12
+ private clearSession;
13
+ /** Returns the session cookie value Windchill issued on this response, if any (last one wins). */
14
+ private extractSessionCookie;
15
+ private cookieHeader;
8
16
  private getRequestOptions;
9
- getCSRF(): Promise<{
17
+ getCSRF(getNewSession?: boolean): Promise<{
10
18
  nonce_key: any;
11
19
  nonce: any;
20
+ sessionCookie: string;
12
21
  }>;
13
22
  sendRequest(csrf: any, payload: any): Promise<Response>;
14
23
  protected processRequest(payload: any): Promise<FlexPLMResponseData>;
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlexPLMConnect = void 0;
4
4
  const app_framework_1 = require("@contrail/app-framework");
5
+ // Module-level on purpose: a new FlexPLMConnect is constructed for every event. Declaring for Session resuing use case.
6
+ const sessionCache = new Map();
7
+ // Added for Session reuse - ET-390 - Ends
5
8
  class FlexPLMConnect {
6
9
  constructor(_config, endPoint = undefined, payloadAsArray = undefined) {
7
10
  this.vibeEventEndpoint = '';
@@ -19,9 +22,75 @@ class FlexPLMConnect {
19
22
  }
20
23
  }
21
24
  /////////////////////////////////////////////////////////////////////////////
25
+ ///////// ET-390 - Session reuse: start
26
+ /////////////////////////////////////////////////////////////////////////////
27
+ get sessionOptions() {
28
+ const sessReuseBool = this.config?.flexplmConnect?.sessionReuse;
29
+ return {
30
+ enabled: sessReuseBool?.enabled === true || String(sessReuseBool?.enabled).toLowerCase() === 'true',
31
+ ttlMs: (sessReuseBool?.ttlSeconds ?? 900) * 1000,
32
+ cookieName: sessReuseBool?.cookieName ?? 'JSESSIONID',
33
+ retryStatuses: (sessReuseBool?.retryStatuses ?? [401, 403]),
34
+ };
35
+ }
36
+ getSessionKey() {
37
+ return [this.config.apiHost, this.config.urlContext, this.config.userName(), this.config.plmEnviornment].join('|');
38
+ }
39
+ getCachedSession() {
40
+ if (!this.sessionOptions.enabled)
41
+ return undefined;
42
+ const key = this.getSessionKey();
43
+ const cachedSessValue = sessionCache.get(key);
44
+ if (!cachedSessValue)
45
+ return undefined;
46
+ if (cachedSessValue.expiresAt <= Date.now()) {
47
+ sessionCache.delete(key);
48
+ return undefined;
49
+ }
50
+ return cachedSessValue.cookie;
51
+ }
52
+ storeSession(cookie) {
53
+ // sliding expiry: Windchill's session timeout is also sliding
54
+ sessionCache.set(this.getSessionKey(), { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
55
+ }
56
+ clearSession() {
57
+ sessionCache.delete(this.getSessionKey());
58
+ }
59
+ /** Returns the session cookie value Windchill issued on this response, if any (last one wins). */
60
+ extractSessionCookie(response) {
61
+ const respHeader = response?.headers;
62
+ if (!respHeader)
63
+ return undefined;
64
+ let raw = [];
65
+ if (typeof respHeader.getSetCookie === 'function') {
66
+ raw = respHeader.getSetCookie();
67
+ }
68
+ else if (typeof respHeader.get === 'function' && respHeader.get('set-cookie')) {
69
+ raw = [respHeader.get('set-cookie')];
70
+ }
71
+ const name = this.sessionOptions.cookieName;
72
+ const regexFilter = new RegExp('(?:^|[,\\s])' + name + '=([^;,\\s]+)', 'g');
73
+ let jSession;
74
+ for (const line of raw) {
75
+ let cookieNameMatcher = regexFilter.exec(line);
76
+ while (cookieNameMatcher !== null) {
77
+ jSession = cookieNameMatcher[1];
78
+ cookieNameMatcher = regexFilter.exec(line);
79
+ }
80
+ }
81
+ return jSession;
82
+ }
83
+ cookieHeader(sessionCookie) {
84
+ return sessionCookie ? `${this.sessionOptions.cookieName}=${sessionCookie}` : undefined;
85
+ }
86
+ /////////////////////////////////////////////////////////////////////////////
87
+ ///////// ET-390 - Session reuse: end
88
+ /////////////////////////////////////////////////////////////////////////////
89
+ /////////////////////////////////////////////////////////////////////////////
22
90
  ///////// Custom getRequestOptions: start
23
91
  /////////////////////////////////////////////////////////////////////////////
24
- getRequestOptions(method) {
92
+ // ET-390 - adding a new parameter sessionCookie to the getRequestOptions function
93
+ getRequestOptions(method, sessionCookie) {
25
94
  const csrfOptions = {
26
95
  method,
27
96
  headers: {
@@ -33,9 +102,19 @@ class FlexPLMConnect {
33
102
  if (this.staticHeaders) {
34
103
  Object.assign(csrfOptions.headers, this.staticHeaders);
35
104
  }
105
+ // Added for Session reuse - ET-390 - Starts
106
+ const cookie = this.cookieHeader(sessionCookie);
107
+ if (cookie) {
108
+ csrfOptions.headers['Cookie'] = cookie;
109
+ }
110
+ // Added for Session reuse - ET-390 - Ends
36
111
  if (app_framework_1.Logger.isInfoOn()) {
37
112
  const logOptions = JSON.parse(JSON.stringify(csrfOptions));
38
113
  logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
114
+ // Added for Session reuse - ET-390 - Starts
115
+ if (logOptions.headers['Cookie'])
116
+ logOptions.headers['Cookie'] = '***masked***';
117
+ // Added for Session reuse - ET-390 - Ends
39
118
  console.info('csrfOptions: ' + JSON.stringify(logOptions));
40
119
  }
41
120
  return csrfOptions;
@@ -43,13 +122,30 @@ class FlexPLMConnect {
43
122
  /////////////////////////////////////////////////////////////////////////////
44
123
  ///////// Custom getRequestOptions: end
45
124
  /////////////////////////////////////////////////////////////////////////////
46
- async getCSRF() {
125
+ // ET-390 - adding a new parameter getNewSession to the getCSRF function
126
+ // Whenever a getCSRF is called without getNewSession, the default value of false will be used
127
+ async getCSRF(getNewSession = false) {
47
128
  const urlContext = this.config.urlContext;
48
129
  const csrfEndpoint = this.config.csrfEndpoint;
49
130
  const csrfURL = this.config.apiHost + urlContext + csrfEndpoint;
50
131
  console.info('csrfURL: ' + csrfURL);
51
- const csrfOptions = this.getRequestOptions('GET');
52
- const response = await fetch(csrfURL, csrfOptions);
132
+ // Added for Session reuse - ET-390 - Starts
133
+ if (getNewSession)
134
+ this.clearSession();
135
+ let sentCookie = this.getCachedSession();
136
+ // getRequestOptions will be called with additional sentCookie parameter
137
+ const csrfOptions = this.getRequestOptions('GET', sentCookie);
138
+ // Added for Session reuse - ET-390 - Ends
139
+ let response = await fetch(csrfURL, csrfOptions);
140
+ // Added for Session reuse - ET-390 - Starts
141
+ // if the response status is 401 or 403, then get a new session id
142
+ if (sentCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
143
+ console.warn('Cached FlexPLM session rejected on csrf call (status ' + response.status + '). Retrying with a new session.');
144
+ this.clearSession();
145
+ sentCookie = undefined;
146
+ response = await fetch(csrfURL, this.getRequestOptions('GET'));
147
+ }
148
+ // Added for Session reuse - ET-390 - Ends
53
149
  if (response.status >= 300) {
54
150
  const message = 'Error connecting to FlexPLM:status: ' + response.status;
55
151
  console.error(message);
@@ -58,14 +154,25 @@ class FlexPLMConnect {
58
154
  e['httpResponseStatus'] = response.status;
59
155
  throw e;
60
156
  }
157
+ // Added for Session reuse - ET-390 - Starts
158
+ // If the cookie we sent was invalid, Windchill answers with a fresh Set-Cookie --> replace the cache.
159
+ let sessionCookie = sentCookie;
160
+ if (this.sessionOptions.enabled) {
161
+ sessionCookie = this.extractSessionCookie(response) ?? sentCookie;
162
+ if (sessionCookie)
163
+ this.storeSession(sessionCookie);
164
+ }
165
+ // Added for Session reuse - ET-390 - Ends
61
166
  try {
62
167
  const body = await response.json();
63
168
  const nonce_key = body.items[0].attributes.nonce_key;
64
169
  const nonce = body.items[0].attributes.nonce;
65
170
  console.info('nonce_key: ' + nonce_key);
171
+ //ET-390 - adding sessionCookie to the return
66
172
  return {
67
173
  nonce_key,
68
- nonce
174
+ nonce,
175
+ sessionCookie
69
176
  };
70
177
  }
71
178
  catch (e) {
@@ -79,7 +186,7 @@ class FlexPLMConnect {
79
186
  if (this.payloadSendAsArray && !Array.isArray(payload)) {
80
187
  payload = [payload];
81
188
  }
82
- const xferOptions = this.getRequestOptions('POST');
189
+ const xferOptions = this.getRequestOptions('POST', csrf?.sessionCookie);
83
190
  xferOptions['body'] = JSON.stringify(payload);
84
191
  xferOptions.headers[csrf.nonce_key] = csrf.nonce;
85
192
  const urlContext = this.config.urlContext;
@@ -90,6 +197,8 @@ class FlexPLMConnect {
90
197
  const logOptions = JSON.parse(JSON.stringify(xferOptions));
91
198
  if (logOptions?.headers?.Authorization)
92
199
  logOptions.headers.Authorization = logOptions?.headers?.Authorization.substring(0, 9);
200
+ if (logOptions?.headers?.Cookie)
201
+ logOptions.headers.Cookie = '***masked***';
93
202
  if (csrf?.nonce_key)
94
203
  logOptions.headers[csrf.nonce_key] = csrf.nonce ? `***masked(length:${csrf.nonce.length})***` : '***masked(empty)***';
95
204
  console.info('csrfOptions: ' + JSON.stringify(logOptions));
@@ -107,13 +216,21 @@ class FlexPLMConnect {
107
216
  if (app_framework_1.Logger.isInfoOn()) {
108
217
  console.info('payload: ' + JSON.stringify(payload));
109
218
  }
110
- const csrf = await this.getCSRF();
219
+ let csrf = await this.getCSRF();
111
220
  if (!csrf) {
112
221
  const message = 'Failed to get CSRF nonce';
113
222
  console.error(message);
114
223
  throw new Error(message);
115
224
  }
116
- const response = await this.sendRequest(csrf, payload);
225
+ /// Added for Session reuse - ET-390 - Starts
226
+ // const response = await this.sendRequest(csrf, payload);
227
+ let response = await this.sendRequest(csrf, payload);
228
+ if (csrf.sessionCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
229
+ console.warn('FlexPLM rejected session/nonce (status ' + response.status + '). Getting a new session and retrying once.');
230
+ csrf = await this.getCSRF(true);
231
+ response = await this.sendRequest(csrf, payload);
232
+ }
233
+ // Added for Session reuse - ET-390 - Ends
117
234
  const status = response.status;
118
235
  if (status >= 300) {
119
236
  const message = 'Error sending data to FlexPLM:status: ' + response.status;
@@ -166,8 +283,20 @@ class FlexPLMConnect {
166
283
  const urlContext = includeUrlContext ? this.config.urlContext : '';
167
284
  const path = urlPath || ('/servlet/rest' + this.vibeEventEndpoint);
168
285
  const vibeEventsURL = this.config.apiHost + urlContext + path;
169
- const csrfOptions = this.getRequestOptions('GET');
170
- const response = await fetch(vibeEventsURL, csrfOptions);
286
+ // Added for Session reuse - ET-390 - Starts
287
+ const cached = this.getCachedSession();
288
+ const csrfOptions = this.getRequestOptions('GET', cached);
289
+ let response = await fetch(vibeEventsURL, csrfOptions);
290
+ if (cached && this.sessionOptions.retryStatuses.includes(response.status)) {
291
+ this.clearSession();
292
+ response = await fetch(vibeEventsURL, this.getRequestOptions('GET'));
293
+ }
294
+ if (this.sessionOptions.enabled) {
295
+ const cachedCookie = this.extractSessionCookie(response) ?? cached;
296
+ if (cachedCookie)
297
+ this.storeSession(cachedCookie);
298
+ }
299
+ // Added for Session reuse - ET-390 - Ends
171
300
  if (response.status >= 300) {
172
301
  const message = 'Error connecting to FlexPLM:status: ' + response.status;
173
302
  console.error(message);
@@ -28,6 +28,22 @@ const createConfig = (overrides) => ({
28
28
  useDistinctRestEndPointForImages: false,
29
29
  ...overrides,
30
30
  });
31
+ // Added for Session reuse - ET-390 - Starts
32
+ const csrfBody = { items: [{ attributes: { nonce_key: 'CSRF_NONCE', nonce: 'n1' } }] };
33
+ // Response with Set-Cookie support. setCookie = list of raw Set-Cookie header lines.
34
+ const resp = (status, setCookie = [], json = csrfBody) => ({
35
+ status,
36
+ json: jest.fn().mockResolvedValue(json),
37
+ text: jest.fn().mockResolvedValue('error text'),
38
+ headers: { getSetCookie: () => setCookie, get: () => null },
39
+ });
40
+ let userCounter = 0;
41
+ const uniqueUser = () => 'sess-user-' + (++userCounter);
42
+ const sessionConfig = (user, sessionReuse = { enabled: true }) => createConfig({ userName: () => user, flexplmConnect: { sessionReuse } });
43
+ const fetchMock = () => global.fetch;
44
+ const callHeaders = (i) => fetchMock().mock.calls[i][1].headers;
45
+ const callMethod = (i) => fetchMock().mock.calls[i][1].method;
46
+ // Added for Session reuse - ET-390 - Ends
31
47
  describe('FlexPLMConnect.getRequest', () => {
32
48
  it('should call default URL with urlContext when no params provided', async () => {
33
49
  const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
@@ -121,3 +137,249 @@ describe('FlexPLMConnect.sendRequest', () => {
121
137
  consoleInfoSpy.mockRestore();
122
138
  });
123
139
  });
140
+ // Added for Session reuse - ET-390 - Starts
141
+ describe('FlexPLMConnect session reuse', () => {
142
+ const payload = { id: '123' };
143
+ it('captures JSESSIONID from the csrf GET and sends it on the POST', async () => {
144
+ const u = uniqueUser();
145
+ fetchMock()
146
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/; HttpOnly']))
147
+ .mockResolvedValueOnce(resp(200, [], {}));
148
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
149
+ expect(fetchMock()).toHaveBeenCalledTimes(2);
150
+ expect(callHeaders(0).Cookie).toBeUndefined(); // first ever call: no cached session
151
+ expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
152
+ expect(callHeaders(1).CSRF_NONCE).toBe('n1');
153
+ });
154
+ it('reuses the cached session on the next event (new FlexPLMConnect instance)', async () => {
155
+ const u = uniqueUser();
156
+ fetchMock()
157
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/'])) // event 1 GET
158
+ .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
159
+ .mockResolvedValueOnce(resp(200, [])) // event 2 GET (valid session -> no Set-Cookie)
160
+ .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
161
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
162
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
163
+ expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1'); // GET reuses it
164
+ expect(callHeaders(3).Cookie).toBe('JSESSIONID=abc.tomcat1'); // POST reuses it
165
+ });
166
+ it('replaces the cached session when Windchill answers the GET with a new Set-Cookie', async () => {
167
+ const u = uniqueUser();
168
+ fetchMock()
169
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
170
+ .mockResolvedValueOnce(resp(200, [], {}))
171
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // old one was stale -> new issued
172
+ .mockResolvedValueOnce(resp(200, [], {}))
173
+ .mockResolvedValueOnce(resp(200, []))
174
+ .mockResolvedValueOnce(resp(200, [], {}));
175
+ const connect = () => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
176
+ await connect().sendToFlexPLM(payload);
177
+ await connect().sendToFlexPLM(payload);
178
+ await connect().sendToFlexPLM(payload);
179
+ expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1'); // event 2 GET sent the old one
180
+ expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1'); // POST uses the replacement
181
+ expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1'); // event 3 GET sends the replacement
182
+ });
183
+ it('retries once with a new session when the POST is rejected with 403', async () => {
184
+ const u = uniqueUser();
185
+ fetchMock()
186
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/'])) // GET
187
+ .mockResolvedValueOnce(resp(403, [], {})) // POST rejected
188
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // GET again, fresh session
189
+ .mockResolvedValueOnce(resp(200, [], { ok: true })); // POST succeeds
190
+ const result = await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
191
+ expect(fetchMock()).toHaveBeenCalledTimes(4);
192
+ expect(callHeaders(2).Cookie).toBeUndefined(); // retry GET must NOT send the stale cookie
193
+ expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1');
194
+ expect(result.status).toBe(200);
195
+ expect(result.data).toEqual({ ok: true });
196
+ });
197
+ it('also retries on 401', async () => {
198
+ const u = uniqueUser();
199
+ fetchMock()
200
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
201
+ .mockResolvedValueOnce(resp(401, [], {}))
202
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
203
+ .mockResolvedValueOnce(resp(200, [], {}));
204
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
205
+ expect(fetchMock()).toHaveBeenCalledTimes(4);
206
+ });
207
+ it('does not loop: a second 403 after the retry is surfaced as an error', async () => {
208
+ const u = uniqueUser();
209
+ fetchMock()
210
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
211
+ .mockResolvedValueOnce(resp(403, [], {}))
212
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
213
+ .mockResolvedValueOnce(resp(403, [], {}));
214
+ await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
215
+ .rejects.toThrow('Error sending data to FlexPLM:status: 403');
216
+ expect(fetchMock()).toHaveBeenCalledTimes(4);
217
+ });
218
+ it('does not retry on non-auth errors such as 500 (event may already be processed)', async () => {
219
+ const u = uniqueUser();
220
+ fetchMock()
221
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
222
+ .mockResolvedValueOnce(resp(500, [], {}));
223
+ await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
224
+ .rejects.toThrow('Error sending data to FlexPLM:status: 500');
225
+ expect(fetchMock()).toHaveBeenCalledTimes(2);
226
+ });
227
+ it('retries the csrf GET without a cookie when the cached session is rejected there', async () => {
228
+ const u = uniqueUser();
229
+ fetchMock()
230
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1'])) // event 1 GET
231
+ .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
232
+ .mockResolvedValueOnce(resp(401, [])) // event 2 GET with cached cookie rejected
233
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'])) // event 2 GET retry, no cookie
234
+ .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
235
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
236
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
237
+ expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1');
238
+ expect(callHeaders(3).Cookie).toBeUndefined();
239
+ expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1');
240
+ });
241
+ it('expires the cached session after ttlSeconds', async () => {
242
+ const u = uniqueUser();
243
+ const realNow = Date.now();
244
+ const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
245
+ fetchMock()
246
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
247
+ .mockResolvedValueOnce(resp(200, [], {}))
248
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
249
+ .mockResolvedValueOnce(resp(200, [], {}));
250
+ const cfg = sessionConfig(u, { enabled: true, ttlSeconds: 1 });
251
+ await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
252
+ nowSpy.mockReturnValue(realNow + 2000); // 2s later > 1s ttl
253
+ await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
254
+ expect(callHeaders(2).Cookie).toBeUndefined(); // expired -> GET sends no cookie
255
+ expect(callHeaders(3).Cookie).toBe('JSESSIONID=def.tomcat1');
256
+ nowSpy.mockRestore();
257
+ });
258
+ it('uses the last JSESSIONID when several Set-Cookie lines are returned', async () => {
259
+ const u = uniqueUser();
260
+ fetchMock()
261
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=first.tomcat1; Path=/', 'OTHER=x; Path=/', 'JSESSIONID=second.tomcat1; Path=/']))
262
+ .mockResolvedValueOnce(resp(200, [], {}));
263
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
264
+ expect(callHeaders(1).Cookie).toBe('JSESSIONID=second.tomcat1');
265
+ });
266
+ it('falls back to headers.get("set-cookie") when getSetCookie is not available', async () => {
267
+ const u = uniqueUser();
268
+ const legacy = {
269
+ status: 200,
270
+ json: jest.fn().mockResolvedValue(csrfBody),
271
+ text: jest.fn(),
272
+ headers: { get: (k) => (k.toLowerCase() === 'set-cookie' ? 'JSESSIONID=legacy.tomcat1; Path=/' : null) },
273
+ };
274
+ fetchMock().mockResolvedValueOnce(legacy).mockResolvedValueOnce(resp(200, [], {}));
275
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
276
+ expect(callHeaders(1).Cookie).toBe('JSESSIONID=legacy.tomcat1');
277
+ });
278
+ it('honours a custom cookieName', async () => {
279
+ const u = uniqueUser();
280
+ fetchMock()
281
+ .mockResolvedValueOnce(resp(200, ['MYSESSION=zzz; Path=/', 'JSESSIONID=ignored; Path=/']))
282
+ .mockResolvedValueOnce(resp(200, [], {}));
283
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'MYSESSION' })).sendToFlexPLM(payload);
284
+ expect(callHeaders(1).Cookie).toBe('MYSESSION=zzz');
285
+ });
286
+ it('accepts enabled as the string "true"', async () => {
287
+ const u = uniqueUser();
288
+ fetchMock()
289
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
290
+ .mockResolvedValueOnce(resp(200, [], {}));
291
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: 'true' })).sendToFlexPLM(payload);
292
+ expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
293
+ });
294
+ it('keeps separate sessions per user', async () => {
295
+ const a = uniqueUser();
296
+ const b = uniqueUser();
297
+ fetchMock()
298
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=aaa.tomcat1']))
299
+ .mockResolvedValueOnce(resp(200, [], {}))
300
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=bbb.tomcat1']))
301
+ .mockResolvedValueOnce(resp(200, [], {}));
302
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(a)).sendToFlexPLM(payload);
303
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(b)).sendToFlexPLM(payload);
304
+ expect(callHeaders(2).Cookie).toBeUndefined(); // user b must not see user a's cookie
305
+ expect(callHeaders(3).Cookie).toBe('JSESSIONID=bbb.tomcat1');
306
+ });
307
+ it('never logs the cookie value', async () => {
308
+ const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
309
+ const info = jest.spyOn(console, 'info').mockImplementation();
310
+ const u = uniqueUser();
311
+ fetchMock()
312
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=supersecret.tomcat1']))
313
+ .mockResolvedValueOnce(resp(200, [], {}))
314
+ .mockResolvedValueOnce(resp(200, []))
315
+ .mockResolvedValueOnce(resp(200, [], {}));
316
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
317
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload); // 2nd event sends Cookie on GET and POST
318
+ const logged = info.mock.calls.map(c => String(c[0] ?? '')).join('\n');
319
+ expect(logged).toContain('"Cookie":"***masked***"');
320
+ expect(logged).not.toContain('supersecret');
321
+ isInfoOn.mockRestore();
322
+ info.mockRestore();
323
+ });
324
+ });
325
+ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
326
+ const payload = { id: '123' };
327
+ it('sends no Cookie header and makes exactly one GET + one POST', async () => {
328
+ const u = uniqueUser();
329
+ fetchMock()
330
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
331
+ .mockResolvedValueOnce(resp(200, [], {}))
332
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
333
+ .mockResolvedValueOnce(resp(200, [], {}));
334
+ const cfg = createConfig({ userName: () => u }); // no flexplmConnect at all
335
+ await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
336
+ await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
337
+ for (let i = 0; i < 4; i++) {
338
+ expect(callHeaders(i).Cookie).toBeUndefined();
339
+ }
340
+ });
341
+ it('does not retry on 403 (behaves exactly as before)', async () => {
342
+ const u = uniqueUser();
343
+ fetchMock()
344
+ .mockResolvedValueOnce(resp(200))
345
+ .mockResolvedValueOnce(resp(403, [], {}));
346
+ await expect(new flexplm_connect_1.FlexPLMConnect(createConfig({ userName: () => u })).sendToFlexPLM(payload))
347
+ .rejects.toThrow('Error sending data to FlexPLM:status: 403');
348
+ expect(fetchMock()).toHaveBeenCalledTimes(2);
349
+ });
350
+ it('explicit enabled:false behaves the same as not configured', async () => {
351
+ const u = uniqueUser();
352
+ fetchMock()
353
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
354
+ .mockResolvedValueOnce(resp(200, [], {}));
355
+ await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: false })).sendToFlexPLM(payload);
356
+ expect(callHeaders(1).Cookie).toBeUndefined();
357
+ });
358
+ });
359
+ describe('FlexPLMConnect.getRequest session reuse (only if you applied step 2g)', () => {
360
+ it('sends the cached cookie on image/GET requests', async () => {
361
+ const u = uniqueUser();
362
+ fetchMock()
363
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
364
+ .mockResolvedValueOnce(resp(200, [], {})) // POST
365
+ .mockResolvedValueOnce(resp(200, [], { items: [] })); // getRequest
366
+ const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
367
+ await connect.sendToFlexPLM({ id: '1' });
368
+ await connect.getRequest();
369
+ expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
370
+ });
371
+ it('retries once without the cookie when the cached session is rejected on getRequest', async () => {
372
+ const u = uniqueUser();
373
+ fetchMock()
374
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
375
+ .mockResolvedValueOnce(resp(200, [], {}))
376
+ .mockResolvedValueOnce(resp(401, [])) // getRequest with cached cookie
377
+ .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'], { items: [] })); // retry, no cookie
378
+ const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
379
+ await connect.sendToFlexPLM({ id: '1' });
380
+ await connect.getRequest();
381
+ expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
382
+ expect(callHeaders(3).Cookie).toBeUndefined();
383
+ });
384
+ });
385
+ // Added for Session reuse - ET-390 - Ends
package/package.json CHANGED
@@ -1,66 +1,66 @@
1
- {
2
- "name": "@contrail/flexplm",
3
- "version": "1.7.5",
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
- }
1
+ {
2
+ "name": "@contrail/flexplm",
3
+ "version": "1.7.6-alpha.a804d5a",
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
+ }