@jumpgroup/github-integration 1.0.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.
@@ -0,0 +1,24 @@
1
+ name: run npm-version pipeline
2
+ on:
3
+ push:
4
+ tags:
5
+ - '*'
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout
12
+ uses: actions/checkout@master
13
+
14
+ - uses: actions/setup-node@v1
15
+ with:
16
+ node-version: 18
17
+ registry-url: 'https://registry.npmjs.org'
18
+
19
+ - run: npm install
20
+
21
+ - name: Publish package on NPM 📦
22
+ run: npm publish
23
+ env:
24
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,8 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": "../../.."
5
+ }
6
+ ],
7
+ "settings": {}
8
+ }
package/bin/tools.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from "commander";
4
+ import { listRepoSecrets, createRepoSecrets } from "../src/githubactions/secrets.js";
5
+ import { listRepoVariables, createRepoVariable } from "../src/githubactions/variables.js";
6
+
7
+ // Initialize program
8
+ program.version("0.0.1").description("A CLI tool to integrate with GitHub");
9
+
10
+ // Register 'secrets' command
11
+ const secrets = program.command("secrets").description("Manage GitHub secrets");
12
+
13
+ secrets
14
+ .command("list")
15
+ .description("List all secrets")
16
+ .option("-o, --owner <owner>", "Owner of the repository")
17
+ .option("-r, --repo <repo>", "Repository name")
18
+ .action((options) => {
19
+ console.log("Listing all secrets");
20
+ listRepoSecrets(options);
21
+ });
22
+
23
+ secrets
24
+ .command("create")
25
+ .description("Create all repo secrets")
26
+ .option("-o, --owner <owner>", "Owner of the repository")
27
+ .option("-r, --repo <repo>", "Repository name")
28
+ .option("-s, --secret <secret>", "Secret name")
29
+ .action((options) => {
30
+ console.log("Creating all repo secrets");
31
+ createRepoSecrets(options);
32
+ });
33
+
34
+ const variables = program.command("variables").description("Manage GitHub variables");
35
+
36
+ variables
37
+ .command("list")
38
+ .description("List all variables")
39
+ .option("-o, --owner <owner>", "Owner of the repository")
40
+ .option("-r, --repo <repo>", "Repository name")
41
+ .action((options) => {
42
+ console.log("Listing all variables");
43
+ listRepoVariables(options);
44
+ });
45
+
46
+ variables
47
+ .command("create")
48
+ .description("Create a new variable")
49
+ .option("-o, --owner <owner>", "Owner of the repository")
50
+ .option("-r, --repo <repo>", "Repository name")
51
+ .option("-v, --variable <variable>", "Variable name")
52
+ .action((options) => {
53
+ console.log("Creating a new variable");
54
+ createRepoVariable(options);
55
+ });
56
+
57
+ // Parse command line arguments
58
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@jumpgroup/github-integration",
3
+ "version": "1.0.0",
4
+ "description": "A new package to manage github variables and secrets",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": "20"
9
+ },
10
+ "bin": {
11
+ "github-integration": "bin/tools.js"
12
+ },
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1"
15
+ },
16
+ "author": "JumpGroup srl",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "@inquirer/prompts": "^6.0.1",
20
+ "@jumpgroup/secret-fetcher": "^2.0.0",
21
+ "@octokit/rest": "^21.0.2",
22
+ "commander": "^12.1.0",
23
+ "keytar": "^7.9.0",
24
+ "libsodium-wrappers": "^0.7.15"
25
+ },
26
+ "devDependencies": {}
27
+ }
@@ -0,0 +1,172 @@
1
+ import { Octokit } from '@octokit/rest';
2
+ import keytar from 'keytar';
3
+ import { input, confirm } from '@inquirer/prompts';
4
+ import { getSecrets } from '@jumpgroup/secret-fetcher';
5
+ import sodium from 'libsodium-wrappers';
6
+ import { getGitRepo } from '../utilities.js';
7
+ import { getLocalKeys } from '../utilities.js';
8
+ import { getToken } from '../utilities.js';
9
+
10
+ const encryptSecret = async (publicKeyBase64, secretValue) => {
11
+ // Assicurati che libsodium sia pronto
12
+ await sodium.ready;
13
+
14
+ // Converti la chiave pubblica da Base64 a Uint8Array
15
+ const publicKey = sodium.from_base64(publicKeyBase64, sodium.base64_variants.ORIGINAL);
16
+
17
+ // Converti il valore segreto in Uint8Array (formato binario)
18
+ const secret = sodium.from_string(secretValue);
19
+
20
+ // Crittografa il valore segreto usando la chiave pubblica
21
+ const encrypted = sodium.crypto_box_seal(secret, publicKey);
22
+
23
+ // Converte il risultato crittografato in Base64
24
+ return sodium.to_base64(encrypted, sodium.base64_variants.ORIGINAL);
25
+ };
26
+
27
+ const formatSSHPrivateKey = async (key) => {
28
+ // Define the key header and footer
29
+ const header = '-----BEGIN RSA PRIVATE KEY-----\n';
30
+ const footer = '\n-----END RSA PRIVATE KEY-----';
31
+
32
+ let cleanedKey = key.replace(/-----BEGIN RSA PRIVATE KEY-----|-----END RSA PRIVATE KEY-----/g, '')
33
+ .replace(/\s+/g, '');
34
+
35
+ // Add line breaks every 64 characters
36
+ let formattedKey = cleanedKey.match(/.{1,64}/g).join('\n');
37
+
38
+ // Concatenate the final key with header and footer
39
+ return header + formattedKey + footer;
40
+ };
41
+
42
+
43
+ // Function to list secrets
44
+ export async function listRepoSecrets(options) {
45
+
46
+ // Instantiate octokit with an authentication token
47
+ const octokit = new Octokit({
48
+ auth: await getToken(), // Replace with your GitHub token or use environment variable
49
+ });
50
+
51
+ // get repo from git
52
+ var repoName = '';
53
+ var repoOwner = '';
54
+
55
+ if (!options.repo && !options.owner) {
56
+ const repo = await getGitRepo();
57
+ repoOwner = repo.split(':')[1].split('/')[0];
58
+ repoName = repo.split('/').pop().replace('.git', '');
59
+ } else {
60
+ repoOwner = options.owner;
61
+ repoName = options.repo;
62
+ }
63
+
64
+ console.log('Owner:', repoOwner);
65
+ console.log('repoName:', repoName);
66
+
67
+ try {
68
+ const response = await octokit.paginate("GET /repos/{owner}/{repo}/actions/secrets", {
69
+ owner: repoOwner,
70
+ repo: repoName,
71
+ headers: {
72
+ 'X-GitHub-Api-Version': '2022-11-28'
73
+ }
74
+ });
75
+
76
+ if (response.length === 0) {
77
+ console.log('No secrets found');
78
+ return [];
79
+ }
80
+ console.log('Secrets:', response);
81
+ return response;
82
+ } catch (error) {
83
+ console.error('Error fetching secrets:', error);
84
+ }
85
+ }
86
+
87
+ export async function createRepoSecrets(options) {
88
+ // Instantiate octokit with an authentication token
89
+ const octokit = new Octokit({
90
+ auth: await getToken(), // Replace with your GitHub token or use environment variable
91
+ });
92
+
93
+ // get repo from git
94
+ var repoName = '';
95
+ var repoOwner = '';
96
+
97
+ if (!options.repo && !options.owner) {
98
+ const repo = await getGitRepo();
99
+ repoOwner = repo.split(':')[1].split('/')[0];
100
+ repoName = repo.split('/').pop().replace('.git', '');
101
+ } else {
102
+ repoOwner = options.owner;
103
+ repoName = options.repo;
104
+ }
105
+
106
+ console.log('Owner:', repoOwner);
107
+ console.log('repoName:', repoName);
108
+
109
+ //check if exists a secret with the same name
110
+ const secrets = await listRepoSecrets(options);
111
+ const secretNames = secrets.map(secret => secret.name);
112
+
113
+ const new_options = {
114
+ // name: "autodeploy",
115
+ env: "secret"
116
+ };
117
+ //take the secret value from secret-fetcher
118
+ const autodeploySecrets = await getSecrets(new_options);
119
+
120
+ let secretValues = autodeploySecrets.secret;
121
+ const settings = await getLocalKeys();
122
+ secretValues.GROUP_SECRET = settings.groupSecret;
123
+
124
+ console.log('Secret value:', secretValues);
125
+
126
+ //get key_id
127
+ const { data: { key, key_id } } = await octokit.request('GET /repos/{owner}/{repo}/actions/secrets/public-key', {
128
+ owner: repoOwner,
129
+ repo: repoName
130
+ });
131
+
132
+ console.log('Key:', key);
133
+ const publicKey = key;
134
+ console.log('Key ID:', key_id);
135
+
136
+ // foreach secret value, use key like name and value like value
137
+ for (const [key, value] of Object.entries(secretValues)) {
138
+ var secret = value;
139
+ if (key === 'SSH_PRIVATE_KEY') {
140
+ secret = await formatSSHPrivateKey(value);
141
+
142
+ console.log('Secret:', secret);
143
+ }
144
+ if (secretNames.includes(key)) {
145
+ console.log('A secret with the same name already exists');
146
+ const overwrite = await confirm({ message: 'Do you want to overwrite it?' });
147
+
148
+ if (!overwrite) {
149
+ console.log(`Skipping secret: ${key}`);
150
+ continue; //
151
+ }
152
+ }
153
+ const encryptValue = await encryptSecret(publicKey, secret);
154
+ try {
155
+ const response = await octokit.request('PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}', {
156
+ owner: repoOwner,
157
+ repo: repoName,
158
+ secret_name: key,
159
+ encrypted_value: encryptValue,
160
+ key_id: key_id,
161
+ headers: {
162
+ 'X-GitHub-Api-Version': '2022-11-28'
163
+ }
164
+ })
165
+
166
+ console.log('Secret created:', response);
167
+ } catch (error) {
168
+ console.error('Error creating secret:', error);
169
+ }
170
+ }
171
+ }
172
+
@@ -0,0 +1,138 @@
1
+ import { Octokit } from '@octokit/rest';
2
+ import keytar from 'keytar';
3
+ import { getToken } from '../utilities.js';
4
+ import { getGitRepo } from '../utilities.js';
5
+ import { getSecrets } from '@jumpgroup/secret-fetcher';
6
+ import { input, confirm } from '@inquirer/prompts';
7
+ import { getLocalKeys } from '../utilities.js';
8
+
9
+ // Function to list variables
10
+ export async function listRepoVariables(options) {
11
+
12
+ // Instantiate octokit with an authentication token
13
+ const octokit = new Octokit({
14
+ auth: await getToken(), // Replace with your GitHub token or use environment variable
15
+ });
16
+
17
+ // get repo from git
18
+ var repoName = '';
19
+ var repoOwner = '';
20
+
21
+ if (!options.repo && !options.owner) {
22
+ const repo = await getGitRepo();
23
+ repoOwner = repo.split(':')[1].split('/')[0];
24
+ repoName = repo.split('/').pop().replace('.git', '');
25
+ } else {
26
+ repoOwner = options.owner;
27
+ repoName = options.repo;
28
+ }
29
+
30
+ console.log('Owner:', repoOwner);
31
+ console.log('repoName:', repoName);
32
+
33
+ try {
34
+ const response = await octokit.paginate("GET /repos/{owner}/{repo}/actions/variables", {
35
+ owner: repoOwner,
36
+ repo: repoName,
37
+ headers: {
38
+ 'X-GitHub-Api-Version': '2022-11-28'
39
+ }
40
+ });
41
+
42
+ if (response.length === 0) {
43
+ console.log('No variables found');
44
+ return [];
45
+ }
46
+ console.log('Variables:', response);
47
+ return response;
48
+ } catch (error) {
49
+ console.error('Error fetching secrets:', error);
50
+ }
51
+ }
52
+
53
+ export async function createRepoVariable(options) {
54
+ // Instantiate octokit with an authentication token
55
+ const octokit = new Octokit({
56
+ auth: await getToken(), // Replace with your GitHub token or use environment variable
57
+ });
58
+
59
+ // get repo from git
60
+ var repoName = '';
61
+ var repoOwner = '';
62
+
63
+ if (!options.repo && !options.owner) {
64
+ const repo = await getGitRepo();
65
+ repoOwner = repo.split(':')[1].split('/')[0];
66
+ repoName = repo.split('/').pop().replace('.git', '');
67
+ } else {
68
+ repoOwner = options.owner;
69
+ repoName = options.repo;
70
+ }
71
+
72
+ console.log('Owner:', repoOwner);
73
+ console.log('repoName:', repoName);
74
+
75
+ //check if exists a secret with the same name
76
+ const variables = await listRepoVariables(options);
77
+ const variableNames = variables.map(variable => variable.name);
78
+
79
+ const new_options = {
80
+ // name: "autodeploy",
81
+ env: "variable"
82
+ };
83
+ //take the secret value from secret-fetcher
84
+ const autodeployVariables = await getSecrets(new_options);
85
+
86
+ let variableValues = autodeployVariables.variable;
87
+ const settings = await getLocalKeys();
88
+ variableValues.GROUP_KEY = settings.groupKey;
89
+
90
+ console.log('Variables :', variableValues);
91
+
92
+ // foreach secret value, use key like name and value like value
93
+ for (const [key, value] of Object.entries(variableValues)) {
94
+ if (variableNames.includes(key)) {
95
+ console.log('A secret with the same name already exists');
96
+ const overwrite = await confirm({ message: 'Do you want to overwrite it?' });
97
+
98
+ if (!overwrite) {
99
+ console.log(`Skipping secret: ${key}`);
100
+ continue; //
101
+ }
102
+
103
+ // Effettua una richiesta PATCH per aggiornare la variabile esistente
104
+ try {
105
+ const response = await octokit.request('PATCH /repos/{owner}/{repo}/actions/variables/{name}', {
106
+ owner: repoOwner,
107
+ repo: repoName,
108
+ name: key,
109
+ value: value,
110
+ headers: {
111
+ 'X-GitHub-Api-Version': '2022-11-28'
112
+ }
113
+ });
114
+
115
+ console.log('Variable updated:', response);
116
+ } catch (error) {
117
+ console.error('Error updating secret:', error);
118
+ }
119
+ }else{
120
+ try {
121
+ const response = await octokit.request('POST /repos/{owner}/{repo}/actions/variables', {
122
+ owner: repoOwner,
123
+ repo: repoName,
124
+ name: key,
125
+ value: value,
126
+ headers: {
127
+ 'X-GitHub-Api-Version': '2022-11-28'
128
+ }
129
+ })
130
+
131
+ console.log('Variable created:', response);
132
+ } catch (error) {
133
+ console.error('Error creating secret:', error);
134
+ }
135
+ }
136
+ }
137
+
138
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,55 @@
1
+ import { execSync } from 'child_process';
2
+ import keytar from 'keytar';
3
+ import { input } from '@inquirer/prompts';
4
+ import { globSync } from 'glob';
5
+ import { config } from 'dotenv';
6
+
7
+ export const getGitRepo = async () => {
8
+ try {
9
+ const repoPath = execSync('git config --get remote.origin.url', { encoding: 'utf-8' }).trim();
10
+ return repoPath;
11
+ } catch (error) {
12
+ // Handle the error if the current directory is not a Git repository
13
+ console.error(`Error: ${error.stderr}`);
14
+ return null;
15
+ }
16
+ }
17
+
18
+ // Get the variables from the local file
19
+ export const getLocalKeys = async () => {
20
+ const localFile = globSync(".secret-fetcher")[0];
21
+ if (!localFile) {
22
+ throw new Error("No .secret_fetcher file found");
23
+ }
24
+
25
+ const variables = config({
26
+ path: '.secret-fetcher'
27
+ }).parsed;
28
+
29
+ return variables;
30
+ }
31
+
32
+ // Function to get the GitHub token from the Keychain
33
+ export async function getToken() {
34
+ // Recupera il token dal Keychain
35
+ let token = await keytar.getPassword("github-integration", "github-token");
36
+
37
+ // Se il token non è trovato nel Keychain, chiede all'utente di inserirlo
38
+ if (!token) {
39
+ console.log('Token not found in Keychain, please insert it now:');
40
+ //in inglese se non ce l'hai contatta Giada
41
+ console.log('If you don\'t have the token, please contact Giada');
42
+
43
+ // Ottieni l'input dell'utente (simulato qui con una variabile di esempio)
44
+ const inputToken = await input({ message: 'Insert GitHub Token: ' }); // Assicurati che 'input' restituisca il token come una stringa
45
+
46
+ // Salva il token nel Keychain
47
+ await keytar.setPassword("github-integration", "github-token", inputToken);
48
+ console.log('Token saved in Keychain');
49
+
50
+ // Usa il nuovo token appena inserito
51
+ token = inputToken;
52
+ }
53
+
54
+ return token;
55
+ }