@soft-artel/ci 1.2.15 → 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.
Files changed (88) hide show
  1. package/.env +21 -0
  2. package/.eslintcache +1 -0
  3. package/.eslintignore +4 -0
  4. package/.eslintrc +246 -0
  5. package/.gitlab-ci.yml +12 -0
  6. package/_publish.sh +24 -0
  7. package/k8s/App.ts +200 -0
  8. package/k8s/DockerImage.ts +147 -0
  9. package/k8s/Node.ts +119 -0
  10. package/k8s-build.ts +175 -0
  11. package/k8s-deploy.ts +174 -0
  12. package/libs/Exception.ts +19 -0
  13. package/libs/Git.ts +199 -0
  14. package/libs/Gitlab.ts +86 -0
  15. package/libs/Jira.ts +239 -0
  16. package/libs/Project.ts +215 -0
  17. package/libs/Reporter.ts +181 -0
  18. package/libs/Shell.ts +119 -0
  19. package/libs/helpers.ts +114 -0
  20. package/libs/prototype.ts +313 -0
  21. package/package.json +1 -1
  22. package/tsconfig.json +24 -0
  23. package/upd_pkg.ts +21 -0
  24. package/xcode.ts +226 -0
  25. package/k8s/App.d.ts +0 -22
  26. package/k8s/App.d.ts.map +0 -1
  27. package/k8s/App.js +0 -150
  28. package/k8s/App.js.map +0 -1
  29. package/k8s/DockerImage.d.ts +0 -15
  30. package/k8s/DockerImage.d.ts.map +0 -1
  31. package/k8s/DockerImage.js +0 -112
  32. package/k8s/DockerImage.js.map +0 -1
  33. package/k8s/Node.d.ts +0 -17
  34. package/k8s/Node.d.ts.map +0 -1
  35. package/k8s/Node.js +0 -103
  36. package/k8s/Node.js.map +0 -1
  37. package/k8s-build.d.ts +0 -3
  38. package/k8s-build.d.ts.map +0 -1
  39. package/k8s-build.js +0 -125
  40. package/k8s-build.js.map +0 -1
  41. package/k8s-deploy.d.ts +0 -3
  42. package/k8s-deploy.d.ts.map +0 -1
  43. package/k8s-deploy.js +0 -128
  44. package/k8s-deploy.js.map +0 -1
  45. package/libs/Exception.d.ts +0 -5
  46. package/libs/Exception.d.ts.map +0 -1
  47. package/libs/Exception.js +0 -13
  48. package/libs/Exception.js.map +0 -1
  49. package/libs/Git.d.ts +0 -44
  50. package/libs/Git.d.ts.map +0 -1
  51. package/libs/Git.js +0 -160
  52. package/libs/Git.js.map +0 -1
  53. package/libs/Gitlab.d.ts +0 -12
  54. package/libs/Gitlab.d.ts.map +0 -1
  55. package/libs/Gitlab.js +0 -78
  56. package/libs/Gitlab.js.map +0 -1
  57. package/libs/Jira.d.ts +0 -31
  58. package/libs/Jira.d.ts.map +0 -1
  59. package/libs/Jira.js +0 -157
  60. package/libs/Jira.js.map +0 -1
  61. package/libs/Project.d.ts +0 -39
  62. package/libs/Project.d.ts.map +0 -1
  63. package/libs/Project.js +0 -177
  64. package/libs/Project.js.map +0 -1
  65. package/libs/Reporter.d.ts +0 -34
  66. package/libs/Reporter.d.ts.map +0 -1
  67. package/libs/Reporter.js +0 -129
  68. package/libs/Reporter.js.map +0 -1
  69. package/libs/Shell.d.ts +0 -39
  70. package/libs/Shell.d.ts.map +0 -1
  71. package/libs/Shell.js +0 -107
  72. package/libs/Shell.js.map +0 -1
  73. package/libs/helpers.d.ts +0 -29
  74. package/libs/helpers.d.ts.map +0 -1
  75. package/libs/helpers.js +0 -101
  76. package/libs/helpers.js.map +0 -1
  77. package/libs/prototype.d.ts +0 -9
  78. package/libs/prototype.d.ts.map +0 -1
  79. package/libs/prototype.js +0 -186
  80. package/libs/prototype.js.map +0 -1
  81. package/upd_pkg.d.ts +0 -3
  82. package/upd_pkg.d.ts.map +0 -1
  83. package/upd_pkg.js +0 -28
  84. package/upd_pkg.js.map +0 -1
  85. package/xcode.d.ts +0 -3
  86. package/xcode.d.ts.map +0 -1
  87. package/xcode.js +0 -163
  88. package/xcode.js.map +0 -1
