@intecoag/inteco-cli 1.7.5 → 1.8.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.7.5",
3
+ "version": "1.8.0",
4
4
  "description": "CLI-Tools for Inteco",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ import { azureCreateSyncConfig, azurePush, azurePull } from './modules/azureSync
24
24
  import githubSecurityAdvisories from './modules/githubSecurityAdvisories.js';
25
25
 
26
26
  import updateNotifier from 'update-notifier';
27
+ import { addGithubDeploymentKey } from './modules/githubDeploymentKey.js';
27
28
 
28
29
  updateNotifier({
29
30
  pkg: {
@@ -111,6 +112,9 @@ switch (cli.input[0]) {
111
112
  case "github_security_advisories":
112
113
  githubSecurityAdvisories();
113
114
  break;
115
+ case "github_add_deploy_key":
116
+ addGithubDeploymentKey();
117
+ break;
114
118
  default:
115
119
  cli.showHelp()
116
120
  break;
@@ -0,0 +1,265 @@
1
+ import prompts from 'prompts';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { execSync } from 'child_process';
5
+ import { getGithubToken, fetchPaginatedGithubAPI, fetchGithubAPI } from '../utils/github/github.js';
6
+
7
+ export async function addGithubDeploymentKey() {
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
+ // Step 1: Select Organization
30
+ const orgResponse = await prompts([
31
+ {
32
+ type: 'text',
33
+ name: 'organization',
34
+ message: 'GitHub Organization Name?',
35
+ initial: 'intecoag',
36
+ validate: (value) => value.length > 0 || 'Organization name is required'
37
+ }
38
+ ], {
39
+ onCancel: () => {
40
+ console.log();
41
+ console.log(chalk.red('Cancelled GitHub Deployment Key setup!'));
42
+ console.log();
43
+ process.exit(0);
44
+ }
45
+ });
46
+
47
+ const { organization } = orgResponse;
48
+
49
+ const spinner = ora('Fetching repositories...').start();
50
+
51
+ try {
52
+ // Fetch all repositories for the organization
53
+ const baseUrl = `https://api.github.com/orgs/${organization}/repos`;
54
+ const repos = await fetchPaginatedGithubAPI(baseUrl, token);
55
+
56
+ if (repos.length === 0) {
57
+ spinner.warn(`No repositories found in organization "${organization}"`);
58
+ console.log();
59
+ return;
60
+ }
61
+
62
+ spinner.succeed(`Found ${repos.length} repositories`);
63
+ console.log();
64
+
65
+ // Step 2: Select Repository
66
+ const repoResponse = await prompts([
67
+ {
68
+ type: 'autocomplete',
69
+ name: 'repository',
70
+ message: 'Select repository:',
71
+ choices: repos.map(repo => ({
72
+ title: repo.name,
73
+ value: repo.name,
74
+ description: repo.description || 'No description'
75
+ })),
76
+ hint: 'Start typing to filter',
77
+ validate: (value) => {
78
+ const exists = repos.some(repo => repo.name === value);
79
+ return exists || 'Repository does not exist in this organization';
80
+ }
81
+ }
82
+ ], {
83
+ onCancel: () => {
84
+ console.log();
85
+ console.log(chalk.red('Cancelled GitHub Deployment Key setup!'));
86
+ console.log();
87
+ process.exit(0);
88
+ }
89
+ });
90
+
91
+ const { repository } = repoResponse;
92
+
93
+ // Step 3: Generate and display setup script
94
+ displayDeploymentKeyScript(organization, repository);
95
+
96
+ // Step 4: Prompt for public key and create deployment key
97
+ const publicKeyResponse = await prompts([
98
+ {
99
+ type: 'text',
100
+ name: 'publicKey',
101
+ message: 'Paste the public key content here:',
102
+ validate: (value) => {
103
+ if (!value.trim().length) return 'Public key is required';
104
+ if (!value.includes('ssh-ed25519') && !value.includes('ssh-rsa') && !value.includes('ecdsa-sha2')) {
105
+ return 'Invalid SSH public key format';
106
+ }
107
+ return true;
108
+ }
109
+ },
110
+ {
111
+ type: 'text',
112
+ name: 'keyName',
113
+ message: 'Deployment key name:',
114
+ validate: (value) => value.length > 0 || 'Key name is required'
115
+ }
116
+ ], {
117
+ onCancel: () => {
118
+ console.log();
119
+ console.log(chalk.yellow('Skipped creating deployment key on GitHub. You can add it manually later.'));
120
+ console.log();
121
+ process.exit(0);
122
+ }
123
+ });
124
+
125
+ const { publicKey, keyName } = publicKeyResponse;
126
+
127
+ // Create deployment key on GitHub
128
+ await createDeploymentKey(organization, repository, publicKey, keyName);
129
+
130
+ // Step 5: Show clone command
131
+ displayCloneCommand(organization, repository);
132
+
133
+ } catch (error) {
134
+ console.log();
135
+ spinner.fail('Error fetching repositories');
136
+
137
+ if (error.message.startsWith('401')) {
138
+ console.error(chalk.red(`✗ ${error.message}`));
139
+ console.log(chalk.yellow('\nAuthentication setup:'));
140
+ console.log(chalk.gray(' Option 1: Install GitHub CLI and run: gh auth login'));
141
+ console.log(chalk.gray(' Option 2: Set GITHUB_TOKEN environment variable'));
142
+ } else if (error.message.startsWith('404')) {
143
+ console.error(chalk.red(`✗ ${error.message} - Organization not found`));
144
+ } else {
145
+ console.error(chalk.red(`Error: ${error.message}`));
146
+ }
147
+ console.log();
148
+ process.exit(1);
149
+ }
150
+ }
151
+
152
+ function displayDeploymentKeyScript(organization, repository) {
153
+ // Use organization and repository in the key name to avoid collisions
154
+ const keyPath = `~/.ssh/id_ed25519_github_${organization}_${repository}`;
155
+ const deploymentKeyName = `${repository}-deployment-key`;
156
+
157
+ console.log();
158
+ console.log(chalk.bold.cyan('═══════════════════════════════════════════════════════════'));
159
+ console.log(chalk.bold.cyan(`Deployment Key Setup for: ${organization}/${repository}`));
160
+ console.log(chalk.bold.cyan('═══════════════════════════════════════════════════════════'));
161
+ console.log();
162
+
163
+ console.log(chalk.bold.yellow('STEP 1: Copy and paste the script below on your server'));
164
+ console.log(chalk.gray('─────────────────────────────────────────────────────────────'));
165
+ console.log();
166
+
167
+ const linuxScript = generateBashScript(keyPath, deploymentKeyName, organization, repository);
168
+
169
+ console.log(chalk.bold('Linux/macOS Setup Script:'));
170
+ console.log(chalk.blue('─────────────────────────────────────────────────────────────'));
171
+ console.log(linuxScript);
172
+ console.log();
173
+
174
+ }
175
+
176
+ function generateBashScript(keyPath, keyName, organization, repository) {
177
+ // Convert ~ to $HOME for proper expansion in bash
178
+ const expandedKeyPath = keyPath.replace(/^~/, '$HOME');
179
+
180
+ return `
181
+ # Create SSH directory if it doesn't exist
182
+ mkdir -p ~/.ssh
183
+ chmod 700 ~/.ssh
184
+
185
+ # Generate ED25519 SSH key (secure and modern)
186
+ ssh-keygen -t ed25519 -C "${keyName}" -f "${expandedKeyPath}" -N ""
187
+
188
+ # Set proper permissions
189
+ chmod 600 "${expandedKeyPath}"
190
+ chmod 644 "${expandedKeyPath}.pub"
191
+
192
+ # Add to SSH config for easy usage
193
+ if ! grep -q "Host github-${organization}-${repository}" ~/.ssh/config 2>/dev/null; then
194
+ cat >> ~/.ssh/config << 'EOF'
195
+
196
+ Host github-${organization}-${repository}
197
+ HostName github.com
198
+ User git
199
+ IdentityFile ${keyPath}
200
+ IdentitiesOnly yes
201
+ EOF
202
+ fi
203
+
204
+ # Display public key for GitHub setup
205
+ echo ""
206
+ echo "========================================"
207
+ echo "Public Key (copy this to Inteco CLI):"
208
+ echo "========================================"
209
+ cat "${expandedKeyPath}.pub"
210
+ echo ""
211
+ echo "========================================"
212
+ echo "Private key saved to: ${expandedKeyPath}"
213
+ echo "SSH config updated for: github-${organization}-${repository}"
214
+ echo "========================================"
215
+ echo ""`;
216
+ }
217
+
218
+ async function createDeploymentKey(organization, repository, publicKey, keyName) {
219
+ const spinner = ora('Creating deployment key on GitHub...').start();
220
+
221
+ try {
222
+ // Use GitHub CLI to add the deployment key
223
+ const result = execSync(
224
+ `gh repo deploy-key add --repo ${organization}/${repository} --title "${keyName}" -`,
225
+ {
226
+ input: publicKey,
227
+ encoding: 'utf-8'
228
+ }
229
+ );
230
+
231
+ spinner.succeed(`Deployment key "${keyName}" created successfully`);
232
+ console.log();
233
+
234
+ } catch (error) {
235
+ spinner.fail('Failed to create deployment key');
236
+
237
+ if (error.message.includes('not found')) {
238
+ console.error(chalk.red(`✗ Repository not found: ${organization}/${repository}`));
239
+ } else if (error.message.includes('already exists')) {
240
+ console.error(chalk.red(`✗ A key with this name already exists`));
241
+ } else if (error.message.includes('Permission denied')) {
242
+ console.error(chalk.red(`✗ Permission denied. You may not have write access to this repository`));
243
+ } else {
244
+ console.error(chalk.red(`✗ Error: ${error.message}`));
245
+ }
246
+ console.log();
247
+ process.exit(1);
248
+ }
249
+ }
250
+
251
+ function displayCloneCommand(organization, repository) {
252
+ console.log(chalk.bold.yellow('Clone Command'));
253
+ console.log(chalk.gray('─────────────────────────────────────────────────────────────'));
254
+ console.log('Use this command to clone the repository with the deployment key:');
255
+ console.log();
256
+ console.log(chalk.cyan(`git clone git@github-${organization}-${repository}:${organization}/${repository}.git`));
257
+ console.log();
258
+ console.log(chalk.bold.yellow('Deployment Setup Complete!'));
259
+ console.log(chalk.green(`✓ SSH key generated and stored locally`));
260
+ console.log(chalk.green(`✓ Deployment key added to ${organization}/${repository}`));
261
+ console.log(chalk.green(`✓ SSH config configured for easy access`));
262
+ console.log();
263
+ }
264
+
265
+ export default addGithubDeploymentKey;
@@ -61,5 +61,8 @@
61
61
  },
62
62
  "github_security_advisories": {
63
63
  "desc": "Checks all GitHub repositories in an organization for open and unresolved security advisories (requires GitHub CLI or GITHUB_TOKEN)"
64
+ },
65
+ "github_add_deploy_key": {
66
+ "desc": "Workflow to add a deployment key to a GitHub repository for a remote server (requires GitHub CLI)"
64
67
  }
65
68
  }