avo 3.2.13 → 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 +754 -594
  3. package/package.json +14 -7
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.');
@@ -1104,7 +1224,7 @@ function status(source, json, argv) {
1104
1224
  .map((resultPath) => pify(fs.lstat)(resultPath)
1105
1225
  .then((stats) => {
1106
1226
  if (stats.isSymbolicLink()) {
1107
- return [];
1227
+ return null;
1108
1228
  }
1109
1229
  return pify(fs.readFile)(resultPath, 'utf8')
1110
1230
  .then((data) => [resultPath, data])
@@ -1112,13 +1232,17 @@ function status(source, json, argv) {
1112
1232
  if (argv.verbose) {
1113
1233
  report.warn(`Failed to parse file: ${resultPath}`);
1114
1234
  }
1235
+ return null;
1115
1236
  });
1116
1237
  })
1117
1238
  .catch(() => {
1118
1239
  if (argv.verbose) {
1119
1240
  report.warn(`Failed to read file stats: ${resultPath}`);
1120
1241
  }
1121
- }))).then((cachePairs) => Object.fromEntries(cachePairs)));
1242
+ return null;
1243
+ }))).then((cachePairs) =>
1244
+ // Filter out null values and create object from valid pairs
1245
+ Object.fromEntries(cachePairs.filter((pair) => pair != null))));
1122
1246
  fileCache
