@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.
- package/.github/workflows/main.yml +24 -0
- package/.github/workflows/scripts.code-workspace +8 -0
- package/bin/tools.js +58 -0
- package/package.json +27 -0
- package/src/githubactions/secrets.js +172 -0
- package/src/githubactions/variables.js +138 -0
- package/src/index.js +2 -0
- package/src/utilities.js +55 -0
|
@@ -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 }}
|
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
package/src/utilities.js
ADDED
|
@@ -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
|
+
}
|