@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.
@@ -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.aaaa660",
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/**/*",
@@ -2,13 +2,9 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
 
5
- const TEMPLATES = ['mapping-template.ts.template', 'config-template.json.template'];
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
- for (const templateFilename of TEMPLATES) {
8
- const SRC = path.join('src', 'cli', 'template', templateFilename);
9
- const DST = path.join('lib', 'cli', 'template', templateFilename);
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,5 +0,0 @@
1
- export declare class ConfigCreateCommand {
2
- private prompt;
3
- private findTemplate;
4
- run(): Promise<void>;
5
- }
@@ -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 {};