@contrail/flexplm 1.7.4-alpha.aaaa660 → 1.7.4-alpha.ad2bc4e
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/interfaces/item-family-changes.d.ts +1 -0
- package/lib/interfaces/item-family-changes.js +2 -0
- package/lib/publish/base-process-publish-assortment.js +110 -47
- package/lib/publish/base-process-publish-assortment.spec.js +263 -0
- package/package.json +2 -3
- 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 -85
- 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 -130
- 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
|
@@ -1337,6 +1337,176 @@ describe('getItemFamilyChanges', () => {
|
|
|
1337
1337
|
expect(ifc.colorDeletes.length).toEqual(1);
|
|
1338
1338
|
});
|
|
1339
1339
|
});
|
|
1340
|
+
describe('getItemFamilyChanges - lifecycle-suppressed options', () => {
|
|
1341
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1342
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1343
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1344
|
+
const sinceDate = new Date('2026-08-01T00:00:00.000Z');
|
|
1345
|
+
const afterSince = '2026-08-26T00:00:00.000Z';
|
|
1346
|
+
const beforeSince = '2026-05-11T00:00:00.000Z';
|
|
1347
|
+
const noDetailChanges = () => ({ adds: [], deletes: [], updates: [], familyItemsRemoved: [] });
|
|
1348
|
+
const buildPpa = () => {
|
|
1349
|
+
const config = { itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1350
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1351
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1352
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1353
|
+
};
|
|
1354
|
+
const buildPcd = (releasedIds) => {
|
|
1355
|
+
const seasonFed = {
|
|
1356
|
+
entityReference: 'assortment:11EZML5dMn3tREuT',
|
|
1357
|
+
objectClass: 'LCSSeason'
|
|
1358
|
+
};
|
|
1359
|
+
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', seasonFed, 'Pg3y2x5IclYxk3hM', sinceDate);
|
|
1360
|
+
pcd.itemToFederatedIdMapping = new Map();
|
|
1361
|
+
releasedIds.forEach(id => pcd.releasedForDevelopmentItemIds.push(id));
|
|
1362
|
+
return pcd;
|
|
1363
|
+
};
|
|
1364
|
+
const familyItem = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1365
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant', updatedOn
|
|
1366
|
+
});
|
|
1367
|
+
const familyRow = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1368
|
+
itemId: familyId,
|
|
1369
|
+
item: familyItem(updatedOn, lifecycleStage),
|
|
1370
|
+
projectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1371
|
+
});
|
|
1372
|
+
const optionRow = (id, updatedOn, lifecycleStage = 'concept') => ({
|
|
1373
|
+
itemId: id,
|
|
1374
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, updatedOn, itemFamily: familyItem() },
|
|
1375
|
+
projectItem: { id: 'pi-' + id, updatedOn: beforeSince },
|
|
1376
|
+
familyProjectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1377
|
+
});
|
|
1378
|
+
it('records changed concept options as suppressed, not as colour updates', () => {
|
|
1379
|
+
const ppa = buildPpa();
|
|
1380
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1381
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince), optionRow(limeId, afterSince)]
|
|
1382
|
+
});
|
|
1383
|
+
const pcd = buildPcd([familyId]);
|
|
1384
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1385
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1386
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1387
|
+
expect(ifc.colorSuppressed).toEqual([maroonId, limeId]);
|
|
1388
|
+
expect(ifc.colorAdds.length).toEqual(0);
|
|
1389
|
+
expect(ifc.colorUpdates.length).toEqual(0);
|
|
1390
|
+
expect(ifc.colorDeletes.length).toEqual(0);
|
|
1391
|
+
expect(ifc.familyUpdate).toBe(false); // family itself unchanged
|
|
1392
|
+
});
|
|
1393
|
+
it('keeps the family reachable when only concept option rows are in the baseline', () => {
|
|
1394
|
+
const ppa = buildPpa();
|
|
1395
|
+
const assortmentItems = [optionRow(maroonId, afterSince), optionRow(limeId, afterSince)];
|
|
1396
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
|
|
1397
|
+
// derive the gate the way production does, rather than seeding it
|
|
1398
|
+
const releasedIds = ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1399
|
+
expect(releasedIds).toEqual([familyId]);
|
|
1400
|
+
const pcd = buildPcd(releasedIds);
|
|
1401
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1402
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1403
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1404
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1405
|
+
expect(ifc.colorSuppressed.length).toEqual(2);
|
|
1406
|
+
expect(ppa.getProjectItem(ifc, familyId)).toEqual({ id: 'pi-' + familyId, updatedOn: beforeSince });
|
|
1407
|
+
});
|
|
1408
|
+
it('does not suppress-record an unchanged concept option', () => {
|
|
1409
|
+
const ppa = buildPpa();
|
|
1410
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1411
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1412
|
+
});
|
|
1413
|
+
const pcd = buildPcd([familyId]);
|
|
1414
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1415
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1416
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1417
|
+
});
|
|
1418
|
+
it('picks up a concept option listed in the change detail even when timestamps are stale', () => {
|
|
1419
|
+
const ppa = buildPpa();
|
|
1420
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1421
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1422
|
+
});
|
|
1423
|
+
const details = noDetailChanges();
|
|
1424
|
+
details.updates = [{ id: maroonId }];
|
|
1425
|
+
const pcd = buildPcd([familyId]);
|
|
1426
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1427
|
+
expect(pcd.itemFamilyChanges.get(familyId).colorSuppressed).toEqual([maroonId]);
|
|
1428
|
+
});
|
|
1429
|
+
it('skips everything when the family itself is pre-development', () => {
|
|
1430
|
+
const ppa = buildPpa();
|
|
1431
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1432
|
+
assortmentItems: [familyRow(afterSince, 'concept'), optionRow(maroonId, afterSince)]
|
|
1433
|
+
});
|
|
1434
|
+
const pcd = buildPcd([]); // nothing released
|
|
1435
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1436
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
1437
|
+
});
|
|
1438
|
+
it('still classifies released options normally', () => {
|
|
1439
|
+
const ppa = buildPpa();
|
|
1440
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1441
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince, 'released')]
|
|
1442
|
+
});
|
|
1443
|
+
const details = noDetailChanges();
|
|
1444
|
+
details.adds = [{ id: maroonId }];
|
|
1445
|
+
const pcd = buildPcd([familyId, maroonId]);
|
|
1446
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1447
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1448
|
+
expect(ifc.colorAdds).toEqual([maroonId]);
|
|
1449
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1450
|
+
});
|
|
1451
|
+
it('publishes a family-only row whose lifecycle just flipped (item-level edit)', () => {
|
|
1452
|
+
const ppa = buildPpa();
|
|
1453
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1454
|
+
assortmentItems: [familyRow(afterSince)] // item.updatedOn moved, projectItem did not
|
|
1455
|
+
});
|
|
1456
|
+
const pcd = buildPcd([familyId]);
|
|
1457
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1458
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1459
|
+
expect(ifc.familyUpdate).toBe(true); // fails without Step 3
|
|
1460
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1461
|
+
});
|
|
1462
|
+
it('leaves an unchanged family-only row with no flags', () => {
|
|
1463
|
+
const ppa = buildPpa();
|
|
1464
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow()] });
|
|
1465
|
+
const pcd = buildPcd([familyId]);
|
|
1466
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1467
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1468
|
+
expect(ifc.familyAdd).toBe(false);
|
|
1469
|
+
expect(ifc.familyUpdate).toBe(false);
|
|
1470
|
+
expect(ifc.familyDelete).toBe(false);
|
|
1471
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1472
|
+
});
|
|
1473
|
+
it('does not set familyUpdate when the family item was removed', () => {
|
|
1474
|
+
const ppa = buildPpa();
|
|
1475
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow(afterSince)] });
|
|
1476
|
+
const details = noDetailChanges();
|
|
1477
|
+
details.familyItemsRemoved = [{ itemId: familyId }]; // note: itemId, not id
|
|
1478
|
+
const pcd = buildPcd([familyId]);
|
|
1479
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1480
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1481
|
+
expect(ifc.familyItemRemoved).toBe(true);
|
|
1482
|
+
expect(ifc.familyUpdate).toBe(false); // fails if the !familyItemRemoved guard is dropped
|
|
1483
|
+
expect(ifc.assortmentItemFullChangeMap.size).toEqual(1); // third pass must not re-insert the row
|
|
1484
|
+
});
|
|
1485
|
+
it('flags the family when its row is removed but option rows remain', () => {
|
|
1486
|
+
const ppa = buildPpa();
|
|
1487
|
+
const assortmentItems = [optionRow(maroonId, beforeSince), optionRow(limeId, beforeSince, 'released')];
|
|
1488
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
|
|
1489
|
+
const details = { ...noDetailChanges(), familyItemsRemoved: [{ itemId: familyId, item: familyItem() }] };
|
|
1490
|
+
const pcd = buildPcd([familyId, limeId]);
|
|
1491
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1492
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1493
|
+
expect(ifc.familyItemRemoved).toBe(true); // false before the third pass
|
|
1494
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1495
|
+
expect(ifc.colorDeletes.length).toEqual(0); // no SKU event should follow
|
|
1496
|
+
expect(ifc.assortmentItemFullChangeMap.has(familyId)).toBe(false); // removed row carries no projectItem
|
|
1497
|
+
});
|
|
1498
|
+
it('leaves a hard-deleted family row to the delete pass', () => {
|
|
1499
|
+
const ppa = buildPpa();
|
|
1500
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [] });
|
|
1501
|
+
const deleteMap = new Map([[familyId, familyRow()]]);
|
|
1502
|
+
const details = { ...noDetailChanges(), familyItemsRemoved: [{ itemId: familyId, item: familyItem() }] };
|
|
1503
|
+
const pcd = buildPcd([familyId]);
|
|
1504
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, deleteMap);
|
|
1505
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1506
|
+
expect(ifc.familyDelete).toBe(true); // set by the delete pass
|
|
1507
|
+
expect(ifc.familyItemRemoved).toBe(false); // third pass skipped it - no double handling
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1340
1510
|
describe('getProjectItem', () => {
|
|
1341
1511
|
const config = {
|
|
1342
1512
|
identifierAtts: {
|
|
@@ -1500,6 +1670,70 @@ describe('meetsCriteria', () => {
|
|
|
1500
1670
|
expect(results).toBeTruthy();
|
|
1501
1671
|
});
|
|
1502
1672
|
});
|
|
1673
|
+
describe('getReleasedForDevelopmentItemAndFamilyIds', () => {
|
|
1674
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1675
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1676
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1677
|
+
const buildPpa = (preDev = ['concept']) => {
|
|
1678
|
+
const config = {
|
|
1679
|
+
identifierAtts: { LCSSeason: ['flexPLMSeasonName'] },
|
|
1680
|
+
itemPreDevelopmentLifecycleStages: preDev
|
|
1681
|
+
};
|
|
1682
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1683
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1684
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1685
|
+
};
|
|
1686
|
+
const familyItem = (lifecycleStage = 'released') => ({
|
|
1687
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant'
|
|
1688
|
+
});
|
|
1689
|
+
// option row as the hydrated baseline actually delivers it: itemFamily present
|
|
1690
|
+
const optionRow = (id, lifecycleStage = 'concept', family = familyItem()) => ({
|
|
1691
|
+
itemId: id,
|
|
1692
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, itemFamily: family }
|
|
1693
|
+
});
|
|
1694
|
+
const familyRow = (lifecycleStage = 'released') => ({
|
|
1695
|
+
itemId: familyId,
|
|
1696
|
+
item: familyItem(lifecycleStage)
|
|
1697
|
+
});
|
|
1698
|
+
const run = (ppa, assortmentItems) => ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1699
|
+
it('surfaces the family when every option is pre-development', () => {
|
|
1700
|
+
const ppa = buildPpa();
|
|
1701
|
+
expect(run(ppa, [optionRow(maroonId), optionRow(limeId)])).toEqual([familyId]);
|
|
1702
|
+
});
|
|
1703
|
+
it('does not duplicate the family id when a family row and a released option both exist', () => {
|
|
1704
|
+
const ppa = buildPpa();
|
|
1705
|
+
const ids = run(ppa, [familyRow(), optionRow(limeId, 'released')]);
|
|
1706
|
+
expect(ids.filter(id => id === familyId)).toHaveLength(1);
|
|
1707
|
+
});
|
|
1708
|
+
it('still resolves the family when an earlier row lacks itemFamily hydration', () => {
|
|
1709
|
+
const ppa = buildPpa();
|
|
1710
|
+
const noFamilyHydration = {
|
|
1711
|
+
itemId: maroonId,
|
|
1712
|
+
item: { id: maroonId, itemFamilyId: familyId, lifecycleStage: 'concept' }
|
|
1713
|
+
};
|
|
1714
|
+
expect(run(ppa, [noFamilyHydration, optionRow(limeId)])).toEqual([familyId]);
|
|
1715
|
+
});
|
|
1716
|
+
it('returns nothing when the family itself is pre-development', () => {
|
|
1717
|
+
const ppa = buildPpa();
|
|
1718
|
+
expect(run(ppa, [optionRow(maroonId, 'concept', familyItem('concept'))])).toEqual([]);
|
|
1719
|
+
});
|
|
1720
|
+
it('includes a family-only row exactly once', () => {
|
|
1721
|
+
const ppa = buildPpa();
|
|
1722
|
+
expect(run(ppa, [familyRow()])).toEqual([familyId]);
|
|
1723
|
+
});
|
|
1724
|
+
it('includes a released option and its family, excluding the concept option', () => {
|
|
1725
|
+
const ppa = buildPpa();
|
|
1726
|
+
const ids = run(ppa, [optionRow(maroonId, 'concept'), optionRow(limeId, 'released')]);
|
|
1727
|
+
expect(ids).toContain(familyId);
|
|
1728
|
+
expect(ids).toContain(limeId);
|
|
1729
|
+
expect(ids).not.toContain(maroonId);
|
|
1730
|
+
});
|
|
1731
|
+
it('treats the stage list as case-sensitive (documents the Concept vs concept trap)', () => {
|
|
1732
|
+
const ppa = buildPpa(['Concept']); // as mis-configured in praveen-dev-1
|
|
1733
|
+
const ids = run(ppa, [optionRow(maroonId), optionRow(limeId)]);
|
|
1734
|
+
expect(ids).toEqual([maroonId, familyId, limeId]); // filter inert: everything qualifies
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1503
1737
|
describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
1504
1738
|
const config = {
|
|
1505
1739
|
identifierAtts: {
|
|
@@ -1705,6 +1939,35 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1705
1939
|
// Verify SKU event called with assortment entity
|
|
1706
1940
|
expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
|
|
1707
1941
|
});
|
|
1942
|
+
it('should emit only an LCSProductSeasonLink when the family row was removed', async () => {
|
|
1943
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1944
|
+
const itemFamilyId = 'AERfdvdAt1HHEVvQ';
|
|
1945
|
+
const prodEntityData = { id: itemFamilyId, identifier: 'FAM-11', itemNumber: 'FAM-11' };
|
|
1946
|
+
//const projectItem = { id: 'proj-fam', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1947
|
+
const projectItem = { id: 'proj-fam', updatedOn: '2022-12-01T00:00:00.000Z' };
|
|
1948
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1949
|
+
const seasonFed = {
|
|
1950
|
+
entityReference: 'assortment:assort123',
|
|
1951
|
+
objectClass: 'LCSSeason',
|
|
1952
|
+
flexPLMSeasonName: 'Fall 2023'
|
|
1953
|
+
};
|
|
1954
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1955
|
+
itemFamilyChanges.familyItemRemoved = true; // the only trigger
|
|
1956
|
+
itemFamilyChanges.colorUnchanged.push('rsmWhWiXRNv_XBJG'); // surviving option must not emit
|
|
1957
|
+
itemFamilyChanges.itemFamilyObject = prodEntityData;
|
|
1958
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1959
|
+
jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
|
|
1960
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
|
|
1961
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1962
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1963
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1964
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1965
|
+
const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1966
|
+
expect(events).toHaveLength(1);
|
|
1967
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
1968
|
+
expect(events[0].eventType).toBe('UPDATE_ON_SEASON');
|
|
1969
|
+
expect(events[0].entityReference).toBe('item:' + itemFamilyId);
|
|
1970
|
+
});
|
|
1708
1971
|
});
|
|
1709
1972
|
describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
1710
1973
|
const config = {
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/flexplm",
|
|
3
|
-
"version": "1.7.4-alpha.
|
|
3
|
+
"version": "1.7.4-alpha.ad2bc4e",
|
|
4
4
|
"description": "Library used for integration with flexplm.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
-
"flexplm-config": "lib/cli/config-index.js"
|
|
8
|
+
"flexplm-mapping": "lib/cli/index.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"lib/**/*",
|
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,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
|
-
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('trims whitespace around the orgName input', async () => {
|
|
63
|
-
answers = [' acme ', '@vibeiq/flexplm-connector'];
|
|
64
|
-
await new config_create_1.ConfigCreateCommand().run();
|
|
65
|
-
expect(fs.existsSync(path.join(tempDir, 'acme-flexplmConfig.json'))).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
it('defaults appIdentifier to @vibeiq/flexplm-connector when left blank', async () => {
|
|
68
|
-
answers = ['acme', ''];
|
|
69
|
-
await new config_create_1.ConfigCreateCommand().run();
|
|
70
|
-
const outPath = path.join(tempDir, 'acme-flexplmConfig.json');
|
|
71
|
-
const config = JSON.parse(fs.readFileSync(outPath, 'utf8'));
|
|
72
|
-
expect(config.appIdentifier).toEqual('@vibeiq/flexplm-connector');
|
|
73
|
-
});
|
|
74
|
-
it('throws when orgName is empty', async () => {
|
|
75
|
-
answers = [' '];
|
|
76
|
-
await expect(new config_create_1.ConfigCreateCommand().run()).rejects.toThrow(/orgName is required/);
|
|
77
|
-
});
|
|
78
|
-
it('refuses to overwrite an existing file', async () => {
|
|
79
|
-
const existing = path.join(tempDir, 'acme-flexplmConfig.json');
|
|
80
|
-
fs.writeFileSync(existing, 'do not clobber', 'utf8');
|
|
81
|
-
answers = ['acme', '@vibeiq/flexplm-connector'];
|
|
82
|
-
await expect(new config_create_1.ConfigCreateCommand().run()).rejects.toThrow(/Refusing to overwrite/);
|
|
83
|
-
expect(fs.readFileSync(existing, 'utf8')).toEqual('do not clobber');
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -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 {};
|