package/k8s/Node.ts ADDED
@@ -0,0 +1,119 @@
1
+ import '../libs/prototype';
2
+
3
+ import yaml from 'js-yaml';
4
+
5
+ import Shell, { ExecOptions } from '../libs/Shell';
6
+ import { log, resolvePath } from '../libs/helpers';
7
+ import { Stage } from '../libs/Project';
8
+ import { Exception } from '../libs/Exception';
9
+ import { Reporter } from '../libs/Reporter';
10
+
11
+ const ENV = process.env;
12
+
13
+
14
+ // ================================================================================================
15
+
16
+ export class Node{
17
+
18
+ configFile = '';
19
+ namespace = '';
20
+
21
+ // ----------
22
+
23
+ constructor(stage: Stage){
24
+ this.namespace = stage;
25
+
26
+ const configKey = `K8S_${ stage.toUpperCase() }_CONFIG`;
27
+ let configFile = ENV[ configKey ];
28
+
29
+ if(stage !== 'prod' && !configFile){
30
+ configFile = ENV.K8S_STAGES_CONFIG;
31
+ }
32
+
33
+ if(!configFile){
34
+ throw new Exception(`K8s config file not found: ${ configKey }`);
35
+ }
36
+
37
+ this.configFile = resolvePath(configFile) ;
38
+ }
39
+
40
+ // ----------
41
+
42
+ async applyObject(manifestObj: Record<string, any>){
43
+ const manifestYaml = yaml.dump(manifestObj);
44
+ await this.exec(`apply -f -\n${ manifestYaml }`, {silent: false}, 'cat <<EOF | ');
45
+ }
46
+
47
+ // ----------
48
+
49
+ async apply(manifestFile: string, appName: string): Promise<{valid: boolean; msg: string}>{
50
+
51
+ let status: string;
52
+
53
+ try{
54
+ await this.exec(`apply -f ${ manifestFile } --dry-run=server --validate`);
55
+ status = (await Shell.exec(`cat ${ manifestFile } | grep 'image:'`)).replace('image: ', '').replace(ENV.CI_REGISTRY_IMAGE+'/', '').trim();
56
+ }catch(e){
57
+ log.error(e);
58
+ log.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
59
+ log.error(`VALIDATE FAIL: ${appName} at ${manifestFile}`);
60
+ log.error(e.message);
61
+ log.error(`${ await Shell.cat(manifestFile) }`);
62
+ log.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
63
+
64
+ return { valid:false, msg: e.message };
65
+ }
66
+
67
+ try{
68
+ status = await this.exec(`apply -f ${ manifestFile }`);
69
+ await this.exec(`rollout status deploy/${ appName } --watch=true --timeout=300s`);
70
+
71
+ log.info('+++++++++++++++++++++++++++++');
72
+ log.info(`DEPLOYED: ${appName}`);
73
+ log.info(await this.exec(`get pods --no-headers -l app=${appName} -o wide`, { ignoreError:true, silent:true }));
74
+ log.info('+++++++++++++++++++++++++++++');
75
+
76
+ return { valid:true, msg: status };
77
+
78
+ } catch(error){
79
+ log.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
80
+ log.error(`DEPLOY FAIL: ${appName}`);
81
+ log.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
82
+ log.error(await this.exec(`get pods --no-headers -l app=${appName} -o wide`, { ignoreError:true, silent:true }));
83
+ throw error;
84
+ }
85
+
86
+ }
87
+
88
+ // ----------
89
+
90
+ async status(reporter: Reporter | undefined = undefined, appsOnly: boolean = true): Promise<string[]>{
91
+
92
+ const cmd = `get pods -o=jsonpath='{range .items[*]}{"\\n"}{"<b>"}{.metadata.labels.app}{"</b> "}{range .spec.containers[*]}{.image}{end}{" "}{.metadata.labels.pod-template-hash}{" "}{.status.phase}{end}'`;
93
+ const statusArr = (await this.exec(cmd)).split('\n');
94
+
95
+ const result: string[] = [];
96
+ for (const row of statusArr) {
97
+ if(row.length < 4 || (appsOnly && (RegExp((ENV.CI_REGISTRY_IMAGE || '')).exec(row)) === null)){
98
+ continue;
99
+ }
100
+ result.push(row.replace(ENV.CI_REGISTRY_IMAGE+'/', ''));
101
+ }
102
+
103
+ log.info(`K8S Node: ${ this.namespace.toUpperCase() }`, result);
104
+
105
+ if(reporter){
106
+ const msg = `\n<b>${ this.namespace.toUpperCase() }</b> Pods:\n-----------------\n ` + result.join('\n-----------------\n ');
107
+ await reporter.send(msg, '');
108
+ }
109
+
110
+ return result;
111
+ }
112
+
113
+ // ----------
114
+
115
+ async exec(cmd: string, opt: ExecOptions | undefined = undefined, cmdPrefix = ''): Promise<string>{
116
+ return Shell.exec(`${cmdPrefix}kubectl --kubeconfig ${ this.configFile } -n ${ this.namespace } ${ cmd }`, opt);
117
+ }
118
+
119
+ }
package/k8s-build.ts ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ import './libs/prototype';
3
+
4
+ import {program, OptionValues} from 'commander';
5
+
6
+ import Shell from './libs/Shell';
7
+ import { Jira } from './libs/Jira';
8
+ import Git from './libs/Git';
9
+ import { Project } from './libs/Project';
10
+ import { Reporter } from './libs/Reporter';
11
+ import { log, checkEnvVars } from './libs/helpers';
12
+ import { DockerImage } from './k8s/DockerImage';
13
+ import { Config, getApps } from './k8s/App';
14
+
15
+ const ENV = process.env;
16
+
17
+ // ========================================================
18
+
19
+
20
+ async function main() {
21
+
22
+ program
23
+ .option('--path <project dir>', 'Path to project directory [default - current]')
24
+
25
+ .option('--no-prebuild', 'Skip prebuild phase')
26
+
27
+ .option('--no-changelog', 'Skip create gitlog')
28
+ .option('--no-gittag', 'Skip create and push new git tag')
29
+ .action(
30
+ async () => {
31
+
32
+ let reporter: Reporter | undefined;
33
+
34
+ try {
35
+
36
+ // ------------------
37
+ // Инициализируемся
38
+ checkEnvVars(['CI_STAGES', 'REPORTER']);
39
+
40
+ const opts = program.opts() || {};
41
+ const prj = new Project(opts.path);
42
+
43
+ const reporterOpts = JSON.parse(ENV.REPORTER!);
44
+ reporterOpts.postTitle = opts.scheme;
45
+
46
+ const pkg = JSON.parse(await Shell.cat(prj.rootPath +'/package.json') || 'no package.json file!');
47
+ const pkgBuild = Number(pkg.version.split('.')[2]);
48
+
49
+ prj.build = prj.build < pkgBuild ? pkgBuild : prj.build;
50
+ prj.version = `${ pkg.version.split('.').splice(0, 2).join('.') }.${ prj.build }`;
51
+
52
+ if(!pkg.ci){
53
+ throw new Error('No ci config in package.json file!');
54
+ }
55
+
56
+ reporter = new Reporter(prj, reporterOpts);
57
+
58
+ const config: Config = pkg.ci;
59
+
60
+ // Запускаем команду
61
+ await run(prj, reporter, opts, config);
62
+
63
+ } catch(e){
64
+
65
+ log.error(e);
66
+
67
+ if(reporter){
68
+ try {
69
+ await reporter.fail(e);
70
+ } catch(e2){
71
+ log.error(e2);
72
+ }
73
+
74
+ }
75
+
76
+ process.exit(1);
77
+ }
78
+ }
79
+ );
80
+
81
+ await program.parseAsync(process.argv);
82
+ }
83
+ main();
84
+
85
+ // ============================================
86
+
87
+ async function run(prj: Project, reporter: Reporter, opts: OptionValues, config: Config) {
88
+
89
+ // ------------------
90
+ // Инициализируемся
91
+
92
+ const release = opts.changelog ? await Jira.resolve(prj) : { tasks:[], bugs:[], changelog:''};
93
+
94
+ // --build: Build and push docker image to registry
95
+ Shell.cd(prj.rootPath);
96
+
97
+ // -----------------------------
98
+ // 1. Start
99
+ prj.incrementBuild();
100
+ await reporter.startBuild();
101
+ log.info(`1. build ${ prj.stage }`);
102
+
103
+ // -----------------------------
104
+ // 2. Chek updated apps
105
+ await reporter.send(` + Load apps and check updated`);
106
+ log.info(`2. Load apps and check updated`);
107
+
108
+ const updateFiles = await Git.getDiffFiles();
109
+ log.info(`\n\nUPDATED FILES:\n$`, updateFiles);
110
+
111
+ const { apps, updated } = await getApps(prj, config, updateFiles);
112
+
113
+ if(!updated){
114
+ log.info('SKIP BUILD: NO Apps to update!');
115
+ await reporter.send(`⏩ SKIP - no apps to update\n ${ updateFiles.join('\n ')}`, ' ');
116
+ process.exit(0);
117
+ }
118
+
119
+ await reporter.send(` ~ <b>${ updated.join('</b>\n ~ <b>') }</b>`);
120
+
121
+ // -----------------------------
122
+ // 4. write k8s manifests!
123
+ await reporter.send(` + Write K8s manifests`);
124
+ log.info(`3. Write K8s manifests`);
125
+
126
+ const image = new DockerImage(prj.rootPath, prj.shortVersion, prj.build);
127
+ log.dbg(`DockerImage`, image);
128
+
129
+ for (const appName of updated) {
130
+ await apps[ appName ]?.writeManifest(prj, image);
131
+ }
132
+
133
+ // -----------------------------
134
+ // 5. Build
135
+ if(opts.prebuild){
136
+ await reporter.send(` + Test and distrib`);
137
+ log.dbg(`4. Pre build`);
138
+ await image.prebuild();
139
+ }
140
+
141
+ await reporter.send(` + Build docker image: ${ image.tag }`);
142
+ log.dbg(`5. Build`);
143
+ await image.build();
144
+
145
+ await reporter.send(` + Push docker image to registry`);
146
+ log.dbg(`6. Docker push`);
147
+ await image.push();
148
+
149
+ // -----------------------------
150
+ // 5. Save new build to GitLab CI Variable
151
+
152
+ await reporter.send(` + Save current build: ${ prj.build }`);
153
+ log.dbg(`7. Put current build:${ prj.build } to GitLab`);
154
+ await prj.saveGitLabBuild();
155
+
156
+ // -----------------------
157
+
158
+
159
+ if(opts.changelog){
160
+ await prj.updateChangeLog(release.changelog);
161
+ await reporter.send(` + Update <a href=\"${ prj.url }/-/blob/${ ENV.CI_COMMIT_REF_NAME || 'dev' }/CHANGELOG.md\">changelog</a>.`);
162
+ }
163
+
164
+ await Git.setOrigin();
165
+ await Shell.exec(`git add -A`, { ignoreError: true, silent: false });
166
+ await Shell.exec(`git commit -a -m "v${ prj.version }"`, { ignoreError: true, silent: false });
167
+ await Git.push(ENV.CI_COMMIT_REF_NAME || 'dev');
168
+
169
+ if(opts.gittag){
170
+ await reporter.send(` + Git tag new version`);
171
+ await Git.makeTag(prj.version);
172
+ }
173
+
174
+ await reporter.send(`\n✅ <b>${ reporter.title }</b> builded!`, ' ');
175
+ }
package/k8s-deploy.ts ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ import './libs/prototype';
3
+
4
+ import {program, OptionValues} from 'commander';
5
+
6
+ import Shell from './libs/Shell';
7
+ import { Jira } from './libs/Jira';
8
+ import Git from './libs/Git';
9
+ import { Project, Stage } from './libs/Project';
10
+ import { Reporter } from './libs/Reporter';
11
+ import { log, checkEnvVars } from './libs/helpers';
12
+ import { DockerImage } from './k8s/DockerImage';
13
+ import { Node } from './k8s/Node';
14
+ import { App, Config, getApps } from './k8s/App';
15
+
16
+ const ENV = process.env;
17
+
18
+ // ========================================================
19
+
20
+
21
+ async function main() {
22
+
23
+ program
24
+ .option('--path <project dir>', 'Path to project directory [default - current]')
25
+ .option('--stage <stage>', 'Deploy manifests to K8s <stage>')
26
+
27
+ .option('--no-changelog', 'Skip create gitlog')
28
+ .option('--no-gitpush', 'Skip pushing to stage branch after deploy')
29
+
30
+ .option('--debug', 'Output extra debugging info')
31
+ .action(
32
+ async () => {
33
+
34
+ let reporter: Reporter | undefined;
35
+
36
+ try {
37
+
38
+ // ------------------
39
+ // Инициализируемся
40
+ checkEnvVars(['CI_STAGES', 'REPORTER']);
41
+
42
+ const opts = program.opts() || {};
43
+ const prj = new Project(opts.path);
44
+
45
+ const reporterOpts = JSON.parse(ENV.REPORTER!);
46
+ reporterOpts.postTitle = opts.scheme;
47
+
48
+ const pkg = JSON.parse(await Shell.cat(prj.rootPath +'/package.json') || 'no package.json file!');
49
+ const pkgBuild = Number(pkg.version.split('.')[2]);
50
+
51
+ prj.build = prj.build < pkgBuild ? pkgBuild : prj.build;
52
+ prj.version = `${ pkg.version.split('.').splice(0, 2).join('.') }.${ prj.build }`;
53
+
54
+ if(!pkg.ci){
55
+ throw new Error('No ci config in package.json file!');
56
+ }
57
+
58
+ reporter = new Reporter(prj, reporterOpts);
59
+
60
+ const config: Config = pkg.ci;
61
+
62
+ // Запускаем команду
63
+ await run(prj, reporter, opts, config);
64
+
65
+ } catch(e){
66
+
67
+ log.error(e);
68
+
69
+ if(reporter){
70
+ try {
71
+ await reporter.fail(e);
72
+ } catch(e2){
73
+ log.error(e2);
74
+ }
75
+
76
+ }
77
+
78
+ process.exit(1);
79
+ }
80
+ }
81
+ );
82
+
83
+ await program.parseAsync(process.argv);
84
+ }
85
+ main();
86
+
87
+ // ============================================
88
+
89
+ async function run(prj: Project, reporter: Reporter, opts: OptionValues) {
90
+
91
+ // ------------------
92
+ // Инициализируемся
93
+
94
+ const release = opts.changelog ? await Jira.resolve(prj) : { tasks:[], bugs:[], changelog:''};
95
+
96
+ // 1. Меняем стейдж на новый, потому что деплоим в него!
97
+ const stage = opts.stage;
98
+
99
+ prj.stage = stage;
100
+ await reporter.startDeploy(stage);
101
+
102
+ const node = new Node(stage);
103
+
104
+ // 2. Получаем путь
105
+ const k8s_dir = `${ prj.rootPath }/_k8s/${stage}/`;
106
+ Shell.cd(k8s_dir);
107
+
108
+ // 3. Обходим ВСЕ файлы - те что не поменялись не применятся
109
+ const deployedApps: Record<string, string> = {};
110
+ const k8s_files = await Shell.ls(k8s_dir);
111
+
112
+ try{
113
+
114
+ // Деплоим все(!) аппы - те что не поменялись не применятся
115
+ for (const k8s_file of k8s_files) {
116
+
117
+ const appName = k8s_file.replace('.yml', '');
118
+ const status = await node.apply(`${ k8s_dir }/${k8s_file}`, appName);
119
+
120
+ let msg = '';
121
+ if(status.valid){
122
+ deployedApps[ appName ] = status.msg;
123
+ msg = ` + <b>${appName}</b>`;
124
+ await reporter.send(msg);
125
+ } else {
126
+ msg = ` ❌ <b>${appName}</b> yaml is invalid!\n${ status.msg }`;
127
+ await reporter.send(msg);
128
+ throw new Error(`FAIL: Deployment app: ${appName}`);
129
+ }
130
+
131
+ }
132
+
133
+ await node.status(reporter);
134
+
135
+ } catch(error){
136
+
137
+ // Деплой сфейлился - надо откатыватся
138
+ if(Object.keys(deployedApps).length > 0){
139
+
140
+ log.error('FAIL: Deployments to rollout', Object.keys(deployedApps));
141
+ await reporter.send(` 🚫 <b>APPLAY FAIL</b>: Rollout all deployed apps`);
142
+
143
+ for (const appName of Object.keys(deployedApps)) {
144
+ await node.exec(`rollout undo deployment/${ appName }`);
145
+ }
146
+
147
+ }
148
+
149
+ await node.status(reporter);
150
+ throw error;
151
+ }
152
+
153
+ // deploy succsess, push changes to stage brunch
154
+ if(opts.gitpush){
155
+ await Git.push( stage );
156
+ }
157
+
158
+ await reporter.send(` + Save ${ prj.stage} -> ${ prj.version} to GitLab`);
159
+ await prj.saveGitLabStagesVersions();
160
+
161
+ if(opts.changelog){
162
+ await reporter.send(` + Resolved tasks:${ release.tasks.length }, bugs: ${ release.bugs.length }`);
163
+
164
+ const bugsDeployed = await Jira.moveBugsAsDeployed(release.bugs);
165
+ if(bugsDeployed.length > 0){
166
+ await reporter.send(` + Deployed ${ bugsDeployed.length } bugs.`);
167
+ } else {
168
+ await reporter.send(` - Deploy bugs. [SKIPED]`);
169
+ }
170
+ }
171
+
172
+ await reporter.release(release.changelog);
173
+
174
+ }
@@ -0,0 +1,19 @@
1
+ export class Exception extends Error {
2
+ params?: Record<string, any>;
3
+
4
+ // -------------
5
+
6
+ constructor(
7
+ message: string,
8
+ params?: Record<string, any>) {
9
+ super();
10
+
11
+ this.message = message;
12
+ this.params = params;
13
+
14
+ /**
15
+ * @see https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
16
+ */
17
+ Object.setPrototypeOf(this, Exception.prototype);
18
+ }
19
+ }
package/libs/Git.ts ADDED
@@ -0,0 +1,199 @@
1
+ import Shell from './Shell';
2
+
3
+ const ENV = process.env;
4
+
5
+
6
+ // Can be redifined in ENV
7
+ // ------------------------
8
+
9
+ const GITLAB_PASS = ENV.GITLAB_PASS;
10
+
11
+ // ===========================================
12
+
13
+ class Commit{
14
+
15
+ date = new Date(ENV.CI_COMMIT_TIMESTAMP || 0 * 1);
16
+ sha = ENV.CI_COMMIT_SHA || '';
17
+ msg = ENV.CI_COMMIT_MESSAGE || '';
18
+ name = ENV.GITLAB_USER_NAME || '';
19
+ email = ENV.CI_COMMIT_AUTHOR || '';
20
+
21
+ constructor(row: string, delimetr: string){
22
+ if(!row || !delimetr){
23
+ return;
24
+ }
25
+
26
+ const cols = row.split(delimetr);
27
+
28
+ this.date = new Date(Date.parse(cols[3]));
29
+ this.sha = cols[0];
30
+ this.email = cols[2];
31
+ this.name = cols[1];
32
+ this.msg = cols[4];
33
+ }
34
+
35
+ get issuesKeys(){
36
+ const matchedIssues = this.msg.match(/[A-Z0-9]+-\d+/g);
37
+ return !matchedIssues || matchedIssues.length === 0 ? [] : matchedIssues;
38
+ }
39
+
40
+ }
41
+
42
+
43
+ // ----------------------
44
+
45
+ async function setOrigin(opts: { url?: string; jobId?: number} = {}){
46
+ if(!opts.jobId && !ENV.CI_JOB_ID){
47
+ // setOrigin ONLY in GitLab CI Jobs runner, as they are detached!
48
+ return;
49
+ }
50
+
51
+ let url = opts.url || ENV.CI_PROJECT_URL || '';
52
+
53
+ url = url.replace('https://', `https://'gitlab':${ GITLAB_PASS }@`);
54
+ await Shell.exec(`git remote set-url --push origin ${ url }.git`);
55
+ await Shell.exec(`git remote set-url origin ${ url }.git`);
56
+
57
+ }
58
+
59
+ // -----------------------------------------
60
+
61
+ async function push(branch: string){
62
+ await Shell.exec(`git pull --ff origin ${ branch }`);
63
+ await Shell.execRepeat(`git push origin HEAD:${ branch }`);
64
+ }
65
+
66
+ // ----------------------
67
+
68
+ async function makeTag(version: string){
69
+ const tag = `v${version}`;
70
+ await Shell.exec(`git tag --force "${ tag }"`);
71
+ await Shell.execRepeat(`git push --force origin ${ tag }`);
72
+ }
73
+
74
+ // ----------------------
75
+
76
+ async function getTags(limit = 1000, mask = /v[\d]+\.[\d]+\.[\d]+$/){
77
+ await Shell.exec(`git fetch -t`, { ignoreError: true });
78
+
79
+ const tagsStr = await Shell.exec('git tag | sort --version-sort', {silent: true, ignoreError:true});
80
+
81
+ let list = tagsStr.split('\n').filter((f)=>f.length > 2);
82
+ list = list.length > 0 ? list.reverse() : [tagsStr];
83
+
84
+ const tags = [];
85
+
86
+ let i = 1;
87
+ for (const tag of list) {
88
+ if(tag === '' || mask && (mask.exec(tag))=== null){
89
+ continue;
90
+ }
91
+ tags.push(tag);
92
+ i++;
93
+ if(i >= limit){
94
+ break;
95
+ }
96
+ }
97
+
98
+ return tags;
99
+ }
100
+
101
+ // -----------------------------------------
102
+
103
+ async function lastTag(){
104
+ const list = await getTags(1);
105
+ if(list && Array.isArray(list) && list.length > 0){
106
+ return list[0];
107
+ }
108
+ return undefined;
109
+ }
110
+
111
+ // -----------------------------------------
112
+
113
+ async function existTag(version: string){
114
+ const list = await getTags(100);
115
+
116
+ if(list && Array.isArray(list) && list.includes(`v${ version }`)){
117
+ return `v${ version }`;
118
+ }
119
+
120
+ return undefined;
121
+ }
122
+
123
+ // -----------------------------------------
124
+
125
+ async function getCommitsAndIssues(opt: { onlyJira?: boolean; limit?: number; fromTag: string; toTag: string}){
126
+
127
+ opt.onlyJira = opt.onlyJira ?? true;
128
+ opt.limit = opt.limit ?? 10;
129
+
130
+ const result: { commits: Record<string, any>; issues: Record<string, any> } = { commits: {}, issues: {} };
131
+
132
+ const d = '#!|!#';
133
+
134
+ const from = opt.fromTag;
135
+ const to = opt.toTag || '';
136
+
137
+ const par = `${from}..${ from !== '' && to === '' ? 'HEAD' : to }`;
138
+
139
+ const commits = await Shell.exec(`git log ${ par !== '..' ? par : '' } --pretty='format:%H${d}%cN${d}%cE${d}%ci${d}%s' --max-count=${ opt.limit }`, { silent: true });
140
+
141
+ if(!commits || commits.includes(d) === false){
142
+ return result;
143
+ }
144
+
145
+ let arr = commits.split('\n');
146
+ if(arr.length === 0){
147
+ arr = [commits];
148
+ }
149
+
150
+ for (const row of arr) {
151
+
152
+ const commit = new Commit(row, d);
153
+ const issuesKeys = commit.issuesKeys;
154
+
155
+ if(opt.onlyJira && issuesKeys.length === 0){
156
+ continue;
157
+ }
158
+
159
+ result.commits[ commit.sha ] = commit;
160
+
161
+ for (const key of issuesKeys) {
162
+ const issue = result.issues[ key ] || [];
163
+ issue.push(commit.sha);
164
+ result.issues[ key ] = issue;
165
+ }
166
+ }
167
+
168
+ return result;
169
+ }
170
+
171
+ // -----------------------------------------
172
+
173
+ async function getDiffFiles(fromTag?: string, opt = { silent: true, ignoreError: true }){
174
+
175
+ const from = fromTag || await lastTag();
176
+ const diffCmd = from && from !== '' ? `git diff --name-only HEAD ${ from }` : "find . -not -path '*/_*' -not -path '*/\.*' -not -path '*/node_modules*' -type d -maxdepth 2";
177
+ const diffStr = await Shell.exec(diffCmd, opt) || '';
178
+
179
+ let arr = diffStr.split('\n');
180
+ if(arr.length === 0){
181
+ arr = [diffStr];
182
+ }
183
+
184
+ return arr.filter((f)=>f !== '').sort();
185
+ }
186
+
187
+ // ------------------------
188
+
189
+ export default {
190
+ Commit,
191
+ setOrigin,
192
+ push,
193
+ makeTag,
194
+ lastTag,
195
+ existTag,
196
+ getTags,
197
+ getCommitsAndIssues,
198
+ getDiffFiles,
199
+ };