@socketsecurity/cli-with-sentry 0.14.53 → 0.14.56

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.
@@ -29,16 +29,18 @@ var constants = require('./constants.js');
29
29
  var meow = _socketInterop(require('meow'));
30
30
  var objects = require('@socketsecurity/registry/lib/objects');
31
31
  var regexps = require('@socketsecurity/registry/lib/regexps');
32
+ var commonTags = _socketInterop(require('common-tags'));
32
33
  var fs$1 = require('node:fs/promises');
33
34
  var ScreenWidget = _socketInterop(require('blessed/lib/widgets/screen'));
34
35
  var contrib = _socketInterop(require('blessed-contrib'));
35
36
  var prompts = require('@socketsecurity/registry/lib/prompts');
36
37
  var yargsParse = _socketInterop(require('yargs-parser'));
37
38
  var words = require('@socketsecurity/registry/lib/words');
38
- var npm = require('@socketsecurity/registry/lib/npm');
39
+ var shadowBin = require('./shadow-bin.js');
39
40
  var chalkTable = _socketInterop(require('chalk-table'));
40
41
  var util = require('node:util');
41
42
  var registry = require('@socketsecurity/registry');
43
+ var npm = require('@socketsecurity/registry/lib/npm');
42
44
  var packages = require('@socketsecurity/registry/lib/packages');
43
45
  var registryConstants = require('@socketsecurity/registry/lib/constants');
44
46
  var isInteractive = require('@socketregistry/is-interactive/index.cjs');
