avo 3.2.14 → 3.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/cli.js +748 -592
  3. package/package.json +13 -6
package/cli.js CHANGED
@@ -783,6 +783,15 @@ function writeAvoJson(json) {
783
783
  indent: 2,
784
784
  }).then(() => json);
785
785
  }
786
+ // Helper function to map targets to sources and filter out nulls
787
+ function mapTargetsToSources(targets, sources) {
788
+ return targets
789
+ .map((target) => {
790
+ const source = sources.find(({ id }) => id === target.id);
791
+ return source ? { target, source } : null;
792
+ })
793
+ .filter((item) => item !== null);
794
+ }
786
795
  function codegen(json, { schema, sources: targets, warnings, success, errors }) {
787
796
  const newJson = { ...JSON.parse(JSON.stringify(json)), schema };
788
797
  newJson.sources = newJson.sources.map((source) => {
@@ -801,9 +810,75 @@ function codegen(json, { schema, sources: targets, warnings, success, errors })
801
810
  }
802
811
  return source;
803
812
  });
813
+ // Before writing files: detect file-per-event mode and store old event lists
814
+ const oldEventMaps = new Map();
815
+ mapTargetsToSources(targets, json.sources).forEach(({ source }) => {
816
+ try {
817
+ // Read existing main file to check for file-per-event mode
818
+ if (fs.existsSync(source.path)) {
819
+ const existingContent = fs.readFileSync(source.path, 'utf8');
820
+ // eslint-disable-next-line no-use-before-define
821
+ const moduleMap = getModuleMap(existingContent, false);
822
+ if (moduleMap) {
823
+ // getModuleMap returns a string (the module name), not an array
824
+ // The type annotation is incorrect, but the actual value is a string
825
+ // Handle both string and array types for safety
826
+ let moduleName = null;
827
+ if (typeof moduleMap === 'string') {
828
+ moduleName = moduleMap;
829
+ }
830
+ else if (Array.isArray(moduleMap) && moduleMap.length > 0) {
831
+ [moduleName] = moduleMap;
832
+ }
833
+ // eslint-disable-next-line no-use-before-define
834
+ if (moduleName && isFilePerEventMode(source.path, moduleName)) {
835
+ // eslint-disable-next-line no-use-before-define
836
+ const oldEvents = getEventMap(existingContent, false);
837
+ if (oldEvents) {
838
+ oldEventMaps.set(source.id, {
839
+ moduleName,
840
+ events: oldEvents,
841
+ });
842
+ }
843
+ }
844
+ }
845
+ }
846
+ }
847
+ catch (err) {
848
+ // If we can't read the file, skip cleanup for this source
849
+ if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {
850
+ // Only log non-ENOENT errors
851
+ report.warn(`Failed to read existing file for cleanup check: ${err.message}`);
852
+ }
853
+ }
854
+ });
804
855
  const sourceTasks = targets.map((target) => Promise.all(target.code.map((code) => writeFile(code.path, code.content))));
805
856
  const avoJsonTask = writeAvoJson(newJson);
