@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
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;
|
package/src/ressources/cmds.json
CHANGED
|
@@ -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
|
+
}
|