avo 3.2.14 → 3.3.0-alpha.2

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 +765 -593
  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.');
@@ -1161,10 +1281,12 @@ function status(source, json, argv) {
1161
1281
  if (argv.verbose) {
1162
1282
  console.log('Looking in files with extensions:', sourcePathExts);
1163
1283
  }
1284
+ const eventsDir = getEventsDirectoryPath(source.path, moduleName);
1164
1285
  const globs = [
1165
1286
  new Minimatch(source.analysis?.glob ??
1166
1287
  `**/*.+(${sourcePathExts.join('|')})`, {}),
1167
1288
  new Minimatch(`!${source.path}`, {}),
1289
+ new Minimatch(`!${eventsDir}/**`, {}),
1168
1290
  ];
1169
1291
  const lookup = {};
1170
1292
  Object.entries(cache).forEach(([cachePath, value]) => {
@@ -1181,8 +1303,22 @@ function status(source, json, argv) {
1181
1303
  report.info(`Looking in module: ${moduleName}`);
1182
1304
  report.info(`Looking in files: ${combinedPaths.join('\n')}`);
1183
1305
  }
1306
+ // Check if file-per-event mode is active
1307
+ const isFilePerEvent = isFilePerEventMode(source.path, moduleName);
1184
1308
  return Promise.all(eventMap.map((eventName) => {
1185
- const re = new RegExp(`(${moduleName}\\.${eventName}|\\[${moduleName} ${eventName})`);
1309
+ // Build regex pattern based on mode
1310
+ // In file-per-event mode, events can be called without module prefix
1311
+ let pattern;
1312
+ if (isFilePerEvent) {
1313
+ // File-per-event mode: support both with and without module prefix
1314
+ // Pattern matches: Module.eventName, [Module eventName, eventName(, [eventName
1315
+ pattern = `(${moduleName}\\.${eventName}|\\[${moduleName} ${eventName}|${eventName}\\(|\\[${eventName})`;
1316
+ }
1317
+ else {
1318
+ // Legacy mode: only match with module prefix
1319
+ pattern = `(${moduleName}\\.${eventName}|\\[${moduleName} ${eventName})`;
1320
+ }
1321
+ const re = new RegExp(pattern);
1186
1322
  const results = Object.entries(lookup)
1187
1323
  .map(([path, data]) => {
1188
1324
  const results = findMatches(data, re);
@@ -1357,190 +1493,380 @@ function logout(refreshToken) {
1357
1493
  function parseForceFeaturesParam(forceFeatures) {
1358
1494
  return forceFeatures?.split(',').map((it) => it.trim());
1359
1495
  }
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);
1496
+ // Only execute yargs CLI if this file is run directly (not imported for testing)
1497
+ // Skip execution if we're in a test environment
1498
+ // Check multiple indicators that we're running under Jest
1499
+ const isMainModule = !process.env.AVO_TEST_MODE &&
1500
+ !process.env.JEST_WORKER_ID &&
1501
+ typeof jest === 'undefined' &&
1502
+ !process.argv.some((arg) => arg.includes('jest')) &&
1503
+ !process.argv.some((arg) => arg.includes('jest.js')) &&
1504
+ process.argv[1]?.endsWith('cli.js');
1505
+ if (isMainModule) {
1506
+ try {
1507
+ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1508
+ .usage('$0 command')
1509
+ .scriptName('avo')
1510
+ .version(pkg.version)
1511
+ .option('v', {
1512
+ alias: 'verbose',
1513
+ default: false,
1514
+ describe: 'make output more verbose',
1515
+ type: 'boolean',
1516
+ })
1517
+ .command({
1518
+ command: 'track-install',
1519
+ describe: false,
1520
+ handler: async (argv) => {
1521
+ try {
1522
+ Avo.cliInstalled({
1523
+ userId_: installIdOrUserId(),
1524
+ cliInvokedByCi: invokedByCi(),
1525
+ }).catch((error) => {
1526
+ if (argv.verbose) {
1527
+ console.error('Request to track cli installed failed', error);
1528
+ }
1529
+ });
1381
1530
  }
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) {
1531
+ catch (error) {
1532
+ console.error('Unexpected error failed to track cli installed', error);
1533
+ }
1534
+ },
1535
+ })
1536
+ .command({
1537
+ command: 'init',
1538
+ describe: 'Initialize an Avo workspace in the current folder',
1539
+ handler: (argv) => {
1540
+ loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
1541
+ .then((json) => {
1542
+ if (json) {
1543
+ Avo.cliInvoked({
1544
+ schemaId: json.schema.id,
1545
+ schemaName: json.schema.name,
1546
+ branchId: json.branch.id,
1547
+ branchName: json.branch.name,
1548
+ userId_: installIdOrUserId(),
1549
+ cliAction: Avo.CliAction.INIT,
1550
+ cliInvokedByCi: invokedByCi(),
1551
+ force: undefined,
1552
+ forceFeatures: undefined,
1553
+ });
1554
+ report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
1555
+ return Promise.resolve();
1556
+ }
1557
+ Avo.cliInvoked({
1558
+ schemaId: 'N/A',
1559
+ schemaName: 'N/A',
1560
+ branchId: 'N/A',
1561
+ branchName: 'N/A',
1562
+ userId_: installIdOrUserId(),
1563
+ cliAction: Avo.CliAction.INIT,
1564
+ cliInvokedByCi: invokedByCi(),
1565
+ force: undefined,
1566
+ forceFeatures: undefined,
1567
+ });
1568
+ return requireAuth(argv, () => init()
1569
+ .then(writeAvoJson)
1570
+ .then(() => {
1571
+ report.info("Run 'avo pull' to pull analytics wrappers from Avo");
1572
+ }));
1573
+ })
1574
+ .catch(() => {
1575
+ Avo.cliInvoked({
1576
+ schemaId: 'N/A',
1577
+ schemaName: 'N/A',
1578
+ branchId: 'N/A',
1579
+ branchName: 'N/A',
1580
+ userId_: installIdOrUserId(),
1581
+ cliAction: Avo.CliAction.INIT,
1582
+ cliInvokedByCi: invokedByCi(),
1583
+ force: undefined,
1584
+ forceFeatures: undefined,
1585
+ });
1586
+ });
1587
+ },
1588
+ })
1589
+ .command({
1590
+ command: 'pull [source]',
1591
+ describe: 'Pull analytics wrappers from Avo workspace',
1592
+ builder: (yargs) => yargs
1593
+ .option('branch', {
1594
+ describe: 'Name of Avo branch to pull from',
1595
+ type: 'string',
1596
+ })
1597
+ .option('f', {
1598
+ alias: 'force',
1599
+ describe: 'Proceed ignoring the unsupported features for given source',
1600
+ default: false,
1601
+ type: 'boolean',
1602
+ })
1603
+ .option('forceFeatures', {
1604
+ describe: 'Optional comma separated list of features to force enable, pass unsupported name to get the list of available features',
1605
+ default: undefined,
1606
+ type: 'string',
1607
+ }),
1608
+ handler: (argv) => {
1609
+ loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1610
+ .then((json) => {
1611
+ Avo.cliInvoked({
1612
+ schemaId: json.schema.id,
1613
+ schemaName: json.schema.name,
1614
+ branchId: json.branch.id,
1615
+ branchName: json.branch.name,
1616
+ userId_: installIdOrUserId(),
1617
+ cliAction: Avo.CliAction.PULL,
1618
+ cliInvokedByCi: invokedByCi(),
1619
+ force: argv.f === true,
1620
+ forceFeatures: parseForceFeaturesParam(argv.forceFeatures),
1621
+ });
1622
+ requireAuth(argv, () => {
1623
+ if (argv.branch && json.branch.name !== argv.branch) {
1624
+ return checkout(argv.branch, json)
1625
+ .then((data) => getSource(argv, data))
1626
+ .then(([source, data]) => pull(source, data));
1627
+ }
1628
+ report.info(`Pulling from branch '${json.branch.name}'`);
1629
+ return getSource(argv, json).then(([source, data]) => pull(source, data));
1630
+ });
1631
+ })
1632
+ .catch((error) => {
1633
+ Avo.cliInvoked({
1634
+ schemaId: 'N/A',
1635
+ schemaName: 'N/A',
1636
+ branchId: 'N/A',
1637
+ branchName: 'N/A',
1638
+ userId_: installIdOrUserId(),
1639
+ cliAction: Avo.CliAction.PULL,
1640
+ cliInvokedByCi: invokedByCi(),
1641
+ force: undefined,
1642
+ forceFeatures: undefined,
1643
+ });
1644
+ throw error;
1645
+ });
1646
+ },
1647
+ })
1648
+ .command({
1649
+ command: 'checkout [branch]',
1650
+ aliases: ['branch'],
1651
+ describe: 'Switch branches',
1652
+ handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1653
+ .then((json) => {
1396
1654
  Avo.cliInvoked({
1397
1655
  schemaId: json.schema.id,
1398
1656
  schemaName: json.schema.name,
1399
1657
  branchId: json.branch.id,
1400
1658
  branchName: json.branch.name,
1401
1659
  userId_: installIdOrUserId(),
1402
- cliAction: Avo.CliAction.INIT,
1660
+ cliAction: Avo.CliAction.CHECKOUT,
1403
1661
  cliInvokedByCi: invokedByCi(),
1404
1662
  force: undefined,
1405
1663
  forceFeatures: undefined,
1406
1664
  });
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
- }));
1665
+ report.info(`Currently on branch '${json.branch.name}'`);
1666
+ requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
1667
+ })
1668
+ .catch((error) => {
1669
+ Avo.cliInvoked({
1670
+ schemaId: 'N/A',
1671
+ schemaName: 'N/A',
1672
+ branchId: 'N/A',
1673
+ branchName: 'N/A',
1674
+ userId_: installIdOrUserId(),
1675
+ cliAction: Avo.CliAction.CHECKOUT,
1676
+ cliInvokedByCi: invokedByCi(),
1677
+ force: undefined,
1678
+ forceFeatures: undefined,
1679
+ });
1680
+ throw error;
1681
+ }),
1426
1682
  })
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
- });
1683
+ .command({
1684
+ command: 'source <command>',
1685
+ describe: 'Manage sources for the current project',
1686
+ builder: (yargs) => yargs
1687
+ .command({
1688
+ command: '$0',
1689
+ describe: 'List sources in this project',
1690
+ handler: (argv) => {
1691
+ loadAvoJsonOrInit({
1692
+ argv,
1693
+ skipInit: false,
1694
+ skipPullMaster: false,
1695
+ })
1696
+ .then((json) => {
1697
+ Avo.cliInvoked({
1698
+ schemaId: json.schema.id,
1699
+ schemaName: json.schema.name,
1700
+ branchId: json.branch.id,
1701
+ branchName: json.branch.name,
1702
+ userId_: installIdOrUserId(),
1703
+ cliAction: Avo.CliAction.SOURCE,
1704
+ cliInvokedByCi: invokedByCi(),
1705
+ force: undefined,
1706
+ forceFeatures: undefined,
1707
+ });
1708
+ if (!json.sources || !json.sources.length) {
1709
+ report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1710
+ return;
1711
+ }
1712
+ report.info('Sources in this project:');
1713
+ report.tree('sources', json.sources.map((source) => ({
1714
+ name: source.name,
1715
+ children: [{ name: source.path }],
1716
+ })));
1717
+ })
1718
+ .catch((error) => {
1719
+ Avo.cliInvoked({
1720
+ schemaId: 'N/A',
1721
+ schemaName: 'N/A',
1722
+ branchId: 'N/A',
1723
+ branchName: 'N/A',
1724
+ userId_: installIdOrUserId(),
1725
+ cliAction: Avo.CliAction.SOURCE,
1726
+ cliInvokedByCi: invokedByCi(),
1727
+ force: undefined,
1728
+ forceFeatures: undefined,
1729
+ });
1730
+ throw error;
1731
+ });
1732
+ },
1733
+ })
1734
+ .command({
1735
+ command: 'add [source]',
1736
+ describe: 'Add a source to this project',
1737
+ handler: (argv) => {
1738
+ loadAvoJsonOrInit({
1739
+ argv,
1740
+ skipInit: false,
1741
+ skipPullMaster: false,
1742
+ })
1743
+ .then((json) => {
1744
+ Avo.cliInvoked({
1745
+ schemaId: json.schema.id,
1746
+ schemaName: json.schema.name,
1747
+ branchId: json.branch.id,
1748
+ branchName: json.branch.name,
1749
+ userId_: installIdOrUserId(),
1750
+ cliAction: Avo.CliAction.SOURCE_ADD,
1751
+ cliInvokedByCi: invokedByCi(),
1752
+ force: undefined,
1753
+ forceFeatures: undefined,
1754
+ });
1755
+ requireAuth(argv, () => {
1756
+ selectSource(argv.source, json).then(writeAvoJson);
1757
+ });
1758
+ })
1759
+ .catch((error) => {
1760
+ Avo.cliInvoked({
1761
+ schemaId: 'N/A',
1762
+ schemaName: 'N/A',
1763
+ branchId: 'N/A',
1764
+ branchName: 'N/A',
1765
+ userId_: installIdOrUserId(),
1766
+ cliAction: Avo.CliAction.SOURCE_ADD,
1767
+ cliInvokedByCi: invokedByCi(),
1768
+ force: undefined,
1769
+ forceFeatures: undefined,
1770
+ });
1771
+ throw error;
1772
+ });
1773
+ },
1774
+ })
1775
+ .command({
1776
+ command: 'remove [source]',
1777
+ aliases: ['rm'],
1778
+ describe: 'Remove a source from this project',
1779
+ handler: (argv) => {
1780
+ loadAvoJsonOrInit({
1781
+ argv,
1782
+ skipInit: false,
1783
+ skipPullMaster: false,
1784
+ })
1785
+ .then((json) => {
1786
+ Avo.cliInvoked({
1787
+ schemaId: json.schema.id,
1788
+ schemaName: json.schema.name,
1789
+ branchId: json.branch.id,
1790
+ branchName: json.branch.name,
1791
+ userId_: installIdOrUserId(),
1792
+ cliAction: Avo.CliAction.SOURCE_REMOVE,
1793
+ cliInvokedByCi: invokedByCi(),
1794
+ force: undefined,
1795
+ forceFeatures: undefined,
1796
+ });
1797
+ if (!json.sources || !json.sources.length) {
1798
+ report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
1799
+ return;
1800
+ }
1801
+ const getSourceToRemove = (argv, json) => {
1802
+ if (argv.source) {
1803
+ return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
1804
+ }
1805
+ const choices = json.sources.map((source) => ({
1806
+ value: source,
1807
+ name: source.name,
1808
+ }));
1809
+ return inquirer
1810
+ .prompt({
1811
+ type: 'list',
1812
+ name: 'source',
1813
+ message: 'Select a source to remove',
1814
+ choices,
1815
+ pageSize: 15,
1816
+ })
1817
+ .then((answer) => answer.source);
1818
+ };
1819
+ getSourceToRemove(argv, json).then((targetSource) => {
1820
+ if (!targetSource) {
1821
+ report.error(`Source ${argv.source} not found in project.`);
1822
+ return Promise.resolve();
1823
+ }
1824
+ return inquirer
1825
+ .prompt([
1826
+ {
1827
+ type: 'confirm',
1828
+ name: 'remove',
1829
+ default: true,
1830
+ message: `Are you sure you want to remove source ${targetSource.name} from project`,
1831
+ },
1832
+ ])
1833
+ .then((answer) => {
1834
+ if (answer.remove) {
1835
+ const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
1836
+ const newJson = { ...json, sources };
1837
+ return writeAvoJson(newJson).then(() => {
1838
+ // XXX ask to remove file as well?
1839
+ report.info(`Removed source ${targetSource.name} from project`);
1840
+ });
1841
+ }
1842
+ report.info(`Did not remove source ${targetSource.name} from project`);
1843
+ return Promise.resolve();
1844
+ });
1845
+ });
1846
+ })
1847
+ .catch((error) => {
1848
+ Avo.cliInvoked({
1849
+ schemaId: 'N/A',
1850
+ schemaName: 'N/A',
1851
+ branchId: 'N/A',
1852
+ branchName: 'N/A',
1853
+ userId_: installIdOrUserId(),
1854
+ cliAction: Avo.CliAction.SOURCE_REMOVE,
1855
+ cliInvokedByCi: invokedByCi(),
1856
+ force: undefined,
1857
+ forceFeatures: undefined,
1858
+ });
1859
+ throw error;
1860
+ });
1861
+ },
1862
+ }),
1863
+ handler: () => {
1864
+ // Parent command - subcommands handle the actual logic
1865
+ },
1484
1866
  })
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
1867
  .command({
1542
- command: '$0',
1543
- desc: 'List sources in this project',
1868
+ command: 'status [source]',
1869
+ describe: 'Show the status of the Avo implementation',
1544
1870
  handler: (argv) => {
1545
1871
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1546
1872
  .then((json) => {
@@ -1550,21 +1876,15 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1550
1876
  branchId: json.branch.id,
1551
1877
  branchName: json.branch.name,
1552
1878
  userId_: installIdOrUserId(),
1553
- cliAction: Avo.CliAction.SOURCE,
1879
+ cliAction: Avo.CliAction.STATUS,
1554
1880
  cliInvokedByCi: invokedByCi(),
1555
1881
  force: undefined,
1556
1882
  forceFeatures: undefined,
1557
1883
  });
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
- })));
1884
+ report.info(`Currently on branch '${json.branch.name}'`);
1885
+ return getSource(argv, json);
1567
1886
  })
1887
+ .then(([source, json]) => status(source, json, argv))
1568
1888
  .catch((error) => {
1569
1889
  Avo.cliInvoked({
1570
1890
  schemaId: 'N/A',
@@ -1572,7 +1892,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1572
1892
  branchId: 'N/A',
1573
1893
  branchName: 'N/A',
1574
1894
  userId_: installIdOrUserId(),
1575
- cliAction: Avo.CliAction.SOURCE,
1895
+ cliAction: Avo.CliAction.STATUS,
1576
1896
  cliInvokedByCi: invokedByCi(),
1577
1897
  force: undefined,
1578
1898
  forceFeatures: undefined,
@@ -1582,10 +1902,17 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1582
1902
  },
1583
1903
  })
1584
1904
  .command({
1585
- command: 'add [source]',
1586
- desc: 'Add a source to this project',
1905
+ command: 'merge main',
1906
+ aliases: ['merge master'],
1907
+ describe: 'Pull the Avo main branch into your current branch',
1908
+ builder: (yargs) => yargs.option('f', {
1909
+ alias: 'force',
1910
+ describe: 'Proceed with merge when incoming branch is open',
1911
+ default: false,
1912
+ type: 'boolean',
1913
+ }),
1587
1914
  handler: (argv) => {
1588
- loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1915
+ loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
1589
1916
  .then((json) => {
1590
1917
  Avo.cliInvoked({
1591
1918
  schemaId: json.schema.id,
@@ -1593,14 +1920,12 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1593
1920
  branchId: json.branch.id,
1594
1921
  branchName: json.branch.name,
1595
1922
  userId_: installIdOrUserId(),
1596
- cliAction: Avo.CliAction.SOURCE_ADD,
1923
+ cliAction: Avo.CliAction.MERGE,
1597
1924
  cliInvokedByCi: invokedByCi(),
1598
- force: undefined,
1925
+ force: json.force,
1599
1926
  forceFeatures: undefined,
1600
1927
  });
1601
- requireAuth(argv, () => {
1602
- selectSource(argv.source, json).then(writeAvoJson);
1603
- });
1928
+ return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
1604
1929
  })
1605
1930
  .catch((error) => {
1606
1931
  Avo.cliInvoked({
@@ -1609,7 +1934,7 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1609
1934
  branchId: 'N/A',
1610
1935
  branchName: 'N/A',
1611
1936
  userId_: installIdOrUserId(),
1612
- cliAction: Avo.CliAction.SOURCE_ADD,
1937
+ cliAction: Avo.CliAction.MERGE,
1613
1938
  cliInvokedByCi: invokedByCi(),
1614
1939
  force: undefined,
1615
1940
  forceFeatures: undefined,
@@ -1619,9 +1944,63 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1619
1944
  },
1620
1945
  })
1621
1946
  .command({
1622
- command: 'remove [source]',
1623
- aliases: ['rm'],
1624
- desc: 'Remove a source from this project',
1947
+ command: 'conflict',
1948
+ aliases: ['resolve', 'conflicts'],
1949
+ describe: 'Resolve git conflicts in Avo files',
1950
+ handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
1951
+ .then((avoFile) => {
1952
+ if (hasMergeConflicts(avoFile)) {
1953
+ return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
1954
+ argv,
1955
+ skipPullMaster: false,
1956
+ }).then((json) => {
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
+ pull(null, json);
1969
+ }));
1970
+ }
1971
+ report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
1972
+ const json = JSON.parse(avoFile);
1973
+ Avo.cliInvoked({
1974
+ schemaId: json.schema.id,
1975
+ schemaName: json.schema.name,
1976
+ branchId: json.branch.id,
1977
+ branchName: json.branch.name,
1978
+ userId_: installIdOrUserId(),
1979
+ cliAction: Avo.CliAction.CONFLICT,
1980
+ cliInvokedByCi: invokedByCi(),
1981
+ force: undefined,
1982
+ forceFeatures: undefined,
1983
+ });
1984
+ return Promise.resolve(json);
1985
+ })
1986
+ .catch((error) => {
1987
+ Avo.cliInvoked({
1988
+ schemaId: 'N/A',
1989
+ schemaName: 'N/A',
1990
+ branchId: 'N/A',
1991
+ branchName: 'N/A',
1992
+ userId_: installIdOrUserId(),
1993
+ cliAction: Avo.CliAction.CONFLICT,
1994
+ cliInvokedByCi: invokedByCi(),
1995
+ force: undefined,
1996
+ forceFeatures: undefined,
1997
+ });
1998
+ throw error;
1999
+ }),
2000
+ })
2001
+ .command({
2002
+ command: 'edit',
2003
+ describe: 'Open the Avo workspace in your browser',
1625
2004
  handler: (argv) => {
1626
2005
  loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1627
2006
  .then((json) => {
@@ -1631,432 +2010,225 @@ yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
1631
2010
  branchId: json.branch.id,
1632
2011
  branchName: json.branch.name,
1633
2012
  userId_: installIdOrUserId(),
1634
- cliAction: Avo.CliAction.SOURCE_REMOVE,
2013
+ cliAction: Avo.CliAction.EDIT,
2014
+ cliInvokedByCi: invokedByCi(),
2015
+ force: undefined,
2016
+ forceFeatures: undefined,
2017
+ });
2018
+ const { schema } = json;
2019
+ const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
2020
+ report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
2021
+ open(schemaUrl);
2022
+ })
2023
+ .catch((error) => {
2024
+ Avo.cliInvoked({
2025
+ schemaId: 'N/A',
2026
+ schemaName: 'N/A',
2027
+ branchId: 'N/A',
2028
+ branchName: 'N/A',
2029
+ userId_: installIdOrUserId(),
2030
+ cliAction: Avo.CliAction.EDIT,
1635
2031
  cliInvokedByCi: invokedByCi(),
1636
2032
  force: undefined,
1637
2033
  forceFeatures: undefined,
1638
2034
  });
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`);
2035
+ throw error;
2036
+ });
2037
+ },
2038
+ })
2039
+ .command({
2040
+ command: 'login',
2041
+ describe: 'Log into the Avo platform',
2042
+ handler: () => {
2043
+ const command = () => {
2044
+ const user = conf.get('user');
2045
+ if (user) {
2046
+ report.info(`Already logged in as ${email(user.email)}`);
1641
2047
  return;
1642
2048
  }
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();
2049
+ login()
2050
+ .then((result) => {
2051
+ conf.set('user', result.user);
2052
+ conf.set('tokens', result.tokens);
2053
+ Avo.signedIn({
2054
+ userId_: result.user.user_id,
2055
+ email: result.user.email,
2056
+ authenticationMethod: Avo.AuthenticationMethod.CLI,
2057
+ });
2058
+ report.success(`Logged in as ${email(result.user.email)}`);
2059
+ })
2060
+ .catch(() => {
2061
+ Avo.signInFailed({
2062
+ userId_: conf.get('avo_install_id'),
2063
+ emailInput: '', // XXX this is not passed back here
2064
+ signInError: Avo.SignInError.UNKNOWN,
1686
2065
  });
1687
2066
  });
2067
+ };
2068
+ loadAvoJson()
2069
+ .then((json) => {
2070
+ Avo.cliInvoked({
2071
+ schemaId: json.schema.id,
2072
+ schemaName: json.schema.name,
2073
+ branchId: json.branch.id,
2074
+ branchName: json.branch.name,
2075
+ userId_: installIdOrUserId(),
2076
+ cliAction: Avo.CliAction.LOGIN,
2077
+ cliInvokedByCi: invokedByCi(),
2078
+ force: undefined,
2079
+ forceFeatures: undefined,
2080
+ });
2081
+ command();
1688
2082
  })
1689
- .catch((error) => {
2083
+ .catch(() => {
1690
2084
  Avo.cliInvoked({
1691
2085
  schemaId: 'N/A',
1692
2086
  schemaName: 'N/A',
1693
2087
  branchId: 'N/A',
1694
2088
  branchName: 'N/A',
1695
2089
  userId_: installIdOrUserId(),
1696
- cliAction: Avo.CliAction.SOURCE_REMOVE,
2090
+ cliAction: Avo.CliAction.LOGIN,
1697
2091
  cliInvokedByCi: invokedByCi(),
1698
2092
  force: undefined,
1699
2093
  forceFeatures: undefined,
1700
2094
  });
1701
- throw error;
2095
+ command();
1702
2096
  });
1703
2097
  },
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
2098
  })
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,
2099
+ .command({
2100
+ command: 'logout',
2101
+ describe: 'Log out from the Avo platform',
2102
+ handler: () => {
2103
+ const command = () => {
2104
+ const user = conf.get('user');
2105
+ const tokens = conf.get('tokens');
2106
+ const currentToken = tokens.refreshToken;
2107
+ const token = currentToken;
2108
+ api.setRefreshToken(token);
2109
+ if (token) {
2110
+ logout(token);
2111
+ }
2112
+ if (token || user || tokens) {
2113
+ let msg = 'Logged out';
2114
+ if (token === currentToken) {
2115
+ if (user) {
2116
+ msg += ` from ${bold(user.email)}`;
2117
+ }
2118
+ }
2119
+ else {
2120
+ msg += ` token "${bold(token)}"`;
2121
+ }
2122
+ report.log(msg);
2123
+ }
2124
+ else {
2125
+ report.log("No need to logout, you're not logged in");
2126
+ }
2127
+ };
2128
+ loadAvoJson()
2129
+ .then((json) => {
2130
+ Avo.cliInvoked({
2131
+ schemaId: json.schema.id,
2132
+ schemaName: json.schema.name,
2133
+ branchId: json.branch.id,
2134
+ branchName: json.branch.name,
2135
+ userId_: installIdOrUserId(),
2136
+ cliAction: Avo.CliAction.LOGOUT,
2137
+ cliInvokedByCi: invokedByCi(),
2138
+ force: undefined,
2139
+ forceFeatures: undefined,
2140
+ });
2141
+ command();
2142
+ })
2143
+ .catch(() => {
2144
+ Avo.cliInvoked({
2145
+ schemaId: 'N/A',
2146
+ schemaName: 'N/A',
2147
+ branchId: 'N/A',
2148
+ branchName: 'N/A',
2149
+ userId_: installIdOrUserId(),
2150
+ cliAction: Avo.CliAction.LOGOUT,
2151
+ cliInvokedByCi: invokedByCi(),
2152
+ force: undefined,
2153
+ forceFeatures: undefined,
2154
+ });
2155
+ command();
1807
2156
  });
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);
2157
+ },
1862
2158
  })
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,
2159
+ .command({
2160
+ command: 'whoami',
2161
+ describe: 'Shows the currently logged in username',
2162
+ handler: (argv) => {
2163
+ const command = () => {
2164
+ requireAuth(argv, () => {
2165
+ if (conf.has('user')) {
2166
+ const user = conf.get('user');
2167
+ report.info(`Logged in as ${email(user.email)}`);
2168
+ }
2169
+ else {
2170
+ report.warn('Not logged in');
2171
+ }
2172
+ });
2173
+ };
2174
+ loadAvoJson()
2175
+ .then((json) => {
2176
+ Avo.cliInvoked({
2177
+ schemaId: json.schema.id,
2178
+ schemaName: json.schema.name,
2179
+ branchId: json.branch.id,
2180
+ branchName: json.branch.name,
2181
+ userId_: installIdOrUserId(),
2182
+ cliAction: Avo.CliAction.WHOAMI,
2183
+ cliInvokedByCi: invokedByCi(),
2184
+ force: undefined,
2185
+ forceFeatures: undefined,
2186
+ });
2187
+ command();
2188
+ })
2189
+ .catch(() => {
2190
+ Avo.cliInvoked({
2191
+ schemaId: 'N/A',
2192
+ schemaName: 'N/A',
2193
+ branchId: 'N/A',
2194
+ branchName: 'N/A',
2195
+ userId_: installIdOrUserId(),
2196
+ cliAction: Avo.CliAction.WHOAMI,
2197
+ cliInvokedByCi: invokedByCi(),
2198
+ force: undefined,
2199
+ forceFeatures: undefined,
2200
+ });
2201
+ command();
1905
2202
  });
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();
2203
+ },
1922
2204
  })
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);
2205
+ .demandCommand(1, 'must provide a valid command')
2206
+ .recommendCommands()
2207
+ .help().argv;
2208
+ }
2209
+ catch (err) {
2210
+ // Always log yargs errors for visibility (e.g., in tests)
2211
+ report.error(`yargs error: ${err instanceof Error ? err.message : String(err)}`);
2212
+ // Only exit if we're actually running as the main module
2213
+ if (isMainModule) {
2214
+ throw err;
2215
+ }
2216
+ }
2217
+ /// ///////////////// ////////
2218
+ // catch unhandled promises
2219
+ if (isMainModule) {
2220
+ process.on('unhandledRejection', (err) => {
2221
+ cancelWait();
2222
+ if (!(err instanceof Error) && !(err instanceof AvoError)) {
2223
+ report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
1963
2224
  }
1964
2225
  else {
1965
- report.log("No need to logout, you're not logged in");
2226
+ // @ts-ignore
2227
+ report.error(err.message);
1966
2228
  }
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();
2229
+ // @ts-ignore
2230
+ // console.error(err.stack);
2231
+ process.exit(1);
1996
2232
  });
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
2233
  }
2059
- // @ts-ignore
2060
- // console.error(err.stack);
2061
- process.exit(1);
2062
- });
2234
+ }