@@ -1518,7 +1520,7 @@ function meowOrExit({
1518
1520
  }
1519
1521
  function getAsciiHeader(command) {
1520
1522
  const cliVersion = // The '@rollup/plugin-replace' will replace "process.env['SOCKET_CLI_VERSION_HASH']".
1521
- "0.14.53:e7fcb39:b41fef49:pub";
1523
+ "0.14.56:5a261bf:186ce7ee:pub";
1522
1524
  const nodeVersion = process.version;
1523
1525
  const apiToken = index.getSetting('apiToken');
1524
1526
  const shownToken = apiToken ? getLastFiveOfApiToken(apiToken) : 'no';
@@ -1592,15 +1594,116 @@ async function run$z(argv, importMeta, {
1592
1594
  await runAction(githubEventBefore, githubEventAfter);
1593
1595
  }
1594
1596
 
1597
+ async function fetchOrgAnalyticsData(time, spinner, apiToken) {
1598
+ const socketSdk = await index.setupSdk(apiToken);
1599
+ const result = await handleApiCall(socketSdk.getOrgAnalytics(time.toString()), 'fetching analytics data');
1600
+ if (result.success === false) {
1601
+ handleUnsuccessfulApiResponse('getOrgAnalytics', result, spinner);
1602
+ return undefined;
1603
+ }
1604
+ spinner.stop();
1605
+ if (!result.data.length) {
1606
+ logger.logger.log('No analytics data is available for this organization yet.');
1607
+ return undefined;
1608
+ }
1609
+ return result.data;
1610
+ }
1611
+
1612
+ async function fetchRepoAnalyticsData(repo, time, spinner, apiToken) {
1613
+ const socketSdk = await index.setupSdk(apiToken);
1614
+ const result = await handleApiCall(socketSdk.getRepoAnalytics(repo, time.toString()), 'fetching analytics data');
1615
+ if (result.success === false) {
1616
+ handleUnsuccessfulApiResponse('getRepoAnalytics', result, spinner);
1617
+ return undefined;
1618
+ }
1619
+ spinner.stop();
1620
+ if (!result.data.length) {
1621
+ logger.logger.log('No analytics data is available for this organization yet.');
1622
+ return undefined;
1623
+ }
1624
+ return result.data;
1625
+ }
1626
+
1627
+ function mdTableStringNumber(title1, title2, obj) {
1628
+ // | Date | Counts |
1629
+ // | ----------- | ------ |
1630
+ // | Header | 201464 |
1631
+ // | Paragraph | 18 |
1632
+ let mw1 = title1.length;
1633
+ let mw2 = title2.length;
1634
+ for (const [key, value] of Object.entries(obj)) {
1635
+ mw1 = Math.max(mw1, key.length);
1636
+ mw2 = Math.max(mw2, String(value ?? '').length);
1637
+ }
1638
+ const lines = [];
1639
+ lines.push(`| ${title1.padEnd(mw1, ' ')} | ${title2.padEnd(mw2)} |`);
1640
+ lines.push(`| ${'-'.repeat(mw1)} | ${'-'.repeat(mw2)} |`);
1641
+ for (const [key, value] of Object.entries(obj)) {
1642
+ lines.push(`| ${key.padEnd(mw1, ' ')} | ${String(value ?? '').padStart(mw2, ' ')} |`);
1643
+ }
1644
+ lines.push(`| ${'-'.repeat(mw1)} | ${'-'.repeat(mw2)} |`);
1645
+ return lines.join('\n');
1646
+ }
1647
+ function mdTable(logs,
1648
+ // This is saying "an array of strings and the strings are a valid key of elements of T"
1649
+ // In turn, T is defined above as the audit log event type from our OpenAPI docs.
1650
+ cols) {
1651
+ // Max col width required to fit all data in that column
1652
+ const cws = cols.map(col => col.length);
1653
+ for (const log of logs) {
1654
+ for (let i = 0; i < cols.length; ++i) {
1655
+ // @ts-ignore
1656
+ const val = log[cols[i] ?? ''] ?? '';
1657
+ cws[i] = Math.max(cws[i] ?? 0, String(val).length);
1658
+ }
1659
+ }
1660
+ let div = '|';
1661
+ for (const cw of cws) div += ' ' + '-'.repeat(cw) + ' |';
1662
+ let header = '|';
1663
+ for (let i = 0; i < cols.length; ++i) header += ' ' + String(cols[i]).padEnd(cws[i] ?? 0, ' ') + ' |';
1664
+ let body = '';
1665
+ for (const log of logs) {
1666
+ body += '|';
1667
+ for (let i = 0; i < cols.length; ++i) {
1668
+ // @ts-ignore
1669
+ const val = log[cols[i] ?? ''] ?? '';
1670
+ body += ' ' + String(val).padEnd(cws[i] ?? 0, ' ') + ' |';
1671
+ }
1672
+ body += '\n';
1673
+ }
1674
+ return [div, header, div, body.trim(), div].filter(s => !!s.trim()).join('\n');
1675
+ }
1676
+
1595
1677
  // Note: Widgets does not seem to actually work as code :'(
1596
1678
 
1679
+ const METRICS = ['total_critical_alerts', 'total_high_alerts', 'total_medium_alerts', 'total_low_alerts', 'total_critical_added', 'total_medium_added', 'total_low_added', 'total_high_added', 'total_critical_prevented', 'total_high_prevented', 'total_medium_prevented', 'total_low_prevented'];
1680
+
1597
1681
  // Note: This maps `new Date(date).getMonth()` to English three letters
1598
1682
  const Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1599
- const METRICS = ['total_critical_alerts', 'total_high_alerts', 'total_medium_alerts', 'total_low_alerts', 'total_critical_added', 'total_medium_added', 'total_low_added', 'total_high_added', 'total_critical_prevented', 'total_high_prevented', 'total_medium_prevented', 'total_low_prevented'];
1600
1683
  async function displayAnalytics({
1684
+ filePath,
1685
+ outputKind,
1686
+ repo,
1687
+ scope,
1688
+ time
1689
+ }) {
1690
+ const apiToken = index.getDefaultToken();
1691
+ if (!apiToken) {
1692
+ throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API token.');
1693
+ }
1694
+ await outputAnalyticsWithToken({
1695
+ apiToken,
1696
+ filePath,
1697
+ outputKind,
1698
+ repo,
1699
+ scope,
1700
+ time
1701
+ });
1702
+ }
1703
+ async function outputAnalyticsWithToken({
1601
1704
  apiToken,
1602
1705
  filePath,
1603
- outputJson,
1706
+ outputKind,
1604
1707
  repo,
1605
1708
  scope,
1606
1709
  time
@@ -1616,22 +1719,70 @@ async function displayAnalytics({
1616
1719
  } else if (repo) {
1617
1720
  data = await fetchRepoAnalyticsData(repo, time, spinner, apiToken);
1618
1721
  }
1619
- if (data) {
1620
- if (outputJson && !filePath) {
1621
- logger.logger.log(data);
1622
- } else if (filePath) {
1722
+
1723
+ // A message should already have been printed if we have no data here
1724
+ if (!data) return;
1725
+ if (outputKind === 'json') {
1726
+ let serialized = renderJson(data);
1727
+ if (!serialized) return;
1728
+ if (filePath && filePath !== '-') {
1623
1729
  try {
1624
- await fs$1.writeFile(filePath, JSON.stringify(data), 'utf8');
1730
+ await fs$1.writeFile(filePath, serialized, 'utf8');
1625
1731
  logger.logger.log(`Data successfully written to ${filePath}`);
1626
1732
  } catch (e) {
1733
+ logger.logger.error('There was an error trying to write the json to disk');
1627
1734
  logger.logger.error(e);
1735
+ process.exitCode = 1;
1736
+ }
1737
+ } else {
1738
+ logger.logger.log(serialized);
1739
+ }
1740
+ } else {
1741
+ const fdata = scope === 'org' ? formatDataOrg(data) : formatDataRepo(data);
1742
+ if (outputKind === 'markdown') {
1743
+ const serialized = renderMarkdown(fdata, time, repo);
1744
+ if (filePath && filePath !== '-') {
1745
+ try {
1746
+ await fs$1.writeFile(filePath, serialized, 'utf8');
1747
+ logger.logger.log(`Data successfully written to ${filePath}`);
1748
+ } catch (e) {
1749
+ logger.logger.error(e);
1750
+ }
1751
+ } else {
1752
+ logger.logger.log(serialized);
1628
1753
  }
1629
1754
  } else {
1630
- const fdata = scope === 'org' ? formatData(data, 'org') : formatData(data, 'repo');
1631
1755
  displayAnalyticsScreen(fdata);
1632
1756
  }
1633
1757
  }
1634
1758
  }
1759
+ function renderJson(data) {
1760
+ try {
1761
+ return JSON.stringify(data, null, 2);
1762
+ } catch (e) {
1763
+ // This could be caused by circular references, which is an "us" problem
1764
+ logger.logger.error('There was a problem converting the data set to JSON. Please try without --json or with --markdown');
1765
+ process.exitCode = 1;
1766
+ return;
1767
+ }
1768
+ }
1769
+ function renderMarkdown(data, days, repoSlug) {
1770
+ return commonTags.stripIndents`
1771
+ # Socket Alert Analytics
1772
+
1773
+ These are the Socket.dev stats are analytics for the ${repoSlug ? `${repoSlug} repo` : 'org'} of the past ${days} days
1774
+
1775
+ ${[['Total critical alerts', mdTableStringNumber('Date', 'Counts', data['total_critical_alerts'])], ['Total high alerts', mdTableStringNumber('Date', 'Counts', data['total_high_alerts'])], ['Total critical alerts added to the main branch', mdTableStringNumber('Date', 'Counts', data['total_critical_added'])], ['Total high alerts added to the main branch', mdTableStringNumber('Date', 'Counts', data['total_high_added'])], ['Total critical alerts prevented from the main branch', mdTableStringNumber('Date', 'Counts', data['total_critical_prevented'])], ['Total high alerts prevented from the main branch', mdTableStringNumber('Date', 'Counts', data['total_high_prevented'])], ['Total medium alerts prevented from the main branch', mdTableStringNumber('Date', 'Counts', data['total_medium_prevented'])], ['Total low alerts prevented from the main branch', mdTableStringNumber('Date', 'Counts', data['total_low_prevented'])]].map(([title, table]) => commonTags.stripIndents`
1776
+ ## ${title}
1777
+
1778
+ ${table}
1779
+ `).join('\n\n')}
1780
+
1781
+ ## Top 5 alert types
1782
+
1783
+ ${mdTableStringNumber('Name', 'Counts', data['top_five_alert_types'])}
1784
+ `;
1785
+ }
1635
1786
  function displayAnalyticsScreen(data) {
1636
1787
  const screen = new ScreenWidget({});
1637
1788
  const grid = new contrib.grid({
@@ -1664,91 +1815,69 @@ function displayAnalyticsScreen(data) {
1664
1815
  screen.render();
1665
1816
  screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
1666
1817
  }
1667
- async function fetchOrgAnalyticsData(time, spinner, apiToken) {
1668
- const socketSdk = await index.setupSdk(apiToken);
1669
- const result = await handleApiCall(socketSdk.getOrgAnalytics(time.toString()), 'fetching analytics data');
1670
- if (result.success === false) {
1671
- handleUnsuccessfulApiResponse('getOrgAnalytics', result, spinner);
1672
- return undefined;
1818
+ function formatDataRepo(data) {
1819
+ const sortedTopFiveAlerts = {};
1820
+ const totalTopAlerts = {};
1821
+ const formattedData = {};
1822
+ for (const metric of METRICS) {
1823
+ formattedData[metric] = {};
1673
1824
  }
1674
- spinner.stop();
1675
- if (!result.data.length) {
1676
- logger.logger.log('No analytics data is available for this organization yet.');
1677
- return undefined;
1825
+ for (const entry of data) {
1826
+ const topFiveAlertTypes = entry['top_five_alert_types'];
1827
+ for (const type of Object.keys(topFiveAlertTypes)) {
1828
+ const count = topFiveAlertTypes[type] ?? 0;
1829
+ if (!totalTopAlerts[type]) {
1830
+ totalTopAlerts[type] = count;
1831
+ } else if (count > (totalTopAlerts[type] ?? 0)) {
1832
+ totalTopAlerts[type] = count;
1833
+ }
1834
+ }
1678
1835
  }
1679
- return result.data;
1680
- }
1681
- async function fetchRepoAnalyticsData(repo, time, spinner, apiToken) {
1682
- const socketSdk = await index.setupSdk(apiToken);
1683
- const result = await handleApiCall(socketSdk.getRepoAnalytics(repo, time.toString()), 'fetching analytics data');
1684
- if (result.success === false) {
1685
- handleUnsuccessfulApiResponse('getRepoAnalytics', result, spinner);
1686
- return undefined;
1836
+ for (const entry of data) {
1837
+ for (const metric of METRICS) {
1838
+ formattedData[metric][formatDate(entry['created_at'])] = entry[metric];
1839
+ }
1687
1840
  }
1688
- spinner.stop();
1689
- if (!result.data.length) {
1690
- logger.logger.log('No analytics data is available for this organization yet.');
1691
- return undefined;
1841
+ const topFiveAlertEntries = Object.entries(totalTopAlerts).sort(([_keya, a], [_keyb, b]) => b - a).slice(0, 5);
1842
+ for (const [key, value] of topFiveAlertEntries) {
1843
+ sortedTopFiveAlerts[key] = value;
1692
1844
  }
1693
- return result.data;
1845
+ return {
1846
+ ...formattedData,
1847
+ top_five_alert_types: sortedTopFiveAlerts
1848
+ };
1694
1849
  }
1695
- function formatData(data, scope) {
1696
- const formattedData = {};
1850
+ function formatDataOrg(data) {
1697
1851
  const sortedTopFiveAlerts = {};
1698
1852
  const totalTopAlerts = {};
1853
+ const formattedData = {};
1699
1854
  for (const metric of METRICS) {
1700
1855
  formattedData[metric] = {};
1701
1856
  }
1702
- if (scope === 'org') {
1703
- for (const entry of data) {
1704
- const topFiveAlertTypes = entry['top_five_alert_types'];
1705
- for (const type of Object.keys(topFiveAlertTypes)) {
1706
- const count = topFiveAlertTypes[type] ?? 0;
1707
- if (!totalTopAlerts[type]) {
1708
- totalTopAlerts[type] = count;
1709
- } else {
1710
- totalTopAlerts[type] += count;
1711
- }
1712
- }
1713
- }
1714
- for (const metric of METRICS) {
1715
- const formatted = formattedData[metric];
1716
- for (const entry of data) {
1717
- const date = formatDate(entry['created_at']);
1718
- if (!formatted[date]) {
1719
- formatted[date] = entry[metric];
1720
- } else {
1721
- formatted[date] += entry[metric];
1722
- }
1723
- }
1724
- }
1725
- } else if (scope === 'repo') {
1726
- for (const entry of data) {
1727
- const topFiveAlertTypes = entry['top_five_alert_types'];
1728
- for (const type of Object.keys(topFiveAlertTypes)) {
1729
- const count = topFiveAlertTypes[type] ?? 0;
1730
- if (!totalTopAlerts[type]) {
1731
- totalTopAlerts[type] = count;
1732
- } else if (count > (totalTopAlerts[type] ?? 0)) {
1733
- totalTopAlerts[type] = count;
1734
- }
1857
+ for (const entry of data) {
1858
+ const topFiveAlertTypes = entry['top_five_alert_types'];
1859
+ for (const type of Object.keys(topFiveAlertTypes)) {
1860
+ const count = topFiveAlertTypes[type] ?? 0;
1861
+ if (!totalTopAlerts[type]) {
1862
+ totalTopAlerts[type] = count;
1863
+ } else {
1864
+ totalTopAlerts[type] += count;
1735
1865
  }
1736
1866
  }
1867
+ }
1868
+ for (const metric of METRICS) {
1869
+ const formatted = formattedData[metric];
1737
1870
  for (const entry of data) {
1738
- for (const metric of METRICS) {
1739
- formattedData[metric][formatDate(entry['created_at'])] = entry[metric];
1871
+ const date = formatDate(entry['created_at']);
1872
+ if (!formatted[date]) {
1873
+ formatted[date] = entry[metric];
1874
+ } else {
1875
+ formatted[date] += entry[metric];
1740
1876
  }
1741
1877
  }
1742
1878
  }
1743
- const topFiveAlertEntries = Object.entries(totalTopAlerts).sort(({
1744
- 1: a
1745
- }, {
1746
- 1: b
1747
- }) => b - a).slice(0, 5);
1748
- for (const {
1749
- 0: key,
1750
- 1: value
1751
- } of topFiveAlertEntries) {
1879
+ const topFiveAlertEntries = Object.entries(totalTopAlerts).sort(([_keya, a], [_keyb, b]) => b - a).slice(0, 5);
1880
+ for (const [key, value] of topFiveAlertEntries) {
1752
1881
  sortedTopFiveAlerts[key] = value;
1753
1882
  }
1754
1883
  return {
@@ -1793,6 +1922,18 @@ const config$y = {
1793
1922
  flags: {
1794
1923
  ...commonFlags,
1795
1924
  ...outputFlags,
1925
+ file: {
1926
+ type: 'string',
1927
+ shortFlag: 'f',
1928
+ default: '-',
1929
+ description: 'Path to a local file to save the output. Only valid with --json/--markdown. Defaults to stdout.'
1930
+ },
1931
+ repo: {
1932
+ type: 'string',
1933
+ shortFlag: 'r',
1934
+ default: '',
1935
+ description: 'Name of the repository. Only valid when scope=repo'
1936
+ },
1796
1937
  scope: {
1797
1938
  type: 'string',
1798
1939
  shortFlag: 's',
@@ -1804,18 +1945,6 @@ const config$y = {
1804
1945
  shortFlag: 't',
1805
1946
  default: 7,
1806
1947
  description: 'Time filter - either 7, 30 or 90, default: 7'
1807
- },
1808
- repo: {
1809
- type: 'string',
1810
- shortFlag: 'r',
1811
- default: '',
1812
- description: 'Name of the repository'
1813
- },
1814
- file: {
1815
- type: 'string',
1816
- shortFlag: 'f',
1817
- default: '',
1818
- description: 'Path to a local file to save the output'
1819
1948
  }
1820
1949
  },
1821
1950
  help: (command, {
@@ -1851,6 +1980,9 @@ async function run$y(argv, importMeta, {
1851
1980
  parentName
1852
1981
  });
1853
1982
  const {
1983
+ file,
1984
+ json,
1985
+ markdown,
1854
1986
  repo,
1855
1987
  scope,
1856
1988
  time
@@ -1858,66 +1990,125 @@ async function run$y(argv, importMeta, {
1858
1990
  const badScope = scope !== 'org' && scope !== 'repo';
1859
1991
  const badTime = time !== 7 && time !== 30 && time !== 90;
1860
1992
  const badRepo = scope === 'repo' && !repo;
1861
- if (badScope || badTime || badRepo) {
1993
+ const badFile = file !== '-' && !json && !markdown;
1994
+ const badFlags = json && markdown;
1995
+ if (badScope || badTime || badRepo || badFile || badFlags) {
1862
1996
  // Use exit status of 2 to indicate incorrect usage, generally invalid
1863
1997
  // options or missing arguments.
1864
1998
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
1865
1999
  process.exitCode = 2;
1866
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
1867
- - Scope must be "repo" or "org" ${badScope ? colors.red('(bad!)') : colors.green('(ok)')}\n
1868
- - The time filter must either be 7, 30 or 90 ${badTime ? colors.red('(bad!)') : colors.green('(ok)')}\n
1869
- - Repository name using --repo when scope is "repo" ${badRepo ? colors.red('(bad!)') : colors.green('(ok)')}\n`);
2000
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
2001
+
2002
+ - Scope must be "repo" or "org" ${badScope ? colors.red('(bad!)') : colors.green('(ok)')}
2003
+
2004
+ - The time filter must either be 7, 30 or 90 ${badTime ? colors.red('(bad!)') : colors.green('(ok)')}
2005
+
2006
+ ${scope === 'repo' ? `- Repository name using --repo when scope is "repo" ${badRepo ? colors.red('(bad!)') : colors.green('(ok)')}` : ''}
2007
+
2008
+ ${badFlags ? `- The \`--json\` and \`--markdown\` flags can not be used at the same time ${badFlags ? colors.red('(bad!)') : colors.green('(ok)')}` : ''}
2009
+
2010
+ ${badFile ? `- The \`--file\` flag is only valid when using \`--json\` or \`--markdown\` ${badFile ? colors.red('(bad!)') : colors.green('(ok)')}` : ''}
2011
+ `.split('\n').filter(s => !!s.trim()).join('\n'));
1870
2012
  return;
1871
2013
  }
1872
2014
  if (cli.flags['dryRun']) {
1873
2015
  logger.logger.log(DRY_RUN_BAIL_TEXT$x);
1874
2016
  return;
1875
2017
  }
1876
- const apiToken = index.getDefaultToken();
1877
- if (!apiToken) {
1878
- throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API token.');
1879
- }
1880
2018
  return await displayAnalytics({
1881
- apiToken,
1882
2019
  scope,
1883
2020
  time,
1884
2021
  repo: String(repo || ''),
1885
- outputJson: Boolean(cli.flags['json']),
1886
- filePath: String(cli.flags['file'] || '')
2022
+ outputKind: json ? 'json' : markdown ? 'markdown' : 'print',
2023
+ filePath: String(file || '')
1887
2024
  });
1888
2025
  }
1889
2026
 
1890
2027
  async function getAuditLog({
1891
- apiToken,
2028
+ logType,
1892
2029
  orgSlug,
1893
- outputJson,
1894
- outputMarkdown,
2030
+ outputKind,
1895
2031
  page,
1896
- perPage,
1897
- type
2032
+ perPage
1898
2033
  }) {
1899
- // Lazily access constants.spinner.
1900
- const {
1901
- spinner
1902
- } = constants;
1903
- spinner.start(`Looking up audit log for ${orgSlug}`);
1904
- const socketSdk = await index.setupSdk(apiToken);
1905
- const result = await handleApiCall(socketSdk.getAuditLogEvents(orgSlug, {
1906
- outputJson,
1907
- outputMarkdown,
2034
+ const apiToken = index.getDefaultToken();
2035
+ if (!apiToken) {
2036
+ throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
2037
+ }
2038
+ const auditLogs = await getAuditLogWithToken({
2039
+ apiToken,
1908
2040
  orgSlug,
1909
- type,
2041
+ outputKind,
1910
2042
  page,
1911
- per_page: perPage
1912
- }), `Looking up audit log for ${orgSlug}\n`);
1913
- if (!result.success) {
1914
- handleUnsuccessfulApiResponse('getAuditLogEvents', result, spinner);
2043
+ perPage,
2044
+ logType
2045
+ });
2046
+ if (!auditLogs) return;
2047
+ if (outputKind === 'json') await outputAsJson(auditLogs.results, orgSlug, logType, page, perPage);else if (outputKind === 'markdown') await outputAsMarkdown(auditLogs.results, orgSlug, logType, page, perPage);else await outputAsPrint(auditLogs.results, orgSlug, logType);
2048
+ }
2049
+ async function outputAsJson(auditLogs, orgSlug, logType, page, perPage) {
2050
+ let json;
2051
+ try {
2052
+ json = JSON.stringify({
2053
+ desc: 'Audit logs for given query',
2054
+ generated: new Date().toISOString(),
2055
+ org: orgSlug,
2056
+ logType,
2057
+ page,
2058
+ perPage,
2059
+ logs: auditLogs.map(log => {
2060
+ // Note: The subset is pretty arbitrary
2061
+ const {
2062
+ created_at,
2063
+ event_id,
2064
+ ip_address,
2065
+ type,
2066
+ user_agent,
2067
+ user_email
2068
+ } = log;
2069
+ return {
2070
+ event_id,
2071
+ created_at,
2072
+ ip_address,
2073
+ type,
2074
+ user_agent,
2075
+ user_email
2076
+ };
2077
+ })
2078
+ }, null, 2);
2079
+ } catch (e) {
2080
+ logger.logger.error('There was a problem converting the logs to JSON, please try without the `--json` flag');
2081
+ process.exitCode = 1;
1915
2082
  return;
1916
2083
  }
1917
- spinner.stop();
2084
+ logger.logger.log(json);
2085
+ }
2086
+ async function outputAsMarkdown(auditLogs, orgSlug, logType, page, perPage) {
2087
+ try {
2088
+ const table = mdTable(auditLogs, ['event_id', 'created_at', 'type', 'user_email', 'ip_address', 'user_agent']);
2089
+ logger.logger.log(commonTags.stripIndents`
2090
+ # Socket Audit Logs
2091
+
2092
+ These are the Socket.dev audit logs as per requested query.
2093
+ - org: ${orgSlug}
2094
+ - type filter: ${logType || '(none)'}
2095
+ - page: ${page}
2096
+ - per page: ${perPage}
2097
+ - generated: ${new Date().toISOString()}
2098
+
2099
+ ${table}
2100
+ `);
2101
+ } catch (e) {
2102
+ logger.logger.error('There was a problem converting the logs to JSON, please try without the `--json` flag');
2103
+ logger.logger.error(e);
2104
+ process.exitCode = 1;
2105
+ return;
2106
+ }
2107
+ }
2108
+ async function outputAsPrint(auditLogs, orgSlug, logType) {
1918
2109
  const data = [];
1919
2110
  const logDetails = {};
1920
- for (const d of result.data.results) {
2111
+ for (const d of auditLogs) {
1921
2112
  const {
1922
2113
  created_at
1923
2114
  } = d;
@@ -1934,11 +2125,42 @@ async function getAuditLog({
1934
2125
  }
1935
2126
  }
1936
2127
  logger.logger.log(logDetails[await prompts.select({
1937
- message: type ? `\n Audit log for: ${orgSlug} with type: ${type}\n` : `\n Audit log for: ${orgSlug}\n`,
2128
+ message: logType ? `\n Audit log for: ${orgSlug} with type: ${logType}\n` : `\n Audit log for: ${orgSlug}\n`,
1938
2129
  choices: data,
1939
2130
  pageSize: 30
1940
2131
  })]);
1941
2132
  }
2133
+ async function getAuditLogWithToken({
2134
+ apiToken,
2135
+ logType,
2136
+ orgSlug,
2137
+ outputKind,
2138
+ page,
2139
+ perPage
2140
+ }) {
2141
+ // Lazily access constants.spinner.
2142
+ const {
2143
+ spinner
2144
+ } = constants;
2145
+ spinner.start(`Looking up audit log for ${orgSlug}`);
2146
+ const socketSdk = await index.setupSdk(apiToken);
2147
+ const result = await handleApiCall(socketSdk.getAuditLogEvents(orgSlug, {
2148
+ outputJson: outputKind === 'json',
2149
+ // I'm not sure this is used at all
2150
+ outputMarkdown: outputKind === 'markdown',
2151
+ // I'm not sure this is used at all
2152
+ orgSlug,
2153
+ type: logType,
2154
+ page,
2155
+ per_page: perPage
2156
+ }), `Looking up audit log for ${orgSlug}\n`);
2157
+ if (!result.success) {
2158
+ handleUnsuccessfulApiResponse('getAuditLogEvents', result, spinner);
2159
+ return;
2160
+ }
2161
+ spinner.stop();
2162
+ return result.data;
2163
+ }
1942
2164
 
1943
2165
  const {
1944
2166
  DRY_RUN_BAIL_TEXT: DRY_RUN_BAIL_TEXT$w
@@ -1994,48 +2216,43 @@ async function run$x(argv, importMeta, {
1994
2216
  importMeta,
1995
2217
  parentName
1996
2218
  });
1997
- const type = String(cli.flags['type'] || '');
2219
+ const {
2220
+ json,
2221
+ markdown,
2222
+ page,
2223
+ perPage,
2224
+ type
2225
+ } = cli.flags;
2226
+ const logType = String(type || '');
1998
2227
  const [orgSlug = ''] = cli.input;
1999
2228
  if (!orgSlug) {
2000
2229
  // Use exit status of 2 to indicate incorrect usage, generally invalid
2001
2230
  // options or missing arguments.
2002
2231
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
2003
2232
  process.exitCode = 2;
2004
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
2005
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
2233
+ logger.logger.error(commonTags.stripIndents`
2234
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
2235
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
2236
+ `);
2006
2237
  return;
2007
2238
  }
2008
2239
  if (cli.flags['dryRun']) {
2009
2240
  logger.logger.log(DRY_RUN_BAIL_TEXT$w);
2010
2241
  return;
2011
2242
  }
2012
- const apiToken = index.getDefaultToken();
2013
- if (!apiToken) {
2014
- throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
2015
- }
2016
2243
  await getAuditLog({
2017
- apiToken,
2018
2244
  orgSlug,
2019
- outputJson: Boolean(cli.flags['json']),
2020
- outputMarkdown: Boolean(cli.flags['markdown']),
2021
- page: Number(cli.flags['page'] || 0),
2022
- perPage: Number(cli.flags['perPage'] || 0),
2023
- type: type.charAt(0).toUpperCase() + type.slice(1)
2245
+ outputKind: json ? 'json' : markdown ? 'markdown' : 'print',
2246
+ page: Number(page || 0),
2247
+ perPage: Number(perPage || 0),
2248
+ logType: logType.charAt(0).toUpperCase() + logType.slice(1)
2024
2249
  });
2025
2250
  }
2026
2251
 
2027
- const {
2028
- SBOM_SIGN_ALGORITHM,
2029
- // Algorithm. Example: RS512
2030
- SBOM_SIGN_PRIVATE_KEY,
2031
- // Location to the RSA private key
2032
- SBOM_SIGN_PUBLIC_KEY // Optional. Location to the RSA public key
2033
- } = process$1.env;
2034
2252
  const {
2035
2253
  NPM: NPM$e,
2036
- PNPM: PNPM$8,
2037
- cdxgenBinPath,
2038
- synpBinPath
2254
+ NPX: NPX$3,
2255
+ PNPM: PNPM$8
2039
2256
  } = constants;
2040
2257
  const nodejsPlatformTypes = new Set(['javascript', 'js', 'nodejs', NPM$e, PNPM$8, 'ts', 'tsx', 'typescript']);
2041
2258
  async function runCycloneDX(yargv) {
@@ -2047,21 +2264,13 @@ async function runCycloneDX(yargv) {
2047
2264
  // Use synp to create a package-lock.json from the yarn.lock,
2048
2265
  // based on the node_modules folder, for a more accurate SBOM.
2049
2266
  try {
2050
- await npm.runBin(await fs.promises.realpath(synpBinPath), ['--source-file', './yarn.lock']);
2267
+ await shadowBin(NPX$3, ['synp@1.9.14', '--', '--source-file', './yarn.lock'], 2);
2051
2268
  yargv.type = NPM$e;
2052
2269
  cleanupPackageLock = true;
2053
2270
  } catch {}
2054
2271
  }
2055
2272
  }
2056
- await npm.runBin(await fs.promises.realpath(cdxgenBinPath), argvToArray(yargv), {
2057
- env: {
2058
- NODE_ENV: '',
2059
- SBOM_SIGN_ALGORITHM,
2060
- SBOM_SIGN_PRIVATE_KEY,
2061
- SBOM_SIGN_PUBLIC_KEY
2062
- },
2063
- stdio: 'inherit'
2064
- });
2273
+ await shadowBin(NPX$3, ['@cyclonedx/cdxgen@11.2.0', '--', ...argvToArray(yargv)], 2);
2065
2274
  if (cleanupPackageLock) {
2066
2275
  try {
2067
2276
  await fs.promises.rm('./package-lock.json');
@@ -2207,8 +2416,11 @@ async function run$w(argv, importMeta, {
2207
2416
  //
2208
2417
  //
2209
2418
  // if (cli.input.length)
2210
- // logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
2211
- // - Unexpected arguments\n
2419
+ // logger.error(
2420
+ // stripIndents`
2421
+ // ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
2422
+ //
2423
+ // - Unexpected arguments
2212
2424
  // `)
2213
2425
  // config.help(parentName, config)
2214
2426
  // return
@@ -2270,6 +2482,7 @@ async function findDependencies({
2270
2482
  logger.logger.log(result.data);
2271
2483
  return;
2272
2484
  }
2485
+ logger.logger.log('Request details: Offset:', offset, ', limit:', limit, ', is there more data after this?', result.data.end ? 'no' : 'yes');
2273
2486
  const options = {
2274
2487
  columns: [{
2275
2488
  field: 'namespace',
@@ -2361,43 +2574,95 @@ async function run$v(argv, importMeta, {
2361
2574
  async function getDiffScan({
2362
2575
  after,
2363
2576
  before,
2577
+ depth,
2364
2578
  file,
2365
2579
  orgSlug,
2366
2580
  outputJson
2367
- }, apiToken) {
2581
+ }) {
2582
+ const apiToken = index.getDefaultToken();
2583
+ if (!apiToken) {
2584
+ throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
2585
+ }
2586
+ await getDiffScanWithToken({
2587
+ after,
2588
+ before,
2589
+ depth,
2590
+ file,
2591
+ orgSlug,
2592
+ outputJson,
2593
+ apiToken
2594
+ });
2595
+ }
2596
+ async function getDiffScanWithToken({
2597
+ after,
2598
+ apiToken,
2599
+ before,
2600
+ depth,
2601
+ file,
2602
+ orgSlug,
2603
+ outputJson
2604
+ }) {
2368
2605
  // Lazily access constants.spinner.
2369
2606
  const {
2370
2607
  spinner
2371
2608
  } = constants;
2372
2609
  spinner.start('Getting diff scan...');
2373
- const response = await queryAPI(`${orgSlug}/full-scans/diff?before=${before}&after=${after}&preview`, apiToken);
2374
- const data = await response.json();
2610
+ const response = await queryAPI(`orgs/${orgSlug}/full-scans/diff?before=${encodeURIComponent(before)}&after=${encodeURIComponent(after)}`, apiToken);
2375
2611
  if (!response.ok) {
2376
2612
  const err = await handleAPIError(response.status);
2377
2613
  spinner.errorAndStop(`${colors.bgRed(colors.white(response.statusText))}: ${err}`);
2378
2614
  return;
2379
2615
  }
2616
+ const result = await handleApiCall(await response.json(), 'Deserializing json');
2380
2617
  spinner.stop();
2381
- if (file && !outputJson) {
2382
- fs.writeFile(file, JSON.stringify(data), err => {
2383
- err ? logger.logger.error(err) : logger.logger.log(`Data successfully written to ${file}`);
2384
- });
2385
- return;
2386
- }
2387
- if (outputJson) {
2388
- logger.logger.log(`\n Diff scan result: \n`);
2389
- logger.logger.log(util.inspect(data, {
2390
- showHidden: false,
2391
- depth: null,
2392
- colors: true
2393
- }));
2394
- logger.logger.log(`\n View this diff scan in the Socket dashboard: ${colors.cyan(data?.['diff_report_url'])}`);
2618
+ const dashboardUrl = result?.['diff_report_url'];
2619
+ const dashboardMessage = dashboardUrl ? `\n View this diff scan in the Socket dashboard: ${colors.cyan(dashboardUrl)}` : '';
2620
+
2621
+ // When forcing json, or dumping to file, serialize to string such that it
2622
+ // won't get truncated. The only way to dump the full raw JSON to stdout is
2623
+ // to use `--json --file -` (the dash is a standard notation for stdout)
2624
+ if (outputJson || file) {
2625
+ let json;
2626
+ try {
2627
+ json = JSON.stringify(result, null, 2);
2628
+ } catch (e) {
2629
+ // Most likely caused by a circular reference (or OOM)
2630
+ logger.logger.error('There was a problem converting the data to JSON');
2631
+ process.exitCode = 1;
2632
+ return;
2633
+ }
2634
+ if (file && file !== '-') {
2635
+ logger.logger.log(`Writing json to \`${file}\``);
2636
+ fs.writeFile(file, JSON.stringify(result, null, 2), err => {
2637
+ if (err) {
2638
+ logger.logger.error(`Writing to \`${file}\` failed...`);
2639
+ logger.logger.error(err);
2640
+ } else {
2641
+ logger.logger.log(`Data successfully written to \`${file}\``);
2642
+ }
2643
+ logger.logger.error(dashboardMessage);
2644
+ });
2645
+ } else {
2646
+ // TODO: expose different method for writing to stderr when simply dodging stdout
2647
+ logger.logger.error(`\n Diff scan result: \n`);
2648
+ logger.logger.log(json);
2649
+ logger.logger.error(dashboardMessage);
2650
+ }
2395
2651
  return;
2396
2652
  }
2653
+
2654
+ // In this case neither the --json nor the --file flag was passed
2655
+ // Dump the JSON to CLI and let NodeJS deal with truncation
2656
+
2397
2657
  logger.logger.log('Diff scan result:');
2398
- logger.logger.log(data);
2658
+ logger.logger.log(util.inspect(result, {
2659
+ showHidden: false,
2660
+ depth: depth > 0 ? depth : null,
2661
+ colors: true,
2662
+ maxArrayLength: null
2663
+ }));
2399
2664
  logger.logger.log(`\n 📝 To display the detailed report in the terminal, use the --json flag \n`);
2400
- logger.logger.log(`\n View this diff scan in the Socket dashboard: ${colors.cyan(data?.['diff_report_url'])}`);
2665
+ logger.logger.log(dashboardMessage);
2401
2666
  }
2402
2667
 
2403
2668
  const {
@@ -2409,36 +2674,44 @@ const config$u = {
2409
2674
  hidden: false,
2410
2675
  flags: {
2411
2676
  ...commonFlags,
2677
+ after: {
2678
+ type: 'string',
2679
+ shortFlag: 'a',
2680
+ default: '',
2681
+ description: 'The full scan ID of the head scan'
2682
+ },
2412
2683
  before: {
2413
2684
  type: 'string',
2414
2685
  shortFlag: 'b',
2415
2686
  default: '',
2416
2687
  description: 'The full scan ID of the base scan'
2417
2688
  },
2418
- after: {
2419
- type: 'string',
2420
- shortFlag: 'a',
2421
- default: '',
2422
- description: 'The full scan ID of the head scan'
2689
+ depth: {
2690
+ type: 'number',
2691
+ default: 2,
2692
+ description: 'Max depth of JSON to display before truncating, use zero for no limit (without --json/--file)'
2423
2693
  },
2424
- preview: {
2694
+ json: {
2425
2695
  type: 'boolean',
2426
- shortFlag: 'p',
2427
- default: true,
2428
- description: 'A boolean flag to persist or not the diff scan result'
2696
+ shortFlag: 'j',
2697
+ default: false,
2698
+ description: 'Output result as json. This can be big. Use --file to store it to disk without truncation.'
2429
2699
  },
2430
2700
  file: {
2431
2701
  type: 'string',
2432
2702
  shortFlag: 'f',
2433
2703
  default: '',
2434
- description: 'Path to a local file where the output should be saved'
2435
- },
2436
- ...outputFlags
2704
+ description: 'Path to a local file where the output should be saved. Use `-` to force stdout.'
2705
+ }
2437
2706
  },
2438
2707
  help: (command, config) => `
2439
2708
  Usage
2440
2709
  $ ${command} <org slug> --before=<before> --after=<after>
2441
2710
 
2711
+ This command displays the package changes between two scans. The full output
2712
+ can be pretty large depending on the size of your repo and time range. It is
2713
+ best stored to disk to be further analyzed by other tools.
2714
+
2442
2715
  Options
2443
2716
  ${getFlagListOutput(config.flags, 6)}
2444
2717
 
@@ -2471,6 +2744,7 @@ async function run$u(argv, importMeta, {
2471
2744
  logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
2472
2745
  - Specify a before and after full scan ID ${!before && !after ? colors.red('(missing before and after!)') : !before ? colors.red('(missing before!)') : !after ? colors.red('(missing after!)') : colors.green('(ok)')}\n
2473
2746
  - To get full scans IDs, you can run the command "socket scan list <your org slug>".
2747
+ The args are expecting a full \`aaa0aa0a-aaaa-0000-0a0a-0000000a00a0\` ID.\n
2474
2748
  - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
2475
2749
  return;
2476
2750
  }
@@ -2478,24 +2752,24 @@ async function run$u(argv, importMeta, {
2478
2752
  logger.logger.log(DRY_RUN_BAIL_TEXT$t);
2479
2753
  return;
2480
2754
  }
2481
- const apiToken = index.getDefaultToken();
2482
- if (!apiToken) {
2483
- throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
2484
- }
2485
2755
  await getDiffScan({
2486
2756
  outputJson: Boolean(cli.flags['json']),
2487
- outputMarkdown: Boolean(cli.flags['markdown']),
2488
2757
  before,
2489
2758
  after,
2490
- preview: Boolean(cli.flags['preview']),
2759
+ depth: Number(cli.flags['depth']),
2491
2760
  orgSlug,
2492
2761
  file: String(cli.flags['file'] || '')
2493
- }, apiToken);
2762
+ });
2494
2763
  }
2495
2764
 
2496
2765
  const description$3 = 'Diff scans related commands';
2497
2766
  const cmdDiffScan = {
2498
2767
  description: description$3,
2768
+ // Hidden because it was broken all this time (nobody could be using it)
2769
+ // and we're not sure if it's useful to anyone in its current state.
2770
+ // Until we do, we'll hide this to keep the help tidier.
2771
+ // And later, we may simply move this under `scan`, anyways.
2772
+ hidden: true,
2499
2773
  async run(argv, importMeta, {
2500
2774
  parentName
2501
2775
  }) {
@@ -2727,7 +3001,12 @@ function getSeverityCount(issues, lowestToInclude) {
2727
3001
  return severityCount;
2728
3002
  }
2729
3003
 
2730
- async function fetchPackageInfo(pkgName, pkgVersion, includeAllIssues, spinner) {
3004
+ async function fetchPackageInfo(pkgName, pkgVersion, includeAllIssues) {
3005
+ // Lazily access constants.spinner.
3006
+ const {
3007
+ spinner
3008
+ } = constants;
3009
+ spinner.start(pkgVersion === 'latest' ? `Looking up data for the latest version of ${pkgName}` : `Looking up data for version ${pkgVersion} of ${pkgName}`);
2731
3010
  const socketSdk = await index.setupSdk(index.getPublicToken());
2732
3011
  const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package');
2733
3012
  const scoreResult = await handleApiCall(socketSdk.getScoreByNPMPackage(pkgName, pkgVersion), 'looking up package score');
@@ -2738,6 +3017,7 @@ async function fetchPackageInfo(pkgName, pkgVersion, includeAllIssues, spinner)
2738
3017
  return handleUnsuccessfulApiResponse('getScoreByNPMPackage', scoreResult, spinner);
2739
3018
  }
2740
3019
  const severityCount = getSeverityCount(result.data, includeAllIssues ? undefined : 'high');
3020
+ spinner?.successAndStop('Data fetched');
2741
3021
  return {
2742
3022
  data: result.data,
2743
3023
  severityCount,
@@ -2753,50 +3033,55 @@ function formatPackageInfo({
2753
3033
  score,
2754
3034
  severityCount
2755
3035
  }, {
2756
- name,
2757
- outputJson,
2758
- outputMarkdown,
3036
+ name,
3037
+ outputKind,
2759
3038
  pkgName,
2760
- pkgVersion,
2761
- strict
2762
- }, spinner) {
2763
- if (outputJson) {
3039
+ pkgVersion
3040
+ }) {
3041
+ if (outputKind === 'json') {
2764
3042
  logger.logger.log(JSON.stringify(data, undefined, 2));
3043
+ return;
3044
+ }
3045
+ if (outputKind === 'markdown') {
3046
+ logger.logger.log(`\n# Package report for ${pkgName}\n`);
3047
+ logger.logger.log('Package report card:\n');
2765
3048
  } else {
2766
- logger.logger.log('\nPackage report card:');
2767
- const scoreResult = {
2768
- 'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100),
2769
- Maintenance: Math.floor(score.maintenance.score * 100),
2770
- Quality: Math.floor(score.quality.score * 100),
2771
- Vulnerabilities: Math.floor(score.vulnerability.score * 100),
2772
- License: Math.floor(score.license.score * 100)
2773
- };
2774
- Object.entries(scoreResult).map(score => logger.logger.log(`- ${score[0]}: ${formatScore(score[1])}`));
2775
- logger.logger.log('\n');
2776
- if (objectSome(severityCount)) {
2777
- spinner[strict ? 'error' : 'success'](`Package has these issues: ${formatSeverityCount(severityCount)}`);
2778
- formatPackageIssuesDetails(data, outputMarkdown);
2779
- } else {
2780
- spinner.successAndStop('Package has no issues');
2781
- }
2782
- const format = new index.ColorOrMarkdown(!!outputMarkdown);
2783
- const url = index.getSocketDevPackageOverviewUrl(NPM$c, pkgName, pkgVersion);
2784
- logger.logger.log('\n');
2785
- if (pkgVersion === 'latest') {
2786
- logger.logger.log(`Detailed info on socket.dev: ${format.hyperlink(`${pkgName}`, url, {
2787
- fallbackToUrl: true
2788
- })}`);
2789
- } else {
2790
- logger.logger.log(`Detailed info on socket.dev: ${format.hyperlink(`${pkgName} v${pkgVersion}`, url, {
2791
- fallbackToUrl: true
2792
- })}`);
2793
- }
2794
- if (!outputMarkdown) {
2795
- logger.logger.log(colors.dim(`\nOr rerun ${colors.italic(name)} using the ${colors.italic('--json')} flag to get full JSON output`));
2796
- }
3049
+ logger.logger.log(`\nPackage report card for ${pkgName}:\n`);
3050
+ }
3051
+ const scoreResult = {
3052
+ 'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100),
3053
+ Maintenance: Math.floor(score.maintenance.score * 100),
3054
+ Quality: Math.floor(score.quality.score * 100),
3055
+ Vulnerabilities: Math.floor(score.vulnerability.score * 100),
3056
+ License: Math.floor(score.license.score * 100)
3057
+ };
3058
+ Object.entries(scoreResult).map(score => logger.logger.log(`- ${score[0]}: ${formatScore(score[1])}`));
3059
+ logger.logger.log('\n');
3060
+ if (objectSome(severityCount)) {
3061
+ if (outputKind === 'markdown') {
3062
+ logger.logger.log('# Issues\n');
3063
+ }
3064
+ logger.logger.log(`Package has these issues: ${formatSeverityCount(severityCount)}\n`);
3065
+ formatPackageIssuesDetails(data, outputKind === 'markdown');
3066
+ } else {
3067
+ logger.logger.log('Package has no issues');
2797
3068
  }
2798
- if (strict && objectSome(severityCount)) {
2799
- process$1.exit(1);
3069
+ const format = new index.ColorOrMarkdown(outputKind === 'markdown');
3070
+ const url = index.getSocketDevPackageOverviewUrl(NPM$c, pkgName, pkgVersion);
3071
+ logger.logger.log('\n');
3072
+ if (pkgVersion === 'latest') {
3073
+ logger.logger.log(`Detailed info on socket.dev: ${format.hyperlink(`${pkgName}`, url, {
3074
+ fallbackToUrl: true
3075
+ })}`);
3076
+ } else {
3077
+ logger.logger.log(`Detailed info on socket.dev: ${format.hyperlink(`${pkgName} v${pkgVersion}`, url, {
3078
+ fallbackToUrl: true
3079
+ })}`);
3080
+ }
3081
+ if (outputKind !== 'markdown') {
3082
+ logger.logger.log(colors.dim(`\nOr rerun ${colors.italic(name)} using the ${colors.italic('--json')} flag to get full JSON output`));
3083
+ } else {
3084
+ logger.logger.log('');
2800
3085
  }
2801
3086
  }
2802
3087
  function formatPackageIssuesDetails(packageData, outputMarkdown) {
@@ -2817,7 +3102,7 @@ function formatPackageIssuesDetails(packageData, outputMarkdown) {
2817
3102
  }
2818
3103
  return acc;
2819
3104
  }, {});
2820
- const format = new index.ColorOrMarkdown(!!outputMarkdown);
3105
+ const format = new index.ColorOrMarkdown(outputMarkdown);
2821
3106
  for (const issue of Object.keys(uniqueIssues)) {
2822
3107
  const issueWithLink = format.hyperlink(`${uniqueIssues[issue]?.label}`, index.getSocketDevAlertUrl(issue), {
2823
3108
  fallbackToUrl: true
@@ -2841,27 +3126,23 @@ function formatScore(score) {
2841
3126
  async function getPackageInfo({
2842
3127
  commandName,
2843
3128
  includeAllIssues,
2844
- outputJson,
2845
- outputMarkdown,
3129
+ outputKind,
2846
3130
  pkgName,
2847
3131
  pkgVersion,
2848
3132
  strict
2849
3133
  }) {
2850
- // Lazily access constants.spinner.
2851
- const {
2852
- spinner
2853
- } = constants;
2854
- spinner.start(pkgVersion === 'latest' ? `Looking up data for the latest version of ${pkgName}` : `Looking up data for version ${pkgVersion} of ${pkgName}`);
2855
- const packageData = await fetchPackageInfo(pkgName, pkgVersion, includeAllIssues, spinner);
3134
+ const packageData = await fetchPackageInfo(pkgName, pkgVersion, includeAllIssues);
2856
3135
  if (packageData) {
2857
3136
  formatPackageInfo(packageData, {
2858
3137
  name: commandName,
2859
- outputJson,
2860
- outputMarkdown,
3138
+ outputKind,
2861
3139
  pkgName,
2862
- pkgVersion,
2863
- strict
2864
- }, spinner);
3140
+ pkgVersion
3141
+ });
3142
+ if (strict && objectSome(packageData.severityCount)) {
3143
+ // Let NodeJS exit gracefully but with exit(1)
3144
+ process$1.exitCode = 1;
3145
+ }
2865
3146
  }
2866
3147
  }
2867
3148
 
@@ -2903,6 +3184,12 @@ async function run$s(argv, importMeta, {
2903
3184
  importMeta,
2904
3185
  parentName
2905
3186
  });
3187
+ const {
3188
+ all,
3189
+ json,
3190
+ markdown,
3191
+ strict
3192
+ } = cli.flags;
2906
3193
  const [rawPkgName = ''] = cli.input;
2907
3194
  if (!rawPkgName || cli.input.length > 1) {
2908
3195
  // Use exit status of 2 to indicate incorrect usage, generally invalid
@@ -2923,12 +3210,11 @@ async function run$s(argv, importMeta, {
2923
3210
  }
2924
3211
  await getPackageInfo({
2925
3212
  commandName: `${parentName} ${config$s.commandName}`,
2926
- includeAllIssues: Boolean(cli.flags['all']),
2927
- outputJson: Boolean(cli.flags['json']),
2928
- outputMarkdown: Boolean(cli.flags['markdown']),
3213
+ includeAllIssues: Boolean(all),
3214
+ outputKind: json ? 'json' : markdown ? 'markdown' : 'print',
2929
3215
  pkgName,
2930
3216
  pkgVersion,
2931
- strict: Boolean(cli.flags['strict'])
3217
+ strict: Boolean(strict)
2932
3218
  });
2933
3219
  }
2934
3220
 
@@ -2961,7 +3247,7 @@ async function attemptLogin(apiBaseUrl, apiProxy) {
2961
3247
  throw new index.AuthError();
2962
3248
  }
2963
3249
  orgs = result.data;
2964
- spinner.success('API key verified');
3250
+ spinner.successAndStop('API key verified');
2965
3251
  } catch {
2966
3252
  spinner.errorAndStop('Invalid API key');
2967
3253
  return;
@@ -3002,8 +3288,10 @@ async function attemptLogin(apiBaseUrl, apiProxy) {
3002
3288
  const oldToken = index.getSetting('apiToken');
3003
3289
  try {
3004
3290
  applyLogin(apiToken, enforcedOrgs, apiBaseUrl, apiProxy);
3291
+ spinner.start();
3005
3292
  spinner.successAndStop(`API credentials ${oldToken ? 'updated' : 'set'}`);
3006
3293
  } catch {
3294
+ spinner.start();
3007
3295
  spinner.errorAndStop(`API login failed`);
3008
3296
  }
3009
3297
  }
@@ -3138,7 +3426,6 @@ async function convertGradleToMaven(target, bin, _out, verbose, gradleOpts) {
3138
3426
  logger.logger.log(`- src dir: \`${target}\``);
3139
3427
  logger.logger.groupEnd();
3140
3428
  }
3141
- spinner.start(`Converting gradle to maven from \`${bin}\` on \`${target}\`...`);
3142
3429
  try {
3143
3430
  // Run sbt with the init script we provide which should yield zero or more pom files.
3144
3431
  // We have to figure out where to store those pom files such that we can upload them and predict them through the GitHub API.
@@ -3148,8 +3435,9 @@ async function convertGradleToMaven(target, bin, _out, verbose, gradleOpts) {
3148
3435
  const initLocation = path.join(constants.rootDistPath, 'init.gradle');
3149
3436
  const commandArgs = ['--init-script', initLocation, ...gradleOpts, 'pom'];
3150
3437
  if (verbose) {
3151
- spinner.log('[VERBOSE] Executing:', bin, commandArgs);
3438
+ logger.logger.log('[VERBOSE] Executing:', bin, commandArgs);
3152
3439
  }
3440
+ spinner.start(`Converting gradle to maven from \`${bin}\` on \`${target}\`...`);
3153
3441
  const output = await spawn.spawn(bin, commandArgs, {
3154
3442
  cwd: target || '.'
3155
3443
  });
@@ -3167,7 +3455,8 @@ async function convertGradleToMaven(target, bin, _out, verbose, gradleOpts) {
3167
3455
  logger.logger.error(output.stderr);
3168
3456
  logger.logger.groupEnd();
3169
3457
  }
3170
- process.exit(1);
3458
+ process.exitCode = 1;
3459
+ return;
3171
3460
  }
3172
3461
  logger.logger.success('Executed gradle successfully');
3173
3462
  logger.logger.log('Reported exports:');
@@ -3204,14 +3493,15 @@ async function convertGradleToMaven(target, bin, _out, verbose, gradleOpts) {
3204
3493
  // spinner.successAndStop(`OK. File should be available in \`${out}\``)
3205
3494
  // }
3206
3495
  } catch (e) {
3207
- spinner.stop();
3208
- logger.logger.error('There was an unexpected error while running this' + (verbose ? '' : ' (use --verbose for details)'));
3496
+ spinner.errorAndStop('There was an unexpected error while running this' + (verbose ? '' : ' (use --verbose for details)'));
3209
3497
  if (verbose) {
3210
3498
  logger.logger.group('[VERBOSE] error:');
3211
3499
  logger.logger.log(e);
3212
3500
  logger.logger.groupEnd();
3213
3501
  }
3214
- process.exit(1);
3502
+ process.exitCode = 1;
3503
+ } finally {
3504
+ spinner.stop();
3215
3505
  }
3216
3506
  }
3217
3507
 
@@ -3321,9 +3611,11 @@ async function run$p(argv, importMeta, {
3321
3611
  // options or missing arguments.
3322
3612
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
3323
3613
  process.exitCode = 2;
3324
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
3325
- - The DIR arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}\n
3326
- - Can only accept one DIR (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}\n`);
3614
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
3615
+
3616
+ - The DIR arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}
3617
+
3618
+ - Can only accept one DIR (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}`);
3327
3619
  return;
3328
3620
  }
3329
3621
  let bin;
@@ -3377,8 +3669,9 @@ async function convertSbtToMaven(target, bin, out, verbose, sbtOpts) {
3377
3669
  // logger.log(`- dst dir: \`${out}\``)
3378
3670
  logger.logger.groupEnd();
3379
3671
  }
3380
- spinner.start(`Converting sbt to maven from \`${bin}\` on \`${target}\`...`);
3381
3672
  try {
3673
+ spinner.start(`Converting sbt to maven from \`${bin}\` on \`${target}\`...`);
3674
+
3382
3675
  // Run sbt with the init script we provide which should yield zero or more
3383
3676
  // pom files. We have to figure out where to store those pom files such that
3384
3677
  // we can upload them and predict them through the GitHub API. We could do a
@@ -3401,7 +3694,8 @@ async function convertSbtToMaven(target, bin, out, verbose, sbtOpts) {
3401
3694
  logger.logger.error(output.stderr);
3402
3695
  logger.logger.groupEnd();
3403
3696
  }
3404
- process.exit(1);
3697
+ process.exitCode = 1;
3698
+ return;
3405
3699
  }
3406
3700
  const poms = [];
3407
3701
  output.stdout.replace(/Wrote (.*?.pom)\n/g, (_all, fn) => {
@@ -3410,7 +3704,8 @@ async function convertSbtToMaven(target, bin, out, verbose, sbtOpts) {
3410
3704
  });
3411
3705
  if (!poms.length) {
3412
3706
  logger.logger.error('There were no errors from sbt but it seems to not have generated any poms either');
3413
- process.exit(1);
3707
+ process.exitCode = 1;
3708
+ return;
3414
3709
  }
3415
3710
  // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout
3416
3711
  // TODO: what to do with multiple output files? Do we want to dump them to stdout? Raw or with separators or ?
@@ -3424,7 +3719,8 @@ async function convertSbtToMaven(target, bin, out, verbose, sbtOpts) {
3424
3719
  logger.logger.error('Requested out target was stdout but there are multiple generated files');
3425
3720
  poms.forEach(fn => logger.logger.error('-', fn));
3426
3721
  logger.logger.error('Exiting now...');
3427
- process.exit(1);
3722
+ process.exitCode = 1;
3723
+ return;
3428
3724
  } else {
3429
3725
  // if (verbose) {
3430
3726
  // logger.log(
@@ -3440,14 +3736,15 @@ async function convertSbtToMaven(target, bin, out, verbose, sbtOpts) {
3440
3736
  logger.logger.success(`OK`);
3441
3737
  }
3442
3738
  } catch (e) {
3443
- spinner.stop();
3444
- logger.logger.error('There was an unexpected error while running this' + (verbose ? '' : ' (use --verbose for details)'));
3739
+ spinner?.errorAndStop('There was an unexpected error while running this' + (verbose ? '' : ' (use --verbose for details)'));
3445
3740
  if (verbose) {
3446
3741
  logger.logger.group('[VERBOSE] error:');
3447
3742
  logger.logger.log(e);
3448
3743
  logger.logger.groupEnd();
3449
3744
  }
3450
- process.exit(1);
3745
+ process.exitCode = 1;
3746
+ } finally {
3747
+ spinner.stop();
3451
3748
  }
3452
3749
  }
3453
3750
 
@@ -3555,9 +3852,11 @@ async function run$o(argv, importMeta, {
3555
3852
  // options or missing arguments.
3556
3853
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
3557
3854
  process.exitCode = 2;
3558
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
3559
- - The DIR or FILE arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}\n
3560
- - Can only accept one DIR or FILE (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}\n`);
3855
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
3856
+
3857
+ - The DIR or FILE arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}
3858
+
3859
+ - Can only accept one DIR or FILE (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}`);
3561
3860
  return;
3562
3861
  }
3563
3862
  let bin = 'sbt';
@@ -3813,9 +4112,11 @@ async function run$m(argv, importMeta, {
3813
4112
  // options or missing arguments.
3814
4113
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
3815
4114
  process.exitCode = 2;
3816
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
3817
- - The DIR arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}\n
3818
- - Can only accept one DIR (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}\n`);
4115
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
4116
+
4117
+ - The DIR arg is required ${!target ? colors.red('(missing!)') : target === '-' ? colors.red('(stdin is not supported)') : colors.green('(ok)')}
4118
+
4119
+ - Can only accept one DIR (make sure to escape spaces!) ${cli.input.length > 1 ? colors.red(`(received ${cli.input.length}!)`) : colors.green('(ok)')}`);
3819
4120
  return;
3820
4121
  }
3821
4122
  let bin;
@@ -5213,9 +5514,10 @@ async function run$g(argv, importMeta, {
5213
5514
  // options or missing arguments.
5214
5515
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
5215
5516
  process.exitCode = 2;
5216
- logger.logger.error(`
5217
- ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
5218
- - The json and markdown flags cannot be both set, pick one
5517
+ logger.logger.error(commonTags.stripIndents`
5518
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
5519
+
5520
+ - The json and markdown flags cannot be both set, pick one
5219
5521
  `);
5220
5522
  return;
5221
5523
  }
@@ -5250,13 +5552,10 @@ const config$f = {
5250
5552
  description: `Temporarily disable the Socket ${NPM} wrapper`,
5251
5553
  hidden: false,
5252
5554
  flags: {},
5253
- help: (command, config) => `
5555
+ help: command => `
5254
5556
  Usage
5255
5557
  $ ${command} <command>
5256
5558
 
5257
- Options
5258
- ${getFlagListOutput(config.flags, 6)}
5259
-
5260
5559
  Examples
5261
5560
  $ ${command} install
5262
5561
  `
@@ -5307,13 +5606,10 @@ const config$e = {
5307
5606
  description: `Temporarily disable the Socket ${NPX} wrapper`,
5308
5607
  hidden: false,
5309
5608
  flags: {},
5310
- help: (command, config) => `
5609
+ help: command => `
5311
5610
  Usage
5312
5611
  $ ${command} <command>
5313
5612
 
5314
- Options
5315
- ${getFlagListOutput(config.flags, 6)}
5316
-
5317
5613
  Examples
5318
5614
  $ ${command} install
5319
5615
  `
@@ -5360,10 +5656,8 @@ async function createReport(socketConfig, inputPaths, {
5360
5656
  cause
5361
5657
  });
5362
5658
  });
5363
- const packagePaths = await npmPaths.getPackageFiles(cwd, inputPaths, socketConfig, supportedFiles);
5364
- const {
5365
- length: packagePathsCount
5366
- } = packagePaths;
5659
+ const packagePaths = await npmPaths.getPackageFilesFullScans(cwd, inputPaths, supportedFiles, socketConfig);
5660
+ const packagePathsCount = packagePaths.length;
5367
5661
  if (packagePathsCount && debug.isDebug()) {
5368
5662
  for (const pkgPath of packagePaths) {
5369
5663
  debug.debugLog(`Uploading: ${pkgPath}`);
@@ -5453,11 +5747,12 @@ function formatReportDataOutput(reportId, data, commandName, outputJson, outputM
5453
5747
  logger.logger.log(JSON.stringify(data, undefined, 2));
5454
5748
  } else {
5455
5749
  const format = new index.ColorOrMarkdown(outputMarkdown);
5456
- logger.logger.log('\nDetailed info on socket.dev: ' + format.hyperlink(reportId, data.url, {
5750
+ logger.logger.log(commonTags.stripIndents`
5751
+ Detailed info on socket.dev: ${format.hyperlink(reportId, data.url, {
5457
5752
  fallbackToUrl: true
5458
- }));
5753
+ })}`);
5459
5754
  if (!outputMarkdown) {
5460
- logger.logger.log(colors.dim(`\nOr rerun ${colors.italic(commandName)} using the ${colors.italic('--json')} flag to get full JSON output`));
5755
+ logger.logger.log(colors.dim(`Or rerun ${colors.italic(commandName)} using the ${colors.italic('--json')} flag to get full JSON output`));
5461
5756
  }
5462
5757
  }
5463
5758
  if (strict && !data.healthy) {
@@ -5483,7 +5778,7 @@ const {
5483
5778
  } = constants;
5484
5779
  const config$d = {
5485
5780
  commandName: 'create',
5486
- description: 'Create a project report',
5781
+ description: '[Deprecated] Create a project report',
5487
5782
  hidden: false,
5488
5783
  flags: {
5489
5784
  ...commonFlags,
@@ -5501,27 +5796,9 @@ const config$d = {
5501
5796
  description: 'Will wait for and return the created report'
5502
5797
  }
5503
5798
  },
5504
- help: (command, config) => `
5505
- Usage
5506
- $ ${command} <paths-to-package-folders-and-files>
5507
-
5508
- Uploads the specified "package.json" and lock files for JavaScript, Python, and Go dependency manifests.
5509
- If any folder is specified, the ones found in there recursively are uploaded.
5510
-
5511
- Supports globbing such as "**/package.json", "**/requirements.txt", "**/pyproject.toml", and "**/go.mod".
5512
-
5513
- Ignores any file specified in your project's ".gitignore", your project's
5514
- "socket.yml" file's "projectIgnorePaths" and also has a sensible set of
5515
- default ignores from the "ignore-by-default" module.
5516
-
5517
- Options
5518
- ${getFlagListOutput(config.flags, 6)}
5519
-
5520
- Examples
5521
- $ ${command} .
5522
- $ ${command} '**/package.json'
5523
- $ ${command} /path/to/a/package.json /path/to/another/package.json
5524
- $ ${command} . --view --json
5799
+ help: () => `
5800
+ This command is deprecated in favor of \`socket scan create\`.
5801
+ It will be removed in the next major release of the CLI.
5525
5802
  `
5526
5803
  };
5527
5804
  const cmdReportCreate = {
@@ -5586,22 +5863,16 @@ const {
5586
5863
  } = constants;
5587
5864
  const config$c = {
5588
5865
  commandName: 'view',
5589
- description: 'View a project report',
5866
+ description: '[Deprecated] View a project report',
5590
5867
  hidden: false,
5591
5868
  flags: {
5592
5869
  ...commonFlags,
5593
5870
  ...outputFlags,
5594
5871
  ...validationFlags
5595
5872
  },
5596
- help: (command, config) => `
5597
- Usage
5598
- $ ${command} <report-identifier>
5599
-
5600
- Options
5601
- ${getFlagListOutput(config.flags, 6)}
5602
-
5603
- Examples
5604
- $ ${command} QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
5873
+ help: () => `
5874
+ This command is deprecated in favor of \`socket scan view\`.
5875
+ It will be removed in the next major release of the CLI.
5605
5876
  `
5606
5877
  };
5607
5878
  const cmdReportView = {
@@ -5626,9 +5897,11 @@ async function run$c(argv, importMeta, {
5626
5897
  // options or missing arguments.
5627
5898
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
5628
5899
  process.exitCode = 2;
5629
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
5630
- - Need at least one report ID ${!reportId ? colors.red('(missing!)') : colors.green('(ok)')}\n
5631
- - Can only handle a single report ID ${extraInput.length < 2 ? colors.red(`(received ${extraInput.length}!)`) : colors.green('(ok)')}\n`);
5900
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
5901
+
5902
+ - Need at least one report ID ${!reportId ? colors.red('(missing!)') : colors.green('(ok)')}
5903
+
5904
+ - Can only handle a single report ID ${extraInput.length < 2 ? colors.red(`(received ${extraInput.length}!)`) : colors.green('(ok)')}`);
5632
5905
  return;
5633
5906
  }
5634
5907
  if (cli.flags['dryRun']) {
@@ -5647,6 +5920,8 @@ async function run$c(argv, importMeta, {
5647
5920
  const description$2 = '[Deprecated] Project report related commands';
5648
5921
  const cmdReport = {
5649
5922
  description: description$2,
5923
+ hidden: true,
5924
+ // Deprecated in favor of `scan`
5650
5925
  async run(argv, importMeta, {
5651
5926
  parentName
5652
5927
  }) {
@@ -5769,9 +6044,11 @@ async function run$b(argv, importMeta, {
5769
6044
  // options or missing arguments.
5770
6045
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
5771
6046
  process.exitCode = 2;
5772
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
5773
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
5774
- - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}\n`);
6047
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6048
+
6049
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6050
+
6051
+ - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}`);
5775
6052
  return;
5776
6053
  }
5777
6054
  if (cli.flags['dryRun']) {
@@ -5851,10 +6128,13 @@ async function run$a(argv, importMeta, {
5851
6128
  // options or missing arguments.
5852
6129
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
5853
6130
  process.exitCode = 2;
5854
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
5855
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
5856
- - Repository name as the second argument ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}\n
5857
- - At least one TARGET (e.g. \`.\` or \`./package.json\`\n`);
6131
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6132
+
6133
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6134
+
6135
+ - Repository name as the second argument ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}
6136
+
6137
+ - At least one TARGET (e.g. \`.\` or \`./package.json\``);
5858
6138
  return;
5859
6139
  }
5860
6140
  if (cli.flags['dryRun']) {
@@ -5996,9 +6276,11 @@ async function run$9(argv, importMeta, {
5996
6276
  // options or missing arguments.
5997
6277
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
5998
6278
  process.exitCode = 2;
5999
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6000
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6001
- - At least one TARGET (e.g. \`.\` or \`./package.json\`\n`);
6279
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6280
+
6281
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6282
+
6283
+ - At least one TARGET (e.g. \`.\` or \`./package.json\``);
6002
6284
  return;
6003
6285
  }
6004
6286
  if (cli.flags['dryRun']) {
@@ -6128,10 +6410,13 @@ async function run$8(argv, importMeta, {
6128
6410
  // options or missing arguments.
6129
6411
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6130
6412
  process.exitCode = 2;
6131
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6132
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6133
- - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}\n
6134
- - At least one TARGET (e.g. \`.\` or \`./package.json\`\n`);
6413
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6414
+
6415
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6416
+
6417
+ - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}
6418
+
6419
+ - At least one TARGET (e.g. \`.\` or \`./package.json\``);
6135
6420
  return;
6136
6421
  }
6137
6422
  if (cli.flags['dryRun']) {
@@ -6238,9 +6523,13 @@ async function run$7(argv, importMeta, {
6238
6523
  // options or missing arguments.
6239
6524
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6240
6525
  process.exitCode = 2;
6241
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6242
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6243
- - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}\n`);
6526
+ logger.logger.error(commonTags.stripIndents`
6527
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6528
+
6529
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6530
+
6531
+ - Repository name using --repoName ${!repoName ? colors.red('(missing!)') : typeof repoName !== 'string' ? colors.red('(invalid!)') : colors.green('(ok)')}
6532
+ `);
6244
6533
  return;
6245
6534
  }
6246
6535
  if (cli.flags['dryRun']) {
@@ -6457,53 +6746,65 @@ async function createFullScan({
6457
6746
  targets = received ?? [];
6458
6747
  updatedInput = true;
6459
6748
  }
6460
- const packagePaths = await npmPaths.getPackageFilesFullScans(cwd, targets, supportedFiles);
6749
+
6750
+ // // TODO: we'll probably use socket.json or something else soon...
6751
+ // const absoluteConfigPath = path.join(cwd, 'socket.yml')
6752
+ // const socketConfig = await getSocketConfig(absoluteConfigPath)
6753
+
6754
+ const packagePaths = await npmPaths.getPackageFilesFullScans(cwd, targets, supportedFiles
6755
+ // socketConfig
6756
+ );
6461
6757
 
6462
6758
  // We're going to need an api token to suggest data because those suggestions
6463
6759
  // must come from data we already know. Don't error on missing api token yet.
6464
6760
  // If the api-token is not set, ignore it for the sake of suggestions.
6465
6761
  const apiToken = index.getDefaultToken();
6466
- if (apiToken && !orgSlug) {
6467
- const suggestion = await suggestOrgSlug(socketSdk);
6468
- if (suggestion) orgSlug = suggestion;
6469
- updatedInput = true;
6470
- }
6471
6762
 
6472
6763
  // If the current cwd is unknown and is used as a repo slug anyways, we will
6473
6764
  // first need to register the slug before we can use it.
6474
6765
  let repoDefaultBranch = '';
6475
-
6476
- // (Don't bother asking for the rest if we didn't get an org slug above)
6477
- if (apiToken && orgSlug && !repoName) {
6478
- const suggestion = await suggestRepoSlug(socketSdk, orgSlug);
6479
- if (suggestion) {
6480
- ({
6481
- defaultBranch: repoDefaultBranch,
6482
- slug: repoName
6483
- } = suggestion);
6766
+ if (apiToken) {
6767
+ if (!orgSlug) {
6768
+ const suggestion = await suggestOrgSlug(socketSdk);
6769
+ if (suggestion) orgSlug = suggestion;
6770
+ updatedInput = true;
6771
+ }
6772
+
6773
+ // (Don't bother asking for the rest if we didn't get an org slug above)
6774
+ if (orgSlug && !repoName) {
6775
+ const suggestion = await suggestRepoSlug(socketSdk, orgSlug);
6776
+ if (suggestion) {
6777
+ repoDefaultBranch = suggestion.defaultBranch;
6778
+ repoName = suggestion.slug;
6779
+ }
6780
+ updatedInput = true;
6484
6781
  }
6485
- updatedInput = true;
6486
- }
6487
6782
 
6488
- // (Don't bother asking for the rest if we didn't get an org/repo above)
6489
- if (apiToken && orgSlug && repoName && !branchName) {
6490
- const suggestion = await suggestBranchSlug(repoDefaultBranch);
6491
- if (suggestion) branchName = suggestion;
6492
- updatedInput = true;
6783
+ // (Don't bother asking for the rest if we didn't get an org/repo above)
6784
+ if (orgSlug && repoName && !branchName) {
6785
+ const suggestion = await suggestBranchSlug(repoDefaultBranch);
6786
+ if (suggestion) branchName = suggestion;
6787
+ updatedInput = true;
6788
+ }
6493
6789
  }
6494
6790
  if (!orgSlug || !repoName || !branchName || !packagePaths.length) {
6495
6791
  // Use exit status of 2 to indicate incorrect usage, generally invalid
6496
6792
  // options or missing arguments.
6497
6793
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6498
6794
  process$1.exitCode = 2;
6499
- logger.logger.error(`
6500
- ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6501
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6502
- - Repository name using --repo ${!repoName ? colors.red('(missing!)') : colors.green('(ok)')}\n
6503
- - Branch name using --branch ${!branchName ? colors.red('(missing!)') : colors.green('(ok)')}\n
6504
- - At least one TARGET (e.g. \`.\` or \`./package.json\`) ${!packagePaths.length ? colors.red(targets.length > 0 ? '(TARGET' + (targets.length ? 's' : '') + ' contained no matching/supported files!)' : '(missing)') : colors.green('(ok)')}\n
6505
- ${!apiToken ? 'Note: was unable to make suggestions because no API Token was found; this would make command fail regardless\n' : ''}
6506
- `);
6795
+ logger.logger.error(commonTags.stripIndents`
6796
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6797
+
6798
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6799
+
6800
+ - Repository name using --repo ${!repoName ? colors.red('(missing!)') : colors.green('(ok)')}
6801
+
6802
+ - Branch name using --branch ${!branchName ? colors.red('(missing!)') : colors.green('(ok)')}
6803
+
6804
+ - At least one TARGET (e.g. \`.\` or \`./package.json\`) ${!packagePaths.length ? colors.red(targets.length > 0 ? '(TARGET' + (targets.length ? 's' : '') + ' contained no matching/supported files!)' : '(missing)') : colors.green('(ok)')}
6805
+
6806
+ ${!apiToken ? 'Note: was unable to make suggestions because no API Token was found; this would make command fail regardless' : ''}
6807
+ `);
6507
6808
  return;
6508
6809
  }
6509
6810
  if (updatedInput) {
@@ -6519,7 +6820,7 @@ async function createFullScan({
6519
6820
  logger.logger.log('[ReadOnly] Bailing now');
6520
6821
  return;
6521
6822
  }
6522
- spinner.start('Creating a scan...');
6823
+ spinner.start(`Creating a scan with ${packagePaths.length} packages...`);
6523
6824
  const result = await handleApiCall(socketSdk.createOrgFullScan(orgSlug, {
6524
6825
  repo: repoName,
6525
6826
  branch: branchName,
@@ -6619,13 +6920,29 @@ const config$6 = {
6619
6920
  shortFlag: 't',
6620
6921
  default: false,
6621
6922
  description: 'Set the visibility (true/false) of the scan in your dashboard'
6923
+ },
6924
+ view: {
6925
+ type: 'boolean',
6926
+ shortFlag: 'v',
6927
+ default: true,
6928
+ description: 'Will wait for and return the created report. Use --no-view to disable.'
6622
6929
  }
6623
6930
  },
6931
+ // TODO: your project's "socket.yml" file's "projectIgnorePaths"
6624
6932
  help: (command, config) => `
6625
6933
  Usage
6626
6934
  $ ${command} [...options] <org> <TARGET> [TARGET...]
6627
6935
 
6628
- Where TARGET is a FILE or DIR that _must_ be inside the CWD.
6936
+ Uploads the specified "package.json" and lock files for JavaScript, Python,
6937
+ Go, Scala, Gradle, and Kotlin dependency manifests.
6938
+ If any folder is specified, the ones found in there recursively are uploaded.
6939
+
6940
+ Supports globbing such as "**/package.json", "**/requirements.txt", etc.
6941
+
6942
+ Ignores any file specified in your project's ".gitignore" and also has a
6943
+ sensible set of default ignores from the "ignore-by-default" module.
6944
+
6945
+ TARGET should be a FILE or DIR that _must_ be inside the CWD.
6629
6946
 
6630
6947
  When a FILE is given only that FILE is targeted. Otherwise any eligible
6631
6948
  files in the given DIR will be considered.
@@ -6657,7 +6974,8 @@ async function run$6(argv, importMeta, {
6657
6974
  branch: branchName,
6658
6975
  repo: repoName
6659
6976
  } = cli.flags;
6660
- const apiToken = index.getDefaultToken();
6977
+ const apiToken = index.getDefaultToken(); // This checks if we _can_ suggest anything
6978
+
6661
6979
  if (!apiToken && (!orgSlug || !repoName || !branchName || !targets.length)) {
6662
6980
  // Without api token we cannot recover because we can't request more info
6663
6981
  // from the server, to match and help with the current cwd/git status.
@@ -6665,13 +6983,18 @@ async function run$6(argv, importMeta, {
6665
6983
  // options or missing arguments.
6666
6984
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6667
6985
  process$1.exitCode = 2;
6668
- logger.logger.error(`
6669
- ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6670
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6671
- - Repository name using --repo ${!repoName ? colors.red('(missing!)') : colors.green('(ok)')}\n
6672
- - Branch name using --branch ${!branchName ? colors.red('(missing!)') : colors.green('(ok)')}\n
6673
- - At least one TARGET (e.g. \`.\` or \`./package.json\`) ${!targets.length ? '(missing)' : colors.green('(ok)')}\n
6674
- (Additionally, no API Token was set so we cannot auto-discover these details)\n
6986
+ logger.logger.error(commonTags.stripIndents`
6987
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
6988
+
6989
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
6990
+
6991
+ - Repository name using --repo ${!repoName ? colors.red('(missing!)') : colors.green('(ok)')}
6992
+
6993
+ - Branch name using --branch ${!branchName ? colors.red('(missing!)') : colors.green('(ok)')}
6994
+
6995
+ - At least one TARGET (e.g. \`.\` or \`./package.json\`) ${!targets.length ? '(missing)' : colors.green('(ok)')}
6996
+
6997
+ (Additionally, no API Token was set so we cannot auto-discover these details)
6675
6998
  `);
6676
6999
  return;
6677
7000
  }
@@ -6698,7 +7021,14 @@ async function run$6(argv, importMeta, {
6698
7021
  });
6699
7022
  }
6700
7023
 
6701
- async function deleteOrgFullScan(orgSlug, fullScanId, apiToken) {
7024
+ async function deleteOrgFullScan(orgSlug, fullScanId) {
7025
+ const apiToken = index.getDefaultToken();
7026
+ if (!apiToken) {
7027
+ throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
7028
+ }
7029
+ await deleteOrgFullScanWithToken(orgSlug, fullScanId, apiToken);
7030
+ }
7031
+ async function deleteOrgFullScanWithToken(orgSlug, fullScanId, apiToken) {
6702
7032
  // Lazily access constants.spinner.
6703
7033
  const {
6704
7034
  spinner
@@ -6755,35 +7085,77 @@ async function run$5(argv, importMeta, {
6755
7085
  // options or missing arguments.
6756
7086
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6757
7087
  process.exitCode = 2;
6758
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6759
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6760
- - Full Scan ID to delete as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
7088
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
7089
+
7090
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
7091
+
7092
+ - Full Scan ID to delete as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}`);
6761
7093
  return;
6762
7094
  }
6763
7095
  if (cli.flags['dryRun']) {
6764
7096
  logger.logger.log(DRY_RUN_BAIL_TEXT$5);
6765
7097
  return;
6766
7098
  }
7099
+ await deleteOrgFullScan(orgSlug, fullScanId);
7100
+ }
7101
+
7102
+ // @ts-ignore
7103
+ async function listFullScans({
7104
+ direction,
7105
+ from_time,
7106
+ orgSlug,
7107
+ outputKind,
7108
+ page,
7109
+ per_page,
7110
+ sort
7111
+ }) {
6767
7112
  const apiToken = index.getDefaultToken();
6768
7113
  if (!apiToken) {
6769
7114
  throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
6770
7115
  }
6771
- await deleteOrgFullScan(orgSlug, fullScanId, apiToken);
7116
+ await listFullScansWithToken({
7117
+ apiToken,
7118
+ direction,
7119
+ from_time,
7120
+ orgSlug,
7121
+ outputKind,
7122
+ page,
7123
+ per_page,
7124
+ sort
7125
+ });
6772
7126
  }
6773
-
6774
- // @ts-ignore
6775
- async function listFullScans(orgSlug, input, apiToken) {
7127
+ async function listFullScansWithToken({
7128
+ apiToken,
7129
+ direction,
7130
+ from_time,
7131
+ orgSlug,
7132
+ outputKind,
7133
+ page,
7134
+ per_page,
7135
+ sort
7136
+ }) {
6776
7137
  // Lazily access constants.spinner.
6777
7138
  const {
6778
7139
  spinner
6779
7140
  } = constants;
6780
- spinner.start('Listing scans...');
7141
+ spinner.start('Fetching list of scans...');
6781
7142
  const socketSdk = await index.setupSdk(apiToken);
6782
- const result = await handleApiCall(socketSdk.getOrgFullScanList(orgSlug, input), 'Listing scans');
7143
+ const result = await handleApiCall(socketSdk.getOrgFullScanList(orgSlug, {
7144
+ sort,
7145
+ direction,
7146
+ per_page,
7147
+ page,
7148
+ from: from_time
7149
+ }), 'Listing scans');
6783
7150
  if (!result.success) {
6784
7151
  handleUnsuccessfulApiResponse('getOrgFullScanList', result, spinner);
6785
7152
  return;
6786
7153
  }
7154
+ spinner.stop(`Fetch complete`);
7155
+ if (outputKind === 'json') {
7156
+ logger.logger.log(result.data);
7157
+ return;
7158
+ }
6787
7159
  const options = {
6788
7160
  columns: [{
6789
7161
  field: 'id',
@@ -6811,7 +7183,6 @@ async function listFullScans(orgSlug, input, apiToken) {
6811
7183
  branch: d.branch
6812
7184
  };
6813
7185
  });
6814
- spinner.stop(`Listing scans for: ${orgSlug}`);
6815
7186
  logger.logger.log(chalkTable(options, formattedResults));
6816
7187
  }
6817
7188
 
@@ -6893,47 +7264,64 @@ async function run$4(argv, importMeta, {
6893
7264
  // options or missing arguments.
6894
7265
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6895
7266
  process.exitCode = 2;
6896
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6897
- - Org name as the argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
7267
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
7268
+
7269
+ - Org name as the argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}`);
6898
7270
  return;
6899
7271
  }
6900
7272
  if (cli.flags['dryRun']) {
6901
7273
  logger.logger.log(DRY_RUN_BAIL_TEXT$4);
6902
7274
  return;
6903
7275
  }
7276
+ await listFullScans({
7277
+ direction: String(cli.flags['direction'] || ''),
7278
+ from_time: String(cli.flags['fromTime'] || ''),
7279
+ orgSlug,
7280
+ outputKind: cli.flags['json'] ? 'json' : cli.flags['markdown'] ? 'markdown' : 'print',
7281
+ page: Number(cli.flags['page'] || 1),
7282
+ per_page: Number(cli.flags['perPage'] || 30),
7283
+ sort: String(cli.flags['sort'] || '')
7284
+ });
7285
+ }
7286
+
7287
+ async function getOrgScanMetadata(orgSlug, scanId, outputKind) {
6904
7288
  const apiToken = index.getDefaultToken();
6905
7289
  if (!apiToken) {
6906
7290
  throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
6907
7291
  }
6908
- await listFullScans(orgSlug,
6909
- // TODO: refine this object to what we need
6910
- {
6911
- outputJson: cli.flags['json'],
6912
- outputMarkdown: cli.flags['markdown'],
6913
- orgSlug,
6914
- sort: cli.flags['sort'],
6915
- direction: cli.flags['direction'],
6916
- per_page: cli.flags['perPage'],
6917
- page: cli.flags['page'],
6918
- from_time: cli.flags['fromTime'],
6919
- until_time: cli.flags['untilTime']
6920
- }, apiToken);
7292
+ await getOrgScanMetadataWithToken(orgSlug, scanId, apiToken, outputKind);
6921
7293
  }
6922
-
6923
- async function getOrgScanMetadata(orgSlug, scanId, apiToken) {
7294
+ async function getOrgScanMetadataWithToken(orgSlug, scanId, apiToken, outputKind) {
6924
7295
  // Lazily access constants.spinner.
6925
7296
  const {
6926
7297
  spinner
6927
7298
  } = constants;
6928
- spinner.start("Getting scan's metadata...");
7299
+ spinner.start('Fetching meta data for a full scan...');
6929
7300
  const socketSdk = await index.setupSdk(apiToken);
6930
7301
  const result = await handleApiCall(socketSdk.getOrgFullScanMetadata(orgSlug, scanId), 'Listing scans');
6931
7302
  if (!result.success) {
6932
7303
  handleUnsuccessfulApiResponse('getOrgFullScanMetadata', result, spinner);
6933
7304
  return;
6934
7305
  }
6935
- spinner.stop('Scan metadata:');
6936
- logger.logger.log(result.data);
7306
+ spinner?.successAndStop('Fetched the meta data\n');
7307
+ if (outputKind === 'json') {
7308
+ logger.logger.log(result.data);
7309
+ } else {
7310
+ // Markdown = print
7311
+ if (outputKind === 'markdown') {
7312
+ logger.logger.log('# Scan meta data\n');
7313
+ }
7314
+ logger.logger.log(`Scan ID: ${scanId}\n`);
7315
+ for (const [key, value] of Object.entries(result.data)) {
7316
+ if (['id', 'updated_at', 'organization_id', 'repository_id', 'commit_hash', 'html_report_url'].includes(key)) continue;
7317
+ logger.logger.log(`- ${key}:`, value);
7318
+ }
7319
+ if (outputKind === 'markdown') {
7320
+ logger.logger.log(`\nYou can view this report at: [${result.data.html_report_url}](${result.data.html_report_url})\n`);
7321
+ } else {
7322
+ logger.logger.log(`\nYou can view this report at: ${result.data.html_report_url}]\n`);
7323
+ }
7324
+ }
6937
7325
  }
6938
7326
 
6939
7327
  const {
@@ -6978,44 +7366,117 @@ async function run$3(argv, importMeta, {
6978
7366
  // options or missing arguments.
6979
7367
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
6980
7368
  process.exitCode = 2;
6981
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
6982
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
6983
- - Full Scan ID to inspect as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
7369
+ logger.logger.error(commonTags.stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
7370
+
7371
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
7372
+
7373
+ - Full Scan ID to inspect as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}`);
6984
7374
  return;
6985
7375
  }
6986
7376
  if (cli.flags['dryRun']) {
6987
7377
  logger.logger.log(DRY_RUN_BAIL_TEXT$3);
6988
7378
  return;
6989
7379
  }
7380
+ await getOrgScanMetadata(orgSlug, fullScanId, cli.flags['json'] ? 'json' : cli.flags['markdown'] ? 'markdown' : 'print');
7381
+ }
7382
+
7383
+ async function streamFullScan(orgSlug, fullScanId, file) {
7384
+ // Lazily access constants.spinner.
7385
+ const {
7386
+ spinner
7387
+ } = constants;
6990
7388
  const apiToken = index.getDefaultToken();
6991
7389
  if (!apiToken) {
6992
7390
  throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
6993
7391
  }
6994
- await getOrgScanMetadata(orgSlug, fullScanId, apiToken);
7392
+ spinner.start('Fetching scan...');
7393
+ const socketSdk = await index.setupSdk(apiToken);
7394
+ const data = await handleApiCall(socketSdk.getOrgFullScan(orgSlug, fullScanId, file === '-' ? undefined : file), 'Fetching a scan');
7395
+ if (!data?.success) {
7396
+ handleUnsuccessfulApiResponse('getOrgFullScan', data, spinner);
7397
+ return;
7398
+ }
7399
+ spinner?.successAndStop(file ? `Full scan details written to ${file}` : 'stdout');
7400
+ return data;
6995
7401
  }
6996
7402
 
6997
- async function getFullScan(orgSlug, fullScanId, file, apiToken) {
7403
+ async function getFullScan(orgSlug, fullScanId) {
6998
7404
  // Lazily access constants.spinner.
6999
7405
  const {
7000
7406
  spinner
7001
7407
  } = constants;
7002
- spinner.start('Streaming scan...');
7003
- const socketSdk = await index.setupSdk(apiToken);
7004
- const data = await handleApiCall(socketSdk.getOrgFullScan(orgSlug, fullScanId, file === '-' ? undefined : file), 'Streaming a scan');
7005
- if (data?.success) {
7006
- spinner.stop(file ? `Full scan details written to ${file}` : '');
7007
- } else {
7008
- handleUnsuccessfulApiResponse('getOrgFullScan', data, spinner);
7408
+ const apiToken = index.getDefaultToken();
7409
+ if (!apiToken) {
7410
+ throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
7411
+ }
7412
+ spinner.start('Fetching full-scan...');
7413
+ const response = await queryAPI(`orgs/${orgSlug}/full-scans/${encodeURIComponent(fullScanId)}`, apiToken);
7414
+ spinner.stop('Fetch complete.');
7415
+ if (!response.ok) {
7416
+ const err = await handleAPIError(response.status);
7417
+ logger.logger.error(`${colors.bgRed(colors.white(response.statusText))}: Fetch error: ${err}`);
7418
+ return;
7009
7419
  }
7420
+
7421
+ // This is nd-json; each line is a json object
7422
+ const jsons = await response.text();
7423
+ const lines = jsons.split('\n').filter(Boolean);
7424
+ const data = lines.map(line => {
7425
+ try {
7426
+ return JSON.parse(line);
7427
+ } catch {
7428
+ console.error('At least one line item was returned that could not be parsed as JSON...');
7429
+ return {};
7430
+ }
7431
+ });
7010
7432
  return data;
7011
7433
  }
7012
7434
 
7435
+ async function viewFullScan(orgSlug, fullScanId, filePath) {
7436
+ const artifacts = await getFullScan(orgSlug, fullScanId);
7437
+ if (!artifacts) return;
7438
+ const display = artifacts.map(art => {
7439
+ const author = Array.isArray(art.author) ? `${art.author[0]}${art.author.length > 1 ? ' et.al.' : ''}` : art.author;
7440
+ return {
7441
+ type: art.type,
7442
+ name: art.name,
7443
+ version: art.version,
7444
+ author,
7445
+ score: JSON.stringify(art.score)
7446
+ };
7447
+ });
7448
+ const md = mdTable(display, ['type', 'version', 'name', 'author', 'score']);
7449
+ const report = `
7450
+ # Scan Details
7451
+
7452
+ These are the artifacts and their scores found.
7453
+
7454
+ Sscan ID: ${fullScanId}
7455
+
7456
+ ${md}
7457
+
7458
+ View this report at: https://socket.dev/dashboard/org/${orgSlug}/sbom/${fullScanId}
7459
+ `.trim() + '\n';
7460
+ if (filePath && filePath !== '-') {
7461
+ try {
7462
+ await fs$1.writeFile(filePath, report, 'utf8');
7463
+ logger.logger.log(`Data successfully written to ${filePath}`);
7464
+ } catch (e) {
7465
+ logger.logger.error('There was an error trying to write the json to disk');
7466
+ logger.logger.error(e);
7467
+ process.exitCode = 1;
7468
+ }
7469
+ } else {
7470
+ logger.logger.log(report);
7471
+ }
7472
+ }
7473
+
7013
7474
  const {
7014
7475
  DRY_RUN_BAIL_TEXT: DRY_RUN_BAIL_TEXT$2
7015
7476
  } = constants;
7016
7477
  const config$2 = {
7017
- commandName: 'stream',
7018
- description: 'Stream the output of a scan',
7478
+ commandName: 'view',
7479
+ description: 'View the raw results of a scan',
7019
7480
  hidden: false,
7020
7481
  flags: {
7021
7482
  ...commonFlags,
@@ -7034,7 +7495,7 @@ const config$2 = {
7034
7495
  $ ${command} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 ./stream.txt
7035
7496
  `
7036
7497
  };
7037
- const cmdScanStream = {
7498
+ const cmdScanView = {
7038
7499
  description: config$2.description,
7039
7500
  hidden: config$2.hidden,
7040
7501
  run: run$2
@@ -7054,23 +7515,27 @@ async function run$2(argv, importMeta, {
7054
7515
  // options or missing arguments.
7055
7516
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
7056
7517
  process.exitCode = 2;
7057
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
7058
- - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}\n
7059
- - Full Scan ID to fetch as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}\n`);
7518
+ logger.logger.error(commonTags.stripIndents`
7519
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
7520
+
7521
+ - Org name as the first argument ${!orgSlug ? colors.red('(missing!)') : colors.green('(ok)')}
7522
+
7523
+ - Full Scan ID to fetch as second argument ${!fullScanId ? colors.red('(missing!)') : colors.green('(ok)')}
7524
+ `);
7060
7525
  return;
7061
7526
  }
7062
7527
  if (cli.flags['dryRun']) {
7063
7528
  logger.logger.log(DRY_RUN_BAIL_TEXT$2);
7064
7529
  return;
7065
7530
  }
7066
- const apiToken = index.getDefaultToken();
7067
- if (!apiToken) {
7068
- throw new index.AuthError('User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.');
7531
+ if (cli.flags['json']) {
7532
+ await streamFullScan(orgSlug, fullScanId, file);
7533
+ } else {
7534
+ await viewFullScan(orgSlug, fullScanId, file);
7069
7535
  }
7070
- await getFullScan(orgSlug, fullScanId, file, apiToken);
7071
7536
  }
7072
7537
 
7073
- const description = 'Scans related commands';
7538
+ const description = 'Full Scan related commands';
7074
7539
  const cmdScan = {
7075
7540
  description,
7076
7541
  async run(argv, importMeta, {
@@ -7078,11 +7543,19 @@ const cmdScan = {
7078
7543
  }) {
7079
7544
  await meowWithSubcommands({
7080
7545
  create: cmdScanCreate,
7081
- stream: cmdScanStream,
7082
7546
  list: cmdScanList,
7083
7547
  del: cmdScanDel,
7084
- metadata: cmdScanMetadata
7548
+ metadata: cmdScanMetadata,
7549
+ view: cmdScanView
7085
7550
  }, {
7551
+ aliases: {
7552
+ // Backwards compat. TODO: Drop next major bump
7553
+ stream: {
7554
+ description: cmdScanView.description,
7555
+ hidden: true,
7556
+ argv: ['view'] // Original args will be appended (!)
7557
+ }
7558
+ },
7086
7559
  argv,
7087
7560
  description,
7088
7561
  importMeta,
@@ -7255,7 +7728,7 @@ function addSocketWrapper(file) {
7255
7728
  }
7256
7729
  // TODO: pretty sure you need to source the file or restart
7257
7730
  // any terminal session before changes are reflected.
7258
- logger.logger.log(`
7731
+ logger.logger.log(commonTags.stripIndents`
7259
7732
  The alias was added to ${file}. Running 'npm install' will now be wrapped in Socket's "safe npm" 🎉
7260
7733
  If you want to disable it at any time, run \`socket wrapper --disable\`
7261
7734
  `);
@@ -7408,8 +7881,11 @@ async function run(argv, importMeta, {
7408
7881
  // options or missing arguments.
7409
7882
  // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
7410
7883
  process.exitCode = 2;
7411
- logger.logger.error(`${colors.bgRed(colors.white('Input error'))}: Please provide the required flags:\n
7412
- - Must use --enabled or --disabled\n`);
7884
+ logger.logger.error(commonTags.stripIndents`
7885
+ ${colors.bgRed(colors.white('Input error'))}: Please provide the required flags:
7886
+
7887
+ - Must use --enabled or --disabled
7888
+ `);
7413
7889
  return;
7414
7890
  }
7415
7891
  if (cli.flags['dryRun']) {
@@ -7518,5 +7994,5 @@ void (async () => {
7518
7994
  await index.captureException(e);
7519
7995
  }
7520
7996
  })();
7521
- //# debugId=436f332e-aa2a-480e-ac61-6da13459f0cb
7997
+ //# debugId=4fe0e5e5-54cb-444b-88dc-36bf76ff766a
7522
7998
  //# sourceMappingURL=cli.js.map