@intecoag/inteco-cli 1.8.0 → 1.9.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
|
@@ -22,6 +22,7 @@ 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
24
|
import githubSecurityAdvisories from './modules/githubSecurityAdvisories.js';
|
|
25
|
+
import listGithubDeploymentKeys from './modules/githubDeploymentKeysList.js';
|
|
25
26
|
|
|
26
27
|
import updateNotifier from 'update-notifier';
|
|
27
28
|
import { addGithubDeploymentKey } from './modules/githubDeploymentKey.js';
|
|
@@ -115,6 +116,9 @@ switch (cli.input[0]) {
|
|
|
115
116
|
case "github_add_deploy_key":
|
|
116
117
|
addGithubDeploymentKey();
|
|
117
118
|
break;
|
|
119
|
+
case "github_list_deploy_keys":
|
|
120
|
+
listGithubDeploymentKeys();
|
|
121
|
+
break;
|
|
118
122
|
default:
|
|
119
123
|
cli.showHelp()
|
|
120
124
|
break;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import Table from 'cli-table3';
|
|
5
|
+
import { getGithubToken, fetchPaginatedGithubAPI, fetchGithubAPI } from '../utils/github/github.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format a date string to a readable format, or show "Never" if null
|
|
9
|
+
* @param {string|null} dateString - ISO date string or null
|
|
10
|
+
* @returns {string} Formatted date or "Never"
|
|
11
|
+
*/
|
|
12
|
+
function formatDate(dateString) {
|
|
13
|
+
if (!dateString) return chalk.gray('Never');
|
|
14
|
+
|
|
15
|
+
const date = new Date(dateString);
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const diffMs = now - date;
|
|
18
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
19
|
+
|
|
20
|
+
if (diffDays === 0) return chalk.green('Today');
|
|
21
|
+
if (diffDays === 1) return chalk.green('Yesterday');
|
|
22
|
+
if (diffDays < 7) return chalk.green(`${diffDays} days ago`);
|
|
23
|
+
if (diffDays < 30) return chalk.yellow(`${Math.floor(diffDays / 7)} weeks ago`);
|
|
24
|
+
if (diffDays < 365) return chalk.yellow(`${Math.floor(diffDays / 30)} months ago`);
|
|
25
|
+
|
|
26
|
+
return chalk.red(`${Math.floor(diffDays / 365)} years ago`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fetch all deployment keys for a repository
|
|
31
|
+
* @param {string} baseUrl - Base API URL for the repository keys
|
|
32
|
+
* @param {string} token - GitHub authentication token
|
|
33
|
+
* @returns {Promise<Array>} Array of deployment keys
|
|
34
|
+
*/
|
|
35
|
+
async function fetchDeploymentKeys(baseUrl, token) {
|
|
36
|
+
try {
|
|
37
|
+
return await fetchPaginatedGithubAPI(baseUrl, token);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// Return empty array if there's an error fetching keys for this repo
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* List all deployment keys across all repositories in an organization
|
|
46
|
+
*/
|
|
47
|
+
export async function listGithubDeploymentKeys() {
|
|
48
|
+
console.log();
|
|
49
|
+
|
|
50
|
+
// Get GitHub authentication token
|
|
51
|
+
let token;
|
|
52
|
+
let authMethod;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
({ token, authMethod } = await getGithubToken());
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.log();
|
|
58
|
+
console.error(chalk.red(`✗ ${error.message}`));
|
|
59
|
+
console.log(chalk.yellow('\nAuthentication setup:'));
|
|
60
|
+
console.log(chalk.gray(' Option 1: Install GitHub CLI and run: gh auth login'));
|
|
61
|
+
console.log(chalk.gray(' Option 2: Set GITHUB_TOKEN environment variable'));
|
|
62
|
+
console.log();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(chalk.green(`✓ Authenticated via: ${authMethod}`));
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
// Get organization name
|
|
70
|
+
const orgResponse = await prompts([
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
name: 'organization',
|
|
74
|
+
message: 'GitHub Organization Name?',
|
|
75
|
+
initial: 'intecoag',
|
|
76
|
+
validate: (value) => value.length > 0 || 'Organization name is required'
|
|
77
|
+
}
|
|
78
|
+
], {
|
|
79
|
+
onCancel: () => {
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk.red('Cancelled deployment keys listing!'));
|
|
82
|
+
console.log();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { organization } = orgResponse;
|
|
88
|
+
|
|
89
|
+
let mainSpinner = ora('Fetching repositories...').start();
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Fetch all repositories for the organization
|
|
93
|
+
const baseReposUrl = `https://api.github.com/orgs/${organization}/repos`;
|
|
94
|
+
const repos = await fetchPaginatedGithubAPI(baseReposUrl, token);
|
|
95
|
+
|
|
96
|
+
if (repos.length === 0) {
|
|
97
|
+
mainSpinner.warn(`No repositories found in organization "${organization}"`);
|
|
98
|
+
console.log();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fetch deployment keys for all repositories
|
|
103
|
+
const allKeys = [];
|
|
104
|
+
for (let i = 0; i < repos.length; i++) {
|
|
105
|
+
const repo = repos[i];
|
|
106
|
+
const progress = `[${i + 1}/${repos.length}]`;
|
|
107
|
+
mainSpinner.text = `${progress} Fetching deployment keys from: ${chalk.blue(repo.name)}...`;
|
|
108
|
+
|
|
109
|
+
const keysUrl = `https://api.github.com/repos/${organization}/${repo.name}/keys`;
|
|
110
|
+
const keys = await fetchDeploymentKeys(keysUrl, token);
|
|
111
|
+
|
|
112
|
+
keys.forEach(key => {
|
|
113
|
+
allKeys.push({
|
|
114
|
+
repository: repo.name,
|
|
115
|
+
keyId: key.id,
|
|
116
|
+
title: key.title,
|
|
117
|
+
createdAt: key.created_at,
|
|
118
|
+
lastUsedAt: key.last_used,
|
|
119
|
+
readOnly: key.read_only
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mainSpinner.succeed(`Found ${allKeys.length} deployment keys across ${repos.length} repositories`);
|
|
125
|
+
console.log();
|
|
126
|
+
|
|
127
|
+
if (allKeys.length === 0) {
|
|
128
|
+
console.log(chalk.yellow('No deployment keys found in this organization.'));
|
|
129
|
+
console.log();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Display keys in a table
|
|
134
|
+
const table = new Table({
|
|
135
|
+
head: [
|
|
136
|
+
chalk.cyan('Repository'),
|
|
137
|
+
chalk.cyan('Key Title'),
|
|
138
|
+
chalk.cyan('Created'),
|
|
139
|
+
chalk.cyan('Last Used'),
|
|
140
|
+
chalk.cyan('Read-Only')
|
|
141
|
+
].map(h => h),
|
|
142
|
+
style: { head: [], border: ['cyan'] },
|
|
143
|
+
colWidths: [25, 30, 20, 20, 12],
|
|
144
|
+
wordWrap: true
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Sort by repository name and then by last used date (newest first)
|
|
148
|
+
allKeys.sort((a, b) => {
|
|
149
|
+
if (a.repository !== b.repository) {
|
|
150
|
+
return a.repository.localeCompare(b.repository);
|
|
151
|
+
}
|
|
152
|
+
return new Date(b.lastUsedAt || 0) - new Date(a.lastUsedAt || 0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
allKeys.forEach(key => {
|
|
156
|
+
table.push([
|
|
157
|
+
chalk.blue(key.repository),
|
|
158
|
+
key.title,
|
|
159
|
+
new Date(key.createdAt).toLocaleDateString(),
|
|
160
|
+
formatDate(key.lastUsedAt),
|
|
161
|
+
key.readOnly ? chalk.green('Yes') : chalk.yellow('No')
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log(table.toString());
|
|
166
|
+
console.log();
|
|
167
|
+
|
|
168
|
+
// Show statistics
|
|
169
|
+
const keysNeverUsed = allKeys.filter(k => !k.lastUsedAt).length;
|
|
170
|
+
const readOnlyKeys = allKeys.filter(k => k.readOnly).length;
|
|
171
|
+
const readWriteKeys = allKeys.filter(k => !k.readOnly).length;
|
|
172
|
+
|
|
173
|
+
console.log(chalk.bold('Summary:'));
|
|
174
|
+
console.log(` Total deployment keys: ${chalk.cyan(allKeys.length)}`);
|
|
175
|
+
console.log(` Never used: ${chalk.yellow(keysNeverUsed)}`);
|
|
176
|
+
console.log(` Read-only keys: ${chalk.green(readOnlyKeys)}`);
|
|
177
|
+
console.log(` Read-write keys: ${chalk.yellow(readWriteKeys)}`);
|
|
178
|
+
console.log();
|
|
179
|
+
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.log();
|
|
182
|
+
mainSpinner.fail('Error fetching deployment keys');
|
|
183
|
+
|
|
184
|
+
if (error.message.startsWith('401')) {
|
|
185
|
+
console.error(chalk.red(`✗ ${error.message}`));
|
|
186
|
+
console.log(chalk.yellow('\nAuthentication setup:'));
|
|
187
|
+
console.log(chalk.gray(' Option 1: Install GitHub CLI and run: gh auth login'));
|
|
188
|
+
console.log(chalk.gray(' Option 2: Set GITHUB_TOKEN environment variable'));
|
|
189
|
+
} else if (error.message.startsWith('404')) {
|
|
190
|
+
console.error(chalk.red(`✗ ${error.message} - Organization not found`));
|
|
191
|
+
} else {
|
|
192
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
193
|
+
}
|
|
194
|
+
console.log();
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default listGithubDeploymentKeys;
|
package/src/ressources/cmds.json
CHANGED
|
@@ -64,5 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"github_add_deploy_key": {
|
|
66
66
|
"desc": "Workflow to add a deployment key to a GitHub repository for a remote server (requires GitHub CLI)"
|
|
67
|
+
},
|
|
68
|
+
"github_list_deploy_keys": {
|
|
69
|
+
"desc": "Lists all deployment keys across all repositories in a GitHub organization with their last used dates (requires GitHub CLI or GITHUB_TOKEN)"
|
|
67
70
|
}
|
|
68
71
|
}
|