@soft-artel/ci 1.2.13 → 1.3.15
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/.env +21 -0
- package/.eslintcache +1 -0
- package/.eslintignore +4 -0
- package/.eslintrc +246 -0
- package/.gitlab-ci.yml +12 -0
- package/_publish.sh +24 -0
- package/k8s/App.ts +200 -0
- package/k8s/DockerImage.ts +147 -0
- package/k8s/Node.ts +119 -0
- package/k8s-build.ts +175 -0
- package/k8s-deploy.ts +174 -0
- package/libs/Exception.ts +19 -0
- package/libs/Git.ts +199 -0
- package/libs/Gitlab.ts +86 -0
- package/libs/Jira.ts +239 -0
- package/libs/Project.ts +215 -0
- package/libs/Reporter.ts +181 -0
- package/libs/Shell.ts +119 -0
- package/libs/helpers.ts +114 -0
- package/libs/prototype.ts +313 -0
- package/package.json +1 -1
- package/tsconfig.json +24 -0
- package/upd_pkg.ts +21 -0
- package/xcode.ts +226 -0
- package/k8s/App.d.ts +0 -22
- package/k8s/App.d.ts.map +0 -1
- package/k8s/App.js +0 -149
- package/k8s/App.js.map +0 -1
- package/k8s/DockerImage.d.ts +0 -15
- package/k8s/DockerImage.d.ts.map +0 -1
- package/k8s/DockerImage.js +0 -108
- package/k8s/DockerImage.js.map +0 -1
- package/k8s/Node.d.ts +0 -17
- package/k8s/Node.d.ts.map +0 -1
- package/k8s/Node.js +0 -103
- package/k8s/Node.js.map +0 -1
- package/k8s-build.d.ts +0 -3
- package/k8s-build.d.ts.map +0 -1
- package/k8s-build.js +0 -125
- package/k8s-build.js.map +0 -1
- package/k8s-deploy.d.ts +0 -3
- package/k8s-deploy.d.ts.map +0 -1
- package/k8s-deploy.js +0 -128
- package/k8s-deploy.js.map +0 -1
- package/libs/Exception.d.ts +0 -5
- package/libs/Exception.d.ts.map +0 -1
- package/libs/Exception.js +0 -13
- package/libs/Exception.js.map +0 -1
- package/libs/Git.d.ts +0 -44
- package/libs/Git.d.ts.map +0 -1
- package/libs/Git.js +0 -160
- package/libs/Git.js.map +0 -1
- package/libs/Gitlab.d.ts +0 -12
- package/libs/Gitlab.d.ts.map +0 -1
- package/libs/Gitlab.js +0 -78
- package/libs/Gitlab.js.map +0 -1
- package/libs/Jira.d.ts +0 -31
- package/libs/Jira.d.ts.map +0 -1
- package/libs/Jira.js +0 -157
- package/libs/Jira.js.map +0 -1
- package/libs/Project.d.ts +0 -39
- package/libs/Project.d.ts.map +0 -1
- package/libs/Project.js +0 -177
- package/libs/Project.js.map +0 -1
- package/libs/Reporter.d.ts +0 -34
- package/libs/Reporter.d.ts.map +0 -1
- package/libs/Reporter.js +0 -129
- package/libs/Reporter.js.map +0 -1
- package/libs/Shell.d.ts +0 -39
- package/libs/Shell.d.ts.map +0 -1
- package/libs/Shell.js +0 -107
- package/libs/Shell.js.map +0 -1
- package/libs/helpers.d.ts +0 -29
- package/libs/helpers.d.ts.map +0 -1
- package/libs/helpers.js +0 -101
- package/libs/helpers.js.map +0 -1
- package/libs/prototype.d.ts +0 -9
- package/libs/prototype.d.ts.map +0 -1
- package/libs/prototype.js +0 -186
- package/libs/prototype.js.map +0 -1
- package/upd_pkg.d.ts +0 -3
- package/upd_pkg.d.ts.map +0 -1
- package/upd_pkg.js +0 -28
- package/upd_pkg.js.map +0 -1
- package/xcode.d.ts +0 -3
- package/xcode.d.ts.map +0 -1
- package/xcode.js +0 -163
- package/xcode.js.map +0 -1
package/libs/Gitlab.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
import { Exception } from '../libs/Exception';
|
|
4
|
+
import { checkVarsExisting } from '../libs/helpers';
|
|
5
|
+
|
|
6
|
+
// ------------------------
|
|
7
|
+
|
|
8
|
+
class Gitlab {
|
|
9
|
+
|
|
10
|
+
privateToken: string;
|
|
11
|
+
configVarName: string;
|
|
12
|
+
projectId: number;
|
|
13
|
+
url: string;
|
|
14
|
+
|
|
15
|
+
constructor(privateToken: string, projectId: number, configGitlabUrl = 'https://gitlab.soft-artel.com', configVarName = 'CONFIG') {
|
|
16
|
+
this.privateToken = privateToken;
|
|
17
|
+
this.projectId = projectId;
|
|
18
|
+
this.configVarName = configVarName;
|
|
19
|
+
|
|
20
|
+
this.url = `${ configGitlabUrl }/api/v4/projects/${ this.projectId }/variables/${ configVarName }`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async downloadConfig() {
|
|
24
|
+
|
|
25
|
+
try{
|
|
26
|
+
const resp = await axios({
|
|
27
|
+
method:'GET', timeout: 600, url: this.url, headers:{ 'PRIVATE-TOKEN': this.privateToken },
|
|
28
|
+
});
|
|
29
|
+
return JSON.parse(resp.data.value);
|
|
30
|
+
} catch(e){
|
|
31
|
+
|
|
32
|
+
const errTitle = `CONFIG ERR: GitLab project [id:${ this.projectId }] has not`;
|
|
33
|
+
|
|
34
|
+
if(e.response && (e.response.status === 401 || e.response.status === 403)){
|
|
35
|
+
const err = new Exception(`${ errTitle } access, HTTP-401`);
|
|
36
|
+
err.params = e.config;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if(e.response && e.response.status === 404){
|
|
41
|
+
const err = new Exception(`${ errTitle } CI VARIABLE: '${ this.configVarName }'`);
|
|
42
|
+
err.params = e.config;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
e.request = '...';
|
|
47
|
+
e.connection = '...';
|
|
48
|
+
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async uploadConfig(config: Record<string, any>) {
|
|
54
|
+
const resp = await axios({
|
|
55
|
+
method:'PUT', timeout: 600, url: this.url,
|
|
56
|
+
headers:{ 'PRIVATE-TOKEN': this.privateToken },
|
|
57
|
+
data:{ value: JSON.stringify(config, null, 3) },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if(resp.status !== 200){
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.error(resp);
|
|
63
|
+
throw new Error('CONFIG ERR: GitLab update VAR - HTTP'+resp.status);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Export and check
|
|
69
|
+
|
|
70
|
+
const ENV = process.env;
|
|
71
|
+
|
|
72
|
+
const privateToken = ENV.GITLAB_PASS;
|
|
73
|
+
const projectId = ENV.CI_PROJECT_ID;
|
|
74
|
+
|
|
75
|
+
checkVarsExisting([{
|
|
76
|
+
field: privateToken,
|
|
77
|
+
exceptionMessage: 'GITLAB_PASS must be provided',
|
|
78
|
+
}, {
|
|
79
|
+
field: projectId,
|
|
80
|
+
exceptionMessage: 'CI_PROJECT_ID must be provided',
|
|
81
|
+
}]);
|
|
82
|
+
|
|
83
|
+
const configGitlabUrl = ENV.CONFIG_GITLAB_URL;
|
|
84
|
+
const configVarName = ENV.CONFIG_VAR_NAME;
|
|
85
|
+
|
|
86
|
+
export default new Gitlab(privateToken!, Number(projectId), configGitlabUrl, configVarName);
|
package/libs/Jira.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
import { log, stripTags } from './helpers';
|
|
4
|
+
import git from './Git';
|
|
5
|
+
import { Project } from './Project';
|
|
6
|
+
|
|
7
|
+
const ENV = process.env;
|
|
8
|
+
|
|
9
|
+
// Can be redefined in ENV
|
|
10
|
+
// ------------------------
|
|
11
|
+
|
|
12
|
+
const RELEASE_BO_URL = ENV.RELEASE_BO_URL || 'https://bo.soft-artel.com/ci/jira';
|
|
13
|
+
const RELEASE_BO_TOKEN = ENV.RELEASE_BO_TOKEN || ENV.GITLAB_PASS;
|
|
14
|
+
|
|
15
|
+
const RELEASE_JIRA_URL = ENV.RELEASE_JIRA_URL || 'https://jira.soft-artel.com';
|
|
16
|
+
|
|
17
|
+
export interface ReleaseInfo{
|
|
18
|
+
components: string[];
|
|
19
|
+
bugs: Issue[];
|
|
20
|
+
tasks: Issue[];
|
|
21
|
+
changelog: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ===========================================
|
|
25
|
+
|
|
26
|
+
async function resolve(prj: Project, head = true): Promise<ReleaseInfo>{
|
|
27
|
+
|
|
28
|
+
if(RELEASE_BO_TOKEN === undefined){
|
|
29
|
+
throw new Error('NO RELEASE_BO_TOKEN!');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const release: ReleaseInfo = {
|
|
33
|
+
components: [], bugs:[], tasks:[], changelog: 'No issues found in git commits!',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 1. Получаем информацию из гита - по тому какие коммиты были
|
|
37
|
+
|
|
38
|
+
const fromTag = prj.prevVersion ? await git.existTag(prj.prevVersion) : await git.lastTag();
|
|
39
|
+
const toTag = head ? undefined : `v${prj.version}`;
|
|
40
|
+
|
|
41
|
+
const gitLog = await git.getCommitsAndIssues({
|
|
42
|
+
fromTag: fromTag ?? '', toTag: toTag ?? '', onlyJira: true, limit: 1000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const issuesKeys = Object.keys(gitLog.issues);
|
|
46
|
+
|
|
47
|
+
if(issuesKeys.length === 0){
|
|
48
|
+
// если никто не комитил с ключами - выходим!!!
|
|
49
|
+
log.info('No issues found in git log!');
|
|
50
|
+
return release;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Резолвим задачи - получаем названия и компоненты
|
|
54
|
+
|
|
55
|
+
try{
|
|
56
|
+
|
|
57
|
+
const resp = await axios({
|
|
58
|
+
method:'POST',
|
|
59
|
+
url: `${ RELEASE_BO_URL }/resolve`,
|
|
60
|
+
headers:{ 'PRIVATE-TOKEN': RELEASE_BO_TOKEN },
|
|
61
|
+
data: { issues: issuesKeys },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if(resp?.data?.data?.bugs && Array.isArray(resp.data.data.bugs)){
|
|
65
|
+
for (const fields of resp.data.data.bugs) {
|
|
66
|
+
const issue = new Issue(fields);
|
|
67
|
+
if(issue.key !== ''){
|
|
68
|
+
release.bugs.push(issue);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if(resp?.data?.data?.tasks && Array.isArray(resp.data.data.tasks)){
|
|
74
|
+
for (const fields of resp.data.data.tasks) {
|
|
75
|
+
const issue = new Issue(fields);
|
|
76
|
+
if(issue.key !== ''){
|
|
77
|
+
release.tasks.push(issue);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if(resp?.data?.data?.components){
|
|
83
|
+
release.components = Object.values<string>(resp.data.data.components).sort();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} catch(err){
|
|
87
|
+
err.request = '...';
|
|
88
|
+
err.connection = '...';
|
|
89
|
+
err.message = 'JIRA RESOLVE ERR: ' + err.message;
|
|
90
|
+
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// 3. Формируем ченж лог
|
|
96
|
+
|
|
97
|
+
log.info('\n\n===============');
|
|
98
|
+
log.info('RELEASE:', prj.group, prj.name, 'v'+prj.version, ' pev:'+fromTag);
|
|
99
|
+
|
|
100
|
+
// let changelog = `🎉 ${ prj.group.toUpperCase() }: <b>${ prj.name.toUpperCase() } v${ prj.version }</b> [ ${ stage.toUpperCase() } ] \n\n`
|
|
101
|
+
|
|
102
|
+
let msg = '';
|
|
103
|
+
|
|
104
|
+
if(release.components.length > 0){
|
|
105
|
+
msg += '<b>Components:</b>\n';
|
|
106
|
+
for (const comp of release.components) {
|
|
107
|
+
msg += `- ${ comp }\n`;
|
|
108
|
+
}
|
|
109
|
+
msg += '\n';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(release.tasks.length > 0){
|
|
113
|
+
msg += '<b>Done:</b>\n';
|
|
114
|
+
for (const issue of release.tasks) {
|
|
115
|
+
msg += `- 🛠 <b><a href="${ RELEASE_JIRA_URL }/browse/${ issue.key }">${ issue.key }</a></b>: ${issue.summary}\n`;
|
|
116
|
+
}
|
|
117
|
+
msg += '\n';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if(release.bugs.length > 0){
|
|
121
|
+
msg += '<b>BugFixes:</b>\n';
|
|
122
|
+
for (const issue of release.bugs) {
|
|
123
|
+
msg += `- 🐞 <b><a href="${ RELEASE_JIRA_URL }/browse/${ issue.key }">${ issue.key }</a></b>: ${issue.summary}\n`;
|
|
124
|
+
}
|
|
125
|
+
msg += '\n';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
release.changelog = msg;
|
|
129
|
+
|
|
130
|
+
log.info('changelog', msg);
|
|
131
|
+
log.info('text', stripTags(msg));
|
|
132
|
+
|
|
133
|
+
return release;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ------------------------
|
|
137
|
+
|
|
138
|
+
export class Issue{
|
|
139
|
+
|
|
140
|
+
key = '';
|
|
141
|
+
updated = new Date();
|
|
142
|
+
|
|
143
|
+
type = '';
|
|
144
|
+
status = '';
|
|
145
|
+
summary = '';
|
|
146
|
+
assignee = { name: '', email: '', login: '' };
|
|
147
|
+
components: string[] = [];
|
|
148
|
+
|
|
149
|
+
constructor(fields: Record<string, any>){
|
|
150
|
+
this.key = fields.key;
|
|
151
|
+
this.updated = new Date(Date.parse(fields.updated));
|
|
152
|
+
this.type = fields.type;
|
|
153
|
+
this.status = fields.status;
|
|
154
|
+
this.summary = fields.summary;
|
|
155
|
+
if(fields.assignee && typeof fields.assignee === 'object'){
|
|
156
|
+
this.assignee.name = fields.assignee.name;
|
|
157
|
+
this.assignee.email = fields.assignee.email;
|
|
158
|
+
this.assignee.login = fields.assignee.login;
|
|
159
|
+
}
|
|
160
|
+
this.components = Array.isArray(fields.components) ? fields.components : [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// --------------------
|
|
165
|
+
|
|
166
|
+
async function moveBugsAsDeployed(bugs: { key: string }[]){
|
|
167
|
+
|
|
168
|
+
if(RELEASE_BO_TOKEN === undefined){
|
|
169
|
+
throw new Error('NO RELEASE_BO_TOKEN!');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const issuesKeys = [];
|
|
173
|
+
|
|
174
|
+
for (const bug of bugs) {
|
|
175
|
+
issuesKeys.push(bug.key);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if(issuesKeys.length === 0){
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const resp = await axios({
|
|
183
|
+
method:'POST',
|
|
184
|
+
url: `${ RELEASE_BO_URL }/deployed`,
|
|
185
|
+
headers:{ 'PRIVATE-TOKEN': RELEASE_BO_TOKEN },
|
|
186
|
+
data: { issues: issuesKeys },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if(resp?.data?.data?.deployed && Array.isArray(resp.data.data.deployed)){
|
|
190
|
+
return resp.data.data.deployed;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
// ------------------------
|
|
198
|
+
|
|
199
|
+
export const Jira = {
|
|
200
|
+
resolve,
|
|
201
|
+
moveBugsAsDeployed,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// let msg = `## **${ prj.version }**
|
|
206
|
+
// ${ new Date().toLocaleString('ru-RU', {
|
|
207
|
+
// weekday: 'long',
|
|
208
|
+
// year: 'numeric',
|
|
209
|
+
// month: 'short',
|
|
210
|
+
// day: 'numeric',
|
|
211
|
+
// timeZone: 'Europe/Moscow',
|
|
212
|
+
// hour12: false,
|
|
213
|
+
// hour: 'numeric',
|
|
214
|
+
// minute: 'numeric',
|
|
215
|
+
// }) }`
|
|
216
|
+
|
|
217
|
+
// if( release.components.length > 0 ){
|
|
218
|
+
// msg += '**Components:**\n'
|
|
219
|
+
// for (const comp of release.components) {
|
|
220
|
+
// msg += `- **${ comp }**\n`
|
|
221
|
+
// }
|
|
222
|
+
// msg += '\n\n'
|
|
223
|
+
// }
|
|
224
|
+
|
|
225
|
+
// if( release.tasks.length > 0 ){
|
|
226
|
+
// msg += '**Done:**\n'
|
|
227
|
+
// for (const issue of release.tasks) {
|
|
228
|
+
// msg += `- 🛠 **[${ issue.key }](${ RELEASE_JIRA_URL }/browse/${ issue.key })**: ${issue.summary}, _by [${ issue.assignee.name }](${ RELEASE_JIRA_URL }/issues/?jql=assignee%20in%20(${issue.assignee.login}))_\n`
|
|
229
|
+
// }
|
|
230
|
+
// msg += '\n\n'
|
|
231
|
+
// }
|
|
232
|
+
|
|
233
|
+
// if( release.bugs.length > 0 ){
|
|
234
|
+
// msg += '**BugFixes:**\n'
|
|
235
|
+
// for (const issue of release.bugs) {
|
|
236
|
+
// msg += `- 🐞 **[${ issue.key }](${ RELEASE_JIRA_URL }/browse/${ issue.key })**: ${issue.summary}, _by [${ issue.assignee.name }](${ RELEASE_JIRA_URL }/issues/?jql=assignee%20in%20(${issue.assignee.login}))_\n`
|
|
237
|
+
// }
|
|
238
|
+
// msg += '\n'
|
|
239
|
+
// }
|
package/libs/Project.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { promises as fsAsync } from 'fs';
|
|
3
|
+
|
|
4
|
+
import { Exception } from './Exception';
|
|
5
|
+
import { log, capitalizeFirstLetter, htmlToMd, resolvePath } from './helpers';
|
|
6
|
+
import Shell from './Shell';
|
|
7
|
+
|
|
8
|
+
const ENV = process.env;
|
|
9
|
+
|
|
10
|
+
export type Stage = keyof typeof ALLOW_STAGES;
|
|
11
|
+
const ALLOW_STAGES: Record<string, string> = {
|
|
12
|
+
dev: 'dev',
|
|
13
|
+
alpha: 'alpha',
|
|
14
|
+
rc:'rc',
|
|
15
|
+
hotfix:'hotfix',
|
|
16
|
+
prod:'prod',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class Project {
|
|
20
|
+
|
|
21
|
+
// ENV FROM GitLab Runner ---------------
|
|
22
|
+
isRunner = ENV.CI_JOB_ID !== undefined;
|
|
23
|
+
|
|
24
|
+
id = (Number(ENV.CI_PROJECT_ID as unknown as number || 0));
|
|
25
|
+
path = ENV.CI_PROJECT_PATH || ''; // <group>/<project>
|
|
26
|
+
url = ENV.CI_PROJECT_URL || ''; // http://<gitlab>/<group>/<project>
|
|
27
|
+
|
|
28
|
+
name = ENV.CI_PROJECT_NAME || '';
|
|
29
|
+
group = ENV.CI_PROJECT_NAMESPACE || '';
|
|
30
|
+
|
|
31
|
+
// Common proporties ---------------
|
|
32
|
+
rootPath: string = '';
|
|
33
|
+
|
|
34
|
+
build = 0;
|
|
35
|
+
version = '0.0.0';
|
|
36
|
+
stage: Stage = 'dev';
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
$stages: Stage[] = ['dev'];
|
|
40
|
+
$stagesVersions: Record<string, string> = { dev: '0.0.0' };
|
|
41
|
+
|
|
42
|
+
job = {
|
|
43
|
+
|
|
44
|
+
id: ENV.CI_JOB_ID,
|
|
45
|
+
name: ENV.CI_JOB_NAME || 'No Job',
|
|
46
|
+
step: ENV.CI_JOB_STAGE || 'build',
|
|
47
|
+
branch: ENV.CI_COMMIT_REF_NAME || 'dev',
|
|
48
|
+
|
|
49
|
+
url: ENV.CI_JOB_URL || '',
|
|
50
|
+
pipline: ENV.CI_PIPELINE_URL || '',
|
|
51
|
+
|
|
52
|
+
user: {
|
|
53
|
+
ID: ENV.GITLAB_USER_ID || '',
|
|
54
|
+
login: ENV.GITLAB_USER_LOGIN || '',
|
|
55
|
+
name: ENV.GITLAB_USER_NAME || '',
|
|
56
|
+
email: ENV.GITLAB_USER_EMAIL || '',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// --------------
|
|
61
|
+
|
|
62
|
+
constructor(workdir: string | undefined, stage?: Stage){
|
|
63
|
+
this.rootPath = resolvePath(workdir || ENV.CI_PROJECT_DIR || Shell.pwd().toString());
|
|
64
|
+
const branch = ENV.CI_COMMIT_REF_NAME || 'dev';
|
|
65
|
+
this.stage = stage || ALLOW_STAGES[ branch ] || 'dev';
|
|
66
|
+
|
|
67
|
+
try{
|
|
68
|
+
this.$stages = JSON.parse(ENV.CI_STAGES || '[dev]');
|
|
69
|
+
log.dbg('Allowed stages:', this.$stages);
|
|
70
|
+
}catch(e){
|
|
71
|
+
log.dbg('CI VARIABLE: CI_STAGES is invalid!');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try{
|
|
75
|
+
this.$stagesVersions = JSON.parse(ENV.VERSIONS || '{}');
|
|
76
|
+
log.dbg('stagesVersions:', this.$stagesVersions);
|
|
77
|
+
}catch(e){
|
|
78
|
+
log.dbg('CI VARIABLE:VERSIONS is invalid!');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.build = Number(ENV.LAST_BUILD) || 0;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
this.version = '0.0.' + this.build; // Mast init later! [ depends fom platform]
|
|
85
|
+
|
|
86
|
+
Shell.cd(this.rootPath);
|
|
87
|
+
log.dbg(`[${this.stage}, ${this.version}] `+this.rootPath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --------------
|
|
91
|
+
|
|
92
|
+
get shortVersion(){
|
|
93
|
+
return this.version.split('.').splice(0, 2).join('.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get prevVersion(){
|
|
97
|
+
const pV = this.$stagesVersions[ this.stage ];
|
|
98
|
+
return pV !== '0.0.0' ? pV : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --------------
|
|
102
|
+
|
|
103
|
+
incrementBuild(newBuild?: number){
|
|
104
|
+
if (newBuild) {
|
|
105
|
+
this.build = newBuild;
|
|
106
|
+
} else {
|
|
107
|
+
this.build += 1;
|
|
108
|
+
}
|
|
109
|
+
this.version = `${ this.shortVersion }.${ this.build}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --------------
|
|
113
|
+
|
|
114
|
+
async saveGitLabBuild(){
|
|
115
|
+
await saveGitLabVariable(this, 'LAST_BUILD', this.build);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ----------------
|
|
119
|
+
|
|
120
|
+
async saveGitLabStagesVersions(){
|
|
121
|
+
this.$stagesVersions[ this.stage ] = this.version;
|
|
122
|
+
await saveGitLabVariable(this, 'VERSIONS', JSON.stringify(this.$stagesVersions, null, 3));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --------------
|
|
126
|
+
|
|
127
|
+
async updateChangeLog(whatsNewHtml: string, filename = 'CHANGELOG.md') {
|
|
128
|
+
|
|
129
|
+
const filePath = `${ this.rootPath}/${ filename }`;
|
|
130
|
+
|
|
131
|
+
let title = `# ${ capitalizeFirstLetter(this.group) }: **${ this.name.toUpperCase() }**`;
|
|
132
|
+
|
|
133
|
+
log.info(`Update ${filename}:\n${title}`);
|
|
134
|
+
|
|
135
|
+
let changeLog = await Shell.cat(filePath, {silent:true, ignoreError:true}) || '';
|
|
136
|
+
const lines = changeLog.split('\n');
|
|
137
|
+
|
|
138
|
+
if(Array.isArray(lines) && lines.length > 1){
|
|
139
|
+
title = lines.shift() || title;
|
|
140
|
+
changeLog = lines.join('\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dateTime = new Date().toLocaleString('ru-RU', {
|
|
144
|
+
weekday: 'long',
|
|
145
|
+
year: 'numeric',
|
|
146
|
+
month: 'short',
|
|
147
|
+
day: 'numeric',
|
|
148
|
+
timeZone: 'Europe/Moscow',
|
|
149
|
+
hour12: false,
|
|
150
|
+
hour: 'numeric',
|
|
151
|
+
minute: 'numeric',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const whatsNewMd = htmlToMd(whatsNewHtml);
|
|
155
|
+
|
|
156
|
+
changeLog = `${ title }
|
|
157
|
+
## **${ this.version }**
|
|
158
|
+
${ capitalizeFirstLetter(dateTime) }
|
|
159
|
+
|
|
160
|
+
${ whatsNewMd }
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
${ changeLog }
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
await fsAsync.writeFile(filePath, changeLog, { encoding: 'utf8', flag: 'w+'});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
// ==================================
|
|
175
|
+
// GitLab Helpers
|
|
176
|
+
|
|
177
|
+
async function saveGitLabVariable(prj: Project, name: string, value: any) {
|
|
178
|
+
|
|
179
|
+
try{
|
|
180
|
+
const resp = await axios({
|
|
181
|
+
method:'PUT',
|
|
182
|
+
timeout: 6000,
|
|
183
|
+
url: `${ ENV.CONFIG_GITLAB_URL || 'https://gitlab.soft-artel.com' }/api/v4/projects/${ prj.id }/variables/${ name }`,
|
|
184
|
+
headers:{ 'PRIVATE-TOKEN': ENV.GITLAB_API_TOKEN || '' },
|
|
185
|
+
data:{
|
|
186
|
+
value,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
log.dbg(resp.data);
|
|
191
|
+
return JSON.parse(resp.data.value);
|
|
192
|
+
|
|
193
|
+
} catch(e){
|
|
194
|
+
|
|
195
|
+
const errTitle = `GitLab project [${ prj.id }] has not`;
|
|
196
|
+
|
|
197
|
+
if(e.response && (e.response.status === 401 || e.response.status === 403)){
|
|
198
|
+
const err = new Exception(`${ errTitle } access, HTTP-401`);
|
|
199
|
+
err.params = e.config;
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if(e.response && e.response.status === 404){
|
|
204
|
+
const err = new Exception(`${ errTitle } CI VARIABLE: ${ name }`);
|
|
205
|
+
err.params = e.config;
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
e.request = '...';
|
|
210
|
+
e.connection = '...';
|
|
211
|
+
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
package/libs/Reporter.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
|
|
2
|
+
const ENV = process.env;
|
|
3
|
+
ENV.NTBA_FIX_319='true';
|
|
4
|
+
|
|
5
|
+
import './prototype';
|
|
6
|
+
|
|
7
|
+
import TelegramBot from 'node-telegram-bot-api';
|
|
8
|
+
|
|
9
|
+
import { log } from '../libs/helpers';
|
|
10
|
+
import { Project } from './Project';
|
|
11
|
+
|
|
12
|
+
// ------------
|
|
13
|
+
|
|
14
|
+
type PipelineTypes = 'build' | 'deploy' | 'setup';
|
|
15
|
+
|
|
16
|
+
// ------------------------
|
|
17
|
+
|
|
18
|
+
export interface ReporterOptions {
|
|
19
|
+
chat_id: number;
|
|
20
|
+
chat_rel?: number;
|
|
21
|
+
|
|
22
|
+
token: string;
|
|
23
|
+
|
|
24
|
+
showGroup?: boolean;
|
|
25
|
+
shortVersion?: boolean;
|
|
26
|
+
showBuild?: boolean;
|
|
27
|
+
|
|
28
|
+
preTitle?: string;
|
|
29
|
+
postTitle?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ------------------------
|
|
33
|
+
|
|
34
|
+
export class Reporter {
|
|
35
|
+
|
|
36
|
+
$bot;
|
|
37
|
+
$chat_id = 0;
|
|
38
|
+
$message_id = 0;
|
|
39
|
+
$message_text = '';
|
|
40
|
+
|
|
41
|
+
title = '';
|
|
42
|
+
stage: string | undefined;
|
|
43
|
+
|
|
44
|
+
$prj: Project;
|
|
45
|
+
$chat_rel = 0;
|
|
46
|
+
$opt: ReporterOptions;
|
|
47
|
+
$ver = '';
|
|
48
|
+
|
|
49
|
+
// --------------
|
|
50
|
+
|
|
51
|
+
constructor(prj: Project, opt: ReporterOptions){
|
|
52
|
+
this.$prj = prj;
|
|
53
|
+
this.$chat_id = opt.chat_id;
|
|
54
|
+
this.$opt = opt;
|
|
55
|
+
|
|
56
|
+
this.$bot = new TelegramBot(opt.token);
|
|
57
|
+
|
|
58
|
+
if(opt.chat_rel){
|
|
59
|
+
this.$chat_rel = opt.chat_rel;
|
|
60
|
+
}
|
|
61
|
+
this.updateTitle();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --------------
|
|
65
|
+
updateTitle(){
|
|
66
|
+
|
|
67
|
+
this.title = this.$opt.preTitle ? this.$opt.preTitle + ' ' : '';
|
|
68
|
+
|
|
69
|
+
this.title += this.$opt.showGroup ? this.$prj.group.toUpperCase() + ': ': '';
|
|
70
|
+
this.title += this.$prj.name.toUpperCase();
|
|
71
|
+
|
|
72
|
+
this.title += this.$opt.postTitle ? ' ' + this.$opt.postTitle : '';
|
|
73
|
+
|
|
74
|
+
if(this.$opt.shortVersion){
|
|
75
|
+
this.$ver= `v${ this.$prj.shortVersion } #${ this.$prj.build }`;
|
|
76
|
+
}else{
|
|
77
|
+
this.$ver= `v${ this.$prj.version }`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.title += ' ' + this.$ver;
|
|
81
|
+
}
|
|
82
|
+
// --------------
|
|
83
|
+
|
|
84
|
+
async startBuild(){
|
|
85
|
+
this.updateTitle();
|
|
86
|
+
await this.send(`📦 ${ this.title } <a href="${ this.$prj.job.url }">BUILD</a> [${this.$prj.job.user.name}]`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async startDeploy(to: string){
|
|
90
|
+
await this.send(`🚀 ${ this.title } <a href="${ this.$prj.job.url }">DEPLOY</a>: <b>${ to.toUpperCase() }</b> [${this.$prj.job.user.name}]`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async deploy(to: string){
|
|
94
|
+
await this.send(`\n\n🚀 <a href="${ this.$prj.job.url }">DEPLOY</a>: <b>${ to.toUpperCase() }</b>`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --------------
|
|
98
|
+
|
|
99
|
+
async fail(err: Error){
|
|
100
|
+
let msg = '\n';
|
|
101
|
+
if(this.$message_id === 0){
|
|
102
|
+
msg = `${ this.$prj.group.toUpperCase() }: <b>${ this.$prj.name.toUpperCase() } v${ this.$prj.version }</b>\n`;
|
|
103
|
+
}
|
|
104
|
+
msg += `‼️ <b><a href="${ this.$prj.job.url }">FAIL</a></b>\n${ htmlEntities(err.stack || err.message) }`;
|
|
105
|
+
await this.send(msg, ' ');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --------------
|
|
109
|
+
|
|
110
|
+
async release(changelog: string){
|
|
111
|
+
await this.send(`\n✅ RELEASED: <b>${ this.$ver }</b> ${ this.stage ? `to ${ this.stage }` : '' }`, ' ');
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
let msg = `🎉 ${ this.title } ${ this.stage ? ` [ ${ this.stage } ]` : `` }`;
|
|
115
|
+
|
|
116
|
+
if(changelog.length > 0){
|
|
117
|
+
msg += `
|
|
118
|
+
---------------------
|
|
119
|
+
${ changelog }`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if(this.$chat_rel !== 0){
|
|
123
|
+
// Дублируем сообщение в другой чат если указан
|
|
124
|
+
await this.send(msg, '', this.$chat_rel);
|
|
125
|
+
} else if(changelog.length > 0){
|
|
126
|
+
await this.send(msg, '', this.$chat_id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --------------
|
|
132
|
+
|
|
133
|
+
async send(text: string, appendExist = '...', chat_id_opt = this.$chat_id){
|
|
134
|
+
|
|
135
|
+
const chat_id = chat_id_opt;
|
|
136
|
+
const message_id = this.$message_id;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
if(message_id && message_id !== 0 && appendExist){
|
|
140
|
+
|
|
141
|
+
let fullMsg = this.$message_text + '\n' + text;
|
|
142
|
+
fullMsg = fullMsg.length < 4090 ? fullMsg : fullMsg.slice(0, 4090);
|
|
143
|
+
|
|
144
|
+
await this.$bot.editMessageText(fullMsg + '\n' + appendExist, {
|
|
145
|
+
chat_id, message_id, parse_mode:'HTML', disable_web_page_preview: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
this.$message_text = fullMsg;
|
|
149
|
+
return this.$message_id;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
text = text.length < 4090 ? text : text.slice(0, 4090);
|
|
153
|
+
|
|
154
|
+
const res = await this.$bot.sendMessage(chat_id, appendExist && appendExist.length > 0 ? text + '\n' + appendExist : text, { parse_mode: 'HTML', disable_web_page_preview: true });
|
|
155
|
+
|
|
156
|
+
if(appendExist){
|
|
157
|
+
this.$message_id = res.message_id;
|
|
158
|
+
this.$message_text = text;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return res.message_id;
|
|
162
|
+
|
|
163
|
+
} catch (err) {
|
|
164
|
+
err.message = 'ERROR Sending message\n' + err.message;
|
|
165
|
+
log.error(err.message);
|
|
166
|
+
log.error(err, { chat_id, message_id, text });
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// -------------
|
|
175
|
+
// Helpers:
|
|
176
|
+
|
|
177
|
+
function htmlEntities(rawStr: string){
|
|
178
|
+
return rawStr.replace(/[\u00A0-\u9999<>\&]/g, function(i) {
|
|
179
|
+
return '&#'+i.charCodeAt(0)+';';
|
|
180
|
+
});
|
|
181
|
+
}
|