806
- Promise.all([avoJsonTask].concat(sourceTasks)).then(() => {
857
+ return Promise.all([avoJsonTask].concat(sourceTasks)).then(() => {
858
+ // After writing files: cleanup obsolete event files
859
+ mapTargetsToSources(targets, newJson.sources)
860
+ .map(({ target, source }) => {
861
+ const oldEventMap = oldEventMaps.get(source.id);
862
+ return oldEventMap ? { target, source, oldEventMap } : null;
863
+ })
864
+ .filter((item) => item !== null)
865
+ .forEach(({ target, source, oldEventMap }) => {
866
+ // Find the main file in target.code that matches source.path
867
+ const mainFile = target.code.find((code) => code.path === source.path);
868
+ if (mainFile) {
869
+ // Parse AVOEVENTMAP from the newly written main file
870
+ // eslint-disable-next-line no-use-before-define
871
+ const newEvents = getEventMap(mainFile.content, false);
872
+ if (newEvents) {
873
+ // eslint-disable-next-line no-use-before-define
874
+ const eventsDir = getEventsDirectoryPath(source.path, oldEventMap.moduleName);
875
+ // Extract file extension from source path (e.g., .ts, .kt, .swift)
876
+ const sourceExtension = path.extname(source.path);
877
+ // eslint-disable-next-line no-use-before-define
878
+ cleanupObsoleteEventFiles(eventsDir, oldEventMap.events, newEvents, sourceExtension);
879
+ }
880
+ }
881
+ });
807
882
  if (errors !== undefined && errors !== null && errors !== '') {
808
883
  report.warn(`${errors}\n`);
809
884
  }
@@ -1068,6 +1143,51 @@ function getModuleMap(data, verbose) {
1068
1143
  }
1069
1144
  return null;
1070
1145
  }
1146
+ // Get events directory path from source path and module name
1147
+ // e.g., source.path="./Avo.ts", moduleName="Avo" -> "./AvoEvents"
1148
+ export function getEventsDirectoryPath(sourcePath, moduleName) {
1149
+ const parsed = path.parse(sourcePath);
1150
+ return path.join(parsed.dir, `${moduleName}Events`);
1151
+ }
1152
+ // Check if file-per-event mode is active by checking for events directory
1153
+ export function isFilePerEventMode(sourcePath, moduleName) {
1154
+ const eventsDir = getEventsDirectoryPath(sourcePath, moduleName);
1155
+ try {
1156
+ if (!fs.existsSync(eventsDir)) {
1157
+ return false;
1158
+ }
1159
+ return fs.statSync(eventsDir).isDirectory();
1160
+ }
1161
+ catch (error) {
1162
+ // Treat any error (permission, transient FS errors, etc.) as "not a directory"
1163
+ // Log the error for debugging purposes
1164
+ if (error instanceof Error) {
1165
+ report.warn(`Error checking file-per-event mode for ${eventsDir}: ${error.message}`);
1166
+ }
1167
+ return false;
1168
+ }
1169
+ }
1170
+ // Convert event name to file name (camelCase convention from codegen)
1171
+ export function eventNameToFileName(eventName, extension) {
1172
+ const camelCase = eventName.charAt(0).toLowerCase() + eventName.slice(1);
1173
+ return `${camelCase}${extension}`;
1174
+ }
1175
+ // Cleanup obsolete event files
1176
+ export function cleanupObsoleteEventFiles(eventsDir, oldEvents, newEvents, extension) {
1177
+ const removedEvents = oldEvents.filter((e) => !newEvents.includes(e));
1178
+ removedEvents.forEach((eventName) => {
1179
+ const filePath = path.join(eventsDir, eventNameToFileName(eventName, extension));
1180
+ if (fs.existsSync(filePath)) {
1181
+ try {
1182
+ fs.unlinkSync(filePath);
1183
+ report.info(`Removed obsolete event file: ${filePath}`);
1184
+ }
1185
+ catch (error) {
1186
+ report.error(`Failed to remove obsolete event file: ${filePath} - ${error instanceof Error ? error.message : String(error)}`);
1187
+ }
1188
+ }
1189
+ });
1190
+ }
1071
1191
  function getSource(argv, json) {
1072
1192
  if (!json.sources || !json.sources.length) {
1073
1193
  report.info('No sources configured.');
@@ -1357,190 +1477,380 @@ function logout(refreshToken) {
1357
1477
  function parseForceFeaturesParam(forceFeatures) {
1358
1478
  return forceFeatures?.split(',').map((it) => it.trim());
1359
1479
  }
1360
- yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1361
- .usage('$0 command')
1362
- .scriptName('avo')
1363
- .version(pkg.version)
1364
- .option('v', {
1365
- alias: 'verbose',
1366
- default: false,
1367
- describe: 'make output more verbose',
1368
- type: 'boolean',
1369
- })
1370
- .command({
1371
- command: 'track-install',
1372
- desc: false,
1373
- handler: async (argv) => {
1374
- try {
1375
- Avo.cliInstalled({
1376
- userId_: installIdOrUserId(),
1377
- cliInvokedByCi: invokedByCi(),
1378
- }).catch((error) => {
1379
- if (argv.verbose) {
1380
- console.error('Request to track cli installed failed', error);
1480
+ // Only execute yargs CLI if this file is run directly (not imported for testing)
1481
+ // Skip execution if we're in a test environment
1482
+ // Check multiple indicators that we're running under Jest
1483
+ const isMainModule = !process.env.AVO_TEST_MODE &&
1484
+ !process.env.JEST_WORKER_ID &&
1485
+ typeof jest === 'undefined' &&
1486
+ !process.argv.some((arg) => arg.includes('jest')) &&
1487
+ !process.argv.some((arg) => arg.includes('jest.js')) &&
1488
+ process.argv[1]?.endsWith('cli.js');
1489
+ if (isMainModule) {
1490
+ try {
1491
+ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1492
+ .usage('$0 command')
1493
+ .scriptName('avo')
1494
+ .version(pkg.version)
1495
+ .option('v', {
1496
+ alias: 'verbose',
1497
+ default: false,
1498
+ describe: 'make output more verbose',
1499
+ type: 'boolean',
1500
+ })
1501
+ .command({
1502
+ command: 'track-install',
1503
+ describe: false,
1504
+ handler: async (argv) => {
1505
+ try {
1506
+ Avo.cliInstalled({
1507
+ userId_: installIdOrUserId(),
1508
+ cliInvokedByCi: invokedByCi(),
1509
+ }).catch((error) => {
1510
+ if (argv.verbose) {
1511
+ console.error('Request to track cli installed failed', error);
1512
+ }
1513
+ });
1381
1514
  }
1382
- });
1383
- }
1384
- catch (error) {
1385
- console.error('Unexpected error failed to track cli installed', error);
1386
- }
1387
- },
1388
- })
1389
- .command({
1390
- command: 'init',
1391
- desc: 'Initialize an Avo workspace in the current folder',
1392
- handler: (argv) => {
1393
- loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
1394
- .then((json) => {
1395
- if (json) {
1515
+ catch (error) {
1516
+ console.error('Unexpected error failed to track cli installed', error);
1517
+ }
1518
+ },
1519
+ })
1520
+ .command({
1521
+ command: 'init',
1522
+ describe: 'Initialize an Avo workspace in the current folder',
1523
+ handler: (argv) => {
1524
+ loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
1525
+ .then((json) => {
1526
+ if (json) {
1527
+ Avo.cliInvoked({
1528
+ schemaId: json.schema.id,
1529
+ schemaName: json.schema.name,
1530
+ branchId: json.branch.id,
1531
+ branchName: json.branch.name,
1532
+ userId_: installIdOrUserId(),
1533
+ cliAction: Avo.CliAction.INIT,
1534
+ cliInvokedByCi: invokedByCi(),
1535
+ force: undefined,
1536
+ forceFeatures: undefined,
1537
+ });
1538
+ report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
1539
+ return Promise.resolve();
1540
+ }
1541
+ Avo.cliInvoked({
1542
+ schemaId: 'N/A',
1543
+ schemaName: 'N/A',
1544
+ branchId: 'N/A',
1545
+ branchName: 'N/A',
1546
+ userId_: installIdOrUserId(),
1547
+ cliAction: Avo.CliAction.INIT,
1548
+ cliInvokedByCi: invokedByCi(),
1549
+ force: undefined,
1550
+ forceFeatures: undefined,
1551
+ });
1552
+ return requireAuth(argv, () => init()
1553
+ .then(writeAvoJson)
1554
+ .then(() => {
1555
+ report.info("Run 'avo pull' to pull analytics wrappers from Avo");
1556
+ }));
1557
+ })
1558
+ .catch(() => {
1559
+ Avo.cliInvoked({
1560
+ schemaId: 'N/A',
1561
+ schemaName: 'N/A',
1562
+ branchId: 'N/A',
1563
+ branchName: 'N/A',
1564
+ userId_: installIdOrUserId(),
1565
+ cliAction: Avo.CliAction.INIT,
1566
+ cliInvokedByCi: invokedByCi(),
1567
+ force: undefined,
1568
+ forceFeatures: undefined,
1569
+ });
1570
+ });
1571
+ },
1572
+ })
1573
+ .command({
1574
+ command: 'pull [source]',
1575
+ describe: 'Pull analytics wrappers from Avo workspace',
1576
+ builder: (yargs) => yargs
1577
+ .option('branch', {
1578
+ describe: 'Name of Avo branch to pull from',
1579
+ type: 'string',
1580
+ })
1581
+ .option('f', {
1582
+ alias: 'force',
1583
+ describe: 'Proceed ignoring the unsupported features for given source',
1584
+ default: false,
1585
+ type: 'boolean',
1586
+ })
1587
+ .option('forceFeatures', {
1588
+ describe: 'Optional comma separated list of features to force enable, pass unsupported name to get the list of available features',
1589
+ default: undefined,
1590
+ type: 'string',
1591
+ }),
1592
+ handler: (argv) => {
1593
+ loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1594
+ .then((json) => {
1595
+ Avo.cliInvoked({
1596
+ schemaId: json.schema.id,
1597
+ schemaName: json.schema.name,
1598
+ branchId: json.branch.id,
1599
+ branchName: json.branch.name,
1600
+ userId_: installIdOrUserId(),
1601
+ cliAction: Avo.CliAction.PULL,
1602
+ cliInvokedByCi: invokedByCi(),
1603
+ force: argv.f === true,
1604
+ forceFeatures: parseForceFeaturesParam(argv.forceFeatures),
1605
+ });
1606
+ requireAuth(argv, () => {
1607
+ if (argv.branch && json.branch.name !== argv.branch) {
1608
+ return checkout(argv.branch, json)
1609
+ .then((data) => getSource(argv, data))
1610
+ .then(([source, data]) => pull(source, data));
1611
+ }
1612
+ report.info(`Pulling from branch '${json.branch.name}'`);
1613
+ return getSource(argv, json).then(([source, data]) => pull(source, data));
1614
+ });
1615
+ })
1616
+ .catch((error) => {
1617
+ Avo.cliInvoked({
1618
+ schemaId: 'N/A',
1619
+ schemaName: 'N/A',
1620
+ branchId: 'N/A',
1621
+ branchName: 'N/A',
1622
+ userId_: installIdOrUserId(),
1623
+ cliAction: Avo.CliAction.PULL,
1624
+ cliInvokedByCi: invokedByCi(),
1625
+ force: undefined,
1626
+ forceFeatures: undefined,
1627
+ });
1628
+ throw error;
1629
+ });
1630
+ },
1631
+ })
1632
+ .command({
1633
+ command: 'checkout [branch]',
1634
+ aliases: ['branch'],
1635
+ describe: 'Switch branches',
1636
+ handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1637
+ .then((json) => {
1396
1638
  Avo.cliInvoked({
1397
1639
  schemaId: json.schema.id,
1398
1640
  schemaName: json.schema.name,
1399
1641
  branchId: json.branch.id,
1400
1642
  branchName: json.branch.name,
1401
1643
  userId_: installIdOrUserId(),
1402
- cliAction: Avo.CliAction.INIT,
1644
+ cliAction: Avo.CliAction.CHECKOUT,
1403
1645
  cliInvokedByCi: invokedByCi(),
1404
1646
  force: undefined,
1405
1647
  forceFeatures: undefined,
1406
1648
  });
1407
- report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
1408
- return Promise.resolve();
1409
- }
1410
- Avo.cliInvoked({
1411
- schemaId: 'N/A',
1412
- schemaName: 'N/A',
1413
- branchId: 'N/A',
1414
- branchName: 'N/A',
1415
- userId_: installIdOrUserId(),
1416
- cliAction: Avo.CliAction.INIT,
1417
- cliInvokedByCi: invokedByCi(),
1418
- force: undefined,
1419
- forceFeatures: undefined,
1420
- });
1421
- return requireAuth(argv, () => init()
1422
- .then(writeAvoJson)
1423
- .then(() => {
1424
- report.info("Run 'avo pull' to pull analytics wrappers from Avo");
1425
- }));
1649
+ report.info(`Currently on branch '${json.branch.name}'`);
1650
+ requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
1651
+ })
1652
+ .catch((error) => {
1653
+ Avo.cliInvoked({
1654
+ schemaId: 'N/A',
1655
+ schemaName: 'N/A',
1656
+ branchId: 'N/A',
1657
+ branchName: 'N/A',
1658
+ userId_: installIdOrUserId(),
1659
+ cliAction: Avo.CliAction.CHECKOUT,
1660
+ cliInvokedByCi: invokedByCi(),
1661
+ force: undefined,
1662
+ forceFeatures: undefined,
1663
+ });
1664
+ throw error;
1665
+ }),
1426
1666
  })
1427
- .catch(() => {
1428
- Avo.cliInvoked({
1429
- schemaId: 'N/A',
1430
- schemaName: 'N/A',
1431
- branchId: 'N/A',
1432
- branchName: 'N/A',
1433
- userId_: installIdOrUserId(),
1434
- cliAction: Avo.CliAction.INIT,
1435
- cliInvokedByCi: invokedByCi(),
1436
- force: undefined,
1437
- forceFeatures: undefined,
1438
- });
1439
- });
1440
- },
1441
- })
1442
- .command({
1443
- command: 'pull [source]',
1444
- desc: 'Pull analytics wrappers from Avo workspace',
1445
- builder: (yargs) => yargs
1446
- .option('branch', {
1447
- describe: 'Name of Avo branch to pull from',
1448
- type: 'string',
1449
- })
1450
- .option('f', {
1451
- alias: 'force',
1452
- describe: 'Proceed ignoring the unsupported features for given source',
1453
- default: false,
1454
- type: 'boolean',
1455
- })
1456
- .option('forceFeatures', {
1457
- describe: 'Optional comma separated list of features to force enable, pass unsupported name to get the list of available features',
1458
- default: undefined,
1459
- type: 'string',
1460
- }),
1461
- handler: (argv) => {
1462
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1463
- .then((json) => {
1464
- Avo.cliInvoked({
1465
- schemaId: json.schema.id,
1466
- schemaName: json.schema.name,
1467
- branchId: json.branch.id,
1468
- branchName: json.branch.name,
1469
- userId_: installIdOrUserId(),
1470
- cliAction: Avo.CliAction.PULL,
1471
- cliInvokedByCi: invokedByCi(),
1472
- force: argv.f === true,
1473
- forceFeatures: parseForceFeaturesParam(argv.forceFeatures),
1474
- });
1475
- requireAuth(argv, () => {
1476
- if (argv.branch && json.branch.name !== argv.branch) {
1477
- return checkout(argv.branch, json)
1478
- .then((data) => getSource(argv, data))
1479
- .then(([source, data]) => pull(source, data));
1480
- }
1481
- report.info(`Pulling from branch '${json.branch.name}'`);
1482
- return getSource(argv, json).then(([source, data]) => pull(source, data));
1483
- });
1667
+ .command({
1668
+ command: 'source <command>',
1669
+ describe: 'Manage sources for the current project',
1670
+ builder: (yargs) => yargs
1671
+ .command({
1672
+ command: '$0',
1673
+ describe: 'List sources in this project',
1674
+ handler: (argv) => {
1675
+ loadAvoJsonOrInit({
1676
+ argv,
1677
+ skipInit: false,
1678
+ skipPullMaster: false,
1679
+ })
1680
+ .then((json) => {
1681
+ Avo.cliInvoked({
1682
+ schemaId: json.schema.id,
1683
+ schemaName: json.schema.name,
1684
+ branchId: json.branch.id,
1685
+ branchName: json.branch.name,
1686
+ userId_: installIdOrUserId(),
1687
+ cliAction: Avo.CliAction.SOURCE,
1688
+ cliInvokedByCi: invokedByCi(),
1689
+ force: undefined,
1690
+ forceFeatures: undefined,
1691
+ });
1692
+ if (!json.sources || !json.sources.length) {
1693
+ report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1694
+ return;
1695
+ }
1696
+ report.info('Sources in this project:');
1697
+ report.tree('sources', json.sources.map((source) => ({
1698
+ name: source.name,
1699
+ children: [{ name: source.path }],
1700
+ })));
1701
+ })
1702
+ .catch((error) => {
1703
+ Avo.cliInvoked({
1704
+ schemaId: 'N/A',
1705
+ schemaName: 'N/A',
1706
+ branchId: 'N/A',
1707
+ branchName: 'N/A',
1708
+ userId_: installIdOrUserId(),
1709
+ cliAction: Avo.CliAction.SOURCE,
1710
+ cliInvokedByCi: invokedByCi(),
1711
+ force: undefined,
1712
+ forceFeatures: undefined,
1713
+ });
1714
+ throw error;
1715
+ });
1716
+ },
1717
+ })
1718
+ .command({
1719
+ command: 'add [source]',
1720
+ describe: 'Add a source to this project',
1721
+ handler: (argv) => {
1722
+ loadAvoJsonOrInit({
1723
+ argv,
1724
+ skipInit: false,
1725
+ skipPullMaster: false,
1726
+ })
1727
+ .then((json) => {
1728
+ Avo.cliInvoked({
1729
+ schemaId: json.schema.id,
1730
+ schemaName: json.schema.name,
1731
+ branchId: json.branch.id,
1732
+ branchName: json.branch.name,
1733
+ userId_: installIdOrUserId(),
1734
+ cliAction: Avo.CliAction.SOURCE_ADD,
1735
+ cliInvokedByCi: invokedByCi(),
1736
+ force: undefined,
1737
+ forceFeatures: undefined,
1738
+ });
1739
+ requireAuth(argv, () => {
1740
+ selectSource(argv.source, json).then(writeAvoJson);
1741
+ });
1742
+ })
1743
+ .catch((error) => {
1744
+ Avo.cliInvoked({
1745
+ schemaId: 'N/A',
1746
+ schemaName: 'N/A',
1747
+ branchId: 'N/A',
1748
+ branchName: 'N/A',
1749
+ userId_: installIdOrUserId(),
1750
+ cliAction: Avo.CliAction.SOURCE_ADD,
1751
+ cliInvokedByCi: invokedByCi(),
1752
+ force: undefined,
1753
+ forceFeatures: undefined,
1754
+ });
1755
+ throw error;
1756
+ });
1757
+ },
1758
+ })
1759
+ .command({
1760
+ command: 'remove [source]',
1761
+ aliases: ['rm'],
1762
+ describe: 'Remove a source from this project',
1763
+ handler: (argv) => {
1764
+ loadAvoJsonOrInit({
1765
+ argv,
1766
+ skipInit: false,
1767
+ skipPullMaster: false,
1768
+ })
1769
+ .then((json) => {
1770
+ Avo.cliInvoked({
1771
+ schemaId: json.schema.id,
1772
+ schemaName: json.schema.name,
1773
+ branchId: json.branch.id,
1774
+ branchName: json.branch.name,
1775
+ userId_: installIdOrUserId(),
1776
+ cliAction: Avo.CliAction.SOURCE_REMOVE,
1777
+ cliInvokedByCi: invokedByCi(),
1778
+ force: undefined,
1779
+ forceFeatures: undefined,
1780
+ });
1781
+ if (!json.sources || !json.sources.length) {
1782
+ report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1783
+ return;
1784
+ }
1785
+ const getSourceToRemove = (argv, json) => {
1786
+ if (argv.source) {
1787
+ return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
1788
+ }
1789
+ const choices = json.sources.map((source) => ({
1790
+ value: source,
1791
+ name: source.name,
1792
+ }));
1793
+ return inquirer
1794
+ .prompt({
1795
+ type: 'list',
1796
+ name: 'source',
1797
+ message: 'Select a source to remove',
1798
+ choices,
1799
+ pageSize: 15,
1800
+ })
1801
+ .then((answer) => answer.source);
1802
+ };
1803
+ getSourceToRemove(argv, json).then((targetSource) => {
1804
+ if (!targetSource) {
1805
+ report.error(`Source ${argv.source} not found in project.`);
1806
+ return Promise.resolve();
1807
+ }
1808
+ return inquirer
1809
+ .prompt([
1810
+ {
1811
+ type: 'confirm',
1812
+ name: 'remove',
1813
+ default: true,
1814
+ message: `Are you sure you want to remove source ${targetSource.name} from project`,
1815
+ },
1816
+ ])
1817
+ .then((answer) => {
1818
+ if (answer.remove) {
1819
+ const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
1820
+ const newJson = { ...json, sources };
1821
+ return writeAvoJson(newJson).then(() => {
1822
+ // XXX ask to remove file as well?
1823
+ report.info(`Removed source ${targetSource.name} from project`);
1824
+ });
1825
+ }
1826
+ report.info(`Did not remove source ${targetSource.name} from project`);
1827
+ return Promise.resolve();
1828
+ });
1829
+ });
1830
+ })
1831
+ .catch((error) => {
1832
+ Avo.cliInvoked({
1833
+ schemaId: 'N/A',
1834
+ schemaName: 'N/A',
1835
+ branchId: 'N/A',
1836
+ branchName: 'N/A',
1837
+ userId_: installIdOrUserId(),
1838
+ cliAction: Avo.CliAction.SOURCE_REMOVE,
1839
+ cliInvokedByCi: invokedByCi(),
1840
+ force: undefined,
1841
+ forceFeatures: undefined,
1842
+ });
1843
+ throw error;
1844
+ });
1845
+ },
1846
+ }),
1847
+ handler: () => {
1848
+ // Parent command - subcommands handle the actual logic
1849
+ },
1484
1850
  })
1485
- .catch((error) => {
1486
- Avo.cliInvoked({
1487
- schemaId: 'N/A',
1488
- schemaName: 'N/A',
1489
- branchId: 'N/A',
1490
- branchName: 'N/A',
1491
- userId_: installIdOrUserId(),
1492
- cliAction: Avo.CliAction.PULL,
1493
- cliInvokedByCi: invokedByCi(),
1494
- force: undefined,
1495
- forceFeatures: undefined,
1496
- });
1497
- throw error;
1498
- });
1499
- },
1500
- })
1501
- .command({
1502
- command: 'checkout [branch]',
1503
- aliases: ['branch'],
1504
- desc: 'Switch branches',
1505
- handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1506
- .then((json) => {
1507
- Avo.cliInvoked({
1508
- schemaId: json.schema.id,
1509
- schemaName: json.schema.name,
1510
- branchId: json.branch.id,
1511
- branchName: json.branch.name,
1512
- userId_: installIdOrUserId(),
1513
- cliAction: Avo.CliAction.CHECKOUT,
1514
- cliInvokedByCi: invokedByCi(),
1515
- force: undefined,
1516
- forceFeatures: undefined,
1517
- });
1518
- report.info(`Currently on branch '${json.branch.name}'`);
1519
- requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
1520
- })
1521
- .catch((error) => {
1522
- Avo.cliInvoked({
1523
- schemaId: 'N/A',
1524
- schemaName: 'N/A',
1525
- branchId: 'N/A',
1526
- branchName: 'N/A',
1527
- userId_: installIdOrUserId(),
1528
- cliAction: Avo.CliAction.CHECKOUT,
1529
- cliInvokedByCi: invokedByCi(),
1530
- force: undefined,
1531
- forceFeatures: undefined,
1532
- });
1533
- throw error;
1534
- }),
1535
- })
1536
- .command({
1537
- command: 'source <command>',
1538
- desc: 'Manage sources for the current project',
1539
- builder: (yargs) => {
1540
- yargs
1541
1851
  .command({
1542
- command: '$0',
1543
- desc: 'List sources in this project',
1852
+ command: 'status [source]',
1853
+ describe: 'Show the status of the Avo implementation',
1544
1854
  handler: (argv) => {
1545
1855
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1546
1856
  .then((json) => {
@@ -1550,21 +1860,15 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1550
1860
  branchId: json.branch.id,
1551
1861
  branchName: json.branch.name,
1552
1862
  userId_: installIdOrUserId(),
1553
- cliAction: Avo.CliAction.SOURCE,
1863
+ cliAction: Avo.CliAction.STATUS,
1554
1864
  cliInvokedByCi: invokedByCi(),
1555
1865
  force: undefined,
1556
1866
  forceFeatures: undefined,
1557
1867
  });
1558
- if (!json.sources || !json.sources.length) {
1559
- report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1560
- return;
1561
- }
1562
- report.info('Sources in this project:');
1563
- report.tree('sources', json.sources.map((source) => ({
1564
- name: source.name,
1565
- children: [{ name: source.path }],
1566
- })));
1868
+ report.info(`Currently on branch '${json.branch.name}'`);
1869
+ return getSource(argv, json);
1567
1870
  })
1871
+ .then(([source, json]) => status(source, json, argv))
1568
1872
  .catch((error) => {
1569
1873
  Avo.cliInvoked({
1570
1874
  schemaId: 'N/A',
@@ -1572,7 +1876,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1572
1876
  branchId: 'N/A',
1573
1877
  branchName: 'N/A',
1574
1878
  userId_: installIdOrUserId(),
1575
- cliAction: Avo.CliAction.SOURCE,
1879
+ cliAction: Avo.CliAction.STATUS,
1576
1880
  cliInvokedByCi: invokedByCi(),
1577
1881
  force: undefined,
1578
1882
  forceFeatures: undefined,
@@ -1582,10 +1886,17 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1582
1886
  },
1583
1887
  })
1584
1888
  .command({
1585
- command: 'add [source]',
1586
- desc: 'Add a source to this project',
1889
+ command: 'merge main',
1890
+ aliases: ['merge master'],
1891
+ describe: 'Pull the Avo main branch into your current branch',
1892
+ builder: (yargs) => yargs.option('f', {
1893
+ alias: 'force',
1894
+ describe: 'Proceed with merge when incoming branch is open',
1895
+ default: false,
1896
+ type: 'boolean',
1897
+ }),
1587
1898
  handler: (argv) => {
1588
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1899
+ loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
1589
1900
  .then((json) => {
1590
1901
  Avo.cliInvoked({
1591
1902
  schemaId: json.schema.id,
@@ -1593,14 +1904,12 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1593
1904
  branchId: json.branch.id,
1594
1905
  branchName: json.branch.name,
1595
1906
  userId_: installIdOrUserId(),
1596
- cliAction: Avo.CliAction.SOURCE_ADD,
1907
+ cliAction: Avo.CliAction.MERGE,
1597
1908
  cliInvokedByCi: invokedByCi(),
1598
- force: undefined,
1909
+ force: json.force,
1599
1910
  forceFeatures: undefined,
1600
1911
  });
1601
- requireAuth(argv, () => {
1602
- selectSource(argv.source, json).then(writeAvoJson);
1603
- });
1912
+ return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
1604
1913
  })
1605
1914
  .catch((error) => {
1606
1915
  Avo.cliInvoked({
@@ -1609,7 +1918,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1609
1918
  branchId: 'N/A',
1610
1919
  branchName: 'N/A',
1611
1920
  userId_: installIdOrUserId(),
1612
- cliAction: Avo.CliAction.SOURCE_ADD,
1921
+ cliAction: Avo.CliAction.MERGE,
1613
1922
  cliInvokedByCi: invokedByCi(),
1614
1923
  force: undefined,
1615
1924
  forceFeatures: undefined,
@@ -1619,9 +1928,63 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1619
1928
  },
1620
1929
  })
1621
1930
  .command({
1622
- command: 'remove [source]',
1623
- aliases: ['rm'],
1624
- desc: 'Remove a source from this project',
1931
+ command: 'conflict',
1932
+ aliases: ['resolve', 'conflicts'],
1933
+ describe: 'Resolve git conflicts in Avo files',
1934
+ handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
1935
+ .then((avoFile) => {
1936
+ if (hasMergeConflicts(avoFile)) {
1937
+ return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
1938
+ argv,
1939
+ skipPullMaster: false,
1940
+ }).then((json) => {
1941
+ Avo.cliInvoked({
1942
+ schemaId: json.schema.id,
1943
+ schemaName: json.schema.name,
1944
+ branchId: json.branch.id,
1945
+ branchName: json.branch.name,
1946
+ userId_: installIdOrUserId(),
1947
+ cliAction: Avo.CliAction.CONFLICT,
1948
+ cliInvokedByCi: invokedByCi(),
1949
+ force: undefined,
1950
+ forceFeatures: undefined,
1951
+ });
1952
+ pull(null, json);
1953
+ }));
1954
+ }
1955
+ report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
1956
+ const json = JSON.parse(avoFile);
1957
+ Avo.cliInvoked({
1958
+ schemaId: json.schema.id,
1959
+ schemaName: json.schema.name,
1960
+ branchId: json.branch.id,
1961
+ branchName: json.branch.name,
1962
+ userId_: installIdOrUserId(),
1963
+ cliAction: Avo.CliAction.CONFLICT,
1964
+ cliInvokedByCi: invokedByCi(),
1965
+ force: undefined,
1966
+ forceFeatures: undefined,
1967
+ });
1968
+ return Promise.resolve(json);
1969
+ })
1970
+ .catch((error) => {
1971
+ Avo.cliInvoked({
1972
+ schemaId: 'N/A',
1973
+ schemaName: 'N/A',
1974
+ branchId: 'N/A',
1975
+ branchName: 'N/A',
1976
+ userId_: installIdOrUserId(),
1977
+ cliAction: Avo.CliAction.CONFLICT,
1978
+ cliInvokedByCi: invokedByCi(),
1979
+ force: undefined,
1980
+ forceFeatures: undefined,
1981
+ });
1982
+ throw error;
1983
+ }),
1984
+ })
1985
+ .command({
1986
+ command: 'edit',
1987
+ describe: 'Open the Avo workspace in your browser',
1625
1988
  handler: (argv) => {
1626
1989
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1627
1990
  .then((json) => {
@@ -1631,432 +1994,225 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1631
1994
  branchId: json.branch.id,
1632
1995
  branchName: json.branch.name,
1633
1996
  userId_: installIdOrUserId(),
1634
- cliAction: Avo.CliAction.SOURCE_REMOVE,
1997
+ cliAction: Avo.CliAction.EDIT,
1998
+ cliInvokedByCi: invokedByCi(),
1999
+ force: undefined,
2000
+ forceFeatures: undefined,
2001
+ });
2002
+ const { schema } = json;
2003
+ const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
2004
+ report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
2005
+ open(schemaUrl);
2006
+ })
2007
+ .catch((error) => {
2008
+ Avo.cliInvoked({
2009
+ schemaId: 'N/A',
2010
+ schemaName: 'N/A',
2011
+ branchId: 'N/A',
2012
+ branchName: 'N/A',
2013
+ userId_: installIdOrUserId(),
2014
+ cliAction: Avo.CliAction.EDIT,
1635
2015
  cliInvokedByCi: invokedByCi(),
1636
2016
  force: undefined,
1637
2017
  forceFeatures: undefined,
1638
2018
  });
1639
- if (!json.sources || !json.sources.length) {
1640
- report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
2019
+ throw error;
2020
+ });
2021
+ },
2022
+ })
2023
+ .command({
2024
+ command: 'login',
2025
+ describe: 'Log into the Avo platform',
2026
+ handler: () => {
2027
+ const command = () => {
2028
+ const user = conf.get('user');
2029
+ if (user) {
2030
+ report.info(`Already logged in as ${email(user.email)}`);
1641
2031
  return;
1642
2032
  }
1643
- const getSourceToRemove = (argv, json) => {
1644
- if (argv.source) {
1645
- return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
1646
- }
1647
- const choices = json.sources.map((source) => ({
1648
- value: source,
1649
- name: source.name,
1650
- }));
1651
- return inquirer
1652
- .prompt({
1653
- type: 'list',
1654
- name: 'source',
1655
- message: 'Select a source to remove',
1656
- choices,
1657
- pageSize: 15,
1658
- })
1659
- .then((answer) => answer.source);
1660
- };
1661
- getSourceToRemove(argv, json).then((targetSource) => {
1662
- if (!targetSource) {
1663
- report.error(`Source ${argv.source} not found in project.`);
1664
- return Promise.resolve();
1665
- }
1666
- return inquirer
1667
- .prompt([
1668
- {
1669
- type: 'confirm',
1670
- name: 'remove',
1671
- default: true,
1672
- message: `Are you sure you want to remove source ${targetSource.name} from project`,
1673
- },
1674
- ])
1675
- .then((answer) => {
1676
- if (answer.remove) {
1677
- const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
1678
- const newJson = { ...json, sources };
1679
- return writeAvoJson(newJson).then(() => {
1680
- // XXX ask to remove file as well?
1681
- report.info(`Removed source ${targetSource.name} from project`);
1682
- });
1683
- }
1684
- report.info(`Did not remove source ${targetSource.name} from project`);
1685
- return Promise.resolve();
2033
+ login()
2034
+ .then((result) => {
2035
+ conf.set('user', result.user);
2036
+ conf.set('tokens', result.tokens);
2037
+ Avo.signedIn({
2038
+ userId_: result.user.user_id,
2039
+ email: result.user.email,
2040
+ authenticationMethod: Avo.AuthenticationMethod.CLI,
2041
+ });
2042
+ report.success(`Logged in as ${email(result.user.email)}`);
2043
+ })
2044
+ .catch(() => {
2045
+ Avo.signInFailed({
2046
+ userId_: conf.get('avo_install_id'),
2047
+ emailInput: '', // XXX this is not passed back here
2048
+ signInError: Avo.SignInError.UNKNOWN,
1686
2049
  });
1687
2050
  });
2051
+ };
2052
+ loadAvoJson()
2053
+ .then((json) => {
2054
+ Avo.cliInvoked({
2055
+ schemaId: json.schema.id,
2056
+ schemaName: json.schema.name,
2057
+ branchId: json.branch.id,
2058
+ branchName: json.branch.name,
2059
+ userId_: installIdOrUserId(),
2060
+ cliAction: Avo.CliAction.LOGIN,
2061
+ cliInvokedByCi: invokedByCi(),
2062
+ force: undefined,
2063
+ forceFeatures: undefined,
2064
+ });
2065
+ command();
1688
2066
  })
1689
- .catch((error) => {
2067
+ .catch(() => {
1690
2068
  Avo.cliInvoked({
1691
2069
  schemaId: 'N/A',
1692
2070
  schemaName: 'N/A',
1693
2071
  branchId: 'N/A',
1694
2072
  branchName: 'N/A',
1695
2073
  userId_: installIdOrUserId(),
1696
- cliAction: Avo.CliAction.SOURCE_REMOVE,
2074
+ cliAction: Avo.CliAction.LOGIN,
1697
2075
  cliInvokedByCi: invokedByCi(),
1698
2076
  force: undefined,
1699
2077
  forceFeatures: undefined,
1700
2078
  });
1701
- throw error;
2079
+ command();
1702
2080
  });
1703
2081
  },
1704
- });
1705
- },
1706
- })
1707
- .command({
1708
- command: 'status [source]',
1709
- desc: 'Show the status of the Avo implementation',
1710
- handler: (argv) => {
1711
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1712
- .then((json) => {
1713
- Avo.cliInvoked({
1714
- schemaId: json.schema.id,
1715
- schemaName: json.schema.name,
1716
- branchId: json.branch.id,
1717
- branchName: json.branch.name,
1718
- userId_: installIdOrUserId(),
1719
- cliAction: Avo.CliAction.STATUS,
1720
- cliInvokedByCi: invokedByCi(),
1721
- force: undefined,
1722
- forceFeatures: undefined,
1723
- });
1724
- report.info(`Currently on branch '${json.branch.name}'`);
1725
- return getSource(argv, json);
1726
2082
  })
1727
- .then(([source, json]) => status(source, json, argv))
1728
- .catch((error) => {
1729
- Avo.cliInvoked({
1730
- schemaId: 'N/A',
1731
- schemaName: 'N/A',
1732
- branchId: 'N/A',
1733
- branchName: 'N/A',
1734
- userId_: installIdOrUserId(),
1735
- cliAction: Avo.CliAction.STATUS,
1736
- cliInvokedByCi: invokedByCi(),
1737
- force: undefined,
1738
- forceFeatures: undefined,
1739
- });
1740
- throw error;
1741
- });
1742
- },
1743
- })
1744
- .command({
1745
- command: 'merge main',
1746
- aliases: ['merge master'],
1747
- desc: 'Pull the Avo main branch into your current branch',
1748
- builder: (yargs) => yargs.option('f', {
1749
- alias: 'force',
1750
- describe: 'Proceed with merge when incoming branch is open',
1751
- default: false,
1752
- type: 'boolean',
1753
- }),
1754
- handler: (argv) => {
1755
- loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
1756
- .then((json) => {
1757
- Avo.cliInvoked({
1758
- schemaId: json.schema.id,
1759
- schemaName: json.schema.name,
1760
- branchId: json.branch.id,
1761
- branchName: json.branch.name,
1762
- userId_: installIdOrUserId(),
1763
- cliAction: Avo.CliAction.MERGE,
1764
- cliInvokedByCi: invokedByCi(),
1765
- force: json.force,
1766
- forceFeatures: undefined,
1767
- });
1768
- return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
1769
- })
1770
- .catch((error) => {
1771
- Avo.cliInvoked({
1772
- schemaId: 'N/A',
1773
- schemaName: 'N/A',
1774
- branchId: 'N/A',
1775
- branchName: 'N/A',
1776
- userId_: installIdOrUserId(),
1777
- cliAction: Avo.CliAction.MERGE,
1778
- cliInvokedByCi: invokedByCi(),
1779
- force: undefined,
1780
- forceFeatures: undefined,
1781
- });
1782
- throw error;
1783
- });
1784
- },
1785
- })
1786
- .command({
1787
- command: 'conflict',
1788
- aliases: ['resolve', 'conflicts'],
1789
- desc: 'Resolve git conflicts in Avo files',
1790
- handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
1791
- .then((avoFile) => {
1792
- if (hasMergeConflicts(avoFile)) {
1793
- return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
1794
- argv,
1795
- skipPullMaster: false,
1796
- }).then((json) => {
1797
- Avo.cliInvoked({
1798
- schemaId: json.schema.id,
1799
- schemaName: json.schema.name,
1800
- branchId: json.branch.id,
1801
- branchName: json.branch.name,
1802
- userId_: installIdOrUserId(),
1803
- cliAction: Avo.CliAction.CONFLICT,
1804
- cliInvokedByCi: invokedByCi(),
1805
- force: undefined,
1806
- forceFeatures: undefined,
2083
+ .command({
2084
+ command: 'logout',
2085
+ describe: 'Log out from the Avo platform',
2086
+ handler: () => {
2087
+ const command = () => {
2088
+ const user = conf.get('user');
2089
+ const tokens = conf.get('tokens');
2090
+ const currentToken = tokens.refreshToken;
2091
+ const token = currentToken;
2092
+ api.setRefreshToken(token);
2093
+ if (token) {
2094
+ logout(token);
2095
+ }
2096
+ if (token || user || tokens) {
2097
+ let msg = 'Logged out';
2098
+ if (token === currentToken) {
2099
+ if (user) {
2100
+ msg += ` from ${bold(user.email)}`;
2101
+ }
2102
+ }
2103
+ else {
2104
+ msg += ` token "${bold(token)}"`;
2105
+ }
2106
+ report.log(msg);
2107
+ }
2108
+ else {
2109
+ report.log("No need to logout, you're not logged in");
2110
+ }
2111
+ };
2112
+ loadAvoJson()
2113
+ .then((json) => {
2114
+ Avo.cliInvoked({
2115
+ schemaId: json.schema.id,
2116
+ schemaName: json.schema.name,
2117
+ branchId: json.branch.id,
2118
+ branchName: json.branch.name,
2119
+ userId_: installIdOrUserId(),
2120
+ cliAction: Avo.CliAction.LOGOUT,
2121
+ cliInvokedByCi: invokedByCi(),
2122
+ force: undefined,
2123
+ forceFeatures: undefined,
2124
+ });
2125
+ command();
2126
+ })
2127
+ .catch(() => {
2128
+ Avo.cliInvoked({
2129
+ schemaId: 'N/A',
2130
+ schemaName: 'N/A',
2131
+ branchId: 'N/A',
2132
+ branchName: 'N/A',
2133
+ userId_: installIdOrUserId(),
2134
+ cliAction: Avo.CliAction.LOGOUT,
2135
+ cliInvokedByCi: invokedByCi(),
2136
+ force: undefined,
2137
+ forceFeatures: undefined,
2138
+ });
2139
+ command();
1807
2140
  });
1808
- pull(null, json);
1809
- }));
1810
- }
1811
- report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
1812
- const json = JSON.parse(avoFile);
1813
- Avo.cliInvoked({
1814
- schemaId: json.schema.id,
1815
- schemaName: json.schema.name,
1816
- branchId: json.branch.id,
1817
- branchName: json.branch.name,
1818
- userId_: installIdOrUserId(),
1819
- cliAction: Avo.CliAction.CONFLICT,
1820
- cliInvokedByCi: invokedByCi(),
1821
- force: undefined,
1822
- forceFeatures: undefined,
1823
- });
1824
- return Promise.resolve(json);
1825
- })
1826
- .catch((error) => {
1827
- Avo.cliInvoked({
1828
- schemaId: 'N/A',
1829
- schemaName: 'N/A',
1830
- branchId: 'N/A',
1831
- branchName: 'N/A',
1832
- userId_: installIdOrUserId(),
1833
- cliAction: Avo.CliAction.CONFLICT,
1834
- cliInvokedByCi: invokedByCi(),
1835
- force: undefined,
1836
- forceFeatures: undefined,
1837
- });
1838
- throw error;
1839
- }),
1840
- })
1841
- .command({
1842
- command: 'edit',
1843
- desc: 'Open the Avo workspace in your browser',
1844
- handler: (argv) => {
1845
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1846
- .then((json) => {
1847
- Avo.cliInvoked({
1848
- schemaId: json.schema.id,
1849
- schemaName: json.schema.name,
1850
- branchId: json.branch.id,
1851
- branchName: json.branch.name,
1852
- userId_: installIdOrUserId(),
1853
- cliAction: Avo.CliAction.EDIT,
1854
- cliInvokedByCi: invokedByCi(),
1855
- force: undefined,
1856
- forceFeatures: undefined,
1857
- });
1858
- const { schema } = json;
1859
- const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
1860
- report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
1861
- open(schemaUrl);
2141
+ },
1862
2142
  })
1863
- .catch((error) => {
1864
- Avo.cliInvoked({
1865
- schemaId: 'N/A',
1866
- schemaName: 'N/A',
1867
- branchId: 'N/A',
1868
- branchName: 'N/A',
1869
- userId_: installIdOrUserId(),
1870
- cliAction: Avo.CliAction.EDIT,
1871
- cliInvokedByCi: invokedByCi(),
1872
- force: undefined,
1873
- forceFeatures: undefined,
1874
- });
1875
- throw error;
1876
- });
1877
- },
1878
- })
1879
- .command({
1880
- command: 'login',
1881
- desc: 'Log into the Avo platform',
1882
- handler: () => {
1883
- const command = () => {
1884
- const user = conf.get('user');
1885
- if (user) {
1886
- report.info(`Already logged in as ${email(user.email)}`);
1887
- return;
1888
- }
1889
- login()
1890
- .then((result) => {
1891
- conf.set('user', result.user);
1892
- conf.set('tokens', result.tokens);
1893
- Avo.signedIn({
1894
- userId_: result.user.user_id,
1895
- email: result.user.email,
1896
- authenticationMethod: Avo.AuthenticationMethod.CLI,
1897
- });
1898
- report.success(`Logged in as ${email(result.user.email)}`);
1899
- })
1900
- .catch(() => {
1901
- Avo.signInFailed({
1902
- userId_: conf.get('avo_install_id'),
1903
- emailInput: '', // XXX this is not passed back here
1904
- signInError: Avo.SignInError.UNKNOWN,
2143
+ .command({
2144
+ command: 'whoami',
2145
+ describe: 'Shows the currently logged in username',
2146
+ handler: (argv) => {
2147
+ const command = () => {
2148
+ requireAuth(argv, () => {
2149
+ if (conf.has('user')) {
2150
+ const user = conf.get('user');
2151
+ report.info(`Logged in as ${email(user.email)}`);
2152
+ }
2153
+ else {
2154
+ report.warn('Not logged in');
2155
+ }
2156
+ });
2157
+ };
2158
+ loadAvoJson()
2159
+ .then((json) => {
2160
+ Avo.cliInvoked({
2161
+ schemaId: json.schema.id,
2162
+ schemaName: json.schema.name,
2163
+ branchId: json.branch.id,
2164
+ branchName: json.branch.name,
2165
+ userId_: installIdOrUserId(),
2166
+ cliAction: Avo.CliAction.WHOAMI,
2167
+ cliInvokedByCi: invokedByCi(),
2168
+ force: undefined,
2169
+ forceFeatures: undefined,
2170
+ });
2171
+ command();
2172
+ })
2173
+ .catch(() => {
2174
+ Avo.cliInvoked({
2175
+ schemaId: 'N/A',
2176
+ schemaName: 'N/A',
2177
+ branchId: 'N/A',
2178
+ branchName: 'N/A',
2179
+ userId_: installIdOrUserId(),
2180
+ cliAction: Avo.CliAction.WHOAMI,
2181
+ cliInvokedByCi: invokedByCi(),
2182
+ force: undefined,
2183
+ forceFeatures: undefined,
2184
+ });
2185
+ command();
1905
2186
  });
1906
- });
1907
- };
1908
- loadAvoJson()
1909
- .then((json) => {
1910
- Avo.cliInvoked({
1911
- schemaId: json.schema.id,
1912
- schemaName: json.schema.name,
1913
- branchId: json.branch.id,
1914
- branchName: json.branch.name,
1915
- userId_: installIdOrUserId(),
1916
- cliAction: Avo.CliAction.LOGIN,
1917
- cliInvokedByCi: invokedByCi(),
1918
- force: undefined,
1919
- forceFeatures: undefined,
1920
- });
1921
- command();
2187
+ },
1922
2188
  })
1923
- .catch(() => {
1924
- Avo.cliInvoked({
1925
- schemaId: 'N/A',
1926
- schemaName: 'N/A',
1927
- branchId: 'N/A',
1928
- branchName: 'N/A',
1929
- userId_: installIdOrUserId(),
1930
- cliAction: Avo.CliAction.LOGIN,
1931
- cliInvokedByCi: invokedByCi(),
1932
- force: undefined,
1933
- forceFeatures: undefined,
1934
- });
1935
- command();
1936
- });
1937
- },
1938
- })
1939
- .command({
1940
- command: 'logout',
1941
- desc: 'Log out from the Avo platform',
1942
- handler: () => {
1943
- const command = () => {
1944
- const user = conf.get('user');
1945
- const tokens = conf.get('tokens');
1946
- const currentToken = tokens.refreshToken;
1947
- const token = currentToken;
1948
- api.setRefreshToken(token);
1949
- if (token) {
1950
- logout(token);
1951
- }
1952
- if (token || user || tokens) {
1953
- let msg = 'Logged out';
1954
- if (token === currentToken) {
1955
- if (user) {
1956
- msg += ` from ${bold(user.email)}`;
1957
- }
1958
- }
1959
- else {
1960
- msg += ` token "${bold(token)}"`;
1961
- }
1962
- report.log(msg);
2189
+ .demandCommand(1, 'must provide a valid command')
2190
+ .recommendCommands()
2191
+ .help().argv;
2192
+ }
2193
+ catch (err) {
2194
+ // Always log yargs errors for visibility (e.g., in tests)
2195
+ report.error(`yargs error: ${err instanceof Error ? err.message : String(err)}`);
2196
+ // Only exit if we're actually running as the main module
2197
+ if (isMainModule) {
2198
+ throw err;
2199
+ }
2200
+ }
2201
+ /// ///////////////// ////////
2202
+ // catch unhandled promises
2203
+ if (isMainModule) {
2204
+ process.on('unhandledRejection', (err) => {
2205
+ cancelWait();
2206
+ if (!(err instanceof Error) && !(err instanceof AvoError)) {
2207
+ report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
1963
2208
  }
1964
2209
  else {
1965
- report.log("No need to logout, you're not logged in");
2210
+ // @ts-ignore
2211
+ report.error(err.message);
1966
2212
  }
1967
- };
1968
- loadAvoJson()
1969
- .then((json) => {
1970
- Avo.cliInvoked({
1971
- schemaId: json.schema.id,
1972
- schemaName: json.schema.name,
1973
- branchId: json.branch.id,
1974
- branchName: json.branch.name,
1975
- userId_: installIdOrUserId(),
1976
- cliAction: Avo.CliAction.LOGOUT,
1977
- cliInvokedByCi: invokedByCi(),
1978
- force: undefined,
1979
- forceFeatures: undefined,
1980
- });
1981
- command();
1982
- })
1983
- .catch(() => {
1984
- Avo.cliInvoked({
1985
- schemaId: 'N/A',
1986
- schemaName: 'N/A',
1987
- branchId: 'N/A',
1988
- branchName: 'N/A',
1989
- userId_: installIdOrUserId(),
1990
- cliAction: Avo.CliAction.LOGOUT,
1991
- cliInvokedByCi: invokedByCi(),
1992
- force: undefined,
1993
- forceFeatures: undefined,
1994
- });
1995
- command();
2213
+ // @ts-ignore
2214
+ // console.error(err.stack);
2215
+ process.exit(1);
1996
2216
  });
1997
- },
1998
- })
1999
- .command({
2000
- command: 'whoami',
2001
- desc: 'Shows the currently logged in username',
2002
- handler: (argv) => {
2003
- const command = () => {
2004
- requireAuth(argv, () => {
2005
- if (conf.has('user')) {
2006
- const user = conf.get('user');
2007
- report.info(`Logged in as ${email(user.email)}`);
2008
- }
2009
- else {
2010
- report.warn('Not logged in');
2011
- }
2012
- });
2013
- };
2014
- loadAvoJson()
2015
- .then((json) => {
2016
- Avo.cliInvoked({
2017
- schemaId: json.schema.id,
2018
- schemaName: json.schema.name,
2019
- branchId: json.branch.id,
2020
- branchName: json.branch.name,
2021
- userId_: installIdOrUserId(),
2022
- cliAction: Avo.CliAction.WHOAMI,
2023
- cliInvokedByCi: invokedByCi(),
2024
- force: undefined,
2025
- forceFeatures: undefined,
2026
- });
2027
- command();
2028
- })
2029
- .catch(() => {
2030
- Avo.cliInvoked({
2031
- schemaId: 'N/A',
2032
- schemaName: 'N/A',
2033
- branchId: 'N/A',
2034
- branchName: 'N/A',
2035
- userId_: installIdOrUserId(),
2036
- cliAction: Avo.CliAction.WHOAMI,
2037
- cliInvokedByCi: invokedByCi(),
2038
- force: undefined,
2039
- forceFeatures: undefined,
2040
- });
2041
- command();
2042
- });
2043
- },
2044
- })
2045
- .demandCommand(1, 'must provide a valid command')
2046
- .recommendCommands()
2047
- .help().argv;
2048
- /// ///////////////// ////////
2049
- // catch unhandled promises
2050
- process.on('unhandledRejection', (err) => {
2051
- cancelWait();
2052
- if (!(err instanceof Error) && !(err instanceof AvoError)) {
2053
- report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
2054
- }
2055
- else {
2056
- // @ts-ignore
2057
- report.error(err.message);
2058
2217
  }
2059
- // @ts-ignore
2060
- // console.error(err.stack);
2061
- process.exit(1);
2062
- });
2218
+ }