1123
1247
  .then((cache) => {
1124
1248
  sources = Promise.all(sources.map((source) => pify(fs.readFile)(source.path, 'utf8').then((data) => {
@@ -1353,190 +1477,380 @@ function logout(refreshToken) {
1353
1477
  function parseForceFeaturesParam(forceFeatures) {
1354
1478
  return forceFeatures?.split(',').map((it) => it.trim());
1355
1479
  }
1356
- yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1357
- .usage('$0 command')
1358
- .scriptName('avo')
1359
- .version(pkg.version)
1360
- .option('v', {
1361
- alias: 'verbose',
1362
- default: false,
1363
- describe: 'make output more verbose',
1364
- type: 'boolean',
1365
- })
1366
- .command({
1367
- command: 'track-install',
1368
- desc: false,
1369
- handler: async (argv) => {
1370
- try {
1371
- Avo.cliInstalled({
1372
- userId_: installIdOrUserId(),
1373
- cliInvokedByCi: invokedByCi(),
1374
- }).catch((error) => {
1375
- if (argv.verbose) {
1376
- 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
+ });
1377
1514
  }
1378
- });
1379
- }
1380
- catch (error) {
1381
- console.error('Unexpected error failed to track cli installed', error);
1382
- }
1383
- },
1384
- })
1385
- .command({
1386
- command: 'init',
1387
- desc: 'Initialize an Avo workspace in the current folder',
1388
- handler: (argv) => {
1389
- loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
1390
- .then((json) => {
1391
- 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) => {
1392
1638
  Avo.cliInvoked({
1393
1639
  schemaId: json.schema.id,
1394
1640
  schemaName: json.schema.name,
1395
1641
  branchId: json.branch.id,
1396
1642
  branchName: json.branch.name,
1397
1643
  userId_: installIdOrUserId(),
1398
- cliAction: Avo.CliAction.INIT,
1644
+ cliAction: Avo.CliAction.CHECKOUT,
1399
1645
  cliInvokedByCi: invokedByCi(),
1400
1646
  force: undefined,
1401
1647
  forceFeatures: undefined,
1402
1648
  });
1403
- report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
1404
- return Promise.resolve();
1405
- }
1406
- Avo.cliInvoked({
1407
- schemaId: 'N/A',
1408
- schemaName: 'N/A',
1409
- branchId: 'N/A',
1410
- branchName: 'N/A',
1411
- userId_: installIdOrUserId(),
1412
- cliAction: Avo.CliAction.INIT,
1413
- cliInvokedByCi: invokedByCi(),
1414
- force: undefined,
1415
- forceFeatures: undefined,
1416
- });
1417
- return requireAuth(argv, () => init()
1418
- .then(writeAvoJson)
1419
- .then(() => {
1420
- report.info("Run 'avo pull' to pull analytics wrappers from Avo");
1421
- }));
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
+ }),
1422
1666
  })
1423
- .catch(() => {
1424
- Avo.cliInvoked({
1425
- schemaId: 'N/A',
1426
- schemaName: 'N/A',
1427
- branchId: 'N/A',
1428
- branchName: 'N/A',
1429
- userId_: installIdOrUserId(),
1430
- cliAction: Avo.CliAction.INIT,
1431
- cliInvokedByCi: invokedByCi(),
1432
- force: undefined,
1433
- forceFeatures: undefined,
1434
- });
1435
- });
1436
- },
1437
- })
1438
- .command({
1439
- command: 'pull [source]',
1440
- desc: 'Pull analytics wrappers from Avo workspace',
1441
- builder: (yargs) => yargs
1442
- .option('branch', {
1443
- describe: 'Name of Avo branch to pull from',
1444
- type: 'string',
1445
- })
1446
- .option('f', {
1447
- alias: 'force',
1448
- describe: 'Proceed ignoring the unsupported features for given source',
1449
- default: false,
1450
- type: 'boolean',
1451
- })
1452
- .option('forceFeatures', {
1453
- describe: 'Optional comma separated list of features to force enable, pass unsupported name to get the list of available features',
1454
- default: undefined,
1455
- type: 'string',
1456
- }),
1457
- handler: (argv) => {
1458
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1459
- .then((json) => {
1460
- Avo.cliInvoked({
1461
- schemaId: json.schema.id,
1462
- schemaName: json.schema.name,
1463
- branchId: json.branch.id,
1464
- branchName: json.branch.name,
1465
- userId_: installIdOrUserId(),
1466
- cliAction: Avo.CliAction.PULL,
1467
- cliInvokedByCi: invokedByCi(),
1468
- force: argv.f === true,
1469
- forceFeatures: parseForceFeaturesParam(argv.forceFeatures),
1470
- });
1471
- requireAuth(argv, () => {
1472
- if (argv.branch && json.branch.name !== argv.branch) {
1473
- return checkout(argv.branch, json)
1474
- .then((data) => getSource(argv, data))
1475
- .then(([source, data]) => pull(source, data));
1476
- }
1477
- report.info(`Pulling from branch '${json.branch.name}'`);
1478
- return getSource(argv, json).then(([source, data]) => pull(source, data));
1479
- });
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
+ },
1480
1850
  })
1481
- .catch((error) => {
1482
- Avo.cliInvoked({
1483
- schemaId: 'N/A',
1484
- schemaName: 'N/A',
1485
- branchId: 'N/A',
1486
- branchName: 'N/A',
1487
- userId_: installIdOrUserId(),
1488
- cliAction: Avo.CliAction.PULL,
1489
- cliInvokedByCi: invokedByCi(),
1490
- force: undefined,
1491
- forceFeatures: undefined,
1492
- });
1493
- throw error;
1494
- });
1495
- },
1496
- })
1497
- .command({
1498
- command: 'checkout [branch]',
1499
- aliases: ['branch'],
1500
- desc: 'Switch branches',
1501
- handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1502
- .then((json) => {
1503
- Avo.cliInvoked({
1504
- schemaId: json.schema.id,
1505
- schemaName: json.schema.name,
1506
- branchId: json.branch.id,
1507
- branchName: json.branch.name,
1508
- userId_: installIdOrUserId(),
1509
- cliAction: Avo.CliAction.CHECKOUT,
1510
- cliInvokedByCi: invokedByCi(),
1511
- force: undefined,
1512
- forceFeatures: undefined,
1513
- });
1514
- report.info(`Currently on branch '${json.branch.name}'`);
1515
- requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
1516
- })
1517
- .catch((error) => {
1518
- Avo.cliInvoked({
1519
- schemaId: 'N/A',
1520
- schemaName: 'N/A',
1521
- branchId: 'N/A',
1522
- branchName: 'N/A',
1523
- userId_: installIdOrUserId(),
1524
- cliAction: Avo.CliAction.CHECKOUT,
1525
- cliInvokedByCi: invokedByCi(),
1526
- force: undefined,
1527
- forceFeatures: undefined,
1528
- });
1529
- throw error;
1530
- }),
1531
- })
1532
- .command({
1533
- command: 'source <command>',
1534
- desc: 'Manage sources for the current project',
1535
- builder: (yargs) => {
1536
- yargs
1537
1851
  .command({
1538
- command: '$0',
1539
- desc: 'List sources in this project',
1852
+ command: 'status [source]',
1853
+ describe: 'Show the status of the Avo implementation',
1540
1854
  handler: (argv) => {
1541
1855
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1542
1856
  .then((json) => {
@@ -1546,21 +1860,15 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1546
1860
  branchId: json.branch.id,
1547
1861
  branchName: json.branch.name,
1548
1862
  userId_: installIdOrUserId(),
1549
- cliAction: Avo.CliAction.SOURCE,
1863
+ cliAction: Avo.CliAction.STATUS,
1550
1864
  cliInvokedByCi: invokedByCi(),
1551
1865
  force: undefined,
1552
1866
  forceFeatures: undefined,
1553
1867
  });
1554
- if (!json.sources || !json.sources.length) {
1555
- report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1556
- return;
1557
- }
1558
- report.info('Sources in this project:');
1559
- report.tree('sources', json.sources.map((source) => ({
1560
- name: source.name,
1561
- children: [{ name: source.path }],
1562
- })));
1868
+ report.info(`Currently on branch '${json.branch.name}'`);
1869
+ return getSource(argv, json);
1563
1870
  })
1871
+ .then(([source, json]) => status(source, json, argv))
1564
1872
  .catch((error) => {
1565
1873
  Avo.cliInvoked({
1566
1874
  schemaId: 'N/A',
@@ -1568,7 +1876,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1568
1876
  branchId: 'N/A',
1569
1877
  branchName: 'N/A',
1570
1878
  userId_: installIdOrUserId(),
1571
- cliAction: Avo.CliAction.SOURCE,
1879
+ cliAction: Avo.CliAction.STATUS,
1572
1880
  cliInvokedByCi: invokedByCi(),
1573
1881
  force: undefined,
1574
1882
  forceFeatures: undefined,
@@ -1578,10 +1886,17 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1578
1886
  },
1579
1887
  })
1580
1888
  .command({
1581
- command: 'add [source]',
1582
- 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
+ }),
1583
1898
  handler: (argv) => {
1584
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1899
+ loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
1585
1900
  .then((json) => {
1586
1901
  Avo.cliInvoked({
1587
1902
  schemaId: json.schema.id,
@@ -1589,14 +1904,12 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1589
1904
  branchId: json.branch.id,
1590
1905
  branchName: json.branch.name,
1591
1906
  userId_: installIdOrUserId(),
1592
- cliAction: Avo.CliAction.SOURCE_ADD,
1907
+ cliAction: Avo.CliAction.MERGE,
1593
1908
  cliInvokedByCi: invokedByCi(),
1594
- force: undefined,
1909
+ force: json.force,
1595
1910
  forceFeatures: undefined,
1596
1911
  });
1597
- requireAuth(argv, () => {
1598
- selectSource(argv.source, json).then(writeAvoJson);
1599
- });
1912
+ return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
1600
1913
  })
1601
1914
  .catch((error) => {
1602
1915
  Avo.cliInvoked({
@@ -1605,7 +1918,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1605
1918
  branchId: 'N/A',
1606
1919
  branchName: 'N/A',
1607
1920
  userId_: installIdOrUserId(),
1608
- cliAction: Avo.CliAction.SOURCE_ADD,
1921
+ cliAction: Avo.CliAction.MERGE,
1609
1922
  cliInvokedByCi: invokedByCi(),
1610
1923
  force: undefined,
1611
1924
  forceFeatures: undefined,
@@ -1615,9 +1928,63 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1615
1928
  },
1616
1929
  })
1617
1930
  .command({
1618
- command: 'remove [source]',
1619
- aliases: ['rm'],
1620
- 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',
1621
1988
  handler: (argv) => {
1622
1989
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1623
1990
  .then((json) => {
@@ -1627,432 +1994,225 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1627
1994
  branchId: json.branch.id,
1628
1995
  branchName: json.branch.name,
1629
1996
  userId_: installIdOrUserId(),
1630
- cliAction: Avo.CliAction.SOURCE_REMOVE,
1997
+ cliAction: Avo.CliAction.EDIT,
1631
1998
  cliInvokedByCi: invokedByCi(),
1632
1999
  force: undefined,
1633
2000
  forceFeatures: undefined,
1634
2001
  });
1635
- if (!json.sources || !json.sources.length) {
1636
- report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
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,
2015
+ cliInvokedByCi: invokedByCi(),
2016
+ force: undefined,
2017
+ forceFeatures: undefined,
2018
+ });
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)}`);
1637
2031
  return;
1638
2032
  }
1639
- const getSourceToRemove = (argv, json) => {
1640
- if (argv.source) {
1641
- return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
1642
- }
1643
- const choices = json.sources.map((source) => ({
1644
- value: source,
1645
- name: source.name,
1646
- }));
1647
- return inquirer
1648
- .prompt({
1649
- type: 'list',
1650
- name: 'source',
1651
- message: 'Select a source to remove',
1652
- choices,
1653
- pageSize: 15,
1654
- })
1655
- .then((answer) => answer.source);
1656
- };
1657
- getSourceToRemove(argv, json).then((targetSource) => {
1658
- if (!targetSource) {
1659
- report.error(`Source ${argv.source} not found in project.`);
1660
- return Promise.resolve();
1661
- }
1662
- return inquirer
1663
- .prompt([
1664
- {
1665
- type: 'confirm',
1666
- name: 'remove',
1667
- default: true,
1668
- message: `Are you sure you want to remove source ${targetSource.name} from project`,
1669
- },
1670
- ])
1671
- .then((answer) => {
1672
- if (answer.remove) {
1673
- const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
1674
- const newJson = { ...json, sources };
1675
- return writeAvoJson(newJson).then(() => {
1676
- // XXX ask to remove file as well?
1677
- report.info(`Removed source ${targetSource.name} from project`);
1678
- });
1679
- }
1680
- report.info(`Did not remove source ${targetSource.name} from project`);
1681
- 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,
1682
2049
  });
1683
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();
1684
2066
  })
1685
- .catch((error) => {
2067
+ .catch(() => {
1686
2068
  Avo.cliInvoked({
1687
2069
  schemaId: 'N/A',
1688
2070
  schemaName: 'N/A',
1689
2071
  branchId: 'N/A',
1690
2072
  branchName: 'N/A',
1691
2073
  userId_: installIdOrUserId(),
1692
- cliAction: Avo.CliAction.SOURCE_REMOVE,
2074
+ cliAction: Avo.CliAction.LOGIN,
1693
2075
  cliInvokedByCi: invokedByCi(),
1694
2076
  force: undefined,
1695
2077
  forceFeatures: undefined,
1696
2078
  });
1697
- throw error;
2079
+ command();
1698
2080
  });
1699
2081
  },
1700
- });
1701
- },
1702
- })
1703
- .command({
1704
- command: 'status [source]',
1705
- desc: 'Show the status of the Avo implementation',
1706
- handler: (argv) => {
1707
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1708
- .then((json) => {
1709
- Avo.cliInvoked({
1710
- schemaId: json.schema.id,
1711
- schemaName: json.schema.name,
1712
- branchId: json.branch.id,
1713
- branchName: json.branch.name,
1714
- userId_: installIdOrUserId(),
1715
- cliAction: Avo.CliAction.STATUS,
1716
- cliInvokedByCi: invokedByCi(),
1717
- force: undefined,
1718
- forceFeatures: undefined,
1719
- });
1720
- report.info(`Currently on branch '${json.branch.name}'`);
1721
- return getSource(argv, json);
1722
2082
  })
1723
- .then(([source, json]) => status(source, json, argv))
1724
- .catch((error) => {
1725
- Avo.cliInvoked({
1726
- schemaId: 'N/A',
1727
- schemaName: 'N/A',
1728
- branchId: 'N/A',
1729
- branchName: 'N/A',
1730
- userId_: installIdOrUserId(),
1731
- cliAction: Avo.CliAction.STATUS,
1732
- cliInvokedByCi: invokedByCi(),
1733
- force: undefined,
1734
- forceFeatures: undefined,
1735
- });
1736
- throw error;
1737
- });
1738
- },
1739
- })
1740
- .command({
1741
- command: 'merge main',
1742
- aliases: ['merge master'],
1743
- desc: 'Pull the Avo main branch into your current branch',
1744
- builder: (yargs) => yargs.option('f', {
1745
- alias: 'force',
1746
- describe: 'Proceed with merge when incoming branch is open',
1747
- default: false,
1748
- type: 'boolean',
1749
- }),
1750
- handler: (argv) => {
1751
- loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
1752
- .then((json) => {
1753
- Avo.cliInvoked({
1754
- schemaId: json.schema.id,
1755
- schemaName: json.schema.name,
1756
- branchId: json.branch.id,
1757
- branchName: json.branch.name,
1758
- userId_: installIdOrUserId(),
1759
- cliAction: Avo.CliAction.MERGE,
1760
- cliInvokedByCi: invokedByCi(),
1761
- force: json.force,
1762
- forceFeatures: undefined,
1763
- });
1764
- return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
1765
- })
1766
- .catch((error) => {
1767
- Avo.cliInvoked({
1768
- schemaId: 'N/A',
1769
- schemaName: 'N/A',
1770
- branchId: 'N/A',
1771
- branchName: 'N/A',
1772
- userId_: installIdOrUserId(),
1773
- cliAction: Avo.CliAction.MERGE,
1774
- cliInvokedByCi: invokedByCi(),
1775
- force: undefined,
1776
- forceFeatures: undefined,
1777
- });
1778
- throw error;
1779
- });
1780
- },
1781
- })
1782
- .command({
1783
- command: 'conflict',
1784
- aliases: ['resolve', 'conflicts'],
1785
- desc: 'Resolve git conflicts in Avo files',
1786
- handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
1787
- .then((avoFile) => {
1788
- if (hasMergeConflicts(avoFile)) {
1789
- return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
1790
- argv,
1791
- skipPullMaster: false,
1792
- }).then((json) => {
1793
- Avo.cliInvoked({
1794
- schemaId: json.schema.id,
1795
- schemaName: json.schema.name,
1796
- branchId: json.branch.id,
1797
- branchName: json.branch.name,
1798
- userId_: installIdOrUserId(),
1799
- cliAction: Avo.CliAction.CONFLICT,
1800
- cliInvokedByCi: invokedByCi(),
1801
- force: undefined,
1802
- 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();
1803
2140
  });
1804
- pull(null, json);
1805
- }));
1806
- }
1807
- report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
1808
- const json = JSON.parse(avoFile);
1809
- Avo.cliInvoked({
1810
- schemaId: json.schema.id,
1811
- schemaName: json.schema.name,
1812
- branchId: json.branch.id,
1813
- branchName: json.branch.name,
1814
- userId_: installIdOrUserId(),
1815
- cliAction: Avo.CliAction.CONFLICT,
1816
- cliInvokedByCi: invokedByCi(),
1817
- force: undefined,
1818
- forceFeatures: undefined,
1819
- });
1820
- return Promise.resolve(json);
1821
- })
1822
- .catch((error) => {
1823
- Avo.cliInvoked({
1824
- schemaId: 'N/A',
1825
- schemaName: 'N/A',
1826
- branchId: 'N/A',
1827
- branchName: 'N/A',
1828
- userId_: installIdOrUserId(),
1829
- cliAction: Avo.CliAction.CONFLICT,
1830
- cliInvokedByCi: invokedByCi(),
1831
- force: undefined,
1832
- forceFeatures: undefined,
1833
- });
1834
- throw error;
1835
- }),
1836
- })
1837
- .command({
1838
- command: 'edit',
1839
- desc: 'Open the Avo workspace in your browser',
1840
- handler: (argv) => {
1841
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1842
- .then((json) => {
1843
- Avo.cliInvoked({
1844
- schemaId: json.schema.id,
1845
- schemaName: json.schema.name,
1846
- branchId: json.branch.id,
1847
- branchName: json.branch.name,
1848
- userId_: installIdOrUserId(),
1849
- cliAction: Avo.CliAction.EDIT,
1850
- cliInvokedByCi: invokedByCi(),
1851
- force: undefined,
1852
- forceFeatures: undefined,
1853
- });
1854
- const { schema } = json;
1855
- const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
1856
- report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
1857
- open(schemaUrl);
2141
+ },
1858
2142
  })
1859
- .catch((error) => {
1860
- Avo.cliInvoked({
1861
- schemaId: 'N/A',
1862
- schemaName: 'N/A',
1863
- branchId: 'N/A',
1864
- branchName: 'N/A',
1865
- userId_: installIdOrUserId(),
1866
- cliAction: Avo.CliAction.EDIT,
1867
- cliInvokedByCi: invokedByCi(),
1868
- force: undefined,
1869
- forceFeatures: undefined,
1870
- });
1871
- throw error;
1872
- });
1873
- },
1874
- })
1875
- .command({
1876
- command: 'login',
1877
- desc: 'Log into the Avo platform',
1878
- handler: () => {
1879
- const command = () => {
1880
- const user = conf.get('user');
1881
- if (user) {
1882
- report.info(`Already logged in as ${email(user.email)}`);
1883
- return;
1884
- }
1885
- login()
1886
- .then((result) => {
1887
- conf.set('user', result.user);
1888
- conf.set('tokens', result.tokens);
1889
- Avo.signedIn({
1890
- userId_: result.user.user_id,
1891
- email: result.user.email,
1892
- authenticationMethod: Avo.AuthenticationMethod.CLI,
1893
- });
1894
- report.success(`Logged in as ${email(result.user.email)}`);
1895
- })
1896
- .catch(() => {
1897
- Avo.signInFailed({
1898
- userId_: conf.get('avo_install_id'),
1899
- emailInput: '', // XXX this is not passed back here
1900
- 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();
1901
2186
  });
1902
- });
1903
- };
1904
- loadAvoJson()
1905
- .then((json) => {
1906
- Avo.cliInvoked({
1907
- schemaId: json.schema.id,
1908
- schemaName: json.schema.name,
1909
- branchId: json.branch.id,
1910
- branchName: json.branch.name,
1911
- userId_: installIdOrUserId(),
1912
- cliAction: Avo.CliAction.LOGIN,
1913
- cliInvokedByCi: invokedByCi(),
1914
- force: undefined,
1915
- forceFeatures: undefined,
1916
- });
1917
- command();
2187
+ },
1918
2188
  })
1919
- .catch(() => {
1920
- Avo.cliInvoked({
1921
- schemaId: 'N/A',
1922
- schemaName: 'N/A',
1923
- branchId: 'N/A',
1924
- branchName: 'N/A',
1925
- userId_: installIdOrUserId(),
1926
- cliAction: Avo.CliAction.LOGIN,
1927
- cliInvokedByCi: invokedByCi(),
1928
- force: undefined,
1929
- forceFeatures: undefined,
1930
- });
1931
- command();
1932
- });
1933
- },
1934
- })
1935
- .command({
1936
- command: 'logout',
1937
- desc: 'Log out from the Avo platform',
1938
- handler: () => {
1939
- const command = () => {
1940
- const user = conf.get('user');
1941
- const tokens = conf.get('tokens');
1942
- const currentToken = tokens.refreshToken;
1943
- const token = currentToken;
1944
- api.setRefreshToken(token);
1945
- if (token) {
1946
- logout(token);
1947
- }
1948
- if (token || user || tokens) {
1949
- let msg = 'Logged out';
1950
- if (token === currentToken) {
1951
- if (user) {
1952
- msg += ` from ${bold(user.email)}`;
1953
- }
1954
- }
1955
- else {
1956
- msg += ` token "${bold(token)}"`;
1957
- }
1958
- 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)}`));
1959
2208
  }
1960
2209
  else {
1961
- report.log("No need to logout, you're not logged in");
2210
+ // @ts-ignore
2211
+ report.error(err.message);
1962
2212
  }
1963
- };
1964
- loadAvoJson()
1965
- .then((json) => {
1966
- Avo.cliInvoked({
1967
- schemaId: json.schema.id,
1968
- schemaName: json.schema.name,
1969
- branchId: json.branch.id,
1970
- branchName: json.branch.name,
1971
- userId_: installIdOrUserId(),
1972
- cliAction: Avo.CliAction.LOGOUT,
1973
- cliInvokedByCi: invokedByCi(),
1974
- force: undefined,
1975
- forceFeatures: undefined,
1976
- });
1977
- command();
1978
- })
1979
- .catch(() => {
1980
- Avo.cliInvoked({
1981
- schemaId: 'N/A',
1982
- schemaName: 'N/A',
1983
- branchId: 'N/A',
1984
- branchName: 'N/A',
1985
- userId_: installIdOrUserId(),
1986
- cliAction: Avo.CliAction.LOGOUT,
1987
- cliInvokedByCi: invokedByCi(),
1988
- force: undefined,
1989
- forceFeatures: undefined,
1990
- });
1991
- command();
2213
+ // @ts-ignore
2214
+ // console.error(err.stack);
2215
+ process.exit(1);
1992
2216
  });
1993
- },
1994
- })
1995
- .command({
1996
- command: 'whoami',
1997
- desc: 'Shows the currently logged in username',
1998
- handler: (argv) => {
1999
- const command = () => {
2000
- requireAuth(argv, () => {
2001
- if (conf.has('user')) {
2002
- const user = conf.get('user');
2003
- report.info(`Logged in as ${email(user.email)}`);
2004
- }
2005
- else {
2006
- report.warn('Not logged in');
2007
- }
2008
- });
2009
- };
2010
- loadAvoJson()
2011
- .then((json) => {
2012
- Avo.cliInvoked({
2013
- schemaId: json.schema.id,
2014
- schemaName: json.schema.name,
2015
- branchId: json.branch.id,
2016
- branchName: json.branch.name,
2017
- userId_: installIdOrUserId(),
2018
- cliAction: Avo.CliAction.WHOAMI,
2019
- cliInvokedByCi: invokedByCi(),
2020
- force: undefined,
2021
- forceFeatures: undefined,
2022
- });
2023
- command();
2024
- })
2025
- .catch(() => {
2026
- Avo.cliInvoked({
2027
- schemaId: 'N/A',
2028
- schemaName: 'N/A',
2029
- branchId: 'N/A',
2030
- branchName: 'N/A',
2031
- userId_: installIdOrUserId(),
2032
- cliAction: Avo.CliAction.WHOAMI,
2033
- cliInvokedByCi: invokedByCi(),
2034
- force: undefined,
2035
- forceFeatures: undefined,
2036
- });
2037
- command();
2038
- });
2039
- },
2040
- })
2041
- .demandCommand(1, 'must provide a valid command')
2042
- .recommendCommands()
2043
- .help().argv;
2044
- /// ///////////////// ////////
2045
- // catch unhandled promises
2046
- process.on('unhandledRejection', (err) => {
2047
- cancelWait();
2048
- if (!(err instanceof Error) && !(err instanceof AvoError)) {
2049
- report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
2050
- }
2051
- else {
2052
- // @ts-ignore
2053
- report.error(err.message);
2054
2217
  }
2055
- // @ts-ignore
2056
- // console.error(err.stack);
2057
- process.exit(1);
2058
- });
2218
+ }