@intecoag/inteco-cli 1.6.1 → 1.7.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intecoag/inteco-cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "CLI-Tools for Inteco",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -21,6 +21,7 @@ import syncConfig from './modules/syncConfig.js';
21
21
  import configMutation from './modules/configMutation.js';
22
22
  import bundleProduct from './modules/bundleProduct.js';
23
23
  import { azureCreateSyncConfig, azurePush, azurePull } from './modules/azureSync.js';
24
+ import githubSecurityAdvisories from './modules/githubSecurityAdvisories.js';
24
25
 
25
26
  import updateNotifier from 'update-notifier';
26
27
 
@@ -107,6 +108,9 @@ switch (cli.input[0]) {
107
108
  case "azure_sync_pull":
108
109
  azurePull();
109
110
  break;
111
+ case "github_security_advisories":
112
+ githubSecurityAdvisories();
113
+ break;
110
114
  default:
111
115
  cli.showHelp()
112
116
  break;
@@ -0,0 +1,160 @@
1
+ import prompts from 'prompts';
2
+ import chalk from 'chalk';
3
+ import Table from 'cli-table3';
4
+ import ora from 'ora';
5
+ import { getGithubToken, fetchPaginatedGithubAPI, fetchGithubAPI } from '../utils/github/github.js';
6
+
7
+ async function githubSecurityAdvisories() {
8
+ console.log();
9
+
10
+ // Get GitHub authentication token
11
+ let token;
12
+ let authMethod;
13
+
14
+ try {
15
+ ({ token, authMethod } = await getGithubToken());
16
+ } catch (error) {
17
+ console.log();
18
+ console.error(chalk.red(`✗ ${error.message}`));
19
+ console.log(chalk.yellow('\nAuthentication setup:'));
20
+ console.log(chalk.gray(' Option 1: Install GitHub CLI and run: gh auth login'));
21
+ console.log(chalk.gray(' Option 2: Set GITHUB_TOKEN environment variable'));
22
+ console.log();
23
+ process.exit(1);
24
+ }
25
+
26
+ console.log(chalk.green(`✓ Authenticated via: ${authMethod}`));
27
+ console.log();
28
+
29
+ const responses = await prompts([
30
+ {
31
+ type: 'text',
32
+ name: 'organization',
33
+ message: 'GitHub Organization Name?',
34
+ initial: 'intecoag',
35
+ validate: (value) => value.length > 0 || 'Organization name is required'
36
+ }
37
+ ], {
38
+ onCancel: () => {
39
+ console.log();
40
+ console.log(chalk.red('Cancelled GitHub Security Advisories check!'));
41
+ console.log();
42
+ process.exit(0);
43
+ }
44
+ });
45
+
46
+ const { organization } = responses;
47
+
48
+ const spinner = ora('Fetching repositories...').start();
49
+
50
+ try {
51
+ // Fetch all repositories for the organization
52
+ const baseUrl = `https://api.github.com/orgs/${organization}/repos`;
53
+ const repos = await fetchPaginatedGithubAPI(baseUrl, token);
54
+
55
+ if (repos.length === 0) {
56
+ spinner.warn(`No repositories found in organization "${organization}"`);
57
+ console.log();
58
+ return;
59
+ }
60
+
61
+ spinner.text = `Found ${repos.length} repositories. Checking security advisories...`;
62
+
63
+ // Check security advisories for each repo
64
+ const results = [];
65
+ for (let i = 0; i < repos.length; i++) {
66
+ const repo = repos[i];
67
+ spinner.text = `Checking security advisories... [${i + 1}/${repos.length}] ${repo.name}`;
68
+
69
+ const advisories = await fetchSecurityAdvisories(organization, repo.name, token);
70
+
71
+ if (advisories.length > 0) {
72
+ results.push({
73
+ repository: repo.name,
74
+ advisoryCount: advisories.length,
75
+ url: repo.html_url
76
+ });
77
+ }
78
+ }
79
+
80
+ spinner.succeed('Security advisories check completed!');
81
+ console.log();
82
+
83
+ if (results.length === 0) {
84
+ console.log(chalk.green.bold('✓ No security advisories found in any repositories!'));
85
+ console.log();
86
+ return;
87
+ }
88
+
89
+ // Sort by advisory count (descending)
90
+ results.sort((a, b) => b.advisoryCount - a.advisoryCount);
91
+
92
+ // Display results in a table
93
+ const table = new Table({
94
+ head: [
95
+ chalk.bold.cyan('Repository'),
96
+ chalk.bold.cyan('Advisories'),
97
+ chalk.bold.cyan('URL')
98
+ ],
99
+ style: {
100
+ head: [],
101
+ border: ['cyan']
102
+ },
103
+ colWidths: [40, 15, 60],
104
+ wordWrap: true
105
+ });
106
+
107
+ for (const result of results) {
108
+ const advisoryColor = chalk.red;
109
+ table.push([
110
+ result.repository,
111
+ advisoryColor.bold(result.advisoryCount.toString()),
112
+ chalk.blue.underline(result.url)
113
+ ]);
114
+ }
115
+
116
+ console.log(table.toString());
117
+ console.log();
118
+ console.log(chalk.yellow.bold(`Summary: ${results.length} repository/repositories with open security advisories`));
119
+ console.log();
120
+
121
+ } catch (error) {
122
+ console.log();
123
+ spinner.fail('Error checking security advisories');
124
+
125
+ if (error.message.includes('Organization')) {
126
+ console.error(chalk.red(`✗ ${error.message}`));
127
+ } else if (error.message.includes('authentication')) {
128
+ console.error(chalk.red(`✗ ${error.message}`));
129
+ console.log(chalk.yellow('\nAuthentication setup:'));
130
+ console.log(chalk.gray(' Option 1: Install GitHub CLI and run: gh auth login'));
131
+ console.log(chalk.gray(' Option 2: Set GITHUB_TOKEN environment variable'));
132
+ } else {
133
+ console.error(chalk.red(`Error: ${error.message}`));
134
+ }
135
+ console.log();
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ async function fetchSecurityAdvisories(organization, repository, token) {
141
+ const url = `https://api.github.com/repos/${organization}/${repository}/dependabot/alerts`;
142
+
143
+ try {
144
+ const res = await fetchGithubAPI(url, token);
145
+ const alerts = await res.json();
146
+
147
+ // Filter for open and unresolved alerts
148
+ const openAlerts = Array.isArray(alerts) ? alerts.filter(alert => alert.state === 'open') : [];
149
+
150
+ return openAlerts;
151
+ } catch (error) {
152
+ // If the endpoint is not available or the repo is not accessible, return empty array
153
+ if (error.message.includes('404') || error.message.includes('403')) {
154
+ return [];
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ export default githubSecurityAdvisories;
@@ -51,12 +51,15 @@
51
51
  "desc": "Shows the changelog history"
52
52
  },
53
53
  "azure_sync_config": {
54
- "desc": "Creates or updates the .az-sync configuration file"
54
+ "desc": "Creates or updates the .az-sync configuration file (requires Azure-CLI)"
55
55
  },
56
56
  "azure_push": {
57
- "desc": "Pushes local files to Azure Blob Storage using checksum comparison"
57
+ "desc": "Pushes local files to Azure Blob Storage using checksum comparison (requires Azure-CLI)"
58
58
  },
59
59
  "azure_pull": {
60
- "desc": "Pulls blobs to local files using checksum comparison"
60
+ "desc": "Pulls blobs to local files using checksum comparison (requires Azure-CLI)"
61
+ },
62
+ "github_security_advisories": {
63
+ "desc": "Checks all GitHub repositories in an organization for open and unresolved security advisories (requires GitHub CLI or GITHUB_TOKEN)"
61
64
  }
62
65
  }
@@ -0,0 +1,99 @@
1
+ import chalk from 'chalk';
2
+ import { execSync } from 'child_process';
3
+
4
+ /**
5
+ * Get GitHub authentication token from environment or GitHub CLI
6
+ * @returns {Promise<{token: string, authMethod: string}>} Token and authentication method
7
+ * @throws {Error} If no authentication method is available
8
+ */
9
+ export async function getGithubToken() {
10
+ let token = null;
11
+ let authMethod = null;
12
+
13
+ // 1. Try GITHUB_TOKEN environment variable
14
+ if (process.env.GITHUB_TOKEN) {
15
+ token = process.env.GITHUB_TOKEN;
16
+ authMethod = 'GITHUB_TOKEN (environment variable)';
17
+ }
18
+
19
+ // 2. Try GitHub CLI authentication if available
20
+ if (!token) {
21
+ try {
22
+ token = execSync('gh auth token', { encoding: 'utf-8' }).trim();
23
+ authMethod = 'GitHub CLI';
24
+ } catch (error) {
25
+ // GitHub CLI not available or not authenticated
26
+ }
27
+ }
28
+
29
+ // 3. If no token found, prompt user to authenticate
30
+ if (!token) {
31
+ console.log(chalk.yellow('No GitHub authentication found. Please authenticate with GitHub CLI:'));
32
+ console.log(chalk.gray(' Run: gh auth login'));
33
+ console.log();
34
+ throw new Error('GitHub authentication required. Please run "gh auth login" or set GITHUB_TOKEN environment variable.');
35
+ }
36
+
37
+ return { token, authMethod };
38
+ }
39
+
40
+ /**
41
+ * Make a GitHub API fetch request with proper headers and error handling
42
+ * @param {string} url - GitHub API endpoint URL
43
+ * @param {string} token - GitHub authentication token
44
+ * @returns {Promise<Response>} Fetch response
45
+ * @throws {Error} If fetch fails or returns error status
46
+ */
47
+ export async function fetchGithubAPI(url, token) {
48
+ const res = await fetch(url, {
49
+ headers: {
50
+ 'Authorization': `Bearer ${token}`,
51
+ 'Accept': 'application/vnd.github+json',
52
+ 'X-GitHub-Api-Version': '2022-11-28',
53
+ 'User-Agent': 'inteco-cli'
54
+ }
55
+ });
56
+
57
+ if (!res.ok) {
58
+ if (res.status === 404) {
59
+ throw new Error('Resource not found (404)');
60
+ }
61
+ if (res.status === 401 || res.status === 403) {
62
+ throw new Error('GitHub authentication failed. Please ensure your authentication is valid (run "gh auth login" or check GITHUB_TOKEN)');
63
+ }
64
+ throw new Error(`GitHub API request failed: ${res.statusText}`);
65
+ }
66
+
67
+ return res;
68
+ }
69
+
70
+ /**
71
+ * Fetch all items from a paginated GitHub API endpoint
72
+ * @param {string} baseUrl - Base GitHub API endpoint URL (without pagination params)
73
+ * @param {string} token - GitHub authentication token
74
+ * @param {number} page - Current page (default: 1)
75
+ * @param {Array} allItems - Accumulated items (default: [])
76
+ * @returns {Promise<Array>} All items from all pages
77
+ */
78
+ export async function fetchPaginatedGithubAPI(baseUrl, token, page = 1, allItems = []) {
79
+ const url = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}page=${page}&per_page=100`;
80
+
81
+ try {
82
+ const res = await fetchGithubAPI(url, token);
83
+ const items = await res.json();
84
+
85
+ if (items.length === 0) {
86
+ return allItems;
87
+ }
88
+
89
+ // Recursively fetch all pages
90
+ return fetchPaginatedGithubAPI(baseUrl, token, page + 1, [...allItems, ...items]);
91
+ } catch (error) {
92
+ if (page === 1) {
93
+ // Only throw on first page - error getting initial data
94
+ throw error;
95
+ }
96
+ // On subsequent pages, return what we have so far
97
+ return allItems;
98
+ }
99
+ }